1
|
|
|
import abc |
2
|
|
|
import math |
3
|
|
|
import os |
4
|
|
|
import re |
5
|
|
|
import stat |
6
|
|
|
from typing import Dict, List, Optional, Tuple, Union |
7
|
|
|
|
8
|
|
|
from pystratum_backend.StratumStyle import StratumStyle |
9
|
|
|
|
10
|
|
|
from pystratum_common.DocBlockReflection import DocBlockReflection |
11
|
|
|
from pystratum_common.exception.LoaderException import LoaderException |
12
|
|
|
from pystratum_common.helper.DataTypeHelper import DataTypeHelper |
13
|
|
|
|
14
|
|
|
|
15
|
|
View Code Duplication |
class RoutineLoaderHelper(metaclass=abc.ABCMeta): |
|
|
|
|
16
|
|
|
""" |
17
|
|
|
Class for loading a single stored routine into a RDBMS instance from a (pseudo) SQL file. |
18
|
|
|
""" |
19
|
|
|
|
20
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
21
|
|
|
def __init__(self, |
22
|
|
|
io: StratumStyle, |
23
|
|
|
routine_filename: str, |
24
|
|
|
routine_file_encoding: str, |
25
|
|
|
pystratum_old_metadata: Dict, |
26
|
|
|
replace_pairs: Dict[str, str], |
27
|
|
|
rdbms_old_metadata: Dict): |
28
|
|
|
""" |
29
|
|
|
Object constructor. |
30
|
|
|
|
31
|
|
|
:param PyStratumStyle io: The output decorator. |
32
|
|
|
:param str routine_filename: The filename of the source of the stored routine. |
33
|
|
|
:param str routine_file_encoding: The encoding of the source file. |
34
|
|
|
:param dict pystratum_old_metadata: The metadata of the stored routine from PyStratum. |
35
|
|
|
:param dict[str,str] replace_pairs: A map from placeholders to their actual values. |
36
|
|
|
:param dict rdbms_old_metadata: The old metadata of the stored routine from the RDBMS instance. |
37
|
|
|
""" |
38
|
|
|
self._source_filename: str = routine_filename |
39
|
|
|
""" |
40
|
|
|
The source filename holding the stored routine. |
41
|
|
|
""" |
42
|
|
|
|
43
|
|
|
self._routine_file_encoding: str = routine_file_encoding |
44
|
|
|
""" |
45
|
|
|
The encoding of the routine file. |
46
|
|
|
""" |
47
|
|
|
|
48
|
|
|
self._pystratum_old_metadata: Dict = pystratum_old_metadata |
49
|
|
|
""" |
50
|
|
|
The old metadata of the stored routine. Note: this data comes from the metadata file. |
51
|
|
|
""" |
52
|
|
|
|
53
|
|
|
self._pystratum_metadata: Dict = {} |
54
|
|
|
""" |
55
|
|
|
The metadata of the stored routine. Note: this data is stored in the metadata file and is generated by |
56
|
|
|
pyStratum. |
57
|
|
|
""" |
58
|
|
|
|
59
|
|
|
self._replace_pairs: Dict[str, str] = replace_pairs |
60
|
|
|
""" |
61
|
|
|
A map from placeholders to their actual values. |
62
|
|
|
""" |
63
|
|
|
|
64
|
|
|
self._rdbms_old_metadata: Dict = rdbms_old_metadata |
65
|
|
|
""" |
66
|
|
|
The old information about the stored routine. Note: this data comes from the metadata of the RDBMS instance. |
67
|
|
|
""" |
68
|
|
|
|
69
|
|
|
self._m_time: int = 0 |
70
|
|
|
""" |
71
|
|
|
The last modification time of the source file. |
72
|
|
|
""" |
73
|
|
|
|
74
|
|
|
self._routine_name: Optional[str] = None |
75
|
|
|
""" |
76
|
|
|
The name of the stored routine. |
77
|
|
|
""" |
78
|
|
|
|
79
|
|
|
self._routine_source_code: Optional[str] = None |
80
|
|
|
""" |
81
|
|
|
The source code as a single string of the stored routine. |
82
|
|
|
""" |
83
|
|
|
|
84
|
|
|
self._routine_source_code_lines: List[str] = [] |
85
|
|
|
""" |
86
|
|
|
The source code as an array of lines string of the stored routine. |
87
|
|
|
""" |
88
|
|
|
|
89
|
|
|
self._replace: Dict = {} |
90
|
|
|
""" |
91
|
|
|
The replace pairs (i.e. placeholders and their actual values). |
92
|
|
|
""" |
93
|
|
|
|
94
|
|
|
self._routine_type: Optional[str] = None |
95
|
|
|
""" |
96
|
|
|
The stored routine type (i.e. procedure or function) of the stored routine. |
97
|
|
|
""" |
98
|
|
|
|
99
|
|
|
self._designation_type: Optional[str] = None |
100
|
|
|
""" |
101
|
|
|
The designation type of the stored routine. |
102
|
|
|
""" |
103
|
|
|
|
104
|
|
|
self._doc_block_parts_source: Dict = dict() |
105
|
|
|
""" |
106
|
|
|
All DocBlock parts as found in the source of the stored routine. |
107
|
|
|
""" |
108
|
|
|
|
109
|
|
|
self._doc_block_parts_wrapper: Dict = dict() |
110
|
|
|
""" |
111
|
|
|
The DocBlock parts to be used by the wrapper generator. |
112
|
|
|
""" |
113
|
|
|
|
114
|
|
|
self._columns_types: Optional[List] = None |
115
|
|
|
""" |
116
|
|
|
The column types of columns of the table for bulk insert of the stored routine. |
117
|
|
|
""" |
118
|
|
|
|
119
|
|
|
self._fields: Optional[List] = None |
120
|
|
|
""" |
121
|
|
|
The keys in the dictionary for bulk insert. |
122
|
|
|
""" |
123
|
|
|
|
124
|
|
|
self._parameters: List[Dict] = [] |
125
|
|
|
""" |
126
|
|
|
The information about the parameters of the stored routine. |
127
|
|
|
""" |
128
|
|
|
|
129
|
|
|
self._table_name: Optional[str] = None |
130
|
|
|
""" |
131
|
|
|
If designation type is bulk_insert the table name for bulk insert. |
132
|
|
|
""" |
133
|
|
|
|
134
|
|
|
self._columns: Optional[List] = None |
135
|
|
|
""" |
136
|
|
|
The key or index columns (depending on the designation type) of the stored routine. |
137
|
|
|
""" |
138
|
|
|
|
139
|
|
|
self._io: StratumStyle = io |
140
|
|
|
""" |
141
|
|
|
The output decorator. |
142
|
|
|
""" |
143
|
|
|
|
144
|
|
|
self.shadow_directory: Optional[str] = None |
145
|
|
|
""" |
146
|
|
|
The name of the directory were copies with pure SQL of the stored routine sources must be stored. |
147
|
|
|
""" |
148
|
|
|
|
149
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
150
|
|
|
def load_stored_routine(self) -> Union[Dict[str, str], bool]: |
151
|
|
|
""" |
152
|
|
|
Loads the stored routine into the instance of MySQL. |
153
|
|
|
|
154
|
|
|
Returns the metadata of the stored routine if the stored routine is loaded successfully. Otherwise returns |
155
|
|
|
False. |
156
|
|
|
|
157
|
|
|
:rtype: dict[str,str]|bool |
158
|
|
|
""" |
159
|
|
|
try: |
160
|
|
|
self._routine_name = os.path.splitext(os.path.basename(self._source_filename))[0] |
161
|
|
|
|
162
|
|
|
if os.path.exists(self._source_filename): |
163
|
|
|
if os.path.isfile(self._source_filename): |
164
|
|
|
self._m_time = int(os.path.getmtime(self._source_filename)) |
165
|
|
|
else: |
166
|
|
|
raise LoaderException("Unable to get mtime of file '{}'".format(self._source_filename)) |
167
|
|
|
else: |
168
|
|
|
raise LoaderException("Source file '{}' does not exist".format(self._source_filename)) |
169
|
|
|
|
170
|
|
|
if self._pystratum_old_metadata: |
171
|
|
|
self._pystratum_metadata = self._pystratum_old_metadata |
172
|
|
|
|
173
|
|
|
load = self._must_reload() |
174
|
|
|
if load: |
175
|
|
|
self.__read_source_file() |
176
|
|
|
self.__get_placeholders() |
177
|
|
|
self._get_designation_type() |
178
|
|
|
self._get_name() |
179
|
|
|
self.__substitute_replace_pairs() |
180
|
|
|
self._load_routine_file() |
181
|
|
|
if self._designation_type == 'bulk_insert': |
182
|
|
|
self._get_bulk_insert_table_columns_info() |
183
|
|
|
self._get_routine_parameters_info() |
184
|
|
|
self.__get_doc_block_parts_wrapper() |
185
|
|
|
self.__save_shadow_copy() |
186
|
|
|
self.__validate_parameter_lists() |
187
|
|
|
self._update_metadata() |
188
|
|
|
|
189
|
|
|
return self._pystratum_metadata |
190
|
|
|
|
191
|
|
|
except Exception as exception: |
192
|
|
|
self._log_exception(exception) |
193
|
|
|
return False |
194
|
|
|
|
195
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
196
|
|
|
def __validate_parameter_lists(self) -> None: |
197
|
|
|
""" |
198
|
|
|
Validates the parameters found the DocBlock in the source of the stored routine against the parameters from the |
199
|
|
|
metadata of MySQL and reports missing and unknown parameters names. |
200
|
|
|
""" |
201
|
|
|
# Make list with names of parameters used in database. |
202
|
|
|
database_parameters_names = [] |
203
|
|
|
for parameter in self._parameters: |
204
|
|
|
database_parameters_names.append(parameter['name']) |
205
|
|
|
|
206
|
|
|
# Make list with names of parameters used in dock block of routine. |
207
|
|
|
doc_block_parameters_names = [] |
208
|
|
|
if 'parameters' in self._doc_block_parts_source: |
209
|
|
|
for parameter in self._doc_block_parts_source['parameters']: |
210
|
|
|
doc_block_parameters_names.append(parameter['name']) |
211
|
|
|
|
212
|
|
|
# Check and show warning if any parameters is missing in doc block. |
213
|
|
|
for parameter in database_parameters_names: |
214
|
|
|
if parameter not in doc_block_parameters_names: |
215
|
|
|
self._io.warning('Parameter {} is missing in doc block'.format(parameter)) |
216
|
|
|
|
217
|
|
|
# Check and show warning if find unknown parameters in doc block. |
218
|
|
|
for parameter in doc_block_parameters_names: |
219
|
|
|
if parameter not in database_parameters_names: |
220
|
|
|
self._io.warning('Unknown parameter {} found in doc block'.format(parameter)) |
221
|
|
|
|
222
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
223
|
|
|
def __read_source_file(self) -> None: |
224
|
|
|
""" |
225
|
|
|
Reads the file with the source of the stored routine. |
226
|
|
|
""" |
227
|
|
|
with open(self._source_filename, 'r', encoding=self._routine_file_encoding) as file: |
228
|
|
|
self._routine_source_code = file.read() |
229
|
|
|
|
230
|
|
|
self._routine_source_code_lines = self._routine_source_code.split("\n") |
231
|
|
|
|
232
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
233
|
|
|
def __save_shadow_copy(self) -> None: |
234
|
|
|
""" |
235
|
|
|
Saves a copy of the stored routine source with pure SQL (if shadow directory is set). |
236
|
|
|
""" |
237
|
|
|
if not self.shadow_directory: |
238
|
|
|
return |
239
|
|
|
|
240
|
|
|
destination_filename = os.path.join(self.shadow_directory, self._routine_name) + '.sql' |
241
|
|
|
|
242
|
|
|
if os.path.realpath(destination_filename) == os.path.realpath(self._source_filename): |
243
|
|
|
raise LoaderException("Shadow copy will override routine source '{}'".format(self._source_filename)) |
244
|
|
|
|
245
|
|
|
# Remove the (read only) shadow file if it exists. |
246
|
|
|
if os.path.exists(destination_filename): |
247
|
|
|
os.remove(destination_filename) |
248
|
|
|
|
249
|
|
|
# Write the shadow file. |
250
|
|
|
with open(destination_filename, 'wt', encoding=self._routine_file_encoding) as handle: |
251
|
|
|
handle.write(self._routine_source_code) |
252
|
|
|
|
253
|
|
|
# Make the file read only. |
254
|
|
|
mode = os.stat(self._source_filename)[stat.ST_MODE] |
255
|
|
|
os.chmod(destination_filename, mode & ~stat.S_IWUSR & ~stat.S_IWGRP & ~stat.S_IWOTH) |
256
|
|
|
|
257
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
258
|
|
|
def __substitute_replace_pairs(self) -> None: |
259
|
|
|
""" |
260
|
|
|
Substitutes all replace pairs in the source of the stored routine. |
261
|
|
|
""" |
262
|
|
|
self._set_magic_constants() |
263
|
|
|
|
264
|
|
|
routine_source = [] |
265
|
|
|
i = 0 |
266
|
|
|
for line in self._routine_source_code_lines: |
267
|
|
|
self._replace['__LINE__'] = "'%d'" % (i + 1) |
268
|
|
|
for search, replace in self._replace.items(): |
269
|
|
|
tmp = re.findall(search, line, re.IGNORECASE) |
270
|
|
|
if tmp: |
271
|
|
|
line = line.replace(tmp[0], replace) |
272
|
|
|
routine_source.append(line) |
273
|
|
|
i += 1 |
274
|
|
|
|
275
|
|
|
self._routine_source_code = "\n".join(routine_source) |
276
|
|
|
|
277
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
278
|
|
|
def _log_exception(self, exception: Exception) -> None: |
279
|
|
|
""" |
280
|
|
|
Logs an exception. |
281
|
|
|
|
282
|
|
|
:param Exception exception: The exception. |
283
|
|
|
|
284
|
|
|
:rtype: None |
285
|
|
|
""" |
286
|
|
|
self._io.error(str(exception).strip().split(os.linesep)) |
287
|
|
|
|
288
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
289
|
|
|
@abc.abstractmethod |
290
|
|
|
def _must_reload(self) -> bool: |
291
|
|
|
""" |
292
|
|
|
Returns True if the source file must be load or reloaded. Otherwise returns False. |
293
|
|
|
|
294
|
|
|
:rtype: bool |
295
|
|
|
""" |
296
|
|
|
raise NotImplementedError() |
297
|
|
|
|
298
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
299
|
|
|
def __get_placeholders(self) -> None: |
300
|
|
|
""" |
301
|
|
|
Extracts the placeholders from the stored routine source. |
302
|
|
|
""" |
303
|
|
|
pattern = re.compile('(@[A-Za-z0-9_.]+(%(max-)?type)?@)') |
304
|
|
|
matches = pattern.findall(self._routine_source_code) |
305
|
|
|
|
306
|
|
|
placeholders = [] |
307
|
|
|
|
308
|
|
|
if len(matches) != 0: |
309
|
|
|
for tmp in matches: |
310
|
|
|
placeholder = tmp[0] |
311
|
|
|
if placeholder.lower() not in self._replace_pairs: |
312
|
|
|
raise LoaderException("Unknown placeholder '{0}' in file {1}". |
313
|
|
|
format(placeholder, self._source_filename)) |
314
|
|
|
if placeholder not in placeholders: |
315
|
|
|
placeholders.append(placeholder) |
316
|
|
|
|
317
|
|
|
for placeholder in placeholders: |
318
|
|
|
if placeholder not in self._replace: |
319
|
|
|
self._replace[placeholder] = self._replace_pairs[placeholder.lower()] |
320
|
|
|
|
321
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
322
|
|
|
def _get_designation_type(self) -> None: |
323
|
|
|
""" |
324
|
|
|
Extracts the designation type of the stored routine. |
325
|
|
|
""" |
326
|
|
|
self._get_designation_type_old() |
327
|
|
|
if not self._designation_type: |
328
|
|
|
self._get_designation_type_new() |
329
|
|
|
|
330
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
331
|
|
|
def _get_designation_type_new(self) -> None: |
332
|
|
|
""" |
333
|
|
|
Extracts the designation type of the stored routine. |
334
|
|
|
""" |
335
|
|
|
if not self._designation_type: |
336
|
|
|
raise LoaderException("Unable to find the designation type of the stored routine in file {0}". |
337
|
|
|
format(self._source_filename)) |
338
|
|
|
|
339
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
340
|
|
|
def _get_designation_type_old(self) -> None: |
341
|
|
|
""" |
342
|
|
|
Extracts the designation type of the stored routine. |
343
|
|
|
""" |
344
|
|
|
positions = self._get_specification_positions() |
345
|
|
|
if positions[0] != -1 and positions[1] != -1: |
346
|
|
|
pattern = re.compile(r'^\s*--\s+type\s*:\s*(\w+)\s*(.+)?\s*', re.IGNORECASE) |
347
|
|
|
for line_number in range(positions[0], positions[1] + 1): |
348
|
|
|
matches = pattern.findall(self._routine_source_code_lines[line_number]) |
349
|
|
|
if matches: |
350
|
|
|
self._designation_type = matches[0][0].lower() |
351
|
|
|
tmp = str(matches[0][1]) |
352
|
|
|
if self._designation_type == 'bulk_insert': |
353
|
|
|
n = re.compile(r'([a-zA-Z0-9_]+)\s+([a-zA-Z0-9_,]+)', re.IGNORECASE) |
354
|
|
|
info = n.findall(tmp) |
355
|
|
|
if not info: |
356
|
|
|
raise LoaderException('Expected: -- type: bulk_insert <table_name> <columns> in file {0}'. |
357
|
|
|
format(self._source_filename)) |
358
|
|
|
self._table_name = info[0][0] |
359
|
|
|
self._columns = str(info[0][1]).split(',') |
360
|
|
|
|
361
|
|
|
elif self._designation_type == 'rows_with_key' or self._designation_type == 'rows_with_index': |
362
|
|
|
self._columns = str(matches[0][1]).split(',') |
363
|
|
|
else: |
364
|
|
|
if matches[0][1]: |
365
|
|
|
raise LoaderException('Expected: -- type: {}'.format(self._designation_type)) |
366
|
|
|
|
367
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
368
|
|
|
def _get_specification_positions(self) -> Tuple[int, int]: |
369
|
|
|
""" |
370
|
|
|
Returns a tuple with the start and end line numbers of the stored routine specification. |
371
|
|
|
|
372
|
|
|
:rtype: tuple |
373
|
|
|
""" |
374
|
|
|
start = -1 |
375
|
|
|
for (i, line) in enumerate(self._routine_source_code_lines): |
376
|
|
|
if self._is_start_of_stored_routine(line): |
377
|
|
|
start = i |
378
|
|
|
|
379
|
|
|
end = -1 |
380
|
|
|
for (i, line) in enumerate(self._routine_source_code_lines): |
381
|
|
|
if self._is_start_of_stored_routine_body(line): |
382
|
|
|
end = i - 1 |
383
|
|
|
|
384
|
|
|
return start, end |
385
|
|
|
|
386
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
387
|
|
|
@abc.abstractmethod |
388
|
|
|
def _is_start_of_stored_routine(self, line: str) -> bool: |
389
|
|
|
""" |
390
|
|
|
Returns True if a line is the start of the code of the stored routine. |
391
|
|
|
|
392
|
|
|
:param str line: The line with source code of the stored routine. |
393
|
|
|
|
394
|
|
|
:rtype: bool |
395
|
|
|
""" |
396
|
|
|
raise NotImplementedError() |
397
|
|
|
|
398
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
399
|
|
|
def _is_start_of_stored_routine_body(self, line: str) -> bool: |
400
|
|
|
""" |
401
|
|
|
Returns True if a line is the start of the body of the stored routine. |
402
|
|
|
|
403
|
|
|
:param str line: The line with source code of the stored routine. |
404
|
|
|
|
405
|
|
|
:rtype: bool |
406
|
|
|
""" |
407
|
|
|
raise NotImplementedError() |
408
|
|
|
|
409
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
410
|
|
|
def __get_doc_block_lines(self) -> Tuple[int, int]: |
411
|
|
|
""" |
412
|
|
|
Returns the start and end line of the DocBlock of the stored routine code. |
413
|
|
|
""" |
414
|
|
|
line1 = None |
415
|
|
|
line2 = None |
416
|
|
|
|
417
|
|
|
i = 0 |
418
|
|
|
for line in self._routine_source_code_lines: |
419
|
|
|
if re.match(r'\s*/\*\*', line): |
420
|
|
|
line1 = i |
421
|
|
|
|
422
|
|
|
if re.match(r'\s*\*/', line): |
423
|
|
|
line2 = i |
424
|
|
|
|
425
|
|
|
if self._is_start_of_stored_routine(line): |
426
|
|
|
break |
427
|
|
|
|
428
|
|
|
i += 1 |
429
|
|
|
|
430
|
|
|
return line1, line2 |
431
|
|
|
|
432
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
433
|
|
|
def __get_doc_block_parts_source(self) -> None: |
434
|
|
|
""" |
435
|
|
|
Extracts the DocBlock (in parts) from the source of the stored routine source. |
436
|
|
|
""" |
437
|
|
|
line1, line2 = self.__get_doc_block_lines() |
438
|
|
|
|
439
|
|
|
if line1 is not None and line2 is not None and line1 <= line2: |
440
|
|
|
doc_block = self._routine_source_code_lines[line1:line2 - line1 + 1] |
441
|
|
|
else: |
442
|
|
|
doc_block = list() |
443
|
|
|
|
444
|
|
|
reflection = DocBlockReflection(doc_block) |
445
|
|
|
|
446
|
|
|
self._doc_block_parts_source['description'] = reflection.get_description() |
447
|
|
|
|
448
|
|
|
self._doc_block_parts_source['parameters'] = list() |
449
|
|
|
for tag in reflection.get_tags('param'): |
450
|
|
|
parts = re.match(r'^(@param)\s+(\w+)\s*(.+)?', tag, re.DOTALL) |
451
|
|
|
if parts: |
452
|
|
|
self._doc_block_parts_source['parameters'].append({'name': parts.group(2), |
453
|
|
|
'description': parts.group(3)}) |
454
|
|
|
|
455
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
456
|
|
|
def __get_parameter_doc_description(self, name: str) -> str: |
457
|
|
|
""" |
458
|
|
|
Returns the description by name of the parameter as found in the DocBlock of the stored routine. |
459
|
|
|
|
460
|
|
|
:param str name: The name of the parameter. |
461
|
|
|
|
462
|
|
|
:rtype: str |
463
|
|
|
""" |
464
|
|
|
for param in self._doc_block_parts_source['parameters']: |
465
|
|
|
if param['name'] == name: |
466
|
|
|
return param['description'] |
467
|
|
|
|
468
|
|
|
return '' |
469
|
|
|
|
470
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
471
|
|
|
@abc.abstractmethod |
472
|
|
|
def _get_data_type_helper(self) -> DataTypeHelper: |
473
|
|
|
""" |
474
|
|
|
Returns a data type helper object appropriate for the RDBMS. |
475
|
|
|
|
476
|
|
|
:rtype: DataTypeHelper |
477
|
|
|
""" |
478
|
|
|
raise NotImplementedError() |
479
|
|
|
|
480
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
481
|
|
|
def __get_doc_block_parts_wrapper(self) -> None: |
482
|
|
|
""" |
483
|
|
|
Generates the DocBlock parts to be used by the wrapper generator. |
484
|
|
|
""" |
485
|
|
|
self.__get_doc_block_parts_source() |
486
|
|
|
|
487
|
|
|
helper = self._get_data_type_helper() |
488
|
|
|
|
489
|
|
|
parameters = list() |
490
|
|
|
for parameter_info in self._parameters: |
491
|
|
|
parameters.append( |
492
|
|
|
{'parameter_name': parameter_info['name'], |
493
|
|
|
'python_type': helper.column_type_to_python_type(parameter_info), |
494
|
|
|
'python_type_hint': helper.column_type_to_python_type_hint(parameter_info), |
495
|
|
|
'data_type_descriptor': parameter_info['data_type_descriptor'], |
496
|
|
|
'description': self.__get_parameter_doc_description(parameter_info['name'])}) |
497
|
|
|
|
498
|
|
|
self._doc_block_parts_wrapper['description'] = self._doc_block_parts_source['description'] |
499
|
|
|
self._doc_block_parts_wrapper['parameters'] = parameters |
500
|
|
|
|
501
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
502
|
|
|
@abc.abstractmethod |
503
|
|
|
def _get_name(self) -> None: |
504
|
|
|
""" |
505
|
|
|
Extracts the name of the stored routine and the stored routine type (i.e. procedure or function) source. |
506
|
|
|
|
507
|
|
|
:rtype: None |
508
|
|
|
""" |
509
|
|
|
raise NotImplementedError() |
510
|
|
|
|
511
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
512
|
|
|
@abc.abstractmethod |
513
|
|
|
def _load_routine_file(self) -> None: |
514
|
|
|
""" |
515
|
|
|
Loads the stored routine into the RDBMS instance. |
516
|
|
|
""" |
517
|
|
|
raise NotImplementedError() |
518
|
|
|
|
519
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
520
|
|
|
@abc.abstractmethod |
521
|
|
|
def _get_bulk_insert_table_columns_info(self) -> None: |
522
|
|
|
""" |
523
|
|
|
Gets the column names and column types of the current table for bulk insert. |
524
|
|
|
""" |
525
|
|
|
raise NotImplementedError() |
526
|
|
|
|
527
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
528
|
|
|
@abc.abstractmethod |
529
|
|
|
def _get_routine_parameters_info(self) -> None: |
530
|
|
|
""" |
531
|
|
|
Retrieves information about the stored routine parameters from the meta data of the RDBMS. |
532
|
|
|
""" |
533
|
|
|
raise NotImplementedError() |
534
|
|
|
|
535
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
536
|
|
|
def _update_metadata(self) -> None: |
537
|
|
|
""" |
538
|
|
|
Updates the metadata of the stored routine. |
539
|
|
|
""" |
540
|
|
|
self._pystratum_metadata['routine_name'] = self._routine_name |
541
|
|
|
self._pystratum_metadata['designation'] = self._designation_type |
542
|
|
|
self._pystratum_metadata['table_name'] = self._table_name |
543
|
|
|
self._pystratum_metadata['parameters'] = self._parameters |
544
|
|
|
self._pystratum_metadata['columns'] = self._columns |
545
|
|
|
self._pystratum_metadata['fields'] = self._fields |
546
|
|
|
self._pystratum_metadata['column_types'] = self._columns_types |
547
|
|
|
self._pystratum_metadata['timestamp'] = self._m_time |
548
|
|
|
self._pystratum_metadata['replace'] = self._replace |
549
|
|
|
self._pystratum_metadata['pydoc'] = self._doc_block_parts_wrapper |
550
|
|
|
|
551
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
552
|
|
|
@abc.abstractmethod |
553
|
|
|
def _drop_routine(self) -> None: |
554
|
|
|
""" |
555
|
|
|
Drops the stored routine if it exists. |
556
|
|
|
""" |
557
|
|
|
raise NotImplementedError() |
558
|
|
|
|
559
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
560
|
|
|
def _set_magic_constants(self) -> None: |
561
|
|
|
""" |
562
|
|
|
Adds magic constants to replace list. |
563
|
|
|
""" |
564
|
|
|
real_path = os.path.realpath(self._source_filename) |
565
|
|
|
|
566
|
|
|
self._replace['__FILE__'] = "'%s'" % real_path |
567
|
|
|
self._replace['__ROUTINE__'] = "'%s'" % self._routine_name |
568
|
|
|
self._replace['__DIR__'] = "'%s'" % os.path.dirname(real_path) |
569
|
|
|
|
570
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
571
|
|
|
def _unset_magic_constants(self) -> None: |
572
|
|
|
""" |
573
|
|
|
Removes magic constants from current replace list. |
574
|
|
|
""" |
575
|
|
|
if '__FILE__' in self._replace: |
576
|
|
|
del self._replace['__FILE__'] |
577
|
|
|
|
578
|
|
|
if '__ROUTINE__' in self._replace: |
579
|
|
|
del self._replace['__ROUTINE__'] |
580
|
|
|
|
581
|
|
|
if '__DIR__' in self._replace: |
582
|
|
|
del self._replace['__DIR__'] |
583
|
|
|
|
584
|
|
|
if '__LINE__' in self._replace: |
585
|
|
|
del self._replace['__LINE__'] |
586
|
|
|
|
587
|
|
|
# ------------------------------------------------------------------------------------------------------------------ |
588
|
|
|
def _print_sql_with_error(self, sql: str, error_line: int) -> None: |
589
|
|
|
""" |
590
|
|
|
Writes a SQL statement with an syntax error to the output. The line where the error occurs is highlighted. |
591
|
|
|
|
592
|
|
|
:param str sql: The SQL statement. |
593
|
|
|
:param int error_line: The line where the error occurs. |
594
|
|
|
""" |
595
|
|
|
if os.linesep in sql: |
596
|
|
|
lines = sql.split(os.linesep) |
597
|
|
|
digits = math.ceil(math.log(len(lines) + 1, 10)) |
598
|
|
|
i = 1 |
599
|
|
|
for line in lines: |
600
|
|
|
if i == error_line: |
601
|
|
|
self._io.text('<error>{0:{width}} {1}</error>'.format(i, line, width=digits, )) |
602
|
|
|
else: |
603
|
|
|
self._io.text('{0:{width}} {1}'.format(i, line, width=digits, )) |
604
|
|
|
i += 1 |
605
|
|
|
else: |
606
|
|
|
self._io.text(sql) |
607
|
|
|
|
608
|
|
|
# ---------------------------------------------------------------------------------------------------------------------- |
609
|
|
|
|