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