From 5798b832476cf0561567da49977ab5dd7582b6fe Mon Sep 17 00:00:00 2001 From: minleejae Date: Thu, 24 Sep 2026 21:41:23 +0900 Subject: [PATCH] fix: share PostgreSQL typed and partition column overrides Signed-off-by: minleejae --- .../net/sf/jsqlparser/parser/JSqlParserCC.jjt | 25 +++-- .../PostgreSqlTableColumnOverridesTest.java | 99 +++++++++++++++++++ 2 files changed, 118 insertions(+), 6 deletions(-) create mode 100644 src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableColumnOverridesTest.java diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index 0df99f424..f66a0abdf 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -14968,6 +14968,7 @@ CreateTable CreateTable(boolean isUsingOrReplace): Table partitionOfTable = null; PartitionBound partitionBound = null; ColDataType ofType = null; + ObjectNames typeName; } { { createTable.setOrReplace(isUsingOrReplace);} @@ -14987,17 +14988,21 @@ CreateTable CreateTable(boolean isUsingOrReplace): [ LOOKAHEAD(2) tableColumn=TimeTravelAfterAlias() { likeTable.setTimeTravel(tableColumn); } ] { createTable.setCloneTable(likeTable); likeTable = null; } ] - [ ofType=ColDataType() { createTable.setOfType(ofType); } ] + [ typeName=RelObjectNames() { + ofType = new ColDataType(); + ofType.setDataType(typeName.getNames()); + createTable.setOfType(ofType); + } ] [ LOOKAHEAD(2) partitionOfTable=Table() { createTable.setPartitionOf(partitionOfTable); } ] [ LOOKAHEAD(2) ( - LOOKAHEAD(3) ( + LOOKAHEAD("(" RelObjectName() ("," RelObjectName())* ")") ( "(" tableColumn=RelObjectName() { columns.add(tableColumn); } ("," tableColumn=RelObjectName() { columns.add(tableColumn); } )* ")" ) | ( - "(" element=CreateTableElement(ofType != null) { tableElements.add(element); } - ( LOOKAHEAD(2) "," element=CreateTableElement(ofType != null) + "(" element=CreateTableElement(ofType != null || partitionOfTable != null) { tableElements.add(element); } + ( LOOKAHEAD(2) "," element=CreateTableElement(ofType != null || partitionOfTable != null) { tableElements.add(element); } )* [ LOOKAHEAD({ Dialect.SQLSERVER.name().equals(getAsString(Feature.dialect)) @@ -15035,8 +15040,16 @@ CreateTable CreateTable(boolean isUsingOrReplace): createTable.setCreateOptionsStrings(createOptions); if (typedTableOptions.size() > 0) createTable.setTableOptions(typedTableOptions); - if (columns.size() > 0) - createTable.setColumns(columns); + if (!columns.isEmpty()) { + if (ofType != null || partitionOfTable != null) { + for (String name : columns) { + tableElements.add(new ColumnDefinition().withColumnName(name)); + } + createTable.setTableElements(tableElements); + } else { + createTable.setColumns(columns); + } + } return createTable; } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableColumnOverridesTest.java b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableColumnOverridesTest.java new file mode 100644 index 000000000..0bebf9b35 --- /dev/null +++ b/src/test/java/net/sf/jsqlparser/statement/create/PostgreSqlTableColumnOverridesTest.java @@ -0,0 +1,99 @@ +/*- + * #%L + * JSQLParser library + * %% + * Copyright (C) 2004 - 2019 JSQLParser + * %% + * Dual licensed under GNU LGPL 2.1 or Apache License 2.0 + * #L% + */ +package net.sf.jsqlparser.statement.create; + +import static org.junit.jupiter.api.Assertions.*; +import java.util.ArrayList; +import java.util.List; +import net.sf.jsqlparser.JSQLParserException; +import net.sf.jsqlparser.expression.LongValue; +import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect; +import net.sf.jsqlparser.parser.CCJSqlParserUtil; +import net.sf.jsqlparser.statement.create.table.ColumnDefinition; +import net.sf.jsqlparser.statement.create.table.ColumnOption; +import net.sf.jsqlparser.statement.create.table.CreateTable; +import net.sf.jsqlparser.util.deparser.ExpressionDeParser; +import net.sf.jsqlparser.util.deparser.SelectDeParser; +import net.sf.jsqlparser.util.deparser.StatementDeParser; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +class PostgreSqlTableColumnOverridesTest { + @ParameterizedTest + @ValueSource(strings = {"row_type", "public.row_type", "\"Type.Schema\".\"Row.Type\""}) + void keepsTypeNameSeparateFromFirstColumn(String type) throws JSQLParserException { + for (String withOptions : new String[] {"", " WITH OPTIONS"}) { + CreateTable table = parse("CREATE TABLE t OF " + type + " (a" + withOptions + + " DEFAULT 7 NOT NULL, b, CHECK (a > 0))"); + assertEquals(type, table.getOfType().getDataType()); + assertNull(table.getOfType().getArgumentsStringList()); + assertEquals(2, table.getColumnDefinitions().size()); + ColumnDefinition first = table.getColumnDefinitions().get(0); + assertNull(first.getColDataType()); + assertEquals(!withOptions.isEmpty(), first.isWithOptions()); + assertEquals("b", table.getColumnDefinitions().get(1).getColumnName()); + assertEquals(ColumnOption.Kind.DEFAULT, first.getColumnOptions().get(0).getKind()); + roundTrip(table); + first.getColumnOptions().get(0).setDefaultExpression(new LongValue(9)); + assertTrue(table.toString().contains("DEFAULT 9 NOT NULL")); + roundTrip(table); + } + } + + @ParameterizedTest + @ValueSource(strings = {"DEFAULT", "FOR VALUES IN (1, 2)", + "FOR VALUES FROM (0) TO (10)", "FOR VALUES WITH (MODULUS 4, REMAINDER 0)"}) + void sharesOverridesForPartitionChildren(String bound) throws JSQLParserException { + for (String options : new String[] {"", " WITH OPTIONS"}) { + CreateTable table = parse("CREATE TABLE child PARTITION OF public.parent (a" + options + + " DEFAULT 7 NOT NULL, b, CHECK (a > 0)) " + bound); + assertNotNull(table.getPartitionBound()); + assertNull(table.getColumnDefinitions().get(0).getColDataType()); + assertNull(table.getColumns()); + roundTrip(table); + List values = new ArrayList<>(); + ExpressionDeParser expressions = new ExpressionDeParser() { + @Override + public StringBuilder visit(LongValue value, S context) { + values.add(value.getValue()); + return getBuilder().append(value.getValue()); + } + }; + StringBuilder out = new StringBuilder(); + table.accept(new StatementDeParser(expressions, new SelectDeParser(), out)); + assertEquals(table.toString(), out.toString()); + assertEquals(1, values.stream().filter(v -> v == 7).count()); + } + } + + @Test + void singleBareOverrideAndOrdinaryTypeParametersRemainDistinct() throws JSQLParserException { + assertNotNull(parse("CREATE TABLE t OF row_type (a)").getColumnDefinitions()); + assertNotNull( + parse("CREATE TABLE child PARTITION OF parent (a) DEFAULT").getColumnDefinitions()); + assertEquals("numeric (10, 2)", parse("CREATE TABLE t (a numeric(10, 2))") + .getColumnDefinitions().get(0).getColDataType().toString()); + assertEquals(List.of("a"), parse("CREATE TABLE t (a) AS SELECT 1").getColumns()); + assertEquals(2, CCJSqlParserUtil + .parseStatements("CREATE TABLE t OF row_type (a DEFAULT 1); SELECT 1").size()); + } + + private static CreateTable parse(String sql) throws JSQLParserException { + return (CreateTable) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL)); + } + + private static void roundTrip(CreateTable table) throws JSQLParserException { + StringBuilder out = new StringBuilder(); + table.accept(new StatementDeParser(out)); + assertEquals(table.toString(), out.toString()); + assertEquals(out.toString(), parse(out.toString()).toString()); + } +}