Skip to content

fix: end column DEFAULT values before a top-level COLLATE - #2849

Open
minleejae wants to merge 2 commits into
JSQLParser:masterfrom
minleejae:fix/column-default-collate-20261023
Open

minleejae wants to merge 2 commits into
JSQLParser:masterfrom
minleejae:fix/column-default-collate-20261023

Conversation

@minleejae

@minleejae minleejae commented Oct 23, 2026 •

Copy link
Copy Markdown
Contributor

In MySQL and PostgreSQL column definitions, a COLLATE after the DEFAULT value is a column attribute. MySQL's DEFAULT takes a literal (now_or_signed_literal) and PostgreSQL's a b_expr; neither includes COLLATE. The column DEFAULT parsed a full expression, so deleted VARCHAR(30) NOT NULL DEFAULT 'N' COLLATE utf8mb4_unicode_ci produced one DEFAULT option holding a CollateExpression.

A column DEFAULT value now ends before a COLLATE at its top level, which becomes the column's COLLATE option. A COLLATE inside parentheses, function arguments or CASE stays in the expression. SET DEFAULT, an a_expr in PostgreSQL, and CREATE DOMAIN are unchanged. SQL output does not change. The nesting walk of slice subscripts moves to a private nested TokenNesting class in the grammar and serves both checks.

API migration: such defaults no longer hold a CollateExpression. Read the DEFAULT option's value and the COLLATE option's getCollation() instead. getColumnSpecs() returns [DEFAULT, 'N', COLLATE, utf8mb4_unicode_ci]. usage.rst describes this under "Structured column attributes". Public method signatures are retained.

Syntax reference: MySQL 8.4 CREATE TABLE, PostgreSQL 18 CREATE TABLE.

Validation:

  • ColumnDefaultExpressionTest adds 15 cases for CREATE TABLE and ALTER ADD/MODIFY/CHANGE, with literals, introducers, signed numbers, concatenation, function calls, CASE and qualified collations. 12 fail on master. The other 3 pin forms that keep the COLLATE in the expression or were already split.
  • Java 17 Gradle check: 12,092 tests, 0 failures, 25 skipped; grammar ambiguity, formatting (Spotless ratchet against upstream/master), Checkstyle, PMD, SpotBugs and JaCoCo pass.
  • MySQL 8.4.11 and PostgreSQL 18.3 store the COLLATE as the column's collation: MySQL keeps DEFAULT 'N' with collation utf8mb4_unicode_ci, and PostgreSQL keeps 'a' || 'b' as the default with collation C. The original and rendered statements give the same results on both servers.

In MySQL and PostgreSQL column definitions, a COLLATE after the DEFAULT
value is a column attribute: MySQL's DEFAULT takes a literal and
PostgreSQL's a b_expr, and neither includes COLLATE. The column DEFAULT
parsed a full expression, so DEFAULT 'N' COLLATE x became one DEFAULT
option holding a CollateExpression.

Parse column DEFAULT values with a ColumnDefaultValue production that
stops before a COLLATE at its top level. That COLLATE becomes the
column's COLLATE option. A COLLATE inside parentheses, function
arguments or CASE stays in the expression. SET DEFAULT, an a_expr in
PostgreSQL, and CREATE DOMAIN are unchanged. SQL output does not change.

The nesting walk of slice subscripts moves to the package-private
TokenNesting, like RoutineBodyBoundary, and serves both checks. The
generated CCJSqlParser class is close to the size JaCoCo can still
instrument.

Signed-off-by: minleejae <mmj9808@gmail.com>
* Whether the target is not nested in brackets, braces, parentheses or a CASE expression opened
* from the first token on.
*/
static boolean isTopLevelFrom(Token first, Token target) {

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.

Why is this an Extra Class now? Why not show it like the other helper classes in the Grammar?
I would love to keep this consistent.

@minleejae minleejae Oct 23, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks, applied in 36ec002. It is now a nested class in the grammar.

Move TokenNesting from its own file into the grammar as a private
nested class next to the lookaheads that use it, as asked in review.
The nested class compiles to its own class file, so its branches stay
out of CCJSqlParser.class. Inlining the walk as a parser method took
18 more of the constant-pool entries JaCoCo can still instrument.
Behaviour does not change.

Signed-off-by: minleejae <mmj9808@gmail.com>
minleejae added a commit to minleejae/JSqlParser that referenced this pull request Oct 23, 2026
Move AlterConstraintLookahead from its own file into the grammar as a
private nested class, as done for TokenNesting in JSQLParser#2849. Split
needsSharedTableConstraint into a check for the forms only the shared
production accepts and a scan for constraint attributes. Codacy's PMD
reported an NPath complexity of 440 for the combined method; the
largest part is now 14. The nested class keeps its branches out of
CCJSqlParser.class. Behaviour does not change.

Signed-off-by: minleejae <mmj9808@gmail.com>

This branch has not been deployed

No deployments
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