Skip to content

fix(ddl): strip the auto-increment counter without mangling literals - #1300

Draft
aparajon wants to merge 2 commits into
mainfrom
aparajon/auto-increment-strip-literals
Draft

fix(ddl): strip the auto-increment counter without mangling literals#1300
aparajon wants to merge 2 commits into
mainfrom
aparajon/auto-increment-strip-literals

Conversation

@aparajon

@aparajon aparajon commented Sep 5, 2026

Copy link
Copy Markdown
Collaborator

The AUTO_INCREMENT table counter was stripped from live schema reads by a regex substitution over the whole SHOW CREATE TABLE text. The pattern's = is optional, so it matched the keyword anywhere it appeared — inside a column default, a column comment, or a quoted identifier.

schemabot pull then wrote the mangled DDL into the schema file. That file becomes the desired state, so the next plan proposes an ALTER against a table nobody changed: setting a real default to '', or truncating a comment.

What a pulled schema file looked like

Live table:

CREATE TABLE `orders` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `note` varchar(64) NOT NULL DEFAULT 'AUTO_INCREMENT=123',
  `rollover` int DEFAULT NULL COMMENT 'reset AUTO_INCREMENT 1000 on rollover',
  `auto_increment_2024` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

Before — two columns silently rewritten:

CREATE TABLE `orders` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `note` varchar(64) NOT NULL DEFAULT '',
  `rollover` int DEFAULT NULL COMMENT 'reset on rollover',
  `auto_increment_2024` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

After — only the counter is gone:

