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 @@ -21,7 +21,7 @@
public class TableOption implements Serializable {

public enum Kind {
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, OTHER
ENGINE, CHARACTER_SET, COLLATE, COMMENT, AUTO_INCREMENT, STATS_AUTO_RECALC, STATS_PERSISTENT, STATS_SAMPLE_PAGES, UNION, ENCRYPTION, PASSWORD, DATA_DIRECTORY, INDEX_DIRECTORY, ENGINE_ATTRIBUTE, SECONDARY_ENGINE_ATTRIBUTE, OTHER
}

private Kind kind = Kind.OTHER;
Expand Down
23 changes: 18 additions & 5 deletions src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
Original file line number Diff line number Diff line change
Expand Up @@ -1555,9 +1555,13 @@ public class CCJSqlParser extends AbstractJSqlParser<CCJSqlParser> {
|| "STATS_SAMPLE_PAGES".equalsIgnoreCase(name);
}

private boolean isMySqlEngineAttributeAhead() {
return isKeywordAhead("ENGINE_ATTRIBUTE") || isKeywordAhead("SECONDARY_ENGINE_ATTRIBUTE");
}

private boolean isMySqlScalarTableOptionAhead() {
int kind = getToken(1).kind;
return kind == K_ENCRYPTION || kind == K_PASSWORD || kind == K_AUTO_INCREMENT
return isMySqlEngineAttributeAhead() || kind == K_ENCRYPTION || kind == K_PASSWORD || kind == K_AUTO_INCREMENT
|| (kind == K_DATA || kind == K_INDEX) && getToken(2).kind == K_DIRECTORY;
}

Expand Down Expand Up @@ -15237,6 +15241,9 @@ TableOption MySqlTableOption(): {
"Expected DEFAULT, 0, or 1 for " + name);
}
|
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) option=MySqlScalarTableOption()
{ return option; }
|
tk=<K_ENGINE> {
name = tk.image;
kind = TableOption.Kind.ENGINE;
Expand Down Expand Up @@ -15271,9 +15278,6 @@ TableOption MySqlTableOption(): {
}
[ "=" { useEquals = true; } ]
value=MySqlTableOptionValue()
|
LOOKAHEAD({ isMySqlScalarTableOptionAhead() }) option=MySqlScalarTableOption()
{ return option; }
)
{
option = new TableOption(kind, name, value, useEquals);
Expand Down Expand Up @@ -15301,6 +15305,10 @@ TableOption MySqlScalarTableOption():
| <K_PASSWORD> { name = "PASSWORD"; kind = TableOption.Kind.PASSWORD; }
| <K_DATA> <K_DIRECTORY> { name = "DATA DIRECTORY"; kind = TableOption.Kind.DATA_DIRECTORY; }
| <K_INDEX> <K_DIRECTORY> { name = "INDEX DIRECTORY"; kind = TableOption.Kind.INDEX_DIRECTORY; }
| LOOKAHEAD({ isMySqlEngineAttributeAhead() }) token=<S_IDENTIFIER> {
name = token.image.toUpperCase(Locale.ROOT);
kind = TableOption.Kind.valueOf(name);
}
)
[ "=" { useEquals = true; } ] token=<S_CHAR_LITERAL> { value = token.image; }
)
Expand Down Expand Up @@ -16676,7 +16684,8 @@ void IndexOptionList(List<String> list) :
{}
{
(
LOOKAHEAD(2) IndexOption(list)
LOOKAHEAD(2, { !(getToken(1).kind == K_USING && getToken(2).kind == K_INDEX) })
IndexOption(list)
)*
}

Expand All @@ -16697,9 +16706,13 @@ void IndexOption(List<String> list) :
Token tk2 = null;
String sk1 = null;
boolean useEqual = false;
TableOption attribute;
}
{
(
LOOKAHEAD({ isMySqlEngineAttributeAhead() }) attribute=MySqlScalarTableOption()
{ list.addAll(attribute.getTokens()); }
|
tk1=<K_KEY_BLOCK_SIZE> ["=" { useEqual = true; } ] tk2=<S_LONG>
{
list.add("KEY_BLOCK_SIZE" + (useEqual ? " = " : "") + tk2.image);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*-
* #%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.alter;

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.Statement;
import net.sf.jsqlparser.statement.create.table.Index;
import net.sf.jsqlparser.statement.create.table.TableOption;
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 IndexOptionBoundaryTest {
@ParameterizedTest
@ValueSource(strings = {"UNIQUE", "PRIMARY KEY"})
void tablespaceBelongsToConstraintOptions(String key) throws JSQLParserException {
Alter alter = (Alter) parse("ALTER TABLE t ADD CONSTRAINT k " + key
+ " (id) USING INDEX TABLESPACE pg_default DEFERRABLE", Dialect.POSTGRESQL);
Index index = alter.getAlterExpressions().get(0).getIndex();
assertEquals("pg_default", index.getTableSpace());
assertNull(index.getUsing());
assertTrue(index.getConstraintAttributes().getDeferrable());
index.setTableSpace("other_space");
assertEquals("ALTER TABLE t ADD CONSTRAINT k " + key
+ " (id) USING INDEX TABLESPACE other_space DEFERRABLE", alter.toString());
assertRoundTrip(alter, Dialect.POSTGRESQL);
}

@ParameterizedTest
@ValueSource(strings = {"SECONDARY_ENGINE_ATTRIBUTE = '{}'",
"SECONDARY_ENGINE_ATTRIBUTE '{\"key\": \"value\"}'",
"SECONDARY_ENGINE_ATTRIBUTE = '{}' VISIBLE COMMENT 'index'"})
void mysqlAddIndexAttributes(String options) throws JSQLParserException {
Alter alter = (Alter) parse("ALTER TABLE t ADD INDEX ix (id) " + options, Dialect.MYSQL);
assertEquals("ix", alter.getAlterExpressions().get(0).getIndex().getName());
assertEquals("ALTER TABLE t ADD INDEX ix (id) " + options, alter.toString());
assertRoundTrip(alter, Dialect.MYSQL);
}

@Test
void sharedTableAttributeRemainsMutable() throws JSQLParserException {
Alter alter =
(Alter) parse("ALTER TABLE t SECONDARY_ENGINE_ATTRIBUTE = '{}'", Dialect.MYSQL);
AlterExpressionTableOption action =
(AlterExpressionTableOption) alter.getAlterExpressions().get(0);
TableOption option = action.getStructuredTableOption();
assertEquals(TableOption.Kind.SECONDARY_ENGINE_ATTRIBUTE, option.getKind());
option.setValue("'{\"key\":1}'");
assertEquals("ALTER TABLE t SECONDARY_ENGINE_ATTRIBUTE = '{\"key\":1}'", alter.toString());
assertRoundTrip(alter, Dialect.MYSQL);
}

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

private static void assertRoundTrip(Statement statement, Dialect dialect)
throws JSQLParserException {
StringBuilder sql = new StringBuilder();
statement.accept(new StatementDeParser(sql), null);
assertEquals(statement.toString(), sql.toString());
assertEquals(statement.toString(), parse(sql.toString(), dialect).toString());
}
}
Loading