diff --git a/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/SemanticErrorCode.java b/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/SemanticErrorCode.java index 66b6d73302598..d5492e6bb6932 100644 --- a/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/SemanticErrorCode.java +++ b/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/SemanticErrorCode.java @@ -83,6 +83,8 @@ public enum SemanticErrorCode VIEW_IS_STALE, VIEW_IS_RECURSIVE, MATERIALIZED_VIEW_IS_RECURSIVE, + MISSING_VIEW, + VIEW_ALREADY_EXISTS, NON_NUMERIC_SAMPLE_PERCENTAGE, diff --git a/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/utils/StatementUtils.java b/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/utils/StatementUtils.java index 0a2b34bebef10..bb16839f87288 100644 --- a/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/utils/StatementUtils.java +++ b/presto-analyzer/src/main/java/com/facebook/presto/sql/analyzer/utils/StatementUtils.java @@ -51,6 +51,7 @@ import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameSchema; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Revoke; import com.facebook.presto.sql.tree.RevokeRoles; @@ -134,6 +135,7 @@ private StatementUtils() {} builder.put(AddConstraint.class, QueryType.DATA_DEFINITION); builder.put(AlterColumnNotNull.class, QueryType.DATA_DEFINITION); builder.put(CreateView.class, QueryType.DATA_DEFINITION); + builder.put(RenameView.class, QueryType.DATA_DEFINITION); builder.put(TruncateTable.class, QueryType.DATA_DEFINITION); builder.put(DropView.class, QueryType.DATA_DEFINITION); builder.put(CreateMaterializedView.class, QueryType.DATA_DEFINITION); diff --git a/presto-docs/src/main/sphinx/connector/iceberg.rst b/presto-docs/src/main/sphinx/connector/iceberg.rst index 86f83bb3674c6..6644c2ade0afb 100644 --- a/presto-docs/src/main/sphinx/connector/iceberg.rst +++ b/presto-docs/src/main/sphinx/connector/iceberg.rst @@ -1209,6 +1209,15 @@ For example, to set `commit_retries` to 6 for the table `iceberg.web.page_views_ ALTER TABLE iceberg.web.page_views_v2 SET PROPERTIES (commit_retries = 6); +ALTER VIEW +^^^^^^^^^^ + +Alter view operations to alter the name of an existing view to a new name is supported in the Iceberg connector. + +.. code-block:: sql + + ALTER VIEW iceberg.web.page_views RENAME TO iceberg.web.page_new_views; + TRUNCATE ^^^^^^^^ diff --git a/presto-docs/src/main/sphinx/connector/memory.rst b/presto-docs/src/main/sphinx/connector/memory.rst index 0a49cc4a6290a..041282c60d84f 100644 --- a/presto-docs/src/main/sphinx/connector/memory.rst +++ b/presto-docs/src/main/sphinx/connector/memory.rst @@ -92,6 +92,15 @@ To delete an existing table: .. note:: After using ``DROP TABLE``, memory is not released immediately. It is released after the next write access to the memory connector. +ALTER VIEW +^^^^^^^^^^ + +Alter view operations to alter the name of an existing view to a new name is supported in the Memory connector. + +.. code-block:: sql + + ALTER VIEW memory.default.nation RENAME TO memory.default.new_nation; + Memory Connector Limitations ---------------------------- diff --git a/presto-docs/src/main/sphinx/sql.rst b/presto-docs/src/main/sphinx/sql.rst index 88a4613a27512..84b7ced26d1b7 100644 --- a/presto-docs/src/main/sphinx/sql.rst +++ b/presto-docs/src/main/sphinx/sql.rst @@ -10,6 +10,7 @@ This chapter describes the SQL syntax used in Presto. sql/alter-function sql/alter-schema sql/alter-table + sql/alter-view sql/analyze sql/call sql/commit diff --git a/presto-docs/src/main/sphinx/sql/alter-view.rst b/presto-docs/src/main/sphinx/sql/alter-view.rst new file mode 100644 index 0000000000000..dfe57193bfbb4 --- /dev/null +++ b/presto-docs/src/main/sphinx/sql/alter-view.rst @@ -0,0 +1,42 @@ +========== +ALTER VIEW +========== + +Synopsis +-------- + +.. code-block:: sql + + ALTER VIEW [IF EXISTS] old_view_name RENAME TO new_view_name; + +Description +----------- + +The ``ALTER VIEW [IF EXISTS] RENAME TO`` statement renames an existing view to a +new name. This allows you to change the name of a view without having to drop +and recreate it. The view's definition, security settings, and dependencies +remain unchanged; only the name of the view is updated. + +The optional ``IF EXISTS`` clause prevents an error from being raised if the +view does not exist. Instead, no action is taken, and a notice is issued. + +Renaming a view does not affect the data or structure of the underlying +query used to define the view. Any permissions or dependencies on the +view are retained, and queries or applications using the old name must +be updated to use the new name. + +Examples +-------- + +Rename the view ``users`` to ``people``:: + + ALTER VIEW users RENAME TO people; + +Rename the view ``users`` to ``people`` if view ``users`` exists:: + + ALTER VIEW IF EXISTS users RENAME TO people; + +See Also +-------- + +:doc:`create-view` diff --git a/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/MetastoreUtil.java b/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/MetastoreUtil.java index 28cb2b84017d5..87cd7e7e061ca 100644 --- a/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/MetastoreUtil.java +++ b/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/MetastoreUtil.java @@ -1026,6 +1026,11 @@ public static boolean isIcebergTable(Map tableParameters) return ICEBERG_TABLE_TYPE_VALUE.equalsIgnoreCase(tableParameters.get(ICEBERG_TABLE_TYPE_NAME)); } + public static boolean isIcebergView(Table table) + { + return "true".equalsIgnoreCase(table.getParameters().get(PRESTO_VIEW_FLAG)); + } + public static PrincipalPrivileges buildInitialPrivilegeSet(String tableOwner) { PrestoPrincipal owner = new PrestoPrincipal(USER, tableOwner); diff --git a/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/file/FileHiveMetastore.java b/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/file/FileHiveMetastore.java index 5ed409e353669..560bf7dd8f847 100644 --- a/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/file/FileHiveMetastore.java +++ b/presto-hive-metastore/src/main/java/com/facebook/presto/hive/metastore/file/FileHiveMetastore.java @@ -99,6 +99,7 @@ import static com.facebook.presto.hive.metastore.MetastoreUtil.getHiveBasicStatistics; import static com.facebook.presto.hive.metastore.MetastoreUtil.getPartitionNamesWithEmptyVersion; import static com.facebook.presto.hive.metastore.MetastoreUtil.isIcebergTable; +import static com.facebook.presto.hive.metastore.MetastoreUtil.isIcebergView; import static com.facebook.presto.hive.metastore.MetastoreUtil.makePartName; import static com.facebook.presto.hive.metastore.MetastoreUtil.toPartitionValues; import static com.facebook.presto.hive.metastore.MetastoreUtil.updateStatisticsParameters; @@ -511,13 +512,11 @@ public synchronized MetastoreOperationResult renameTable(MetastoreContext metast requireNonNull(tableName, "tableName is null"); requireNonNull(newDatabaseName, "newDatabaseName is null"); requireNonNull(newTableName, "newTableName is null"); - Table table = getRequiredTable(metastoreContext, databaseName, tableName); getRequiredDatabase(metastoreContext, newDatabaseName); - if (isIcebergTable(table)) { + if (isIcebergTable(table) && !isIcebergView(table)) { throw new PrestoException(NOT_SUPPORTED, "Rename not supported for Iceberg tables"); } - // verify new table does not exist verifyTableNotExists(metastoreContext, newDatabaseName, newTableName); diff --git a/presto-hive/src/main/java/com/facebook/presto/hive/security/LegacyAccessControl.java b/presto-hive/src/main/java/com/facebook/presto/hive/security/LegacyAccessControl.java index 2277d1dab0f53..d1b77eb6d685d 100644 --- a/presto-hive/src/main/java/com/facebook/presto/hive/security/LegacyAccessControl.java +++ b/presto-hive/src/main/java/com/facebook/presto/hive/security/LegacyAccessControl.java @@ -201,6 +201,11 @@ public void checkCanCreateView(ConnectorTransactionHandle transaction, Connector { } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + } + @Override public void checkCanDropView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-hive/src/main/java/com/facebook/presto/hive/security/SqlStandardAccessControl.java b/presto-hive/src/main/java/com/facebook/presto/hive/security/SqlStandardAccessControl.java index 6fd805e34c5e2..f55247fb69397 100644 --- a/presto-hive/src/main/java/com/facebook/presto/hive/security/SqlStandardAccessControl.java +++ b/presto-hive/src/main/java/com/facebook/presto/hive/security/SqlStandardAccessControl.java @@ -70,6 +70,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeRoles; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySelectTable; @@ -451,6 +452,24 @@ public void checkCanCreateView(ConnectorTransactionHandle transaction, Connector } } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + MetastoreContext metastoreContext = new MetastoreContext( + identity, context.getQueryId().getId(), + context.getClientInfo(), + context.getClientTags(), + context.getSource(), + Optional.empty(), + false, + HiveColumnConverterProvider.DEFAULT_COLUMN_CONVERTER_PROVIDER, + context.getWarningCollector(), + context.getRuntimeStats()); + if (!isTableOwner(transaction, identity, metastoreContext, viewName)) { + denyRenameView(viewName.toString(), newViewName.toString()); + } + } + @Override public void checkCanDropView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-hive/src/main/java/com/facebook/presto/hive/security/SystemTableAwareAccessControl.java b/presto-hive/src/main/java/com/facebook/presto/hive/security/SystemTableAwareAccessControl.java index 9f2b182d39d4b..74a433e5d6356 100644 --- a/presto-hive/src/main/java/com/facebook/presto/hive/security/SystemTableAwareAccessControl.java +++ b/presto-hive/src/main/java/com/facebook/presto/hive/security/SystemTableAwareAccessControl.java @@ -173,6 +173,12 @@ public void checkCanCreateView(ConnectorTransactionHandle transactionHandle, Con delegate.checkCanCreateView(transactionHandle, identity, context, viewName); } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + delegate.checkCanRenameView(transactionHandle, identity, context, viewName, newViewName); + } + @Override public void checkCanDropView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java b/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java index 5f7e38a813751..ec75d1d7cf7de 100644 --- a/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java +++ b/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java @@ -421,6 +421,13 @@ public Map getViews(ConnectorSession s return views.build(); } + @Override + public void renameView(ConnectorSession session, SchemaTableName source, SchemaTableName target) + { + // Not checking if source view exists as this is already done in RenameViewTask + metastore.renameTable(getMetastoreContext(session), source.getSchemaName(), source.getTableName(), target.getSchemaName(), target.getTableName()); + } + @Override public void dropView(ConnectorSession session, SchemaTableName viewName) { diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMetadataListing.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMetadataListing.java index 4cb866e1072bd..4b18df2cacce7 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMetadataListing.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMetadataListing.java @@ -153,4 +153,25 @@ public void testTableValidation() assertQuerySucceeds("SELECT * FROM iceberg.test_schema.iceberg_table1"); assertQueryFails("SELECT * FROM iceberg.test_schema.hive_table", "Not an Iceberg table: test_schema.hive_table"); } + + @Test + public void testRenameView() + { + assertQuerySucceeds("CREATE TABLE iceberg.test_schema.iceberg_test_table (_string VARCHAR, _integer INTEGER)"); + assertUpdate("CREATE VIEW iceberg.test_schema.test_view_to_be_renamed AS SELECT * FROM iceberg.test_schema.iceberg_test_table"); + assertUpdate("ALTER VIEW IF EXISTS iceberg.test_schema.test_view_to_be_renamed RENAME TO iceberg.test_schema.test_view_renamed"); + assertUpdate("CREATE VIEW iceberg.test_schema.test_view2_to_be_renamed AS SELECT * FROM iceberg.test_schema.iceberg_test_table"); + assertUpdate("ALTER VIEW iceberg.test_schema.test_view2_to_be_renamed RENAME TO iceberg.test_schema.test_view2_renamed"); + assertQuerySucceeds("SELECT * FROM iceberg.test_schema.test_view_renamed"); + assertQuerySucceeds("SELECT * FROM iceberg.test_schema.test_view2_renamed"); + assertUpdate("DROP VIEW iceberg.test_schema.test_view_renamed"); + assertUpdate("DROP VIEW iceberg.test_schema.test_view2_renamed"); + assertUpdate("DROP TABLE iceberg.test_schema.iceberg_test_table"); + } + @Test + public void testRenameViewIfNotExists() + { + assertQueryFails("ALTER VIEW iceberg.test_schema.test_rename_view_not_exist RENAME TO iceberg.test_schema.test_renamed_view_not_exist", "line 1:1: View 'iceberg.test_schema.test_rename_view_not_exist' does not exist"); + assertQuerySucceeds("ALTER VIEW IF EXISTS iceberg.test_schema.test_rename_view_not_exist RENAME TO iceberg.test_schema.test_renamed_view_not_exist"); + } } diff --git a/presto-main/src/main/java/com/facebook/presto/execution/RenameViewTask.java b/presto-main/src/main/java/com/facebook/presto/execution/RenameViewTask.java new file mode 100644 index 0000000000000..61c3cadc8619f --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/execution/RenameViewTask.java @@ -0,0 +1,79 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.facebook.presto.execution; + +import com.facebook.presto.Session; +import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.metadata.Metadata; +import com.facebook.presto.spi.WarningCollector; +import com.facebook.presto.spi.analyzer.ViewDefinition; +import com.facebook.presto.spi.security.AccessControl; +import com.facebook.presto.sql.analyzer.SemanticException; +import com.facebook.presto.sql.tree.Expression; +import com.facebook.presto.sql.tree.RenameView; +import com.facebook.presto.transaction.TransactionManager; +import com.google.common.util.concurrent.ListenableFuture; + +import java.util.List; +import java.util.Optional; + +import static com.facebook.presto.metadata.MetadataUtil.createQualifiedObjectName; +import static com.facebook.presto.sql.analyzer.SemanticErrorCode.MISSING_CATALOG; +import static com.facebook.presto.sql.analyzer.SemanticErrorCode.MISSING_VIEW; +import static com.facebook.presto.sql.analyzer.SemanticErrorCode.NOT_SUPPORTED; +import static com.facebook.presto.sql.analyzer.SemanticErrorCode.VIEW_ALREADY_EXISTS; +import static com.google.common.util.concurrent.Futures.immediateFuture; + +public class RenameViewTask + implements DDLDefinitionTask +{ + @Override + public String getName() + { + return "RENAME VIEW"; + } + + public ListenableFuture execute(RenameView statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector) + { + QualifiedObjectName viewName = createQualifiedObjectName(session, statement, statement.getSource()); + + Optional view = metadata.getMetadataResolver(session).getView(viewName); + if (!view.isPresent()) { + if (!statement.isExists()) { + throw new SemanticException(MISSING_VIEW, statement, "View '%s' does not exist", viewName); + } + return immediateFuture(null); + } + + QualifiedObjectName target = createQualifiedObjectName(session, statement, statement.getTarget()); + if (!metadata.getCatalogHandle(session, target.getCatalogName()).isPresent()) { + throw new SemanticException(MISSING_CATALOG, statement, "Target catalog '%s' does not exist", target.getCatalogName()); + } + if (metadata.getMetadataResolver(session).getView(target).isPresent()) { + throw new SemanticException(VIEW_ALREADY_EXISTS, statement, "Target view '%s' already exists", target); + } + if (!viewName.getSchemaName().equals(target.getSchemaName())) { + throw new SemanticException(NOT_SUPPORTED, statement, "View rename across schemas is not supported"); + } + if (!viewName.getCatalogName().equals(target.getCatalogName())) { + throw new SemanticException(NOT_SUPPORTED, statement, "View rename across catalogs is not supported"); + } + + accessControl.checkCanRenameView(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), viewName, target); + + metadata.renameView(session, viewName, target); + + return immediateFuture(null); + } +} diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java b/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java index 8954086f99db4..83cd4594032f5 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java @@ -432,6 +432,12 @@ public void createView(Session session, String catalogName, ConnectorTableMetada delegate.createView(session, catalogName, viewMetadata, viewData, replace); } + @Override + public void renameView(Session session, QualifiedObjectName existingViewName, QualifiedObjectName newViewName) + { + delegate.renameView(session, existingViewName, newViewName); + } + @Override public void dropView(Session session, QualifiedObjectName viewName) { diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java b/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java index 1cc99fcce3b9b..4f3f698be42c9 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/Metadata.java @@ -368,6 +368,11 @@ public interface Metadata */ void createView(Session session, String catalogName, ConnectorTableMetadata viewMetadata, String viewData, boolean replace); + /** + * Rename the specified view. + */ + void renameView(Session session, QualifiedObjectName existingViewName, QualifiedObjectName newViewName); + /** * Drops the specified view. */ diff --git a/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java b/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java index 7f9812c48f891..3041d3990fdf8 100644 --- a/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java +++ b/presto-main/src/main/java/com/facebook/presto/metadata/MetadataManager.java @@ -1004,6 +1004,16 @@ public void createView(Session session, String catalogName, ConnectorTableMetada metadata.createView(session.toConnectorSession(connectorId), viewMetadata, viewData, replace); } + @Override + public void renameView(Session session, QualifiedObjectName source, QualifiedObjectName target) + { + CatalogMetadata catalogMetadata = getCatalogMetadataForWrite(session, target.getCatalogName()); + ConnectorId connectorId = catalogMetadata.getConnectorId(); + ConnectorMetadata metadata = catalogMetadata.getMetadata(); + + metadata.renameView(session.toConnectorSession(connectorId), toSchemaTableName(source), toSchemaTableName(target)); + } + @Override public void dropView(Session session, QualifiedObjectName viewName) { diff --git a/presto-main/src/main/java/com/facebook/presto/security/AccessControlManager.java b/presto-main/src/main/java/com/facebook/presto/security/AccessControlManager.java index d4d0125959c53..468d7d0ed2f3b 100644 --- a/presto-main/src/main/java/com/facebook/presto/security/AccessControlManager.java +++ b/presto-main/src/main/java/com/facebook/presto/security/AccessControlManager.java @@ -514,6 +514,23 @@ public void checkCanCreateView(TransactionId transactionId, Identity identity, A } } + @Override + public void checkCanRenameView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + requireNonNull(context, "context is null"); + requireNonNull(viewName, "viewName is null"); + requireNonNull(newViewName, "newViewName is null"); + + authenticationCheck(() -> checkCanAccessCatalog(identity, context, viewName.getCatalogName())); + + authorizationCheck(() -> systemAccessControl.get().checkCanRenameView(identity, context, toCatalogSchemaTableName(viewName), toCatalogSchemaTableName(newViewName))); + + CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, viewName.getCatalogName()); + if (entry != null) { + authorizationCheck(() -> entry.getAccessControl().checkCanRenameView(entry.getTransactionHandle(transactionId), identity.toConnectorIdentity(viewName.getCatalogName()), context, toSchemaTableName(viewName), toSchemaTableName(newViewName))); + } + } + @Override public void checkCanDropView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { diff --git a/presto-main/src/main/java/com/facebook/presto/security/AllowAllSystemAccessControl.java b/presto-main/src/main/java/com/facebook/presto/security/AllowAllSystemAccessControl.java index 4197801047f77..a0e1ea6c315f2 100644 --- a/presto-main/src/main/java/com/facebook/presto/security/AllowAllSystemAccessControl.java +++ b/presto-main/src/main/java/com/facebook/presto/security/AllowAllSystemAccessControl.java @@ -192,6 +192,11 @@ public void checkCanCreateView(Identity identity, AccessControlContext context, { } + @Override + public void checkCanRenameView(Identity identity, AccessControlContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + } + @Override public void checkCanDropView(Identity identity, AccessControlContext context, CatalogSchemaTableName view) { diff --git a/presto-main/src/main/java/com/facebook/presto/security/FileBasedSystemAccessControl.java b/presto-main/src/main/java/com/facebook/presto/security/FileBasedSystemAccessControl.java index f9bf1d5b976d8..199490f096f29 100644 --- a/presto-main/src/main/java/com/facebook/presto/security/FileBasedSystemAccessControl.java +++ b/presto-main/src/main/java/com/facebook/presto/security/FileBasedSystemAccessControl.java @@ -65,6 +65,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySetTableProperties; import static com.facebook.presto.spi.security.AccessDeniedException.denySetUser; @@ -388,6 +389,14 @@ public void checkCanCreateView(Identity identity, AccessControlContext context, } } + @Override + public void checkCanRenameView(Identity identity, AccessControlContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + if (!canAccessCatalog(identity, view.getCatalogName(), ALL)) { + denyRenameView(view.toString(), newView.toString()); + } + } + @Override public void checkCanDropView(Identity identity, AccessControlContext context, CatalogSchemaTableName view) { diff --git a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java index d9fb6707c20d5..1e4e86af06446 100644 --- a/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java +++ b/presto-main/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java @@ -136,6 +136,7 @@ import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameSchema; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Return; import com.facebook.presto.sql.tree.Revoke; @@ -1006,6 +1007,12 @@ protected Scope visitAlterColumnNotNull(AlterColumnNotNull node, Optional return createAndAssignScope(node, scope); } + @Override + protected Scope visitRenameView(RenameView node, Optional scope) + { + return createAndAssignScope(node, scope); + } + @Override protected Scope visitDropView(DropView node, Optional scope) { diff --git a/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java b/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java index 3656b5d734936..af9e69895b349 100644 --- a/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java +++ b/presto-main/src/main/java/com/facebook/presto/testing/LocalQueryRunner.java @@ -70,6 +70,7 @@ import com.facebook.presto.execution.QueryManagerConfig; import com.facebook.presto.execution.RenameColumnTask; import com.facebook.presto.execution.RenameTableTask; +import com.facebook.presto.execution.RenameViewTask; import com.facebook.presto.execution.ResetSessionTask; import com.facebook.presto.execution.RollbackTask; import com.facebook.presto.execution.ScheduledSplit; @@ -210,6 +211,7 @@ import com.facebook.presto.sql.tree.Prepare; import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Rollback; import com.facebook.presto.sql.tree.SetProperties; @@ -578,6 +580,7 @@ private LocalQueryRunner(Session defaultSession, FeaturesConfig featuresConfig, .put(DropMaterializedView.class, new DropMaterializedViewTask()) .put(RenameColumn.class, new RenameColumnTask()) .put(RenameTable.class, new RenameTableTask()) + .put(RenameView.class, new RenameViewTask()) .put(ResetSession.class, new ResetSessionTask()) .put(SetSession.class, new SetSessionTask()) .put(Prepare.class, new PrepareTask(sqlParser)) diff --git a/presto-main/src/main/java/com/facebook/presto/testing/TestingAccessControlManager.java b/presto-main/src/main/java/com/facebook/presto/testing/TestingAccessControlManager.java index 1db74c076d0b2..fa2e681c1878d 100644 --- a/presto-main/src/main/java/com/facebook/presto/testing/TestingAccessControlManager.java +++ b/presto-main/src/main/java/com/facebook/presto/testing/TestingAccessControlManager.java @@ -50,6 +50,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denySelectColumns; import static com.facebook.presto.spi.security.AccessDeniedException.denySetCatalogSessionProperty; import static com.facebook.presto.spi.security.AccessDeniedException.denySetSystemSessionProperty; @@ -73,6 +74,7 @@ import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_COLUMN; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_SCHEMA; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_TABLE; +import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.RENAME_VIEW; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.SELECT_COLUMN; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.SET_SESSION; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.SET_TABLE_PROPERTIES; @@ -286,6 +288,17 @@ public void checkCanCreateView(TransactionId transactionId, Identity identity, A } } + @Override + public void checkCanRenameView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + if (shouldDenyPrivilege(identity.getUser(), viewName.getObjectName(), RENAME_VIEW)) { + denyRenameView(viewName.toString(), newViewName.toString()); + } + if (denyPrivileges.isEmpty()) { + super.checkCanRenameView(transactionId, identity, context, viewName, newViewName); + } + } + @Override public void checkCanDropView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { @@ -383,7 +396,7 @@ public enum TestingPrivilegeType CREATE_TABLE, DROP_TABLE, RENAME_TABLE, INSERT_TABLE, DELETE_TABLE, TRUNCATE_TABLE, UPDATE_TABLE, ADD_COLUMN, DROP_COLUMN, RENAME_COLUMN, SELECT_COLUMN, ADD_CONSTRAINT, DROP_CONSTRAINT, - CREATE_VIEW, DROP_VIEW, CREATE_VIEW_WITH_SELECT_COLUMNS, SET_TABLE_PROPERTIES, + CREATE_VIEW, RENAME_VIEW, DROP_VIEW, CREATE_VIEW_WITH_SELECT_COLUMNS, SET_TABLE_PROPERTIES, SET_SESSION } diff --git a/presto-main/src/main/java/com/facebook/presto/util/PrestoDataDefBindingHelper.java b/presto-main/src/main/java/com/facebook/presto/util/PrestoDataDefBindingHelper.java index f83e282373e0d..3170b0f62d792 100644 --- a/presto-main/src/main/java/com/facebook/presto/util/PrestoDataDefBindingHelper.java +++ b/presto-main/src/main/java/com/facebook/presto/util/PrestoDataDefBindingHelper.java @@ -42,6 +42,7 @@ import com.facebook.presto.execution.RenameColumnTask; import com.facebook.presto.execution.RenameSchemaTask; import com.facebook.presto.execution.RenameTableTask; +import com.facebook.presto.execution.RenameViewTask; import com.facebook.presto.execution.ResetSessionTask; import com.facebook.presto.execution.RevokeRolesTask; import com.facebook.presto.execution.RevokeTask; @@ -80,6 +81,7 @@ import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameSchema; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Revoke; import com.facebook.presto.sql.tree.RevokeRoles; @@ -129,6 +131,7 @@ private PrestoDataDefBindingHelper() {} dataDefBuilder.put(DropTable.class, DropTableTask.class); dataDefBuilder.put(TruncateTable.class, TruncateTableTask.class); dataDefBuilder.put(CreateView.class, CreateViewTask.class); + dataDefBuilder.put(RenameView.class, RenameViewTask.class); dataDefBuilder.put(DropView.class, DropViewTask.class); dataDefBuilder.put(CreateMaterializedView.class, CreateMaterializedViewTask.class); dataDefBuilder.put(DropMaterializedView.class, DropMaterializedViewTask.class); diff --git a/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java b/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java index 48210df7d5594..49a6051c22362 100644 --- a/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java +++ b/presto-main/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java @@ -464,6 +464,12 @@ public void createView(Session session, String catalogName, ConnectorTableMetada throw new UnsupportedOperationException(); } + @Override + public void renameView(Session session, QualifiedObjectName source, QualifiedObjectName target) + { + throw new UnsupportedOperationException(); + } + @Override public void dropView(Session session, QualifiedObjectName viewName) { diff --git a/presto-main/src/test/java/com/facebook/presto/security/TestAccessControlManager.java b/presto-main/src/test/java/com/facebook/presto/security/TestAccessControlManager.java index 7039ba042697d..767e0a06d35b3 100644 --- a/presto-main/src/test/java/com/facebook/presto/security/TestAccessControlManager.java +++ b/presto-main/src/test/java/com/facebook/presto/security/TestAccessControlManager.java @@ -458,6 +458,12 @@ public void checkCanCreateView(ConnectorTransactionHandle transactionHandle, Con throw new UnsupportedOperationException(); } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + throw new UnsupportedOperationException(); + } + @Override public void checkCanDropView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-memory/src/main/java/com/facebook/presto/plugin/memory/MemoryMetadata.java b/presto-memory/src/main/java/com/facebook/presto/plugin/memory/MemoryMetadata.java index 0c8c9e8b62f99..b600eabc8ea16 100644 --- a/presto-memory/src/main/java/com/facebook/presto/plugin/memory/MemoryMetadata.java +++ b/presto-memory/src/main/java/com/facebook/presto/plugin/memory/MemoryMetadata.java @@ -291,6 +291,21 @@ else if (views.putIfAbsent(viewName, viewData) != null) { } } + @Override + public synchronized void renameView(ConnectorSession session, SchemaTableName viewName, SchemaTableName newViewName) + { + checkSchemaExists(newViewName.getSchemaName()); + if (tableIds.containsKey(newViewName)) { + throw new PrestoException(ALREADY_EXISTS, "Table already exists: " + newViewName); + } + + if (views.containsKey(newViewName)) { + throw new PrestoException(ALREADY_EXISTS, "View already exists: " + newViewName); + } + + views.put(newViewName, views.remove(viewName)); + } + @Override public synchronized void dropView(ConnectorSession session, SchemaTableName viewName) { diff --git a/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemoryMetadata.java b/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemoryMetadata.java index 1a1c200935f2a..acc108c57a76b 100644 --- a/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemoryMetadata.java +++ b/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemoryMetadata.java @@ -226,6 +226,8 @@ public void testViews() test2, ImmutableList.of(new ColumnMetadata("a", BIGINT))); + SchemaTableName test3 = new SchemaTableName("test", "test_view3"); + // create schema metadata.createSchema(SESSION, "test", ImmutableMap.of()); @@ -265,8 +267,14 @@ public void testViews() views = metadata.getViews(SESSION, new SchemaTablePrefix("test")); assertEquals(views.keySet(), ImmutableSet.of(test2)); + // rename second view + metadata.renameView(SESSION, test2, test3); + + Map result = metadata.getViews(SESSION, new SchemaTablePrefix("test")); + assertTrue(result.containsKey(test3)); + // drop second view - metadata.dropView(SESSION, test2); + metadata.dropView(SESSION, test3); views = metadata.getViews(SESSION, new SchemaTablePrefix("test")); assertTrue(views.isEmpty()); diff --git a/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemorySmoke.java b/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemorySmoke.java index 35c31c556540e..4e0a436852913 100644 --- a/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemorySmoke.java +++ b/presto-memory/src/test/java/com/facebook/presto/plugin/memory/TestMemorySmoke.java @@ -185,6 +185,28 @@ public void testViews() assertQueryFails("DROP VIEW test_view", "line 1:1: View 'memory.default.test_view' does not exist"); } + @Test + public void testRenameView() + { + @Language("SQL") String query = "SELECT orderkey, orderstatus, totalprice / 2 half FROM orders"; + + assertUpdate("CREATE VIEW test_view_to_be_renamed AS " + query); + assertUpdate("ALTER VIEW test_view_to_be_renamed RENAME TO test_view_renamed"); + assertQuery("SELECT * FROM test_view_renamed", query); + + assertUpdate("CREATE SCHEMA test_different_schema"); + assertQueryFails("ALTER VIEW test_view_renamed RENAME TO test_different_schema.test_view_renamed", "line 1:1: View rename across schemas is not supported"); + assertUpdate("DROP VIEW test_view_renamed"); + assertUpdate("DROP SCHEMA test_different_schema"); + } + + @Test + public void testRenameViewIfNotExists() + { + assertQueryFails("ALTER VIEW test_rename_view_not_exist RENAME TO test_renamed_view_not_exist", "line 1:1: View 'memory.default.test_rename_view_not_exist' does not exist"); + assertQuerySucceeds("ALTER VIEW IF EXISTS test_rename_view_not_exist RENAME TO test_renamed_view_not_exist"); + } + private List listMemoryTables() { return getQueryRunner().listTables(getSession(), "memory", "default"); diff --git a/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 b/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 index c5b80d73eb2b3..e5ee6b44b9a40 100644 --- a/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 +++ b/presto-parser/src/main/antlr4/com/facebook/presto/sql/parser/SqlBase.g4 @@ -74,6 +74,8 @@ statement | type) #createType | CREATE (OR REPLACE)? VIEW qualifiedName (SECURITY (DEFINER | INVOKER))? AS query #createView + | ALTER VIEW (IF EXISTS)? from=qualifiedName + RENAME TO to=qualifiedName #renameView | DROP VIEW (IF EXISTS)? qualifiedName #dropView | CREATE MATERIALIZED VIEW (IF NOT EXISTS)? qualifiedName (COMMENT string)? diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java b/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java index ea7c192cc8592..bb853ba01a8e2 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/SqlFormatter.java @@ -82,6 +82,7 @@ import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameSchema; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Return; import com.facebook.presto.sql.tree.Revoke; @@ -689,6 +690,20 @@ protected Void visitExternalBodyReference(ExternalBodyReference node, Integer in return null; } + @Override + protected Void visitRenameView(RenameView node, Integer context) + { + builder.append("ALTER VIEW "); + if (node.isExists()) { + builder.append("IF EXISTS "); + } + builder.append(formatName(node.getSource())) + .append(" RENAME TO ") + .append(formatName(node.getTarget())); + + return null; + } + @Override protected Void visitDropView(DropView node, Integer context) { diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java b/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java index f916feed3ad76..7b1dd90a57f5e 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/parser/AstBuilder.java @@ -127,6 +127,7 @@ import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameSchema; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Return; import com.facebook.presto.sql.tree.Revoke; @@ -486,6 +487,12 @@ public Node visitRenameColumn(SqlBaseParser.RenameColumnContext context) context.EXISTS().stream().anyMatch(node -> node.getSymbol().getTokenIndex() > context.COLUMN().getSymbol().getTokenIndex())); } + @Override + public Node visitRenameView(SqlBaseParser.RenameViewContext context) + { + return new RenameView(getLocation(context), getQualifiedName(context.from), getQualifiedName(context.to), context.EXISTS() != null); + } + @Override public Node visitAnalyze(SqlBaseParser.AnalyzeContext context) { diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java index 8eec8cf4936fb..8f42707192a4a 100644 --- a/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/AstVisitor.java @@ -627,6 +627,11 @@ protected R visitCreateView(CreateView node, C context) return visitStatement(node, context); } + protected R visitRenameView(RenameView node, C context) + { + return visitStatement(node, context); + } + protected R visitDropView(DropView node, C context) { return visitStatement(node, context); diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/RenameView.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/RenameView.java new file mode 100644 index 0000000000000..f591d733ed858 --- /dev/null +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/RenameView.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 com.facebook.presto.sql.tree; + +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public final class RenameView + extends Statement +{ + private final QualifiedName source; + private final QualifiedName target; + + private final boolean exists; + + public RenameView(QualifiedName source, QualifiedName target, boolean exists) + { + this(Optional.empty(), source, target, exists); + } + + public RenameView(NodeLocation location, QualifiedName source, QualifiedName target, boolean exists) + { + this(Optional.of(location), source, target, exists); + } + + private RenameView(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.visitRenameView(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; + } + RenameView o = (RenameView) 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/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java index ce3715b8c296f..68b590834653d 100644 --- a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java +++ b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParser.java @@ -110,6 +110,7 @@ import com.facebook.presto.sql.tree.RenameColumn; import com.facebook.presto.sql.tree.RenameSchema; import com.facebook.presto.sql.tree.RenameTable; +import com.facebook.presto.sql.tree.RenameView; import com.facebook.presto.sql.tree.ResetSession; import com.facebook.presto.sql.tree.Return; import com.facebook.presto.sql.tree.Revoke; @@ -1505,6 +1506,13 @@ public void testTruncateTable() assertStatement("TRUNCATE TABLE a.b.c", new TruncateTable(QualifiedName.of("a", "b", "c"))); } + @Test + public void testRenameView() + { + assertStatement("ALTER VIEW a RENAME TO b", new RenameView(QualifiedName.of("a"), QualifiedName.of("b"), false)); + assertStatement("ALTER VIEW IF EXISTS a RENAME TO b", new RenameView(QualifiedName.of("a"), QualifiedName.of("b"), true)); + } + @Test public void testDropView() { diff --git a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AllowAllAccessControl.java b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AllowAllAccessControl.java index 38f94530bedd5..30e99555fe432 100644 --- a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AllowAllAccessControl.java +++ b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/AllowAllAccessControl.java @@ -131,6 +131,11 @@ public void checkCanCreateView(ConnectorTransactionHandle transaction, Connector { } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + } + @Override public void checkCanDropView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/FileBasedAccessControl.java b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/FileBasedAccessControl.java index d9d13ccdb314b..e1ee1b874815c 100644 --- a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/FileBasedAccessControl.java +++ b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/FileBasedAccessControl.java @@ -57,6 +57,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySelectTable; import static com.facebook.presto.spi.security.AccessDeniedException.denySetTableProperties; @@ -227,6 +228,14 @@ public void checkCanCreateView(ConnectorTransactionHandle transaction, Connector } } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + if (!checkTablePermission(identity, viewName, OWNERSHIP) || !checkTablePermission(identity, newViewName, OWNERSHIP)) { + denyRenameView(viewName.toString(), newViewName.toString()); + } + } + @Override public void checkCanDropView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingConnectorAccessControl.java b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingConnectorAccessControl.java index bb4f74134f024..dd28ee9183d56 100644 --- a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingConnectorAccessControl.java +++ b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingConnectorAccessControl.java @@ -167,6 +167,12 @@ public void checkCanCreateView(ConnectorTransactionHandle transactionHandle, Con delegate().checkCanCreateView(transactionHandle, identity, context, viewName); } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + delegate().checkCanRenameView(transactionHandle, identity, context, viewName, newViewName); + } + @Override public void checkCanDropView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingSystemAccessControl.java b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingSystemAccessControl.java index 0adec4a48aa32..6e9cd6a91c715 100644 --- a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingSystemAccessControl.java +++ b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ForwardingSystemAccessControl.java @@ -206,6 +206,12 @@ public void checkCanCreateView(Identity identity, AccessControlContext context, delegate().checkCanCreateView(identity, context, view); } + @Override + public void checkCanRenameView(Identity identity, AccessControlContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + delegate().checkCanRenameView(identity, context, view, newView); + } + @Override public void checkCanDropView(Identity identity, AccessControlContext context, CatalogSchemaTableName view) { diff --git a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ReadOnlyAccessControl.java b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ReadOnlyAccessControl.java index def67490ab690..30022c2079858 100644 --- a/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ReadOnlyAccessControl.java +++ b/presto-plugin-toolkit/src/main/java/com/facebook/presto/plugin/base/security/ReadOnlyAccessControl.java @@ -25,6 +25,7 @@ import java.util.Set; import static com.facebook.presto.spi.security.AccessDeniedException.denyGrantTablePrivilege; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denyTruncateTable; @@ -71,6 +72,12 @@ public void checkCanCreateViewWithSelectFromColumns(ConnectorTransactionHandle t // allow } + @Override + public void checkCanRenameView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + denyRenameView(viewName.toString(), newViewName.toString()); + } + @Override public void checkCanSetCatalogSessionProperty(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, String propertyName) { diff --git a/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java b/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java index 1c081887df656..daf64d750d421 100644 --- a/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java +++ b/presto-plugin-toolkit/src/test/java/com/facebook/presto/plugin/base/security/TestFileBasedAccessControl.java @@ -69,7 +69,12 @@ public void testTableRules() accessControl.checkCanDeleteFromTable(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable")); accessControl.checkCanSelectFromColumns(TRANSACTION_HANDLE, user("joe"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableSet.of()); accessControl.checkCanCreateViewWithSelectFromColumns(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableSet.of()); + accessControl.checkCanRenameTable(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), new SchemaTableName("aliceschema", "newbobtable")); + accessControl.checkCanRenameTable(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("aliceschema", "alicetable"), new SchemaTableName("aliceschema", "newalicetable")); + accessControl.checkCanRenameView(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("bobschema", "bobview"), new SchemaTableName("aliceschema", "newbobview")); + accessControl.checkCanRenameView(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("aliceschema", "aliceview"), new SchemaTableName("aliceschema", "newaliceview")); accessControl.checkCanDropTable(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("bobschema", "bobtable")); + assertDenied(() -> accessControl.checkCanRenameTable(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), new SchemaTableName("bobschema", "newbobtable"))); accessControl.checkCanSetTableProperties(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableMap.of()); accessControl.checkCanSetTableProperties(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("aliceSchema", "aliceTable"), ImmutableMap.of()); assertDenied(() -> accessControl.checkCanInsertIntoTable(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("bobschema", "bobtable"))); @@ -79,6 +84,8 @@ public void testTableRules() assertDenied(() -> accessControl.checkCanSelectFromColumns(TRANSACTION_HANDLE, user("admin"), CONTEXT, new SchemaTableName("secret", "secret"), ImmutableSet.of())); assertDenied(() -> accessControl.checkCanSelectFromColumns(TRANSACTION_HANDLE, user("joe"), CONTEXT, new SchemaTableName("secret", "secret"), ImmutableSet.of())); assertDenied(() -> accessControl.checkCanCreateViewWithSelectFromColumns(TRANSACTION_HANDLE, user("joe"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableSet.of())); + assertDenied(() -> accessControl.checkCanRenameView(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobview"), new SchemaTableName("bobschema", "newbobview"))); + assertDenied(() -> accessControl.checkCanRenameView(TRANSACTION_HANDLE, user("alice"), CONTEXT, new SchemaTableName("aliceschema", "alicetable"), new SchemaTableName("bobschema", "newalicetable"))); } @Test diff --git a/presto-plugin-toolkit/src/test/resources/table.json b/presto-plugin-toolkit/src/test/resources/table.json index 18b76320fc162..2ebef52bdcdb6 100644 --- a/presto-plugin-toolkit/src/test/resources/table.json +++ b/presto-plugin-toolkit/src/test/resources/table.json @@ -9,6 +9,11 @@ "schema": ".*", "privileges": ["SELECT", "INSERT", "DELETE", "OWNERSHIP"] }, + { + "user": "alice", + "schema": "aliceschema", + "privileges": ["SELECT", "INSERT", "DELETE", "OWNERSHIP"] + }, { "user": "bob", "schema": "bobschema", diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorAccessControl.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorAccessControl.java index 62f7208301ee1..e5621d458c0b0 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorAccessControl.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorAccessControl.java @@ -44,6 +44,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeRoles; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySelectColumns; @@ -270,6 +271,16 @@ default void checkCanCreateView(ConnectorTransactionHandle transactionHandle, Co denyCreateView(viewName.toString()); } + /** + * Check if identity is allowed to rename the specified view in this catalog. + * + * @throws com.facebook.presto.spi.security.AccessDeniedException if not allowed + */ + default void checkCanRenameView(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName, SchemaTableName newViewName) + { + denyRenameView(viewName.toString(), newViewName.toString()); + } + /** * Check if identity is allowed to drop the specified view in this catalog. * diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java index 06ebb4acbc12d..d31c12b68e23b 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/ConnectorMetadata.java @@ -561,6 +561,14 @@ default void createView(ConnectorSession session, ConnectorTableMetadata viewMet throw new PrestoException(NOT_SUPPORTED, "This connector does not support creating views"); } + /** + * Rename the specified view + */ + default void renameView(ConnectorSession session, SchemaTableName viewName, SchemaTableName newViewName) + { + throw new PrestoException(NOT_SUPPORTED, "This connector does not support renaming views"); + } + /** * Drop the specified view. */ diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java index f1e5407176141..fa1aeb780f691 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java @@ -474,6 +474,14 @@ public void createView(ConnectorSession session, ConnectorTableMetadata viewMeta } } + @Override + public void renameView(ConnectorSession session, SchemaTableName viewName, SchemaTableName newViewName) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.renameView(session, viewName, newViewName); + } + } + @Override public ColumnHandle getDeleteRowIdColumnHandle(ConnectorSession session, ConnectorTableHandle tableHandle) { diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessControl.java b/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessControl.java index c75f3ee58e248..131c0af1577d2 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessControl.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessControl.java @@ -193,6 +193,13 @@ default AuthorizedIdentity selectAuthorizedIdentity(Identity identity, AccessCon */ void checkCanCreateView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName); + /** + * Check if identity is allowed to rename the specified view. + * + * @throws com.facebook.presto.spi.security.AccessDeniedException if not allowed + */ + void checkCanRenameView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName); + /** * Check if identity is allowed to drop the specified view. * diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessDeniedException.java b/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessDeniedException.java index 1766e1b961998..5992554dd887f 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessDeniedException.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/security/AccessDeniedException.java @@ -251,6 +251,16 @@ public static void denyCreateViewWithSelect(String sourceName, ConnectorIdentity throw new AccessDeniedException(format("View owner '%s' cannot create view that selects from %s%s", identity.getUser(), sourceName, formatExtraInfo(extraInfo))); } + public static void denyRenameView(String viewName, String newViewName) + { + denyRenameView(viewName, newViewName, null); + } + + public static void denyRenameView(String viewName, String newViewName, String extraInfo) + { + throw new AccessDeniedException(format("Cannot rename view from %s to %s%s", viewName, newViewName, formatExtraInfo(extraInfo))); + } + public static void denyDropView(String viewName) { denyDropView(viewName, null); diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/security/AllowAllAccessControl.java b/presto-spi/src/main/java/com/facebook/presto/spi/security/AllowAllAccessControl.java index 70458ba83ef03..f07a4e71abc11 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/security/AllowAllAccessControl.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/security/AllowAllAccessControl.java @@ -145,6 +145,11 @@ public void checkCanCreateView(TransactionId transactionId, Identity identity, A { } + @Override + public void checkCanRenameView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + } + @Override public void checkCanDropView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/security/DenyAllAccessControl.java b/presto-spi/src/main/java/com/facebook/presto/spi/security/DenyAllAccessControl.java index 0fdbbe5576b61..72b4dbabfc843 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/security/DenyAllAccessControl.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/security/DenyAllAccessControl.java @@ -47,6 +47,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeRoles; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySelectColumns; @@ -207,6 +208,12 @@ public void checkCanCreateView(TransactionId transactionId, Identity identity, A denyCreateView(viewName.toString()); } + @Override + public void checkCanRenameView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName, QualifiedObjectName newViewName) + { + denyRenameView(viewName.toString(), newViewName.toString()); + } + @Override public void checkCanDropView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/security/SystemAccessControl.java b/presto-spi/src/main/java/com/facebook/presto/spi/security/SystemAccessControl.java index 7d209ce6b392d..fc5e8cbf6805f 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/security/SystemAccessControl.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/security/SystemAccessControl.java @@ -42,6 +42,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameSchema; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; +import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameView; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySelectColumns; import static com.facebook.presto.spi.security.AccessDeniedException.denySetCatalogSessionProperty; @@ -300,6 +301,16 @@ default void checkCanCreateView(Identity identity, AccessControlContext context, denyCreateView(view.toString()); } + /** + * Check if identity is allowed to rename the specified view in a catalog. + * + * @throws AccessDeniedException if not allowed + */ + default void checkCanRenameView(Identity identity, AccessControlContext context, CatalogSchemaTableName view, CatalogSchemaTableName newView) + { + denyRenameView(view.toString(), newView.toString()); + } + /** * Check if identity is allowed to drop the specified view in a catalog. *