Skip to content

Canonicalise DEFAULT / ON UPDATE / CHECK via vitess#17

Merged
winebarrel merged 1 commit into
mainfrom
silent-diff-fixes
May 2, 2026
Merged

Canonicalise DEFAULT / ON UPDATE / CHECK via vitess#17
winebarrel merged 1 commit into
mainfrom
silent-diff-fixes

Conversation

@winebarrel

Copy link
Copy Markdown
Owner

Summary

Closes the two top-priority "silent diff" items in TODO.md. Previously column-default and CHECK comparisons were either byte-equal (ptrEq) or relied on lower + strip whitespace + strip backticks, so plenty of semantically-equal SQL tripped the diff:

Catalog returns Parser produces Old diff New diff
CURRENT_TIMESTAMP current_timestamp() spurious change equal
(1+2) 1+2 spurious change equal
a > 0 a>0 equal (loose) equal
CHECK (\a` > 0)` CHECK (a > 0) equal (loose) equal (precise)

Implementation (diff/expr.go)

  • canonicalExpr(s) — parse SELECT <s> with vitess, return lowercased restore of the result expression
  • equalExpr / equalExprPtr — byte-compare first, then canonicalise both sides on mismatch; fall back to byte equality if either side fails to parse
  • equalCheckDef — split CHECK (expr) [NOT ENFORCED] into expr + suffix, normalise expr through equalExpr, compare suffix case-insensitively
  • looseEqual — the surviving (col1, col2) PRIMARY KEY / UNIQUE body comparison

columnEqual now uses equalExprPtr for Default / OnUpdate / Generated (the latter was previously a byte-compare too — fixed by the same change).

Test plan

  • 13 new unit tests in diff/expr_test.go cover identical, casing, spacing, parens, NULL/TRUE/FALSE, paren-wrapping, NOT ENFORCED suffix, and unparseable-fallback paths
  • make test (parser + diff + catalog round-trip + YAML harness)
  • make test-scenario
  • golangci-lint
  • All three sample DBs round-trip without drift
  • CI green on this PR

Misc

TODO.md: tick the default-normalisation and CHECK-normalisation items; also mark CI workflow and .golangci.yml as done since they shipped earlier and the entries were stale.

🤖 Generated with Claude Code

…byte-compare

Previously column-default and CHECK comparisons were either byte-equal
(ptrEq) or relied on `lower + strip whitespace + strip backticks`,
which let plenty of semantically-equal SQL trip the diff:

  CURRENT_TIMESTAMP   vs  current_timestamp()
  (1+2)               vs  1+2
  a > 0               vs  a>0
  CHECK (`a` > 0)     vs  CHECK (a > 0)

New diff/expr.go gives us:

- canonicalExpr: parse `SELECT <s>` with vitess and return the
  lower-cased restore of the result expression.
- equalExpr / equalExprPtr: byte-compare first, then canonicalise
  both sides on mismatch and compare again. Falls back to byte
  equality if either side fails to parse, so weird literals don't
  silently lie.
- equalCheckDef: split `CHECK (expr) [NOT ENFORCED]` into expr +
  suffix, run expr through equalExpr, compare suffix
  case-insensitively.
- looseEqual: the surviving `(col1, col2)` PRIMARY KEY / UNIQUE body
  comparison; not worth a parse pass.

columnEqual now uses equalExprPtr for Default / OnUpdate / Generated
(the latter was previously a byte-compare too — same fix). The old
constraint normaliser is gone; replaced by the CHECK-aware path and
looseEqual fallback.

13 new unit tests in diff/expr_test.go cover the patterns above. All
existing tests / scenarios / sample-DB round-trips (Chinook, employees,
sakila) still pass.

TODO.md: tick the default-normalisation and CHECK-normalisation items;
also mark CI workflow and .golangci.yml as done since they shipped
weeks ago and the entries were stale.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@winebarrel winebarrel merged commit c811209 into main May 2, 2026
2 checks passed
@winebarrel winebarrel deleted the silent-diff-fixes branch May 2, 2026 05:17
@codecov

codecov Bot commented May 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.62162% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 40.20%. Comparing base (6c4ffd0) to head (39b30a7).
⚠️ Report is 2 commits behind head on main.

Files with missing lines Patch % Lines
diff/expr.go 80.00% 6 Missing and 6 partials ⚠️
diff/tables.go 35.71% 5 Missing and 4 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #17      +/-   ##
==========================================
+ Coverage   38.66%   40.20%   +1.54%     
==========================================
  Files          24       25       +1     
  Lines        1570     1634      +64     
==========================================
+ Hits          607      657      +50     
- Misses        843      852       +9     
- Partials      120      125       +5     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@winebarrel winebarrel mentioned this pull request May 2, 2026
5 tasks
winebarrel added a commit that referenced this pull request May 2, 2026
Four inline comments. Two typo nits, two flow consistency fixes.

Typos
  - parser/directive.go:415 — comment for stripUntilAfterBacktickedName
    contained a curly close-quote (\`name\“) instead of a backtick.
    Replaced. (Copilot #1.)
  - diff/rename_test.go:390 — comment said "RenameFrm"; should be
    "RenameFrom". Fixed. (Copilot #2.)

parser/directive.go (flow consistency)
  - ValidateDirectives now goes through the same
    reduceLeadingBlocks + multi-line block state path the extractors
    use, so a malformed directive after `/* header */` or after
    `*/` on the same multi-line block-close line still errors at
    validation time. Without this, the validator and extractor
    disagreed on what counts as a "directive line", letting bad
    directives slip past validation and be silently ignored.
    (Copilot #3.)
  - ExtractStmtRenameFrom and ExtractInlineRenames re-process the
    suffix after a multi-line `*/` close on the same line, so
    `*/ -- myschema:renamed-from old` still attaches. Previously
    the code cleared inBlock and `continue`'d, dropping the
    directive. (Copilot #4.)

Tests
  - TestValidateDirectivesAfterLeadingBlockComment
  - TestValidateDirectivesAfterMultiLineBlockClose
  - TestExtractStmtRenameFromMultiLineBlockCloseSameLineAsDirective
  - TestExtractInlineRenamesMultiLineBlockCloseSameLineAsDirective

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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