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;
}