Skip to content

Commit d24e1e2

Browse files
authored
feat: support PostgreSQL CTAS data population clauses (#2689)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent bf26b8d commit d24e1e2

3 files changed

Lines changed: 112 additions & 2 deletions

File tree

‎src/main/java/net/sf/jsqlparser/statement/create/table/CreateTable.java‎

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ public class CreateTable implements Statement {
4040
private ColDataType ofType;
4141
private boolean selectParenthesis;
4242
private boolean useAsKeyword = true;
43+
private Boolean withData;
4344
private boolean ifNotExists = false;
4445
private boolean orReplace = false;
4546
private TablePartitioning partitioning;
@@ -204,6 +205,15 @@ public void setUseAsKeyword(boolean useAsKeyword) {
204205
this.useAsKeyword = useAsKeyword;
205206
}
206207

208+
/** Returns null for omission, true for WITH DATA, and false for WITH NO DATA. */
209+
public Boolean getWithData() {
210+
return withData;
211+
}
212+
213+
public void setWithData(Boolean withData) {
214+
this.withData = withData;
215+
}
216+
207217
public StringBuilder appendSelectTo(StringBuilder builder,
208218
java.util.function.Consumer<Select> selectRenderer) {
209219
if (select != null) {
@@ -215,6 +225,9 @@ public StringBuilder appendSelectTo(StringBuilder builder,
215225
if (selectParenthesis) {
216226
builder.append(")");
217227
}
228+
if (withData != null) {
229+
builder.append(withData ? " WITH DATA" : " WITH NO DATA");
230+
}
218231
}
219232
return builder;
220233
}

‎src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14979,6 +14979,7 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1497914979
Table partitionOfTable = null;
1498014980
PartitionBound partitionBound = null;
1498114981
ColDataType ofType = null;
14982+
boolean withData;
1498214983
}
1498314984
{
1498414985
{ createTable.setOrReplace(isUsingOrReplace);}
@@ -15032,6 +15033,8 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1503215033
[ <K_AS> { createTable.setUseAsKeyword(true); } ]
1503315034
( LOOKAHEAD(<K_TABLE>) select=TableStatement() | select=Select() )
1503415035
{ createTable.setSelect(select, false); }
15036+
[ LOOKAHEAD(2) <K_WITH> withData=DataPopulation()
15037+
{ createTable.setWithData(withData); } ]
1503515038
]
1503615039
[
1503715040
<K_LIKE> ( LOOKAHEAD("(" Table() ")") "(" likeTable=Table() { createTable.setLikeTable(likeTable, true); } ")"
@@ -15622,7 +15625,7 @@ CreateView CreateView(boolean isUsingOrReplace):
1562215625
List<Index.Option> storageParameters = null;
1562315626
List<ViewOption> viewOptions = new ArrayList<ViewOption>();
1562415627
ViewOption viewOption = null;
15625-
boolean withData = true;
15628+
boolean withData;
1562615629
}
1562715630
{
1562815631
{ createView.setOrReplace(isUsingOrReplace);}
@@ -15677,7 +15680,7 @@ CreateView CreateView(boolean isUsingOrReplace):
1567715680
}
1567815681
}
1567915682
|
15680-
[ <K_NO> { withData = false; } ] <K_DATA> { createView.setWithData(withData); }
15683+
withData=DataPopulation() { createView.setWithData(withData); }
1568115684
)
1568215685
]
1568315686
{ createView.validateOptions(); return createView; }
@@ -15695,6 +15698,14 @@ ViewOption PostgreSqlViewOption():
1569515698
{ return new ViewOption(name, value, useEquals); }
1569615699
}
1569715700

