Skip to content

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

Merged
manticore-projects merged 2 commits into
JSQLParser:masterfrom
minleejae:fix/column-default-collate-20261023
Oct 24, 2026
Merged

manticore-projects merged 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>
manticore-projects pushed a commit that referenced this pull request Oct 24, 2026
)

* fix: reuse table constraints for ALTER TABLE ADD in every dialect

ALTER TABLE ADD parsed table constraints with the shared CREATE TABLE
production only for the MySQL and PostgreSQL dialects (#2612, #2654).
Without one of them, the generic ALTER branches rejected forms that
CREATE TABLE accepts in every dialect: an unnamed CHECK (c > 0),
NOT VALID, DEFERRABLE INITIALLY DEFERRED, an unnamed FOREIGN KEY with
attributes and EXCLUDE.

Route an ADD to the shared production in every dialect when it uses a
form that only that production accepts: an unnamed CHECK, FOREIGN KEY,
EXCLUDE, or INITIALLY IMMEDIATE|DEFERRED, NOT VALID or ENFORCED outside
a CHECK before the end of the action. Forms that already parsed keep
their generic representation, accessors and output; the PostgreSQL-only
UNIQUE USING INDEX and NOT NULL constraints still need that dialect.

The scan lives in the package-private AlterConstraintLookahead, like
RoutineBodyBoundary, because the generated CCJSqlParser class is close
to the size JaCoCo can still instrument.

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

* fix: parse VALIDATE CONSTRAINT as a structured action in every dialect

ALTER TABLE t VALIDATE CONSTRAINT ck was a structured RelationAlterAction
only with the PostgreSQL dialect. Elsewhere the remaining text became
an UNSPECIFIC expression, so the constraint name could not be read and
the action could not follow an ADD that now parses in every dialect.

VALIDATE CONSTRAINT starts no other ALTER TABLE action, so recognise it
in every dialect. Its output is unchanged.

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

* refactor: keep the ALTER constraint lookahead in the grammar

Move AlterConstraintLookahead from its own file into the grammar as a
private nested class, as done for TokenNesting in #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>

---------

Signed-off-by: minleejae <mmj9808@gmail.com>
@manticore-projects
manticore-projects merged commit 97c61b1 into JSQLParser:master Oct 24, 2026
10 checks passed
@manticore-projects

Copy link
Copy Markdown
Contributor

Thank you much!

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