velocity-python 0.0.154__tar.gz → 0.0.156__tar.gz
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_python-0.0.154 → velocity_python-0.0.156}/PKG-INFO +1 -1
- {velocity_python-0.0.154 → velocity_python-0.0.156}/pyproject.toml +1 -1
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/__init__.py +1 -1
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/engine.py +3 -2
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/table.py +39 -14
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/transaction.py +9 -4
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/mysql/sql.py +32 -10
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/postgres/sql.py +165 -91
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlite/sql.py +34 -10
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlserver/sql.py +31 -12
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_table_comprehensive.py +3 -1
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_db_utils.py +49 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/utils.py +67 -4
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity_python.egg-info/PKG-INFO +1 -1
- {velocity_python-0.0.154 → velocity_python-0.0.156}/tests/test_sys_modified_count_postgres_demo.py +2 -2
- {velocity_python-0.0.154 → velocity_python-0.0.156}/LICENSE +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/README.md +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/setup.cfg +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/invoices.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/orders.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/payments.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/purchase_orders.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/tests/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/tests/test_email_processing.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/tests/test_payment_profile_sorting.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/app/tests/test_spreadsheet_functions.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/amplify.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/base_handler.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/context.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/exceptions.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/lambda_handler.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/mixins/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/mixins/activity_tracker.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/mixins/aws_session_mixin.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/mixins/error_handler.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/mixins/legacy_mixin.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/mixins/standard_mixin.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/response.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/handlers/sqs_handler.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/tests/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/tests/test_lambda_handler_json_serialization.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/aws/tests/test_response.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/column.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/database.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/decorators.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/result.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/row.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/core/sequence.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/exceptions.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/base/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/base/initializer.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/base/operators.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/base/sql.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/base/types.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/mysql/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/mysql/operators.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/mysql/reserved.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/mysql/types.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/postgres/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/postgres/operators.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/postgres/reserved.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/postgres/types.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlite/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlite/operators.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlite/reserved.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlite/types.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlserver/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlserver/operators.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlserver/reserved.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/sqlserver/types.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/servers/tablehelper.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/common_db_test.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/common.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_column.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_connections.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_database.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_engine.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_general_usage.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_imports.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_result.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_row.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_row_comprehensive.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_schema_locking.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_schema_locking_unit.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_sequence.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_sql_comprehensive.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_table.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/postgres/test_transaction.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/sql/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/sql/common.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/sql/test_postgres_select_advanced.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/sql/test_postgres_select_variances.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_cursor_rowcount_fix.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_postgres.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_postgres_unchanged.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_process_error_robustness.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_result_caching.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_result_sql_aware.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_row_get_missing_column.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_schema_locking_initializers.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_schema_locking_simple.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_sql_builder.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/db/tests/test_tablehelper.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/conv/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/conv/iconv.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/conv/oconv.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/db.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/export.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/format.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/mail.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/merge.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/__init__.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_db.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_fix.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_format.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_iconv.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_merge.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_oconv.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_original_error.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tests/test_timer.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/timer.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity/misc/tools.py +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity_python.egg-info/SOURCES.txt +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity_python.egg-info/dependency_links.txt +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity_python.egg-info/requires.txt +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/src/velocity_python.egg-info/top_level.txt +0 -0
- {velocity_python-0.0.154 → velocity_python-0.0.156}/tests/test_where_clause_validation.py +0 -0
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import inspect
|
|
2
2
|
import re
|
|
3
|
-
import os
|
|
4
3
|
from contextlib import contextmanager
|
|
5
4
|
from functools import wraps
|
|
6
5
|
from velocity.db import exceptions
|
|
7
6
|
from velocity.db.core.transaction import Transaction
|
|
7
|
+
from velocity.db.utils import mask_config_for_display
|
|
8
8
|
|
|
9
9
|
import logging
|
|
10
10
|
|
|
@@ -27,7 +27,8 @@ class Engine:
|
|
|
27
27
|
self.__schema_locked = schema_locked
|
|
28
28
|
|
|
29
29
|
def __str__(self):
|
|
30
|
-
|
|
30
|
+
safe_config = mask_config_for_display(self.config)
|
|
31
|
+
return f"[{self.sql.server}] engine({safe_config})"
|
|
31
32
|
|
|
32
33
|
def connect(self):
|
|
33
34
|
"""
|
|
@@ -24,7 +24,24 @@ class Query:
|
|
|
24
24
|
return self.sql
|
|
25
25
|
|
|
26
26
|
|
|
27
|
+
SYSTEM_COLUMN_NAMES = (
|
|
28
|
+
"sys_id",
|
|
29
|
+
"sys_created",
|
|
30
|
+
"sys_modified",
|
|
31
|
+
"sys_modified_by",
|
|
32
|
+
"sys_modified_row",
|
|
33
|
+
"sys_modified_count",
|
|
34
|
+
"sys_dirty",
|
|
35
|
+
"sys_table",
|
|
36
|
+
"description",
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
_SYSTEM_COLUMN_SET = {name.lower() for name in SYSTEM_COLUMN_NAMES}
|
|
40
|
+
|
|
41
|
+
|
|
27
42
|
class Table:
|
|
43
|
+
SYSTEM_COLUMNS = SYSTEM_COLUMN_NAMES
|
|
44
|
+
|
|
28
45
|
"""
|
|
29
46
|
Provides an interface for performing CRUD and metadata operations on a DB table.
|
|
30
47
|
"""
|
|
@@ -94,9 +111,15 @@ class Table:
|
|
|
94
111
|
|
|
95
112
|
def columns(self):
|
|
96
113
|
"""
|
|
97
|
-
Returns column names
|
|
114
|
+
Returns non-system column names.
|
|
98
115
|
"""
|
|
99
|
-
return [col for col in self.sys_columns() if not
|
|
116
|
+
return [col for col in self.sys_columns() if not self.is_system_column(col)]
|
|
117
|
+
|
|
118
|
+
@staticmethod
|
|
119
|
+
def is_system_column(column_name):
|
|
120
|
+
if not column_name:
|
|
121
|
+
return False
|
|
122
|
+
return column_name.lower() in _SYSTEM_COLUMN_SET or column_name.lower().startswith("sys_")
|
|
100
123
|
|
|
101
124
|
@return_default(None, (exceptions.DbObjectExistsError,))
|
|
102
125
|
def create_index(
|
|
@@ -217,12 +240,8 @@ class Table:
|
|
|
217
240
|
return self.name in [f"{x[0]}.{x[1]}" for x in result.as_tuple()]
|
|
218
241
|
return self.name in [x[1] for x in result.as_tuple()]
|
|
219
242
|
|
|
220
|
-
def
|
|
221
|
-
"""
|
|
222
|
-
Ensure the sys_modified_count column and trigger exist for this table.
|
|
223
|
-
|
|
224
|
-
Returns early when the column is already present unless `force=True` is provided.
|
|
225
|
-
"""
|
|
243
|
+
def ensure_system_columns(self, **kwds):
|
|
244
|
+
"""Ensure Velocity system columns and triggers exist for this table."""
|
|
226
245
|
force = kwds.get("force", False)
|
|
227
246
|
|
|
228
247
|
try:
|
|
@@ -230,15 +249,21 @@ class Table:
|
|
|
230
249
|
except Exception:
|
|
231
250
|
columns = []
|
|
232
251
|
|
|
233
|
-
|
|
234
|
-
has_row_column = "sys_modified_row" in columns
|
|
252
|
+
sql_method = getattr(self.sql, "ensure_system_columns", None)
|
|
235
253
|
|
|
236
|
-
if
|
|
237
|
-
|
|
254
|
+
if sql_method is None:
|
|
255
|
+
raise AttributeError(
|
|
256
|
+
f"{self.sql.__class__.__name__} does not implement ensure_system_columns"
|
|
257
|
+
)
|
|
238
258
|
|
|
239
|
-
|
|
240
|
-
self.name,
|
|
259
|
+
result = sql_method(
|
|
260
|
+
self.name, existing_columns=columns, force=force
|
|
241
261
|
)
|
|
262
|
+
|
|
263
|
+
if not result:
|
|
264
|
+
return
|
|
265
|
+
|
|
266
|
+
sql, vals = result
|
|
242
267
|
if kwds.get("sql_only", False):
|
|
243
268
|
return sql, vals
|
|
244
269
|
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
@@ -6,6 +6,7 @@ from velocity.db.core.result import Result
|
|
|
6
6
|
from velocity.db.core.column import Column
|
|
7
7
|
from velocity.db.core.database import Database
|
|
8
8
|
from velocity.db.core.sequence import Sequence
|
|
9
|
+
from velocity.db.utils import mask_config_for_display
|
|
9
10
|
from velocity.misc.db import randomword
|
|
10
11
|
|
|
11
12
|
debug = False
|
|
@@ -22,10 +23,14 @@ class Transaction:
|
|
|
22
23
|
self.__pg_types = {}
|
|
23
24
|
|
|
24
25
|
def __str__(self):
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
26
|
+
config = mask_config_for_display(self.engine.config)
|
|
27
|
+
|
|
28
|
+
if isinstance(config, dict):
|
|
29
|
+
server = config.get("host", config.get("server"))
|
|
30
|
+
database = config.get("database", config.get("dbname"))
|
|
31
|
+
return f"{self.engine.sql.server}.transaction({server}:{database})"
|
|
32
|
+
|
|
33
|
+
return f"{self.engine.sql.server}.transaction({config})"
|
|
29
34
|
|
|
30
35
|
def __enter__(self):
|
|
31
36
|
return self
|
|
@@ -450,19 +450,40 @@ END;
|
|
|
450
450
|
return "\n".join(statements), tuple()
|
|
451
451
|
|
|
452
452
|
@classmethod
|
|
453
|
-
def
|
|
454
|
-
"""Ensure
|
|
453
|
+
def ensure_system_columns(cls, name, existing_columns=None, force=False):
|
|
454
|
+
"""Ensure MySQL tables maintain the Velocity system metadata."""
|
|
455
|
+
existing_columns = {col.lower() for col in existing_columns or []}
|
|
456
|
+
|
|
455
457
|
table_identifier = quote(name)
|
|
456
458
|
base_name = name.split(".")[-1].replace("`", "")
|
|
457
459
|
base_name_sql = base_name.replace("'", "''")
|
|
458
460
|
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
|
|
459
461
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
462
|
+
has_count = "sys_modified_count" in existing_columns
|
|
463
|
+
|
|
464
|
+
add_column = not has_count
|
|
465
|
+
recreate_triggers = force or add_column
|
|
466
|
+
|
|
467
|
+
if not recreate_triggers and not force:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
statements = []
|
|
471
|
+
|
|
472
|
+
if add_column:
|
|
473
|
+
statements.append(
|
|
474
|
+
f"ALTER TABLE {table_identifier} ADD COLUMN IF NOT EXISTS `sys_modified_count` INT NOT NULL DEFAULT 0;"
|
|
475
|
+
)
|
|
476
|
+
|
|
477
|
+
statements.append(
|
|
478
|
+
f"UPDATE {table_identifier} SET `sys_modified_count` = 0 WHERE `sys_modified_count` IS NULL;"
|
|
479
|
+
)
|
|
480
|
+
|
|
481
|
+
statements.append(f"DROP TRIGGER IF EXISTS {trigger_prefix}_bi;")
|
|
482
|
+
statements.append(f"DROP TRIGGER IF EXISTS {trigger_prefix}_bu;")
|
|
483
|
+
|
|
484
|
+
statements.extend(
|
|
485
|
+
[
|
|
486
|
+
f"""
|
|
466
487
|
CREATE TRIGGER {trigger_prefix}_bi
|
|
467
488
|
BEFORE INSERT ON {table_identifier}
|
|
468
489
|
FOR EACH ROW
|
|
@@ -474,7 +495,7 @@ BEGIN
|
|
|
474
495
|
SET NEW.sys_table = '{base_name_sql}';
|
|
475
496
|
END;
|
|
476
497
|
""".strip(),
|
|
477
|
-
|
|
498
|
+
f"""
|
|
478
499
|
CREATE TRIGGER {trigger_prefix}_bu
|
|
479
500
|
BEFORE UPDATE ON {table_identifier}
|
|
480
501
|
FOR EACH ROW
|
|
@@ -491,7 +512,8 @@ BEGIN
|
|
|
491
512
|
SET NEW.sys_table = '{base_name_sql}';
|
|
492
513
|
END;
|
|
493
514
|
""".strip(),
|
|
494
|
-
|
|
515
|
+
]
|
|
516
|
+
)
|
|
495
517
|
|
|
496
518
|
return "\n".join(statements), tuple()
|
|
497
519
|
|
|
@@ -23,6 +23,8 @@ system_fields = [
|
|
|
23
23
|
"sys_created",
|
|
24
24
|
"sys_modified",
|
|
25
25
|
"sys_modified_by",
|
|
26
|
+
"sys_modified_row",
|
|
27
|
+
"sys_modified_count",
|
|
26
28
|
"sys_dirty",
|
|
27
29
|
"sys_table",
|
|
28
30
|
"description",
|
|
@@ -799,37 +801,13 @@ class SQL(BaseSQLDialect):
|
|
|
799
801
|
def drop_database(cls, name):
|
|
800
802
|
return f"drop database if exists {name}", tuple()
|
|
801
803
|
|
|
802
|
-
@
|
|
803
|
-
def
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
else:
|
|
807
|
-
fqtn = f"public.{TableHelper.quote(name)}"
|
|
808
|
-
schema, table = fqtn.split(".")
|
|
809
|
-
name = fqtn.replace(".", "_")
|
|
810
|
-
sql = []
|
|
811
|
-
if drop:
|
|
812
|
-
sql.append(cls.drop_table(fqtn)[0])
|
|
813
|
-
sql.append(
|
|
814
|
-
f"""
|
|
815
|
-
CREATE TABLE {fqtn} (
|
|
816
|
-
sys_id BIGSERIAL PRIMARY KEY,
|
|
817
|
-
sys_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
818
|
-
sys_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
819
|
-
sys_modified_by TEXT NOT NULL DEFAULT 'SYSTEM',
|
|
820
|
-
sys_modified_row TIMESTAMP NOT NULL DEFAULT CLOCK_TIMESTAMP(),
|
|
821
|
-
sys_modified_count INTEGER NOT NULL DEFAULT 0,
|
|
822
|
-
sys_dirty BOOLEAN NOT NULL DEFAULT FALSE,
|
|
823
|
-
sys_table TEXT NOT NULL,
|
|
824
|
-
description TEXT
|
|
825
|
-
);
|
|
826
|
-
|
|
827
|
-
SELECT SETVAL(PG_GET_SERIAL_SEQUENCE('{fqtn}', 'sys_id'),1000,TRUE);
|
|
828
|
-
|
|
829
|
-
CREATE OR REPLACE FUNCTION {schema}.on_sys_modified()
|
|
804
|
+
@staticmethod
|
|
805
|
+
def _sys_modified_function_sql(schema_identifier):
|
|
806
|
+
return f"""
|
|
807
|
+
CREATE OR REPLACE FUNCTION {schema_identifier}.on_sys_modified()
|
|
830
808
|
RETURNS TRIGGER AS
|
|
831
809
|
$BODY$
|
|
832
|
-
|
|
810
|
+
BEGIN
|
|
833
811
|
IF (TG_OP = 'INSERT') THEN
|
|
834
812
|
NEW.sys_table := TG_TABLE_NAME;
|
|
835
813
|
NEW.sys_created := transaction_timestamp();
|
|
@@ -847,19 +825,58 @@ class SQL(BaseSQLDialect):
|
|
|
847
825
|
NEW.sys_dirty := TRUE;
|
|
848
826
|
END IF;
|
|
849
827
|
NEW.sys_modified := transaction_timestamp();
|
|
828
|
+
NEW.sys_modified_row := clock_timestamp();
|
|
850
829
|
NEW.sys_modified_count := COALESCE(OLD.sys_modified_count, 0) + 1;
|
|
851
830
|
END IF;
|
|
852
831
|
END IF;
|
|
853
|
-
|
|
854
832
|
RETURN NEW;
|
|
855
|
-
|
|
833
|
+
END;
|
|
856
834
|
$BODY$
|
|
857
835
|
LANGUAGE plpgsql VOLATILE
|
|
858
836
|
COST 100;
|
|
837
|
+
"""
|
|
838
|
+
|
|
839
|
+
@classmethod
|
|
840
|
+
def create_table(cls, name, columns={}, drop=False):
|
|
841
|
+
if "." in name:
|
|
842
|
+
fqtn = TableHelper.quote(name)
|
|
843
|
+
else:
|
|
844
|
+
fqtn = f"public.{TableHelper.quote(name)}"
|
|
859
845
|
|
|
860
|
-
|
|
846
|
+
schema, table = fqtn.split(".")
|
|
847
|
+
schema_unquoted = schema.replace('"', "")
|
|
848
|
+
table_unquoted = table.replace('"', "")
|
|
849
|
+
trigger_name = (
|
|
850
|
+
f"on_update_row_{schema_unquoted}_{table_unquoted}".replace(".", "_")
|
|
851
|
+
)
|
|
852
|
+
trigger_identifier = TableHelper.quote(trigger_name)
|
|
853
|
+
schema_identifier = TableHelper.quote(schema_unquoted)
|
|
854
|
+
sql = []
|
|
855
|
+
if drop:
|
|
856
|
+
sql.append(cls.drop_table(fqtn)[0])
|
|
857
|
+
sql.append(
|
|
858
|
+
f"""
|
|
859
|
+
CREATE TABLE {fqtn} (
|
|
860
|
+
sys_id BIGSERIAL PRIMARY KEY,
|
|
861
|
+
sys_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
862
|
+
sys_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
863
|
+
sys_modified_by TEXT NOT NULL DEFAULT 'SYSTEM',
|
|
864
|
+
sys_modified_row TIMESTAMP NOT NULL DEFAULT CLOCK_TIMESTAMP(),
|
|
865
|
+
sys_modified_count INTEGER NOT NULL DEFAULT 0,
|
|
866
|
+
sys_dirty BOOLEAN NOT NULL DEFAULT FALSE,
|
|
867
|
+
sys_table TEXT NOT NULL,
|
|
868
|
+
description TEXT
|
|
869
|
+
);
|
|
870
|
+
|
|
871
|
+
SELECT SETVAL(PG_GET_SERIAL_SEQUENCE('{fqtn}', 'sys_id'),1000,TRUE);
|
|
872
|
+
|
|
873
|
+
{cls._sys_modified_function_sql(schema_identifier)}
|
|
874
|
+
|
|
875
|
+
DROP TRIGGER IF EXISTS {trigger_identifier} ON {fqtn};
|
|
876
|
+
|
|
877
|
+
CREATE TRIGGER {trigger_identifier}
|
|
861
878
|
BEFORE INSERT OR UPDATE ON {fqtn}
|
|
862
|
-
FOR EACH ROW EXECUTE PROCEDURE {
|
|
879
|
+
FOR EACH ROW EXECUTE PROCEDURE {schema_identifier}.on_sys_modified();
|
|
863
880
|
|
|
864
881
|
"""
|
|
865
882
|
)
|
|
@@ -876,81 +893,138 @@ class SQL(BaseSQLDialect):
|
|
|
876
893
|
return sql, tuple()
|
|
877
894
|
|
|
878
895
|
@classmethod
|
|
879
|
-
def
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
896
|
+
def ensure_system_columns(cls, name, existing_columns=None, force=False):
|
|
897
|
+
"""Ensure all Velocity system columns and triggers exist for the table."""
|
|
898
|
+
existing_columns = {
|
|
899
|
+
col.lower() for col in (existing_columns or [])
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
required_columns = [
|
|
903
|
+
"sys_id",
|
|
904
|
+
"sys_created",
|
|
905
|
+
"sys_modified",
|
|
906
|
+
"sys_modified_by",
|
|
907
|
+
"sys_modified_row",
|
|
908
|
+
"sys_modified_count",
|
|
909
|
+
"sys_dirty",
|
|
910
|
+
"sys_table",
|
|
911
|
+
"description",
|
|
912
|
+
]
|
|
913
|
+
|
|
914
|
+
missing_columns = [
|
|
915
|
+
column for column in required_columns if column not in existing_columns
|
|
916
|
+
]
|
|
917
|
+
|
|
918
|
+
if not missing_columns and not force:
|
|
919
|
+
return None
|
|
920
|
+
|
|
883
921
|
if "." in name:
|
|
884
922
|
schema_name, table_name = name.split(".", 1)
|
|
885
923
|
else:
|
|
886
924
|
schema_name = cls.default_schema
|
|
887
925
|
table_name = name
|
|
888
926
|
|
|
889
|
-
|
|
890
|
-
|
|
927
|
+
schema_unquoted = schema_name.replace('"', "")
|
|
928
|
+
table_unquoted = table_name.replace('"', "")
|
|
929
|
+
|
|
930
|
+
schema_identifier = TableHelper.quote(schema_unquoted)
|
|
931
|
+
table_identifier = TableHelper.quote(table_unquoted)
|
|
891
932
|
fqtn = f"{schema_identifier}.{table_identifier}"
|
|
892
933
|
|
|
893
934
|
trigger_name = (
|
|
894
|
-
f"on_update_row_{
|
|
895
|
-
.replace(".", "_")
|
|
896
|
-
.replace('"', "")
|
|
935
|
+
f"on_update_row_{schema_unquoted}_{table_unquoted}".replace(".", "_")
|
|
897
936
|
)
|
|
898
937
|
trigger_identifier = TableHelper.quote(trigger_name)
|
|
899
|
-
|
|
900
|
-
row_column_name = TableHelper.quote("sys_modified_row")
|
|
901
|
-
|
|
938
|
+
|
|
902
939
|
statements = []
|
|
903
|
-
|
|
940
|
+
added_columns = set()
|
|
941
|
+
columns_after = set(existing_columns)
|
|
942
|
+
|
|
943
|
+
if "sys_id" in missing_columns:
|
|
904
944
|
statements.append(
|
|
905
|
-
f"ALTER TABLE {fqtn} ADD COLUMN {
|
|
945
|
+
f"ALTER TABLE {fqtn} ADD COLUMN {TableHelper.quote('sys_id')} BIGSERIAL PRIMARY KEY;"
|
|
906
946
|
)
|
|
907
|
-
|
|
947
|
+
added_columns.add("sys_id")
|
|
948
|
+
columns_after.add("sys_id")
|
|
949
|
+
|
|
950
|
+
column_definitions = {
|
|
951
|
+
"sys_created": "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
952
|
+
"sys_modified": "TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP",
|
|
953
|
+
"sys_modified_by": "TEXT NOT NULL DEFAULT 'SYSTEM'",
|
|
954
|
+
"sys_modified_row": "TIMESTAMP NOT NULL DEFAULT CLOCK_TIMESTAMP()",
|
|
955
|
+
"sys_modified_count": "INTEGER NOT NULL DEFAULT 0",
|
|
956
|
+
"sys_dirty": "BOOLEAN NOT NULL DEFAULT FALSE",
|
|
957
|
+
"sys_table": "TEXT",
|
|
958
|
+
"description": "TEXT",
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
for column, definition in column_definitions.items():
|
|
962
|
+
if column in missing_columns:
|
|
963
|
+
statements.append(
|
|
964
|
+
f"ALTER TABLE {fqtn} ADD COLUMN {TableHelper.quote(column)} {definition};"
|
|
965
|
+
)
|
|
966
|
+
added_columns.add(column)
|
|
967
|
+
columns_after.add(column)
|
|
968
|
+
|
|
969
|
+
default_map = {
|
|
970
|
+
"sys_created": "CURRENT_TIMESTAMP",
|
|
971
|
+
"sys_modified": "CURRENT_TIMESTAMP",
|
|
972
|
+
"sys_modified_by": "'SYSTEM'",
|
|
973
|
+
"sys_modified_row": "CLOCK_TIMESTAMP()",
|
|
974
|
+
"sys_modified_count": "0",
|
|
975
|
+
"sys_dirty": "FALSE",
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
for column, default_sql in default_map.items():
|
|
979
|
+
if column in columns_after and (force or column in added_columns):
|
|
980
|
+
quoted_column = TableHelper.quote(column)
|
|
981
|
+
statements.append(
|
|
982
|
+
f"UPDATE {fqtn} SET {quoted_column} = {default_sql} WHERE {quoted_column} IS NULL;"
|
|
983
|
+
)
|
|
984
|
+
statements.append(
|
|
985
|
+
f"ALTER TABLE {fqtn} ALTER COLUMN {quoted_column} SET DEFAULT {default_sql};"
|
|
986
|
+
)
|
|
987
|
+
|
|
988
|
+
if "sys_table" in columns_after and (force or "sys_table" in added_columns):
|
|
989
|
+
quoted_column = TableHelper.quote("sys_table")
|
|
990
|
+
table_literal = table_unquoted.replace("'", "''")
|
|
908
991
|
statements.append(
|
|
909
|
-
f"
|
|
992
|
+
f"UPDATE {fqtn} SET {quoted_column} = COALESCE({quoted_column}, '{table_literal}') WHERE {quoted_column} IS NULL;"
|
|
910
993
|
)
|
|
911
994
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
COST 100;
|
|
946
|
-
""",
|
|
947
|
-
f"DROP TRIGGER IF EXISTS {trigger_identifier} ON {fqtn};",
|
|
948
|
-
f"""
|
|
949
|
-
CREATE TRIGGER {trigger_identifier}
|
|
950
|
-
BEFORE INSERT OR UPDATE ON {fqtn}
|
|
951
|
-
FOR EACH ROW EXECUTE PROCEDURE {schema_identifier}.on_sys_modified();
|
|
952
|
-
""",
|
|
953
|
-
])
|
|
995
|
+
not_null_columns = {
|
|
996
|
+
"sys_created",
|
|
997
|
+
"sys_modified",
|
|
998
|
+
"sys_modified_by",
|
|
999
|
+
"sys_modified_row",
|
|
1000
|
+
"sys_modified_count",
|
|
1001
|
+
"sys_dirty",
|
|
1002
|
+
"sys_table",
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
for column in not_null_columns:
|
|
1006
|
+
if column in columns_after and (force or column in added_columns):
|
|
1007
|
+
statements.append(
|
|
1008
|
+
f"ALTER TABLE {fqtn} ALTER COLUMN {TableHelper.quote(column)} SET NOT NULL;"
|
|
1009
|
+
)
|
|
1010
|
+
|
|
1011
|
+
reset_trigger = force or bool(added_columns)
|
|
1012
|
+
|
|
1013
|
+
if reset_trigger:
|
|
1014
|
+
statements.append(
|
|
1015
|
+
f"DROP TRIGGER IF EXISTS {trigger_identifier} ON {fqtn};"
|
|
1016
|
+
)
|
|
1017
|
+
statements.append(cls._sys_modified_function_sql(schema_identifier))
|
|
1018
|
+
statements.append(
|
|
1019
|
+
f"""
|
|
1020
|
+
CREATE TRIGGER {trigger_identifier}
|
|
1021
|
+
BEFORE INSERT OR UPDATE ON {fqtn}
|
|
1022
|
+
FOR EACH ROW EXECUTE PROCEDURE {schema_identifier}.on_sys_modified();
|
|
1023
|
+
"""
|
|
1024
|
+
)
|
|
1025
|
+
|
|
1026
|
+
if not statements:
|
|
1027
|
+
return None
|
|
954
1028
|
|
|
955
1029
|
sql = sqlparse.format(
|
|
956
1030
|
" ".join(statements), reindent=True, keyword_case="upper"
|
|
@@ -431,18 +431,40 @@ END;
|
|
|
431
431
|
return "\n".join(statements), tuple()
|
|
432
432
|
|
|
433
433
|
@classmethod
|
|
434
|
-
def
|
|
435
|
-
"""Ensure
|
|
434
|
+
def ensure_system_columns(cls, name, existing_columns=None, force=False):
|
|
435
|
+
"""Ensure SQLite tables maintain the Velocity system triggers/columns."""
|
|
436
|
+
existing_columns = {col.lower() for col in existing_columns or []}
|
|
437
|
+
|
|
436
438
|
table_identifier = quote(name)
|
|
437
439
|
base_name = name.split(".")[-1].replace('"', "")
|
|
438
440
|
base_name_sql = base_name.replace("'", "''")
|
|
439
441
|
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
442
|
+
|
|
443
|
+
has_count = "sys_modified_count" in existing_columns
|
|
444
|
+
|
|
445
|
+
add_column = not has_count
|
|
446
|
+
recreate_triggers = force or add_column
|
|
447
|
+
|
|
448
|
+
if not recreate_triggers and not force:
|
|
449
|
+
return None
|
|
450
|
+
|
|
451
|
+
statements = []
|
|
452
|
+
|
|
453
|
+
if add_column:
|
|
454
|
+
statements.append(
|
|
455
|
+
f"ALTER TABLE {table_identifier} ADD COLUMN sys_modified_count INTEGER NOT NULL DEFAULT 0;"
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
statements.append(
|
|
459
|
+
f"UPDATE {table_identifier} SET sys_modified_count = 0 WHERE sys_modified_count IS NULL;"
|
|
460
|
+
)
|
|
461
|
+
|
|
462
|
+
statements.append(f"DROP TRIGGER IF EXISTS {trigger_prefix}_ai;")
|
|
463
|
+
statements.append(f"DROP TRIGGER IF EXISTS {trigger_prefix}_au;")
|
|
464
|
+
|
|
465
|
+
statements.extend(
|
|
466
|
+
[
|
|
467
|
+
f"""
|
|
446
468
|
CREATE TRIGGER {trigger_prefix}_ai
|
|
447
469
|
AFTER INSERT ON {table_identifier}
|
|
448
470
|
FOR EACH ROW
|
|
@@ -456,7 +478,7 @@ BEGIN
|
|
|
456
478
|
WHERE rowid = NEW.rowid;
|
|
457
479
|
END;
|
|
458
480
|
""".strip(),
|
|
459
|
-
|
|
481
|
+
f"""
|
|
460
482
|
CREATE TRIGGER {trigger_prefix}_au
|
|
461
483
|
AFTER UPDATE ON {table_identifier}
|
|
462
484
|
FOR EACH ROW
|
|
@@ -470,7 +492,9 @@ BEGIN
|
|
|
470
492
|
WHERE rowid = NEW.rowid;
|
|
471
493
|
END;
|
|
472
494
|
""".strip(),
|
|
473
|
-
|
|
495
|
+
]
|
|
496
|
+
)
|
|
497
|
+
|
|
474
498
|
return "\n".join(statements), tuple()
|
|
475
499
|
|
|
476
500
|
@classmethod
|