diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java b/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java index be58e01eb775c..a36846c5cb67b 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java @@ -16,6 +16,7 @@ import com.facebook.presto.Session; import com.facebook.presto.common.CatalogSchemaName; import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.block.BlockEncodingSerde; import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.Type; @@ -98,6 +99,12 @@ public Optional getSystemTable(Session session, QualifiedObjectName return delegate.getSystemTable(session, tableName); } + @Override + public Optional getHandleVersion(Session session, QualifiedObjectName tableName, Optional tableVersionBlock) + { + return delegate.getHandleVersion(session, tableName, tableVersionBlock); + } + @Override public Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, Map analyzeProperties) { diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java b/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java index b1a20c90d6659..afb2386abdbc7 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java @@ -16,6 +16,7 @@ import com.facebook.presto.Session; import com.facebook.presto.common.CatalogSchemaName; import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.block.BlockEncodingSerde; import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.Type; @@ -73,6 +74,11 @@ public interface Metadata Optional getSystemTable(Session session, QualifiedObjectName tableName); + /** + * Returns a table handle for time travel expression + */ + Optional getHandleVersion(Session session, QualifiedObjectName tableName, Optional tableVersionBlock); + Optional getTableHandleForStatisticsCollection(Session session, QualifiedObjectName tableName, Map analyzeProperties); /** diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java b/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java index 197c51a4c1eaa..85286f8995f9a 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java @@ -20,6 +20,7 @@ import com.facebook.presto.Session; import com.facebook.presto.common.CatalogSchemaName; import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.block.BlockEncodingManager; import com.facebook.presto.common.block.BlockEncodingSerde; import com.facebook.presto.common.function.OperatorType; @@ -326,6 +327,12 @@ public Optional getTableHandleForStatisticsCollection(Session sessi return Optional.empty(); } + @Override + public Optional getHandleVersion(Session session, QualifiedObjectName tableName, Optional tableVersionBlock) + { + return getOptionalTableHandle(session, transactionManager, tableName, tableVersionBlock); + } + @Override public Optional getSystemTable(Session session, QualifiedObjectName tableName) { @@ -1010,7 +1017,7 @@ public void dropMaterializedView(Session session, QualifiedObjectName viewName) private MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName materializedViewName, TupleDomain baseQueryDomain) { - Optional materializedViewHandle = getOptionalTableHandle(session, transactionManager, materializedViewName); + Optional materializedViewHandle = getOptionalTableHandle(session, transactionManager, materializedViewName, Optional.empty()); ConnectorId connectorId = materializedViewHandle.get().getConnectorId(); ConnectorMetadata metadata = getMetadata(session, connectorId); @@ -1322,13 +1329,13 @@ public boolean schemaExists(CatalogSchemaName schema) @Override public boolean tableExists(QualifiedObjectName tableName) { - return getOptionalTableHandle(session, transactionManager, tableName).isPresent(); + return getOptionalTableHandle(session, transactionManager, tableName, Optional.empty()).isPresent(); } @Override public Optional getTableHandle(QualifiedObjectName tableName) { - return getOptionalTableHandle(session, transactionManager, tableName); + return getOptionalTableHandle(session, transactionManager, tableName, Optional.empty()); } @Override diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/MetadataUtil.java b/presto-main/src/main/java/com/facebook/presto/metadata/MetadataUtil.java index c98e443a331ac..c396dc5293dfb 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/MetadataUtil.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/MetadataUtil.java @@ -16,6 +16,7 @@ import com.facebook.presto.Session; import com.facebook.presto.common.CatalogSchemaName; import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.type.Type; import com.facebook.presto.spi.ColumnMetadata; import com.facebook.presto.spi.ConnectorId; @@ -167,7 +168,7 @@ public static Optional getOptionalCatalogMetadata(Session sessi return transactionManager.getOptionalCatalogMetadata(session.getRequiredTransactionId(), catalogName); } - public static Optional getOptionalTableHandle(Session session, TransactionManager transactionManager, QualifiedObjectName table) + public static Optional getOptionalTableHandle(Session session, TransactionManager transactionManager, QualifiedObjectName table, Optional tableVersionBlock) { requireNonNull(table, "table is null"); @@ -177,7 +178,10 @@ public static Optional getOptionalTableHandle(Session session, Tran ConnectorId connectorId = catalogMetadata.getConnectorId(session, table); ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); - ConnectorTableHandle tableHandle = metadata.getTableHandle(session.toConnectorSession(connectorId), toSchemaTableName(table)); + ConnectorTableHandle tableHandle; + tableHandle = tableVersionBlock + .map(expression -> metadata.getTableHandle(session.toConnectorSession(connectorId), toSchemaTableName(table), Optional.of(expression))) + .orElseGet(() -> metadata.getTableHandle(session.toConnectorSession(connectorId), toSchemaTableName(table))); if (tableHandle != null) { return Optional.of(new TableHandle( connectorId, 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 c0332ef4b3ef1..10bc3a80e6ba4 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 @@ -18,14 +18,18 @@ import com.facebook.presto.SystemSessionProperties; import com.facebook.presto.common.QualifiedObjectName; import com.facebook.presto.common.Subfield; +import com.facebook.presto.common.block.Block; +import com.facebook.presto.common.block.BlockBuilder; import com.facebook.presto.common.function.OperatorType; import com.facebook.presto.common.predicate.Domain; import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.ArrayType; +import com.facebook.presto.common.type.BigintType; import com.facebook.presto.common.type.DoubleType; import com.facebook.presto.common.type.MapType; import com.facebook.presto.common.type.RealType; import com.facebook.presto.common.type.RowType; +import com.facebook.presto.common.type.TimestampWithTimeZoneType; import com.facebook.presto.common.type.Type; import com.facebook.presto.metadata.Metadata; import com.facebook.presto.metadata.OperatorNotFoundException; @@ -147,6 +151,7 @@ import com.facebook.presto.sql.tree.Statement; import com.facebook.presto.sql.tree.Table; import com.facebook.presto.sql.tree.TableSubquery; +import com.facebook.presto.sql.tree.TableVersionExpression; import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.sql.tree.Union; import com.facebook.presto.sql.tree.Unnest; @@ -188,10 +193,12 @@ import static com.facebook.presto.common.type.BooleanType.BOOLEAN; import static com.facebook.presto.common.type.DoubleType.DOUBLE; import static com.facebook.presto.common.type.TypeSignature.parseTypeSignature; +import static com.facebook.presto.common.type.TypeUtils.writeNativeValue; import static com.facebook.presto.common.type.UnknownType.UNKNOWN; import static com.facebook.presto.common.type.VarcharType.VARCHAR; import static com.facebook.presto.metadata.MetadataUtil.createQualifiedObjectName; import static com.facebook.presto.metadata.MetadataUtil.toSchemaTableName; +import static com.facebook.presto.spi.StandardErrorCode.INVALID_ARGUMENTS; import static com.facebook.presto.spi.StandardErrorCode.INVALID_FUNCTION_ARGUMENT; import static com.facebook.presto.spi.StandardErrorCode.NOT_FOUND; import static com.facebook.presto.spi.StandardWarningCode.PERFORMANCE_WARNING; @@ -268,6 +275,8 @@ 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.TableVersionType.TIMESTAMP; +import static com.facebook.presto.sql.tree.TableVersionExpression.TableVersionType.VERSION; import static com.facebook.presto.util.AnalyzerUtil.createParsingOptions; import static com.facebook.presto.util.MetadataUtils.getMaterializedViewDefinition; import static com.facebook.presto.util.MetadataUtils.getTableColumnsMetadata; @@ -1280,7 +1289,7 @@ protected Scope visitTable(Table table, Optional scope) } TableColumnMetadata tableColumnsMetadata = getTableColumnsMetadata(session, metadataResolver, analysis.getMetadataHandle(), name); - Optional tableHandle = tableColumnsMetadata.getTableHandle(); + Optional tableHandle = getTableHandle(tableColumnsMetadata, table, name, scope); Map columnHandles = tableColumnsMetadata.getColumnHandles(); @@ -1321,6 +1330,51 @@ protected Scope visitTable(Table table, Optional scope) return createAndAssignScope(table, scope, fields.build()); } + private Optional getTableHandle(TableColumnMetadata tableColumnsMetadata, Table table, QualifiedObjectName name, Optional scope) + { + // Process table version AS OF expression + if (table.getTableVersionExpression().isPresent()) { + return processTableVersion(table, name, scope); + } + else { + return tableColumnsMetadata.getTableHandle(); + } + } + private Optional processTableVersion(Table table, QualifiedObjectName name, Optional scope) + { + Expression asOfExpr = table.getTableVersionExpression().get().getAsOfExpression(); + TableVersionExpression.TableVersionType tableVersionType = table.getTableVersionExpression().get().getTableVersionType(); + ExpressionAnalysis expressionAnalysis = analyzeExpression(asOfExpr, 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())); + } + Object evalAsOfExpr = evaluateConstantExpression(asOfExpr, asOfExprType, 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 (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()); + } + } + + // Two block entries for table version type and expression. + BlockBuilder blockBuilder = asOfExprType.createBlockBuilder(null, 2); + writeNativeValue(asOfExprType, blockBuilder, tableVersionType.ordinal()); + writeNativeValue(asOfExprType, blockBuilder, evalAsOfExpr); + Block block = blockBuilder.build(); + + return metadata.getHandleVersion(session, name, Optional.of(block)); + } + private Scope getScopeFromTable(Table table, Optional scope) { QualifiedObjectName tableName = createQualifiedObjectName(session, table, table.getName()); diff --git a/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java b/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java index 65e2883e4e291..aebf548ad63c4 100644 --- a/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java +++ b/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java @@ -16,6 +16,7 @@ import com.facebook.presto.Session; import com.facebook.presto.common.CatalogSchemaName; import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.block.BlockEncodingSerde; import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.Type; @@ -143,6 +144,12 @@ public Optional getTableHandleForStatisticsCollection(Session sessi throw new UnsupportedOperationException(); } + @Override + public Optional getHandleVersion(Session session, QualifiedObjectName tableName, Optional tableVersionBlock) + { + throw new UnsupportedOperationException(); + } + @Override public Optional getSystemTable(Session session, QualifiedObjectName tableName) { 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 56df4a994c1f6..6c899ecffdc2d 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 @@ -324,7 +324,7 @@ columnAliases ; relationPrimary - : qualifiedName #tableName + : qualifiedName tableVersionExpression? #tableName | '(' query ')' #subqueryRelation | UNNEST '(' expression (',' expression)* ')' (WITH ORDINALITY)? #unnest | LATERAL '(' query ')' #lateral @@ -531,6 +531,10 @@ qualifiedName : identifier ('.' identifier)* ; +tableVersionExpression + : FOR tableVersionType=(SYSTEM_TIME | SYSTEM_VERSION | TIMESTAMP | VERSION) AS OF valueExpression #tableVersion + ; + grantor : CURRENT_USER #currentUserGrantor | CURRENT_ROLE #currentRoleGrantor @@ -576,14 +580,14 @@ nonReserved | LANGUAGE | LAST | LATERAL | LEVEL | LIMIT | LOGICAL | MAP | MATERIALIZED | MINUTE | MONTH | NAME | NFC | NFD | NFKC | NFKD | NO | NONE | NULLIF | NULLS - | OFFSET | ONLY | OPTION | ORDINALITY | OUTPUT | OVER + | OF | OFFSET | ONLY | OPTION | ORDINALITY | OUTPUT | OVER | PARTITION | PARTITIONS | POSITION | PRECEDING | PRIVILEGES | PROPERTIES | RANGE | READ | REFRESH | RENAME | REPEATABLE | REPLACE | RESET | RESPECT | RESTRICT | RETURN | RETURNS | REVOKE | ROLE | ROLES | ROLLBACK | ROW | ROWS | SCHEMA | SCHEMAS | SECOND | SECURITY | SERIALIZABLE | SESSION | SET | SETS | SQL - | SHOW | SOME | START | STATS | SUBSTRING | SYSTEM + | SHOW | SOME | START | STATS | SUBSTRING | SYSTEM | SYSTEM_TIME | SYSTEM_VERSION | TABLES | TABLESAMPLE | TEMPORARY | TEXT | TIME | TIMESTAMP | TO | TRANSACTION | TRUNCATE | TRY_CAST | TYPE | UNBOUNDED | UNCOMMITTED | USE | USER - | VALIDATE | VERBOSE | VIEW + | VALIDATE | VERBOSE | VERSION | VIEW | WORK | WRITE | YEAR | ZONE @@ -709,6 +713,7 @@ NOT: 'NOT'; NULL: 'NULL'; NULLIF: 'NULLIF'; NULLS: 'NULLS'; +OF: 'OF'; OFFSET: 'OFFSET'; ON: 'ON'; ONLY: 'ONLY'; @@ -762,6 +767,8 @@ START: 'START'; STATS: 'STATS'; SUBSTRING: 'SUBSTRING'; SYSTEM: 'SYSTEM'; +SYSTEM_TIME: 'SYSTEM_TIME'; +SYSTEM_VERSION: 'SYSTEM_VERSION'; TABLE: 'TABLE'; TABLES: 'TABLES'; TABLESAMPLE: 'TABLESAMPLE'; @@ -787,6 +794,7 @@ USING: 'USING'; VALIDATE: 'VALIDATE'; VALUES: 'VALUES'; VERBOSE: 'VERBOSE'; +VERSION: 'VERSION'; VIEW: 'VIEW'; WHEN: 'WHEN'; WHERE: 'WHERE'; 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 54f46f2f40b06..643b58e0263b0 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 @@ -73,6 +73,7 @@ import com.facebook.presto.sql.tree.SubqueryExpression; import com.facebook.presto.sql.tree.SubscriptExpression; import com.facebook.presto.sql.tree.SymbolReference; +import com.facebook.presto.sql.tree.TableVersionExpression; import com.facebook.presto.sql.tree.TimeLiteral; import com.facebook.presto.sql.tree.TimestampLiteral; import com.facebook.presto.sql.tree.TryExpression; @@ -689,6 +690,11 @@ private String joinExpressions(List expressions) .map((e) -> process(e, null)) .iterator()); } + + protected String visitTableVersion(TableVersionExpression node, Void context) + { + return "FOR " + node.getTableVersionType().name() + " AS OF " + process(node.getAsOfExpression(), context) + " "; + } } static String formatStringLiteral(String s) diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java b/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java index 1e4292d3d8a14..bbcecb9cd4c9a 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java @@ -401,6 +401,10 @@ protected Void visitAllColumns(AllColumns node, Integer context) protected Void visitTable(Table node, Integer indent) { builder.append(formatName(node.getName())); + if (node.getTableVersionExpression().isPresent()) { + builder.append(' '); + process(node.getTableVersionExpression().get(), indent); + } return null; } 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 7ae848b085173..bf7ffe97deb8f 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 @@ -164,6 +164,7 @@ import com.facebook.presto.sql.tree.Table; import com.facebook.presto.sql.tree.TableElement; import com.facebook.presto.sql.tree.TableSubquery; +import com.facebook.presto.sql.tree.TableVersionExpression; import com.facebook.presto.sql.tree.TimeLiteral; import com.facebook.presto.sql.tree.TimestampLiteral; import com.facebook.presto.sql.tree.TransactionAccessMode; @@ -199,6 +200,8 @@ 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.timestampExpression; +import static com.facebook.presto.sql.tree.TableVersionExpression.versionExpression; import static com.google.common.collect.ImmutableList.toImmutableList; import static com.google.common.collect.Iterables.getOnlyElement; import static java.lang.String.format; @@ -1296,6 +1299,10 @@ public Node visitAliasedRelation(SqlBaseParser.AliasedRelationContext context) @Override public Node visitTableName(SqlBaseParser.TableNameContext context) { + if (context.tableVersionExpression() != null) { + return new Table(getLocation(context), getQualifiedName(context.qualifiedName()), (TableVersionExpression) visit(context.tableVersionExpression())); + } + return new Table(getLocation(context), getQualifiedName(context.qualifiedName())); } @@ -1454,6 +1461,23 @@ public Node visitQuantifiedComparison(SqlBaseParser.QuantifiedComparisonContext // ************** value expressions ************** + @Override + public Node visitTableVersion(SqlBaseParser.TableVersionContext context) + { + Expression child = (Expression) visit(context.valueExpression()); + + switch (context.tableVersionType.getType()) { + case SqlBaseLexer.SYSTEM_TIME: + case SqlBaseLexer.TIMESTAMP: + return timestampExpression(getLocation(context), child); + case SqlBaseLexer.SYSTEM_VERSION: + case SqlBaseLexer.VERSION: + return versionExpression(getLocation(context), child); + default: + throw new UnsupportedOperationException("Unsupported Type: " + context.tableVersionType.getText()); + } + } + @Override public Node visitArithmeticUnary(SqlBaseParser.ArithmeticUnaryContext context) { diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java index d57546c449220..dec1bfd08d3b2 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java @@ -352,6 +352,11 @@ protected R visitArithmeticUnary(ArithmeticUnaryExpression node, C context) return visitExpression(node, context); } + protected R visitTableVersion(TableVersionExpression node, C context) + { + return visitExpression(node, context); + } + protected R visitNotExpression(NotExpression node, C context) { return visitExpression(node, context); diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/Table.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/Table.java index 8a3f3d743c33e..9ed1a1c8ab70b 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/tree/Table.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/Table.java @@ -25,21 +25,28 @@ public class Table extends QueryBody { private final QualifiedName name; + private final Optional tableVersionExpression; public Table(QualifiedName name) { - this(Optional.empty(), name); + this(Optional.empty(), name, Optional.empty()); } public Table(NodeLocation location, QualifiedName name) { - this(Optional.of(location), name); + this(Optional.of(location), name, Optional.empty()); } - private Table(Optional location, QualifiedName name) + public Table(NodeLocation location, QualifiedName name, TableVersionExpression tableVersionExpression) + { + this(Optional.of(location), name, Optional.of(tableVersionExpression)); + } + + private Table(Optional location, QualifiedName name, Optional tableVersionExpression) { super(location); this.name = name; + this.tableVersionExpression = tableVersionExpression; } public QualifiedName getName() @@ -56,6 +63,9 @@ public R accept(AstVisitor visitor, C context) @Override public List getChildren() { + if (tableVersionExpression.isPresent()) { + return ImmutableList.of(tableVersionExpression.get()); + } return ImmutableList.of(); } @@ -64,6 +74,7 @@ public String toString() { return toStringHelper(this) .addValue(name) + .addValue(tableVersionExpression) .toString(); } @@ -78,7 +89,7 @@ public boolean equals(Object o) } Table table = (Table) o; - return Objects.equals(name, table.name); + return Objects.equals(name, table.name) && Objects.equals(tableVersionExpression, table.getTableVersionExpression()); } @Override @@ -86,4 +97,9 @@ public int hashCode() { return name.hashCode(); } + + public Optional getTableVersionExpression() + { + return tableVersionExpression; + } } 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 new file mode 100644 index 0000000000000..0e6ae74c4ede5 --- /dev/null +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/TableVersionExpression.java @@ -0,0 +1,118 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.sql.tree; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static java.util.Objects.requireNonNull; + +public class TableVersionExpression + extends Expression +{ + public enum TableVersionType + { + TIMESTAMP, + VERSION + } + + private final Expression asOfExpression; + private final TableVersionType type; + + public TableVersionExpression(TableVersionType type, Expression value) + { + this(Optional.empty(), type, value); + } + + public TableVersionExpression(NodeLocation location, TableVersionType type, Expression value) + { + this(Optional.of(location), type, value); + } + + private TableVersionExpression(Optional location, TableVersionType type, Expression value) + { + super(location); + requireNonNull(value, "value is null"); + requireNonNull(type, "type is null"); + + this.asOfExpression = value; + this.type = type; + } + + public static TableVersionExpression timestampExpression(NodeLocation location, Expression value) + { + return new TableVersionExpression(Optional.of(location), TableVersionType.TIMESTAMP, value); + } + + public static TableVersionExpression versionExpression(NodeLocation location, Expression value) + { + return new TableVersionExpression(Optional.of(location), TableVersionType.VERSION, value); + } + + public static TableVersionExpression timestampExpression(Expression value) + { + return new TableVersionExpression(Optional.empty(), TableVersionType.TIMESTAMP, value); + } + + public static TableVersionExpression versionExpression(Expression value) + { + return new TableVersionExpression(Optional.empty(), TableVersionType.VERSION, value); + } + + public Expression getAsOfExpression() + { + return asOfExpression; + } + + public TableVersionType getTableVersionType() + { + return type; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitTableVersion(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.of(asOfExpression); + } + + @Override + public boolean equals(Object o) + { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + + TableVersionExpression that = (TableVersionExpression) o; + return Objects.equals(asOfExpression, that.asOfExpression) && + (type == that.type); + } + + @Override + public int hashCode() + { + return Objects.hash(asOfExpression, type); + } +} 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 c04438ac4c31f..3a28c5fa3437d 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 @@ -88,6 +88,7 @@ import com.facebook.presto.sql.tree.LongLiteral; import com.facebook.presto.sql.tree.NaturalJoin; import com.facebook.presto.sql.tree.Node; +import com.facebook.presto.sql.tree.NodeLocation; import com.facebook.presto.sql.tree.NotExpression; import com.facebook.presto.sql.tree.NullIfExpression; import com.facebook.presto.sql.tree.NullLiteral; @@ -139,6 +140,7 @@ import com.facebook.presto.sql.tree.SubscriptExpression; import com.facebook.presto.sql.tree.Table; import com.facebook.presto.sql.tree.TableSubquery; +import com.facebook.presto.sql.tree.TableVersionExpression; import com.facebook.presto.sql.tree.TimeLiteral; import com.facebook.presto.sql.tree.TimestampLiteral; import com.facebook.presto.sql.tree.TransactionAccessMode; @@ -2743,4 +2745,144 @@ private static String indent(String value) String indent = " "; return indent + value.trim().replaceAll("\n", "\n" + indent); } + + @Test + public void testSelectWithAsOfVersion() + { + assertStatement("SELECT * FROM table1 FOR VERSION AS OF 8772871542276440693", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR SYSTEM_VERSION AS OF 8772871542276440693", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, new LongLiteral("8772871542276440693"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR VERSION AS OF 8772871542276440693 WHERE (c1 = 100)", + 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"))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR VERSION AS OF 8772871542276440693, table2 FOR VERSION AS OF 123456789012345", + 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 Table(new NodeLocation(1, 60), QualifiedName.of("table2"), + new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, 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 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"))), + 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")))); + + assertStatement("CREATE VIEW view1 AS SELECT * FROM table1 FOR VERSION AS OF 8772871542276440693", + new CreateView(QualifiedName.of("view1"), query, false, Optional.empty())); + } + + @Test + public void testSelectWithAsOfTimestamp() + { + assertStatement("SELECT * FROM table1 FOR TIMESTAMP AS OF 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(TableVersionExpression.TableVersionType.TIMESTAMP, 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 AS OF 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(TableVersionExpression.TableVersionType.TIMESTAMP, 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 AS OF 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(TableVersionExpression.TableVersionType.TIMESTAMP, 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 AS OF CURRENT_TIMESTAMP", + simpleQuery( + selectList(new AllColumns()), + new Table(new NodeLocation(1, 15), QualifiedName.of("table1"), + new TableVersionExpression(TableVersionExpression.TableVersionType.TIMESTAMP, new CurrentTime(CurrentTime.Function.TIMESTAMP))), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty(), + Optional.empty())); + + assertStatement("SELECT * FROM table1 FOR TIMESTAMP AS OF TIMESTAMP '2023-08-17 13:29:46.822 America/Los_Angeles', table2 FOR TIMESTAMP AS OF 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(TableVersionExpression.TableVersionType.TIMESTAMP, 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"))), + 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 Table(new NodeLocation(1, 98), QualifiedName.of("table2"), + new TableVersionExpression(TableVersionExpression.TableVersionType.VERSION, 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")))); + + 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())); + } } diff --git a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java index 43931a16b72ae..7245156fbe149 100644 --- a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java +++ b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java @@ -49,8 +49,7 @@ public Object[][] getStatements() {"select * from 'oops", "line 1:15: mismatched input '''. Expecting: '(', 'LATERAL', 'UNNEST', "}, {"select *\nfrom x\nfrom", - "line 3:1: mismatched input 'from'. Expecting: ',', '.', 'AS', 'CROSS', 'EXCEPT', 'FETCH', 'FULL', 'GROUP', 'HAVING', 'INNER', 'INTERSECT', 'JOIN', 'LEFT', 'LIMIT', 'NATURAL', 'OFFSET', " + - "'ORDER', 'RIGHT', 'TABLESAMPLE', 'UNION', 'WHERE', , "}, + "line 3:1: mismatched input 'from'. Expecting: ',', '.', 'AS', 'CROSS', 'EXCEPT', 'FETCH', 'FOR', 'FULL', 'GROUP', 'HAVING', 'INNER', 'INTERSECT', 'JOIN', 'LEFT', 'LIMIT', 'NATURAL', 'OFFSET', 'ORDER', 'RIGHT', 'TABLESAMPLE', 'UNION', 'WHERE', , "}, {"select *\nfrom x\nwhere from", "line 3:7: mismatched input 'from'. Expecting: "}, {"select * from", @@ -88,7 +87,7 @@ public Object[][] getStatements() {"SELECT grouping(a+2) FROM (VALUES (1)) AS t (a) GROUP BY a+2", "line 1:18: mismatched input '+'. Expecting: ')', ','"}, {"SELECT x() over (ROWS select) FROM t", - "line 1:23: mismatched input 'select'. Expecting: 'BETWEEN', 'CURRENT', 'UNBOUNDED', "}, + "line 1:17: mismatched input '('. Expecting: ',', 'EXCEPT', 'FETCH', 'FROM', 'GROUP', 'HAVING', 'INTERSECT', 'LIMIT', 'OFFSET', 'ORDER', 'UNION', 'WHERE', "}, {"SELECT X() OVER (ROWS UNBOUNDED) FROM T", "line 1:32: mismatched input ')'. Expecting: 'FOLLOWING', 'PRECEDING'"}, {"SELECT a FROM x ORDER BY (SELECT b FROM t WHERE ", @@ -110,7 +109,7 @@ public Object[][] getStatements() {"SELECT CAST(a AS decimal()", "line 1:26: mismatched input ')'. Expecting: , "}, {"SELECT foo(*) filter (", - "line 1:23: mismatched input ''. Expecting: 'WHERE'"}, + "line 1:22: mismatched input '('. Expecting: ',', 'EXCEPT', 'FETCH', 'FROM', 'GROUP', 'HAVING', 'INTERSECT', 'LIMIT', 'OFFSET', 'ORDER', 'UNION', 'WHERE', "}, {"SELECT * FROM t t x", "line 1:19: mismatched input 'x'. Expecting: '(', ',', 'CROSS', 'EXCEPT', 'FETCH', 'FULL', 'GROUP', 'HAVING', 'INNER', 'INTERSECT', 'JOIN', 'LEFT', 'LIMIT', 'NATURAL', 'OFFSET', 'ORDER', " + "'RIGHT', 'TABLESAMPLE', 'UNION', 'WHERE', "}, 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 a033ae88f13ec..3bf4f7b348fcd 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 @@ -13,6 +13,7 @@ */ package com.facebook.presto.spi.connector; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.Type; import com.facebook.presto.spi.ColumnHandle; @@ -85,6 +86,14 @@ 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. + */ + default ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTableName tableName, Optional tableVersionBlock) + { + throw new PrestoException(NOT_SUPPORTED, "This connector does not support table version AS OF expression"); + } + /** * Returns a table handle for the specified table name, or null if the connector does not contain the table. * The returned table handle can contain information in analyzeProperties. diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java index e4baa89e5b0f9..dad70b3aa19be 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java @@ -13,6 +13,7 @@ */ package com.facebook.presto.spi.connector.classloader; +import com.facebook.presto.common.block.Block; import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.Type; import com.facebook.presto.spi.ColumnHandle; @@ -221,6 +222,14 @@ public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTable } } + @Override + public ConnectorTableHandle getTableHandle(ConnectorSession session, SchemaTableName tableName, Optional tableVersionBlock) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.getTableHandle(session, tableName, tableVersionBlock); + } + } + @Override public ConnectorTableHandle getTableHandleForStatisticsCollection(ConnectorSession session, SchemaTableName tableName, Map analyzeProperties) {