Spaces:
Running
fix(ddl): CHECK sanitizer no longer deletes columns named CHECKOUT_*
Browse filesThe chunk-level CHECK-constraint detector used prefix matching
(upper.startswith("CHECK")), so any column whose name begins with CHECK —
CHECKOUT_INITIATED, CHECKOUT_ATTEMPTS, CHECKOUT_CONVERSION_RATE,
CHECKOUT_ABANDONMENT_RATE — was silently dropped from CREATE TABLE while the
generated dataset still carried its values. The INSERT then failed on columns
the table didn't have. Root cause of the "random" schema/data-mismatch build
failures (Cloudscape, Atlas Freight partial, Tixr run 02ec3e7baba6) — it fired
whenever the LLM blueprint author named a column CHECKOUT_*, which
checkout-funnel briefs (Tixr) invite.
Detector now requires the CHECK keyword followed by a parenthesized
expression, and the CONSTRAINT branch uses a word-boundary match so an FK
named CHECKOUT_FK survives. Inline CHECK removal already used \bCHECK\b and
was safe. 4 regression tests added.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- cdw_connector.py +9 -1
- tests/test_cdw_ddl_sanitizer.py +70 -0
|
@@ -258,7 +258,15 @@ class SnowflakeDeployer:
|
|
| 258 |
continue
|
| 259 |
|
| 260 |
upper = stripped.upper()
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
if is_table_check:
|
| 263 |
removed_any = True
|
| 264 |
continue
|
|
|
|
| 258 |
continue
|
| 259 |
|
| 260 |
upper = stripped.upper()
|
| 261 |
+
# A table-level CHECK constraint is the keyword CHECK followed by a
|
| 262 |
+
# parenthesized expression — NOT any chunk that merely starts with the
|
| 263 |
+
# letters CHECK. A column named CHECKOUT_* (e.g. CHECKOUT_ATTEMPTS
|
| 264 |
+
# NUMBER(38,2)) must survive; prefix matching silently deleted such
|
| 265 |
+
# columns from CREATE TABLE while the dataset still carried their
|
| 266 |
+
# values (the FACT_PLATFORM_ENGAGEMENT / CHECKOUT_INITIATED bug).
|
| 267 |
+
is_table_check = bool(re.match(r"CHECK\s*\(", upper)) or (
|
| 268 |
+
upper.startswith("CONSTRAINT") and re.search(r"\bCHECK\s*\(", upper)
|
| 269 |
+
)
|
| 270 |
if is_table_check:
|
| 271 |
removed_any = True
|
| 272 |
continue
|
|
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Regression tests for the CREATE TABLE CHECK-constraint sanitizer.
|
| 2 |
+
|
| 3 |
+
The bug: the chunk-level CHECK detector used prefix matching
|
| 4 |
+
(upper.startswith("CHECK")), so any COLUMN whose name begins with CHECK —
|
| 5 |
+
CHECKOUT_INITIATED, CHECKOUT_ATTEMPTS, CHECKOUT_CONVERSION_RATE... — was
|
| 6 |
+
silently deleted from the CREATE TABLE while the generated dataset still
|
| 7 |
+
carried its values. The INSERT then referenced columns the table didn't have
|
| 8 |
+
(the FACT_PLATFORM_ENGAGEMENT failure caught by the dataset_writer guard).
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
from cdw_connector import SnowflakeDeployer
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def _sanitize(statement: str) -> str:
|
| 15 |
+
return SnowflakeDeployer()._sanitize_create_table_statement(statement)
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
def test_checkout_columns_survive_and_table_check_is_removed():
|
| 19 |
+
stmt = (
|
| 20 |
+
"CREATE TABLE FACT_PLATFORM_ENGAGEMENT (\n"
|
| 21 |
+
" ENGAGEMENT_KEY NUMBER NOT NULL,\n"
|
| 22 |
+
" CHECKOUT_ATTEMPTS NUMBER(38,0) NOT NULL,\n"
|
| 23 |
+
" CHECKOUT_CONVERSION_RATE NUMBER(38,2) NOT NULL,\n"
|
| 24 |
+
" CHECKOUT_ABANDONMENT_RATE NUMBER(38,2) NOT NULL,\n"
|
| 25 |
+
" CHECK (CHECKOUT_ATTEMPTS >= 0)\n"
|
| 26 |
+
")"
|
| 27 |
+
)
|
| 28 |
+
out = _sanitize(stmt)
|
| 29 |
+
assert "CHECKOUT_ATTEMPTS NUMBER(38,0)" in out
|
| 30 |
+
assert "CHECKOUT_CONVERSION_RATE" in out
|
| 31 |
+
assert "CHECKOUT_ABANDONMENT_RATE" in out
|
| 32 |
+
assert "CHECK (" not in out
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def test_named_constraint_starting_with_check_is_only_removed_when_it_is_a_check():
|
| 36 |
+
stmt = (
|
| 37 |
+
"CREATE TABLE T (\n"
|
| 38 |
+
" ID NUMBER NOT NULL,\n"
|
| 39 |
+
" CHECKOUT_KEY NUMBER NOT NULL,\n"
|
| 40 |
+
" CONSTRAINT CHECKOUT_FK FOREIGN KEY (CHECKOUT_KEY) REFERENCES DIM_CHECKOUT(CHECKOUT_KEY),\n"
|
| 41 |
+
" CONSTRAINT POSITIVE_ID CHECK (ID > 0)\n"
|
| 42 |
+
")"
|
| 43 |
+
)
|
| 44 |
+
out = _sanitize(stmt)
|
| 45 |
+
# FK constraint whose NAME contains CHECK must survive.
|
| 46 |
+
assert "FOREIGN KEY (CHECKOUT_KEY)" in out
|
| 47 |
+
# Real named CHECK constraint must go.
|
| 48 |
+
assert "POSITIVE_ID" not in out
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
def test_inline_check_clause_removed_but_column_kept():
|
| 52 |
+
stmt = (
|
| 53 |
+
"CREATE TABLE T (\n"
|
| 54 |
+
" ID NUMBER NOT NULL,\n"
|
| 55 |
+
" QTY NUMBER CHECK (QTY >= 0) NOT NULL\n"
|
| 56 |
+
")"
|
| 57 |
+
)
|
| 58 |
+
out = _sanitize(stmt)
|
| 59 |
+
assert "QTY NUMBER" in out
|
| 60 |
+
assert "CHECK (" not in out
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
def test_statement_without_checks_is_untouched():
|
| 64 |
+
stmt = (
|
| 65 |
+
"CREATE TABLE DIM_CHECKOUT (\n"
|
| 66 |
+
" CHECKOUT_KEY NUMBER NOT NULL,\n"
|
| 67 |
+
" CHECKOUT_CHANNEL VARCHAR NOT NULL\n"
|
| 68 |
+
")"
|
| 69 |
+
)
|
| 70 |
+
assert _sanitize(stmt) == stmt
|