velocity-python 0.0.140__py3-none-any.whl → 0.0.141__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.
Potentially problematic release.
This version of velocity-python might be problematic. Click here for more details.
- velocity/__init__.py +1 -1
- velocity/db/core/table.py +22 -0
- velocity/db/servers/mysql/sql.py +132 -5
- velocity/db/servers/postgres/sql.py +81 -15
- velocity/db/servers/sqlite/sql.py +127 -4
- velocity/db/servers/sqlserver/sql.py +158 -4
- {velocity_python-0.0.140.dist-info → velocity_python-0.0.141.dist-info}/METADATA +1 -1
- {velocity_python-0.0.140.dist-info → velocity_python-0.0.141.dist-info}/RECORD +11 -11
- {velocity_python-0.0.140.dist-info → velocity_python-0.0.141.dist-info}/WHEEL +0 -0
- {velocity_python-0.0.140.dist-info → velocity_python-0.0.141.dist-info}/licenses/LICENSE +0 -0
- {velocity_python-0.0.140.dist-info → velocity_python-0.0.141.dist-info}/top_level.txt +0 -0
velocity/__init__.py
CHANGED
velocity/db/core/table.py
CHANGED
|
@@ -163,6 +163,28 @@ class Table:
|
|
|
163
163
|
return self.name in [f"{x[0]}.{x[1]}" for x in result.as_tuple()]
|
|
164
164
|
return self.name in [x[1] for x in result.as_tuple()]
|
|
165
165
|
|
|
166
|
+
def ensure_sys_modified_count(self, **kwds):
|
|
167
|
+
"""
|
|
168
|
+
Ensure the sys_modified_count column and trigger exist for this table.
|
|
169
|
+
|
|
170
|
+
Returns early when the column is already present unless `force=True` is provided.
|
|
171
|
+
"""
|
|
172
|
+
force = kwds.get("force", False)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
columns = [col.lower() for col in self.sys_columns()]
|
|
176
|
+
except Exception:
|
|
177
|
+
columns = []
|
|
178
|
+
|
|
179
|
+
has_column = "sys_modified_count" in columns
|
|
180
|
+
if has_column and not force:
|
|
181
|
+
return
|
|
182
|
+
|
|
183
|
+
sql, vals = self.sql.ensure_sys_modified_count(self.name, has_column=has_column)
|
|
184
|
+
if kwds.get("sql_only", False):
|
|
185
|
+
return sql, vals
|
|
186
|
+
self.tx.execute(sql, vals, cursor=self.cursor())
|
|
187
|
+
|
|
166
188
|
def column(self, name):
|
|
167
189
|
"""
|
|
168
190
|
Returns a Column object for the given column name.
|
velocity/db/servers/mysql/sql.py
CHANGED
|
@@ -18,6 +18,18 @@ TableHelper.reserved = reserved_words
|
|
|
18
18
|
TableHelper.operators = OPERATORS
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
system_fields = [
|
|
22
|
+
"sys_id",
|
|
23
|
+
"sys_created",
|
|
24
|
+
"sys_modified",
|
|
25
|
+
"sys_modified_by",
|
|
26
|
+
"sys_dirty",
|
|
27
|
+
"sys_table",
|
|
28
|
+
"sys_modified_count",
|
|
29
|
+
"description",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
def quote(data):
|
|
22
34
|
"""Quote MySQL identifiers."""
|
|
23
35
|
if isinstance(data, list):
|
|
@@ -361,12 +373,127 @@ class SQL(BaseSQLDialect):
|
|
|
361
373
|
|
|
362
374
|
@classmethod
|
|
363
375
|
def create_table(cls, name, columns=None, drop=False):
|
|
376
|
+
if not name or not isinstance(name, str):
|
|
377
|
+
raise ValueError("Table name must be a non-empty string")
|
|
378
|
+
|
|
379
|
+
columns = columns or {}
|
|
380
|
+
table_identifier = quote(name)
|
|
381
|
+
base_name = name.split(".")[-1].replace("`", "")
|
|
382
|
+
base_name_sql = base_name.replace("'", "''")
|
|
383
|
+
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
|
|
384
|
+
|
|
385
|
+
statements = []
|
|
364
386
|
if drop:
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
387
|
+
statements.append(f"DROP TABLE IF EXISTS {table_identifier};")
|
|
388
|
+
|
|
389
|
+
statements.append(
|
|
390
|
+
f"""
|
|
391
|
+
CREATE TABLE {table_identifier} (
|
|
392
|
+
`sys_id` BIGINT NOT NULL AUTO_INCREMENT,
|
|
393
|
+
`sys_table` TEXT,
|
|
394
|
+
`sys_created` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
395
|
+
`sys_modified` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
396
|
+
`sys_modified_by` TEXT,
|
|
397
|
+
`sys_modified_count` INT NOT NULL DEFAULT 0,
|
|
398
|
+
`sys_dirty` TINYINT(1) NOT NULL DEFAULT 0,
|
|
399
|
+
`description` TEXT,
|
|
400
|
+
PRIMARY KEY (`sys_id`)
|
|
401
|
+
) ENGINE=InnoDB;
|
|
402
|
+
""".strip()
|
|
403
|
+
)
|
|
404
|
+
|
|
405
|
+
for key, val in columns.items():
|
|
406
|
+
clean_key = re.sub("<>!=%", "", key)
|
|
407
|
+
if clean_key in system_fields:
|
|
408
|
+
continue
|
|
409
|
+
col_type = TYPES.get_type(val)
|
|
410
|
+
statements.append(
|
|
411
|
+
f"ALTER TABLE {table_identifier} ADD COLUMN {quote(clean_key)} {col_type};"
|
|
412
|
+
)
|
|
413
|
+
|
|
414
|
+
statements.extend(
|
|
415
|
+
[
|
|
416
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_bi;",
|
|
417
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_bu;",
|
|
418
|
+
f"""
|
|
419
|
+
CREATE TRIGGER {trigger_prefix}_bi
|
|
420
|
+
BEFORE INSERT ON {table_identifier}
|
|
421
|
+
FOR EACH ROW
|
|
422
|
+
BEGIN
|
|
423
|
+
SET NEW.sys_created = COALESCE(NEW.sys_created, NOW());
|
|
424
|
+
SET NEW.sys_modified = NOW();
|
|
425
|
+
SET NEW.sys_modified_count = 0;
|
|
426
|
+
SET NEW.sys_dirty = IFNULL(NEW.sys_dirty, 0);
|
|
427
|
+
SET NEW.sys_table = '{base_name_sql}';
|
|
428
|
+
END;
|
|
429
|
+
""".strip(),
|
|
430
|
+
f"""
|
|
431
|
+
CREATE TRIGGER {trigger_prefix}_bu
|
|
432
|
+
BEFORE UPDATE ON {table_identifier}
|
|
433
|
+
FOR EACH ROW
|
|
434
|
+
BEGIN
|
|
435
|
+
IF OLD.sys_dirty = TRUE AND NEW.sys_dirty = FALSE THEN
|
|
436
|
+
SET NEW.sys_dirty = 0;
|
|
437
|
+
SET NEW.sys_modified_count = IFNULL(OLD.sys_modified_count, 0);
|
|
438
|
+
ELSE
|
|
439
|
+
SET NEW.sys_dirty = 1;
|
|
440
|
+
SET NEW.sys_modified_count = IFNULL(OLD.sys_modified_count, 0) + 1;
|
|
441
|
+
END IF;
|
|
442
|
+
SET NEW.sys_created = OLD.sys_created;
|
|
443
|
+
SET NEW.sys_modified = NOW();
|
|
444
|
+
SET NEW.sys_table = '{base_name_sql}';
|
|
445
|
+
END;
|
|
446
|
+
""".strip(),
|
|
447
|
+
]
|
|
448
|
+
)
|
|
449
|
+
|
|
450
|
+
return "\n".join(statements), tuple()
|
|
451
|
+
|
|
452
|
+
@classmethod
|
|
453
|
+
def ensure_sys_modified_count(cls, name):
|
|
454
|
+
"""Ensure sys_modified_count column and associated triggers exist for the table."""
|
|
455
|
+
table_identifier = quote(name)
|
|
456
|
+
base_name = name.split(".")[-1].replace("`", "")
|
|
457
|
+
base_name_sql = base_name.replace("'", "''")
|
|
458
|
+
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
|
|
459
|
+
|
|
460
|
+
statements = [
|
|
461
|
+
f"ALTER TABLE {table_identifier} ADD COLUMN IF NOT EXISTS `sys_modified_count` INT NOT NULL DEFAULT 0;",
|
|
462
|
+
f"UPDATE {table_identifier} SET `sys_modified_count` = 0 WHERE `sys_modified_count` IS NULL;",
|
|
463
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_bi;",
|
|
464
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_bu;",
|
|
465
|
+
f"""
|
|
466
|
+
CREATE TRIGGER {trigger_prefix}_bi
|
|
467
|
+
BEFORE INSERT ON {table_identifier}
|
|
468
|
+
FOR EACH ROW
|
|
469
|
+
BEGIN
|
|
470
|
+
SET NEW.sys_created = COALESCE(NEW.sys_created, NOW());
|
|
471
|
+
SET NEW.sys_modified = NOW();
|
|
472
|
+
SET NEW.sys_modified_count = 0;
|
|
473
|
+
SET NEW.sys_dirty = IFNULL(NEW.sys_dirty, 0);
|
|
474
|
+
SET NEW.sys_table = '{base_name_sql}';
|
|
475
|
+
END;
|
|
476
|
+
""".strip(),
|
|
477
|
+
f"""
|
|
478
|
+
CREATE TRIGGER {trigger_prefix}_bu
|
|
479
|
+
BEFORE UPDATE ON {table_identifier}
|
|
480
|
+
FOR EACH ROW
|
|
481
|
+
BEGIN
|
|
482
|
+
IF OLD.sys_dirty = TRUE AND NEW.sys_dirty = FALSE THEN
|
|
483
|
+
SET NEW.sys_dirty = 0;
|
|
484
|
+
SET NEW.sys_modified_count = IFNULL(OLD.sys_modified_count, 0);
|
|
485
|
+
ELSE
|
|
486
|
+
SET NEW.sys_dirty = 1;
|
|
487
|
+
SET NEW.sys_modified_count = IFNULL(OLD.sys_modified_count, 0) + 1;
|
|
488
|
+
END IF;
|
|
489
|
+
SET NEW.sys_created = OLD.sys_created;
|
|
490
|
+
SET NEW.sys_modified = NOW();
|
|
491
|
+
SET NEW.sys_table = '{base_name_sql}';
|
|
492
|
+
END;
|
|
493
|
+
""".strip(),
|
|
494
|
+
]
|
|
495
|
+
|
|
496
|
+
return "\n".join(statements), tuple()
|
|
370
497
|
|
|
371
498
|
@classmethod
|
|
372
499
|
def drop_table(cls, name):
|
|
@@ -734,11 +734,12 @@ class SQL(BaseSQLDialect):
|
|
|
734
734
|
f"""
|
|
735
735
|
CREATE TABLE {fqtn} (
|
|
736
736
|
sys_id BIGSERIAL PRIMARY KEY,
|
|
737
|
-
sys_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
738
737
|
sys_created TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
739
|
-
|
|
738
|
+
sys_modified TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
739
|
+
sys_modified_by TEXT NOT NULL DEFAULT 'SYSTEM',
|
|
740
|
+
sys_modified_count INTEGER NOT NULL DEFAULT 0,
|
|
740
741
|
sys_dirty BOOLEAN NOT NULL DEFAULT FALSE,
|
|
741
|
-
sys_table TEXT,
|
|
742
|
+
sys_table TEXT NOT NULL,
|
|
742
743
|
description TEXT
|
|
743
744
|
);
|
|
744
745
|
|
|
@@ -747,19 +748,29 @@ class SQL(BaseSQLDialect):
|
|
|
747
748
|
CREATE OR REPLACE FUNCTION {schema}.on_sys_modified()
|
|
748
749
|
RETURNS TRIGGER AS
|
|
749
750
|
$BODY$
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
751
|
+
BEGIN
|
|
752
|
+
IF (TG_OP = 'INSERT') THEN
|
|
753
|
+
NEW.sys_table := TG_TABLE_NAME;
|
|
754
|
+
NEW.sys_created := clock_timestamp();
|
|
755
|
+
NEW.sys_modified := clock_timestamp();
|
|
756
|
+
NEW.sys_modified_count := 0;
|
|
757
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
|
758
|
+
NEW.sys_table := TG_TABLE_NAME;
|
|
759
|
+
NEW.sys_created := OLD.sys_created;
|
|
760
|
+
NEW.sys_modified_count := COALESCE(OLD.sys_modified_count, 0);
|
|
761
|
+
IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN
|
|
762
|
+
IF OLD.sys_dirty IS TRUE AND NEW.sys_dirty IS FALSE THEN
|
|
763
|
+
NEW.sys_dirty := FALSE;
|
|
764
|
+
ELSE
|
|
765
|
+
NEW.sys_dirty := TRUE;
|
|
758
766
|
END IF;
|
|
759
|
-
|
|
760
|
-
NEW.
|
|
761
|
-
|
|
762
|
-
|
|
767
|
+
NEW.sys_modified := clock_timestamp();
|
|
768
|
+
NEW.sys_modified_count := COALESCE(OLD.sys_modified_count, 0) + 1;
|
|
769
|
+
END IF;
|
|
770
|
+
END IF;
|
|
771
|
+
|
|
772
|
+
RETURN NEW;
|
|
773
|
+
END;
|
|
763
774
|
$BODY$
|
|
764
775
|
LANGUAGE plpgsql VOLATILE
|
|
765
776
|
COST 100;
|
|
@@ -782,6 +793,61 @@ class SQL(BaseSQLDialect):
|
|
|
782
793
|
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
783
794
|
return sql, tuple()
|
|
784
795
|
|
|
796
|
+
@classmethod
|
|
797
|
+
def ensure_sys_modified_count(cls, name):
|
|
798
|
+
"""Return SQL to backfill sys_modified_count and refresh the on_sys_modified trigger."""
|
|
799
|
+
if "." in name:
|
|
800
|
+
fqtn = TableHelper.quote(name)
|
|
801
|
+
else:
|
|
802
|
+
fqtn = f"public.{TableHelper.quote(name)}"
|
|
803
|
+
schema, _ = fqtn.split(".")
|
|
804
|
+
trigger_name = f"on_update_row_{fqtn.replace('.', '_')}"
|
|
805
|
+
column_name = TableHelper.quote("sys_modified_count")
|
|
806
|
+
|
|
807
|
+
sql = [
|
|
808
|
+
f"ALTER TABLE {fqtn} ADD COLUMN {column_name} INTEGER NOT NULL DEFAULT 0;",
|
|
809
|
+
f"UPDATE {fqtn} SET {column_name} = 0 WHERE {column_name} IS NULL;",
|
|
810
|
+
f"""
|
|
811
|
+
CREATE OR REPLACE FUNCTION {schema}.on_sys_modified()
|
|
812
|
+
RETURNS TRIGGER AS
|
|
813
|
+
$BODY$
|
|
814
|
+
BEGIN
|
|
815
|
+
IF (TG_OP = 'INSERT') THEN
|
|
816
|
+
NEW.sys_table := TG_TABLE_NAME;
|
|
817
|
+
NEW.sys_created := clock_timestamp();
|
|
818
|
+
NEW.sys_modified := clock_timestamp();
|
|
819
|
+
NEW.sys_modified_count := 0;
|
|
820
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
|
821
|
+
NEW.sys_table := TG_TABLE_NAME;
|
|
822
|
+
NEW.sys_created := OLD.sys_created;
|
|
823
|
+
NEW.sys_modified_count := COALESCE(OLD.sys_modified_count, 0);
|
|
824
|
+
IF ROW(NEW.*) IS DISTINCT FROM ROW(OLD.*) THEN
|
|
825
|
+
IF OLD.sys_dirty IS TRUE AND NEW.sys_dirty IS FALSE THEN
|
|
826
|
+
NEW.sys_dirty := FALSE;
|
|
827
|
+
ELSE
|
|
828
|
+
NEW.sys_dirty := TRUE;
|
|
829
|
+
END IF;
|
|
830
|
+
NEW.sys_modified := clock_timestamp();
|
|
831
|
+
NEW.sys_modified_count := COALESCE(OLD.sys_modified_count, 0) + 1;
|
|
832
|
+
END IF;
|
|
833
|
+
END IF;
|
|
834
|
+
RETURN NEW;
|
|
835
|
+
END;
|
|
836
|
+
$BODY$
|
|
837
|
+
LANGUAGE plpgsql VOLATILE
|
|
838
|
+
COST 100;
|
|
839
|
+
""",
|
|
840
|
+
f"DROP TRIGGER IF EXISTS {trigger_name} ON {fqtn};",
|
|
841
|
+
f"""
|
|
842
|
+
CREATE TRIGGER {trigger_name}
|
|
843
|
+
BEFORE INSERT OR UPDATE ON {fqtn}
|
|
844
|
+
FOR EACH ROW EXECUTE PROCEDURE {schema}.on_sys_modified();
|
|
845
|
+
""",
|
|
846
|
+
]
|
|
847
|
+
|
|
848
|
+
sql = sqlparse.format(" ".join(sql), reindent=True, keyword_case="upper")
|
|
849
|
+
return sql, tuple()
|
|
850
|
+
|
|
785
851
|
@classmethod
|
|
786
852
|
def drop_table(cls, name):
|
|
787
853
|
return f"drop table if exists {TableHelper.quote(name)} cascade;", tuple()
|
|
@@ -18,6 +18,18 @@ TableHelper.reserved = reserved_words
|
|
|
18
18
|
TableHelper.operators = OPERATORS
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
system_fields = [
|
|
22
|
+
"sys_id",
|
|
23
|
+
"sys_created",
|
|
24
|
+
"sys_modified",
|
|
25
|
+
"sys_modified_by",
|
|
26
|
+
"sys_dirty",
|
|
27
|
+
"sys_table",
|
|
28
|
+
"sys_modified_count",
|
|
29
|
+
"description",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
def quote(data):
|
|
22
34
|
"""Quote SQLite identifiers."""
|
|
23
35
|
if isinstance(data, list):
|
|
@@ -344,11 +356,122 @@ class SQL(BaseSQLDialect):
|
|
|
344
356
|
|
|
345
357
|
@classmethod
|
|
346
358
|
def create_table(cls, name, columns=None, drop=False):
|
|
359
|
+
if not name or not isinstance(name, str):
|
|
360
|
+
raise ValueError("Table name must be a non-empty string")
|
|
361
|
+
|
|
362
|
+
columns = columns or {}
|
|
363
|
+
table_identifier = quote(name)
|
|
364
|
+
base_name = name.split(".")[-1].replace('"', "")
|
|
365
|
+
base_name_sql = base_name.replace("'", "''")
|
|
366
|
+
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
|
|
367
|
+
|
|
368
|
+
statements = []
|
|
347
369
|
if drop:
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
370
|
+
statements.append(f"DROP TABLE IF EXISTS {table_identifier};")
|
|
371
|
+
|
|
372
|
+
statements.append(
|
|
373
|
+
f"""
|
|
374
|
+
CREATE TABLE {table_identifier} (
|
|
375
|
+
"sys_id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
376
|
+
"sys_table" TEXT,
|
|
377
|
+
"sys_created" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
378
|
+
"sys_modified" TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
379
|
+
"sys_modified_by" TEXT,
|
|
380
|
+
"sys_modified_count" INTEGER NOT NULL DEFAULT 0,
|
|
381
|
+
"sys_dirty" INTEGER NOT NULL DEFAULT 0,
|
|
382
|
+
"description" TEXT
|
|
383
|
+
);
|
|
384
|
+
""".strip()
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
for key, val in columns.items():
|
|
388
|
+
clean_key = re.sub("<>!=%", "", key)
|
|
389
|
+
if clean_key in system_fields:
|
|
390
|
+
continue
|
|
391
|
+
col_type = TYPES.get_type(val)
|
|
392
|
+
statements.append(
|
|
393
|
+
f"ALTER TABLE {table_identifier} ADD COLUMN {quote(clean_key)} {col_type};"
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
statements.extend(
|
|
397
|
+
[
|
|
398
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_ai;",
|
|
399
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_au;",
|
|
400
|
+
f"""
|
|
401
|
+
CREATE TRIGGER {trigger_prefix}_ai
|
|
402
|
+
AFTER INSERT ON {table_identifier}
|
|
403
|
+
FOR EACH ROW
|
|
404
|
+
BEGIN
|
|
405
|
+
UPDATE {table_identifier}
|
|
406
|
+
SET sys_created = COALESCE(NEW.sys_created, CURRENT_TIMESTAMP),
|
|
407
|
+
sys_modified = CURRENT_TIMESTAMP,
|
|
408
|
+
sys_modified_count = 0,
|
|
409
|
+
sys_dirty = COALESCE(NEW.sys_dirty, 0),
|
|
410
|
+
sys_table = '{base_name_sql}'
|
|
411
|
+
WHERE rowid = NEW.rowid;
|
|
412
|
+
END;
|
|
413
|
+
""".strip(),
|
|
414
|
+
f"""
|
|
415
|
+
CREATE TRIGGER {trigger_prefix}_au
|
|
416
|
+
AFTER UPDATE ON {table_identifier}
|
|
417
|
+
FOR EACH ROW
|
|
418
|
+
BEGIN
|
|
419
|
+
UPDATE {table_identifier}
|
|
420
|
+
SET sys_created = OLD.sys_created,
|
|
421
|
+
sys_modified = CURRENT_TIMESTAMP,
|
|
422
|
+
sys_table = '{base_name_sql}',
|
|
423
|
+
sys_dirty = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN 0 ELSE 1 END,
|
|
424
|
+
sys_modified_count = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN COALESCE(OLD.sys_modified_count, 0) ELSE COALESCE(OLD.sys_modified_count, 0) + 1 END
|
|
425
|
+
WHERE rowid = NEW.rowid;
|
|
426
|
+
END;
|
|
427
|
+
""".strip(),
|
|
428
|
+
]
|
|
429
|
+
)
|
|
430
|
+
|
|
431
|
+
return "\n".join(statements), tuple()
|
|
432
|
+
|
|
433
|
+
@classmethod
|
|
434
|
+
def ensure_sys_modified_count(cls, name):
|
|
435
|
+
"""Ensure sys_modified_count exists for SQLite tables."""
|
|
436
|
+
table_identifier = quote(name)
|
|
437
|
+
base_name = name.split(".")[-1].replace('"', "")
|
|
438
|
+
base_name_sql = base_name.replace("'", "''")
|
|
439
|
+
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"cc_sysmod_{base_name}")
|
|
440
|
+
statements = [
|
|
441
|
+
f"ALTER TABLE {table_identifier} ADD COLUMN sys_modified_count INTEGER NOT NULL DEFAULT 0;",
|
|
442
|
+
f"UPDATE {table_identifier} SET sys_modified_count = 0 WHERE sys_modified_count IS NULL;",
|
|
443
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_ai;",
|
|
444
|
+
f"DROP TRIGGER IF EXISTS {trigger_prefix}_au;",
|
|
445
|
+
f"""
|
|
446
|
+
CREATE TRIGGER {trigger_prefix}_ai
|
|
447
|
+
AFTER INSERT ON {table_identifier}
|
|
448
|
+
FOR EACH ROW
|
|
449
|
+
BEGIN
|
|
450
|
+
UPDATE {table_identifier}
|
|
451
|
+
SET sys_created = COALESCE(NEW.sys_created, CURRENT_TIMESTAMP),
|
|
452
|
+
sys_modified = CURRENT_TIMESTAMP,
|
|
453
|
+
sys_modified_count = 0,
|
|
454
|
+
sys_dirty = COALESCE(NEW.sys_dirty, 0),
|
|
455
|
+
sys_table = '{base_name_sql}'
|
|
456
|
+
WHERE rowid = NEW.rowid;
|
|
457
|
+
END;
|
|
458
|
+
""".strip(),
|
|
459
|
+
f"""
|
|
460
|
+
CREATE TRIGGER {trigger_prefix}_au
|
|
461
|
+
AFTER UPDATE ON {table_identifier}
|
|
462
|
+
FOR EACH ROW
|
|
463
|
+
BEGIN
|
|
464
|
+
UPDATE {table_identifier}
|
|
465
|
+
SET sys_created = OLD.sys_created,
|
|
466
|
+
sys_modified = CURRENT_TIMESTAMP,
|
|
467
|
+
sys_table = '{base_name_sql}',
|
|
468
|
+
sys_dirty = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN 0 ELSE 1 END,
|
|
469
|
+
sys_modified_count = CASE WHEN OLD.sys_dirty = 1 AND NEW.sys_dirty = 0 THEN COALESCE(OLD.sys_modified_count, 0) ELSE COALESCE(OLD.sys_modified_count, 0) + 1 END
|
|
470
|
+
WHERE rowid = NEW.rowid;
|
|
471
|
+
END;
|
|
472
|
+
""".strip(),
|
|
473
|
+
]
|
|
474
|
+
return "\n".join(statements), tuple()
|
|
352
475
|
|
|
353
476
|
@classmethod
|
|
354
477
|
def drop_table(cls, name):
|
|
@@ -18,6 +18,18 @@ TableHelper.reserved = reserved_words
|
|
|
18
18
|
TableHelper.operators = OPERATORS
|
|
19
19
|
|
|
20
20
|
|
|
21
|
+
system_fields = [
|
|
22
|
+
"sys_id",
|
|
23
|
+
"sys_created",
|
|
24
|
+
"sys_modified",
|
|
25
|
+
"sys_modified_by",
|
|
26
|
+
"sys_dirty",
|
|
27
|
+
"sys_table",
|
|
28
|
+
"sys_modified_count",
|
|
29
|
+
"description",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
|
|
21
33
|
def quote(data):
|
|
22
34
|
"""Quote SQL Server identifiers."""
|
|
23
35
|
if isinstance(data, list):
|
|
@@ -383,11 +395,153 @@ class SQL(BaseSQLDialect):
|
|
|
383
395
|
|
|
384
396
|
@classmethod
|
|
385
397
|
def create_table(cls, name, columns=None, drop=False):
|
|
398
|
+
if not name or not isinstance(name, str):
|
|
399
|
+
raise ValueError("Table name must be a non-empty string")
|
|
400
|
+
|
|
401
|
+
columns = columns or {}
|
|
402
|
+
|
|
403
|
+
if "." in name:
|
|
404
|
+
schema_part, table_part = name.split(".", 1)
|
|
405
|
+
else:
|
|
406
|
+
schema_part = cls.default_schema or "dbo"
|
|
407
|
+
table_part = name
|
|
408
|
+
|
|
409
|
+
schema_identifier = quote(schema_part)
|
|
410
|
+
table_identifier = quote(name if "." in name else f"{schema_part}.{table_part}")
|
|
411
|
+
base_name = table_part.replace("[", "").replace("]", "")
|
|
412
|
+
base_name_sql = base_name.replace("'", "''")
|
|
413
|
+
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"CC_SYS_MOD_{base_name}")
|
|
414
|
+
|
|
415
|
+
statements = []
|
|
386
416
|
if drop:
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
417
|
+
statements.append(f"IF OBJECT_ID(N'{table_identifier}', N'U') IS NOT NULL DROP TABLE {table_identifier};")
|
|
418
|
+
|
|
419
|
+
statements.append(
|
|
420
|
+
f"""
|
|
421
|
+
CREATE TABLE {table_identifier} (
|
|
422
|
+
[sys_id] BIGINT IDENTITY(1,1) PRIMARY KEY,
|
|
423
|
+
[sys_table] NVARCHAR(255),
|
|
424
|
+
[sys_created] DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
|
|
425
|
+
[sys_modified] DATETIME2 NOT NULL DEFAULT SYSDATETIME(),
|
|
426
|
+
[sys_modified_by] NVARCHAR(255),
|
|
427
|
+
[sys_modified_count] INT NOT NULL DEFAULT 0,
|
|
428
|
+
[sys_dirty] BIT NOT NULL DEFAULT 0,
|
|
429
|
+
[description] NVARCHAR(MAX)
|
|
430
|
+
);
|
|
431
|
+
""".strip()
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
for key, val in columns.items():
|
|
435
|
+
clean_key = re.sub("<>!=%", "", key)
|
|
436
|
+
if clean_key in system_fields:
|
|
437
|
+
continue
|
|
438
|
+
col_type = TYPES.get_type(val)
|
|
439
|
+
statements.append(
|
|
440
|
+
f"ALTER TABLE {table_identifier} ADD {quote(clean_key)} {col_type};"
|
|
441
|
+
)
|
|
442
|
+
|
|
443
|
+
statements.extend(
|
|
444
|
+
[
|
|
445
|
+
f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_insert', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_insert;",
|
|
446
|
+
f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_update', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_update;",
|
|
447
|
+
f"""
|
|
448
|
+
CREATE TRIGGER {schema_identifier}.{trigger_prefix}_insert
|
|
449
|
+
ON {table_identifier}
|
|
450
|
+
AFTER INSERT
|
|
451
|
+
AS
|
|
452
|
+
BEGIN
|
|
453
|
+
SET NOCOUNT ON;
|
|
454
|
+
UPDATE t
|
|
455
|
+
SET sys_created = ISNULL(i.sys_created, SYSDATETIME()),
|
|
456
|
+
sys_modified = SYSDATETIME(),
|
|
457
|
+
sys_modified_count = 0,
|
|
458
|
+
sys_dirty = ISNULL(i.sys_dirty, 0),
|
|
459
|
+
sys_table = '{base_name_sql}'
|
|
460
|
+
FROM {table_identifier} AS t
|
|
461
|
+
INNER JOIN inserted AS i ON t.sys_id = i.sys_id;
|
|
462
|
+
END;
|
|
463
|
+
""".strip(),
|
|
464
|
+
f"""
|
|
465
|
+
CREATE TRIGGER {schema_identifier}.{trigger_prefix}_update
|
|
466
|
+
ON {table_identifier}
|
|
467
|
+
AFTER UPDATE
|
|
468
|
+
AS
|
|
469
|
+
BEGIN
|
|
470
|
+
SET NOCOUNT ON;
|
|
471
|
+
UPDATE t
|
|
472
|
+
SET sys_created = d.sys_created,
|
|
473
|
+
sys_modified = SYSDATETIME(),
|
|
474
|
+
sys_table = '{base_name_sql}',
|
|
475
|
+
sys_dirty = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN 0 ELSE 1 END,
|
|
476
|
+
sys_modified_count = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN ISNULL(d.sys_modified_count, 0) ELSE ISNULL(d.sys_modified_count, 0) + 1 END
|
|
477
|
+
FROM {table_identifier} AS t
|
|
478
|
+
INNER JOIN inserted AS i ON t.sys_id = i.sys_id
|
|
479
|
+
INNER JOIN deleted AS d ON d.sys_id = i.sys_id;
|
|
480
|
+
END;
|
|
481
|
+
""".strip(),
|
|
482
|
+
]
|
|
483
|
+
)
|
|
484
|
+
|
|
485
|
+
return "\n".join(statements), tuple()
|
|
486
|
+
|
|
487
|
+
@classmethod
|
|
488
|
+
def ensure_sys_modified_count(cls, name):
|
|
489
|
+
"""Ensure sys_modified_count exists for SQL Server tables along with maintenance triggers."""
|
|
490
|
+
if "." in name:
|
|
491
|
+
schema, table_name = name.split(".", 1)
|
|
492
|
+
else:
|
|
493
|
+
schema = cls.default_schema or "dbo"
|
|
494
|
+
table_name = name
|
|
495
|
+
|
|
496
|
+
schema_identifier = quote(schema)
|
|
497
|
+
table_identifier = quote(name if "." in name else f"{schema}.{table_name}")
|
|
498
|
+
object_name = f"[{schema}].[{table_name}]"
|
|
499
|
+
table_name_sql = table_name.replace("'", "''")
|
|
500
|
+
trigger_prefix = re.sub(r"[^0-9A-Za-z_]+", "_", f"CC_SYS_MOD_{table_name}")
|
|
501
|
+
|
|
502
|
+
statements = [
|
|
503
|
+
f"IF COL_LENGTH(N'{object_name}', 'sys_modified_count') IS NULL BEGIN ALTER TABLE {table_identifier} ADD sys_modified_count INT NOT NULL CONSTRAINT DF_{trigger_prefix}_COUNT DEFAULT (0); END;",
|
|
504
|
+
f"UPDATE {table_identifier} SET sys_modified_count = 0 WHERE sys_modified_count IS NULL;",
|
|
505
|
+
f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_insert', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_insert;",
|
|
506
|
+
f"IF OBJECT_ID(N'{schema_identifier}.{trigger_prefix}_update', N'TR') IS NOT NULL DROP TRIGGER {schema_identifier}.{trigger_prefix}_update;",
|
|
507
|
+
f"""
|
|
508
|
+
CREATE TRIGGER {schema_identifier}.{trigger_prefix}_insert
|
|
509
|
+
ON {table_identifier}
|
|
510
|
+
AFTER INSERT
|
|
511
|
+
AS
|
|
512
|
+
BEGIN
|
|
513
|
+
SET NOCOUNT ON;
|
|
514
|
+
UPDATE t
|
|
515
|
+
SET sys_created = ISNULL(i.sys_created, SYSDATETIME()),
|
|
516
|
+
sys_modified = SYSDATETIME(),
|
|
517
|
+
sys_modified_count = 0,
|
|
518
|
+
sys_dirty = ISNULL(i.sys_dirty, 0),
|
|
519
|
+
sys_table = '{table_name_sql}'
|
|
520
|
+
FROM {table_identifier} AS t
|
|
521
|
+
INNER JOIN inserted AS i ON t.sys_id = i.sys_id;
|
|
522
|
+
END;
|
|
523
|
+
""".strip(),
|
|
524
|
+
f"""
|
|
525
|
+
CREATE TRIGGER {schema_identifier}.{trigger_prefix}_update
|
|
526
|
+
ON {table_identifier}
|
|
527
|
+
AFTER UPDATE
|
|
528
|
+
AS
|
|
529
|
+
BEGIN
|
|
530
|
+
SET NOCOUNT ON;
|
|
531
|
+
UPDATE t
|
|
532
|
+
SET sys_created = d.sys_created,
|
|
533
|
+
sys_modified = SYSDATETIME(),
|
|
534
|
+
sys_table = '{table_name_sql}',
|
|
535
|
+
sys_dirty = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN 0 ELSE 1 END,
|
|
536
|
+
sys_modified_count = CASE WHEN d.sys_dirty = 1 AND i.sys_dirty = 0 THEN ISNULL(d.sys_modified_count, 0) ELSE ISNULL(d.sys_modified_count, 0) + 1 END
|
|
537
|
+
FROM {table_identifier} AS t
|
|
538
|
+
INNER JOIN inserted AS i ON t.sys_id = i.sys_id
|
|
539
|
+
INNER JOIN deleted AS d ON d.sys_id = i.sys_id;
|
|
540
|
+
END;
|
|
541
|
+
""".strip(),
|
|
542
|
+
]
|
|
543
|
+
|
|
544
|
+
return "\n".join(statements), tuple()
|
|
391
545
|
|
|
392
546
|
@classmethod
|
|
393
547
|
def drop_table(cls, name):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
velocity/__init__.py,sha256=
|
|
1
|
+
velocity/__init__.py,sha256=3h_G3zNbrrqbYLhThVKaATymiHglPHlkI24zAtD_s-A,147
|
|
2
2
|
velocity/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
3
|
velocity/app/invoices.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
4
|
velocity/app/orders.py,sha256=fr1oTBjSFfyeMBUXRG06LV4jgwrlwYNL5mbEBleFwf0,6328
|
|
@@ -36,7 +36,7 @@ velocity/db/core/engine.py,sha256=mNlaFPruHO935phKPVrsxZprGYUvxW-zp2sBcBZ-KCg,20
|
|
|
36
36
|
velocity/db/core/result.py,sha256=b0ie3yZAOj9S57x32uFFGKZ95zhImmZ0iXl0X1qYszc,12813
|
|
37
37
|
velocity/db/core/row.py,sha256=yqxm03uEDy3oSbnkCtKyiqFdSqG3zXTq2HIHYKOvPY4,7291
|
|
38
38
|
velocity/db/core/sequence.py,sha256=VMBc0ZjGnOaWTwKW6xMNTdP8rZ2umQ8ml4fHTTwuGq4,3904
|
|
39
|
-
velocity/db/core/table.py,sha256=
|
|
39
|
+
velocity/db/core/table.py,sha256=zzyVZA8azaxB0QPxNjGFUOEwhk62emAZvHhkFlnACyY,35450
|
|
40
40
|
velocity/db/core/transaction.py,sha256=unjmVkkfb7D8Wow6V8V8aLaxUZo316i--ksZxc4-I1Q,6613
|
|
41
41
|
velocity/db/servers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
42
|
velocity/db/servers/tablehelper.py,sha256=Q48ObN5KD_U2sBP0GUcjaQjKeE4Hr351sPQirwQ0_1s,22163
|
|
@@ -48,22 +48,22 @@ velocity/db/servers/base/types.py,sha256=3LBxFCD35eeIsIqftpAJh0JjUVonDYemz2n6AMt
|
|
|
48
48
|
velocity/db/servers/mysql/__init__.py,sha256=mASO5JB0xkzYngwx2X79yyKifYRqxIdfKFWutIHuw7k,2661
|
|
49
49
|
velocity/db/servers/mysql/operators.py,sha256=wHmVSPxlPGbOdvQEmsfKhD25H8djovSbNcmacLHDVkI,1273
|
|
50
50
|
velocity/db/servers/mysql/reserved.py,sha256=s-aFMwYJpZ_1FBcCMU8fOdhml2ET58-59ZnUm7iw5OU,3312
|
|
51
|
-
velocity/db/servers/mysql/sql.py,sha256=
|
|
51
|
+
velocity/db/servers/mysql/sql.py,sha256=_Ea84IJwILV4Mf70zM3r3PdFL2VHU2jWSCujFhiJlQM,22170
|
|
52
52
|
velocity/db/servers/mysql/types.py,sha256=BMQf4TpsRo1JN-yOl1nSItTO-Juu2piSTNy5o_djBeM,3486
|
|
53
53
|
velocity/db/servers/postgres/__init__.py,sha256=6YcTLXposmsrEaJgdUAM_QgD1TZDSILQrGcwWZ-dibk,2457
|
|
54
54
|
velocity/db/servers/postgres/operators.py,sha256=y9k6enReeR5hJxU_lYYR2epoaw4qCxEqmYJJ5jjaVWA,1166
|
|
55
55
|
velocity/db/servers/postgres/reserved.py,sha256=5tKLaqFV-HrWRj-nsrxl5KGbmeM3ukn_bPZK36XEu8M,3648
|
|
56
|
-
velocity/db/servers/postgres/sql.py,sha256=
|
|
56
|
+
velocity/db/servers/postgres/sql.py,sha256=iZxvALFr3dpJm9BX_WE97E9RdM2RDCmi4M35HwXknVA,50141
|
|
57
57
|
velocity/db/servers/postgres/types.py,sha256=W71x8iRx-IIJkQSjb29k-KGkqp-QS6SxB0BHYXd4k8w,6955
|
|
58
58
|
velocity/db/servers/sqlite/__init__.py,sha256=EIx09YN1-Vm-4CXVcEf9DBgvd8FhIN9rEqIaSRrEcIk,2293
|
|
59
59
|
velocity/db/servers/sqlite/operators.py,sha256=VzZgph8RrnHkIVqqWGqnJwcafgBzc_8ZQp-M8tMl-mw,1221
|
|
60
60
|
velocity/db/servers/sqlite/reserved.py,sha256=4vOI06bjt8wg9KxdzDTF-iOd-ewY23NvSzthpdty2fA,1298
|
|
61
|
-
velocity/db/servers/sqlite/sql.py,sha256=
|
|
61
|
+
velocity/db/servers/sqlite/sql.py,sha256=GD5m4oxvWTisfn-umnKtM0Qz8VUXTUnAqQWi9Uv2trQ,20908
|
|
62
62
|
velocity/db/servers/sqlite/types.py,sha256=jpCJeV25x4Iytf6D6GXgK3hVYFAAFV4WKJC-d-m4kdU,3102
|
|
63
63
|
velocity/db/servers/sqlserver/__init__.py,sha256=LN8OycN7W8da_ZPRYnPQ-O3Bv_xjret9qV1ZCitZlOU,2708
|
|
64
64
|
velocity/db/servers/sqlserver/operators.py,sha256=xK8_doDLssS38SRs1NoAI7XTO0-gNGMDS76nTVru4kE,1104
|
|
65
65
|
velocity/db/servers/sqlserver/reserved.py,sha256=Gn5n9DjxcjM-7PsIZPYigD6XLvMAYGnz1IrPuN7Dp2Y,2120
|
|
66
|
-
velocity/db/servers/sqlserver/sql.py,sha256=
|
|
66
|
+
velocity/db/servers/sqlserver/sql.py,sha256=sdmFFzt_Np9mVRGPkFsnWfYPD8z88V9Sp5no3-maPHM,26208
|
|
67
67
|
velocity/db/servers/sqlserver/types.py,sha256=FAODYEO137m-WugpM89f9bQN9q6S2cjjUaz0a9gfE6M,3745
|
|
68
68
|
velocity/db/tests/__init__.py,sha256=7-hilWb43cKnSnCeXcjFG-6LpziN5k443IpsIvuevP0,24
|
|
69
69
|
velocity/db/tests/common_db_test.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -121,8 +121,8 @@ velocity/misc/tests/test_merge.py,sha256=Vm5_jY5cVczw0hZF-3TYzmxFw81heJOJB-dvhCg
|
|
|
121
121
|
velocity/misc/tests/test_oconv.py,sha256=fy4DwWGn_v486r2d_3ACpuBD-K1oOngNq1HJCGH7X-M,4694
|
|
122
122
|
velocity/misc/tests/test_original_error.py,sha256=iWSd18tckOA54LoPQOGV5j9LAz2W-3_ZOwmyZ8-4YQc,1742
|
|
123
123
|
velocity/misc/tests/test_timer.py,sha256=l9nrF84kHaFofvQYKInJmfoqC01wBhsUB18lVBgXCoo,2758
|
|
124
|
-
velocity_python-0.0.
|
|
125
|
-
velocity_python-0.0.
|
|
126
|
-
velocity_python-0.0.
|
|
127
|
-
velocity_python-0.0.
|
|
128
|
-
velocity_python-0.0.
|
|
124
|
+
velocity_python-0.0.141.dist-info/licenses/LICENSE,sha256=aoN245GG8s9oRUU89KNiGTU4_4OtnNmVi4hQeChg6rM,1076
|
|
125
|
+
velocity_python-0.0.141.dist-info/METADATA,sha256=kFOu082TsOvWmzCFNLoEcchpv47afcYqmPScq42gWtI,34262
|
|
126
|
+
velocity_python-0.0.141.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
127
|
+
velocity_python-0.0.141.dist-info/top_level.txt,sha256=JW2vJPmodgdgSz7H6yoZvnxF8S3fTMIv-YJWCT1sNW0,9
|
|
128
|
+
velocity_python-0.0.141.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|