This is an automated email from the ASF dual-hosted git repository.

jooger 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 3ec8e936ea1 IGNITE-24153: Fixes precision/scale validation  for 
binary/chars/decimal/time/timestamp(#5119)
3ec8e936ea1 is described below

commit 3ec8e936ea174c46cdfc08209d1f0ee49f9b37d1
Author: Max Zhuravkov <[email protected]>
AuthorDate: Fri Feb 7 10:20:29 2025 +0200

    IGNITE-24153: Fixes precision/scale validation  for 
binary/chars/decimal/time/timestamp(#5119)
---
 .../internal/catalog/commands/CatalogUtils.java    | 179 ++++++++++++++++++++-
 .../internal/catalog/commands/ColumnParams.java    |  70 +++++---
 .../catalog/ColumnConstructionValidatorTest.java   |  94 +++++++++--
 .../catalog/commands/CatalogUtilsTest.java         |  86 ++++++++++
 .../ignite/internal/catalog/CatalogTestUtils.java  |   2 +-
 .../apache/ignite/internal/type/NativeTypes.java   |   4 +-
 .../ignite/internal/type/NativeTypeTest.java       |  12 ++
 .../internal/sql/engine/ItAlterTableDdlTest.java   | 124 ++++++++++++++
 .../internal/sql/engine/ItCreateTableDdlTest.java  | 120 +++++++++++++-
 .../internal/sql/engine/ItDataTypesTest.java       |  69 +++++++-
 .../group1/function/string/test_char_length.test   |   8 +-
 .../sql/group1/types/blob/test_blob.test           |   8 +-
 .../sql/group1/types/char/test_char_length.test    |   4 +-
 .../group1/types/timestamp/test_timestamp_ms.test  |   7 +-
 .../internal/sql/engine/prepare/IgnitePlanner.java |   2 +
 .../sql/engine/prepare/IgniteSqlValidator.java     |  84 +++++++++-
 .../prepare/ddl/DdlSqlToCommandConverter.java      |  14 +-
 .../sql/engine/type/IgniteTypeFactory.java         |  51 ++++++
 .../internal/sql/engine/type/IgniteTypeSystem.java |  15 ++
 .../internal/sql/engine/util/IgniteResource.java   |  10 +-
 .../prepare/ddl/DdlSqlToCommandConverterTest.java  |  64 ++++++++
 .../pruning/PartitionPruningPredicateSelfTest.java |   2 +-
 .../sql/engine/type/IgniteTypeSystemTest.java      | 135 ++++++++++++++++
 .../internal/sql/engine/util/SqlTestUtils.java     |   4 +-
 24 files changed, 1095 insertions(+), 73 deletions(-)

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 45655984974..b71486b3b3d 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
@@ -87,6 +87,11 @@ public class CatalogUtils {
      */
     public static final int DEFAULT_SCALE = 0;
 
+    /**
+     * Minimum precision for TIME and TIMESTAMP types.
+     */
+    public static final int MIN_TIME_PRECISION = 0;
+
     /**
      * Maximum TIME and TIMESTAMP precision is implementation-defined.
      *
@@ -94,6 +99,16 @@ public class CatalogUtils {
      */
     public static final int MAX_TIME_PRECISION = 
NativeTypes.MAX_TIME_PRECISION;
 
+    /**
+     * Unspecified precision.
+     */
+    public static final int UNSPECIFIED_PRECISION = -1;
+
+    /**
+     * Minimum DECIMAL precision.
+     */
+    public static final int MIN_DECIMAL_PRECISION = 1;
+
     /**
      * Max DECIMAL precision is implementation-defined.
      *
@@ -101,6 +116,16 @@ public class CatalogUtils {
      */
     public static final int MAX_DECIMAL_PRECISION = Short.MAX_VALUE;
 
+    /**
+     * Minimum DECIMAL scale.
+     */
+    public static final int MIN_DECIMAL_SCALE = 0;
+
+    /**
+     * Unspecified scale.
+     */
+    public static final int UNSPECIFIED_SCALE = -1;
+
     /**
      * Max DECIMAL scale is implementation-defined.
      *
@@ -116,12 +141,37 @@ public class CatalogUtils {
     public static final int DEFAULT_LENGTH = 1;
 
     /**
-     * Max length for VARCHAR and VARBINARY is implementation defined.
+     * Default length for VARCHAR and VARBINARY is implementation defined.
      *
      * <p>SQL`16 part 2 section 6.1 syntax rule 8
      */
     public static final int DEFAULT_VARLEN_LENGTH = 2 << 15;
 
+    /**
+     * Maximum length for VARCHAR and VARBINARY types.
+     */
+    public static final int MAX_VARLEN_LENGTH = 2 << 15;
+
+    /**
+     * Minimum length for VARCHAR and VARBINARY types.
+     */
+    public static final int MIN_VARLEN_PRECISION = 1;
+
+    /**
+     * Unspecified length.
+     */
+    public static final int UNSPECIFIED_LENGTH = -1;
+
+    /**
+     * Minimum precision for interval types.
+     */
+    public static final int MIN_INTERVAL_TYPE_PRECISION = 1;
+
+    /**
+     * Maximum precision for interval types.
+     */
+    public static final int MAX_INTERVAL_TYPE_PRECISION = 10;
+
     public static final ConsistencyMode DEFAULT_CONSISTENCY_MODE = 
ConsistencyMode.STRONG_CONSISTENCY;
 
     private static final Map<ColumnType, Set<ColumnType>> 
ALTER_COLUMN_TYPE_TRANSITIONS = new EnumMap<>(ColumnType.class);
@@ -547,6 +597,133 @@ public class CatalogUtils {
         return defaultZone != null ? defaultZone.id() : null;
     }
 
+
+    /**
+     * Returns the maximum supported precision for given type or {@link 
#UNSPECIFIED_PRECISION}  if the type does not support precision.
+     *
+     * @param columnType Column type.
+     * @return Maximum precision.
+     */
+    public static int getMaxPrecision(ColumnType columnType) {
+        if (!columnType.precisionAllowed()) {
+            return UNSPECIFIED_PRECISION;
+        } else {
+            switch (columnType) {
+                case DECIMAL:
+                    return MAX_DECIMAL_PRECISION;
+                case TIME:
+                case DATETIME:
+                case TIMESTAMP:
+                    return MAX_TIME_PRECISION;
+                case DURATION:
+                case PERIOD:
+                    return MAX_INTERVAL_TYPE_PRECISION;
+                default:
+                    throw new IllegalArgumentException("Unexpected column 
type: " + columnType);
+            }
+        }
+    }
+
+    /**
+     * Returns the minimum supported precision for given type or {@link 
#UNSPECIFIED_PRECISION} if the type does not support precision.
+     *
+     * @param columnType Column type.
+     * @return Minimum precision.
+     */
+    public static int getMinPrecision(ColumnType columnType) {
+        if (!columnType.precisionAllowed()) {
+            return UNSPECIFIED_PRECISION;
+        } else {
+            switch (columnType) {
+                case DECIMAL:
+                    return MIN_DECIMAL_PRECISION;
+                case TIME:
+                case DATETIME:
+                case TIMESTAMP:
+                    return MIN_TIME_PRECISION;
+                case DURATION:
+                case PERIOD:
+                    return MIN_INTERVAL_TYPE_PRECISION;
+                default:
+                    throw new IllegalArgumentException("Unexpected column 
type: " + columnType);
+            }
+        }
+    }
+
+    /**
+     * Returns the maximum supported length for given type or {@link 
#UNSPECIFIED_LENGTH} if the type does not support length.
+     *
+     * @param columnType Column type.
+     * @return Maximum length.
+     */
+    public static int getMaxLength(ColumnType columnType) {
+        if (!columnType.lengthAllowed()) {
+            return UNSPECIFIED_LENGTH;
+        } else {
+            switch (columnType) {
+                case STRING:
+                case BYTE_ARRAY:
+                    return MAX_VARLEN_LENGTH;
+                default:
+                    throw new IllegalArgumentException("Unexpected column 
type: " + columnType);
+            }
+        }
+    }
+
+    /**
+     * Returns the minimum supported length for given type or {@link 
#UNSPECIFIED_LENGTH} if the type does not support length.
+     *
+     * @param columnType Column type.
+     * @return Minimum length.
+     */
+    public static int getMinLength(ColumnType columnType) {
+        if (!columnType.lengthAllowed()) {
+            return UNSPECIFIED_LENGTH;
+        } else {
+            switch (columnType) {
+                case STRING:
+                case BYTE_ARRAY:
+                    return MIN_VARLEN_PRECISION;
+                default:
+                    throw new IllegalArgumentException("Unexpected column 
type: " + columnType);
+            }
+        }
+    }
+
+    /**
+     * Returns the maximum supported scale for given type or {@link 
#UNSPECIFIED_SCALE} if the type does not support scale.
+     *
+     * @param columnType Column type.
+     * @return Maximum scale.
+     */
+    public static int getMaxScale(ColumnType columnType) {
+        if (!columnType.scaleAllowed()) {
+            return UNSPECIFIED_SCALE;
+        } else {
+            if (columnType == ColumnType.DECIMAL) {
+                return MAX_DECIMAL_SCALE;
+            }
+            throw new IllegalArgumentException("Unexpected column type: " + 
columnType);
+        }
+    }
+
+    /**
+     * Returns the minimum supported scale for given type or {@link 
#UNSPECIFIED_SCALE} if the type does not support scale.
+     *
+     * @param columnType Column type.
+     * @return Minimum scale.
+     */
+    public static int getMinScale(ColumnType columnType) {
+        if (!columnType.scaleAllowed()) {
+            return UNSPECIFIED_SCALE;
+        } else {
+            if (columnType == ColumnType.DECIMAL) {
+                return MIN_DECIMAL_SCALE;
+            }
+            throw new IllegalArgumentException("Unexpected column type: " + 
columnType);
+        }
+    }
+
     /**
      * Check if provided default value is a constant or a functional default 
of supported function, or fail otherwise.
      */
diff --git 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/ColumnParams.java
 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/ColumnParams.java
index 5a8d9c1d8a3..62e2a4e5d96 100644
--- 
a/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/ColumnParams.java
+++ 
b/modules/catalog/src/main/java/org/apache/ignite/internal/catalog/commands/ColumnParams.java
@@ -28,9 +28,8 @@ import org.jetbrains.annotations.Nullable;
 /** Defines a particular column within table. */
 public class ColumnParams {
     public static final String ERR_COL_PARAM_NOT_APPLICABLE = "{} is not 
applicable for column '{}' of type '{}'";
-    public static final String ERR_COL_PARAM_VALIDATION = "{} for column '{}' 
of type '{}' must be non-negative";
     public static final String ERR_COL_PARAM_DEFINITION = "{} definition is 
necessary for column '{}' of type '{}'";
-    public static final String ERR_COL_POSITIVE_PARAM_VALIDATION = "{} for 
column '{}' of type '{}' must be at least 1";
+    public static final String ERR_COL_INVALID_TYPE_PARAM = "{} for column 
`{}` of type {} {} must be between {} and {}";
 
     /** Creates parameters builder. */
     public static Builder builder() {
@@ -236,16 +235,9 @@ public class ColumnParams {
             boolean validatePrecision = params.type.precisionAllowed();
             boolean validateScale = params.type.scaleAllowed();
             boolean validateLength = params.type.lengthAllowed();
-            Integer length = params.length();
 
             if (validateLength) {
-                if (length == null) {
-                    throw new 
CatalogValidationException(format(ERR_COL_PARAM_DEFINITION, "Length", 
params.name(), params.type()));
-                }
-
-                if (length < 1) {
-                    throw new 
CatalogValidationException(format(ERR_COL_POSITIVE_PARAM_VALIDATION, "Length", 
params.name(), params.type()));
-                }
+                validateLength(params);
             } else {
                 if (params.length() != null) {
                     throw new 
CatalogValidationException(format(ERR_COL_PARAM_NOT_APPLICABLE, "Length", 
params.name(), params.type()));
@@ -275,28 +267,62 @@ public class ColumnParams {
             }
         }
 
+        private static void validateLength(ColumnParams params) {
+            Integer length = params.length();
+            String name = params.name();
+            ColumnType type = params.type();
+
+            int minLength = CatalogUtils.getMinLength(type);
+            int maxLength = CatalogUtils.getMaxLength(type);
+
+            validateTypeParameter(name, type, "Length", length, minLength, 
maxLength);
+        }
+
         private static void validatePrecision(ColumnParams params) {
             Integer precision = params.precision();
+            String name = params.name();
+            ColumnType type = params.type();
 
-            if (precision == null) {
-                throw new 
CatalogValidationException(format(ERR_COL_PARAM_DEFINITION, "Precision", 
params.name(), params.type()));
-            }
+            int minPrecision = CatalogUtils.getMinPrecision(type);
+            int maxPrecision = CatalogUtils.getMaxPrecision(type);
 
-            if (precision < 0) {
-                throw new 
CatalogValidationException(format(ERR_COL_PARAM_VALIDATION, "Precision", 
params.name(), params.type()));
-            }
+            validateTypeParameter(name, type, "Precision", precision, 
minPrecision, maxPrecision);
         }
 
         private static void validateScale(ColumnParams params) {
             Integer scale = params.scale();
+            String name = params.name();
+            ColumnType type = params.type();
 
-            if (scale == null) {
-                throw new 
CatalogValidationException(format(ERR_COL_PARAM_DEFINITION, "Scale", 
params.name(), params.type()));
-            }
+            int minScale = CatalogUtils.getMinScale(type);
+            int maxScale = CatalogUtils.getMaxScale(type);
 
-            if (scale < 0) {
-                throw new 
CatalogValidationException(format(ERR_COL_PARAM_VALIDATION, "Scale", 
params.name(), params.type()));
-            }
+            validateTypeParameter(name, type, "Scale", scale, minScale, 
maxScale);
+        }
+    }
+
+    private static void validateTypeParameter(
+            String colName,
+            ColumnType type,
+            String paramName,
+            @Nullable Integer value,
+            int min,
+            int max
+    ) {
+        if (value == null) {
+            throw new 
CatalogValidationException(format(ERR_COL_PARAM_DEFINITION, paramName, colName, 
type));
+        }
+
+        if (value < min || value > max) {
+            String errorMessage = format(ERR_COL_INVALID_TYPE_PARAM,
+                    paramName,
+                    colName,
+                    type,
+                    value,
+                    min,
+                    max
+            );
+            throw new CatalogValidationException(errorMessage);
         }
     }
 }
diff --git 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/ColumnConstructionValidatorTest.java
 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/ColumnConstructionValidatorTest.java
index 12a6da6df66..7b44cf33a28 100644
--- 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/ColumnConstructionValidatorTest.java
+++ 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/ColumnConstructionValidatorTest.java
@@ -20,14 +20,14 @@ package org.apache.ignite.internal.catalog;
 import static 
org.apache.ignite.internal.catalog.CatalogTestUtils.DEFAULT_NULLABLE;
 import static 
org.apache.ignite.internal.catalog.CatalogTestUtils.columnParamsBuilder;
 import static 
org.apache.ignite.internal.catalog.CatalogTestUtils.initializeColumnWithDefaults;
+import static 
org.apache.ignite.internal.catalog.commands.ColumnParams.ERR_COL_INVALID_TYPE_PARAM;
 import static 
org.apache.ignite.internal.catalog.commands.ColumnParams.ERR_COL_PARAM_DEFINITION;
 import static 
org.apache.ignite.internal.catalog.commands.ColumnParams.ERR_COL_PARAM_NOT_APPLICABLE;
-import static 
org.apache.ignite.internal.catalog.commands.ColumnParams.ERR_COL_PARAM_VALIDATION;
-import static 
org.apache.ignite.internal.catalog.commands.ColumnParams.ERR_COL_POSITIVE_PARAM_VALIDATION;
 import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
 import static 
org.apache.ignite.internal.testframework.IgniteTestUtils.assertThrowsWithCause;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 
+import org.apache.ignite.internal.catalog.commands.CatalogUtils;
 import org.apache.ignite.internal.catalog.commands.ColumnParams;
 import org.apache.ignite.internal.catalog.commands.ColumnParams.Builder;
 import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
@@ -43,22 +43,30 @@ public class ColumnConstructionValidatorTest extends 
BaseIgniteAbstractTest {
     @EnumSource(value = ColumnType.class, names = "NULL", mode = Mode.EXCLUDE)
     public void testColumnSetPrecision(ColumnType type) {
         Builder noPrecision = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
-        Builder invalidPrecision = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
+        Builder negativePrecision = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
         Builder correctPrecision = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
+        Builder tooLargePrecision = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
 
         initializeColumnWithDefaults(type, noPrecision);
-        initializeColumnWithDefaults(type, invalidPrecision);
+        initializeColumnWithDefaults(type, negativePrecision);
         initializeColumnWithDefaults(type, correctPrecision);
+        initializeColumnWithDefaults(type, tooLargePrecision);
 
         noPrecision.precision(null);
-        invalidPrecision.precision(-1);
+        negativePrecision.precision(-1);
 
         if (type.precisionAllowed()) {
             assertThrowsWithCause(noPrecision::build, 
CatalogValidationException.class,
                     format(ERR_COL_PARAM_DEFINITION, "Precision", "COL", 
type.name()));
 
-            assertThrowsWithCause(invalidPrecision::build, 
CatalogValidationException.class,
-                    format(ERR_COL_PARAM_VALIDATION, "Precision", "COL", 
type.name()));
+            assertThrowsWithCause(negativePrecision::build, 
CatalogValidationException.class,
+                    TypeParameter.PRECISION.errorMessage(type, "COL", -1));
+
+            int largeValue = CatalogUtils.getMaxPrecision(type) + 1;
+            tooLargePrecision.precision(largeValue);
+
+            assertThrowsWithCause(tooLargePrecision::build, 
CatalogValidationException.class,
+                    TypeParameter.PRECISION.errorMessage(type, "COL", 
largeValue));
 
             ColumnParams col = correctPrecision.build();
             assertNotNull(col.precision());
@@ -74,22 +82,29 @@ public class ColumnConstructionValidatorTest extends 
BaseIgniteAbstractTest {
     @EnumSource(value = ColumnType.class, names = "NULL", mode = Mode.EXCLUDE)
     public void testColumnSetScale(ColumnType type) {
         Builder noScale = columnParamsBuilder("COL", type, DEFAULT_NULLABLE);
-        Builder invalidScale = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
+        Builder negativeScale = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
         Builder correctScale = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
+        Builder tooLargeScale = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
 
         initializeColumnWithDefaults(type, noScale);
-        initializeColumnWithDefaults(type, invalidScale);
+        initializeColumnWithDefaults(type, negativeScale);
         initializeColumnWithDefaults(type, correctScale);
+        initializeColumnWithDefaults(type, tooLargeScale);
 
         noScale.scale(null);
-        invalidScale.scale(-1);
+        negativeScale.scale(-1);
 
         if (type.scaleAllowed()) {
             assertThrowsWithCause(noScale::build, 
CatalogValidationException.class,
                     format(ERR_COL_PARAM_DEFINITION, "Scale", "COL", 
type.name()));
 
-            assertThrowsWithCause(invalidScale::build, 
CatalogValidationException.class,
-                    format(ERR_COL_PARAM_VALIDATION, "Scale", "COL", 
type.name()));
+            assertThrowsWithCause(negativeScale::build, 
CatalogValidationException.class,
+                    TypeParameter.SCALE.errorMessage(type, "COL", -1));
+
+            int largeValue = CatalogUtils.getMaxScale(type) + 1;
+            tooLargeScale.scale(largeValue);
+            assertThrowsWithCause(tooLargeScale::build, 
CatalogValidationException.class,
+                    TypeParameter.SCALE.errorMessage(type, "COL", largeValue));
 
             ColumnParams col = correctScale.build();
             assertNotNull(col.scale());
@@ -106,27 +121,33 @@ public class ColumnConstructionValidatorTest extends 
BaseIgniteAbstractTest {
     public void testColumnSetLength(ColumnType type) {
         Builder noLength = columnParamsBuilder("COL", type, DEFAULT_NULLABLE);
         Builder negativeLength = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
-        Builder correctLength = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
         Builder zeroLength = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
+        Builder correctLength = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
+        Builder tooLargeLength = columnParamsBuilder("COL", type, 
DEFAULT_NULLABLE);
 
         initializeColumnWithDefaults(type, noLength);
         initializeColumnWithDefaults(type, negativeLength);
         initializeColumnWithDefaults(type, correctLength);
-        initializeColumnWithDefaults(type, zeroLength);
+        initializeColumnWithDefaults(type, tooLargeLength);
 
         noLength.length(null);
-        negativeLength.length(-1);
         zeroLength.length(0);
+        negativeLength.length(-1);
 
         if (type.lengthAllowed()) {
             assertThrowsWithCause(noLength::build, 
CatalogValidationException.class,
                     format(ERR_COL_PARAM_DEFINITION, "Length", "COL", 
type.name()));
 
             assertThrowsWithCause(negativeLength::build, 
CatalogValidationException.class,
-                    format(ERR_COL_POSITIVE_PARAM_VALIDATION, "Length", "COL", 
type.name()));
+                    TypeParameter.LENGTH.errorMessage(type, "COL", -1));
 
             assertThrowsWithCause(zeroLength::build, 
CatalogValidationException.class,
-                    format(ERR_COL_POSITIVE_PARAM_VALIDATION, "Length", "COL", 
type.name()));
+                    TypeParameter.LENGTH.errorMessage(type, "COL", 0));
+
+            int largeValue = CatalogUtils.getMaxLength(type) + 1;
+            tooLargeLength.length(largeValue);
+            assertThrowsWithCause(tooLargeLength::build, 
CatalogValidationException.class,
+                    TypeParameter.LENGTH.errorMessage(type, "COL", 
largeValue));
 
             ColumnParams col = correctLength.build();
             assertNotNull(col.length());
@@ -153,4 +174,43 @@ public class ColumnConstructionValidatorTest extends 
BaseIgniteAbstractTest {
 
         assertThrowsWithCause(colBuilder::build, 
CatalogValidationException.class, "Type is not specified for column 'COL'");
     }
+
+    private enum TypeParameter {
+        PRECISION,
+        SCALE,
+        LENGTH;
+
+        String errorMessage(ColumnType type, String column, int value) {
+            int min;
+            int max;
+            String name;
+            switch (this) {
+                case PRECISION:
+                    name = "Precision";
+                    min = CatalogUtils.getMinPrecision(type);
+                    max = CatalogUtils.getMaxPrecision(type);
+                    break;
+                case SCALE:
+                    name = "Scale";
+                    min = CatalogUtils.getMinScale(type);
+                    max = CatalogUtils.getMaxScale(type);
+                    break;
+                case LENGTH:
+                    name = "Length";
+                    min = CatalogUtils.getMinLength(type);
+                    max = CatalogUtils.getMaxLength(type);
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unexpected type 
parameter: " + this);
+            }
+            return format(ERR_COL_INVALID_TYPE_PARAM,
+                    name,
+                    column,
+                    type,
+                    value,
+                    min,
+                    max
+            );
+        }
+    }
 }
diff --git 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
index 85d718f873d..4ea45f115d9 100644
--- 
a/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
+++ 
b/modules/catalog/src/test/java/org/apache/ignite/internal/catalog/commands/CatalogUtilsTest.java
@@ -19,12 +19,23 @@ package org.apache.ignite.internal.catalog.commands;
 
 import static java.util.stream.Collectors.toList;
 import static 
org.apache.ignite.internal.catalog.CatalogTestUtils.createCatalogManagerWithTestUpdateLog;
+import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.UNSPECIFIED_LENGTH;
+import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.UNSPECIFIED_PRECISION;
+import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.UNSPECIFIED_SCALE;
 import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.clusterWideEnsuredActivationTimestamp;
 import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.replaceIndex;
 import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.replaceTable;
 import static 
org.apache.ignite.internal.hlc.TestClockService.TEST_MAX_CLOCK_SKEW_MILLIS;
 import static 
org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
+import static org.apache.ignite.sql.ColumnType.BYTE_ARRAY;
+import static org.apache.ignite.sql.ColumnType.DATETIME;
+import static org.apache.ignite.sql.ColumnType.DECIMAL;
+import static org.apache.ignite.sql.ColumnType.DURATION;
 import static org.apache.ignite.sql.ColumnType.INT32;
+import static org.apache.ignite.sql.ColumnType.PERIOD;
+import static org.apache.ignite.sql.ColumnType.STRING;
+import static org.apache.ignite.sql.ColumnType.TIME;
+import static org.apache.ignite.sql.ColumnType.TIMESTAMP;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.is;
@@ -36,6 +47,7 @@ import static org.mockito.Mockito.when;
 
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Stream;
 import org.apache.ignite.internal.catalog.Catalog;
 import org.apache.ignite.internal.catalog.CatalogCommand;
 import org.apache.ignite.internal.catalog.CatalogManager;
@@ -50,9 +62,13 @@ import org.apache.ignite.internal.hlc.HybridTimestamp;
 import org.apache.ignite.internal.manager.ComponentContext;
 import org.apache.ignite.internal.sql.SqlCommon;
 import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.sql.ColumnType;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
 
 /** For {@link CatalogUtils} testing. */
 public class CatalogUtilsTest extends BaseIgniteAbstractTest {
@@ -186,6 +202,76 @@ public class CatalogUtilsTest extends 
BaseIgniteAbstractTest {
         assertEquals(expClusterWideActivationTs, 
clusterWideEnsuredActivationTimestamp(catalog.time(), 
TEST_MAX_CLOCK_SKEW_MILLIS));
     }
 
+    @Test
+    void testUnspecifiedConstants() {
+        assertEquals(-1, UNSPECIFIED_PRECISION, "unspecified precision");
+        assertEquals(-1, UNSPECIFIED_LENGTH, "unspecified length");
+        assertEquals(-1, UNSPECIFIED_LENGTH, "unspecified scale");
+    }
+
+    @ParameterizedTest
+    @MethodSource("columnTypesPrecision")
+    void testGetPrecision(ColumnType columnType, int min, int max) {
+        assertEquals(CatalogUtils.getMinPrecision(columnType), min, "min");
+        assertEquals(CatalogUtils.getMaxPrecision(columnType), max, "max");
+    }
+
+    private static Stream<Arguments> columnTypesPrecision() {
+        Stream<Arguments> types = Stream.of(
+                Arguments.of(DECIMAL, 1, 32767),
+                Arguments.of(TIME, 0, 9),
+                Arguments.of(TIMESTAMP, 0, 9),
+                Arguments.of(DATETIME, 0, 9),
+                Arguments.of(DURATION, 1, 10),
+                Arguments.of(PERIOD, 1, 10)
+        );
+
+        Stream<Arguments> otherTypes = Arrays.stream(ColumnType.values())
+                .filter(t -> !t.precisionAllowed())
+                .map(t -> Arguments.of(t, UNSPECIFIED_PRECISION, 
UNSPECIFIED_PRECISION));
+
+        return Stream.concat(types, otherTypes);
+    }
+
+    @ParameterizedTest
+    @MethodSource("columnTypesScale")
+    void testGetScale(ColumnType columnType, int min, int max) {
+        assertEquals(CatalogUtils.getMinScale(columnType), min, "min");
+        assertEquals(CatalogUtils.getMaxScale(columnType), max, "max");
+    }
+
+    private static Stream<Arguments> columnTypesScale() {
+        Stream<Arguments> types = Stream.of(
+                Arguments.of(DECIMAL, 0, 32767)
+        );
+
+        Stream<Arguments> otherTypes = Arrays.stream(ColumnType.values())
+                .filter(t -> !t.scaleAllowed())
+                .map(t -> Arguments.of(t, UNSPECIFIED_SCALE, 
UNSPECIFIED_SCALE));
+
+        return Stream.concat(types, otherTypes);
+    }
+
+    @ParameterizedTest
+    @MethodSource("columnTypesLength")
+    void testGetLength(ColumnType columnType, int min, int max) {
+        assertEquals(CatalogUtils.getMinLength(columnType), min, "min");
+        assertEquals(CatalogUtils.getMaxLength(columnType), max, "max");
+    }
+
+    private static Stream<Arguments> columnTypesLength() {
+        Stream<Arguments> types = Stream.of(
+                Arguments.of(STRING, 1, 65536),
+                Arguments.of(BYTE_ARRAY, 1, 65536)
+        );
+
+        Stream<Arguments> otherTypes = Arrays.stream(ColumnType.values())
+                .filter(t -> !t.lengthAllowed())
+                .map(t -> Arguments.of(t, UNSPECIFIED_LENGTH, 
UNSPECIFIED_LENGTH));
+
+        return Stream.concat(types, otherTypes);
+    }
+
     private void createTable(String tableName) {
         CatalogCommand catalogCommand = CreateTableCommand.builder()
                 .schemaName(SCHEMA_NAME)
diff --git 
a/modules/catalog/src/testFixtures/java/org/apache/ignite/internal/catalog/CatalogTestUtils.java
 
b/modules/catalog/src/testFixtures/java/org/apache/ignite/internal/catalog/CatalogTestUtils.java
index 9edfbc77cd5..d39e8c9361f 100644
--- 
a/modules/catalog/src/testFixtures/java/org/apache/ignite/internal/catalog/CatalogTestUtils.java
+++ 
b/modules/catalog/src/testFixtures/java/org/apache/ignite/internal/catalog/CatalogTestUtils.java
@@ -316,7 +316,7 @@ public class CatalogTestUtils {
     /** Append precision\scale according to type requirement. */
     public static Builder initializeColumnWithDefaults(ColumnType type, 
Builder colBuilder) {
         if (type.precisionAllowed()) {
-            colBuilder.precision(11);
+            colBuilder.precision(4);
         }
 
         if (type.scaleAllowed()) {
diff --git 
a/modules/core/src/main/java/org/apache/ignite/internal/type/NativeTypes.java 
b/modules/core/src/main/java/org/apache/ignite/internal/type/NativeTypes.java
index 826a003a141..c8c22ef66b5 100644
--- 
a/modules/core/src/main/java/org/apache/ignite/internal/type/NativeTypes.java
+++ 
b/modules/core/src/main/java/org/apache/ignite/internal/type/NativeTypes.java
@@ -78,12 +78,12 @@ public class NativeTypes {
     /**
      * STRING type.
      */
-    public static final NativeType STRING = new 
VarlenNativeType(NativeTypeSpec.STRING, Integer.MAX_VALUE);
+    public static final NativeType STRING = new 
VarlenNativeType(NativeTypeSpec.STRING, 65536);
 
     /**
      * BYTES type.
      */
-    public static final NativeType BYTES = new 
VarlenNativeType(NativeTypeSpec.BYTES, Integer.MAX_VALUE);
+    public static final NativeType BYTES = new 
VarlenNativeType(NativeTypeSpec.BYTES, 65536);
 
     /** Timezone-free three-part value representing a year, month, and day. */
     public static final NativeType DATE = new NativeType(NativeTypeSpec.DATE, 
3);
diff --git 
a/modules/core/src/test/java/org/apache/ignite/internal/type/NativeTypeTest.java
 
b/modules/core/src/test/java/org/apache/ignite/internal/type/NativeTypeTest.java
index 430c9707e4e..d42a70a24b6 100644
--- 
a/modules/core/src/test/java/org/apache/ignite/internal/type/NativeTypeTest.java
+++ 
b/modules/core/src/test/java/org/apache/ignite/internal/type/NativeTypeTest.java
@@ -208,4 +208,16 @@ public class NativeTypeTest {
         assertNotEquals(BYTES, blobOf(10));
         assertNotEquals(blobOf(10), BYTES);
     }
+
+    @Test
+    public void stringTypeMaxLength() {
+        VarlenNativeType nativeType = (VarlenNativeType) STRING;
+        assertEquals(65536, nativeType.length());
+    }
+
+    @Test
+    public void blobTypeMaxLength() {
+        VarlenNativeType nativeType = (VarlenNativeType) BYTES;
+        assertEquals(65536, nativeType.length());
+    }
 }
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableDdlTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableDdlTest.java
index 1009b7743bb..55615f0bd29 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableDdlTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItAlterTableDdlTest.java
@@ -20,6 +20,7 @@ package org.apache.ignite.internal.sql.engine;
 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.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.equalTo;
 import static org.hamcrest.Matchers.is;
@@ -334,4 +335,127 @@ public class ItAlterTableDdlTest extends 
BaseSqlIntegrationTest {
                     .check();
         }
     }
+
+
+    @Test
+    public void testAddColumnWithIncorrectType() {
+        sql("CREATE TABLE test(id INTEGER PRIMARY KEY, val INTEGER)");
+
+        // Char
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARCHAR length 10000000 must be between 1 and 65536. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 VARCHAR(10000000)")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARCHAR length 10000000 must be between 1 and 65536. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 VARCHAR(10000000) 
")
+        );
+
+        // Binary
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "BINARY length 10000000 must be between 1 and 65536. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 BINARY(10000000)")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARBINARY length 10000000 must be between 1 and 65536. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 
VARBINARY(10000000)")
+        );
+
+        // Decimal
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "DECIMAL precision 10000000 must be between 1 and 32767. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 DECIMAL(10000000)")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "DECIMAL scale 10000000 must be between 0 and 32767. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 DECIMAL(100, 
10000000)")
+        );
+
+        // Time
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "TIME precision 10000000 must be between 0 and 9. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 TIME(10000000)")
+        );
+
+        // Timestamp
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "TIMESTAMP precision 10000000 must be between 0 and 9. 
[column=VAL2]",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 
TIMESTAMP(10000000)")
+        );
+    }
+
+    @Test
+    public void testAddColumnWithNotFittingDefaultValues() {
+        // Char
+
+        String longString = "1".repeat(101);
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL2'",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 VARCHAR(100) 
DEFAULT x'" + longString + "'")
+        );
+
+        // Binary
+
+        String longByteString = "01".repeat(101);
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL2'",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 BINARY(100) 
DEFAULT x'" + longByteString + "'")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL2'",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 VARBINARY(100) 
DEFAULT x'" + longByteString + "'")
+        );
+
+        // Decimal
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL2'",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 DECIMAL(5) DEFAULT 
1000000")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL2'",
+                () -> sql("ALTER TABLE test ADD COLUMN val2 DECIMAL(3, 2) 
DEFAULT 333.123")
+        );
+
+        // Time
+
+        sql("CREATE TABLE test_time (id INT PRIMARY KEY, val INT)");
+        sql("ALTER TABLE test_time ADD COLUMN val2 TIME(2) DEFAULT 
'00:00:00.1234'");
+        sql("INSERT INTO test_time VALUES (1, 1, DEFAULT)");
+        assertQuery("SELECT val2 FROM test_time")
+                .returns(LocalTime.of(0, 0, 0, 120_000_000))
+                .check();
+
+        // Timestamp
+
+        sql("CREATE TABLE test_ts (id INT PRIMARY KEY, val INT)");
+        sql("ALTER TABLE test_ts ADD COLUMN val2 TIMESTAMP(2) DEFAULT 
'2000-01-01 00:00:00.1234'");
+        sql("INSERT INTO test_ts VALUES (1, 1, DEFAULT)");
+        assertQuery("SELECT val2 FROM test_ts")
+                .returns(LocalDateTime.of(2000, 1, 1, 0, 0, 0, 120_000_000))
+                .check();
+    }
 }
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 8eb899dad25..77a1d9684f0 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
@@ -36,6 +36,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertThrows;
 
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 import java.util.List;
 import java.util.Objects;
 import java.util.stream.Stream;
@@ -521,19 +523,19 @@ public class ItCreateTableDdlTest extends 
BaseSqlIntegrationTest {
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for column 'ID' of type 'STRING' must be at least 1",
+                "VARCHAR length 0 must be between 1 and 65536. [column=ID]",
                 () -> sql("CREATE TABLE TEST(ID VARCHAR(0) PRIMARY KEY, VAL0 
INT)")
         );
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for column 'ID' of type 'BYTE_ARRAY' must be at least 
1",
+                "BINARY length 0 must be between 1 and 65536. [column=ID]",
                 () -> sql("CREATE TABLE TEST(ID BINARY(0) PRIMARY KEY, VAL0 
INT)")
         );
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for column 'ID' of type 'BYTE_ARRAY' must be at least 
1",
+                "VARBINARY length 0 must be between 1 and 65536. [column=ID]",
                 () -> sql("CREATE TABLE TEST(ID VARBINARY(0) PRIMARY KEY, VAL0 
INT)")
         );
     }
@@ -595,6 +597,118 @@ public class ItCreateTableDdlTest extends 
BaseSqlIntegrationTest {
         );
     }
 
+    @Test
+    public void testCreateTableWithIncorrectType() {
+        // Char
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARCHAR length 10000000 must be between 1 and 65536. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
VARCHAR(10000000) )")
+        );
+
+        // Binary
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "BINARY length 10000000 must be between 1 and 65536. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
BINARY(10000000) )")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARBINARY length 10000000 must be between 1 and 65536. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
VARBINARY(10000000) )")
+        );
+
+        // Decimal
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "DECIMAL precision 10000000 must be between 1 and 32767. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
DECIMAL(10000000) )")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "DECIMAL scale 10000000 must be between 0 and 32767. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
DECIMAL(100, 10000000) )")
+        );
+
+        // Time
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "TIME precision 10000000 must be between 0 and 9. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
TIME(10000000) )")
+        );
+
+        // Timestamp
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "TIMESTAMP precision 10000000 must be between 0 and 9. 
[column=VAL]",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
TIMESTAMP(10000000) )")
+        );
+    }
+
+    @Test
+    public void testNotFittingDefaultValues() {
+        // Char
+
+        String longString = "1".repeat(101);
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL'",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
VARCHAR(100) DEFAULT '" + longString + "' )")
+        );
+
+        // Binary
+
+        String longByteString = "01".repeat(101);
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL'",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
BINARY(100) DEFAULT x'" + longByteString + "' )")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL'",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
VARBINARY(100) DEFAULT x'" + longByteString + "' )")
+        );
+
+        // Decimal
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL'",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
DECIMAL(5) DEFAULT 1000000 )")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "Invalid default value for column 'VAL'",
+                () -> sql("CREATE TABLE test (id INT PRIMARY KEY, val 
DECIMAL(3, 2) DEFAULT 333.123 )")
+        );
+
+        // Time
+
+        sql("CREATE TABLE test_time (id INT PRIMARY KEY, val TIME(2) DEFAULT 
'00:00:00.1234' )");
+        sql("INSERT INTO test_time VALUES (1, DEFAULT)");
+        assertQuery("SELECT val FROM test_time")
+                .returns(LocalTime.of(0, 0, 0, 120_000_000))
+                .check();
+
+        // Timestamp
+
+        sql("CREATE TABLE test_ts (id INT PRIMARY KEY, val TIMESTAMP(2) 
DEFAULT '2000-01-01 00:00:00.1234' )");
+        sql("INSERT INTO test_ts VALUES (1, DEFAULT)");
+        assertQuery("SELECT val FROM test_ts")
+                .returns(LocalDateTime.of(2000, 1, 1, 0, 0, 0, 120_000_000))
+                .check();
+    }
+
     private static @Nullable CatalogTableDescriptor getTable(IgniteImpl node, 
String tableName) {
         CatalogManager catalogManager = node.catalogManager();
         HybridClock clock = node.clock();
diff --git 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
index a546cd952fd..b5427c4f214 100644
--- 
a/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
+++ 
b/modules/sql-engine/src/integrationTest/java/org/apache/ignite/internal/sql/engine/ItDataTypesTest.java
@@ -753,7 +753,7 @@ public class ItDataTypesTest extends BaseSqlIntegrationTest 
{
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for type CHAR must be at least 1",
+                "CHAR length 0 must be between 1 and 65536",
                 () -> sql("SELECT CAST(1 AS CHAR(0))")
         );
 
@@ -762,7 +762,7 @@ public class ItDataTypesTest extends BaseSqlIntegrationTest 
{
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for type VARCHAR must be at least 1",
+                "VARCHAR length 0 must be between 1 and 65536",
                 () -> sql("SELECT CAST(1 AS VARCHAR(0))")
         );
 
@@ -770,18 +770,79 @@ public class ItDataTypesTest extends 
BaseSqlIntegrationTest {
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for type BINARY must be at least 1",
+                "BINARY length 0 must be between 1 and 65536",
                 () -> sql("SELECT CAST(x'0101' AS BINARY(0))")
         );
         // Varbinary
 
         assertThrowsSqlException(
                 STMT_VALIDATION_ERR,
-                "Length for type VARBINARY must be at least 1",
+                "VARBINARY length 0 must be between 1 and 65536",
                 () -> sql("SELECT CAST(x'0101' AS VARBINARY(0))")
         );
     }
 
