velocity-python 0.0.109__py3-none-any.whl → 0.0.155__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- velocity/__init__.py +3 -1
- velocity/app/orders.py +3 -4
- velocity/app/tests/__init__.py +1 -0
- velocity/app/tests/test_email_processing.py +112 -0
- velocity/app/tests/test_payment_profile_sorting.py +191 -0
- velocity/app/tests/test_spreadsheet_functions.py +124 -0
- velocity/aws/__init__.py +3 -0
- velocity/aws/amplify.py +10 -6
- velocity/aws/handlers/__init__.py +2 -0
- velocity/aws/handlers/base_handler.py +248 -0
- velocity/aws/handlers/context.py +167 -2
- velocity/aws/handlers/exceptions.py +16 -0
- velocity/aws/handlers/lambda_handler.py +24 -85
- velocity/aws/handlers/mixins/__init__.py +16 -0
- velocity/aws/handlers/mixins/activity_tracker.py +181 -0
- velocity/aws/handlers/mixins/aws_session_mixin.py +192 -0
- velocity/aws/handlers/mixins/error_handler.py +192 -0
- velocity/aws/handlers/mixins/legacy_mixin.py +53 -0
- velocity/aws/handlers/mixins/standard_mixin.py +73 -0
- velocity/aws/handlers/response.py +1 -1
- velocity/aws/handlers/sqs_handler.py +28 -143
- velocity/aws/tests/__init__.py +1 -0
- velocity/aws/tests/test_lambda_handler_json_serialization.py +120 -0
- velocity/aws/tests/test_response.py +163 -0
- velocity/db/__init__.py +16 -4
- velocity/db/core/decorators.py +20 -4
- velocity/db/core/engine.py +185 -839
- velocity/db/core/result.py +30 -24
- velocity/db/core/row.py +15 -3
- velocity/db/core/table.py +279 -40
- velocity/db/core/transaction.py +19 -11
- velocity/db/exceptions.py +42 -18
- velocity/db/servers/base/__init__.py +9 -0
- velocity/db/servers/base/initializer.py +70 -0
- velocity/db/servers/base/operators.py +98 -0
- velocity/db/servers/base/sql.py +503 -0
- velocity/db/servers/base/types.py +135 -0
- velocity/db/servers/mysql/__init__.py +73 -0
- velocity/db/servers/mysql/operators.py +54 -0
- velocity/db/servers/{mysql_reserved.py → mysql/reserved.py} +2 -14
- velocity/db/servers/mysql/sql.py +718 -0
- velocity/db/servers/mysql/types.py +107 -0
- velocity/db/servers/postgres/__init__.py +59 -11
- velocity/db/servers/postgres/operators.py +34 -0
- velocity/db/servers/postgres/sql.py +474 -120
- velocity/db/servers/postgres/types.py +88 -2
- velocity/db/servers/sqlite/__init__.py +61 -0
- velocity/db/servers/sqlite/operators.py +52 -0
- velocity/db/servers/sqlite/reserved.py +20 -0
- velocity/db/servers/sqlite/sql.py +677 -0
- velocity/db/servers/sqlite/types.py +92 -0
- velocity/db/servers/sqlserver/__init__.py +73 -0
- velocity/db/servers/sqlserver/operators.py +47 -0
- velocity/db/servers/sqlserver/reserved.py +32 -0
- velocity/db/servers/sqlserver/sql.py +805 -0
- velocity/db/servers/sqlserver/types.py +114 -0
- velocity/db/servers/tablehelper.py +117 -91
- velocity/db/tests/__init__.py +1 -0
- velocity/db/tests/common_db_test.py +0 -0
- velocity/db/tests/postgres/__init__.py +1 -0
- velocity/db/tests/postgres/common.py +49 -0
- velocity/db/tests/postgres/test_column.py +29 -0
- velocity/db/tests/postgres/test_connections.py +25 -0
- velocity/db/tests/postgres/test_database.py +21 -0
- velocity/db/tests/postgres/test_engine.py +205 -0
- velocity/db/tests/postgres/test_general_usage.py +88 -0
- velocity/db/tests/postgres/test_imports.py +8 -0
- velocity/db/tests/postgres/test_result.py +19 -0
- velocity/db/tests/postgres/test_row.py +137 -0
- velocity/db/tests/postgres/test_row_comprehensive.py +720 -0
- velocity/db/tests/postgres/test_schema_locking.py +335 -0
- velocity/db/tests/postgres/test_schema_locking_unit.py +115 -0
- velocity/db/tests/postgres/test_sequence.py +34 -0
- velocity/db/tests/postgres/test_sql_comprehensive.py +462 -0
- velocity/db/tests/postgres/test_table.py +101 -0
- velocity/db/tests/postgres/test_table_comprehensive.py +646 -0
- velocity/db/tests/postgres/test_transaction.py +106 -0
- velocity/db/tests/sql/__init__.py +1 -0
- velocity/db/tests/sql/common.py +177 -0
- velocity/db/tests/sql/test_postgres_select_advanced.py +285 -0
- velocity/db/tests/sql/test_postgres_select_variances.py +517 -0
- velocity/db/tests/test_cursor_rowcount_fix.py +150 -0
- velocity/db/tests/test_db_utils.py +221 -0
- velocity/db/tests/test_postgres.py +448 -0
- velocity/db/tests/test_postgres_unchanged.py +81 -0
- velocity/db/tests/test_process_error_robustness.py +292 -0
- velocity/db/tests/test_result_caching.py +279 -0
- velocity/db/tests/test_result_sql_aware.py +117 -0
- velocity/db/tests/test_row_get_missing_column.py +72 -0
- velocity/db/tests/test_schema_locking_initializers.py +226 -0
- velocity/db/tests/test_schema_locking_simple.py +97 -0
- velocity/db/tests/test_sql_builder.py +165 -0
- velocity/db/tests/test_tablehelper.py +486 -0
- velocity/db/utils.py +62 -47
- velocity/misc/conv/__init__.py +2 -0
- velocity/misc/conv/iconv.py +5 -4
- velocity/misc/export.py +1 -4
- velocity/misc/merge.py +1 -1
- velocity/misc/tests/__init__.py +1 -0
- velocity/misc/tests/test_db.py +90 -0
- velocity/misc/tests/test_fix.py +78 -0
- velocity/misc/tests/test_format.py +64 -0
- velocity/misc/tests/test_iconv.py +203 -0
- velocity/misc/tests/test_merge.py +82 -0
- velocity/misc/tests/test_oconv.py +144 -0
- velocity/misc/tests/test_original_error.py +52 -0
- velocity/misc/tests/test_timer.py +74 -0
- velocity/misc/tools.py +0 -1
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/METADATA +2 -2
- velocity_python-0.0.155.dist-info/RECORD +129 -0
- velocity/db/core/exceptions.py +0 -70
- velocity/db/servers/mysql.py +0 -641
- velocity/db/servers/sqlite.py +0 -968
- velocity/db/servers/sqlite_reserved.py +0 -208
- velocity/db/servers/sqlserver.py +0 -921
- velocity/db/servers/sqlserver_reserved.py +0 -314
- velocity_python-0.0.109.dist-info/RECORD +0 -56
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.109.dist-info → velocity_python-0.0.155.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Abstract base class for SQL dialect implementations.
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
|
6
|
+
from collections.abc import Mapping, Sequence
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseSQLDialect(ABC):
|
|
10
|
+
"""
|
|
11
|
+
Abstract base class that defines the interface all database SQL dialects must implement.
|
|
12
|
+
|
|
13
|
+
This class ensures consistency across all database implementations and makes it clear
|
|
14
|
+
what methods each database dialect needs to provide.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
# Database server identifier - must be set by subclasses
|
|
18
|
+
server: str = ""
|
|
19
|
+
|
|
20
|
+
# Column metadata identifiers - database specific
|
|
21
|
+
type_column_identifier: str = ""
|
|
22
|
+
is_nullable: str = ""
|
|
23
|
+
|
|
24
|
+
# Default schema name for this database
|
|
25
|
+
default_schema: str = ""
|
|
26
|
+
|
|
27
|
+
# Error code classifications - must be set by subclasses
|
|
28
|
+
ApplicationErrorCodes: List[str] = []
|
|
29
|
+
DatabaseMissingErrorCodes: List[str] = []
|
|
30
|
+
TableMissingErrorCodes: List[str] = []
|
|
31
|
+
ColumnMissingErrorCodes: List[str] = []
|
|
32
|
+
ForeignKeyMissingErrorCodes: List[str] = []
|
|
33
|
+
ConnectionErrorCodes: List[str] = []
|
|
34
|
+
DuplicateKeyErrorCodes: List[str] = []
|
|
35
|
+
RetryTransactionCodes: List[str] = []
|
|
36
|
+
TruncationErrorCodes: List[str] = []
|
|
37
|
+
LockTimeoutErrorCodes: List[str] = []
|
|
38
|
+
DatabaseObjectExistsErrorCodes: List[str] = []
|
|
39
|
+
DataIntegrityErrorCodes: List[str] = []
|
|
40
|
+
|
|
41
|
+
@classmethod
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def get_error(cls, e: Exception) -> Optional[str]:
|
|
44
|
+
"""
|
|
45
|
+
Extract error information from database exception.
|
|
46
|
+
|
|
47
|
+
Args:
|
|
48
|
+
e: Database exception
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
Error code or message, or None if not applicable
|
|
52
|
+
"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
# Core CRUD Operations
|
|
56
|
+
@classmethod
|
|
57
|
+
@abstractmethod
|
|
58
|
+
def select(
|
|
59
|
+
cls,
|
|
60
|
+
tx: Any,
|
|
61
|
+
columns: Optional[Union[str, List[str]]] = None,
|
|
62
|
+
table: Optional[str] = None,
|
|
63
|
+
where: Optional[Union[str, Dict, List]] = None,
|
|
64
|
+
orderby: Optional[Union[str, List, Dict]] = None,
|
|
65
|
+
groupby: Optional[Union[str, List]] = None,
|
|
66
|
+
having: Optional[Union[str, Dict, List]] = None,
|
|
67
|
+
start: Optional[int] = None,
|
|
68
|
+
qty: Optional[int] = None,
|
|
69
|
+
lock: Optional[bool] = None,
|
|
70
|
+
skip_locked: Optional[bool] = None,
|
|
71
|
+
) -> Tuple[str, List[Any]]:
|
|
72
|
+
"""
|
|
73
|
+
Generate a SELECT statement.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
Tuple of (sql_string, parameters)
|
|
77
|
+
"""
|
|
78
|
+
pass
|
|
79
|
+
|
|
80
|
+
@classmethod
|
|
81
|
+
@abstractmethod
|
|
82
|
+
def insert(cls, table: str, data: Dict[str, Any]) -> Tuple[str, List[Any]]:
|
|
83
|
+
"""
|
|
84
|
+
Generate an INSERT statement.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
table: Table name
|
|
88
|
+
data: Dictionary of column names to values
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Tuple of (sql_string, parameters)
|
|
92
|
+
"""
|
|
93
|
+
pass
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
@abstractmethod
|
|
97
|
+
def update(
|
|
98
|
+
cls,
|
|
99
|
+
tx: Any,
|
|
100
|
+
table: str,
|
|
101
|
+
data: Dict[str, Any],
|
|
102
|
+
where: Optional[Union[str, Dict, List]] = None,
|
|
103
|
+
pk: Optional[Dict[str, Any]] = None,
|
|
104
|
+
excluded: bool = False
|
|
105
|
+
) -> Tuple[str, List[Any]]:
|
|
106
|
+
"""
|
|
107
|
+
Generate an UPDATE statement.
|
|
108
|
+
|
|
109
|
+
Args:
|
|
110
|
+
tx: Database transaction
|
|
111
|
+
table: Table name
|
|
112
|
+
data: Dictionary of columns to update
|
|
113
|
+
where: WHERE clause conditions
|
|
114
|
+
pk: Primary key conditions to merge with where
|
|
115
|
+
excluded: If True, creates EXCLUDED.col expressions for upserts
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Tuple of (sql_string, parameters)
|
|
119
|
+
"""
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
@abstractmethod
|
|
124
|
+
def delete(cls, tx: Any, table: str, where: Union[str, Dict, List]) -> Tuple[str, List[Any]]:
|
|
125
|
+
"""
|
|
126
|
+
Generate a DELETE statement.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
tx: Database transaction
|
|
130
|
+
table: Table name
|
|
131
|
+
where: WHERE clause conditions
|
|
132
|
+
|
|
133
|
+
Returns:
|
|
134
|
+
Tuple of (sql_string, parameters)
|
|
135
|
+
"""
|
|
136
|
+
pass
|
|
137
|
+
|
|
138
|
+
@classmethod
|
|
139
|
+
@abstractmethod
|
|
140
|
+
def merge(
|
|
141
|
+
cls,
|
|
142
|
+
tx: Any,
|
|
143
|
+
table: str,
|
|
144
|
+
data: Dict[str, Any],
|
|
145
|
+
pk: Dict[str, Any],
|
|
146
|
+
on_conflict_do_nothing: bool,
|
|
147
|
+
on_conflict_update: bool
|
|
148
|
+
) -> Tuple[str, List[Any]]:
|
|
149
|
+
"""
|
|
150
|
+
Generate an UPSERT/MERGE statement.
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
tx: Database transaction
|
|
154
|
+
table: Table name
|
|
155
|
+
data: Data to insert/update
|
|
156
|
+
pk: Primary key columns
|
|
157
|
+
on_conflict_do_nothing: If True, ignore conflicts
|
|
158
|
+
on_conflict_update: If True, update on conflicts
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
Tuple of (sql_string, parameters)
|
|
162
|
+
"""
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
# Database Metadata Operations
|
|
166
|
+
@classmethod
|
|
167
|
+
@abstractmethod
|
|
168
|
+
def version(cls) -> str:
|
|
169
|
+
"""Get database version query."""
|
|
170
|
+
pass
|
|
171
|
+
|
|
172
|
+
@classmethod
|
|
173
|
+
@abstractmethod
|
|
174
|
+
def timestamp(cls) -> str:
|
|
175
|
+
"""Get current timestamp query."""
|
|
176
|
+
pass
|
|
177
|
+
|
|
178
|
+
@classmethod
|
|
179
|
+
@abstractmethod
|
|
180
|
+
def user(cls) -> str:
|
|
181
|
+
"""Get current user query."""
|
|
182
|
+
pass
|
|
183
|
+
|
|
184
|
+
@classmethod
|
|
185
|
+
@abstractmethod
|
|
186
|
+
def databases(cls) -> str:
|
|
187
|
+
"""Get list of databases query."""
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
@classmethod
|
|
191
|
+
@abstractmethod
|
|
192
|
+
def schemas(cls) -> str:
|
|
193
|
+
"""Get list of schemas query."""
|
|
194
|
+
pass
|
|
195
|
+
|
|
196
|
+
@classmethod
|
|
197
|
+
@abstractmethod
|
|
198
|
+
def current_schema(cls) -> str:
|
|
199
|
+
"""Get current schema query."""
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
@classmethod
|
|
203
|
+
@abstractmethod
|
|
204
|
+
def current_database(cls) -> str:
|
|
205
|
+
"""Get current database query."""
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
@classmethod
|
|
209
|
+
@abstractmethod
|
|
210
|
+
def tables(cls, system: bool = False) -> str:
|
|
211
|
+
"""
|
|
212
|
+
Get list of tables query.
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
system: Include system tables
|
|
216
|
+
"""
|
|
217
|
+
pass
|
|
218
|
+
|
|
219
|
+
@classmethod
|
|
220
|
+
@abstractmethod
|
|
221
|
+
def views(cls, system: bool = False) -> str:
|
|
222
|
+
"""
|
|
223
|
+
Get list of views query.
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
system: Include system views
|
|
227
|
+
"""
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
# Database Structure Operations
|
|
231
|
+
@classmethod
|
|
232
|
+
@abstractmethod
|
|
233
|
+
def create_database(cls, name: str) -> str:
|
|
234
|
+
"""Generate CREATE DATABASE statement."""
|
|
235
|
+
pass
|
|
236
|
+
|
|
237
|
+
@classmethod
|
|
238
|
+
@abstractmethod
|
|
239
|
+
def drop_database(cls, name: str) -> str:
|
|
240
|
+
"""Generate DROP DATABASE statement."""
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
@classmethod
|
|
244
|
+
@abstractmethod
|
|
245
|
+
def create_table(cls, name: str, columns: Dict[str, Any] = None, drop: bool = False) -> str:
|
|
246
|
+
"""
|
|
247
|
+
Generate CREATE TABLE statement.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
name: Table name
|
|
251
|
+
columns: Column definitions
|
|
252
|
+
drop: Drop table if exists first
|
|
253
|
+
"""
|
|
254
|
+
pass
|
|
255
|
+
|
|
256
|
+
@classmethod
|
|
257
|
+
@abstractmethod
|
|
258
|
+
def drop_table(cls, name: str) -> str:
|
|
259
|
+
"""Generate DROP TABLE statement."""
|
|
260
|
+
pass
|
|
261
|
+
|
|
262
|
+
@classmethod
|
|
263
|
+
@abstractmethod
|
|
264
|
+
def truncate(cls, table: str) -> str:
|
|
265
|
+
"""Generate TRUNCATE statement."""
|
|
266
|
+
pass
|
|
267
|
+
|
|
268
|
+
# Column Operations
|
|
269
|
+
@classmethod
|
|
270
|
+
@abstractmethod
|
|
271
|
+
def columns(cls, name: str) -> str:
|
|
272
|
+
"""Get table columns query."""
|
|
273
|
+
pass
|
|
274
|
+
|
|
275
|
+
@classmethod
|
|
276
|
+
@abstractmethod
|
|
277
|
+
def column_info(cls, table: str, name: str) -> str:
|
|
278
|
+
"""Get column information query."""
|
|
279
|
+
pass
|
|
280
|
+
|
|
281
|
+
@classmethod
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def drop_column(cls, table: str, name: str, cascade: bool = True) -> str:
|
|
284
|
+
"""Generate DROP COLUMN statement."""
|
|
285
|
+
pass
|
|
286
|
+
|
|
287
|
+
@classmethod
|
|
288
|
+
@abstractmethod
|
|
289
|
+
def alter_add(cls, table: str, columns: Dict[str, Any], null_allowed: bool = True) -> str:
|
|
290
|
+
"""Generate ALTER TABLE ADD COLUMN statement."""
|
|
291
|
+
pass
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
@abstractmethod
|
|
295
|
+
def alter_drop(cls, table: str, columns: List[str]) -> str:
|
|
296
|
+
"""Generate ALTER TABLE DROP COLUMN statement."""
|
|
297
|
+
pass
|
|
298
|
+
|
|
299
|
+
@classmethod
|
|
300
|
+
@abstractmethod
|
|
301
|
+
def alter_column_by_type(cls, table: str, column: str, value: str, nullable: bool = True) -> str:
|
|
302
|
+
"""Generate ALTER COLUMN statement by type."""
|
|
303
|
+
pass
|
|
304
|
+
|
|
305
|
+
@classmethod
|
|
306
|
+
@abstractmethod
|
|
307
|
+
def alter_column_by_sql(cls, table: str, column: str, value: str) -> str:
|
|
308
|
+
"""Generate ALTER COLUMN statement by SQL."""
|
|
309
|
+
pass
|
|
310
|
+
|
|
311
|
+
@classmethod
|
|
312
|
+
@abstractmethod
|
|
313
|
+
def rename_column(cls, table: str, orig: str, new: str) -> str:
|
|
314
|
+
"""Generate RENAME COLUMN statement."""
|
|
315
|
+
pass
|
|
316
|
+
|
|
317
|
+
@classmethod
|
|
318
|
+
@abstractmethod
|
|
319
|
+
def rename_table(cls, table: str, new: str) -> str:
|
|
320
|
+
"""Generate RENAME TABLE statement."""
|
|
321
|
+
pass
|
|
322
|
+
|
|
323
|
+
# Key Operations
|
|
324
|
+
@classmethod
|
|
325
|
+
@abstractmethod
|
|
326
|
+
def primary_keys(cls, table: str) -> str:
|
|
327
|
+
"""Get primary key columns query."""
|
|
328
|
+
pass
|
|
329
|
+
|
|
330
|
+
@classmethod
|
|
331
|
+
@abstractmethod
|
|
332
|
+
def foreign_key_info(
|
|
333
|
+
cls,
|
|
334
|
+
table: Optional[str] = None,
|
|
335
|
+
column: Optional[str] = None,
|
|
336
|
+
schema: Optional[str] = None
|
|
337
|
+
) -> str:
|
|
338
|
+
"""Get foreign key information query."""
|
|
339
|
+
pass
|
|
340
|
+
|
|
341
|
+
@classmethod
|
|
342
|
+
@abstractmethod
|
|
343
|
+
def create_foreign_key(
|
|
344
|
+
cls,
|
|
345
|
+
table: str,
|
|
346
|
+
columns: List[str],
|
|
347
|
+
key_to_table: str,
|
|
348
|
+
key_to_columns: List[str],
|
|
349
|
+
name: Optional[str] = None,
|
|
350
|
+
schema: Optional[str] = None,
|
|
351
|
+
) -> str:
|
|
352
|
+
"""Generate CREATE FOREIGN KEY statement."""
|
|
353
|
+
pass
|
|
354
|
+
|
|
355
|
+
@classmethod
|
|
356
|
+
@abstractmethod
|
|
357
|
+
def drop_foreign_key(
|
|
358
|
+
cls,
|
|
359
|
+
table: str,
|
|
360
|
+
columns: List[str],
|
|
361
|
+
key_to_table: Optional[str] = None,
|
|
362
|
+
key_to_columns: Optional[List[str]] = None,
|
|
363
|
+
name: Optional[str] = None,
|
|
364
|
+
schema: Optional[str] = None,
|
|
365
|
+
) -> str:
|
|
366
|
+
"""Generate DROP FOREIGN KEY statement."""
|
|
367
|
+
pass
|
|
368
|
+
|
|
369
|
+
# Index Operations
|
|
370
|
+
@classmethod
|
|
371
|
+
@abstractmethod
|
|
372
|
+
def create_index(
|
|
373
|
+
cls,
|
|
374
|
+
tx: Any,
|
|
375
|
+
table: Optional[str] = None,
|
|
376
|
+
columns: Optional[List[str]] = None,
|
|
377
|
+
unique: bool = False,
|
|
378
|
+
direction: Optional[str] = None,
|
|
379
|
+
where: Optional[str] = None,
|
|
380
|
+
name: Optional[str] = None,
|
|
381
|
+
schema: Optional[str] = None,
|
|
382
|
+
trigram: Optional[bool] = None,
|
|
383
|
+
lower: Optional[bool] = None,
|
|
384
|
+
) -> str:
|
|
385
|
+
"""Generate CREATE INDEX statement."""
|
|
386
|
+
pass
|
|
387
|
+
|
|
388
|
+
@classmethod
|
|
389
|
+
@abstractmethod
|
|
390
|
+
def drop_index(
|
|
391
|
+
cls,
|
|
392
|
+
table: Optional[str] = None,
|
|
393
|
+
columns: Optional[List[str]] = None,
|
|
394
|
+
name: Optional[str] = None,
|
|
395
|
+
schema: Optional[str] = None,
|
|
396
|
+
trigram: Optional[bool] = None,
|
|
397
|
+
) -> str:
|
|
398
|
+
"""Generate DROP INDEX statement."""
|
|
399
|
+
pass
|
|
400
|
+
|
|
401
|
+
@classmethod
|
|
402
|
+
@abstractmethod
|
|
403
|
+
def indexes(cls, table: str) -> str:
|
|
404
|
+
"""Get table indexes query."""
|
|
405
|
+
pass
|
|
406
|
+
|
|
407
|
+
# Transaction Operations
|
|
408
|
+
@classmethod
|
|
409
|
+
@abstractmethod
|
|
410
|
+
def create_savepoint(cls, sp: str) -> str:
|
|
411
|
+
"""Generate SAVEPOINT statement."""
|
|
412
|
+
pass
|
|
413
|
+
|
|
414
|
+
@classmethod
|
|
415
|
+
@abstractmethod
|
|
416
|
+
def release_savepoint(cls, sp: str) -> str:
|
|
417
|
+
"""Generate RELEASE SAVEPOINT statement."""
|
|
418
|
+
pass
|
|
419
|
+
|
|
420
|
+
@classmethod
|
|
421
|
+
@abstractmethod
|
|
422
|
+
def rollback_savepoint(cls, sp: str) -> str:
|
|
423
|
+
"""Generate ROLLBACK TO SAVEPOINT statement."""
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
# View Operations
|
|
427
|
+
@classmethod
|
|
428
|
+
@abstractmethod
|
|
429
|
+
def create_view(cls, name: str, query: str, temp: bool = False, silent: bool = True) -> str:
|
|
430
|
+
"""Generate CREATE VIEW statement."""
|
|
431
|
+
pass
|
|
432
|
+
|
|
433
|
+
@classmethod
|
|
434
|
+
@abstractmethod
|
|
435
|
+
def drop_view(cls, name: str, silent: bool = True) -> str:
|
|
436
|
+
"""Generate DROP VIEW statement."""
|
|
437
|
+
pass
|
|
438
|
+
|
|
439
|
+
# Sequence/Identity Operations
|
|
440
|
+
@classmethod
|
|
441
|
+
@abstractmethod
|
|
442
|
+
def last_id(cls, table: str) -> str:
|
|
443
|
+
"""Get last inserted ID query."""
|
|
444
|
+
pass
|
|
445
|
+
|
|
446
|
+
@classmethod
|
|
447
|
+
@abstractmethod
|
|
448
|
+
def current_id(cls, table: str) -> str:
|
|
449
|
+
"""Get current sequence value query."""
|
|
450
|
+
pass
|
|
451
|
+
|
|
452
|
+
@classmethod
|
|
453
|
+
@abstractmethod
|
|
454
|
+
def set_id(cls, table: str, start: int) -> str:
|
|
455
|
+
"""Generate set sequence value statement."""
|
|
456
|
+
pass
|
|
457
|
+
|
|
458
|
+
@classmethod
|
|
459
|
+
@abstractmethod
|
|
460
|
+
def set_sequence(cls, table: str, next_value: int) -> str:
|
|
461
|
+
"""Generate set sequence next value statement."""
|
|
462
|
+
pass
|
|
463
|
+
|
|
464
|
+
# Utility Operations
|
|
465
|
+
@classmethod
|
|
466
|
+
@abstractmethod
|
|
467
|
+
def massage_data(cls, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
468
|
+
"""
|
|
469
|
+
Massage data before insert/update operations.
|
|
470
|
+
Database-specific data transformations.
|
|
471
|
+
"""
|
|
472
|
+
pass
|
|
473
|
+
|
|
474
|
+
@classmethod
|
|
475
|
+
@abstractmethod
|
|
476
|
+
def alter_trigger(cls, table: str, state: str = "ENABLE", name: str = "USER") -> str:
|
|
477
|
+
"""Generate ALTER TRIGGER statement."""
|
|
478
|
+
pass
|
|
479
|
+
|
|
480
|
+
@classmethod
|
|
481
|
+
@abstractmethod
|
|
482
|
+
def missing(
|
|
483
|
+
cls,
|
|
484
|
+
tx: Any,
|
|
485
|
+
table: str,
|
|
486
|
+
list_values: List[Any],
|
|
487
|
+
column: str = "SYS_ID",
|
|
488
|
+
where: Optional[Union[str, Dict, List]] = None,
|
|
489
|
+
) -> Tuple[str, List[Any]]:
|
|
490
|
+
"""
|
|
491
|
+
Generate query to find missing values from a list.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
tx: Database transaction
|
|
495
|
+
table: Table name
|
|
496
|
+
list_values: List of values to check
|
|
497
|
+
column: Column to check against
|
|
498
|
+
where: Additional WHERE conditions
|
|
499
|
+
|
|
500
|
+
Returns:
|
|
501
|
+
Tuple of (sql_string, parameters)
|
|
502
|
+
"""
|
|
503
|
+
pass
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Abstract base class for database type mapping implementations.
|
|
3
|
+
"""
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import Any, Type, Union
|
|
6
|
+
import datetime
|
|
7
|
+
import decimal
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class BaseTypes(ABC):
|
|
11
|
+
"""
|
|
12
|
+
Abstract base class that defines the interface for database type mappings.
|
|
13
|
+
|
|
14
|
+
Each database implementation should provide concrete implementations of these
|
|
15
|
+
type mapping methods to handle conversion between Python types and SQL types.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# Basic SQL type constants - should be overridden by subclasses
|
|
19
|
+
TEXT: str = "TEXT"
|
|
20
|
+
INTEGER: str = "INTEGER"
|
|
21
|
+
NUMERIC: str = "NUMERIC"
|
|
22
|
+
BOOLEAN: str = "BOOLEAN"
|
|
23
|
+
DATE: str = "DATE"
|
|
24
|
+
TIME: str = "TIME"
|
|
25
|
+
DATETIME: str = "DATETIME"
|
|
26
|
+
TIMESTAMP: str = "TIMESTAMP"
|
|
27
|
+
BINARY: str = "BINARY"
|
|
28
|
+
BIGINT: str = "BIGINT"
|
|
29
|
+
SMALLINT: str = "SMALLINT"
|
|
30
|
+
|
|
31
|
+
@classmethod
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def get_type(cls, v: Any) -> str:
|
|
34
|
+
"""
|
|
35
|
+
Returns a suitable SQL type string for a Python value/object.
|
|
36
|
+
|
|
37
|
+
This method should handle conversion of Python types to appropriate
|
|
38
|
+
SQL column types for table creation and schema operations.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
v: Python value or type to convert
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
SQL type string appropriate for this database
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
get_type(str) -> "TEXT"
|
|
48
|
+
get_type("hello") -> "TEXT"
|
|
49
|
+
get_type(int) -> "INTEGER"
|
|
50
|
+
get_type(123) -> "INTEGER"
|
|
51
|
+
get_type(datetime.datetime.now()) -> "TIMESTAMP"
|
|
52
|
+
"""
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
@abstractmethod
|
|
57
|
+
def get_conv(cls, v: Any) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Returns a base SQL type for expression usage (e.g. CAST operations).
|
|
60
|
+
|
|
61
|
+
This is typically used for CAST expressions and should return
|
|
62
|
+
the fundamental SQL type without precision/scale specifiers.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
v: Python value or type to convert
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
Base SQL type string for casting
|
|
69
|
+
|
|
70
|
+
Examples:
|
|
71
|
+
get_conv(decimal.Decimal("123.45")) -> "NUMERIC"
|
|
72
|
+
get_conv(datetime.datetime.now()) -> "TIMESTAMP"
|
|
73
|
+
"""
|
|
74
|
+
pass
|
|
75
|
+
|
|
76
|
+
@classmethod
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def py_type(cls, sql_type: str) -> Type:
|
|
79
|
+
"""
|
|
80
|
+
Returns the Python type that corresponds to an SQL type string.
|
|
81
|
+
|
|
82
|
+
This method handles the reverse mapping from SQL types back to
|
|
83
|
+
Python types, typically used when reading schema information.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
sql_type: SQL type string from database schema
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
Corresponding Python type
|
|
90
|
+
|
|
91
|
+
Examples:
|
|
92
|
+
py_type("INTEGER") -> int
|
|
93
|
+
py_type("TEXT") -> str
|
|
94
|
+
py_type("TIMESTAMP") -> datetime.datetime
|
|
95
|
+
|
|
96
|
+
Raises:
|
|
97
|
+
Exception: If the SQL type is not recognized
|
|
98
|
+
"""
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
@classmethod
|
|
102
|
+
def _handle_special_values(cls, v: Any) -> tuple[bool, str]:
|
|
103
|
+
"""
|
|
104
|
+
Helper method to handle special value prefixes like @@CURRENT_TIMESTAMP.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
v: Value to check
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Tuple of (is_special, processed_value)
|
|
111
|
+
"""
|
|
112
|
+
if isinstance(v, str) and v.startswith("@@"):
|
|
113
|
+
return True, v[2:] or cls.TEXT
|
|
114
|
+
return False, ""
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def _get_basic_type_mapping(cls) -> dict[Type, str]:
|
|
118
|
+
"""
|
|
119
|
+
Returns basic Python type to SQL type mappings.
|
|
120
|
+
Subclasses can override this to customize mappings.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Dictionary mapping Python types to SQL types
|
|
124
|
+
"""
|
|
125
|
+
return {
|
|
126
|
+
str: cls.TEXT,
|
|
127
|
+
int: cls.INTEGER,
|
|
128
|
+
float: cls.NUMERIC,
|
|
129
|
+
bool: cls.BOOLEAN,
|
|
130
|
+
datetime.datetime: cls.DATETIME,
|
|
131
|
+
datetime.date: cls.DATE,
|
|
132
|
+
datetime.time: cls.TIME,
|
|
133
|
+
decimal.Decimal: cls.NUMERIC,
|
|
134
|
+
bytes: cls.BINARY,
|
|
135
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from ..base.initializer import BaseInitializer
|
|
3
|
+
from velocity.db.core import engine
|
|
4
|
+
from .sql import SQL
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class MySQLInitializer(BaseInitializer):
|
|
8
|
+
"""MySQL database initializer."""
|
|
9
|
+
|
|
10
|
+
@staticmethod
|
|
11
|
+
def initialize(config=None, schema_locked=False, **kwargs):
|
|
12
|
+
"""
|
|
13
|
+
Initialize MySQL engine with mysql.connector driver.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
config: Configuration dictionary
|
|
17
|
+
schema_locked: Boolean to lock schema modifications
|
|
18
|
+
**kwargs: Additional configuration parameters
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
Configured Engine instance
|
|
22
|
+
"""
|
|
23
|
+
try:
|
|
24
|
+
import mysql.connector
|
|
25
|
+
except ImportError:
|
|
26
|
+
raise ImportError(
|
|
27
|
+
"MySQL connector not available. Install with: pip install mysql-connector-python"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
# Base configuration from environment (if available)
|
|
31
|
+
base_config = {
|
|
32
|
+
"database": os.environ.get("DBDatabase"),
|
|
33
|
+
"host": os.environ.get("DBHost"),
|
|
34
|
+
"port": os.environ.get("DBPort"),
|
|
35
|
+
"user": os.environ.get("DBUser"),
|
|
36
|
+
"password": os.environ.get("DBPassword"),
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Remove None values
|
|
40
|
+
base_config = {k: v for k, v in base_config.items() if v is not None}
|
|
41
|
+
|
|
42
|
+
# Set MySQL-specific defaults
|
|
43
|
+
mysql_defaults = {
|
|
44
|
+
"host": "localhost",
|
|
45
|
+
"port": 3306,
|
|
46
|
+
"charset": "utf8mb4",
|
|
47
|
+
"autocommit": False,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
# Merge configurations: defaults < env < config < kwargs
|
|
51
|
+
final_config = mysql_defaults.copy()
|
|
52
|
+
final_config.update(base_config)
|
|
53
|
+
final_config = MySQLInitializer._merge_config(final_config, config, **kwargs)
|
|
54
|
+
|
|
55
|
+
# Validate required configuration
|
|
56
|
+
required_keys = ["database", "host", "user"]
|
|
57
|
+
MySQLInitializer._validate_required_config(final_config, required_keys)
|
|
58
|
+
|
|
59
|
+
# Check for environment variable override for schema locking
|
|
60
|
+
if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
|
|
61
|
+
schema_locked = True
|
|
62
|
+
|
|
63
|
+
return engine.Engine(mysql.connector, final_config, SQL, schema_locked=schema_locked)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# Maintain backward compatibility
|
|
67
|
+
def initialize(config=None, schema_locked=False, **kwargs):
|
|
68
|
+
"""Backward compatible initialization function."""
|
|
69
|
+
# Check for environment variable override for schema locking
|
|
70
|
+
if os.environ.get("VELOCITY_SCHEMA_LOCKED", "").lower() in ('true', '1', 'yes'):
|
|
71
|
+
schema_locked = True
|
|
72
|
+
|
|
73
|
+
return MySQLInitializer.initialize(config, schema_locked, **kwargs)
|