IGNITE-22161 Sql. Fix infinity loop after query validation failure (#3702)
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
index dd21a5c..57a2d1a 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommand.java
@@ -104,6 +104,8 @@
if (!columnNames.add(column.name())) {
throw new CatalogValidationException(format("Column with name '{}' specified more than once", column.name()));
}
+
+ CatalogUtils.ensureNonFunctionalDefault(column.name(), column.defaultValueDefinition());
}
}
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommand.java
index 392e6f3..1a06cc1 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommand.java
@@ -102,6 +102,8 @@
if (table.isPrimaryKeyColumn(origin.name())) {
validatePkColumnChange(origin);
+ } else {
+ validateValueColumnChange(origin);
}
validateColumnChange(origin);
@@ -147,6 +149,19 @@
if (nullable != null && nullable) {
throw new CatalogValidationException("Dropping NOT NULL constraint on key column is not allowed");
}
+ if (deferredDefault != null) {
+ DefaultValue defaultValue = deferredDefault.derive(origin.type());
+
+ CatalogUtils.ensureSupportedDefault(columnName, defaultValue);
+ }
+ }
+
+ private void validateValueColumnChange(CatalogTableColumnDescriptor origin) {
+ if (deferredDefault != null) {
+ DefaultValue defaultValue = deferredDefault.derive(origin.type());
+
+ CatalogUtils.ensureNonFunctionalDefault(columnName, defaultValue);
+ }
}
private void validateColumnChange(CatalogTableColumnDescriptor origin) {
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CatalogUtils.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CatalogUtils.java
index d785e7b..269ec44 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CatalogUtils.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CatalogUtils.java
@@ -19,12 +19,14 @@
import static java.util.stream.Collectors.toList;
import static org.apache.ignite.internal.catalog.CatalogService.SYSTEM_SCHEMA_NAME;
+import static org.apache.ignite.internal.catalog.commands.DefaultValue.Type.FUNCTION_CALL;
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import java.util.Collection;
import java.util.EnumMap;
import java.util.EnumSet;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
@@ -36,6 +38,8 @@
import org.apache.ignite.internal.catalog.DistributionZoneNotFoundValidationException;
import org.apache.ignite.internal.catalog.IndexNotFoundValidationException;
import org.apache.ignite.internal.catalog.TableNotFoundValidationException;
+import org.apache.ignite.internal.catalog.commands.DefaultValue.FunctionCall;
+import org.apache.ignite.internal.catalog.commands.DefaultValue.Type;
import org.apache.ignite.internal.catalog.descriptors.CatalogIndexDescriptor;
import org.apache.ignite.internal.catalog.descriptors.CatalogSchemaDescriptor;
import org.apache.ignite.internal.catalog.descriptors.CatalogStorageProfileDescriptor;
@@ -120,6 +124,11 @@
private static final Map<ColumnType, Set<ColumnType>> ALTER_COLUMN_TYPE_TRANSITIONS = new EnumMap<>(ColumnType.class);
+ /**
+ * Functions that are allowed to be used as columns' functional default. The set contains uppercase function names.
+ */
+ private static final Set<String> FUNCTIONAL_DEFAULT_FUNCTIONS = new HashSet<>();
+
static {
ALTER_COLUMN_TYPE_TRANSITIONS.put(ColumnType.INT8, EnumSet.of(ColumnType.INT8, ColumnType.INT16, ColumnType.INT32,
ColumnType.INT64));
@@ -131,6 +140,8 @@
ALTER_COLUMN_TYPE_TRANSITIONS.put(ColumnType.STRING, EnumSet.of(ColumnType.STRING));
ALTER_COLUMN_TYPE_TRANSITIONS.put(ColumnType.BYTE_ARRAY, EnumSet.of(ColumnType.BYTE_ARRAY));
ALTER_COLUMN_TYPE_TRANSITIONS.put(ColumnType.DECIMAL, EnumSet.of(ColumnType.DECIMAL));
+
+ FUNCTIONAL_DEFAULT_FUNCTIONS.add("GEN_RANDOM_UUID");
}
public static final List<String> SYSTEM_SCHEMAS = List.of(SYSTEM_SCHEMA_NAME);
@@ -581,4 +592,52 @@
return defaultZone != null ? defaultZone.id() : null;
}
+
+ /**
+ * Return {@code true} if a function with given name is allowed to be used as functional default for a column, {@code false} otherwise.
+ */
+ static boolean isSupportedFunctionalDefault(String functionName) {
+ return FUNCTIONAL_DEFAULT_FUNCTIONS.contains(functionName.toUpperCase());
+ }
+
+ /**
+ * Check if provided default value is a constant or a functional default of supported function, or fail otherwise.
+ */
+ static void ensureSupportedDefault(String columnName, @Nullable DefaultValue defaultValue) {
+ if (defaultValue == null || defaultValue.type == Type.CONSTANT) {
+ return;
+ }
+
+ if (defaultValue.type == FUNCTION_CALL) {
+ String functionName = ((FunctionCall) defaultValue).functionName();
+
+ if (isSupportedFunctionalDefault(functionName)) {
+ return;
+ }
+
+ throw new CatalogValidationException(
+ format("Functional default contains unsupported function: [col={}, functionName={}]",
+ columnName, functionName));
+ }
+
+ throw new CatalogValidationException(
+ format("Default of unsupported kind: [col={}, defaultType={}]", columnName, defaultValue.type));
+ }
+
+ /**
+ * Check if provided default value is a constant, or fail otherwise.
+ */
+ static void ensureNonFunctionalDefault(String columnName, @Nullable DefaultValue defaultValue) {
+ if (defaultValue == null || defaultValue.type == Type.CONSTANT) {
+ return;
+ }
+
+ if (defaultValue.type == FUNCTION_CALL) {
+ throw new CatalogValidationException(
+ format("Functional defaults are not supported for non-primary key columns [col={}].", columnName));
+ }
+
+ throw new CatalogValidationException(
+ format("Default of unsupported kind: [col={}, defaultType={}]", columnName, defaultValue.type));
+ }
}
diff --git a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java
index 8ee7784..d8defb6 100644
--- a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java
+++ b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/CreateTableCommand.java
@@ -35,7 +35,6 @@
import org.apache.ignite.internal.catalog.Catalog;
import org.apache.ignite.internal.catalog.CatalogCommand;
import org.apache.ignite.internal.catalog.CatalogValidationException;
-import org.apache.ignite.internal.catalog.commands.DefaultValue.Type;
import org.apache.ignite.internal.catalog.descriptors.CatalogColumnCollation;
import org.apache.ignite.internal.catalog.descriptors.CatalogHashIndexDescriptor;
import org.apache.ignite.internal.catalog.descriptors.CatalogIndexColumnDescriptor;
@@ -179,9 +178,10 @@
for (ColumnParams column : columns) {
boolean partOfPk = primaryKey.columns().contains(column.name());
- if (!partOfPk && column.defaultValueDefinition().type == Type.FUNCTION_CALL) {
- throw new CatalogValidationException(
- format("Functional defaults are not supported for non-primary key columns [col={}].", column.name()));
+ if (partOfPk) {
+ CatalogUtils.ensureSupportedDefault(column.name(), column.defaultValueDefinition());
+ } else {
+ CatalogUtils.ensureNonFunctionalDefault(column.name(), column.defaultValueDefinition());
}
}
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
index 6ce129c..7b2a831 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAddColumnCommandValidationTest.java
@@ -19,6 +19,7 @@
import static org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
import static org.apache.ignite.sql.ColumnType.INT32;
+import static org.apache.ignite.sql.ColumnType.STRING;
import java.util.List;
import org.apache.ignite.internal.catalog.Catalog;
@@ -165,6 +166,32 @@
);
}
+ @Test
+ void cannotAddColumnWithFunctionalDefault() {
+ String tableName = "TEST";
+ String columnName = "TEST";
+ Catalog catalog = catalogWithTable(builder -> builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columns(List.of(ColumnParams.builder().name("ID").type(INT32).build()))
+ .primaryKey(primaryKey("ID"))
+ );
+
+ ColumnParams columnParams = ColumnParams.builder().name(columnName).type(STRING).length(10)
+ .defaultValue(DefaultValue.functionCall("gen_random_uuid")).build();
+
+ AlterTableAddColumnCommandBuilder builder = AlterTableAddColumnCommand.builder()
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columns(List.of(columnParams));
+
+ assertThrowsWithCause(
+ () -> builder.build().get(catalog),
+ CatalogValidationException.class,
+ "Functional defaults are not supported for non-primary key columns"
+ );
+ }
+
@ParameterizedTest
@MethodSource("reservedSchemaNames")
void exceptionIsThrownIfSchemaIsReserved(String schema) {
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommandValidationTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommandValidationTest.java
index 3f07fd1..c9a1580 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommandValidationTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/AlterTableAlterColumnCommandValidationTest.java
@@ -531,6 +531,132 @@
);
}
+ @Test
+ void functionalDefaultCannotBeAppliedToValueColumn() {
+ String tableName = "TEST";
+ String columnName = "VAL";
+ String columnName2 = "VAL2";
+ Catalog catalog = catalogWithTable(builder -> builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columns(List.of(
+ ColumnParams.builder()
+ .name("ID")
+ .type(ColumnType.INT64)
+ .build(),
+ ColumnParams.builder()
+ .name(columnName)
+ .type(ColumnType.STRING)
+ .length(10)
+ .build(),
+ ColumnParams.builder()
+ .name(columnName2)
+ .type(ColumnType.STRING)
+ .length(10)
+ .defaultValue(DefaultValue.constant("some string"))
+ .build())
+ )
+ .primaryKey(primaryKey("ID"))
+ );
+
+ AlterTableAlterColumnCommandBuilder builder = AlterTableAlterColumnCommand.builder();
+
+ // Set functional default for a column without any default.
+ {
+ CatalogCommand command = builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columnName(columnName)
+ .deferredDefaultValue(type -> DefaultValue.functionCall("gen_random_uuid"))
+ .build();
+
+ assertThrowsWithCause(
+ () -> command.get(catalog),
+ CatalogValidationException.class,
+ "Functional defaults are not supported for non-primary key columns"
+ );
+ }
+
+ // Change column default to a functional default.
+ {
+ CatalogCommand command = builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columnName(columnName2)
+ .deferredDefaultValue(type -> DefaultValue.functionCall("gen_random_uuid"))
+ .build();
+
+ assertThrowsWithCause(
+ () -> command.get(catalog),
+ CatalogValidationException.class,
+ "Functional defaults are not supported for non-primary key columns"
+ );
+ }
+ }
+
+ @Test
+ void invalidFunctionalDefaultCannotBeAppliedToPkColumn() {
+ String tableName = "TEST";
+ String columnName = "ID";
+ String columnName2 = "ID2";
+ Catalog catalog = catalogWithTable(builder -> builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columns(List.of(
+ ColumnParams.builder()
+ .name(columnName)
+ .type(ColumnType.STRING)
+ .length(10)
+ .build(),
+ ColumnParams.builder()
+ .name(columnName2)
+ .type(ColumnType.STRING)
+ .defaultValue(DefaultValue.constant(1))
+ .length(10)
+ .build(),
+ ColumnParams.builder()
+ .name("VAL")
+ .type(ColumnType.INT64)
+ .build())
+ )
+ .primaryKey(primaryKey("ID", "ID2"))
+ );
+
+ AlterTableAlterColumnCommandBuilder builder = AlterTableAlterColumnCommand.builder();
+
+ // Set functional default for a column without any default.
+ {
+ CatalogCommand command = builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columnName(columnName)
+ .deferredDefaultValue(type -> DefaultValue.functionCall("invalid_func"))
+ .build();
+
+ assertThrowsWithCause(
+ () -> command.get(catalog),
+ CatalogValidationException.class,
+ "Functional default contains unsupported function: [col=ID, functionName=invalid_func]"
+ );
+ }
+
+ // Change column default to a functional default.
+ {
+ CatalogCommand command = builder
+ .schemaName(SCHEMA_NAME)
+ .tableName(tableName)
+ .columnName(columnName2)
+ .deferredDefaultValue(type -> DefaultValue.functionCall("invalid_func"))
+ .build();
+
+ assertThrowsWithCause(
+ () -> command.get(catalog),
+ CatalogValidationException.class,
+ "Functional default contains unsupported function: [col=ID2, functionName=invalid_func]"
+ );
+ }
+ }
+
@ParameterizedTest
@MethodSource("reservedSchemaNames")
void exceptionIsThrownIfSchemaIsReserved(String schema) {
diff --git a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CreateTableCommandValidationTest.java b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CreateTableCommandValidationTest.java
index 9f48989..7a5bc31 100644
--- a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CreateTableCommandValidationTest.java
+++ b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CreateTableCommandValidationTest.java
@@ -112,7 +112,7 @@
builder = fillProperties(builder);
builder.columns(List.of(
- ColumnParams.builder().name("C").type(INT32).defaultValue(DefaultValue.functionCall("function")).build(),
+ ColumnParams.builder().name("C").type(INT32).defaultValue(DefaultValue.functionCall("gen_random_uuid")).build(),
ColumnParams.builder().name("D").type(INT32).defaultValue(DefaultValue.constant(1)).build()
)).primaryKey(primaryKey("C", "D"));
@@ -121,6 +121,25 @@
}
@Test
+ void unsupportedFunctionalDefault() {
+ CreateTableCommandBuilder builder = CreateTableCommand.builder();
+
+ builder = fillProperties(builder);
+
+ builder.columns(List.of(
+ ColumnParams.builder().name("C").type(INT32).defaultValue(DefaultValue.functionCall("function")).build(),
+ ColumnParams.builder().name("D").type(INT32).defaultValue(DefaultValue.constant(1)).build()
+
+ )).primaryKey(primaryKey("C", "D"));
+
+ assertThrowsWithCause(
+ builder::build,
+ CatalogValidationException.class,
+ "Functional default contains unsupported function: [col=C, functionName=function]"
+ );
+ }
+
+ @Test
void columnShouldNotHaveDuplicates() {
CreateTableCommandBuilder builder = CreateTableCommand.builder();
diff --git a/modules/schema/src/main/java/org/apache/ignite/internal/schema/DefaultValueGenerator.java b/modules/schema/src/main/java/org/apache/ignite/internal/schema/DefaultValueGenerator.java
index daaf4f7..dcd7ef5 100644
--- a/modules/schema/src/main/java/org/apache/ignite/internal/schema/DefaultValueGenerator.java
+++ b/modules/schema/src/main/java/org/apache/ignite/internal/schema/DefaultValueGenerator.java
@@ -52,6 +52,7 @@
}
/** Returns random UUID string. */
+ @SuppressWarnings("unused") // actually method is called via reflection
public static String genRandomUuid() {
return UUID.randomUUID().toString();
}
diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableAlterColumnTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableAlterColumnTest.java
index 043be66..1262a77 100644
--- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableAlterColumnTest.java
+++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableAlterColumnTest.java
@@ -20,11 +20,16 @@
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.apache.ignite.lang.ErrorGroups.Sql.CONSTRAINT_VIOLATION_ERR;
+import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;
@@ -179,6 +184,61 @@
() -> sql("ALTER TABLE t ALTER COLUMN val2 SET DATA TYPE VARCHAR(100) NOT NULL"));
}
+ @Test
+ @SuppressWarnings("ThrowableNotThrown")
+ public void testChangeColumnDefault() {
+ sql("CREATE TABLE test("
+ + "id BIGINT PRIMARY KEY, "
+ + "valint INTEGER, "
+ + "valdate DATE,"
+ + "valtime TIME(3),"
+ + "valts TIMESTAMP(3),"
+ + "valstr VARCHAR,"
+ + "valbin VARBINARY"
+ + ")");
+
+
+ sql("ALTER TABLE test ALTER COLUMN valint SET DEFAULT 1");
+ sql("ALTER TABLE test ALTER COLUMN valdate SET DEFAULT DATE '2001-12-21'");
+ sql("ALTER TABLE test ALTER COLUMN valtime SET DEFAULT TIME '11:22:33.444555'");
+ sql("ALTER TABLE test ALTER COLUMN valts SET DEFAULT TIMESTAMP '2001-12-21 11:22:33.444555'");
+ sql("ALTER TABLE test ALTER COLUMN valstr SET DEFAULT 'string'");
+ sql("ALTER TABLE test ALTER COLUMN valbin SET DEFAULT x'ff'");
+
+ sql("INSERT INTO test (id) VALUES (0)");
+
+ assertQuery("SELECT * FROM test")
+ .returns(0L,
+ 1,
+ LocalDate.of(2001, Month.DECEMBER, 21),
+ LocalTime.of(11, 22, 33, 444000000),
+ LocalDateTime.of(2001, Month.DECEMBER, 21, 11, 22, 33, 444000000),
+ "string",
+ new byte[]{(byte) 0xff}
+ )
+ .check();
+ }
+
+ @Test
+ @SuppressWarnings("ThrowableNotThrown")
+ public void functionalDefaultIsNotSupportedForNonPkColumns() {
+ sql("CREATE TABLE t (id VARCHAR PRIMARY KEY, val VARCHAR)");
+
+ // PK column
+ assertThrowsSqlException(
+ STMT_PARSE_ERR,
+ "Failed to parse query: Encountered \"gen_random_uuid\"",
+ () -> sql("ALTER TABLE t ALTER COLUMN id SET DEFAULT gen_random_uuid")
+ );
+
+ // Non-pk column
+ assertThrowsSqlException(
+ STMT_PARSE_ERR,
+ "Failed to parse query: Encountered \"gen_random_uuid\"",
+ () -> sql("ALTER TABLE t ALTER COLUMN val SET DEFAULT gen_random_uuid")
+ );
+ }
+
@Override
protected int initialNodes() {
return 1;
diff --git a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
index 27869fc..8733e38 100644
--- a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
+++ b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItCreateTableDdlTest.java
@@ -23,6 +23,7 @@
import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
import static org.apache.ignite.internal.sql.engine.util.SqlTestUtils.assertThrowsSqlException;
import static org.apache.ignite.internal.table.TableTestUtils.getTableStrict;
+import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_PARSE_ERR;
import static org.apache.ignite.lang.ErrorGroups.Sql.STMT_VALIDATION_ERR;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
@@ -30,6 +31,10 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.Month;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -48,7 +53,6 @@
import org.apache.ignite.internal.sql.engine.util.TypeUtils;
import org.apache.ignite.internal.type.NativeType;
import org.apache.ignite.internal.type.NativeTypeSpec;
-import org.apache.ignite.lang.ErrorGroups.Sql;
import org.apache.ignite.sql.SqlException;
import org.apache.ignite.tx.Transaction;
import org.apache.ignite.tx.TransactionOptions;
@@ -104,7 +108,7 @@
@Test
public void emptyPk() {
assertThrowsSqlException(
- Sql.STMT_PARSE_ERR,
+ STMT_PARSE_ERR,
"Failed to parse query: Encountered \")\"",
() -> sql("CREATE TABLE T0(ID0 INT, ID1 INT, VAL INT, PRIMARY KEY ())")
);
@@ -119,7 +123,7 @@
@Test
public void tableWithInvalidColumns() {
assertThrowsSqlException(
- Sql.STMT_PARSE_ERR,
+ STMT_PARSE_ERR,
"Failed to parse query: Encountered \")\"",
() -> sql("CREATE TABLE T0()")
);
@@ -142,6 +146,15 @@
}
@Test
+ public void pkWithInvalidFunctionalDefault() {
+ assertThrowsSqlException(
+ STMT_VALIDATION_ERR,
+ "Functional default contains unsupported function: [col=ID, functionName=INVALID_FUNC]",
+ () -> sql("create table t (id varchar default invalid_func primary key, val int)")
+ );
+ }
+
+ @Test
public void undefinedColumnsInPrimaryKey() {
assertThrowsSqlException(
STMT_VALIDATION_ERR,
@@ -333,16 +346,67 @@
}
@Test
+ public void literalAsColumDefault() {
+ sql("CREATE TABLE T0("
+ + "id BIGINT DEFAULT 1 PRIMARY KEY, "
+ + "valdate DATE DEFAULT DATE '2001-12-21',"
+ + "valtime TIME DEFAULT TIME '11:22:33.444',"
+ + "valts TIMESTAMP DEFAULT TIMESTAMP '2001-12-21 11:22:33.444',"
+ + "valstr VARCHAR DEFAULT 'string',"
+ + "valbin VARBINARY DEFAULT x'ff'"
+ + ")");
+
+ List<Column> columns = unwrapTableViewInternal(table("T0")).schemaView().lastKnownSchema().columns();
+
+ assertEquals(6, columns.size());
+ }
+
+ @Test
+ public void addColumnWithConstantDefault() {
+ sql("CREATE TABLE test(id BIGINT DEFAULT 1 PRIMARY KEY)");
+
+ sql("ALTER TABLE test ADD COLUMN valint INTEGER DEFAULT 1");
+ sql("ALTER TABLE test ADD COLUMN valdate DATE DEFAULT DATE '2001-12-21'");
+ sql("ALTER TABLE test ADD COLUMN valtime TIME(3) DEFAULT TIME '11:22:33.444555'");
+ sql("ALTER TABLE test ADD COLUMN valts TIMESTAMP(3) DEFAULT TIMESTAMP '2001-12-21 11:22:33.444555'");
+ sql("ALTER TABLE test ADD COLUMN valstr VARCHAR DEFAULT 'string'");
+ sql("ALTER TABLE test ADD COLUMN valbin VARBINARY DEFAULT x'ff'");
+
+ sql("INSERT INTO test (id) VALUES (0)");
+
+ assertQuery("SELECT * FROM test")
+ .returns(0L,
+ 1,
+ LocalDate.of(2001, Month.DECEMBER, 21),
+ LocalTime.of(11, 22, 33, 444000000),
+ LocalDateTime.of(2001, Month.DECEMBER, 21, 11, 22, 33, 444000000),
+ "string",
+ new byte[]{(byte) 0xff}
+ )
+ .check();
+ }
+
+ @Test
+ @SuppressWarnings("ThrowableNotThrown")
public void doNotAllowFunctionsInNonPkColumns() {
assertThrowsSqlException(
STMT_VALIDATION_ERR,
"Functional defaults are not supported for non-primary key columns",
- () -> sql("create table t (id varchar primary key, val varchar default gen_random_uuid)")
+ () -> sql("CREATE TABLE t (id VARCHAR PRIMARY KEY, val VARCHAR DEFAULT gen_random_uuid)")
+ );
+
+ sql("CREATE TABLE t (id VARCHAR PRIMARY KEY, val VARCHAR)");
+
+ assertThrowsSqlException(
+ STMT_PARSE_ERR,
+ "Failed to parse query: Encountered \"gen_random_uuid\"",
+ () -> sql("ALTER TABLE t ADD COLUMN val2 VARCHAR DEFAULT gen_random_uuid")
);
}
@ParameterizedTest
@MethodSource("reservedSchemaNames")
+ @SuppressWarnings("ThrowableNotThrown")
public void testItIsNotPossibleToCreateTablesInSystemSchema(String schema) {
assertThrowsSqlException(
STMT_VALIDATION_ERR,
diff --git a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
index c2319d9..f530bd7 100644
--- a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
+++ b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/sql/SqlDdlParserTest.java
@@ -34,10 +34,14 @@
import java.util.stream.Collectors;
import org.apache.calcite.schema.ColumnStrategy;
import org.apache.calcite.sql.SqlBasicCall;
+import org.apache.calcite.sql.SqlBinaryStringLiteral;
+import org.apache.calcite.sql.SqlCharStringLiteral;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlNumericLiteral;
+import org.apache.calcite.sql.SqlUnknownLiteral;
import org.apache.calcite.sql.ddl.SqlColumnDeclaration;
import org.apache.ignite.lang.ErrorGroups.Sql;
import org.hamcrest.CustomMatcher;
@@ -110,6 +114,69 @@
}
/**
+ * Parsing of CREATE TABLE with a literal as a default expression.
+ */
+ @Test
+ public void createTableWithDefaultLiteral() {
+ String query = "CREATE TABLE my_table("
+ + "id BIGINT DEFAULT 1, "
+ + "valdate DATE DEFAULT DATE '2001-12-21',"
+ + "valdate2 DATE DEFAULT '2001-12-21',"
+ + "valtime TIME DEFAULT TIME '11:22:33.444',"
+ + "valtime2 TIME DEFAULT '11:22:33.444',"
+ + "valts TIMESTAMP DEFAULT TIMESTAMP '2001-12-21 11:22:33.444',"
+ + "valts2 TIMESTAMP DEFAULT '2001-12-21 11:22:33.444',"
+ + "valbin VARBINARY DEFAULT x'ff',"
+ + "valstr VARCHAR DEFAULT 'string'"
+ + ")";
+
+ SqlNode node = parse(query);
+
+ assertThat(node, instanceOf(IgniteSqlCreateTable.class));
+
+ IgniteSqlCreateTable createTable = (IgniteSqlCreateTable) node;
+
+ assertThat(createTable.name().names, is(List.of("MY_TABLE")));
+ assertThat(createTable.columnList(), hasColumnWithDefault("ID", SqlNumericLiteral.class, "1"));
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALSTR", SqlCharStringLiteral.class, "string"));
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALBIN", SqlBinaryStringLiteral.class, "11111111"));
+
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALDATE", SqlUnknownLiteral.class, "2001-12-21"));
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALTIME", SqlUnknownLiteral.class, "11:22:33.444"));
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALTS", SqlUnknownLiteral.class, "2001-12-21 11:22:33.444"));
+
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALDATE2", SqlCharStringLiteral.class, "2001-12-21"));
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALTIME2", SqlCharStringLiteral.class, "11:22:33.444"));
+ assertThat(createTable.columnList(), hasColumnWithDefault("VALTS2", SqlCharStringLiteral.class, "2001-12-21 11:22:33.444"));
+
+ expectUnparsed(node, "CREATE TABLE \"MY_TABLE\" "
+ + "(\"ID\" BIGINT DEFAULT (1), "
+ + "\"VALDATE\" DATE DEFAULT (DATE '2001-12-21'), "
+ + "\"VALDATE2\" DATE DEFAULT ('2001-12-21'), "
+ + "\"VALTIME\" TIME DEFAULT (TIME '11:22:33.444'), "
+ + "\"VALTIME2\" TIME DEFAULT ('11:22:33.444'), "
+ + "\"VALTS\" TIMESTAMP DEFAULT (TIMESTAMP '2001-12-21 11:22:33.444'), "
+ + "\"VALTS2\" TIMESTAMP DEFAULT ('2001-12-21 11:22:33.444'), "
+ + "\"VALBIN\" VARBINARY DEFAULT (X'FF'), "
+ + "\"VALSTR\" VARCHAR DEFAULT ('string'))"
+ );
+ }
+
+ private Matcher<Iterable<? super SqlColumnDeclaration>> hasColumnWithDefault(
+ String columnName,
+ Class<? extends SqlLiteral> literalType,
+ String literalValue
+ ) {
+ return hasItem(ofTypeMatching(
+ "Column with literal as default: columnName=" + columnName,
+ SqlColumnDeclaration.class,
+ col -> columnName.equals(col.name.getSimple())
+ && literalType.isInstance(col.expression)
+ && literalValue.equals(((SqlLiteral) col.expression).toValue())
+ ));
+ }
+
+ /**
* Parsing of CREATE TABLE statement with quoted identifiers.
*/
@Test