Skip to content

feat(datagrid)!: add an On Update column for MySQL and MariaDB timestamps (#2005) - #2007

Merged
datlechin merged 1 commit into
mainfrom
fix-2005-mysql-on-update
Aug 1, 2026
Merged

feat(datagrid)!: add an On Update column for MySQL and MariaDB timestamps (#2005)#2007
datlechin merged 1 commit into
mainfrom
fix-2005-mysql-on-update

Conversation

@datlechin

Copy link
Copy Markdown
Member

Closes #2005.

The issue asked for a missing field. Tracing it found an active corruption bug sitting behind it, so this fixes both.

The data-loss bug

EditableColumnDefinition.from(_:) hardcoded onUpdate: nil, so the value the server reported was thrown away on every load. MySQL's generateModifyColumnSQL restates the whole column definition, and MODIFY/CHANGE COLUMN drops whatever the restated definition omits.

The result: editing any attribute of a MySQL or MariaDB column that already had ON UPDATE CURRENT_TIMESTAMP silently stripped the clause on save. A rename, a comment, a charset change, anything. No error, no warning. This is DataGrip's DBE-4079 verbatim: "can lead to column property loss by simply renaming the column."

PostgreSQL and SQL Server emit per-attribute ALTER COLUMN and were never exposed. ClickHouse uses the same full-redefinition shape but has no attribute of this kind modelled yet.

The field

StructureColumnField gains .onUpdate, ordered next to .defaultValue since it is that clause's companion. It renders as a YES/NO dropdown through the existing customDropdownOptions path, the same one already driving Nullable, Primary Key, and Auto Inc, so the grid, the inspector, and the Create Table sheet all pick it up with no separate wiring.

Why a dropdown and not a combo box. The HIG points at a combo box for "a list plus arbitrary text", but the vocabulary here is genuinely closed. The emit whitelist only accepts CURRENT_TIMESTAMP forms and silently discards anything else, and MySQL requires the fractional precision to match the column's own type, so a hand-typed CURRENT_TIMESTAMP(3) on a TIMESTAMP(6) column is just a server error. A combo box would let people type values that visibly stick in the cell and then vanish at save time. Sequel Ace, the one tool that has had this right for a decade, uses a fixed popup for the same reason.

Why not a raw Extra passthrough. It would collide with the existing first-class Auto Inc column, and reading information_schema.COLUMNS.EXTRA straight into DDL is what shipped syntax errors in TablePlus (#1078) and DBeaver (#7850, #13577), because MySQL 8.0.13+ puts the non-DDL token DEFAULT_GENERATED in that column. The parser here matches a substring and re-emits a canonical expression, so a compound EXTRA value parses correctly and no server token ever reaches generated SQL.

MySQL DDL builder

buildColumnDefinitionSQL and generateMoveColumnSQL were independently repeating the same eight clauses, including a verbatim copy of the ON UPDATE block. Both now share one builder.

Fractional-second precision is derived from the column's declared type rather than stored or typed, so a TIMESTAMP(6) column gets ON UPDATE CURRENT_TIMESTAMP(6), and a stale (6) carried on a DATETIME(3) column is corrected instead of trusted.

That builder also fixes a sibling bug in the same function: the default branch accepted CURRENT_TIMESTAMP and CURRENT_TIMESTAMP() but not CURRENT_TIMESTAMP(6), so a TIMESTAMP(6) column's default was quoted into DEFAULT 'CURRENT_TIMESTAMP(6)' and the ALTER failed. Auto-deriving precision makes those columns reachable, so leaving it would have shipped a landmine next to the new feature.

Breaking PluginKit change

StructureColumnField was marked @frozen, so adding a case is breaking. It is also un-frozen here, in the same bump.

The release work is identical either way, and the case set was never closed: PostgreSQL already populates identityKind and isGenerated with no home in this enum, and SQL Server computed columns, ClickHouse default-kinds, and SQLite generated columns are all the same shape. Paying the bump once and un-freezing means the next per-engine field is additive and free. By CLAUDE.md's own criterion ("mark @frozen only when an exhaustive switch forces it and its case set is genuinely closed") the marking was already wrong.

scripts/check-pluginkit-abi.sh main confirms it, including one consequence I had not predicted:

-@frozen public enum StructureColumnField : Swift::String, Swift::Sendable, Swift::CaseIterable {
+public enum StructureColumnField : Swift::String, Swift::Sendable, Swift::CaseIterable {
   case defaultValue
+  case onUpdate
-extension TableProPluginKit::StructureColumnField : Swift::BitwiseCopyable {}

Un-freezing also drops the synthesized BitwiseCopyable conformance, since a resilient enum's layout is not statically known across the module boundary. That independently confirms the breaking classification. It is a compiler-synthesized marker protocol, not a requirement any plugin implements, and the raised version floor rejects a stale plugin before it could observe the difference.

Do not add the abi-additive label. This diff is breaking on two counts.

Release order matters

currentPluginKitVersion and minimumCompatiblePluginKitVersion both go 18 → 19, with all 29 plugin Info.plist files.

.github/workflows/build.yml gates Create GitHub Release on check-registry-readiness.py, so scripts/release-all-plugins.sh 19 must run before or with the app release across the 16 registry plugins. Otherwise the release job fails and users on the new app hit noCompatibleBinary.

MariaDB needs three declarations, not one

Worth knowing for review. MariaDB is registered as an additional type id on the MySQL plugin, and registerVariant discards the plugin-built snapshot wholesale when a curated default exists. MariaDB's live field list is therefore the hardcoded literal in PluginMetadataRegistry, not MySQLPlugin.swift. Editing only the plugin would have shipped the field to MySQL and silently skipped MariaDB.

StructureColumnFieldRegistrationTests guards this: it asserts the two engines agree and that both offer the field.

Tests

21 new cases in MySQLColumnDefinitionSQLTests, plus coverage in ColumnDefinitionTests (including the DEFAULT_GENERATED compound-token case), StructureEditingSupportBooleanParsingTests, and StructureColumnFieldRegistrationTests.

MySQLCreateTableTests.swift has never run, and this is why the new tests are not in it. The MySQL plugin is not a module in TableProTests. Pure-logic files are compiled into the test target via pbxproj membershipExceptions and called with no import at all, which is why #if canImport(MySQLDriverPlugin) is false and all 12 tests in that file compile to nothing. The new clause logic lives in MySQLColumnDefinitionSQL.swift, added to that exception set, and the new tests are verified to actually execute. The 12 pre-existing dead tests cover CREATE TABLE assembly and would need generateCreateTableSQL extracted as well; left alone here rather than widen scope, but it is a real gap worth its own issue.

Verification

  • swiftlint lint --strict clean. Local swiftformat cannot run against the repo config (version drift), so it was not run.
  • New and affected suites pass, and are confirmed to execute rather than silently compile out.
  • Full TableProTests failing-case IDs diffed against a stashed clean tree: 76 before, 75 after. The suite is already red in a headless run. The only deltas are one network test and one timer test flipping, and three pasteboard tests flipping the other way, all in areas this change does not touch.
  • Two StructureGridDelegateAddRowTests SQLite cases fail on clean main, independently of this work.
  • Not verified: behaviour against a live MySQL or MariaDB server. The SHOW FULL COLUMNS and INFORMATION_SCHEMA read paths use different queries, and the parser is deliberately case-insensitive so a casing difference between them cannot matter, but a live sanity check on both engines is worth doing before merge.

@mintlify

mintlify Bot commented Aug 1, 2026

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated (UTC)
TablePro 🟢 Ready View Preview Aug 1, 2026, 6:35 AM

💡 Tip: Enable Workflows to automatically generate PRs for you.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@datlechin
datlechin merged commit ab88333 into main Aug 1, 2026
3 checks passed
@datlechin
datlechin deleted the fix-2005-mysql-on-update branch August 1, 2026 06:37
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.

when can add column extra in table structure

1 participant