MySqlRoutineLoaderHelper.__init__()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 50
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 1
CRAP Score 1.6296

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 50
ccs 1
cts 7
cp 0.1429
rs 9.352
c 0
b 0
f 0
cc 1
nop 11
crap 1.6296

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1 1
import re
2 1
from typing import Any, Dict, Optional
3
4 1
from mysql import connector
5 1
from pystratum_backend.StratumIO import StratumIO
6
from pystratum_common.exception.LoaderException import LoaderException
7 1
from pystratum_common.helper.DataTypeHelper import DataTypeHelper
8 1
from pystratum_common.helper.RoutineLoaderHelper import RoutineLoaderHelper
9 1
10 1
from pystratum_mysql.helper.MySqlDataTypeHelper import MySqlDataTypeHelper
11 1
from pystratum_mysql.MySqlMetadataDataLayer import MySqlMetadataDataLayer
12
13
14 1
class MySqlRoutineLoaderHelper(RoutineLoaderHelper):
15
    """
16
    Class for loading a single stored routine into a MySQL instance from a (pseudo) SQL file.
17
    """
18
19
    # ------------------------------------------------------------------------------------------------------------------
20 1
    def __init__(self,
21
                 io: StratumIO,
22
                 dl: MySqlMetadataDataLayer,
23
                 routine_filename: str,
24
                 routine_file_encoding: str,
25
                 pystratum_old_metadata: Optional[Dict],
26
                 replace_pairs: Dict[str, Any],
27
                 rdbms_old_metadata: Optional[Dict],
28
                 sql_mode: str,
29
                 character_set: str,
30
                 collate: str):
31
        """
32
        Object constructor.
33
                                
34
        :param io: The output decorator.
35
        :param dl: The metadata layer.
36
        :param routine_filename: The filename of the source of the stored routine.
37
        :param routine_file_encoding: The encoding of the source file.
38
        :param pystratum_old_metadata: The metadata of the stored routine from PyStratum.
39
        :param replace_pairs: A map from placeholders to their actual values.
40
        :param rdbms_old_metadata: The old metadata of the stored routine from MS SQL Server.
41
        :param sql_mode: The SQL mode under which the stored routine must be loaded and run.
42
        :param character_set: The default character set under which the stored routine must be loaded and run.
43
        :param collate: The default collate under which the stored routine must be loaded and run.
44
        """
45
        RoutineLoaderHelper.__init__(self,
46
                                     io,
47
                                     routine_filename,
48
                                     routine_file_encoding,
49
                                     pystratum_old_metadata,
50
                                     replace_pairs,
51
                                     rdbms_old_metadata)
52
53
        self._character_set: str = character_set
54
        """
55
        The default character set under which the stored routine will be loaded and run.
56
        """
57
58
        self._collate: str = collate
59
        """
60
        The default collate under which the stored routine will be loaded and run.
61
        """
62
63
        self._sql_mode: str = sql_mode
64
        """
65
        The SQL-mode under which the stored routine will be loaded and run.
66
        """
67
68
        self._dl: MySqlMetadataDataLayer = dl
69
        """
70
        The metadata layer.
71
        """
72
73
    # ------------------------------------------------------------------------------------------------------------------
74 1
    def _get_bulk_insert_table_columns_info(self) -> None:
75
        """
76
        Gets the column names and column types of the current table for bulk insert.
77
        """
78
        table_is_non_temporary = self._dl.check_table_exists(self._table_name)
79
80
        if not table_is_non_temporary:
81
            self._dl.call_stored_routine(self._routine_name)
82
83
        columns = self._dl.describe_table(self._table_name)
84
85
        tmp_column_types = []
86
        tmp_fields = []
87
88
        n1 = 0
89
        for column in columns:
90
            prog = re.compile('(\\w+)')
91
            c_type = prog.findall(column['Type'])
92
            tmp_column_types.append(c_type[0])
93
            tmp_fields.append(column['Field'])
94
            n1 += 1
95
96
        n2 = len(self._columns)
97
98
        if not table_is_non_temporary:
99
            self._dl.drop_temporary_table(self._table_name)
100
101
        if n1 != n2:
102
            raise LoaderException("Number of fields %d and number of columns %d don't match." % (n1, n2))
103
104
        self._columns_types = tmp_column_types
105
        self._fields = tmp_fields
106
107
    # ------------------------------------------------------------------------------------------------------------------
108 1
    def _get_data_type_helper(self) -> DataTypeHelper:
109
        """
110
        Returns a data type helper object for MySQL.
111
        """
112
        return MySqlDataTypeHelper()
113
114
    # ------------------------------------------------------------------------------------------------------------------
115
    def _get_name(self) -> None:
116
        """
117 1
        Extracts the name of the stored routine and the stored routine type (i.e. procedure or function) source.
118
        """
119
        prog = re.compile("create\\s+(procedure|function)\\s+([a-zA-Z0-9_]+)")
120
        matches = prog.findall(self._routine_source_code)
121
122
        if matches:
123
            self._routine_type = matches[0][0].lower()
