This is an automated email from the ASF dual-hosted git repository. korlov pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push: new 53a36f27e5 IGNITE-18965 Sql. CREATE TABLE fails when a column with DEFAULT constraint NULL is present (#2489) 53a36f27e5 is described below commit 53a36f27e511c897fb1eacedba78fcd2bf889ad4 Author: Max Zhuravkov <shh...@gmail.com> AuthorDate: Fri Aug 25 10:24:01 2023 +0300 IGNITE-18965 Sql. CREATE TABLE fails when a column with DEFAULT constraint NULL is present (#2489) --- .../ignite/internal/sql/engine/ItDmlTest.java | 38 +++++++-- .../prepare/ddl/DdlSqlToCommandConverter.java | 91 +++++----------------- 2 files changed, 53 insertions(+), 76 deletions(-) diff --git a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java index 18bd95c4b6..7bac2876ed 100644 --- a/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java +++ b/modules/runner/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDmlTest.java @@ -32,11 +32,13 @@ import java.time.LocalTime; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; +import java.util.stream.Stream; import org.apache.ignite.internal.sql.engine.exec.rel.AbstractNode; import org.apache.ignite.internal.testframework.WithSystemProperty; import org.apache.ignite.lang.IgniteException; import org.apache.ignite.sql.SqlException; import org.apache.ignite.tx.Transaction; +import org.jetbrains.annotations.Nullable; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -189,6 +191,17 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { .check(); } + @Test + public void testNullDefault() { + sql("CREATE TABLE test_null_def (id INTEGER PRIMARY KEY, col INTEGER DEFAULT NULL)"); + + sql("INSERT INTO test_null_def VALUES(1, DEFAULT)"); + assertQuery("SELECT col FROM test_null_def WHERE id = 1").returns(null).check(); + + sql("INSERT INTO test_null_def (id, col) VALUES(2, DEFAULT)"); + assertQuery("SELECT col FROM test_null_def WHERE id = 2").returns(null).check(); + } + /**Test full MERGE command. */ @Test public void testMerge() { @@ -440,9 +453,8 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { assertEquals(3, pkVals.size()); } - @Test - public void testInsertDefaultValue() { - var args = List.of( + private static Stream<DefaultValueArg> defaultValueArgs() { + return Stream.of( new DefaultValueArg("BOOLEAN", "TRUE", Boolean.TRUE), new DefaultValueArg("BOOLEAN NOT NULL", "TRUE", Boolean.TRUE), @@ -476,8 +488,11 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { // TODO: IGNITE-17374 // new DefaultValueArg("VARBINARY", "x'010203'", new byte[]{1, 2, 3}) ); + } - checkDefaultValue(args); + @Test + public void testInsertDefaultValue() { + checkDefaultValue(defaultValueArgs().collect(Collectors.toList())); checkWrongDefault("VARCHAR", "10"); checkWrongDefault("INT", "'10'"); @@ -495,6 +510,14 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { checkWrongDefault("VARBINARY", "10"); } + @Test + public void testInsertDefaultNullValue() { + checkDefaultValue(defaultValueArgs() + .filter(a -> !a.sqlType.endsWith("NOT NULL")) + .map(a -> new DefaultValueArg(a.sqlType, "NULL", null)) + .collect(Collectors.toList())); + } + private void checkDefaultValue(List<DefaultValueArg> args) { assert !args.isEmpty(); @@ -566,11 +589,16 @@ public class ItDmlTest extends ClusterPerClassIntegrationTest { final String sqlVal; final Object expectedVal; - private DefaultValueArg(String sqlType, String sqlVal, Object expectedVal) { + private DefaultValueArg(String sqlType, String sqlVal, @Nullable Object expectedVal) { this.sqlType = sqlType; this.sqlVal = sqlVal; this.expectedVal = expectedVal; } + + @Override + public String toString() { + return sqlType + " sql val: " + sqlVal + " java val: " + expectedVal; + } } @Test diff --git a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java index e3b0dce2c4..9cc9b9aa7a 100644 --- a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java +++ b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverter.java @@ -88,6 +88,7 @@ import org.apache.ignite.internal.sql.engine.sql.IgniteSqlDropZone; import org.apache.ignite.internal.sql.engine.sql.IgniteSqlIndexType; import org.apache.ignite.internal.sql.engine.sql.IgniteSqlZoneOption; import org.apache.ignite.internal.sql.engine.util.Commons; +import org.apache.ignite.internal.sql.engine.util.TypeUtils; import org.apache.ignite.lang.IgniteException; import org.apache.ignite.lang.SchemaNotFoundException; import org.apache.ignite.sql.ColumnType; @@ -402,18 +403,20 @@ public class DdlSqlToCommandConverter { return alterTblCmd; } - private DefaultValueDefinition convertDefault(SqlNode expression, RelDataType relType) { - if (expression instanceof SqlIdentifier) { + private static DefaultValueDefinition convertDefault(@Nullable SqlNode expression, RelDataType relType) { + if (expression == null) { + return DefaultValueDefinition.constant(null); + } else if (expression instanceof SqlIdentifier) { return DefaultValueDefinition.functionCall(((SqlIdentifier) expression).getSimple()); - } - - Object val = null; + } else if (expression instanceof SqlLiteral) { + ColumnType columnType = TypeUtils.columnType(relType); + assert columnType != null : "RelType to columnType conversion should not return null"; - if (expression instanceof SqlLiteral) { - val = fromLiteral(relType, (SqlLiteral) expression); + Object val = fromLiteral(columnType, (SqlLiteral) expression); + return DefaultValueDefinition.constant(val); + } else { + throw new IllegalArgumentException("Unsupported default expression: " + expression.getKind()); } - - return DefaultValueDefinition.constant(val); } private AlterColumnCommand convertAlterColumn(IgniteSqlAlterColumn alterColumnNode, PlanningContext ctx) { @@ -440,7 +443,7 @@ public class DdlSqlToCommandConverter { if (expr instanceof SqlLiteral) { resolveDfltFunc = type -> DefaultValue.constant(fromLiteral(type, (SqlLiteral) expr)); } else { - throw new IllegalStateException("Invalid expression type " + expr.getClass().getName()); + throw new IllegalStateException("Invalid expression type " + expr.getKind()); } cmd.defaultValueResolver(resolveDfltFunc); @@ -820,70 +823,13 @@ public class DdlSqlToCommandConverter { /** * Creates a value of required type from the literal. */ - private static Object fromLiteral(RelDataType columnType, SqlLiteral literal) { - try { - SqlTypeName sqlColumnType = columnType.getSqlTypeName(); - - switch (sqlColumnType) { - case VARCHAR: - case CHAR: - return literal.getValueAs(String.class); - case DATE: { - SqlLiteral literal0 = ((SqlUnknownLiteral) literal).resolve(sqlColumnType); - return LocalDate.ofEpochDay(literal0.getValueAs(DateString.class).getDaysSinceEpoch()); - } - case TIME: { - SqlLiteral literal0 = ((SqlUnknownLiteral) literal).resolve(sqlColumnType); - return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(literal0.getValueAs(TimeString.class).getMillisOfDay())); - } - case TIMESTAMP: { - SqlLiteral literal0 = ((SqlUnknownLiteral) literal).resolve(sqlColumnType); - var tsString = literal0.getValueAs(TimestampString.class); - - return LocalDateTime.ofEpochSecond( - TimeUnit.MILLISECONDS.toSeconds(tsString.getMillisSinceEpoch()), - (int) (TimeUnit.MILLISECONDS.toNanos(tsString.getMillisSinceEpoch() % 1000)), - ZoneOffset.UTC - ); - } - case TIMESTAMP_WITH_LOCAL_TIME_ZONE: { - // TODO: IGNITE-17376 - throw new UnsupportedOperationException("https://issues.apache.org/jira/browse/IGNITE-17376"); - } - case INTEGER: - return literal.getValueAs(Integer.class); - case BIGINT: - return literal.getValueAs(Long.class); - case SMALLINT: - return literal.getValueAs(Short.class); - case TINYINT: - return literal.getValueAs(Byte.class); - case DECIMAL: - return literal.getValueAs(BigDecimal.class); - case DOUBLE: - return literal.getValueAs(Double.class); - case REAL: - case FLOAT: - return literal.getValueAs(Float.class); - case BINARY: - case VARBINARY: - return literal.getValueAs(byte[].class); - case BOOLEAN: - return literal.getValueAs(Boolean.class); - default: - throw new IllegalStateException("Unknown type [type=" + columnType + ']'); - } - } catch (Throwable th) { - // catch throwable here because literal throws an AssertionError when unable to cast value to a given class - throw new SqlException(STMT_VALIDATION_ERR, "Unable co convert literal", th); + private static @Nullable Object fromLiteral(ColumnType columnType, SqlLiteral literal) { + if (literal.getValue() == null) { + return null; } - } - private static @Nullable Object fromLiteral(ColumnType columnType, SqlLiteral literal) { try { switch (columnType) { - case NULL: - return null; case STRING: return literal.getValueAs(String.class); case DATE: { @@ -894,7 +840,7 @@ public class DdlSqlToCommandConverter { SqlLiteral literal0 = ((SqlUnknownLiteral) literal).resolve(SqlTypeName.TIME); return LocalTime.ofNanoOfDay(TimeUnit.MILLISECONDS.toNanos(literal0.getValueAs(TimeString.class).getMillisOfDay())); } - case TIMESTAMP: { + case DATETIME: { SqlLiteral literal0 = ((SqlUnknownLiteral) literal).resolve(SqlTypeName.TIMESTAMP); var tsString = literal0.getValueAs(TimestampString.class); @@ -904,6 +850,9 @@ public class DdlSqlToCommandConverter { ZoneOffset.UTC ); } + case TIMESTAMP: + // TODO: IGNITE-17376 + throw new UnsupportedOperationException("Type is not supported: " + columnType); case INT32: return literal.getValueAs(Integer.class); case INT64: