From d8d60a9983635fe2a4e21064f81ec34002865e8b Mon Sep 17 00:00:00 2001 From: Ajay Gupte Date: Mon, 29 Apr 2024 11:33:48 -0700 Subject: [PATCH] BEFORE syntax support for presto engine. --- .../iceberg/TestIcebergTableVersion.java | 29 +-- .../sql/analyzer/StatementAnalyzer.java | 53 ++++-- .../com/facebook/presto/sql/parser/SqlBase.g4 | 10 +- .../presto/sql/ExpressionFormatter.java | 5 +- .../presto/sql/parser/AstBuilder.java | 19 +- .../sql/tree/TableVersionExpression.java | 54 ++++-- .../presto/sql/parser/TestSqlParser.java | 180 ++++++++++++++++-- .../sql/parser/TestStatementBuilder.java | 9 + .../spi/connector/ConnectorMetadata.java | 4 +- .../spi/connector/ConnectorTableVersion.java | 10 +- 10 files changed, 297 insertions(+), 76 deletions(-) diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergTableVersion.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergTableVersion.java index ceb14ad71983..93c7227a0146 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergTableVersion.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergTableVersion.java @@ -217,26 +217,31 @@ public void testTableVersionMisc() @Test public void testTableVersionErrors() { - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF 100", ".* Type integer is invalid. Supported table version AS OF expression type is BIGINT"); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF 'bad'", ".* Type varchar\\(3\\) is invalid. Supported table version AS OF expression type is BIGINT"); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF CURRENT_DATE", ".* Type date is invalid. Supported table version AS OF expression type is BIGINT"); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF CURRENT_TIMESTAMP", ".* Type timestamp with time zone is invalid. Supported table version AS OF expression type is BIGINT"); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF 100", ".* Type integer is invalid. Supported table version AS OF/BEFORE expression type is BIGINT"); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF 'bad'", ".* Type varchar\\(3\\) is invalid. Supported table version AS OF/BEFORE expression type is BIGINT"); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF CURRENT_DATE", ".* Type date is invalid. Supported table version AS OF/BEFORE expression type is BIGINT"); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF CURRENT_TIMESTAMP", ".* Type timestamp with time zone is invalid. Supported table version AS OF/BEFORE expression type is BIGINT"); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF id", ".* cannot be resolved"); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF (SELECT 10000000)", ".* Constant expression cannot contain a subquery"); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF NULL", "Table version AS OF expression cannot be NULL for .*"); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF NULL", "Table version AS OF/BEFORE expression cannot be NULL for .*"); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF " + tab2VersionId1 + " - " + tab2VersionId1, "Iceberg snapshot ID does not exists: 0"); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR VERSION AS OF CAST (100 AS BIGINT)", "Iceberg snapshot ID does not exists: 100"); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF 100", ".* Type integer is invalid. Supported table version AS OF expression type is Timestamp with Time Zone."); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF 'bad'", ".* Type varchar\\(3\\) is invalid. Supported table version AS OF expression type is Timestamp with Time Zone."); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF 100", ".* Type integer is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone."); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF 'bad'", ".* Type varchar\\(3\\) is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone."); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF id", ".* cannot be resolved"); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF (SELECT CURRENT_TIMESTAMP)", ".* Constant expression cannot contain a subquery"); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF NULL", "Table version AS OF expression cannot be NULL for .*"); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF NULL", "Table version AS OF/BEFORE expression cannot be NULL for .*"); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF TIMESTAMP " + "'" + tab2Timestamp1 + "' - INTERVAL '1' MONTH", "No history found based on timestamp for table \"test_tt_schema\".\"test_table_version_tab2\""); assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CAST ('2023-01-01' AS TIMESTAMP WITH TIME ZONE)", "No history found based on timestamp for table \"test_tt_schema\".\"test_table_version_tab2\""); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CAST ('2023-01-01' AS TIMESTAMP)", ".* Type timestamp is invalid. Supported table version AS OF expression type is Timestamp with Time Zone."); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CAST ('2023-01-01' AS DATE)", ".* Type date is invalid. Supported table version AS OF expression type is Timestamp with Time Zone."); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CURRENT_DATE", ".* Type date is invalid. Supported table version AS OF expression type is Timestamp with Time Zone."); - assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF TIMESTAMP '2023-01-01 00:00:00.000'", ".* Type timestamp is invalid. Supported table version AS OF expression type is Timestamp with Time Zone."); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CAST ('2023-01-01' AS TIMESTAMP)", ".* Type timestamp is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone."); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CAST ('2023-01-01' AS DATE)", ".* Type date is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone."); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF CURRENT_DATE", ".* Type date is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone."); + assertQueryFails("SELECT desc FROM " + tableName2 + " FOR TIMESTAMP AS OF TIMESTAMP '2023-01-01 00:00:00.000'", ".* Type timestamp is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone."); + + assertQueryFails("SELECT desc FROM " + tableName1 + " FOR VERSION BEFORE " + tab1VersionId1 + " ORDER BY 1", ".*Table version BEFORE expression is not supported for .*"); + assertQueryFails("SELECT desc FROM " + tableName1 + " FOR SYSTEM_VERSION BEFORE " + tab1VersionId1 + " ORDER BY 1", ".*Table version BEFORE expression is not supported for .*"); + assertQueryFails("SELECT desc FROM " + tableName1 + " FOR TIMESTAMP BEFORE TIMESTAMP " + "'" + tab1Timestamp1 + "'" + " ORDER BY 1", ".*Table version BEFORE expression is not supported for .*"); + assertQueryFails("SELECT desc FROM " + tableName1 + " FOR SYSTEM_TIME BEFORE TIMESTAMP " + "'" + tab1Timestamp1 + "'" + " ORDER BY 1", ".*Table version BEFORE expression is not supported for .*"); } } diff --git a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java index 16059fab4ef2..6b24e9bbda07 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java @@ -208,6 +208,7 @@ import static com.facebook.presto.spi.analyzer.AccessControlRole.TABLE_CREATE; import static com.facebook.presto.spi.analyzer.AccessControlRole.TABLE_DELETE; import static com.facebook.presto.spi.analyzer.AccessControlRole.TABLE_INSERT; +import static com.facebook.presto.spi.connector.ConnectorTableVersion.VersionOperator; import static com.facebook.presto.spi.connector.ConnectorTableVersion.VersionType; import static com.facebook.presto.spi.function.FunctionKind.AGGREGATE; import static com.facebook.presto.spi.function.FunctionKind.WINDOW; @@ -278,6 +279,9 @@ import static com.facebook.presto.sql.tree.FrameBound.Type.PRECEDING; import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_FOLLOWING; import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_PRECEDING; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator.EQUAL; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator.LESS_THAN; import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionType; import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionType.TIMESTAMP; import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionType.VERSION; @@ -1355,7 +1359,7 @@ protected Scope visitTable(Table table, Optional scope) private Optional getTableHandle(TableColumnMetadata tableColumnsMetadata, Table table, QualifiedObjectName name, Optional scope) { - // Process table version AS OF expression + // Process table version AS OF/BEFORE expression if (table.getTableVersionExpression().isPresent()) { return processTableVersion(table, name, scope); } @@ -1364,6 +1368,17 @@ private Optional getTableHandle(TableColumnMetadata tableColumnsMet } } + private VersionOperator toVersionOperator(TableVersionOperator operator) + { + switch (operator) { + case EQUAL: + return VersionOperator.EQUAL; + case LESS_THAN: + return VersionOperator.LESS_THAN; + } + throw new SemanticException(NOT_SUPPORTED, "Table version operator %s not supported." + operator); + } + private VersionType toVersionType(TableVersionType type) { switch (type) { @@ -1372,35 +1387,39 @@ private VersionType toVersionType(TableVersionType type) case VERSION: return VersionType.VERSION; } - throw new SemanticException(NOT_SUPPORTED, type.toString(), "Table version type not supported."); + throw new SemanticException(NOT_SUPPORTED, "Table version type %s not supported." + type); } private Optional processTableVersion(Table table, QualifiedObjectName name, Optional scope) { - Expression asOfExpr = table.getTableVersionExpression().get().getAsOfExpression(); + Expression stateExpr = table.getTableVersionExpression().get().getStateExpression(); TableVersionType tableVersionType = table.getTableVersionExpression().get().getTableVersionType(); - ExpressionAnalysis expressionAnalysis = analyzeExpression(asOfExpr, scope.get()); + TableVersionOperator tableVersionOperator = table.getTableVersionExpression().get().getTableVersionOperator(); + ExpressionAnalysis expressionAnalysis = analyzeExpression(stateExpr, scope.get()); analysis.recordSubqueries(table, expressionAnalysis); - Type asOfExprType = expressionAnalysis.getType(asOfExpr); - if (asOfExprType == UNKNOWN) { - throw new PrestoException(INVALID_ARGUMENTS, format("Table version AS OF expression cannot be NULL for %s", name.toString())); + Type stateExprType = expressionAnalysis.getType(stateExpr); + if (tableVersionOperator == LESS_THAN) { + throw new SemanticException(NOT_SUPPORTED, stateExpr, "Table version BEFORE expression is not supported for %s", name.toString()); + } + if (stateExprType == UNKNOWN) { + throw new PrestoException(INVALID_ARGUMENTS, format("Table version AS OF/BEFORE expression cannot be NULL for %s", name.toString())); } - Object evalAsOfExpr = evaluateConstantExpression(asOfExpr, asOfExprType, metadata, session, analysis.getParameters()); + Object evalStateExpr = evaluateConstantExpression(stateExpr, stateExprType, metadata, session, analysis.getParameters()); if (tableVersionType == TIMESTAMP) { - if (!(asOfExprType instanceof TimestampWithTimeZoneType)) { - throw new SemanticException(TYPE_MISMATCH, asOfExpr, - "Type %s is invalid. Supported table version AS OF expression type is Timestamp with Time Zone.", - asOfExprType.getDisplayName()); + if (!(stateExprType instanceof TimestampWithTimeZoneType)) { + throw new SemanticException(TYPE_MISMATCH, stateExpr, + "Type %s is invalid. Supported table version AS OF/BEFORE expression type is Timestamp with Time Zone.", + stateExprType.getDisplayName()); } } if (tableVersionType == VERSION) { - if (!(asOfExprType instanceof BigintType)) { - throw new SemanticException(TYPE_MISMATCH, asOfExpr, - "Type %s is invalid. Supported table version AS OF expression type is BIGINT", - asOfExprType.getDisplayName()); + if (!(stateExprType instanceof BigintType)) { + throw new SemanticException(TYPE_MISMATCH, stateExpr, + "Type %s is invalid. Supported table version AS OF/BEFORE expression type is BIGINT", + stateExprType.getDisplayName()); } } - ConnectorTableVersion tableVersion = new ConnectorTableVersion(toVersionType(tableVersionType), asOfExprType, evalAsOfExpr); + ConnectorTableVersion tableVersion = new ConnectorTableVersion(toVersionType(tableVersionType), toVersionOperator(tableVersionOperator), stateExprType, evalStateExpr); return metadata.getHandleVersion(session, name, Optional.of(tableVersion)); } diff --git a/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 b/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 index 9676f891b84b..dd13965df6e5 100644 --- a/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 +++ b/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 @@ -547,7 +547,12 @@ qualifiedName ; tableVersionExpression - : FOR tableVersionType=(SYSTEM_TIME | SYSTEM_VERSION | TIMESTAMP | VERSION) AS OF valueExpression #tableVersion + : FOR tableVersionType=(SYSTEM_TIME | SYSTEM_VERSION | TIMESTAMP | VERSION) tableVersionState valueExpression #tableVersion + ; + +tableVersionState + : AS OF #tableversionasof + | BEFORE #tableversionbefore ; grantor @@ -622,7 +627,7 @@ constraintEnforced nonReserved // IMPORTANT: this rule must only contain tokens. Nested rules are not supported. See SqlParser.exitNonReserved : ADD | ADMIN | ALL | ANALYZE | ANY | ARRAY | ASC | AT - | BERNOULLI + | BEFORE | BERNOULLI | CALL | CALLED | CASCADE | CATALOGS | COLUMN | COLUMNS | COMMENT | COMMIT | COMMITTED | CURRENT | CURRENT_ROLE | DATA | DATE | DAY | DEFINER | DESC | DETERMINISTIC | DISABLED | DISTRIBUTED | ENABLED | ENFORCED | EXCLUDING | EXPLAIN | EXTERNAL @@ -659,6 +664,7 @@ ARRAY: 'ARRAY'; AS: 'AS'; ASC: 'ASC'; AT: 'AT'; +BEFORE: 'BEFORE'; BERNOULLI: 'BERNOULLI'; BETWEEN: 'BETWEEN'; BY: 'BY'; diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/ExpressionFormatter.java b/presto-parser/src/main/java/com/facebook/presto/sql/ExpressionFormatter.java index 643b58e0263b..29549470dae9 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/ExpressionFormatter.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/ExpressionFormatter.java @@ -94,6 +94,7 @@ import java.util.function.Function; import static com.facebook.presto.sql.SqlFormatter.formatSql; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator.EQUAL; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.collect.Iterables.getOnlyElement; import static java.lang.String.format; @@ -693,7 +694,9 @@ private String joinExpressions(List expressions) protected String visitTableVersion(TableVersionExpression node, Void context) { - return "FOR " + node.getTableVersionType().name() + " AS OF " + process(node.getAsOfExpression(), context) + " "; + return "FOR " + node.getTableVersionType().name() + + (node.getTableVersionOperator() == EQUAL ? " AS OF " : " BEFORE ") + + process(node.getStateExpression(), context) + " "; } } diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java b/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java index 661940262c1c..e6306e4d3756 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java @@ -209,6 +209,9 @@ import static com.facebook.presto.sql.tree.RoutineCharacteristics.NullCallClause; import static com.facebook.presto.sql.tree.RoutineCharacteristics.NullCallClause.CALLED_ON_NULL_INPUT; import static com.facebook.presto.sql.tree.RoutineCharacteristics.NullCallClause.RETURNS_NULL_ON_NULL_INPUT; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator.EQUAL; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator.LESS_THAN; import static com.facebook.presto.sql.tree.TableVersionExpression.timestampExpression; import static com.facebook.presto.sql.tree.TableVersionExpression.versionExpression; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -1601,10 +1604,10 @@ public Node visitTableVersion(SqlBaseParser.TableVersionContext context) switch (context.tableVersionType.getType()) { case SqlBaseLexer.SYSTEM_TIME: case SqlBaseLexer.TIMESTAMP: - return timestampExpression(getLocation(context), child); + return timestampExpression(getLocation(context), getTableVersionOperator((Token) context.tableVersionState().getChild(0).getPayload()), child); case SqlBaseLexer.SYSTEM_VERSION: case SqlBaseLexer.VERSION: - return versionExpression(getLocation(context), child); + return versionExpression(getLocation(context), getTableVersionOperator((Token) context.tableVersionState().getChild(0).getPayload()), child); default: throw new UnsupportedOperationException("Unsupported Type: " + context.tableVersionType.getText()); } @@ -2347,6 +2350,18 @@ private static ConstraintType getConstraintType(Token token) throw new IllegalArgumentException("Unsupported constraint type: " + token.getText()); } + private static TableVersionOperator getTableVersionOperator(Token token) + { + switch (token.getType()) { + case SqlBaseLexer.AS: + return EQUAL; + case SqlBaseLexer.BEFORE: + return LESS_THAN; + } + + throw new IllegalArgumentException("Unsupported table version operator: " + token.getText()); + } + private static ArithmeticBinaryExpression.Operator getArithmeticBinaryOperator(Token operator) { switch (operator.getType()) { diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/TableVersionExpression.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/TableVersionExpression.java index 0e6ae74c4ede..a9fcc3066f1e 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/tree/TableVersionExpression.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/TableVersionExpression.java @@ -30,58 +30,70 @@ public enum TableVersionType VERSION } - private final Expression asOfExpression; + public enum TableVersionOperator + { + EQUAL, + LESS_THAN + } + private final Expression stateExpression; private final TableVersionType type; + private final TableVersionOperator operator; - public TableVersionExpression(TableVersionType type, Expression value) + public TableVersionExpression(TableVersionType type, TableVersionOperator operator, Expression value) { - this(Optional.empty(), type, value); + this(Optional.empty(), type, operator, value); } - public TableVersionExpression(NodeLocation location, TableVersionType type, Expression value) + public TableVersionExpression(NodeLocation location, TableVersionType type, TableVersionOperator operator, Expression value) { - this(Optional.of(location), type, value); + this(Optional.of(location), type, operator, value); } - private TableVersionExpression(Optional location, TableVersionType type, Expression value) + private TableVersionExpression(Optional location, TableVersionType type, TableVersionOperator operator, Expression value) { super(location); requireNonNull(value, "value is null"); + requireNonNull(operator, "operator is null"); requireNonNull(type, "type is null"); - this.asOfExpression = value; + this.stateExpression = value; + this.operator = operator; this.type = type; } - public static TableVersionExpression timestampExpression(NodeLocation location, Expression value) + public static TableVersionExpression timestampExpression(NodeLocation location, TableVersionOperator operator, Expression value) { - return new TableVersionExpression(Optional.of(location), TableVersionType.TIMESTAMP, value); + return new TableVersionExpression(Optional.of(location), TableVersionType.TIMESTAMP, operator, value); } - public static TableVersionExpression versionExpression(NodeLocation location, Expression value) + public static TableVersionExpression versionExpression(NodeLocation location, TableVersionOperator operator, Expression value) { - return new TableVersionExpression(Optional.of(location), TableVersionType.VERSION, value); + return new TableVersionExpression(Optional.of(location), TableVersionType.VERSION, operator, value); } - public static TableVersionExpression timestampExpression(Expression value) + public static TableVersionExpression timestampExpression(TableVersionOperator operator, Expression value) { - return new TableVersionExpression(Optional.empty(), TableVersionType.TIMESTAMP, value); + return new TableVersionExpression(Optional.empty(), TableVersionType.TIMESTAMP, operator, value); } - public static TableVersionExpression versionExpression(Expression value) + public static TableVersionExpression versionExpression(TableVersionOperator operator, Expression value) { - return new TableVersionExpression(Optional.empty(), TableVersionType.VERSION, value); + return new TableVersionExpression(Optional.empty(), TableVersionType.VERSION, operator, value); } - public Expression getAsOfExpression() + public Expression getStateExpression() { - return asOfExpression; + return stateExpression; } public TableVersionType getTableVersionType() { return type; } + public TableVersionOperator getTableVersionOperator() + { + return operator; + } @Override public R accept(AstVisitor visitor, C context) @@ -92,7 +104,7 @@ public R accept(AstVisitor visitor, C context) @Override public List getChildren() { - return ImmutableList.of(asOfExpression); + return ImmutableList.of(stateExpression); } @Override @@ -106,13 +118,13 @@ public boolean equals(Object o) } TableVersionExpression that = (TableVersionExpression) o; - return Objects.equals(asOfExpression, that.asOfExpression) && - (type == that.type); + return Objects.equals(stateExpression, that.stateExpression) && + (type == that.type) && (operator == that.operator); } @Override public int hashCode() { - return Objects.hash(asOfExpression, type); + return Objects.hash(stateExpression, type, operator); } } diff --git a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java index 5126175e1cfb..02da574d1eae 100644 --- a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java +++ b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java @@ -183,6 +183,7 @@ import static com.facebook.presto.sql.tree.ArithmeticBinaryExpression.Operator.DIVIDE; import static com.facebook.presto.sql.tree.ArithmeticUnaryExpression.negative; import static com.facebook.presto.sql.tree.ArithmeticUnaryExpression.positive; +import static com.facebook.presto.sql.tree.ComparisonExpression.Operator.EQUAL; import static com.facebook.presto.sql.tree.ComparisonExpression.Operator.GREATER_THAN; import static com.facebook.presto.sql.tree.ComparisonExpression.Operator.LESS_THAN; import static com.facebook.presto.sql.tree.ConstraintSpecification.ConstraintType.PRIMARY_KEY; @@ -195,6 +196,9 @@ import static com.facebook.presto.sql.tree.SortItem.NullOrdering.UNDEFINED; import static com.facebook.presto.sql.tree.SortItem.Ordering.ASCENDING; import static com.facebook.presto.sql.tree.SortItem.Ordering.DESCENDING; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionOperator; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionType.TIMESTAMP; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionType.VERSION; import static java.lang.String.format; import static java.util.Collections.emptyList; import static org.testng.Assert.assertEquals; @@ -3099,7 +3103,7 @@ public void testSelectWithAsOfVersion() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693"))), Optional.empty(), Optional.empty(), Optional.empty(), @@ -3111,7 +3115,7 @@ public void testSelectWithAsOfVersion() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693"))), Optional.empty(), Optional.empty(), Optional.empty(), @@ -3123,8 +3127,8 @@ public void testSelectWithAsOfVersion() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), - Optional.of(new ComparisonExpression(ComparisonExpression.Operator.EQUAL, new Identifier("c1"), new LongLiteral("100"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693"))), + Optional.of(new ComparisonExpression(EQUAL, new Identifier("c1"), new LongLiteral("100"))), Optional.empty(), Optional.empty(), Optional.empty(), @@ -3135,22 +3139,22 @@ public void testSelectWithAsOfVersion() simpleQuery(selectList(new AllColumns()), new Join(Join.Type.IMPLICIT, new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693"))), new Table(new NodeLocation(1, 60), QualifiedName.of("table2"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("123456789012345"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("123456789012345"))), Optional.empty()))); assertStatement("SELECT * FROM table1 FOR VERSION AS OF 8772871542276440693, table2 FOR TIMESTAMP AS OF TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles' ", simpleQuery(selectList(new AllColumns()), new Join(Join.Type.IMPLICIT, new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693"))), new Table(new NodeLocation(1, 60), QualifiedName.of("table2"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), Optional.empty()))); Query query = simpleQuery(selectList(new AllColumns()), new Table(new NodeLocation(1, 35), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693")))); + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693")))); assertStatement("CREATE VIEW view1 AS SELECT * FROM table1 FOR VERSION AS OF 8772871542276440693", new CreateView(QualifiedName.of("view1"), query, false, Optional.empty())); @@ -3163,7 +3167,7 @@ public void testSelectWithAsOfTimestamp() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), Optional.empty(), Optional.empty(), Optional.empty(), @@ -3175,7 +3179,7 @@ public void testSelectWithAsOfTimestamp() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), Optional.empty(), Optional.empty(), Optional.empty(), @@ -3187,7 +3191,7 @@ public void testSelectWithAsOfTimestamp() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), Optional.of(new ComparisonExpression(GREATER_THAN, new Identifier("c1"), new LongLiteral("100"))), Optional.empty(), Optional.empty(), @@ -3199,7 +3203,7 @@ public void testSelectWithAsOfTimestamp() simpleQuery( selectList(new AllColumns()), new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new CurrentTime(CurrentTime.Function.TIMESTAMP))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new CurrentTime(CurrentTime.Function.TIMESTAMP))), Optional.empty(), Optional.empty(), Optional.empty(), @@ -3211,24 +3215,164 @@ public void testSelectWithAsOfTimestamp() simpleQuery(selectList(new AllColumns()), new Join(Join.Type.IMPLICIT, new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), new Table(new NodeLocation(1, 98), QualifiedName.of("table2"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-11-01 05:45:25.123 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-11-01 05:45:25.123 America/Los_Angeles"))), Optional.empty()))); assertStatement("SELECT * FROM table1 FOR TIMESTAMP AS OF TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles', table2 FOR VERSION AS OF 8772871542276440693", simpleQuery(selectList(new AllColumns()), new Join(Join.Type.IMPLICIT, new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), new Table(new NodeLocation(1, 98), QualifiedName.of("table2"), - new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + new TableVersionExpression(VERSION, TableVersionOperator.EQUAL, new LongLiteral("8772871542276440693"))), Optional.empty()))); Query query = simpleQuery(selectList(new AllColumns()), new Table(new NodeLocation(1, 35), QualifiedName.of("table1"), - new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles")))); + new TableVersionExpression(TIMESTAMP, TableVersionOperator.EQUAL, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles")))); assertStatement("CREATE VIEW view1 AS SELECT * FROM table1 FOR TIMESTAMP AS OF TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles'", new CreateView(QualifiedName.of("view1"), query, false, Optional.empty())); } + + @Test + public void testSelectWithBeforeVersion() + { + assertStatement("SELECT * FROM table1 FOR VERSION BEFORE 8772871542276440693", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR SYSTEM_VERSION BEFORE 8772871542276440693", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR VERSION BEFORE 8772871542276440693 WHERE (c1 = 100)", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693"))), + Optional.of(new ComparisonExpression(EQUAL, new Identifier("c1"), new LongLiteral("100"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR VERSION BEFORE 8772871542276440693, table2 FOR VERSION BEFORE 123456789012345", + simpleQuery(selectList(new AllColumns()), + new Join(Join.Type.IMPLICIT, + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693"))), + new Table(new NodeLocation(1, 60), QualifiedName.of("table2"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("123456789012345"))), + Optional.empty()))); + + assertStatement("SELECT * FROM table1 FOR VERSION BEFORE 8772871542276440693, table2 FOR TIMESTAMP BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles' ", + simpleQuery(selectList(new AllColumns()), + new Join(Join.Type.IMPLICIT, + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693"))), + new Table(new NodeLocation(1, 60), QualifiedName.of("table2"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + Optional.empty()))); + + Query query = simpleQuery(selectList(new AllColumns()), new Table(new NodeLocation(1, 35), QualifiedName.of("table1"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693")))); + + assertStatement("CREATE VIEW view1 AS SELECT * FROM table1 FOR VERSION BEFORE 8772871542276440693", + new CreateView(QualifiedName.of("view1"), query, false, Optional.empty())); + } + + @Test + public void testSelectWithBeforeTimestamp() + { + assertStatement("SELECT * FROM table1 FOR TIMESTAMP BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles' ", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR SYSTEM_TIME BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles' ", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR TIMESTAMP BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles' WHERE (c1 > 100)", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + Optional.of(new ComparisonExpression(GREATER_THAN, new Identifier("c1"), new LongLiteral("100"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR TIMESTAMP BEFORE CURRENT_TIMESTAMP", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new CurrentTime(CurrentTime.Function.TIMESTAMP))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR TIMESTAMP BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles', table2 FOR TIMESTAMP BEFORE TIMESTAMP '2023-11-01 05:45:25.123 America/Los_Angeles'", + simpleQuery(selectList(new AllColumns()), + new Join(Join.Type.IMPLICIT, + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new Table(new NodeLocation(1, 98), QualifiedName.of("table2"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-11-01 05:45:25.123 America/Los_Angeles"))), + Optional.empty()))); + + assertStatement("SELECT * FROM table1 FOR TIMESTAMP BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles', table2 FOR VERSION BEFORE 8772871542276440693", + simpleQuery(selectList(new AllColumns()), + new Join(Join.Type.IMPLICIT, + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles"))), + new Table(new NodeLocation(1, 98), QualifiedName.of("table2"), + new TableVersionExpression(VERSION, TableVersionOperator.LESS_THAN, new LongLiteral("8772871542276440693"))), + Optional.empty()))); + + Query query = simpleQuery(selectList(new AllColumns()), new Table(new NodeLocation(1, 35), QualifiedName.of("table1"), + new TableVersionExpression(TIMESTAMP, TableVersionOperator.LESS_THAN, new TimestampLiteral("2023-08-17 13:29:46.822 America/Los_Angeles")))); + + assertStatement("CREATE VIEW view1 AS SELECT * FROM table1 FOR TIMESTAMP BEFORE TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles'", + new CreateView(QualifiedName.of("view1"), query, false, Optional.empty())); + } } diff --git a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java index 117caf56d2b2..edda43375c1a 100644 --- a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java +++ b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java @@ -130,6 +130,15 @@ public void testStatementBuilder() printStatement("select * from foo tablesample system (10) join bar tablesample bernoulli (30) on a.id = b.id"); printStatement("select * from foo tablesample system (10) join bar tablesample bernoulli (30) on not(a.id > b.id)"); + printStatement("select * from foo for version as of 8772871542276440693"); + printStatement("select * from foo for system_version as of 8772871542276440693"); + printStatement("select * from foo for timestamp as of timestamp '2023-08-17 13:29:46.822 America/Los_Angeles'"); + printStatement("select * from foo for system_time as of timestamp '2023-08-17 13:29:46.822 America/Los_Angeles'"); + printStatement("select * from foo for version before 8772871542276440693"); + printStatement("select * from foo for system_version before 8772871542276440693"); + printStatement("select * from foo for timestamp before timestamp '2023-08-17 13:29:46.822 America/Los_Angeles'"); + printStatement("select * from foo for system_time before timestamp '2023-08-17 13:29:46.822 America/Los_Angeles'"); + printStatement("create table foo as (select * from abc)"); printStatement("create table if not exists foo as (select * from abc)"); printStatement("create table foo with (a = 'apple', b = 'banana') as select * from abc"); diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java index 4c17556ece85..193eb641f2ab 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java @@ -87,11 +87,11 @@ default boolean schemaExists(ConnectorSession session, String schemaName) ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTableName tableName); /** - * Returns an error for connectors which do not support table version AS OF expression. + * Returns an error for connectors which do not support table version AS OF/BEFORE expression. */ default ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTableName tableName, Optional tableVersion) { - throw new PrestoException(NOT_SUPPORTED, "This connector does not support table version AS OF expression"); + throw new PrestoException(NOT_SUPPORTED, "This connector does not support table version AS OF/BEFORE expression"); } /** diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorTableVersion.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorTableVersion.java index 6327e902d423..86a07467b1de 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorTableVersion.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorTableVersion.java @@ -24,16 +24,24 @@ public enum VersionType TIMESTAMP, VERSION } + public enum VersionOperator + { + EQUAL, + LESS_THAN + } private final VersionType versionType; + private final VersionOperator versionOperator; private final Type versionExpressionType; private final Object tableVersion; - public ConnectorTableVersion(VersionType versionType, Type versionExpressionType, Object tableVersion) + public ConnectorTableVersion(VersionType versionType, VersionOperator versionOperator, Type versionExpressionType, Object tableVersion) { requireNonNull(versionType, "versionType is null"); + requireNonNull(versionOperator, "versionOperator is null"); requireNonNull(versionExpressionType, "versionExpressionType is null"); requireNonNull(tableVersion, "tableVersion is null"); this.versionType = versionType; + this.versionOperator = versionOperator; this.versionExpressionType = versionExpressionType; this.tableVersion = tableVersion; }