15701+
/** Shared WITH [NO] DATA tail for table and materialized-view query sources. */
15702+
boolean DataPopulation():
15703+
{ boolean withData = true; }
15704+
{
15705+
[ <K_NO> { withData = false; } ] <K_DATA>
15706+
{ return withData; }
15707+
}
15708+
1569815709
List<String> CreateViewTailComment():
1569915710
{
1570015711
Token tk = null;
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*-
2+
* #%L
3+
* JSQLParser library
4+
* %%
5+
* Copyright (C) 2004 - 2026 JSQLParser
6+
* %%
7+
* Dual licensed under GNU LGPL 2.1 or Apache License 2.0
8+
* #L%
9+
*/
10+
package net.sf.jsqlparser.statement.create;
11+
12+
import static org.junit.jupiter.api.Assertions.*;
13+
import java.util.List;
14+
import net.sf.jsqlparser.JSQLParserException;
15+
import net.sf.jsqlparser.expression.LongValue;
16+
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
17+
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
18+
import net.sf.jsqlparser.statement.create.table.CreateTable;
19+
import net.sf.jsqlparser.statement.create.view.CreateView;
20+
import net.sf.jsqlparser.util.deparser.ExpressionDeParser;
21+
import net.sf.jsqlparser.util.deparser.SelectDeParser;
22+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
23+
import org.junit.jupiter.api.Test;
24+
import org.junit.jupiter.params.ParameterizedTest;
25+
import org.junit.jupiter.params.provider.ValueSource;
26+
27+
class CreateTableDataPopulationTest {
28+
@ParameterizedTest
29+
@ValueSource(strings = {"SELECT id FROM source", "TABLE source", "VALUES (1), (2)",
30+
"(SELECT id FROM source)", "WITH c AS (SELECT 1 AS id) SELECT id FROM c",
31+
"SELECT id FROM source UNION ALL SELECT id FROM other ORDER BY id LIMIT 4"})
32+
void preservesDataChoiceForQuerySources(String query) throws JSQLParserException {
33+
for (String suffix : new String[] {"", " WITH DATA", " WITH NO DATA"}) {
34+
CreateTable table = parse("CREATE TABLE t AS " + query + suffix);
35+
assertEquals(suffix.isEmpty() ? null : !suffix.contains("NO"), table.getWithData());
36+
roundTrip(table);
37+
}
38+
}
39+
40+
@Test
41+
void sharesOutputAndKeepsCustomQueryVisitor() throws JSQLParserException {
42+
CreateTable table =
43+
parse("CREATE TEMP TABLE t (id) ON COMMIT DROP AS SELECT 7 WITH NO DATA");
44+
table.setWithData(true);
45+
assertTrue(table.toString().endsWith("WITH DATA"));
46+
ExpressionDeParser expression = new ExpressionDeParser() {
47+
@Override
48+
public <S> StringBuilder visit(LongValue value, S context) {
49+
return getBuilder().append(value.getValue() + 100);
50+
}
51+
};
52+
StringBuilder out = new StringBuilder();
53+
table.accept(new StatementDeParser(expression, new SelectDeParser(), out));
54+
assertTrue(out.toString().endsWith("SELECT 107 WITH DATA"));
55+
assertTrue(table.toString().contains("SELECT 7"));
56+
table.setWithData(null);
57+
assertFalse(table.toString().contains("WITH DATA"));
58+
roundTrip(table);
59+
}
60+
61+
@Test
62+
void retainsMaterializedViewTailAndStatementBoundaries() throws JSQLParserException {
63+
CreateView view = (CreateView) CCJSqlParserUtil.parse(
64+
"CREATE MATERIALIZED VIEW v AS SELECT 1 WITH NO DATA");
65+
assertEquals(Boolean.FALSE, view.getWithData());
66+
assertEquals(2, CCJSqlParserUtil.parseStatements(
67+
"CREATE TABLE t AS SELECT 1 WITH NO DATA; SELECT 2").size());
68+
for (String sql : List.of("CREATE TABLE t AS SELECT 1 WITH NO",
69+
"CREATE TABLE t AS SELECT 1 WITH DATA WITH NO DATA",
70+
"SELECT 1 WITH NO DATA")) {
71+
assertThrows(JSQLParserException.class, () -> CCJSqlParserUtil.parse(sql));
72+
}
73+
}
74+
75+
private static CreateTable parse(String sql) throws JSQLParserException {
76+
return (CreateTable) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.POSTGRESQL));
77+
}
78+
79+
private static void roundTrip(CreateTable table) throws JSQLParserException {
80+
StringBuilder out = new StringBuilder();
81+
table.accept(new StatementDeParser(out));
82+
assertEquals(table.toString(), out.toString());
83+
assertEquals(table.getWithData(), parse(out.toString()).getWithData());
84+
assertEquals(out.toString(), parse(out.toString()).toString());
85+
}
86+
}

0 commit comments

Comments
 (0)