diff --git a/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java b/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java index 87e8f1ee9..0da992dd6 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/index/CreateIndex.java @@ -12,7 +12,6 @@ import static java.util.stream.Collectors.joining; import java.util.*; - import net.sf.jsqlparser.schema.*; import net.sf.jsqlparser.statement.*; import net.sf.jsqlparser.statement.create.table.*; @@ -106,9 +105,7 @@ public String toString() { buffer.append( index.getColumns().stream() - .map(cp -> cp.columnName + (cp.getParams() != null - ? " " + String.join(" ", cp.getParams()) - : "")) + .map(Index.ColumnParams::toString) .collect(joining(", "))); buffer.append(")"); diff --git a/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java b/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java index b39a3014e..114b30d14 100644 --- a/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java +++ b/src/main/java/net/sf/jsqlparser/statement/create/table/Index.java @@ -17,7 +17,7 @@ import java.util.Collections; import java.util.List; import java.util.Optional; - +import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.statement.select.PlainSelect; public class Index implements Serializable { @@ -32,7 +32,7 @@ public class Index implements Serializable { public List getColumnsNames() { return columns.stream() - .map(col -> col.columnName) + .map(ColumnParams::getColumnName) .collect(toList()); } @@ -202,28 +202,52 @@ public void setCommentText(String commentText) { public static class ColumnParams implements Serializable { public final String columnName; public final List params; + private final Expression expression; public ColumnParams(String columnName) { this.columnName = columnName; this.params = null; + this.expression = null; } public ColumnParams(String columnName, List params) { this.columnName = columnName; this.params = params; + this.expression = null; + } + + public ColumnParams(Expression expression) { + this.columnName = null; + this.params = null; + this.expression = expression; + } + + public ColumnParams(Expression expression, List params) { + this.columnName = null; + this.params = params; + this.expression = expression; } public String getColumnName() { - return columnName; + return expression != null ? expression.toString() : columnName; } public List getParams() { return params; } + public Expression getExpression() { + return expression; + } + + public boolean isExpression() { + return expression != null; + } + @Override public String toString() { - return columnName + (params != null ? " " + String.join(" ", params) : ""); + String head = expression != null ? "(" + expression + ")" : columnName; + return head + (params != null ? " " + String.join(" ", params) : ""); } } } diff --git a/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java b/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java index 211361bb0..cc5d71ba1 100644 --- a/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java +++ b/src/main/java/net/sf/jsqlparser/util/deparser/CreateIndexDeParser.java @@ -54,9 +54,7 @@ public void deParse(CreateIndex createIndex) { if (index.getColumnsNames() != null) { builder.append(" ("); builder.append(index.getColumnWithParams().stream() - .map(cp -> cp.columnName - + (cp.getParams() != null ? " " + String.join(" ", cp.getParams()) - : "")) + .map(Index.ColumnParams::toString) .collect(joining(", "))); builder.append(")"); } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java index e8dc84b72..4db652b96 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/AlterValidator.java @@ -9,8 +9,9 @@ */ package net.sf.jsqlparser.util.validation.validator; -import java.util.EnumSet; +import static java.util.stream.Collectors.toList; +import java.util.EnumSet; import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.alter.Alter; import net.sf.jsqlparser.statement.alter.AlterExpression; @@ -74,7 +75,11 @@ public void validate(Alter alter, AlterExpression e) { if (e.getIndex() != null) { validateName(c, NamedObject.index, e.getIndex().getName()); if (e.getIndex().getColumns() != null) { - validateOptionalColumnNames(c, e.getIndex().getColumnsNames(), + validateOptionalColumnNames(c, + e.getIndex().getColumns().stream() + .filter(cp -> !cp.isExpression()) + .map(cp -> cp.getColumnName()) + .collect(toList()), NamedObject.index); } } diff --git a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java index 0a0860197..d61378497 100644 --- a/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java +++ b/src/main/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidator.java @@ -9,11 +9,13 @@ */ package net.sf.jsqlparser.util.validation.validator; +import static java.util.stream.Collectors.toList; + import net.sf.jsqlparser.parser.feature.Feature; import net.sf.jsqlparser.statement.create.index.CreateIndex; import net.sf.jsqlparser.statement.create.table.Index; -import net.sf.jsqlparser.util.validation.metadata.NamedObject; import net.sf.jsqlparser.util.validation.ValidationCapability; +import net.sf.jsqlparser.util.validation.metadata.NamedObject; /** * @author gitmotte @@ -27,7 +29,14 @@ public void validate(CreateIndex createIndex) { validateFeature(c, Feature.createIndex); validateName(c, NamedObject.table, createIndex.getTable().getFullyQualifiedName()); validateName(c, NamedObject.index, index.getName(), false); - validateOptionalColumnNames(c, index.getColumnsNames(), NamedObject.table); + if (index.getColumns() != null) { + validateOptionalColumnNames(c, + index.getColumns().stream() + .filter(cp -> !cp.isExpression()) + .map(Index.ColumnParams::getColumnName) + .collect(toList()), + NamedObject.table); + } } } diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt index eac28a472..6162253f8 100644 --- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt +++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt @@ -9206,6 +9206,57 @@ List ColumnNamesWithParamsList() : { { return colNames; } } +Index.ColumnParams IndexColumnWithParams(): { + String columnName = null; + List parameter = null; + Expression expression = null; + Index.ColumnParams column = null; +} +{ + ( + columnName=RelObjectName() + { parameter = null; } + [ parameter = CreateParameter() ] + { + column = new Index.ColumnParams(columnName, parameter); + } + | + "(" expression=Expression() ")" + { parameter = null; } + [ LOOKAHEAD(2) parameter = CreateParameter() ] + { + column = new Index.ColumnParams(expression, parameter); + } + ) + { + return column; + } +} + +List IndexColumnsWithParamsList() : { + List colNames = new ArrayList(); + Index.ColumnParams column = null; +} +{ + "(" + column=IndexColumnWithParams() + { + colNames.add(column); + } + + ( + "," + column=IndexColumnWithParams() + { + colNames.add(column); + } + )* + + ")" + + { return colNames; } +} + Index Index(): { ObjectNames name; } @@ -9245,7 +9296,7 @@ CreateIndex CreateIndex(): table=Table() ) ) - colNames = ColumnNamesWithParamsList() + colNames = IndexColumnsWithParamsList() ( LOOKAHEAD(2) parameter=CreateParameter() { tailParameters.addAll(parameter); } )* { index.setColumns(colNames); @@ -9404,7 +9455,7 @@ CreateTable CreateTable(boolean isUsingOrReplace): } tk= sk3=RelObjectName() - colNames = ColumnNamesWithParamsList() + colNames = IndexColumnsWithParamsList() ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* { index = new Index().withType(tk.image).withName(sk3).withColumns(colNames).withIndexSpec(new ArrayList(idxSpec)); @@ -9447,7 +9498,7 @@ CreateTable CreateTable(boolean isUsingOrReplace): [ tk= ] [ tk3= | tk3= ] tk2= sk3=RelObjectName() - colNames = ColumnNamesWithParamsList() + colNames = IndexColumnsWithParamsList() ( parameter=CreateParameter() { idxSpec.addAll(parameter); } )* { index = new Index() @@ -10377,6 +10428,7 @@ AlterExpression AlterExpression(): String sk4 = null; ColDataType dataType; List columnNames = null; + List indexColumnNames = null; List constraints = null; ForeignKeyIndex fkIndex = null; Index index = null; @@ -10426,10 +10478,10 @@ AlterExpression AlterExpression(): LOOKAHEAD(3) sk3 = RelObjectName() [ LOOKAHEAD(2) sk4 = UsingIndexType() ] - [ LOOKAHEAD(2) columnNames = ColumnsNamesList() ] + [ LOOKAHEAD(2) indexColumnNames = IndexColumnsWithParamsList() ] | [ LOOKAHEAD(2) sk4 = UsingIndexType() ] - [ LOOKAHEAD(2) columnNames = ColumnsNamesList() ] + [ LOOKAHEAD(2) indexColumnNames = IndexColumnsWithParamsList() ] ) IndexOptionList(indexSpec = new ArrayList()) { @@ -10437,7 +10489,7 @@ AlterExpression AlterExpression(): .withIndexKeyword(tk.image) .withName(sk3) .withUsing(sk4) - .withColumnsNames(columnNames) + .withColumns(indexColumnNames) .withIndexSpec(indexSpec); alterExp.setIndex(index); diff --git a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java index 5ac38f726..fdd76e8a5 100644 --- a/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/alter/AlterTest.java @@ -2036,6 +2036,33 @@ public void testAlterTableAddIndex_UsingBeforeColumns() throws JSQLParserExcepti assertSqlCanBeParsedAndDeparsed(sql); } + @Test + public void testAlterTableAddFunctionalIndexes() throws JSQLParserException { + String sql = "ALTER TABLE PPK_OLPN ADD INDEX fAdd ((b + c)), " + + "ADD INDEX fCoalesce ((COALESCE(PK, b)) DESC)"; + + Alter alter = (Alter) CCJSqlParserUtil.parse(sql); + assertEquals("PPK_OLPN", alter.getTable().getFullyQualifiedName()); + assertEquals(2, alter.getAlterExpressions().size()); + + AlterExpression addExpression = alter.getAlterExpressions().get(0); + assertEquals(AlterOperation.ADD, addExpression.getOperation()); + assertEquals("fAdd", addExpression.getIndex().getName()); + assertTrue(addExpression.getIndex().getColumns().get(0).isExpression()); + assertEquals("b + c", addExpression.getIndex().getColumns().get(0).getColumnName()); + + AlterExpression coalesceExpression = alter.getAlterExpressions().get(1); + assertEquals(AlterOperation.ADD, coalesceExpression.getOperation()); + assertEquals("fCoalesce", coalesceExpression.getIndex().getName()); + assertTrue(coalesceExpression.getIndex().getColumns().get(0).isExpression()); + assertEquals("COALESCE(PK, b)", + coalesceExpression.getIndex().getColumns().get(0).getColumnName()); + assertEquals(List.of("DESC"), + coalesceExpression.getIndex().getColumns().get(0).getParams()); + + assertSqlCanBeParsedAndDeparsed(sql); + } + @Test public void testAlterTableSetDefaultWithAlgorithm() throws JSQLParserException { String sql = "ALTER TABLE t2 ALTER COLUMN b SET DEFAULT 100, ALGORITHM = INSTANT"; diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java index 23b1d581a..0a95930a3 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateIndexTest.java @@ -11,7 +11,9 @@ import static net.sf.jsqlparser.test.TestUtils.assertSqlCanBeParsedAndDeparsed; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; import java.io.StringReader; import java.util.List; @@ -148,4 +150,22 @@ void testCreateIndexIssue1814() throws JSQLParserException { "CREATE INDEX idx_operationlog_operatetime_regioncode USING BTREE ON operation_log (operate_time,region_biz_code)"; assertSqlCanBeParsedAndDeparsed(sqlStr, true); } + + @Test + public void testCreateIndexWithFunctionalKeyParts() throws JSQLParserException { + String statement = + "CREATE INDEX fAdd ON PPK_OLPN ((b + c), (COALESCE(PK, b)) DESC)"; + CreateIndex createIndex = (CreateIndex) parserManager.parse(new StringReader(statement)); + + assertEquals(2, createIndex.getIndex().getColumns().size()); + assertTrue(createIndex.getIndex().getColumns().get(0).isExpression()); + assertEquals("b + c", createIndex.getIndex().getColumns().get(0).getColumnName()); + assertTrue(createIndex.getIndex().getColumns().get(1).isExpression()); + assertEquals("COALESCE(PK, b)", createIndex.getIndex().getColumns().get(1).getColumnName()); + assertNotNull(createIndex.getIndex().getColumns().get(1).getParams()); + assertEquals("DESC", createIndex.getIndex().getColumns().get(1).getParams().get(0)); + assertEquals(statement, createIndex.toString()); + + assertSqlCanBeParsedAndDeparsed(statement); + } } diff --git a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java index dd6118b47..646f406e8 100644 --- a/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java +++ b/src/test/java/net/sf/jsqlparser/statement/create/CreateTableTest.java @@ -807,6 +807,24 @@ public void testCreateTableIssue924_2() throws JSQLParserException { "CREATE TABLE test_descending_indexes (c1 INT, c2 INT, INDEX idx1 (c1 ASC, c2 ASC), INDEX idx2 (c1 ASC, c2 DESC), INDEX idx3 (c1 DESC, c2 ASC), INDEX idx4 (c1 DESC, c2 DESC))"); } + @Test + public void testCreateTableWithFunctionalIndex() throws JSQLParserException { + String sql = + "CREATE TABLE t (PK INT, b INT, c INT, INDEX fAdd ((b + c), (COALESCE(PK, b)) DESC))"; + CreateTable createTable = (CreateTable) CCJSqlParserUtil.parse(sql); + + assertNotNull(createTable.getIndexes()); + assertEquals(1, createTable.getIndexes().size()); + assertEquals("fAdd", createTable.getIndexes().get(0).getName()); + assertTrue(createTable.getIndexes().get(0).getColumns().get(0).isExpression()); + assertEquals("b + c", createTable.getIndexes().get(0).getColumns().get(0).getColumnName()); + assertTrue(createTable.getIndexes().get(0).getColumns().get(1).isExpression()); + assertEquals("COALESCE(PK, b)", + createTable.getIndexes().get(0).getColumns().get(1).getColumnName()); + + assertSqlCanBeParsedAndDeparsed(sql); + } + @Test public void testCreateTableIssue921() throws JSQLParserException { String statement = "CREATE TABLE binary_test (c1 binary (10))"; diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java index f8b40f6dd..f2aef4e40 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/AlterValidatorTest.java @@ -28,6 +28,12 @@ public void testAlterTableAddColumn_ColumnKeyWordImplicit() throws JSQLParserExc validateNoErrors(sql, 1, DatabaseType.DATABASES); } + @Test + public void testAlterTableAddFunctionalIndex() throws JSQLParserException { + String sql = "ALTER TABLE PPK_OLPN ADD INDEX fAdd ((b + c))"; + validateNoErrors(sql, 1, DatabaseType.DATABASES); + } + @Test public void testAlterTablePrimaryKey() throws JSQLParserException { validateNoErrors("ALTER TABLE animals ADD PRIMARY KEY (id)", 1, DatabaseType.DATABASES); diff --git a/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java b/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java index 754f8ad94..ae3d9fe53 100644 --- a/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java +++ b/src/test/java/net/sf/jsqlparser/util/validation/validator/CreateIndexValidatorTest.java @@ -22,7 +22,8 @@ public class CreateIndexValidatorTest extends ValidationTestAsserts { @Test public void testValidateCreateIndex() throws JSQLParserException { for (String sql : Arrays.asList( - "CREATE INDEX idx_american_football_action_plays_1 ON american_football_action_plays USING btree (play_type)")) { + "CREATE INDEX idx_american_football_action_plays_1 ON american_football_action_plays USING btree (play_type)", + "CREATE INDEX idx_func ON american_football_action_plays ((play_type + 1))")) { validateNoErrors(sql, 1, DatabaseType.DATABASES); } }