Skip to content

fix(plugins): stop the column-default contract mangling literals, expressions and constraints - #2693

Merged
datlechin merged 1 commit into
mainfrom
fix/column-default-contract
Sep 9, 2026
Merged

fix(plugins): stop the column-default contract mangling literals, expressions and constraints#2693
datlechin merged 1 commit into
mainfrom
fix/column-default-contract

Conversation

@datlechin

@datlechin datlechin commented Sep 9, 2026

Copy link
Copy Markdown
Member

What this is

Follow-up to #2690, which redefined defaultValue as the exact SQL that follows DEFAULT. Seven defects that change left behind, four of them regressions #2690 introduced. All were found by a multi-agent audit of the merged contract and each was verified against a live server or a compiled probe before being fixed.

The regressions #2690 introduced

MySQL 8 reads every DEFAULT CURRENT_TIMESTAMP back as (CURRENT_TIMESTAMP). MySQL 8.0.13+ marks a plain CURRENT_TIMESTAMP default DEFAULT_GENERATED in EXTRA, exactly like a real expression, so the temporal branch was skipped and the value was parenthesised. Measured on 8.4.11. The temporal question is now answered before the marker is consulted.

MySQL numeric, BIT and binary defaults were quoted. The catalog reports a string default stripped of its quotes, but a number, a BIT default (b'1') and a binary default (0x61) come back as the literals they already are. Quoting them all was wrong three ways, measured on 8.4.11:

Column Emitted Result
qty INT DEFAULT 0 DEFAULT '0' Accepted, but MariaDB reports 0, so Compare & Sync reports the same difference forever
flag BIT(1) DEFAULT b'1' DEFAULT 'b''1''' ERROR 1067, and the save fails
b VARBINARY(8) DEFAULT 0x61 DEFAULT '0x61' Accepted, and the stored default silently changes from the byte 0x61 to the four characters 0x61 (HEX() goes from 61 to 30783631)

mysqlCatalogReportsLiteralAsSQL now answers this by column type.

A bare keyword default crossed engines unquoted. CrossEngineDefaultValue kept any bare word on the documented premise that "every target's DDL writer quotes an unrecognised word", which was true before #2690 and false after. Measured: PostgreSQL 17.11 reports session_user, current_role, current_schema and user as bare words, and Copy To emitted DEFAULT SESSION_USER into MySQL 8.4, which fails 1064/1054. The CREATE TABLE fails, so that table's rows never arrive. The arm is deleted; such a default now falls to the existing .drop(reason:) and is reported as a conversion note.

The enum cell's default badge stopped appearing on MySQL. EnumMenuPicker matches the column default against the enum's own unquoted tokens, and the default now arrives as 'active'. The value crosses out of SQL at that boundary now.

The other three

An expression default reached MySQL without the parentheses it requires. MariaDB writes uuid() bare and MySQL 8 needs (uuid()). mysqlDefaultValueLiteral now parenthesises every expression for MySQL and leaves MariaDB alone, which fixes it in one place for Copy To, the Structure tab's Custom… > SQL expression mode and SQL import. A value is a literal by shape (quoted string, number, bit or hex literal, NULL), not by an allowlist.

SQL Server dropped a column's DEFAULT constraint and never re-added it. The drop fired on defaultChanged || needsTypeChange; the re-add only on defaultChanged. So changing only a column's type or nullability left it with no default, and every later INSERT that omitted it failed with error 515. Pre-existing, verified with a probe over the real generator. The re-add now mirrors the drop.

A ClickHouse MATERIALIZED, EPHEMERAL or ALIAS column could still be converted. #2690 stopped the silent conversion; deliberately picking a default for such a column still restated it as DEFAULT. The kind has nowhere to ride on a column definition and generateModifyColumnSQL is synchronous, so the driver now answers from what it read out of system.columns itself and refuses.

Verified

Step Result
build PASS
test (11 suites, 201 cases) PASS
test (DamengDriverTests scheme) PASS, 40 tests
plugins (AllPlugins) Only the known oracle-nio @TaskLocal macro failure in the vendored dependency, which breaks this aggregate locally on every branch. No error names a file in this change
docs PASS
lint Clean for this change; the two legacy_swiftui_aspect_ratio findings are in files this branch never touches and are present on main

The MySQL argument tables now pin the measured catalog shapes (0, b'1', 0x61, enum(...), and CURRENT_TIMESTAMP with and without the DEFAULT_GENERATED marker), and the drift guard from #2690 is restated as the invariant it was always meant to check: the writer adds the syntax an engine's grammar demands and never re-quotes a value.

Known and not fixed here

  • Cross-engine Copy To still does not translate defaults within a type family. MySQL and MariaDB share SQLTypeFamily.mysql, so needsTranslation is false and the translator returns the snapshot untouched. The MariaDB-to-MySQL case is closed by the driver-side parenthesising above, but Redshift lacking gen_random_uuid() and CockroachDB's unique_rowid() are not: CrossEngineDefaultValue is keyed on the family, not the engine, so it cannot tell them apart. Fixing that means giving it the target DatabaseType and adding a defaults-only pass to the same-family branch, without flipping translated, which gates sequence copying and the value coercer.
  • A Replace copy drops every target table before any create, and supportsTransactionalDDL is false on MySQL and MariaDB, so a failing create loses the dropped tables with no rollback and the result view does not say so. Pre-existing and independent of this change.
  • Already-installed registry plugins predate the contract and still apply the old quoting. There is no version signal to gate on: feat(datagrid): offer the engine's own default values on the Structure tab's Default cell #2690 took no ABI bump and its only PluginKit diff is doc comments, so a stale and a rebuilt plugin both declare TableProPluginKitVersion 23. Publishing Snowflake, Teradata, Oracle, Dameng, DuckDB, libSQL and Cloudflare D1 with the app release is what closes it.

@datlechin
datlechin merged commit b24e231 into main Sep 9, 2026
8 checks passed
@datlechin
datlechin deleted the fix/column-default-contract branch September 9, 2026 06:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant