Skip to content

Commit 42c4ecb

Browse files
authored
feat: support MySQL TABLE query sources in CREATE TABLE (#2666)
Signed-off-by: minleejae <mmj9808@gmail.com>
1 parent 7f91819 commit 42c4ecb

4 files changed

Lines changed: 111 additions & 25 deletions

File tree

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

Lines changed: 25 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class CreateTable implements Statement {
3939
private Table cloneTable;
4040
private ColDataType ofType;
4141
private boolean selectParenthesis;
42+
private boolean useAsKeyword = true;
4243
private boolean ifNotExists = false;
4344
private boolean orReplace = false;
4445
private TablePartitioning partitioning;
@@ -195,6 +196,29 @@ public Select getSelect() {
195196
return select;
196197
}
197198

199+
public boolean isUseAsKeyword() {
200+
return useAsKeyword;
201+
}
202+
203+
public void setUseAsKeyword(boolean useAsKeyword) {
204+
this.useAsKeyword = useAsKeyword;
205+
}
206+
207+
public StringBuilder appendSelectTo(StringBuilder builder,
208+
java.util.function.Consumer<Select> selectRenderer) {
209+
if (select != null) {
210+
builder.append(useAsKeyword ? " AS " : " ");
211+
if (selectParenthesis) {
212+
builder.append("(");
213+
}
214+
selectRenderer.accept(select);
215+
if (selectParenthesis) {
216+
builder.append(")");
217+
}
218+
}
219+
return builder;
220+
}
221+
198222
public void setSelect(Select select, boolean parenthesis) {
199223
this.select = select;
200224
this.selectParenthesis = parenthesis;
@@ -388,16 +412,7 @@ private void appendTableProperties(StringBuilder b) {
388412
if (rowMovement != null) {
389413
b.append(" ").append(rowMovement.getMode()).append(" ROW MOVEMENT");
390414
}
391-
if (select != null) {
392-
b.append(" AS ");
393-
if (selectParenthesis) {
394-
b.append("(");
395-
}
396-
b.append(select);
397-
if (selectParenthesis) {
398-
b.append(")");
399-
}
400-
}
415+
appendSelectTo(b, b::append);
401416
if (likeTable != null) {
402417
b.append(" LIKE ");
403418
if (selectParenthesis) {

src/main/java/net/sf/jsqlparser/util/deparser/CreateTableDeParser.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
import net.sf.jsqlparser.statement.create.table.Index;
1818
import net.sf.jsqlparser.statement.create.table.TableElement;
1919
import net.sf.jsqlparser.statement.select.PlainSelect;
20-
import net.sf.jsqlparser.statement.select.Select;
2120

2221
public class CreateTableDeParser extends AbstractDeParser<CreateTable> {
2322

@@ -129,17 +128,7 @@ public void deParse(CreateTable createTable) {
129128
builder.append(' ').append(createTable.getRowMovement().getMode().toString())
130129
.append(" ROW MOVEMENT");
131130
}
132-
if (createTable.getSelect() != null) {
133-
builder.append(" AS ");
134-
if (createTable.isSelectParenthesis()) {
135-
builder.append("(");
136-
}
137-
Select sel = createTable.getSelect();
138-
sel.accept(this.statementDeParser, null);
139-
if (createTable.isSelectParenthesis()) {
140-
builder.append(")");
141-
}
142-
}
131+
createTable.appendSelectTo(builder, select -> select.accept(this.statementDeParser, null));
143132
if (createTable.getTrailingLikeTable() != null) {
144133
builder.append(" LIKE ");
145134
if (createTable.isSelectParenthesis()) {

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

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14941,7 +14941,12 @@ CreateTable CreateTable(boolean isUsingOrReplace):
1494114941
// see https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_7002.htm#i2126725
1494214942
// table properties , all these are optional
1494314943
[ rowMovement = RowMovement() { createTable.setRowMovement(rowMovement); }]
14944-
[ <K_AS> select = Select() { createTable.setSelect(select, false); }]
14944+
[ LOOKAHEAD(2)
14945+
{ createTable.setUseAsKeyword(false); }
14946+
[ <K_AS> { createTable.setUseAsKeyword(true); } ]
14947+
( LOOKAHEAD(<K_TABLE>) select=TableStatement() | select=Select() )
14948+
{ createTable.setSelect(select, false); }
14949+
]
1494514950
[
1494614951
<K_LIKE> ( LOOKAHEAD("(" Table() ")") "(" likeTable=Table() { createTable.setLikeTable(likeTable, true); } ")"
1494714952
| likeTable=Table() { createTable.setLikeTable(likeTable, false); } )
@@ -14999,10 +15004,10 @@ ColumnDefinition CreateTableColumnDefinition(boolean typed):
1499915004
void CreateTableOptions(List<TableOption> options):
1500015005
{ TableOption option; }
1500115006
{
15002-
[ LOOKAHEAD(2, { getToken(1).kind != K_AS
15007+
[ LOOKAHEAD(2, { getToken(1).kind != K_AS && getToken(1).kind != K_TABLE
1500315008
&& !(getToken(1).kind == K_PARTITION && getToken(2).kind == K_BY) })
1500415009
option=CreateTableOption() { options.add(option); }
15005-
( LOOKAHEAD(2, { getToken(1).kind != K_AS
15010+
( LOOKAHEAD(2, { getToken(1).kind != K_AS && getToken(1).kind != K_TABLE
1500615011
&& !(getToken(1).kind == K_PARTITION && getToken(2).kind == K_BY)
1500715012
&& !(getToken(1).kind == K_COMMA
1500815013
&& "INTERLEAVE".equalsIgnoreCase(getToken(2).image)) })
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
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.assertj.core.api.Assertions.assertThat;
13+
import static org.junit.jupiter.api.Assertions.*;
14+
15+
import net.sf.jsqlparser.JSQLParserException;
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.select.TableStatement;
20+
import net.sf.jsqlparser.util.TablesNamesFinder;
21+
import net.sf.jsqlparser.util.deparser.StatementDeParser;
22+
import org.junit.jupiter.api.Test;
23+
import org.junit.jupiter.params.ParameterizedTest;
24+
import org.junit.jupiter.params.provider.ValueSource;
25+
26+
class MySqlCreateTableSourceTest {
27+
@ParameterizedTest
28+
@ValueSource(strings = {"CREATE TABLE t AS TABLE db.s", "CREATE TABLE t TABLE db.s",
29+
"CREATE TABLE t (id BIGINT) TABLE db.s",
30+
"CREATE TEMPORARY TABLE IF NOT EXISTS t ENGINE=InnoDB TABLE `db`.`s`",
31+
"CREATE TABLE t (extra INT) ENGINE=InnoDB, COMMENT='source' AS TABLE s ORDER BY id DESC LIMIT 2 OFFSET 1",
32+
"CREATE TABLE t AS SELECT 1 AS id", "CREATE TABLE t SELECT 1 AS id",
33+
"CREATE TABLE t AS (SELECT 1 AS id)"})
34+
void roundTripsQuerySources(String sql) throws Exception {
35+
CreateTable table = parse(sql);
36+
assertNotNull(table.getSelect());
37+
if (sql.contains("TABLE db.") || sql.contains("TABLE `db`")) {
38+
assertInstanceOf(TableStatement.class, table.getSelect());
39+
}
40+
assertRoundTrip(table);
41+
}
42+
43+
@Test
44+
void retainsSourceStructureAndAsChoiceAfterMutation() throws Exception {
45+
CreateTable table = parse("CREATE TABLE target TABLE old.source");
46+
assertFalse(table.isUseAsKeyword());
47+
TableStatement source = assertInstanceOf(TableStatement.class, table.getSelect());
48+
source.getTable().setSchemaName("newdb");
49+
assertThat(new TablesNamesFinder().getTables(table))
50+
.containsExactlyInAnyOrder("target", "newdb.source");
51+
assertEquals("CREATE TABLE target TABLE newdb.source", table.toString());
52+
table.setUseAsKeyword(true);
53+
assertEquals("CREATE TABLE target AS TABLE newdb.source", table.toString());
54+
assertRoundTrip(table);
55+
assertTrue(new CreateTable().isUseAsKeyword());
56+
}
57+
58+
@ParameterizedTest
59+
@ValueSource(strings = {"CREATE TABLE t AS TABLE", "CREATE TABLE t TABLE",
60+
"CREATE TABLE t ENGINE=InnoDB, TABLE s", "CREATE TABLE t AS"})
61+
void rejectsMissingSourcesAndDanglingOptionSeparators(String sql) {
62+
assertThrows(JSQLParserException.class, () -> parse(sql));
63+
}
64+
65+
private static CreateTable parse(String sql) throws JSQLParserException {
66+
return (CreateTable) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
67+
}
68+
69+
private static void assertRoundTrip(CreateTable table) throws Exception {
70+
StringBuilder output = new StringBuilder();
71+
table.accept(new StatementDeParser(output), null);
72+
assertEquals(table.toString(), output.toString());
73+
CreateTable reparsed = parse(output.toString());
74+
assertEquals(table.toString(), reparsed.toString());
75+
assertEquals(table.isUseAsKeyword(), reparsed.isUseAsKeyword());
76+
}
77+
}

0 commit comments

Comments
 (0)