CREATE TABLE `orders` (
  `id` bigint unsigned NOT NULL AUTO_INCREMENT,
  `note` varchar(64) NOT NULL DEFAULT 'AUTO_INCREMENT=123',
  `rollover` int DEFAULT NULL COMMENT 'reset AUTO_INCREMENT 1000 on rollover',
  `auto_increment_2024` int DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci

ddl.StripTableAutoIncrement decides with the parser and edits with byte precision:

  SHOW CREATE TABLE text
          |
          v
   statement.ParseCreateTable
          |
          +-- no ast.TableOptionAutoIncrement --> return the input unchanged
          |
          v
   remove the counter's own span
   (the scan steps over quoted runs and comments, and only
    considers depth 0, so the column attribute is never in range)
          |
          v
   re-parse, compare both sides in canonical form
          |
          +-- anything else moved --> error
          |
          v
   stripped statement

Two properties the shape is built around:

  • The column-level AUTO_INCREMENT attribute is preserved. Removing it would produce a table whose ids no longer generate.
  • The server's own formatting survives, so pulled and onboarded .sql files keep the canonical SHOW CREATE TABLE layout. Restoring the modified AST instead would have reformatted every file belonging to a table that has a live counter, since the parser's Restore is not a pretty-printer.

A statement whose counter cannot be located and removed cleanly is an error, not a passthrough — returning the input would write one instance's counter into a schema file.

Three call sites move off Spirit's WithStrippedAutoIncrement: the pull path (pkg/tern), the differ's live-schema read (pkg/engine/spirit), and the shard-targeted read in LocalScale.

RV-6 — this extends enforcement to the live-schema read path: DDL is now transformed through the dialect's parser rather than by matching its text. The entry's Enforced: line already names the Spirit statement boundary, so the registry needs no change.

Why not just take the upstream fix

block/spirit#1210 landed the same class of fix in table.StripAutoIncrement, over the AST. It parses, deletes the option, and restores — which reformats the statement whenever a counter is present, because the parser's Restore is not a pretty-printer. On a real SHOW CREATE TABLE it returns:

CREATE TABLE `orders` (`id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,`note` VARCHAR(64) NOT NULL DEFAULT _UTF8MB4'AUTO_INCREMENT=123',`rollover` INT DEFAULT NULL COMMENT 'reset AUTO_INCREMENT 1000 on rollover',`created_at` TIMESTAMP NULL DEFAULT CURRENT_TIMESTAMP(),PRIMARY KEY(`id`),UNIQUE `idx_note`(`note`)) ENGINE = InnoDB DEFAULT CHARACTER SET = UTF8MB4 DEFAULT COLLATE = UTF8MB4_0900_AI_CI

Literals survive, so the mangling is genuinely fixed. But every onboarded .sql file for a table with a live counter would arrive on one line, uppercased, with _UTF8MB4'...' prefixes and DEFAULT CURRENT_TIMESTAMP() in place of DEFAULT CURRENT_TIMESTAMP — while tables without a counter keep the server's layout, so a single onboarding would emit two different formats.

That is a property of stripping after the read, and it cannot be avoided in pkg/table: pkg/statement imports pkg/table, so the richer parse-and-splice used here cannot live there without inverting the dependency.

This PR drops SchemaBot's use of WithStrippedAutoIncrement altogether, so the pull and onboard paths keep the server's formatting regardless of what the option does upstream.

This PR was written by Claude Code (Opus 5).

The schema-read paths stripped the AUTO_INCREMENT table counter with a
regex substitution over the whole SHOW CREATE TABLE text, so any column
default, column comment or identifier that happened to spell the keyword
was rewritten too: DEFAULT 'AUTO_INCREMENT=123' became DEFAULT '', and a
comment reading "reset AUTO_INCREMENT 1000 on rollover" lost its middle.
A pull wrote the mangled DDL into the schema file, which then became the
desired state and would generate an ALTER against a table nobody changed.

StripTableAutoIncrement parses the statement with the real parser and only
touches text when the AST actually carries a table-level counter, so a
statement without one is returned byte for byte. When there is one, the
counter's own span is removed and the result is parsed again and compared
with the original in the parser's canonical form: anything else moving is
an error, never a silent rewrite. The column-level AUTO_INCREMENT
attribute is preserved — dropping it would produce a table whose ids no
longer generate. The server's own formatting survives, so pulled and
onboarded schema files keep their canonical SHOW CREATE layout.

Extends RV-6 to the live-schema read path: DDL is now transformed through
the dialect's parser rather than by matching its text.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Copilot AI lite review requested due to automatic review settings September 5, 2026 17:43

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

The new DDL scanner treats any -- sequence as a line comment start, which can mis-handle valid SQL expressions and cause counter stripping to fail on otherwise valid CREATE TABLE statements.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

This PR replaces a regex-based AUTO_INCREMENT counter strip (which could mangle literals/identifiers) with a parser-guided, byte-precise removal of the table-level AUTO_INCREMENT=N option during live schema reads, keeping the server’s SHOW CREATE TABLE formatting intact.

Changes:

  • Introduces ddl.StripTableAutoIncrement, which locates and removes only the table-level counter and re-parses to verify nothing else changed.
  • Updates live-schema read paths (tern pull, Spirit engine fetch, LocalScale schema reads) to use the new stripping logic instead of Spirit’s stripped-auto-increment option.
  • Adds focused unit + integration coverage to ensure literals/comments/identifiers containing AUTO_INCREMENT are preserved while the counter is removed.
File summaries
File Description
pkg/ddl/auto_increment.go Adds parser-verified, byte-precise stripping of the table-level AUTO_INCREMENT=N option.
pkg/ddl/auto_increment_test.go Unit tests for counter stripping behavior and literal/comment preservation.
pkg/engine/spirit/spirit.go Strips the table counter after loading live schema to avoid spurious diffs.
pkg/localscale/helpers.go Switches LocalScale’s SHOW CREATE path to the new counter-strip function with error handling.
pkg/tern/local_client.go Applies counter stripping during schemabot pull table DDL generation.
pkg/tern/local_client_integration_test.go Integration test ensuring pull preserves literals/comments while dropping the table counter.
Review details
  • Files reviewed: 6/6 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/ddl/auto_increment.go
MySQL opens a line comment on `--` only when a whitespace or control
character follows the second dash, so `a--b` subtracts a negated value.
Scanning every `--` as a comment skipped the rest of the line, and a
generated column or default expression written that way hid the table
options behind it: the counter parsed as a table option but could not be
found in the statement text, failing the strip outright.
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.

2 participants