Skip to content

Commit 2054489

Browse files
committed
feat: support PostgreSQL CTAS data population clauses
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 0f58f7f commit 2054489

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
@@ -14968,6 +14968,7 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1496814968
Table partitionOfTable = null;
1496914969
PartitionBound partitionBound = null;
1497014970
ColDataType ofType = null;
14971+
boolean withData;
1497114972
}
1497214973
{
1497314974
{ createTable.setOrReplace(isUsingOrReplace);}
@@ -15021,6 +15022,8 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1502115022
[ <K_AS> { createTable.setUseAsKeyword(true); } ]
1502215023
( LOOKAHEAD(<K_TABLE>) select=TableStatement() | select=Select() )
1502315024
{ createTable.setSelect(select, false); }
15025+
[ LOOKAHEAD(2) <K_WITH> withData=DataPopulation()
15026+
{ createTable.setWithData(withData); } ]
1502415027
]
1502515028
[
1502615029
<K_LIKE> ( LOOKAHEAD("(" Table() ")") "(" likeTable=Table() { createTable.setLikeTable(likeTable, true); } ")"
@@ -15611,7 +15614,7 @@ CreateView CreateView(boolean isUsingOrReplace):
1561115614
List<Index.Option> storageParameters = null;
1561215615
List<ViewOption> viewOptions = new ArrayList<ViewOption>();
1561315616
ViewOption viewOption = null;
15614-
boolean withData = true;
15617+
boolean withData;
1561515618
}
1561615619
{
1561715620
{ createView.setOrReplace(isUsingOrReplace);}
@@ -15666,7 +15669,7 @@ CreateView CreateView(boolean isUsingOrReplace):
1566615669
}
1566715670
}
1566815671
|
15669-
[ <K_NO> { withData = false; } ] <K_DATA> { createView.setWithData(withData); }
15672+
withData=DataPopulation() { createView.setWithData(withData); }
1567015673
)
1567115674
]
1567215675
{ createView.validateOptions(); return createView; }
@@ -15684,6 +15687,14 @@ ViewOption PostgreSqlViewOption():
1568415687
{ return new ViewOption(name, value, useEquals); }
1568515688
}
1568615689

15690+
/** Shared WITH [NO] DATA tail for table and materialized-view query sources. */
15691+
boolean DataPopulation():
15692+
{ boolean withData = true; }
15693+
{
15694+
[ <K_NO> { withData = false; } ] <K_DATA>
15695+
{ return withData; }
15696+
}
15697+
1568715698
List<String> CreateViewTailComment():
1568815699
{
1568915700
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)