diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index 34878e643eeb..30a82fcd38c9 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -4561,36 +4561,32 @@ private Optional extractTableVersion(Table table, QualifiedObjectN // Once the range value is analyzed, we can evaluate it Type versionType = expressionAnalysis.getType(version.get()); PointerType pointerType = toPointerType(table.getQueryPeriod().get().getRangeType()); + if (versionType == UNKNOWN) { + throw semanticException(INVALID_ARGUMENTS, table.getQueryPeriod().get(), "Pointer value cannot be NULL"); + } Object evaluatedVersion = evaluateConstantExpression(version.get(), versionType, plannerContext, session, accessControl, ImmutableMap.of()); TableVersion extractedVersion = new TableVersion(pointerType, versionType, evaluatedVersion); + validateVersionPointer(tableName, table.getQueryPeriod().get(), extractedVersion); + return Optional.of(extractedVersion); + } - // Before checking if the connector supports the version type, verify that version is a valid time-based type + private void validateVersionPointer(QualifiedObjectName tableName, QueryPeriod queryPeriod, TableVersion extractedVersion) + { + Type type = extractedVersion.getObjectType(); if (extractedVersion.getPointerType() == PointerType.TEMPORAL) { - if (!isValidTemporalType(extractedVersion.getObjectType())) { - throw semanticException( - TYPE_MISMATCH, - table.getQueryPeriod().get(), - format( - "Type %s invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date.", - extractedVersion.getObjectType().getDisplayName())); + // Before checking if the connector supports the version type, verify that version is a valid time-based type + if (!(type instanceof TimestampWithTimeZoneType || + type instanceof TimestampType || + type instanceof DateType)) { + throw semanticException(TYPE_MISMATCH, queryPeriod, format( + "Type %s invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date.", + type.getDisplayName())); } } if (!metadata.isValidTableVersion(session, tableName, extractedVersion)) { - throw semanticException( - TYPE_MISMATCH, - table.getQueryPeriod().get(), - format("Type %s not supported by this connector.", extractedVersion.getObjectType().getDisplayName())); + throw semanticException(TYPE_MISMATCH, queryPeriod, format("Type %s not supported by this connector.", type.getDisplayName())); } - - return Optional.of(extractedVersion); - } - - private boolean isValidTemporalType(Type type) - { - return (type instanceof TimestampWithTimeZoneType || - type instanceof TimestampType || - type instanceof DateType); } private PointerType toPointerType(QueryPeriod.RangeType type) diff --git a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java index a7375e7f8e46..d19a9d607b36 100644 --- a/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java +++ b/core/trino-main/src/test/java/io/trino/sql/analyzer/TestAnalyzer.java @@ -366,6 +366,95 @@ public void testSelectAllColumns() .hasErrorCode(COLUMN_NOT_FOUND); } + @Test + public void testTemporalTableVersion() + { + // valid temporal version pointer + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF DATE '2022-01-01'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF TIMESTAMP '2022-01-01'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF TIMESTAMP '2022-01-01 01:02:03.123456789012'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF TIMESTAMP '2022-01-01 UTC'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF TIMESTAMP '2022-01-01 01:02:03.123456789012 Asia/Kathmandu'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + + // wrong type + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF '2022-01-01'") + .hasErrorCode(TYPE_MISMATCH) + .hasMessage("line 1:18: Type varchar(10) invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date."); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF '2022-01-01 01:02:03'") + .hasErrorCode(TYPE_MISMATCH) + .hasMessage("line 1:18: Type varchar(19) invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date."); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF '2022-01-01 01:02:03 UTC'") + .hasErrorCode(TYPE_MISMATCH) + .hasMessage("line 1:18: Type varchar(23) invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date."); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF 1654594283421") + .hasErrorCode(TYPE_MISMATCH) + .hasMessage("line 1:18: Type bigint invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date."); + + // null value with right type + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF CAST(NULL AS date)") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF CAST(NULL AS timestamp(3))") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF CAST(NULL AS timestamp(3) with time zone)") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + + // null value with wrong type + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF NULL") + .hasErrorCode(INVALID_ARGUMENTS) + .hasMessage("line 1:18: Pointer value cannot be NULL"); + assertFails("SELECT * FROM t1 FOR TIMESTAMP AS OF CAST(NULL AS bigint)") + .hasErrorCode(TYPE_MISMATCH) + .hasMessage("line 1:18: Type bigint invalid. Temporal pointers must be of type Timestamp, Timestamp with Time Zone, or Date."); + } + + @Test + public void testRangeIdTableVersion() + { + // integer + assertFails("SELECT * FROM t1 FOR VERSION AS OF 123") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + + // bigint + assertFails("SELECT * FROM t1 FOR VERSION AS OF BIGINT '123'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + + // varchar + assertFails("SELECT * FROM t1 FOR VERSION AS OF '2022-01-01'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + + // date + assertFails("SELECT * FROM t1 FOR VERSION AS OF DATE '2022-01-01'") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + + // null value + assertFails("SELECT * FROM t1 FOR VERSION AS OF NULL") + .hasErrorCode(INVALID_ARGUMENTS) + .hasMessage("line 1:18: Pointer value cannot be NULL"); + assertFails("SELECT * FROM t1 FOR VERSION AS OF CAST(NULL AS bigint)") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + assertFails("SELECT * FROM t1 FOR VERSION AS OF CAST(NULL AS varchar)") + .hasErrorCode(NOT_SUPPORTED) + .hasMessage("This connector does not support versioned tables"); + } + @Test public void testGroupByWithWildcard() {