+    @Test
+    public void testInvalidTypeInCast() {
+        // Char
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "CHAR length 100000000 must be between 1 and 65536",
+                () -> sql("SELECT CAST(1 AS CHAR(100000000))")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARCHAR length 100000000 must be between 1 and 65536",
+                () -> sql("SELECT CAST(1 AS VARCHAR(100000000))")
+        );
+
+        // Binary
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "VARBINARY length 100000000 must be between 1 and 65536",
+                () -> sql("SELECT CAST(x'01' AS VARBINARY(100000000))")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "BINARY length 100000000 must be between 1 and 65536",
+                () -> sql("SELECT CAST(x'01' AS BINARY(100000000))")
+        );
+
+        // Decimal
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "DECIMAL precision 100000000 must be between 1 and 32767",
+                () -> sql("SELECT CAST(1 AS DECIMAL(100000000))")
+        );
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "DECIMAL scale 100000000 must be between 0 and 32767",
+                () -> sql("SELECT CAST(1 AS DECIMAL(100, 100000000))")
+        );
+
+        // Time
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "TIME precision 100000000 must be between 0 and 9",
+                () -> sql("SELECT CAST('00:00:00' AS TIME(100000000))")
+        );
+
+        // Timestamp
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                "TIMESTAMP precision 100000000 must be between 0 and 9",
+                () -> sql("SELECT CAST('2000-01-01 00:00:00' AS 
TIMESTAMP(100000000))")
+        );
+    }
+
     static String asLiteral(Object value, RelDataType type) {
         if (SqlTypeUtil.isCharacter(type)) {
             String str = (String) value;
diff --git 
a/modules/sql-engine/src/integrationTest/sql/group1/function/string/test_char_length.test
 
b/modules/sql-engine/src/integrationTest/sql/group1/function/string/test_char_length.test
index 84b1838d888..8b9ee77a09a 100644
--- 
a/modules/sql-engine/src/integrationTest/sql/group1/function/string/test_char_length.test
+++ 
b/modules/sql-engine/src/integrationTest/sql/group1/function/string/test_char_length.test
@@ -35,21 +35,21 @@ SELECT char_length(null)
 NULL
 
 
-statement error: Length for column 'C1' of type 'STRING' must be at least 1
+statement error: VARCHAR length 0 must be between 1 and 65536. [column=C1]
 CREATE TABLE t_invalid_length(c1 VARCHAR(0));
 
 # length shouldn't be zero;
 statement error: Failed to parse query
 CREATE TABLE t_invalid_length(c1 VARCHAR(-1));
 
-statement error: Length for type VARCHAR must be at least 1
+statement error: VARCHAR length 0 must be between 1 and 65536
 SELECT '1'::VARCHAR(0);
 
 statement error: Failed to parse query
 SELECT 'c'::VARCHAR(-1);
 
-statement error: Length for type VARCHAR must be at least 1
+statement error: VARCHAR length 0 must be between 1 and 65536
 SELECT 'c'::VARCHAR(0);
 
-statement error: Length for type VARCHAR must be at least 1
+statement error: VARCHAR length 0 must be between 1 and 65536
 SELECT ''::VARCHAR(0);
diff --git 
a/modules/sql-engine/src/integrationTest/sql/group1/types/blob/test_blob.test 
b/modules/sql-engine/src/integrationTest/sql/group1/types/blob/test_blob.test
index 37042782cda..46081923fd6 100644
--- 
a/modules/sql-engine/src/integrationTest/sql/group1/types/blob/test_blob.test
+++ 
b/modules/sql-engine/src/integrationTest/sql/group1/types/blob/test_blob.test
@@ -76,16 +76,16 @@ SELECT NULL::VARBINARY
 ----
 NULL
 
-statement error: Length for column 'C1' of type 'BYTE_ARRAY' must be at least 1
+statement error: BINARY length 0 must be between 1 and 65536. [column=C1]
 CREATE TABLE t_invalid_length(c1 BINARY(0));
 
-statement error: Length for column 'C1' of type 'BYTE_ARRAY' must be at least 1
+statement error: VARBINARY length 0 must be between 1 and 65536. [column=C1]
 CREATE TABLE t_invalid_length(c1 VARBINARY(0));
 
-statement error: Length for type BINARY must be at least 1
+statement error: BINARY length 0 must be between 1 and 65536
 SELECT CAST(x'0101' AS BINARY(0))
 
-statement error: Length for type VARBINARY must be at least 1
+statement error: VARBINARY length 0 must be between 1 and 65536
 SELECT CAST(x'0101' AS VARBINARY(0))
 
 statement ok
diff --git 
a/modules/sql-engine/src/integrationTest/sql/group1/types/char/test_char_length.test
 
b/modules/sql-engine/src/integrationTest/sql/group1/types/char/test_char_length.test
index 974eb5313fe..5a57577dab3 100644
--- 
a/modules/sql-engine/src/integrationTest/sql/group1/types/char/test_char_length.test
+++ 
b/modules/sql-engine/src/integrationTest/sql/group1/types/char/test_char_length.test
@@ -11,10 +11,10 @@ statement error: Failed to parse query
 SELECT 'c'::CHAR(-1);
 
 # length shouldn't be zero;
-statement error: Length for type CHAR must be at least 1
+statement error: CHAR length 0 must be between 1 and 65536
 SELECT 'c'::CHAR(0);
 
-statement error: Length for type CHAR must be at least 1
+statement error: CHAR length 0 must be between 1 and 65536
 SELECT ''::CHAR(0);
 
 # If <length> is omitted, then a <length> of 1 (one) is implicit.
diff --git 
a/modules/sql-engine/src/integrationTest/sql/group1/types/timestamp/test_timestamp_ms.test
 
b/modules/sql-engine/src/integrationTest/sql/group1/types/timestamp/test_timestamp_ms.test
index e91b493c13b..656fe92bf1c 100644
--- 
a/modules/sql-engine/src/integrationTest/sql/group1/types/timestamp/test_timestamp_ms.test
+++ 
b/modules/sql-engine/src/integrationTest/sql/group1/types/timestamp/test_timestamp_ms.test
@@ -8,7 +8,8 @@ SELECT CAST('2001-04-20 14:42:11.123' AS TIMESTAMP) a, 
CAST('2001-04-20 14:42:11
 2001-04-20 14:42:11.123        2001-04-20 14:42:11
 
 #  many ms
-query I
+statement error: TIMESTAMP precision 20 must be between 0 and 9
 SELECT TIMESTAMP '2001-04-20 14:42:11.12300000000000000000';
-----
-2001-04-20 14:42:11.123
+
+statement error: TIMESTAMP precision 10 must be between 0 and 9
+SELECT CAST('2001-04-20 14:42:11.123' AS TIMESTAMP(10));
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
index b849757a8f1..68a0b656b62 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgnitePlanner.java
@@ -228,6 +228,8 @@ public class IgnitePlanner implements Planner, 
RelOptTable.ViewExpander {
      * @return Relational type representation of given SQL type.
      */
     public RelDataType convert(SqlDataTypeSpec typeSpec, boolean nullable) {
+        // Validate data type first.
+        validator().validateDataType(typeSpec);
         return typeSpec.deriveType(validator(), nullable);
     }
 
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
index a8957e64d79..7e9aa7a2fe1 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/IgniteSqlValidator.java
@@ -18,6 +18,8 @@
 package org.apache.ignite.internal.sql.engine.prepare;
 
 import static java.util.Objects.requireNonNull;
+import static org.apache.calcite.rel.type.RelDataType.PRECISION_NOT_SPECIFIED;
+import static org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
 import static org.apache.calcite.sql.type.SqlTypeName.INTEGER;
 import static org.apache.calcite.sql.type.SqlTypeUtil.isNull;
 import static org.apache.calcite.util.Static.RESOURCE;
@@ -45,6 +47,7 @@ import org.apache.calcite.prepare.CalciteCatalogReader;
 import org.apache.calcite.prepare.Prepare;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.rel.type.RelDataTypeSystem;
 import org.apache.calcite.runtime.CalciteContextException;
 import org.apache.calcite.runtime.PairList;
 import org.apache.calcite.runtime.Resources;
@@ -53,6 +56,7 @@ import org.apache.calcite.sql.JoinConditionType;
 import org.apache.calcite.sql.JoinType;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlBasicCall;
+import org.apache.calcite.sql.SqlBasicTypeNameSpec;
 import org.apache.calcite.sql.SqlCall;
 import org.apache.calcite.sql.SqlCallBinding;
 import org.apache.calcite.sql.SqlDataTypeSpec;
@@ -71,6 +75,8 @@ import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlTypeNameSpec;
+import org.apache.calcite.sql.SqlUnknownLiteral;
 import org.apache.calcite.sql.SqlUpdate;
 import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlWithItem;
@@ -78,6 +84,7 @@ import org.apache.calcite.sql.dialect.CalciteSqlDialect;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.BasicSqlType;
+import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeMappingRule;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
@@ -105,6 +112,7 @@ import 
org.apache.ignite.internal.sql.engine.util.IgniteCustomAssignmentsRules;
 import org.apache.ignite.internal.sql.engine.util.IgniteResource;
 import org.apache.ignite.internal.sql.engine.util.TypeUtils;
 import org.apache.ignite.internal.type.NativeTypeSpec;
+import org.apache.ignite.sql.ColumnType;
 import org.apache.ignite.sql.SqlException;
 import org.jetbrains.annotations.Nullable;
 
@@ -539,9 +547,26 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     /** {@inheritDoc} */
     @Override
     public void validateLiteral(SqlLiteral literal) {
-        if (literal.getTypeName() != SqlTypeName.DECIMAL) {
+        SqlTypeName typeName = literal.getTypeName();
+
+        if (typeName != SqlTypeName.DECIMAL) {
             super.validateLiteral(literal);
         }
+
+        // SqlLiteral createSqlType can not be called on
+        // SqlUnknownLiteral because SELECT TIMESTAMP 'valid-ts' is a 
SqlUnknownLiteral later converted to timestamp literal
+        if (literal instanceof SqlUnknownLiteral) {
+            return;
+        } else if (literal.getClass() == SqlLiteral.class
+                && !SqlTypeName.CHAR_TYPES.contains(typeName)
+                && !SqlTypeName.INTERVAL_TYPES.contains(typeName)
+                && !SqlTypeName.BINARY_TYPES.contains(typeName)) {
+            // createSqlType can not be called in this case as well.
+            return;
+        }
+
+        RelDataType dataType = literal.createSqlType(typeFactory);
+        validatePrecisionScale(literal, dataType, dataType.getPrecision(), 
dataType.getScale());
     }
 
     @Override
@@ -932,15 +957,66 @@ public class IgniteSqlValidator extends SqlValidatorImpl {
     public void validateDataType(SqlDataTypeSpec dataType) {
         RelDataType type = dataType.deriveType(this);
 
-        if (SqlTypeUtil.isString(type) && type.getPrecision() == 0) {
-            String typeName = type.getSqlTypeName().getSpaceName();
+        SqlTypeNameSpec sqlTypeNameSpec = dataType.getTypeNameSpec();
+        if (sqlTypeNameSpec instanceof SqlBasicTypeNameSpec) {
+            SqlBasicTypeNameSpec typeNameSpec = (SqlBasicTypeNameSpec) 
sqlTypeNameSpec;
 
-            throw newValidationError(dataType, 
IgniteResource.INSTANCE.invalidStringLength(typeName));
+            validatePrecisionScale(dataType, type, 
typeNameSpec.getPrecision(), typeNameSpec.getScale());
         }
 
         super.validateDataType(dataType);
     }
 
+    private void validatePrecisionScale(
+            SqlNode typeNode,
+            RelDataType type,
+            int precision,
+            int scale
+    ) {
+        // TypeFactory sets type's precision to maxPrecision if it exceeds 
type's maxPrecision.
+        // Use precision/scale from type name spec to correct this issue.
+        // Negative values are rejected by the parser so we need to check only 
max values.
+
+        SqlTypeName typeName = type.getSqlTypeName();
+        ColumnType columnType = TypeUtils.columnType(type);
+        boolean allowsLength = columnType.lengthAllowed();
+        boolean allowsScale = columnType.scaleAllowed();
+        boolean allowsPrecision = columnType.precisionAllowed();
+
+        RelDataTypeSystem typeSystem = typeFactory.getTypeSystem();
+
+        if (precision != PRECISION_NOT_SPECIFIED && (allowsPrecision || 
allowsLength)) {
+            int minPrecision = typeSystem.getMinPrecision(typeName);
+            int maxPrecision = typeSystem.getMaxPrecision(typeName);
+
+            // Empty varchar/bytestring literals have zero precision
+            if (typeNode instanceof SqlLiteral && 
SqlTypeFamily.STRING.contains(type)) {
+                minPrecision = 0;
+            }
+
+            if (precision < minPrecision || precision > maxPrecision) {
+                String spaceName = typeName.getSpaceName();
+                if (allowsLength) {
+                    throw newValidationError(typeNode,
+                            
IgniteResource.INSTANCE.invalidLengthForType(spaceName, precision, 
minPrecision, maxPrecision));
+                } else {
+                    throw newValidationError(typeNode,
+                            
IgniteResource.INSTANCE.invalidPrecisionForType(spaceName, precision, 
minPrecision, maxPrecision));
+                }
+            }
+        }
+
+        if (scale != SCALE_NOT_SPECIFIED && allowsScale) {
+            int minScale = typeSystem.getMinScale(typeName);
+            int maxScale = typeSystem.getMaxScale(typeName);
+
+            if (scale < minScale || scale > maxScale) {
+                throw newValidationError(typeNode,
+                        
IgniteResource.INSTANCE.invalidScaleForType(typeName.getSpaceName(), scale, 
minScale, maxScale));
+            }
+        }
+    }
+
     @Override
     protected void validateJoin(SqlJoin join, SqlValidatorScope scope) {
         if (join.getJoinType() == JoinType.ASOF || join.getJoinType() == 
JoinType.LEFT_ASOF) {
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 88a69188dc1..b39118c5b84 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
@@ -434,7 +434,19 @@ public class DdlSqlToCommandConverter {
         assert col.name.isSimple();
 
         String name = col.name.getSimple();
-        RelDataType relType = planner.convert(col.dataType, nullable);
+
+        RelDataType relType;
+        try {
+            relType = planner.convert(col.dataType, nullable);
+        } catch (CalciteContextException e) {
+            String errorMessage = e.getMessage();
+            if (errorMessage == null) {
+                errorMessage = "Unable to resolve data type";
+            }
+
+            String message = format("{} [column={}]", errorMessage, name);
+            throw new SqlException(STMT_VALIDATION_ERR, message, e);
+        }
 
         // TODO: https://issues.apache.org/jira/browse/IGNITE-17373
         //  Remove this after interval type support is added.
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
index 78c667539b9..702c9171ab7 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeFactory.java
@@ -18,6 +18,7 @@
 package org.apache.ignite.internal.sql.engine.type;
 
 import static org.apache.calcite.rel.type.RelDataType.PRECISION_NOT_SPECIFIED;
+import static org.apache.calcite.rel.type.RelDataType.SCALE_NOT_SPECIFIED;
 import static 
org.apache.ignite.internal.catalog.commands.CatalogUtils.DEFAULT_VARLEN_LENGTH;
 import static 
org.apache.ignite.internal.sql.engine.util.TypeUtils.typeFamiliesAreCompatible;
 import static org.apache.ignite.internal.util.CollectionUtils.first;
@@ -53,6 +54,7 @@ import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.BasicSqlType;
 import org.apache.calcite.sql.type.IntervalSqlType;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.type.NativeType;
 import org.apache.ignite.internal.type.NativeTypes;
@@ -120,6 +122,55 @@ public class IgniteTypeFactory extends JavaTypeFactoryImpl 
{
         customDataTypes = new CustomDataTypes(Set.of(uuidType));
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public RelDataType createSqlType(SqlTypeName typeName, int precision) {
+        // Default implementation converts precision > maxPrecision to 
maxPrecision
+        assertBasicType(typeName);
+
+        if (typeName.allowsScale()) {
+            return createSqlType(typeName, precision, 
typeSystem.getDefaultScale(typeName));
+        }
+
+        assert (precision >= 0) || (precision == PRECISION_NOT_SPECIFIED);
+
+        // Does not check precision when typeName is SqlTypeName#NULL.
+        RelDataType newType = precision == PRECISION_NOT_SPECIFIED
+                ? new BasicSqlType(typeSystem, typeName)
+                : new BasicSqlType(typeSystem, typeName, precision);
+        newType = SqlTypeUtil.addCharsetAndCollation(newType, this);
+        return canonize(newType);
+    }
+
+    /** {@inheritDoc} */
+    @Override
+    public RelDataType createSqlType(SqlTypeName typeName, int precision, int 
scale) {
+        // Default implementation converts precision > maxPrecision to 
maxPrecision
+
+        assertBasicType(typeName);
+
+        assert (precision >= 0) || (precision == PRECISION_NOT_SPECIFIED);
+        assert (scale >= 0) || (scale == SCALE_NOT_SPECIFIED);
+
+        RelDataType newType = new BasicSqlType(typeSystem, typeName, 
precision, scale);
+        newType = SqlTypeUtil.addCharsetAndCollation(newType, this);
+        return canonize(newType);
+    }
+
+    private static void assertBasicType(SqlTypeName typeName) {
+        assert typeName != null;
+        assert typeName != SqlTypeName.MULTISET
+                : "use createMultisetType() instead";
+        assert typeName != SqlTypeName.ARRAY
+                : "use createArrayType() instead";
+        assert typeName != SqlTypeName.MAP
+                : "use createMapType() instead";
+        assert typeName != SqlTypeName.ROW
+                : "use createStructType() instead";
+        assert !SqlTypeName.INTERVAL_TYPES.contains(typeName)
+                : "use createSqlIntervalType() instead";
+    }
+
     /** {@inheritDoc} */
     @Override
     public Type getJavaClass(RelDataType type) {
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystem.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystem.java
index ce3b42770ec..94ad7b08863 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystem.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystem.java
@@ -48,6 +48,21 @@ public class IgniteTypeSystem extends RelDataTypeSystemImpl {
         return CatalogUtils.MAX_DECIMAL_PRECISION;
     }
 
+    /** {@inheritDoc} */
+    @Override
+    public int getMinPrecision(SqlTypeName typeName) {
+        switch (typeName) {
+            case TIME:
+            case TIME_WITH_LOCAL_TIME_ZONE:
+            case TIMESTAMP:
+            case TIMESTAMP_WITH_LOCAL_TIME_ZONE:
+                // Calcite's default min precision is 1
+                return CatalogUtils.MIN_TIME_PRECISION;
+            default:
+                return super.getMinPrecision(typeName);
+        }
+    }
+
     /** {@inheritDoc} */
     @Override
     public int getMaxPrecision(SqlTypeName typeName) {
diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
index eacce802b3b..1dd78ca6c21 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/IgniteResource.java
@@ -85,8 +85,14 @@ public interface IgniteResource {
     @BaseMessage("{0} datatype is not supported'")
     ExInst<SqlValidatorException> dataTypeIsNotSupported(String a0);
 
-    @BaseMessage("Length for type {0} must be at least 1")
-    ExInst<SqlValidatorException> invalidStringLength(String typeName);
+    @BaseMessage("{0} length {1,number,#} must be between {2,number,#} and 
{3,number,#}.")
+    ExInst<SqlValidatorException> invalidLengthForType(String typeName, int 
value, int min, int max);
+
+    @BaseMessage("{0} precision {1,number,#} must be between {2,number,#} and 
{3,number,#}.")
+    ExInst<SqlValidatorException> invalidPrecisionForType(String typeName, int 
value, int min, int max);
+
+    @BaseMessage("{0} scale {1,number,#} must be between {2,number,#} and 
{3,number,#}.")
+    ExInst<SqlValidatorException> invalidScaleForType(String typeName, int 
value, int min, int max);
 
     @BaseMessage("Column N#{0} matched using NATURAL keyword or USING clause "
             + "has incompatible types in this context: ''{1}'' to ''{2}''")
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
index 2cf564854e3..6fc18be1a02 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/ddl/DdlSqlToCommandConverterTest.java
@@ -297,6 +297,70 @@ public class DdlSqlToCommandConverterTest extends 
AbstractDdlSqlToCommandConvert
         assertThat(tblEntry.descriptor().zoneId(), is(TEST_ZONE_ID));
     }
 
+    @ParameterizedTest
+    @CsvSource(value = {
+            // Negative values are rejected by the parser
+            // Char
+            "VARCHAR(0); VARCHAR length 0 must be between 1 and 65536",
+            "VARCHAR(100000000); VARCHAR length 100000000 must be between 1 
and 65536",
+            // Binary
+            "BINARY(0); BINARY length 0 must be between 1 and 65536",
+            "BINARY(100000000); BINARY length 100000000 must be between 1 and 
65536",
+            "VARBINARY(0); VARBINARY length 0 must be between 1 and 65536",
+            "VARBINARY(100000000); VARBINARY length 100000000 must be between 
1 and 65536",
+            // Decimal
+            "DECIMAL(0); DECIMAL precision 0 must be between 1 and 32767",
+            "DECIMAL(100000000); DECIMAL precision 100000000 must be between 1 
and 32767",
+            "DECIMAL(100, 100000000); DECIMAL scale 100000000 must be between 
0 and 32767",
+            // Timestamp
+            "TIME(100000000); TIME precision 100000000 must be between 0 and 
9",
+            "TIMESTAMP(100000000); TIMESTAMP precision 100000000 must be 
between 0 and 9",
+    }, delimiter = ';')
+    @WithSystemProperty(key = "IMPLICIT_PK_ENABLED", value = "true")
+    public void tableWithIncorrectType(String type, String error) throws 
SqlParseException {
+        SqlNode node = parse("CREATE TABLE t (val " + type + ")");
+
+        assertThat(node, instanceOf(SqlDdl.class));
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                error + ". [column=VAL]",
+                () -> converter.convert((SqlDdl) node, createContext())
+        );
+    }
+
+    @ParameterizedTest
+    @CsvSource(value = {
+            // Negative values are rejected by the parser
+            // Char
+            "VARCHAR(0); VARCHAR length 0 must be between 1 and 65536",
+            "VARCHAR(100000000); VARCHAR length 100000000 must be between 1 
and 65536",
+            // Binary
+            "BINARY(0); BINARY length 0 must be between 1 and 65536",
+            "BINARY(100000000); BINARY length 100000000 must be between 1 and 
65536",
+            "VARBINARY(0); VARBINARY length 0 must be between 1 and 65536",
+            "VARBINARY(100000000); VARBINARY length 100000000 must be between 
1 and 65536",
+            // Decimal
+            "DECIMAL(0); DECIMAL precision 0 must be between 1 and 32767",
+            "DECIMAL(100000000); DECIMAL precision 100000000 must be between 1 
and 32767",
+            "DECIMAL(100, 100000000); DECIMAL scale 100000000 must be between 
0 and 32767",
+            // Timestamp
+            "TIME(100000000); TIME precision 100000000 must be between 0 and 
9",
+            "TIMESTAMP(100000000); TIMESTAMP precision 100000000 must be 
between 0 and 9",
+    }, delimiter = ';')
+    @WithSystemProperty(key = "IMPLICIT_PK_ENABLED", value = "true")
+    public void tableAddColumnWithIncorrectType(String type, String error) 
throws SqlParseException {
+        SqlNode node = parse("ALTER TABLE t ADD COLUMN val " + type);
+
+        assertThat(node, instanceOf(SqlDdl.class));
+
+        assertThrowsSqlException(
+                STMT_VALIDATION_ERR,
+                error + ". [column=VAL]",
+                () -> converter.convert((SqlDdl) node, createContext())
+        );
+    }
+
     @TestFactory
     @Disabled("https://issues.apache.org/jira/browse/IGNITE-17373";)
     public Stream<DynamicTest> numericDefaultWithIntervalTypes() {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningPredicateSelfTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningPredicateSelfTest.java
index d0ea4d1f1e4..0184af5a813 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningPredicateSelfTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningPredicateSelfTest.java
@@ -127,7 +127,7 @@ public class PartitionPruningPredicateSelfTest extends 
BaseIgniteAbstractTest {
         }
 
         // To prevent generate too big values.
-        if (columnType == ColumnType.BYTE_ARRAY || columnType == 
ColumnType.DECIMAL) {
+        if (columnType == ColumnType.STRING || columnType == 
ColumnType.BYTE_ARRAY || columnType == ColumnType.DECIMAL) {
             precision = 7_000;
             scale = precision / 2;
         }
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
index 47c469619ff..686e58150b7 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/type/IgniteTypeSystemTest.java
@@ -22,12 +22,16 @@ import static 
org.apache.ignite.internal.sql.engine.util.TypeUtils.native2relati
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 
+import java.util.Arrays;
+import java.util.List;
 import java.util.stream.Stream;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.ignite.internal.catalog.commands.CatalogUtils;
 import org.apache.ignite.internal.sql.engine.util.Commons;
 import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
 import org.apache.ignite.internal.type.NativeTypes;
+import org.apache.ignite.sql.ColumnType;
 import org.hamcrest.Matchers;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
@@ -270,4 +274,135 @@ public class IgniteTypeSystemTest extends 
BaseIgniteAbstractTest {
                 )
         );
     }
+
+    // Precision
+
+    @ParameterizedTest
+    @MethodSource("typesWithPrecision")
+    public void testCatalogMaxPrecisionCompatibility(ColumnType columnType, 
SqlTypeName sqlTypeName) {
+        int catalogMaxPrecision = CatalogUtils.getMaxPrecision(columnType);
+        int typeSystemValue = typeSystem.getMaxPrecision(sqlTypeName);
+        assertEquals(catalogMaxPrecision, typeSystemValue);
+    }
+
+    @ParameterizedTest
+    @MethodSource("typesWithPrecision")
+    public void testCatalogMinPrecisionCompatibility(ColumnType columnType, 
SqlTypeName sqlTypeName) {
+        int catalogValue = CatalogUtils.getMinPrecision(columnType);
+        int typeSystemValue = typeSystem.getMinPrecision(sqlTypeName);
+        assertEquals(catalogValue, typeSystemValue);
+    }
+
+    // Length
+
+    @ParameterizedTest
+    @MethodSource("typesWithLength")
+    public void testCatalogMaxLengthCompatibility(ColumnType columnType, 
SqlTypeName sqlTypeName) {
+        int catalogValue = CatalogUtils.getMaxLength(columnType);
+        int typeSystemValue = typeSystem.getMaxPrecision(sqlTypeName);
+        assertEquals(catalogValue, typeSystemValue);
+    }
+
+    @ParameterizedTest
+    @MethodSource("typesWithLength")
+    public void testCatalogMinLengthCompatibility(ColumnType columnType, 
SqlTypeName sqlTypeName) {
+        int catalogValue = CatalogUtils.getMinLength(columnType);
+
+        int typeSystemValue = typeSystem.getMinPrecision(sqlTypeName);
+        assertEquals(catalogValue, typeSystemValue);
+    }
+
+    // Scale
+
+    @ParameterizedTest
+    @MethodSource("typesWithScale")
+    public void testCatalogMaxScaleCompatibility(ColumnType columnType, 
SqlTypeName sqlTypeName) {
+        int catalogValue = CatalogUtils.getMaxScale(columnType);
+
+        int typeSystemValue = typeSystem.getMaxScale(sqlTypeName);
+        assertEquals(catalogValue, typeSystemValue);
+    }
+
+    @ParameterizedTest
+    @MethodSource("typesWithScale")
+    public void testCatalogMinScaleCompatibility(ColumnType columnType, 
SqlTypeName sqlTypeName) {
+        int catalogValue = CatalogUtils.getMinScale(columnType);
+        int typeSystemValue = typeSystem.getMinScale(sqlTypeName);
+        assertEquals(catalogValue, typeSystemValue);
+    }
+
+    private static Stream<Arguments> typesWithPrecision() {
+        return 
Arrays.stream(ColumnType.values()).filter(ColumnType::precisionAllowed)
+                .flatMap(IgniteTypeSystemTest::sqlTypesForColumnType);
+    }
+
+    private static Stream<Arguments> typesWithScale() {
+        return 
Arrays.stream(ColumnType.values()).filter(ColumnType::scaleAllowed)
+                .flatMap(IgniteTypeSystemTest::sqlTypesForColumnType);
+    }
+
+    private static Stream<Arguments> typesWithLength() {
+        return 
Arrays.stream(ColumnType.values()).filter(ColumnType::lengthAllowed)
+                .flatMap(IgniteTypeSystemTest::sqlTypesForColumnType);
+    }
+
+    private static Stream<Arguments> sqlTypesForColumnType(ColumnType 
columnType) {
+        return columnTypeToSqlTypes(columnType).stream().map(s -> 
Arguments.arguments(columnType, s));
+    }
+
+    private static List<SqlTypeName> columnTypeToSqlTypes(ColumnType 
columnType) {
+        switch (columnType) {
+            case NULL:
+                return List.of(SqlTypeName.NULL);
+            case BOOLEAN:
+                return List.of(SqlTypeName.BOOLEAN);
+            case INT8:
+                return List.of(SqlTypeName.TINYINT);
+            case INT16:
+                return List.of(SqlTypeName.SMALLINT);
+            case INT32:
+                return List.of(SqlTypeName.INTEGER);
+            case INT64:
+                return List.of(SqlTypeName.BIGINT);
+            case FLOAT:
+                return List.of(SqlTypeName.REAL);
+            case DOUBLE:
+                return List.of(SqlTypeName.DOUBLE);
+            case DECIMAL:
+                return List.of(SqlTypeName.DECIMAL);
+            case DATE:
+                return List.of(SqlTypeName.DATE);
+            case TIME:
+                return List.of(SqlTypeName.TIME);
+            case DATETIME:
+                return List.of(SqlTypeName.TIMESTAMP);
+            case TIMESTAMP:
+                return List.of(SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE);
+            case STRING:
+                return List.of(SqlTypeName.CHAR, SqlTypeName.VARCHAR);
+            case BYTE_ARRAY:
+                return List.of(SqlTypeName.BINARY, SqlTypeName.VARBINARY);
+            case PERIOD:
+                return List.of(
+                        SqlTypeName.INTERVAL_YEAR,
+                        SqlTypeName.INTERVAL_YEAR_MONTH,
+                        SqlTypeName.INTERVAL_MONTH
+                );
+            case DURATION:
+                return List.of(
+                        SqlTypeName.INTERVAL_DAY,
+                        SqlTypeName.INTERVAL_DAY_HOUR,
+                        SqlTypeName.INTERVAL_DAY_MINUTE,
+                        SqlTypeName.INTERVAL_DAY_SECOND,
+                        SqlTypeName.INTERVAL_HOUR,
+                        SqlTypeName.INTERVAL_HOUR_MINUTE,
+                        SqlTypeName.INTERVAL_HOUR_SECOND,
+                        SqlTypeName.INTERVAL_MINUTE,
+                        SqlTypeName.INTERVAL_MINUTE_SECOND,
+                        SqlTypeName.INTERVAL_SECOND
+                );
+            default:
+                throw new IllegalArgumentException("Unexpected type: " + 
columnType);
+        }
+    }
 }
diff --git 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
index d8bdd3b6aa6..fbfd0b81661 100644
--- 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
+++ 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
@@ -328,7 +328,7 @@ public class SqlTestUtils {
 
     /**
      * Generate random value for given {@link ColumnType}. For precision and 
scale will be used maximums precisions and scale in SQL type
-     * system, except for byte arrays and decimals, to decrease generated 
values.
+     * system, except for strings, byte arrays and decimals, to decrease 
generated values.
      *
      * @param type SQL type to generate value related to the type.
      * @return Generated value for given SQL type.
@@ -339,7 +339,7 @@ public class SqlTestUtils {
         int scale = IgniteTypeSystem.INSTANCE.getMaxScale(sqlTypeName);
 
         // To prevent generate too big values.
-        if (type == ColumnType.BYTE_ARRAY || type == ColumnType.DECIMAL) {
+        if (type == ColumnType.STRING || type == ColumnType.BYTE_ARRAY || type 
== ColumnType.DECIMAL) {
             precision = 7_000;
             scale = precision / 2;
         }

Reply via email to