Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ public class CreateTable implements Statement {
private List<Index> indexes;
private List<TableElement> tableElements;
private Select select;
private DuplicateHandling duplicateHandling;
private Table likeTable;
private Table cloneTable;
private ColDataType ofType;
Expand All @@ -50,6 +51,19 @@ public class CreateTable implements Statement {

private SpannerInterleaveIn interleaveIn = null;

public enum DuplicateHandling {
IGNORE, REPLACE
}

/** MySQL's duplicate-key handling when creating a table from a query; null if omitted. */
public DuplicateHandling getDuplicateHandling() {
return duplicateHandling;
}

public void setDuplicateHandling(DuplicateHandling duplicateHandling) {
this.duplicateHandling = duplicateHandling;
}

@Override
public <T, S> T accept(StatementVisitor<T> statementVisitor, S context) {
return statementVisitor.visit(this, context);
Expand Down Expand Up @@ -207,6 +221,9 @@ public void setUseAsKeyword(boolean useAsKeyword) {
public StringBuilder appendSelectTo(StringBuilder builder,
java.util.function.Consumer<Select> selectRenderer) {
if (select != null) {
if (duplicateHandling != null) {
builder.append(' ').append(duplicateHandling);
}
builder.append(useAsKeyword ? " AS " : " ");
if (selectParenthesis) {
builder.append("(");
Expand Down
14 changes: 11 additions & 3 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1440,6 +1440,11 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
return isKeywordAhead("MATCH_RECOGNIZE") && getToken(2).kind == OPENING_BRACKET;
}

private boolean isCreateTableQueryPrefix() {
int kind = getToken(1).kind;
return kind == K_AS || kind == K_TABLE || kind == K_IGNORE || kind == K_REPLACE;
}

private boolean isRowPatternPrimaryAhead() {
Token next = getToken(1);
return next.kind == OPENING_BRACKET || next.kind == OP_CARET
Expand Down Expand Up @@ -15018,6 +15023,9 @@ CreateTable CreateTable(boolean isUsingOrReplace):
[ rowMovement = RowMovement() { createTable.setRowMovement(rowMovement); }]
[ LOOKAHEAD(2)
{ createTable.setUseAsKeyword(false); }
[ ( tk=<K_IGNORE> | tk=<K_REPLACE> )
{ createTable.setDuplicateHandling(CreateTable.DuplicateHandling.valueOf(
tk.image.toUpperCase(Locale.ROOT))); } ]
[ <K_AS> { createTable.setUseAsKeyword(true); } ]
( LOOKAHEAD(<K_TABLE>) select=TableStatement() | select=Select() )
{ createTable.setSelect(select, false); }
Expand Down Expand Up @@ -15079,10 +15087,10 @@ ColumnDefinition CreateTableColumnDefinition(boolean typed):
void CreateTableOptions(List<TableOption> options):
{ TableOption option; }
{
[ LOOKAHEAD(2, { getToken(1).kind != K_AS && getToken(1).kind != K_TABLE
[ LOOKAHEAD(2, { !isCreateTableQueryPrefix()
&& !(getToken(1).kind == K_PARTITION && getToken(2).kind == K_BY) })
option=CreateTableOption() { options.add(option); }
( LOOKAHEAD(2, { getToken(1).kind != K_AS && getToken(1).kind != K_TABLE
( LOOKAHEAD(2, { !isCreateTableQueryPrefix()
&& !(getToken(1).kind == K_PARTITION && getToken(2).kind == K_BY)
&& !(getToken(1).kind == K_COMMA
&& "INTERLEAVE".equalsIgnoreCase(getToken(2).image)) })
Expand Down Expand Up @@ -16606,7 +16614,7 @@ TablePartitioning CreateTablePartitioning():
]
[ LOOKAHEAD(2) partitionDefinitions=PartitionDefinitions()
{ partitioning.setPartitionDefinitions(partitionDefinitions); } ]
( LOOKAHEAD(2, { getToken(1).kind != K_AS })
( LOOKAHEAD(2, { !isCreateTableQueryPrefix() })
parameter=CreateParameter() { partitionOptions.addAll(parameter); } )*
{
if (!partitionOptions.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*-
* #%L
* JSQLParser library
* %%
* Copyright (C) 2004 - 2026 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 net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.parser.AbstractJSqlParser.Dialect;
import net.sf.jsqlparser.parser.CCJSqlParserUtil;
import net.sf.jsqlparser.statement.create.table.CreateTable;
import net.sf.jsqlparser.statement.create.table.CreateTable.DuplicateHandling;
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 MySqlCreateTableDuplicatesTest {
@ParameterizedTest
@ValueSource(strings = {"SELECT id FROM source", "TABLE source", "VALUES ROW(1), ROW(2)",
"(SELECT id FROM source)", "WITH c AS (SELECT 1 AS id) SELECT id FROM c"})
void retainsModifierAndSourceAfterTableOptions(String query) throws JSQLParserException {
for (DuplicateHandling handling : DuplicateHandling.values()) {
for (boolean useAs : new boolean[] {false, true}) {
CreateTable table = parse("CREATE TABLE t (id INT PRIMARY KEY) ENGINE=InnoDB "
+ handling + (useAs ? " AS " : " ") + query);
assertEquals(handling, table.getDuplicateHandling());
assertEquals(useAs, table.isUseAsKeyword());
assertNotNull(table.getSelect());
roundTrip(table);
}
}
}

@Test
void preservesPartitionAndAllowsAstMutation() throws JSQLParserException {
CreateTable table = parse("CREATE TABLE t (id INT PRIMARY KEY) PARTITION BY HASH(id) "
+ "PARTITIONS 2 IGNORE AS SELECT id FROM source");
assertNotNull(table.getPartitioning());
assertNull(table.getPartitioning().getPartitionOptions());
table.setDuplicateHandling(DuplicateHandling.REPLACE);
assertTrue(table.toString().contains(" REPLACE AS SELECT"));
roundTrip(table);
table.setDuplicateHandling(null);
assertFalse(table.toString().contains("REPLACE"));
roundTrip(table);
assertNull(parse("CREATE TABLE t AS SELECT 1").getDuplicateHandling());
}

@ParameterizedTest
@ValueSource(strings = {"CREATE TABLE t IGNORE", "CREATE TABLE t REPLACE AS",
"CREATE TABLE t IGNORE REPLACE SELECT 1",
"CREATE TABLE t ENGINE=InnoDB, IGNORE SELECT 1"})
void rejectsMissingQueriesAndConflictingModifiers(String sql) {
assertThrows(JSQLParserException.class, () -> parse(sql));
}

private static CreateTable parse(String sql) throws JSQLParserException {
return (CreateTable) CCJSqlParserUtil.parse(sql, p -> p.withDialect(Dialect.MYSQL));
}

private static void roundTrip(CreateTable table) throws JSQLParserException {
StringBuilder out = new StringBuilder();
table.accept(new StatementDeParser(out));
assertEquals(table.toString(), out.toString());
CreateTable parsed = parse(out.toString());
assertEquals(table.getDuplicateHandling(), parsed.getDuplicateHandling());
assertEquals(table.isUseAsKeyword(), parsed.isUseAsKeyword());
assertEquals(out.toString(), parsed.toString());
}
}
Loading