124
125
            if self._routine_name != matches[0][1]:
126
                raise LoaderException('Stored routine name {0} does not match filename in file {1}'.
127
                                      format(matches[0][1], self._source_filename))
128
129
        if not self._routine_type:
130
            raise LoaderException('Unable to find the stored routine name and type in file {0}'.
131
                                  format(self._source_filename))
132
133
    # ------------------------------------------------------------------------------------------------------------------
134
    def _get_routine_parameters_info(self) -> None:
135
        """
136 1
        Retrieves information about the stored routine parameters from the metadata of MySQL.
137
        """
138
        routine_parameters = self._dl.get_routine_parameters(self._routine_name)
139
        for routine_parameter in routine_parameters:
140
            if routine_parameter['parameter_name']:
141
                value = routine_parameter['column_type']
142
                if 'character_set_name' in routine_parameter:
143
                    if routine_parameter['character_set_name']:
144
                        value += ' character set %s' % routine_parameter['character_set_name']
145
                if 'collation' in routine_parameter:
146
                    if routine_parameter['character_set_name']:
147
                        value += ' collation %s' % routine_parameter['collation']
148
149
                self._parameters.append({'name':                 routine_parameter['parameter_name'],
150
                                         'data_type':            routine_parameter['parameter_type'],
151
                                         'numeric_precision':    routine_parameter['numeric_precision'],
152
                                         'numeric_scale':        routine_parameter['numeric_scale'],
153
                                         'data_type_descriptor': value})
154
155
    # ------------------------------------------------------------------------------------------------------------------
156
    def _is_start_of_stored_routine(self, line: str) -> bool:
157
        """
158 1
        Returns True if a line is the start of the code of the stored routine.
159
160
        :param line: The line with source code of the stored routine.
161
        """
162
        return re.match(r'^\s*create\s+(procedure|function)', line, re.IGNORECASE) is not None
163
164
    # ------------------------------------------------------------------------------------------------------------------
165
    def _is_start_of_stored_routine_body(self, line: str) -> bool:
166
        """
167
        Returns True if a line is the start of the body of the stored routine.
168
169 1
        :param line: The line with source code of the stored routine.
170
        """
171
        return re.match(r'^\s*begin', line, re.IGNORECASE) is not None
172
173
    # ------------------------------------------------------------------------------------------------------------------
174
    def _load_routine_file(self) -> None:
175
        """
176
        Loads the stored routine into the MySQL instance.
177
        """
178
        self._io.text('Loading {0} <dbo>{1}</dbo>'.format(self._routine_type, self._routine_name))
179
180 1
        self._unset_magic_constants()
181
        self._drop_routine()
182
183
        self._dl.set_sql_mode(self._sql_mode)
184
185
        self._dl.set_character_set(self._character_set, self._collate)
186
187
        self._dl.execute_none(self._routine_source_code)
188
189
    # ------------------------------------------------------------------------------------------------------------------
190
    def _log_exception(self, exception: Exception) -> None:
191
        """
192
        Logs an exception.
193
194
        :param exception: The exception.
195
        """
196 1
        RoutineLoaderHelper._log_exception(self, exception)
197
198
        if isinstance(exception, connector.errors.Error):
199
            if exception.errno == 1064:
200
                # Exception is caused by an invalid SQL statement.
201
                sql = self._dl.last_sql()
202
                if sql:
203
                    sql = sql.strip()
204
                    # The format of a 1064 message is: %s near '%s' at line %d
205
                    parts = re.search(r'(\d+)$', exception.msg)
206
                    if parts:
207
                        error_line = int(parts.group(1))
208
                    else:
209
                        error_line = 0
210
211
                    self._print_sql_with_error(sql, error_line)
212
213
    # ------------------------------------------------------------------------------------------------------------------
214
    def _must_reload(self) -> bool:
215
        """
216
        Returns whether the source file must be load or reloaded.
217
        """
218
        if not self._pystratum_old_metadata:
219
            return True
220 1
221
        if self._pystratum_old_metadata['timestamp'] != self._m_time:
222
            return True
223
224
        if self._pystratum_old_metadata['replace']:
225
            for key, value in self._pystratum_old_metadata['replace'].items():
226
                if key.lower() not in self._replace_pairs or self._replace_pairs[key.lower()] != value:
227
                    return True
228
229
        if not self._rdbms_old_metadata:
230
            return True
231
232
        if self._rdbms_old_metadata['sql_mode'] != self._sql_mode:
233
            return True
234
235
        if self._rdbms_old_metadata['character_set_client'] != self._character_set:
236
            return True
237
238
        if self._rdbms_old_metadata['collation_connection'] != self._collate:
239
            return True
240
241
        return False
242
243
    # ------------------------------------------------------------------------------------------------------------------
244
    def _drop_routine(self) -> None:
245
        """
246
        Drops the stored routine if it exists.
247
        """
248
        if self._rdbms_old_metadata:
249
            self._dl.drop_stored_routine(self._rdbms_old_metadata['routine_type'], self._routine_name)
250
251
# ----------------------------------------------------------------------------------------------------------------------
252