From b5b178ccdd41d660464621e7034343a2c9aeee5c Mon Sep 17 00:00:00 2001 From: Lukasz Stec Date: Thu, 7 Oct 2021 23:59:43 +0200 Subject: [PATCH 1/3] Use containsAll for metadata.materialized_views asserts --- .../io/trino/testing/BaseConnectorTest.java | 34 +++++++++++-------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 51c66cbcb9a8..845736af02b9 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -637,30 +637,34 @@ public void testMaterializedView() assertUpdate("DROP MATERIALIZED VIEW " + viewWithComment); // test filtering materialized views in system metadata table - assertQuery( - listMaterializedViewsSql("catalog_name = '" + view.getCatalogName() + "'"), - getTestingMaterializedViewsResultRows(view, otherView)); + assertThat(query(listMaterializedViewsSql("catalog_name = '" + view.getCatalogName() + "'"))) + .skippingTypesCheck() + .containsAll(getTestingMaterializedViewsResultRows(view, otherView)); - assertQuery( + assertThat(query( listMaterializedViewsSql( "catalog_name = '" + otherView.getCatalogName() + "'", - "schema_name = '" + otherView.getSchemaName() + "'"), - getTestingMaterializedViewsResultRow(otherView, "sarcastic comment")); + "schema_name = '" + otherView.getSchemaName() + "'"))) + .skippingTypesCheck() + .containsAll(getTestingMaterializedViewsResultRow(otherView, "sarcastic comment")); - assertQuery( + assertThat(query( listMaterializedViewsSql( "catalog_name = '" + view.getCatalogName() + "'", "schema_name = '" + view.getSchemaName() + "'", - "name = '" + view.getObjectName() + "'"), - getTestingMaterializedViewsResultRow(view, "")); + "name = '" + view.getObjectName() + "'"))) + .skippingTypesCheck() + .containsAll(getTestingMaterializedViewsResultRow(view, "")); - assertQuery( - listMaterializedViewsSql("schema_name LIKE '%" + view.getSchemaName() + "%'"), - getTestingMaterializedViewsResultRow(view, "")); + assertThat(query( + listMaterializedViewsSql("schema_name LIKE '%" + view.getSchemaName() + "%'"))) + .skippingTypesCheck() + .containsAll(getTestingMaterializedViewsResultRow(view, "")); - assertQuery( - listMaterializedViewsSql("name LIKE '%" + view.getObjectName() + "%'"), - getTestingMaterializedViewsResultRow(view, "")); + assertThat(query( + listMaterializedViewsSql("name LIKE '%" + view.getObjectName() + "%'"))) + .skippingTypesCheck() + .containsAll(getTestingMaterializedViewsResultRow(view, "")); // verify write in transaction if (!hasBehavior(SUPPORTS_MULTI_STATEMENT_WRITES)) { From edc772289bc48a9a507d59ff9eb3d6e8ce90da75 Mon Sep 17 00:00:00 2001 From: Lukasz Stec Date: Mon, 4 Oct 2021 16:39:00 +0200 Subject: [PATCH 2/3] Add support for renaming materialized views --- .../execution/RenameMaterializedViewTask.java | 108 +++++++++++++ .../io/trino/execution/RenameTableTask.java | 2 +- .../io/trino/execution/RenameViewTask.java | 2 +- .../main/java/io/trino/metadata/Metadata.java | 5 + .../io/trino/metadata/MetadataManager.java | 13 ++ .../java/io/trino/security/AccessControl.java | 7 + .../trino/security/AccessControlManager.java | 14 ++ .../trino/security/AllowAllAccessControl.java | 5 + .../trino/security/DenyAllAccessControl.java | 7 + .../security/ForwardingAccessControl.java | 6 + .../InjectedConnectorAccessControl.java | 7 + .../server/QueryExecutionFactoryModule.java | 3 + .../trino/sql/analyzer/StatementAnalyzer.java | 7 + .../io/trino/testing/LocalQueryRunner.java | 3 + .../testing/TestingAccessControlManager.java | 15 +- .../java/io/trino/util/StatementUtils.java | 2 + .../execution/BaseDataDefinitionTaskTest.java | 8 + .../TestRenameMaterializedViewTask.java | 145 ++++++++++++++++++ .../trino/execution/TestRenameTableTask.java | 2 +- .../trino/execution/TestRenameViewTask.java | 2 +- .../trino/metadata/AbstractMockMetadata.java | 6 + .../antlr4/io/trino/sql/parser/SqlBase.g4 | 2 + .../main/java/io/trino/sql/SqlFormatter.java | 15 ++ .../java/io/trino/sql/parser/AstBuilder.java | 7 + .../java/io/trino/sql/tree/AstVisitor.java | 5 + .../sql/tree/RenameMaterializedView.java | 107 +++++++++++++ .../io/trino/sql/parser/TestSqlParser.java | 8 + .../spi/connector/ConnectorAccessControl.java | 11 ++ .../spi/connector/ConnectorMetadata.java | 8 + .../spi/security/AccessDeniedException.java | 10 ++ .../spi/security/SystemAccessControl.java | 11 ++ ...ClassLoaderSafeConnectorAccessControl.java | 8 + .../ClassLoaderSafeConnectorMetadata.java | 8 + .../base/security/AllowAllAccessControl.java | 5 + .../security/AllowAllSystemAccessControl.java | 5 + .../base/security/FileBasedAccessControl.java | 10 ++ .../FileBasedSystemAccessControl.java | 10 ++ .../ForwardingConnectorAccessControl.java | 6 + .../ForwardingSystemAccessControl.java | 6 + .../base/security/ReadOnlyAccessControl.java | 7 + .../security/TestFileBasedAccessControl.java | 4 + .../hive/HiveMaterializedViewMetadata.java | 2 + .../io/trino/plugin/hive/HiveMetadata.java | 6 + .../NoneHiveMaterializedViewMetadata.java | 6 + .../hive/security/LegacyAccessControl.java | 5 + .../security/SqlStandardAccessControl.java | 9 ++ .../iceberg/TestIcebergMaterializedViews.java | 12 ++ .../java/io/trino/verifier/VerifyCommand.java | 4 + .../io/trino/testing/BaseConnectorTest.java | 64 +++++++- .../testing/TestingConnectorBehavior.java | 1 + 50 files changed, 725 insertions(+), 6 deletions(-) create mode 100644 core/trino-main/src/main/java/io/trino/execution/RenameMaterializedViewTask.java create mode 100644 core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java create mode 100644 core/trino-parser/src/main/java/io/trino/sql/tree/RenameMaterializedView.java diff --git a/core/trino-main/src/main/java/io/trino/execution/RenameMaterializedViewTask.java b/core/trino-main/src/main/java/io/trino/execution/RenameMaterializedViewTask.java new file mode 100644 index 000000000000..b6192e10cd52 --- /dev/null +++ b/core/trino-main/src/main/java/io/trino/execution/RenameMaterializedViewTask.java @@ -0,0 +1,108 @@ +/* + * 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 io.trino.execution; + +import com.google.common.util.concurrent.ListenableFuture; +import io.trino.Session; +import io.trino.execution.warnings.WarningCollector; +import io.trino.metadata.Metadata; +import io.trino.metadata.QualifiedObjectName; +import io.trino.metadata.TableHandle; +import io.trino.security.AccessControl; +import io.trino.spi.connector.ConnectorMaterializedViewDefinition; +import io.trino.spi.connector.ConnectorViewDefinition; +import io.trino.sql.tree.Expression; +import io.trino.sql.tree.RenameMaterializedView; +import io.trino.transaction.TransactionManager; + +import java.util.List; +import java.util.Optional; + +import static com.google.common.util.concurrent.Futures.immediateVoidFuture; +import static io.trino.metadata.MetadataUtil.createQualifiedObjectName; +import static io.trino.spi.StandardErrorCode.CATALOG_NOT_FOUND; +import static io.trino.spi.StandardErrorCode.NOT_SUPPORTED; +import static io.trino.spi.StandardErrorCode.TABLE_ALREADY_EXISTS; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static io.trino.sql.analyzer.SemanticExceptions.semanticException; + +public class RenameMaterializedViewTask + implements DataDefinitionTask +{ + @Override + public String getName() + { + return "RENAME MATERIALIZED VIEW"; + } + + @Override + public ListenableFuture execute( + RenameMaterializedView statement, + TransactionManager transactionManager, + Metadata metadata, + AccessControl accessControl, + QueryStateMachine stateMachine, + List parameters, + WarningCollector warningCollector) + { + Session session = stateMachine.getSession(); + QualifiedObjectName materializedViewName = createQualifiedObjectName(session, statement, statement.getSource()); + Optional materializedView = metadata.getMaterializedView(session, materializedViewName); + if (materializedView.isEmpty()) { + Optional view = metadata.getView(session, materializedViewName); + if (view.isPresent()) { + throw semanticException( + TABLE_NOT_FOUND, + statement, + "Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW %s RENAME ...?", materializedViewName, materializedViewName); + } + + Optional table = metadata.getTableHandle(session, materializedViewName); + if (table.isPresent()) { + throw semanticException( + TABLE_NOT_FOUND, + statement, + "Materialized View '%s' does not exist, but a table with that name exists. Did you mean ALTER TABLE %s RENAME ...?", materializedViewName, materializedViewName); + } + + if (statement.isExists()) { + return immediateVoidFuture(); + } + throw semanticException(TABLE_NOT_FOUND, statement, "Materialized View '%s' does not exist", materializedViewName); + } + + QualifiedObjectName target = createQualifiedObjectName(session, statement, statement.getTarget()); + if (metadata.getCatalogHandle(session, target.getCatalogName()).isEmpty()) { + throw semanticException(CATALOG_NOT_FOUND, statement, "Target catalog '%s' does not exist", target.getCatalogName()); + } + if (metadata.getMaterializedView(session, target).isPresent()) { + throw semanticException(TABLE_ALREADY_EXISTS, statement, "Target materialized view '%s' already exists", target); + } + if (metadata.getView(session, target).isPresent()) { + throw semanticException(TABLE_ALREADY_EXISTS, statement, "Target materialized view '%s' does not exist, but a view with that name exists.", target); + } + if (metadata.getTableHandle(session, target).isPresent()) { + throw semanticException(TABLE_ALREADY_EXISTS, statement, "Target materialized view '%s' does not exist, but a table with that name exists.", target); + } + if (!materializedViewName.getCatalogName().equals(target.getCatalogName())) { + throw semanticException(NOT_SUPPORTED, statement, "Materialized View rename across catalogs is not supported"); + } + + accessControl.checkCanRenameMaterializedView(session.toSecurityContext(), materializedViewName, target); + + metadata.renameMaterializedView(session, materializedViewName, target); + + return immediateVoidFuture(); + } +} diff --git a/core/trino-main/src/main/java/io/trino/execution/RenameTableTask.java b/core/trino-main/src/main/java/io/trino/execution/RenameTableTask.java index a4b63d8e4526..07eb3a001f9f 100644 --- a/core/trino-main/src/main/java/io/trino/execution/RenameTableTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/RenameTableTask.java @@ -65,7 +65,7 @@ public ListenableFuture execute( throw semanticException( TABLE_NOT_FOUND, statement, - "Table '%s' does not exist, but a materialized view with that name exists.", tableName); + "Table '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW %s RENAME ...?", tableName, tableName); } return immediateVoidFuture(); } diff --git a/core/trino-main/src/main/java/io/trino/execution/RenameViewTask.java b/core/trino-main/src/main/java/io/trino/execution/RenameViewTask.java index 4fac1f344f00..faeeaf76726c 100644 --- a/core/trino-main/src/main/java/io/trino/execution/RenameViewTask.java +++ b/core/trino-main/src/main/java/io/trino/execution/RenameViewTask.java @@ -63,7 +63,7 @@ public ListenableFuture execute( throw semanticException( TABLE_NOT_FOUND, statement, - "View '%s' does not exist, but a materialized view with that name exists.", viewName); + "View '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW %s RENAME ...?", viewName, viewName); } Optional viewDefinition = metadata.getView(session, viewName); diff --git a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java index 0fe041538082..01efbbc8757a 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/Metadata.java +++ b/core/trino-main/src/main/java/io/trino/metadata/Metadata.java @@ -691,6 +691,11 @@ default ResolvedFunction getCoercion(Type fromType, Type toType) */ MaterializedViewFreshness getMaterializedViewFreshness(Session session, QualifiedObjectName name); + /** + * Rename the specified materialized view. + */ + void renameMaterializedView(Session session, QualifiedObjectName existingViewName, QualifiedObjectName newViewName); + /** * Returns the result of redirecting the table scan on a given table to a different table. * This method is used by the engine during the plan optimization phase to allow a connector to offload table scans to any other connector. diff --git a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java index a75d3e15225d..de29d974ea35 100644 --- a/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java +++ b/core/trino-main/src/main/java/io/trino/metadata/MetadataManager.java @@ -1403,6 +1403,19 @@ public MaterializedViewFreshness getMaterializedViewFreshness(Session session, Q return new MaterializedViewFreshness(false); } + @Override + public void renameMaterializedView(Session session, QualifiedObjectName source, QualifiedObjectName target) + { + CatalogMetadata catalogMetadata = getCatalogMetadataForWrite(session, target.getCatalogName()); + CatalogName catalogName = catalogMetadata.getCatalogName(); + ConnectorMetadata metadata = catalogMetadata.getMetadata(); + if (!source.getCatalogName().equals(catalogName.getCatalogName())) { + throw new TrinoException(SYNTAX_ERROR, "Cannot rename materialized views across catalogs"); + } + + metadata.renameMaterializedView(session.toConnectorSession(catalogName), source.asSchemaTableName(), target.asSchemaTableName()); + } + @Override public Optional applyTableScanRedirect(Session session, TableHandle tableHandle) { diff --git a/core/trino-main/src/main/java/io/trino/security/AccessControl.java b/core/trino-main/src/main/java/io/trino/security/AccessControl.java index dddae606acb0..673aa5c6b575 100644 --- a/core/trino-main/src/main/java/io/trino/security/AccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/AccessControl.java @@ -334,6 +334,13 @@ default void checkCanSetViewAuthorization(SecurityContext context, QualifiedObje */ void checkCanDropMaterializedView(SecurityContext context, QualifiedObjectName materializedViewName); + /** + * Check if identity is allowed to rename the specified materialized view. + * + * @throws AccessDeniedException if not allowed + */ + void checkCanRenameMaterializedView(SecurityContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName); + /** * Check if identity is allowed to create a view that executes the function. * diff --git a/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java b/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java index a3e556aec885..65552f986e46 100644 --- a/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java +++ b/core/trino-main/src/main/java/io/trino/security/AccessControlManager.java @@ -752,6 +752,20 @@ public void checkCanDropMaterializedView(SecurityContext securityContext, Qualif catalogAuthorizationCheck(materializedViewName.getCatalogName(), securityContext, (control, context) -> control.checkCanDropMaterializedView(context, materializedViewName.asSchemaTableName())); } + @Override + public void checkCanRenameMaterializedView(SecurityContext securityContext, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + requireNonNull(securityContext, "securityContext is null"); + requireNonNull(viewName, "viewName is null"); + requireNonNull(newViewName, "newViewName is null"); + + checkCanAccessCatalog(securityContext, viewName.getCatalogName()); + + systemAuthorizationCheck(control -> control.checkCanRenameMaterializedView(securityContext.toSystemSecurityContext(), viewName.asCatalogSchemaTableName(), newViewName.asCatalogSchemaTableName())); + + catalogAuthorizationCheck(viewName.getCatalogName(), securityContext, (control, context) -> control.checkCanRenameMaterializedView(context, viewName.asSchemaTableName(), newViewName.asSchemaTableName())); + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SecurityContext securityContext, String functionName, Identity grantee, boolean grantOption) { diff --git a/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java b/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java index 432208d785aa..745c861e4e9b 100644 --- a/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/AllowAllAccessControl.java @@ -238,6 +238,11 @@ public void checkCanDropMaterializedView(SecurityContext context, QualifiedObjec { } + @Override + public void checkCanRenameMaterializedView(SecurityContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SecurityContext context, String functionName, Identity grantee, boolean grantOption) { diff --git a/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java b/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java index 478951ea9e0e..0803060c89fd 100644 --- a/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/DenyAllAccessControl.java @@ -55,6 +55,7 @@ import static io.trino.spi.security.AccessDeniedException.denyReadSystemInformationAccess; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; @@ -331,6 +332,12 @@ public void checkCanDropMaterializedView(SecurityContext context, QualifiedObjec denyDropMaterializedView(materializedViewName.toString()); } + @Override + public void checkCanRenameMaterializedView(SecurityContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + denyRenameMaterializedView(viewName.toString(), newViewName.toString()); + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SecurityContext context, String functionName, Identity grantee, boolean grantOption) { diff --git a/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java b/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java index 6758b6c868bf..f1421d9be99f 100644 --- a/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/ForwardingAccessControl.java @@ -296,6 +296,12 @@ public void checkCanDropMaterializedView(SecurityContext context, QualifiedObjec delegate().checkCanDropMaterializedView(context, materializedViewName); } + @Override + public void checkCanRenameMaterializedView(SecurityContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + delegate().checkCanRenameMaterializedView(context, viewName, newViewName); + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SecurityContext context, String functionName, Identity grantee, boolean grantOption) { diff --git a/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java b/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java index bcf0b345f0d3..bc6cf1e9db54 100644 --- a/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java +++ b/core/trino-main/src/main/java/io/trino/security/InjectedConnectorAccessControl.java @@ -278,6 +278,13 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem accessControl.checkCanDropMaterializedView(securityContext, getQualifiedObjectName(materializedViewName)); } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + checkArgument(context == null, "context must be null"); + accessControl.checkCanRenameMaterializedView(securityContext, getQualifiedObjectName(viewName), getQualifiedObjectName(newViewName)); + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java b/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java index acd6ad726d81..b0241a9b9bca 100644 --- a/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java +++ b/core/trino-main/src/main/java/io/trino/server/QueryExecutionFactoryModule.java @@ -41,6 +41,7 @@ import io.trino.execution.PrepareTask; import io.trino.execution.QueryExecution.QueryExecutionFactory; import io.trino.execution.RenameColumnTask; +import io.trino.execution.RenameMaterializedViewTask; import io.trino.execution.RenameSchemaTask; import io.trino.execution.RenameTableTask; import io.trino.execution.RenameViewTask; @@ -78,6 +79,7 @@ import io.trino.sql.tree.GrantRoles; import io.trino.sql.tree.Prepare; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameSchema; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; @@ -135,6 +137,7 @@ public void configure(Binder binder) bindDataDefinitionTask(binder, executionBinder, GrantRoles.class, GrantRolesTask.class); bindDataDefinitionTask(binder, executionBinder, Prepare.class, PrepareTask.class); bindDataDefinitionTask(binder, executionBinder, RenameColumn.class, RenameColumnTask.class); + bindDataDefinitionTask(binder, executionBinder, RenameMaterializedView.class, RenameMaterializedViewTask.class); bindDataDefinitionTask(binder, executionBinder, RenameSchema.class, RenameSchemaTask.class); bindDataDefinitionTask(binder, executionBinder, RenameTable.class, RenameTableTask.class); bindDataDefinitionTask(binder, executionBinder, RenameView.class, RenameViewTask.class); diff --git a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java index e1b6a73b9d1d..f818f3a5a7a1 100644 --- a/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java +++ b/core/trino-main/src/main/java/io/trino/sql/analyzer/StatementAnalyzer.java @@ -143,6 +143,7 @@ import io.trino.sql.tree.RefreshMaterializedView; import io.trino.sql.tree.Relation; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameSchema; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; @@ -978,6 +979,12 @@ protected Scope visitRenameView(RenameView node, Optional scope) return createAndAssignScope(node, scope); } + @Override + protected Scope visitRenameMaterializedView(RenameMaterializedView node, Optional scope) + { + return createAndAssignScope(node, scope); + } + @Override protected Scope visitSetViewAuthorization(SetViewAuthorization node, Optional scope) { diff --git a/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java b/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java index 0af7916b2a20..307211ac1c49 100644 --- a/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java +++ b/core/trino-main/src/main/java/io/trino/testing/LocalQueryRunner.java @@ -67,6 +67,7 @@ import io.trino.execution.QueryPreparer; import io.trino.execution.QueryPreparer.PreparedQuery; import io.trino.execution.RenameColumnTask; +import io.trino.execution.RenameMaterializedViewTask; import io.trino.execution.RenameTableTask; import io.trino.execution.RenameViewTask; import io.trino.execution.ResetSessionTask; @@ -175,6 +176,7 @@ import io.trino.sql.tree.DropView; import io.trino.sql.tree.Prepare; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; import io.trino.sql.tree.ResetSession; @@ -449,6 +451,7 @@ private LocalQueryRunner( .put(DropTable.class, new DropTableTask()) .put(DropView.class, new DropViewTask()) .put(RenameColumn.class, new RenameColumnTask()) + .put(RenameMaterializedView.class, new RenameMaterializedViewTask()) .put(RenameTable.class, new RenameTableTask()) .put(RenameView.class, new RenameViewTask()) .put(Comment.class, new CommentTask()) diff --git a/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java b/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java index 86f68ed4346a..4cc53370b33a 100644 --- a/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java +++ b/core/trino-main/src/main/java/io/trino/testing/TestingAccessControlManager.java @@ -67,6 +67,7 @@ import static io.trino.spi.security.AccessDeniedException.denyKillQuery; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; @@ -100,6 +101,7 @@ import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.KILL_QUERY; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.REFRESH_MATERIALIZED_VIEW; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_COLUMN; +import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_MATERIALIZED_VIEW; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_SCHEMA; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_TABLE; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_VIEW; @@ -526,6 +528,17 @@ public void checkCanDropMaterializedView(SecurityContext context, QualifiedObjec } } + @Override + public void checkCanRenameMaterializedView(SecurityContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + if (shouldDenyPrivilege(context.getIdentity().getUser(), viewName.getObjectName(), RENAME_MATERIALIZED_VIEW)) { + denyRenameMaterializedView(viewName.toString(), newViewName.toString()); + } + if (denyPrivileges.isEmpty()) { + super.checkCanRenameMaterializedView(context, viewName, newViewName); + } + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SecurityContext context, String functionName, Identity grantee, boolean grantOption) { @@ -645,7 +658,7 @@ public enum TestingPrivilegeType SHOW_CREATE_TABLE, CREATE_TABLE, DROP_TABLE, RENAME_TABLE, COMMENT_TABLE, COMMENT_COLUMN, INSERT_TABLE, DELETE_TABLE, UPDATE_TABLE, SHOW_COLUMNS, ADD_COLUMN, DROP_COLUMN, RENAME_COLUMN, SELECT_COLUMN, CREATE_VIEW, RENAME_VIEW, DROP_VIEW, CREATE_VIEW_WITH_SELECT_COLUMNS, - CREATE_MATERIALIZED_VIEW, REFRESH_MATERIALIZED_VIEW, DROP_MATERIALIZED_VIEW, + CREATE_MATERIALIZED_VIEW, REFRESH_MATERIALIZED_VIEW, DROP_MATERIALIZED_VIEW, RENAME_MATERIALIZED_VIEW, GRANT_EXECUTE_FUNCTION, SET_SESSION } diff --git a/core/trino-main/src/main/java/io/trino/util/StatementUtils.java b/core/trino-main/src/main/java/io/trino/util/StatementUtils.java index 23d3c89cb14e..8f7301db7d18 100644 --- a/core/trino-main/src/main/java/io/trino/util/StatementUtils.java +++ b/core/trino-main/src/main/java/io/trino/util/StatementUtils.java @@ -45,6 +45,7 @@ import io.trino.sql.tree.Query; import io.trino.sql.tree.RefreshMaterializedView; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameSchema; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; @@ -141,6 +142,7 @@ private StatementUtils() {} .put(GrantRoles.class, DATA_DEFINITION) .put(Prepare.class, DATA_DEFINITION) .put(RenameColumn.class, DATA_DEFINITION) + .put(RenameMaterializedView.class, DATA_DEFINITION) .put(RenameSchema.class, DATA_DEFINITION) .put(RenameTable.class, DATA_DEFINITION) .put(RenameView.class, DATA_DEFINITION) diff --git a/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java b/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java index 86c92827d44f..acef182c7c88 100644 --- a/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java +++ b/core/trino-main/src/test/java/io/trino/execution/BaseDataDefinitionTaskTest.java @@ -319,5 +319,13 @@ public void renameView(Session session, QualifiedObjectName source, QualifiedObj views.put(target.asSchemaTableName(), verifyNotNull(views.get(oldViewName), "View not found %s", oldViewName)); views.remove(oldViewName); } + + @Override + public void renameMaterializedView(Session session, QualifiedObjectName source, QualifiedObjectName target) + { + SchemaTableName oldViewName = source.asSchemaTableName(); + materializedViews.put(target.asSchemaTableName(), verifyNotNull(materializedViews.get(oldViewName), "Materialized View not found %s", oldViewName)); + materializedViews.remove(oldViewName); + } } } diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java new file mode 100644 index 000000000000..297435a51312 --- /dev/null +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameMaterializedViewTask.java @@ -0,0 +1,145 @@ +/* + * 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 io.trino.execution; + +import com.google.common.collect.ImmutableList; +import com.google.common.util.concurrent.ListenableFuture; +import io.trino.execution.warnings.WarningCollector; +import io.trino.metadata.QualifiedObjectName; +import io.trino.security.AllowAllAccessControl; +import io.trino.sql.tree.QualifiedName; +import io.trino.sql.tree.RenameMaterializedView; +import org.testng.annotations.Test; + +import static io.airlift.concurrent.MoreFutures.getFutureValue; +import static io.trino.spi.StandardErrorCode.TABLE_ALREADY_EXISTS; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static io.trino.testing.assertions.TrinoExceptionAssert.assertTrinoExceptionThrownBy; +import static org.assertj.core.api.Assertions.assertThat; + +@Test(singleThreaded = true) +public class TestRenameMaterializedViewTask + extends BaseDataDefinitionTaskTest +{ + @Test + public void testRenameExistingMaterializedView() + { + QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); + QualifiedObjectName newMaterializedViewName = qualifiedObjectName("existing_materialized_view_new"); + metadata.createMaterializedView(testSession, materializedViewName, someMaterializedView(), false, false); + + getFutureValue(executeRenameMaterializedView(asQualifiedName(materializedViewName), asQualifiedName(newMaterializedViewName))); + assertThat(metadata.getMaterializedView(testSession, materializedViewName)).isEmpty(); + assertThat(metadata.getMaterializedView(testSession, newMaterializedViewName)).isPresent(); + } + + @Test + public void testRenameNotExistingMaterializedView() + { + QualifiedName materializedViewName = qualifiedName("not_existing_materialized_view"); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(materializedViewName, qualifiedName("not_existing_materialized_view_new")))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("Materialized View '%s' does not exist", materializedViewName); + } + + @Test + public void testRenameNotExistingMaterializedViewIfExists() + { + QualifiedName materializedViewName = qualifiedName("not_existing_materialized_view"); + + getFutureValue(executeRenameMaterializedView(materializedViewName, qualifiedName("not_existing_materialized_view_new"), true)); + // no exception + } + + @Test + public void testRenameMaterializedViewOnTable() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, CATALOG_NAME, someTable(tableName), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(asQualifiedName(tableName), qualifiedName("existing_table_new")))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("Materialized View '%s' does not exist, but a table with that name exists. Did you mean ALTER TABLE %s RENAME ...?", tableName, tableName); + } + + @Test + public void testRenameMaterializedViewOnTableIfExists() + { + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, CATALOG_NAME, someTable(tableName), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(asQualifiedName(tableName), qualifiedName("existing_table_new"), true))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("Materialized View '%s' does not exist, but a table with that name exists. Did you mean ALTER TABLE %s RENAME ...?", tableName, tableName); + } + + @Test + public void testRenameMaterializedViewTargetTableExists() + { + QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); + metadata.createMaterializedView(testSession, materializedViewName, someMaterializedView(), false, false); + QualifiedObjectName tableName = qualifiedObjectName("existing_table"); + metadata.createTable(testSession, CATALOG_NAME, someTable(tableName), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(asQualifiedName(materializedViewName), asQualifiedName(tableName)))) + .hasErrorCode(TABLE_ALREADY_EXISTS) + .hasMessage("Target materialized view '%s' does not exist, but a table with that name exists.", tableName); + } + + @Test + public void testRenameMaterializedViewOnView() + { + QualifiedName viewName = qualifiedName("existing_view"); + metadata.createView(testSession, QualifiedObjectName.valueOf(viewName.toString()), someView(), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(viewName, qualifiedName("existing_view_new")))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW catalog.schema.existing_view RENAME ...?", viewName); + } + + @Test + public void testRenameMaterializedViewOnViewIfExists() + { + QualifiedName viewName = qualifiedName("existing_view"); + metadata.createView(testSession, QualifiedObjectName.valueOf(viewName.toString()), someView(), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(viewName, qualifiedName("existing_view_new"), true))) + .hasErrorCode(TABLE_NOT_FOUND) + .hasMessage("Materialized View '%s' does not exist, but a view with that name exists. Did you mean ALTER VIEW catalog.schema.existing_view RENAME ...?", viewName); + } + + @Test + public void testRenameMaterializedViewTargetViewExists() + { + QualifiedObjectName materializedViewName = qualifiedObjectName("existing_materialized_view"); + metadata.createMaterializedView(testSession, materializedViewName, someMaterializedView(), false, false); + QualifiedName viewName = qualifiedName("existing_view"); + metadata.createView(testSession, QualifiedObjectName.valueOf(viewName.toString()), someView(), false); + + assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameMaterializedView(asQualifiedName(materializedViewName), viewName))) + .hasErrorCode(TABLE_ALREADY_EXISTS) + .hasMessage("Target materialized view '%s' does not exist, but a view with that name exists.", viewName); + } + + private ListenableFuture executeRenameMaterializedView(QualifiedName source, QualifiedName target) + { + return executeRenameMaterializedView(source, target, false); + } + + private ListenableFuture executeRenameMaterializedView(QualifiedName source, QualifiedName target, boolean exists) + { + return new RenameMaterializedViewTask().execute(new RenameMaterializedView(source, target, exists), transactionManager, metadata, new AllowAllAccessControl(), queryStateMachine, ImmutableList.of(), WarningCollector.NOOP); + } +} diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java index 506d389edb0a..f80524559610 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameTableTask.java @@ -91,7 +91,7 @@ public void testRenameTableOnMaterializedView() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameTable(viewName, qualifiedName("existing_materialized_view_new"), false))) .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("Table '%s' does not exist, but a materialized view with that name exists.", viewName); + .hasMessage("Table '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW catalog.schema.existing_materialized_view RENAME ...?", viewName); } @Test diff --git a/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java b/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java index 116768b8dbfd..6d5d3e9db511 100644 --- a/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java +++ b/core/trino-main/src/test/java/io/trino/execution/TestRenameViewTask.java @@ -72,7 +72,7 @@ public void testRenameViewOnMaterializedView() assertTrinoExceptionThrownBy(() -> getFutureValue(executeRenameView(viewName, qualifiedName("existing_materialized_view_new")))) .hasErrorCode(TABLE_NOT_FOUND) - .hasMessage("View '%s' does not exist, but a materialized view with that name exists.", viewName); + .hasMessage("View '%s' does not exist, but a materialized view with that name exists. Did you mean ALTER MATERIALIZED VIEW catalog.schema.existing_materialized_view RENAME ...?", viewName); } private ListenableFuture executeRenameView(QualifiedName source, QualifiedName target) diff --git a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java index 2f33248c5f50..cc48b4900f38 100644 --- a/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java +++ b/core/trino-main/src/test/java/io/trino/metadata/AbstractMockMetadata.java @@ -908,6 +908,12 @@ public MaterializedViewFreshness getMaterializedViewFreshness(Session session, Q throw new UnsupportedOperationException(); } + @Override + public void renameMaterializedView(Session session, QualifiedObjectName existingViewName, QualifiedObjectName newViewName) + { + throw new UnsupportedOperationException(); + } + @Override public Optional applyTableScanRedirect(Session session, TableHandle tableHandle) { diff --git a/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 b/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 index 162a4473d9bb..0167fa00907c 100644 --- a/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 +++ b/core/trino-parser/src/main/antlr4/io/trino/sql/parser/SqlBase.g4 @@ -80,6 +80,8 @@ statement (SECURITY (DEFINER | INVOKER))? AS query #createView | REFRESH MATERIALIZED VIEW qualifiedName #refreshMaterializedView | DROP MATERIALIZED VIEW (IF EXISTS)? qualifiedName #dropMaterializedView + | ALTER MATERIALIZED VIEW (IF EXISTS)? from=qualifiedName + RENAME TO to=qualifiedName #renameMaterializedView | DROP VIEW (IF EXISTS)? qualifiedName #dropView | ALTER VIEW from=qualifiedName RENAME TO to=qualifiedName #renameView | ALTER VIEW from=qualifiedName SET AUTHORIZATION principal #setViewAuthorization diff --git a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java index a57254b0eb50..bdfa814509bf 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java +++ b/core/trino-parser/src/main/java/io/trino/sql/SqlFormatter.java @@ -83,6 +83,7 @@ import io.trino.sql.tree.RefreshMaterializedView; import io.trino.sql.tree.Relation; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameSchema; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; @@ -820,6 +821,20 @@ protected Void visitRenameView(RenameView node, Integer context) return null; } + @Override + protected Void visitRenameMaterializedView(RenameMaterializedView node, Integer context) + { + builder.append("ALTER MATERIALIZED VIEW "); + if (node.isExists()) { + builder.append("IF EXISTS "); + } + builder.append(node.getSource()) + .append(" RENAME TO ") + .append(node.getTarget()); + + return null; + } + @Override protected Void visitSetViewAuthorization(SetViewAuthorization node, Integer context) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java index 950a8bace894..2f9122f7c206 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java +++ b/core/trino-parser/src/main/java/io/trino/sql/parser/AstBuilder.java @@ -159,6 +159,7 @@ import io.trino.sql.tree.RefreshMaterializedView; import io.trino.sql.tree.Relation; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameSchema; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; @@ -673,6 +674,12 @@ public Node visitRenameView(SqlBaseParser.RenameViewContext context) return new RenameView(getLocation(context), getQualifiedName(context.from), getQualifiedName(context.to)); } + @Override + public Node visitRenameMaterializedView(SqlBaseParser.RenameMaterializedViewContext context) + { + return new RenameMaterializedView(getLocation(context), getQualifiedName(context.from), getQualifiedName(context.to), context.EXISTS() != null); + } + @Override public Node visitSetViewAuthorization(SqlBaseParser.SetViewAuthorizationContext context) { diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java b/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java index 59e1eec05f58..a064856ae61d 100644 --- a/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/AstVisitor.java @@ -627,6 +627,11 @@ protected R visitRenameView(RenameView node, C context) return visitStatement(node, context); } + protected R visitRenameMaterializedView(RenameMaterializedView node, C context) + { + return visitStatement(node, context); + } + protected R visitSetViewAuthorization(SetViewAuthorization node, C context) { return visitStatement(node, context); diff --git a/core/trino-parser/src/main/java/io/trino/sql/tree/RenameMaterializedView.java b/core/trino-parser/src/main/java/io/trino/sql/tree/RenameMaterializedView.java new file mode 100644 index 000000000000..ef9c05992e14 --- /dev/null +++ b/core/trino-parser/src/main/java/io/trino/sql/tree/RenameMaterializedView.java @@ -0,0 +1,107 @@ +/* + * 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 io.trino.sql.tree; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public final class RenameMaterializedView + extends Statement +{ + private final QualifiedName source; + private final QualifiedName target; + private final boolean exists; + + public RenameMaterializedView(QualifiedName source, QualifiedName target, boolean exists) + { + this(Optional.empty(), source, target, exists); + } + + public RenameMaterializedView(NodeLocation location, QualifiedName source, QualifiedName target, boolean exists) + { + this(Optional.of(location), source, target, exists); + } + + private RenameMaterializedView(Optional location, QualifiedName source, QualifiedName target, boolean exists) + { + super(location); + this.source = requireNonNull(source, "source name is null"); + this.target = requireNonNull(target, "target name is null"); + this.exists = exists; + } + + public QualifiedName getSource() + { + return source; + } + + public QualifiedName getTarget() + { + return target; + } + + public boolean isExists() + { + return exists; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitRenameMaterializedView(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.of(); + } + + @Override + public int hashCode() + { + return Objects.hash(source, target, exists); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if ((obj == null) || (getClass() != obj.getClass())) { + return false; + } + RenameMaterializedView o = (RenameMaterializedView) obj; + return Objects.equals(source, o.source) && + Objects.equals(target, o.target) && + Objects.equals(exists, o.exists); + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("source", source) + .add("target", target) + .add("exists", exists) + .toString(); + } +} diff --git a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java index 3f3bfcad76ba..9fb6306255a5 100644 --- a/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java +++ b/core/trino-parser/src/test/java/io/trino/sql/parser/TestSqlParser.java @@ -131,6 +131,7 @@ import io.trino.sql.tree.RangeQuantifier; import io.trino.sql.tree.RefreshMaterializedView; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameSchema; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; @@ -3181,6 +3182,13 @@ public void testDropMaterializedView() assertStatement("DROP MATERIALIZED VIEW IF EXISTS a.b.c", new DropMaterializedView(QualifiedName.of("a", "b", "c"), true)); } + @Test + public void testRenameMaterializedView() + { + assertStatement("ALTER MATERIALIZED VIEW a RENAME TO b", new RenameMaterializedView(QualifiedName.of("a"), QualifiedName.of("b"), false)); + assertStatement("ALTER MATERIALIZED VIEW IF EXISTS a RENAME TO b", new RenameMaterializedView(QualifiedName.of("a"), QualifiedName.of("b"), true)); + } + @Test public void testNullTreatment() { diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java index 64b431b318b5..3a4477149f54 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorAccessControl.java @@ -44,6 +44,7 @@ import static io.trino.spi.security.AccessDeniedException.denyInsertTable; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; @@ -406,6 +407,16 @@ default void checkCanDropMaterializedView(ConnectorSecurityContext context, Sche denyDropMaterializedView(materializedViewName.toString()); } + /** + * Check if identity is allowed to rename the specified materialized view. + * + * @throws io.trino.spi.security.AccessDeniedException if not allowed + */ + default void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + denyRenameMaterializedView(viewName.toString(), newViewName.toString()); + } + /** * Check if identity is allowed to set the specified property. * diff --git a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java index 2933987a2e81..aec03b2c8e5a 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java +++ b/core/trino-spi/src/main/java/io/trino/spi/connector/ConnectorMetadata.java @@ -1218,6 +1218,14 @@ default MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession throw new TrinoException(GENERIC_INTERNAL_ERROR, "ConnectorMetadata getMaterializedView() is implemented without getMaterializedViewFreshness()"); } + /** + * Rename the specified materialized view + */ + default void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support renaming materialized views"); + } + default Optional applyTableScanRedirect(ConnectorSession session, ConnectorTableHandle tableHandle) { return Optional.empty(); diff --git a/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java b/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java index 9ddf4ff9ac2a..20529940713d 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java +++ b/core/trino-spi/src/main/java/io/trino/spi/security/AccessDeniedException.java @@ -418,6 +418,16 @@ public static void denyDropMaterializedView(String materializedViewName, String throw new AccessDeniedException(format("Cannot drop materialized view %s%s", materializedViewName, formatExtraInfo(extraInfo))); } + public static void denyRenameMaterializedView(String materializedViewName, String newMaterializedViewName) + { + denyRenameMaterializedView(materializedViewName, newMaterializedViewName, null); + } + + public static void denyRenameMaterializedView(String materializedViewName, String newMaterializedViewName, String extraInfo) + { + throw new AccessDeniedException(format("Cannot rename materialized view from %s to %s%s", materializedViewName, newMaterializedViewName, formatExtraInfo(extraInfo))); + } + public static void denyGrantSchemaPrivilege(String privilege, String schemaName) { denyGrantSchemaPrivilege(privilege, schemaName, null); diff --git a/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControl.java b/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControl.java index 5eeb42fff22e..438b429c3fad 100644 --- a/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControl.java +++ b/core/trino-spi/src/main/java/io/trino/spi/security/SystemAccessControl.java @@ -54,6 +54,7 @@ import static io.trino.spi.security.AccessDeniedException.denyKillQuery; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRevokeRoles; @@ -532,6 +533,16 @@ default void checkCanDropMaterializedView(SystemSecurityContext context, Catalog denyDropMaterializedView(materializedView.toString()); } + /** + * Check if identity is allowed to rename the specified materialized view in a catalog. + * + * @throws AccessDeniedException if not allowed + */ + default void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + denyRenameMaterializedView(view.toString(), newView.toString()); + } + /** * Check if identity is allowed to grant an access to the function execution to grantee. * diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java index ddd0423759ee..b5aaf346e943 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorAccessControl.java @@ -307,6 +307,14 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem } } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.checkCanRenameMaterializedView(context, viewName, newViewName); + } + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java index a50d13eceec8..0845c47d4fa4 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/classloader/ClassLoaderSafeConnectorMetadata.java @@ -920,6 +920,14 @@ public MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession s } } + @Override + public void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.renameMaterializedView(session, source, target); + } + } + @Override public Optional applyTableScanRedirect(ConnectorSession session, ConnectorTableHandle tableHandle) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java index fef890893092..aa14a5bb118d 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllAccessControl.java @@ -196,6 +196,11 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem { } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllSystemAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllSystemAccessControl.java index a0d429893793..9aaefeb100e8 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/AllowAllSystemAccessControl.java @@ -285,6 +285,11 @@ public void checkCanDropMaterializedView(SystemSecurityContext context, CatalogS { } + @Override + public void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SystemSecurityContext context, String functionName, TrinoPrincipal grantee, boolean grantOption) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java index 10962b986b99..2164926b0b4b 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedAccessControl.java @@ -63,6 +63,7 @@ import static io.trino.spi.security.AccessDeniedException.denyInsertTable; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; @@ -424,6 +425,15 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem } } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + // check if user owns the existing materialized view, and if they will be an owner of the materialized view after the rename + if (!checkTablePermission(context, viewName, OWNERSHIP) || !checkTablePermission(context, newViewName, OWNERSHIP)) { + denyRenameMaterializedView(viewName.toString(), newViewName.toString()); + } + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedSystemAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedSystemAccessControl.java index a5c3c654a02d..2e3767611cbe 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/FileBasedSystemAccessControl.java @@ -85,6 +85,7 @@ import static io.trino.spi.security.AccessDeniedException.denyReadSystemInformationAccess; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; @@ -756,6 +757,15 @@ public void checkCanDropMaterializedView(SystemSecurityContext context, CatalogS } } + @Override + public void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + // check if user owns the existing materialized view, and if they will be an owner of the materialized view after the rename + if (!checkTablePermission(context, view, OWNERSHIP) || !checkTablePermission(context, newView, OWNERSHIP)) { + denyRenameMaterializedView(view.toString(), newView.toString()); + } + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SystemSecurityContext context, String functionName, TrinoPrincipal grantee, boolean grantOption) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java index 0b2d0b812a6f..099628f7ab44 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingConnectorAccessControl.java @@ -244,6 +244,12 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem delegate().checkCanDropMaterializedView(context, materializedViewName); } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + delegate().checkCanRenameMaterializedView(context, viewName, newViewName); + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingSystemAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingSystemAccessControl.java index ae27f4aa9e9c..395af5d2fa26 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingSystemAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ForwardingSystemAccessControl.java @@ -314,6 +314,12 @@ public void checkCanDropMaterializedView(SystemSecurityContext context, CatalogS delegate().checkCanDropMaterializedView(context, materializedView); } + @Override + public void checkCanRenameMaterializedView(SystemSecurityContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + delegate().checkCanRenameMaterializedView(context, view, newView); + } + @Override public void checkCanGrantExecuteFunctionPrivilege(SystemSecurityContext context, String functionName, TrinoPrincipal grantee, boolean grantOption) { diff --git a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java index 15e968ac9455..c313c073645d 100644 --- a/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java +++ b/lib/trino-plugin-toolkit/src/main/java/io/trino/plugin/base/security/ReadOnlyAccessControl.java @@ -36,6 +36,7 @@ import static io.trino.spi.security.AccessDeniedException.denyInsertTable; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; import static io.trino.spi.security.AccessDeniedException.denyRevokeTablePrivilege; @@ -202,6 +203,12 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem denyDropMaterializedView(materializedViewName.toString()); } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + denyRenameMaterializedView(viewName.toString(), newViewName.toString()); + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControl.java b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControl.java index f446cb437b67..f1368faa46d3 100644 --- a/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControl.java +++ b/lib/trino-plugin-toolkit/src/test/java/io/trino/plugin/base/security/TestFileBasedAccessControl.java @@ -278,6 +278,8 @@ public void testTableRules() accessControl.checkCanRenameTable(ALICE, aliceTable, new SchemaTableName("aliceschema", "newalicetable")); accessControl.checkCanRenameView(ADMIN, new SchemaTableName("bobschema", "bobview"), new SchemaTableName("aliceschema", "newbobview")); accessControl.checkCanRenameView(ALICE, new SchemaTableName("aliceschema", "aliceview"), new SchemaTableName("aliceschema", "newaliceview")); + accessControl.checkCanRenameMaterializedView(ADMIN, new SchemaTableName("bobschema", "bobmaterializedview"), new SchemaTableName("aliceschema", "newbobaterializedview")); + accessControl.checkCanRenameMaterializedView(ALICE, new SchemaTableName("aliceschema", "alicevaterializediew"), new SchemaTableName("aliceschema", "newaliceaterializedview")); assertDenied(() -> accessControl.checkCanInsertIntoTable(ALICE, bobTable)); assertDenied(() -> accessControl.checkCanDropTable(BOB, bobTable)); @@ -289,6 +291,8 @@ public void testTableRules() assertDenied(() -> accessControl.checkCanCreateViewWithSelectFromColumns(JOE, bobTable, ImmutableSet.of())); assertDenied(() -> accessControl.checkCanRenameView(BOB, new SchemaTableName("bobschema", "bobview"), new SchemaTableName("bobschema", "newbobview"))); assertDenied(() -> accessControl.checkCanRenameView(ALICE, aliceTable, new SchemaTableName("bobschema", "newalicetable"))); + assertDenied(() -> accessControl.checkCanRenameMaterializedView(BOB, new SchemaTableName("bobschema", "bobmaterializedview"), new SchemaTableName("bobschema", "newbobaterializedview"))); + assertDenied(() -> accessControl.checkCanRenameMaterializedView(ALICE, aliceTable, new SchemaTableName("bobschema", "newaliceaterializedview"))); accessControl.checkCanSetTableAuthorization(ADMIN, testTable, new TrinoPrincipal(PrincipalType.ROLE, "some_role")); accessControl.checkCanSetTableAuthorization(ADMIN, testTable, new TrinoPrincipal(PrincipalType.USER, "some_user")); diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewMetadata.java index 64f8e54f6b5d..7ee01a137929 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMaterializedViewMetadata.java @@ -40,4 +40,6 @@ public interface HiveMaterializedViewMetadata boolean delegateMaterializedViewRefreshToConnector(ConnectorSession session, SchemaTableName viewName); CompletableFuture refreshMaterializedView(ConnectorSession session, SchemaTableName viewName); + + void renameMaterializedView(ConnectorSession session, SchemaTableName existingViewName, SchemaTableName newViewName); } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java index fb55ebf130cd..d105a8aaa9e1 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/HiveMetadata.java @@ -3075,6 +3075,12 @@ public MaterializedViewFreshness getMaterializedViewFreshness(ConnectorSession s return hiveMaterializedViewMetadata.getMaterializedViewFreshness(session, name); } + @Override + public void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + hiveMaterializedViewMetadata.renameMaterializedView(session, source, target); + } + @Override public boolean delegateMaterializedViewRefreshToConnector(ConnectorSession session, SchemaTableName viewName) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveMaterializedViewMetadata.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveMaterializedViewMetadata.java index a8a89dc04c3c..24953f7b0a0b 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveMaterializedViewMetadata.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/NoneHiveMaterializedViewMetadata.java @@ -79,4 +79,10 @@ public CompletableFuture refreshMaterializedView(ConnectorSession session, Sc { throw new TrinoException(NOT_SUPPORTED, "This connector does not support materialized views"); } + + @Override + public void renameMaterializedView(ConnectorSession session, SchemaTableName existingViewName, SchemaTableName newViewName) + { + throw new TrinoException(NOT_SUPPORTED, "This connector does not support renaming materialized views"); + } } diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java index 6285364f7fb0..54f4a23ff710 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/LegacyAccessControl.java @@ -267,6 +267,11 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem { } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java index a4cdb9a4a780..e62cdebd1324 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/security/SqlStandardAccessControl.java @@ -73,6 +73,7 @@ import static io.trino.spi.security.AccessDeniedException.denyInsertTable; import static io.trino.spi.security.AccessDeniedException.denyRefreshMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameColumn; +import static io.trino.spi.security.AccessDeniedException.denyRenameMaterializedView; import static io.trino.spi.security.AccessDeniedException.denyRenameSchema; import static io.trino.spi.security.AccessDeniedException.denyRenameTable; import static io.trino.spi.security.AccessDeniedException.denyRenameView; @@ -375,6 +376,14 @@ public void checkCanDropMaterializedView(ConnectorSecurityContext context, Schem } } + @Override + public void checkCanRenameMaterializedView(ConnectorSecurityContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + if (!isTableOwner(context, viewName)) { + denyRenameMaterializedView(viewName.toString(), newViewName.toString()); + } + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorSecurityContext context, String propertyName) { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViews.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViews.java index f1514455e407..b878afaf67db 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViews.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMaterializedViews.java @@ -39,6 +39,7 @@ import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.DROP_MATERIALIZED_VIEW; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.INSERT_TABLE; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.REFRESH_MATERIALIZED_VIEW; +import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_MATERIALIZED_VIEW; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.SELECT_COLUMN; import static io.trino.testing.TestingAccessControlManager.TestingPrivilegeType.UPDATE_TABLE; import static io.trino.testing.TestingAccessControlManager.privilege; @@ -208,6 +209,17 @@ public void testDropDenyPermission() assertUpdate("DROP MATERIALIZED VIEW materialized_view_drop_deny"); } + @Test + public void testRenameDenyPermission() + { + assertUpdate("CREATE MATERIALIZED VIEW materialized_view_rename_deny AS SELECT * FROM base_table1"); + assertAccessDenied( + "ALTER MATERIALIZED VIEW materialized_view_rename_deny RENAME TO materialized_view_rename_deny_new", + "Cannot rename materialized view .*.materialized_view_rename_deny.*", + privilege("materialized_view_rename_deny", RENAME_MATERIALIZED_VIEW)); + assertUpdate("DROP MATERIALIZED VIEW materialized_view_rename_deny"); + } + @Test public void testRefreshDenyPermission() { diff --git a/service/trino-verifier/src/main/java/io/trino/verifier/VerifyCommand.java b/service/trino-verifier/src/main/java/io/trino/verifier/VerifyCommand.java index ae2cff383630..7685e4ab9a38 100644 --- a/service/trino-verifier/src/main/java/io/trino/verifier/VerifyCommand.java +++ b/service/trino-verifier/src/main/java/io/trino/verifier/VerifyCommand.java @@ -44,6 +44,7 @@ import io.trino.sql.tree.Insert; import io.trino.sql.tree.RefreshMaterializedView; import io.trino.sql.tree.RenameColumn; +import io.trino.sql.tree.RenameMaterializedView; import io.trino.sql.tree.RenameTable; import io.trino.sql.tree.RenameView; import io.trino.sql.tree.ShowCatalogs; @@ -415,6 +416,9 @@ private static QueryType statementToQueryType(Statement statement) if (statement instanceof DropColumn) { return MODIFY; } + if (statement instanceof RenameMaterializedView) { + return MODIFY; + } if (statement instanceof RenameTable) { return MODIFY; } diff --git a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java index 845736af02b9..38132cdb2d01 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/BaseConnectorTest.java @@ -50,6 +50,7 @@ import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_INSERT; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_MULTI_STATEMENT_WRITES; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_COLUMN; +import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_MATERIALIZED_VIEW; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_SCHEMA; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_TABLE; import static io.trino.testing.TestingConnectorBehavior.SUPPORTS_RENAME_TABLE_ACROSS_SCHEMAS; @@ -677,7 +678,68 @@ public void testMaterializedView() assertQueryReturnsEmptyResult(listMaterializedViewsSql("name = '" + view.getObjectName() + "'")); assertQueryReturnsEmptyResult(listMaterializedViewsSql("name = '" + otherView.getObjectName() + "'")); - assertQueryReturnsEmptyResult(listMaterializedViewsSql("name = '" + viewWithComment.getSchemaName() + "'")); + assertQueryReturnsEmptyResult(listMaterializedViewsSql("name = '" + viewWithComment.getObjectName() + "'")); + } + + @Test + public void testRenameMaterializedView() + { + skipTestUnless(hasBehavior(SUPPORTS_CREATE_MATERIALIZED_VIEW)); + + String schema = "rename_mv_test"; + Session session = Session.builder(getSession()) + .setSchema(schema) + .build(); + + QualifiedObjectName originalMaterializedView = new QualifiedObjectName( + session.getCatalog().orElseThrow(), + session.getSchema().orElseThrow(), + "test_materialized_view_rename_" + randomTableSuffix()); + + createTestingMaterializedView(originalMaterializedView, Optional.empty()); + + String renamedMaterializedView = "test_materialized_view_rename_new_" + randomTableSuffix(); + if (!hasBehavior(SUPPORTS_RENAME_MATERIALIZED_VIEW)) { + assertQueryFails(session, "ALTER MATERIALIZED VIEW " + originalMaterializedView + " RENAME TO " + renamedMaterializedView, "This connector does not support renaming materialized views"); + assertUpdate(session, "DROP MATERIALIZED VIEW " + originalMaterializedView); + return; + } + + // simple rename + assertUpdate(session, "ALTER MATERIALIZED VIEW " + originalMaterializedView + " RENAME TO " + renamedMaterializedView); + assertTestingMaterializedViewQuery(schema, renamedMaterializedView); + // verify new name in the system.metadata.materialized_views + assertQuery(session, "SELECT catalog_name, schema_name FROM system.metadata.materialized_views WHERE name = '" + renamedMaterializedView + "'", + format("VALUES ('%s', '%s')", originalMaterializedView.getCatalogName(), originalMaterializedView.getSchemaName())); + assertQueryReturnsEmptyResult(session, listMaterializedViewsSql("name = '" + originalMaterializedView.getObjectName() + "'")); + + // rename with IF EXISTS on existing materialized view + String testExistsMaterializedViewName = "test_materialized_view_rename_exists_" + randomTableSuffix(); + assertUpdate(session, "ALTER MATERIALIZED VIEW IF EXISTS " + renamedMaterializedView + " RENAME TO " + testExistsMaterializedViewName); + assertTestingMaterializedViewQuery(schema, testExistsMaterializedViewName); + + // rename with upper-case, not delimited identifier + String uppercaseName = "TEST_MATERIALIZED_VIEW_RENAME_UPPERCASE_" + randomTableSuffix(); + assertUpdate(session, "ALTER MATERIALIZED VIEW " + testExistsMaterializedViewName + " RENAME TO " + uppercaseName); + assertTestingMaterializedViewQuery(schema, uppercaseName.toLowerCase(ENGLISH)); // Ensure select allows for lower-case, not delimited identifier + + assertUpdate(session, "DROP MATERIALIZED VIEW " + uppercaseName); + + assertFalse(getQueryRunner().tableExists(session, originalMaterializedView.toString())); + assertFalse(getQueryRunner().tableExists(session, renamedMaterializedView)); + assertFalse(getQueryRunner().tableExists(session, testExistsMaterializedViewName)); + + // rename with IF EXISTS on NOT existing materialized view + assertUpdate(session, "ALTER TABLE IF EXISTS " + originalMaterializedView + " RENAME TO " + renamedMaterializedView); + assertQueryReturnsEmptyResult(session, listMaterializedViewsSql("name = '" + originalMaterializedView.getObjectName() + "'")); + assertQueryReturnsEmptyResult(session, listMaterializedViewsSql("name = '" + renamedMaterializedView + "'")); + } + + private void assertTestingMaterializedViewQuery(String schema, String materializedViewName) + { + assertThat(query("SELECT * FROM " + schema + "." + materializedViewName)) + .skippingTypesCheck() + .matches("SELECT * FROM nation"); } private void createTestingMaterializedView(QualifiedObjectName view, Optional comment) diff --git a/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java b/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java index ec93e6a1d156..749f4bd4f602 100644 --- a/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java +++ b/testing/trino-testing/src/main/java/io/trino/testing/TestingConnectorBehavior.java @@ -63,6 +63,7 @@ public enum TestingConnectorBehavior SUPPORTS_CREATE_VIEW(false), SUPPORTS_CREATE_MATERIALIZED_VIEW(false), + SUPPORTS_RENAME_MATERIALIZED_VIEW(false), SUPPORTS_INSERT, From bdc1b6f341509274d70ce8328302fdbc279616ad Mon Sep 17 00:00:00 2001 From: Lukasz Stec Date: Mon, 4 Oct 2021 17:31:35 +0200 Subject: [PATCH 3/3] Support materialized view rename in iceberg connector --- .../main/java/io/trino/plugin/iceberg/IcebergMetadata.java | 6 ++++++ .../src/main/java/io/trino/plugin/iceberg/TrinoCatalog.java | 2 ++ .../main/java/io/trino/plugin/iceberg/TrinoHiveCatalog.java | 6 ++++++ .../io/trino/plugin/iceberg/BaseIcebergConnectorTest.java | 1 + 4 files changed, 15 insertions(+) diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java index b9cf512c5ae8..169f4d56dce7 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/IcebergMetadata.java @@ -821,6 +821,12 @@ public Optional getMaterializedView(Connect return catalog.getMaterializedView(session, viewName); } + @Override + public void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + catalog.renameMaterializedView(session, source, target); + } + public Optional getTableToken(ConnectorSession session, ConnectorTableHandle tableHandle) { IcebergTableHandle table = (IcebergTableHandle) tableHandle; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoCatalog.java index d623ca25630d..f3364e3912fd 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoCatalog.java @@ -113,4 +113,6 @@ void createMaterializedView( void dropMaterializedView(ConnectorSession session, SchemaTableName schemaViewName); Optional getMaterializedView(ConnectorSession session, SchemaTableName schemaViewName); + + void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target); } diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoHiveCatalog.java index 65cd3c943583..4c3b489fdb17 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/TrinoHiveCatalog.java @@ -632,6 +632,12 @@ public Optional getMaterializedView(Connect properties.build())); } + @Override + public void renameMaterializedView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + metastore.renameTable(new HiveIdentity(session), source.getSchemaName(), source.getTableName(), target.getSchemaName(), target.getTableName()); + } + private List listNamespaces(ConnectorSession session, Optional namespace) { if (namespace.isPresent()) { diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java index b4a6b462f2c2..4481c37b0baa 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/BaseIcebergConnectorTest.java @@ -152,6 +152,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) return true; case SUPPORTS_CREATE_MATERIALIZED_VIEW: + case SUPPORTS_RENAME_MATERIALIZED_VIEW: return true; case SUPPORTS_DELETE: