-
Notifications
You must be signed in to change notification settings - Fork 3.4k
Add WHEN STALE option to CREATE MATERIALIZED VIEW
#27356
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
ee03576
055aca4
41ee0ab
da71e4a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,7 @@ | |
| import io.trino.spi.connector.ColumnHandle; | ||
| import io.trino.spi.connector.ColumnMetadata; | ||
| import io.trino.spi.connector.ColumnSchema; | ||
| import io.trino.spi.connector.ConnectorMaterializedViewDefinition.WhenStaleBehavior; | ||
| import io.trino.spi.connector.ConnectorTableMetadata; | ||
| import io.trino.spi.connector.ConnectorTransactionHandle; | ||
| import io.trino.spi.connector.MaterializedViewFreshness; | ||
|
|
@@ -2285,6 +2286,7 @@ protected Scope visitTable(Table table, Optional<Scope> scope) | |
| if (optionalMaterializedView.isPresent()) { | ||
| MaterializedViewDefinition materializedViewDefinition = optionalMaterializedView.get(); | ||
| analysis.addEmptyColumnReferencesForTable(accessControl, session.getIdentity(), name); | ||
| boolean useLogicalViewSemantics = shouldUseLogicalViewSemantics(materializedViewDefinition); | ||
| if (isMaterializedViewSufficientlyFresh(session, name, materializedViewDefinition)) { | ||
| // If materialized view is sufficiently fresh with respect to its grace period, answer the query using the storage table | ||
| QualifiedName storageName = getMaterializedViewStorageTableName(materializedViewDefinition) | ||
|
|
@@ -2293,10 +2295,13 @@ protected Scope visitTable(Table table, Optional<Scope> scope) | |
| checkStorageTableNotRedirected(storageTableName); | ||
| TableHandle tableHandle = metadata.getTableHandle(session, storageTableName) | ||
| .orElseThrow(() -> semanticException(INVALID_VIEW, table, "Storage table '%s' does not exist", storageTableName)); | ||
| return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.of(tableHandle)); | ||
| return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.of(tableHandle), useLogicalViewSemantics); | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm a bit concerned about this line: the new clause affects the path for fresh MVs ( Is analysis of the underlying query even necessary for fresh MVs, regardless of the |
||
| } | ||
| else if (!useLogicalViewSemantics) { | ||
| throw semanticException(VIEW_IS_STALE, table, "Materialized view '%s' is stale", name); | ||
| } | ||
| // This is a stale materialized view and should be expanded like a logical view | ||
| return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.empty()); | ||
| return createScopeForMaterializedView(table, name, scope, materializedViewDefinition, Optional.empty(), useLogicalViewSemantics); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Instead of passing a |
||
| } | ||
|
|
||
| // This could be a reference to a logical view or a table | ||
|
|
@@ -2385,6 +2390,15 @@ private boolean isMaterializedViewSufficientlyFresh(Session session, QualifiedOb | |
| return staleness.compareTo(gracePeriod) <= 0; | ||
| } | ||
|
|
||
| private static boolean shouldUseLogicalViewSemantics(MaterializedViewDefinition materializedViewDefinition) | ||
| { | ||
| WhenStaleBehavior whenStale = materializedViewDefinition.getWhenStaleBehavior().orElse(WhenStaleBehavior.INLINE); | ||
| return switch (whenStale) { | ||
| case WhenStaleBehavior.INLINE -> true; | ||
| case WhenStaleBehavior.FAIL -> false; | ||
| }; | ||
| } | ||
|
|
||
| private void checkStorageTableNotRedirected(QualifiedObjectName source) | ||
| { | ||
| metadata.getRedirectionAwareTableHandle(session, source).redirectedTableName().ifPresent(name -> { | ||
|
|
@@ -2529,7 +2543,13 @@ private Scope createScopeForCommonTableExpression(Table table, Optional<Scope> s | |
| return createAndAssignScope(table, scope, fields); | ||
| } | ||
|
|
||
| private Scope createScopeForMaterializedView(Table table, QualifiedObjectName name, Optional<Scope> scope, MaterializedViewDefinition view, Optional<TableHandle> storageTable) | ||
| private Scope createScopeForMaterializedView( | ||
| Table table, | ||
| QualifiedObjectName name, | ||
| Optional<Scope> scope, | ||
| MaterializedViewDefinition view, | ||
| Optional<TableHandle> storageTable, | ||
| boolean useLogicalViewSemantics) | ||
| { | ||
| return createScopeForView( | ||
| table, | ||
|
|
@@ -2542,7 +2562,8 @@ private Scope createScopeForMaterializedView(Table table, QualifiedObjectName na | |
| view.getPath(), | ||
| view.getColumns(), | ||
| storageTable, | ||
| true); | ||
| true, | ||
| useLogicalViewSemantics); | ||
| } | ||
|
|
||
| private Scope createScopeForView(Table table, QualifiedObjectName name, Optional<Scope> scope, ViewDefinition view) | ||
|
|
@@ -2557,7 +2578,8 @@ private Scope createScopeForView(Table table, QualifiedObjectName name, Optional | |
| view.getPath(), | ||
| view.getColumns(), | ||
| Optional.empty(), | ||
| false); | ||
| false, | ||
| true); | ||
| } | ||
|
|
||
| private Scope createScopeForView( | ||
|
|
@@ -2571,7 +2593,8 @@ private Scope createScopeForView( | |
| List<CatalogSchemaName> path, | ||
| List<ViewColumn> columns, | ||
| Optional<TableHandle> storageTable, | ||
| boolean isMaterializedView) | ||
| boolean isMaterializedView, | ||
| boolean useLogicalViewSemantics) | ||
| { | ||
| Statement statement = analysis.getStatement(); | ||
| if (statement instanceof CreateView viewStatement) { | ||
|
|
@@ -2590,18 +2613,27 @@ private Scope createScopeForView( | |
| throw semanticException(VIEW_IS_RECURSIVE, table, "View is recursive"); | ||
| } | ||
|
|
||
| Query query = parseView(originalSql, name, table); | ||
| if (useLogicalViewSemantics) { | ||
| Query query = parseView(originalSql, name, table); | ||
|
|
||
| if (!query.getFunctions().isEmpty()) { | ||
| throw semanticException(NOT_SUPPORTED, table, "View contains inline function: %s", name); | ||
| } | ||
| if (!query.getFunctions().isEmpty()) { | ||
| throw semanticException(NOT_SUPPORTED, table, "View contains inline function: %s", name); | ||
| } | ||
|
|
||
| analysis.registerTableForView(table, name, isMaterializedView); | ||
| RelationType descriptor = analyzeView(query, name, catalog, schema, owner, path, table); | ||
| analysis.unregisterTableForView(); | ||
| analysis.registerTableForView(table, name, isMaterializedView); | ||
| RelationType descriptor = analyzeView(query, name, catalog, schema, owner, path, table); | ||
| analysis.unregisterTableForView(); | ||
|
|
||
| checkViewStaleness(columns, descriptor.getVisibleFields(), name, table) | ||
| .ifPresent(explanation -> { throw semanticException(VIEW_IS_STALE, table, "View '%s' is stale or in invalid state: %s", name, explanation); }); | ||
| checkViewStaleness(columns, descriptor.getVisibleFields(), name, table) | ||
| .ifPresent(explanation -> { throw semanticException(VIEW_IS_STALE, table, "View '%s' is stale or in invalid state: %s", name, explanation); }); | ||
|
|
||
| if (storageTable.isEmpty()) { | ||
| analysis.registerNamedQuery(table, query); | ||
| } | ||
| } | ||
| else { | ||
| checkArgument(storageTable.isPresent(), "A storage table must be present when query analysis is skipped"); | ||
| } | ||
|
|
||
| // Derive the type of the view from the stored definition, not from the analysis of the underlying query. | ||
| // This is needed in case the underlying table(s) changed and the query in the view now produces types that | ||
|
|
@@ -2621,9 +2653,6 @@ private Scope createScopeForView( | |
| List<Field> storageTableFields = analyzeStorageTable(table, viewFields, storageTable.get()); | ||
| analysis.setMaterializedViewStorageTableFields(table, storageTableFields); | ||
| } | ||
| else { | ||
| analysis.registerNamedQuery(table, query); | ||
| } | ||
|
|
||
| Scope accessControlScope = Scope.builder() | ||
| .withRelationType(RelationId.anonymous(), new RelationType(viewFields)) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should catalogs need to handle anything in order to support it ? If it is some sort of MV property (like security mode in case of views) - It should be supported for all connectors supporting MVs right ? Or we could fail during the creation or alter phase ?