From c34dad794fd33a60c928b32492323e51544ce319 Mon Sep 17 00:00:00 2001 From: Branimir Vujicic Date: Mon, 28 Mar 2022 09:19:26 +0200 Subject: [PATCH] Support TRUNCATE TABLE statement Cherry-pick of https://github.com/trinodb/trino/pull/8932 Co-authored-by: Yuya Ebihara --- .../presto/plugin/jdbc/BaseJdbcClient.java | 15 ++++ .../presto/plugin/jdbc/JdbcClient.java | 2 + .../plugin/blackhole/BlackHoleMetadata.java | 5 ++ presto-docs/src/main/sphinx/sql.rst | 1 + presto-docs/src/main/sphinx/sql/truncate.rst | 28 ++++++ .../hive/security/LegacyAccessControl.java | 5 ++ .../security/SqlStandardAccessControl.java | 10 +++ .../presto/execution/TruncateTableTask.java | 69 +++++++++++++++ .../facebook/presto/metadata/Metadata.java | 21 +++++ .../presto/metadata/MetadataManager.java | 8 ++ .../presto/security/AccessControl.java | 7 ++ .../presto/security/AccessControlManager.java | 16 ++++ .../security/AllowAllAccessControl.java | 5 ++ .../security/AllowAllSystemAccessControl.java | 5 ++ .../presto/security/DenyAllAccessControl.java | 7 ++ .../FileBasedSystemAccessControl.java | 9 ++ .../sql/analyzer/StatementAnalyzer.java | 7 ++ .../presto/testing/LocalQueryRunner.java | 3 + .../testing/TestingAccessControlManager.java | 15 +++- .../facebook/presto/util/StatementUtils.java | 2 + .../presto/metadata/AbstractMockMetadata.java | 6 ++ .../TestFileBasedSystemAccessControl.java | 5 ++ .../com/facebook/presto/sql/parser/SqlBase.g4 | 4 +- .../com/facebook/presto/sql/SqlFormatter.java | 10 +++ .../presto/sql/parser/AstBuilder.java | 7 ++ .../facebook/presto/sql/tree/AstVisitor.java | 5 ++ .../presto/sql/tree/TruncateTable.java | 88 +++++++++++++++++++ .../presto/sql/parser/TestSqlParser.java | 10 +++ .../parser/TestSqlParserErrorHandling.java | 4 +- .../sql/parser/TestStatementBuilder.java | 2 + .../base/security/AllowAllAccessControl.java | 5 ++ .../base/security/FileBasedAccessControl.java | 9 ++ .../ForwardingConnectorAccessControl.java | 6 ++ .../ForwardingSystemAccessControl.java | 6 ++ .../base/security/ReadOnlyAccessControl.java | 7 ++ .../security/TestFileBasedAccessControl.java | 9 ++ .../spi/connector/ConnectorAccessControl.java | 10 +++ .../spi/connector/ConnectorMetadata.java | 10 +++ .../ClassLoaderSafeConnectorMetadata.java | 8 ++ .../spi/security/AccessDeniedException.java | 10 +++ .../spi/security/SystemAccessControl.java | 10 +++ 41 files changed, 467 insertions(+), 4 deletions(-) create mode 100644 presto-docs/src/main/sphinx/sql/truncate.rst create mode 100644 presto-main/src/main/java/com/facebook/presto/execution/TruncateTableTask.java create mode 100644 presto-parser/src/main/java/com/facebook/presto/sql/tree/TruncateTable.java diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java index 379a85b178365..f13c404010f65 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/BaseJdbcClient.java @@ -545,6 +545,21 @@ public void dropTable(JdbcIdentity identity, JdbcTableHandle handle) } } + @Override + public void truncateTable(JdbcIdentity identity, JdbcTableHandle jdbcTableHandle) + { + StringBuilder sql = new StringBuilder() + .append("TRUNCATE TABLE ") + .append(quoted(jdbcTableHandle.getCatalogName(), jdbcTableHandle.getSchemaName(), jdbcTableHandle.getTableName())); + + try (Connection connection = connectionFactory.openConnection(identity)) { + execute(connection, sql.toString()); + } + catch (SQLException e) { + throw new PrestoException(JDBC_ERROR, e); + } + } + @Override public void rollbackCreateTable(JdbcIdentity identity, JdbcOutputTableHandle handle) { diff --git a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java index 4035ee3daeeed..3c5e08ed257c4 100644 --- a/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java +++ b/presto-base-jdbc/src/main/java/com/facebook/presto/plugin/jdbc/JdbcClient.java @@ -85,6 +85,8 @@ PreparedStatement buildSql(ConnectorSession session, Connection connection, Jdbc void dropTable(JdbcIdentity identity, JdbcTableHandle jdbcTableHandle); + void truncateTable(JdbcIdentity identity, JdbcTableHandle jdbcTableHandle); + void rollbackCreateTable(JdbcIdentity identity, JdbcOutputTableHandle handle); String buildInsertSql(JdbcOutputTableHandle handle); diff --git a/presto-blackhole/src/main/java/com/facebook/presto/plugin/blackhole/BlackHoleMetadata.java b/presto-blackhole/src/main/java/com/facebook/presto/plugin/blackhole/BlackHoleMetadata.java index ce2f837e883ae..51f0db43fc997 100644 --- a/presto-blackhole/src/main/java/com/facebook/presto/plugin/blackhole/BlackHoleMetadata.java +++ b/presto-blackhole/src/main/java/com/facebook/presto/plugin/blackhole/BlackHoleMetadata.java @@ -259,6 +259,11 @@ public List getTableLayouts( return ImmutableList.of(new ConnectorTableLayoutResult(getTableLayout(session, layoutHandle), constraint.getSummary())); } + @Override + public void truncateTable(ConnectorSession session, ConnectorTableHandle tableHandle) + { + } + @Override public ConnectorTableLayout getTableLayout(ConnectorSession session, ConnectorTableLayoutHandle handle) { diff --git a/presto-docs/src/main/sphinx/sql.rst b/presto-docs/src/main/sphinx/sql.rst index 470b2a4426e10..23fab43e6891c 100644 --- a/presto-docs/src/main/sphinx/sql.rst +++ b/presto-docs/src/main/sphinx/sql.rst @@ -57,5 +57,6 @@ This chapter describes the SQL syntax used in Presto. sql/show-stats sql/show-tables sql/start-transaction + sql/truncate sql/use sql/values diff --git a/presto-docs/src/main/sphinx/sql/truncate.rst b/presto-docs/src/main/sphinx/sql/truncate.rst new file mode 100644 index 0000000000000..f142d407627e8 --- /dev/null +++ b/presto-docs/src/main/sphinx/sql/truncate.rst @@ -0,0 +1,28 @@ +======== +TRUNCATE +======== + +Synopsis +-------- + +.. code-block:: none + + TRUNCATE TABLE table_name + +Description +----------- + +Delete all rows from a table. + +Examples +-------- + +Truncate the table ``orders``:: + + TRUNCATE TABLE orders; + +Limitations +----------- + +Some connectors have limited or no support for ``TRUNCATE``. +See connector documentation for more details. 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 f4a648885f907..e3820e8f03298 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 @@ -124,6 +124,11 @@ public void checkCanRenameTable(ConnectorTransactionHandle transaction, Connecto } } + @Override + public void checkCanTruncateTable(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + } + @Override public void checkCanShowTablesMetadata(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, String schemaName) { 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 1b0b093a8e0be..e1364e860afdd 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 @@ -72,6 +72,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denySetCatalogSessionProperty; import static com.facebook.presto.spi.security.AccessDeniedException.denySetRole; import static com.facebook.presto.spi.security.AccessDeniedException.denyShowRoles; +import static com.facebook.presto.spi.security.AccessDeniedException.denyTruncateTable; import static com.facebook.presto.spi.security.PrincipalType.ROLE; import static com.facebook.presto.spi.security.PrincipalType.USER; import static com.google.common.collect.ImmutableSet.toImmutableSet; @@ -229,6 +230,15 @@ public void checkCanDeleteFromTable(ConnectorTransactionHandle transaction, Conn } } + @Override + public void checkCanTruncateTable(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + MetastoreContext metastoreContext = new MetastoreContext(identity, context.getQueryId().getId(), context.getClientInfo(), context.getSource(), Optional.empty(), false, HiveColumnConverterProvider.DEFAULT_COLUMN_CONVERTER_PROVIDER); + if (!checkTablePermission(transaction, identity, metastoreContext, tableName, DELETE, false)) { + denyTruncateTable(tableName.toString()); + } + } + @Override public void checkCanCreateView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { diff --git a/presto-main/src/main/java/com/facebook/presto/execution/TruncateTableTask.java b/presto-main/src/main/java/com/facebook/presto/execution/TruncateTableTask.java new file mode 100644 index 0000000000000..7446ff170843a --- /dev/null +++ b/presto-main/src/main/java/com/facebook/presto/execution/TruncateTableTask.java @@ -0,0 +1,69 @@ +/* + * 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.security.AccessControl; +import com.facebook.presto.spi.TableHandle; +import com.facebook.presto.spi.WarningCollector; +import com.facebook.presto.sql.analyzer.SemanticException; +import com.facebook.presto.sql.tree.Expression; +import com.facebook.presto.sql.tree.TruncateTable; +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_TABLE; +import static com.facebook.presto.sql.analyzer.SemanticErrorCode.NOT_SUPPORTED; +import static com.google.common.util.concurrent.Futures.immediateFuture; + +public class TruncateTableTask + implements DDLDefinitionTask +{ + @Override + public String getName() + { + return "TRUNCATE TABLE"; + } + + @Override + public ListenableFuture execute(TruncateTable statement, TransactionManager transactionManager, Metadata metadata, AccessControl accessControl, Session session, List parameters, WarningCollector warningCollector) + { + QualifiedObjectName tableName = createQualifiedObjectName(session, statement, statement.getTableName()); + + if (metadata.isMaterializedView(session, tableName)) { + throw new SemanticException(NOT_SUPPORTED, statement, "Cannot truncate a materialized view"); + } + + if (metadata.isView(session, tableName)) { + throw new SemanticException(NOT_SUPPORTED, statement, "Cannot truncate a view"); + } + + Optional tableHandle = metadata.getTableHandle(session, tableName); + if (!tableHandle.isPresent()) { + throw new SemanticException(MISSING_TABLE, statement, "Table '%s' does not exist", tableName); + } + + accessControl.checkCanTruncateTable(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), tableName); + + metadata.truncateTable(session, tableHandle.get()); + + return immediateFuture(null); + } +} 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 98c51abeab000..573490c3fcc4e 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 @@ -233,6 +233,11 @@ public interface Metadata */ void dropTable(Session session, TableHandle tableHandle); + /** + * Truncates the specified table + */ + void truncateTable(Session session, TableHandle tableHandle); + Optional getNewTableLayout(Session session, String catalogName, ConnectorTableMetadata tableMetadata); @Experimental @@ -348,6 +353,14 @@ public interface Metadata */ Optional getView(Session session, QualifiedObjectName viewName); + /** + * Is the specified table a view. + */ + default boolean isView(Session session, QualifiedObjectName viewName) + { + return getView(session, viewName).isPresent(); + } + /** * Creates the specified view with the specified view definition. */ @@ -363,6 +376,14 @@ public interface Metadata */ Optional getMaterializedView(Session session, QualifiedObjectName viewName); + /** + * Is the specified table a materialized view. + */ + default boolean isMaterializedView(Session session, QualifiedObjectName viewName) + { + return getMaterializedView(session, viewName).isPresent(); + } + /** * Creates the specified materialized view with the specified view definition. */ 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 41fcf14ff6849..2a9ec20bdb057 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 @@ -725,6 +725,14 @@ public void dropTable(Session session, TableHandle tableHandle) metadata.dropTable(session.toConnectorSession(connectorId), tableHandle.getConnectorHandle()); } + @Override + public void truncateTable(Session session, TableHandle tableHandle) + { + ConnectorId connectorId = tableHandle.getConnectorId(); + ConnectorMetadata metadata = getMetadataForWrite(session, connectorId); + metadata.truncateTable(session.toConnectorSession(connectorId), tableHandle.getConnectorHandle()); + } + @Override public Optional getInsertLayout(Session session, TableHandle table) { diff --git a/presto-main/src/main/java/com/facebook/presto/security/AccessControl.java b/presto-main/src/main/java/com/facebook/presto/security/AccessControl.java index db4ead879bd90..0ba0638df186a 100644 --- a/presto-main/src/main/java/com/facebook/presto/security/AccessControl.java +++ b/presto-main/src/main/java/com/facebook/presto/security/AccessControl.java @@ -161,6 +161,13 @@ public interface AccessControl */ void checkCanDeleteFromTable(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName tableName); + /** + * Check if identity is allowed to truncate the specified table. + * + * @throws com.facebook.presto.spi.security.AccessDeniedException if not allowed + */ + void checkCanTruncateTable(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName tableName); + /** * Check if identity is allowed to create the specified view. * 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 9086bdbe04e36..4eba7d4249fd9 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 @@ -440,6 +440,22 @@ public void checkCanDeleteFromTable(TransactionId transactionId, Identity identi } } + @Override + public void checkCanTruncateTable(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName tableName) + { + requireNonNull(identity, "identity is null"); + requireNonNull(tableName, "tableName is null"); + + authenticationCheck(() -> checkCanAccessCatalog(identity, context, tableName.getCatalogName())); + + authorizationCheck(() -> systemAccessControl.get().checkCanTruncateTable(identity, context, toCatalogSchemaTableName(tableName))); + + CatalogAccessControlEntry entry = getConnectorAccessControl(transactionId, tableName.getCatalogName()); + if (entry != null) { + authorizationCheck(() -> entry.getAccessControl().checkCanTruncateTable(entry.getTransactionHandle(transactionId), identity.toConnectorIdentity(tableName.getCatalogName()), context, toSchemaTableName(tableName))); + } + } + @Override public void checkCanCreateView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { diff --git a/presto-main/src/main/java/com/facebook/presto/security/AllowAllAccessControl.java b/presto-main/src/main/java/com/facebook/presto/security/AllowAllAccessControl.java index 9b07298cc942e..1c4fd2ee30454 100644 --- a/presto-main/src/main/java/com/facebook/presto/security/AllowAllAccessControl.java +++ b/presto-main/src/main/java/com/facebook/presto/security/AllowAllAccessControl.java @@ -128,6 +128,11 @@ public void checkCanDeleteFromTable(TransactionId transactionId, Identity identi { } + @Override + public void checkCanTruncateTable(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName tableName) + { + } + @Override public void checkCanCreateView(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 e36e95b007a92..40971facc04a7 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 @@ -164,6 +164,11 @@ public void checkCanDeleteFromTable(Identity identity, AccessControlContext cont { } + @Override + public void checkCanTruncateTable(Identity identity, AccessControlContext context, CatalogSchemaTableName table) + { + } + @Override public void checkCanCreateView(Identity identity, AccessControlContext context, CatalogSchemaTableName view) { diff --git a/presto-main/src/main/java/com/facebook/presto/security/DenyAllAccessControl.java b/presto-main/src/main/java/com/facebook/presto/security/DenyAllAccessControl.java index 87db09b7abc5d..d27bedd9f3e8b 100644 --- a/presto-main/src/main/java/com/facebook/presto/security/DenyAllAccessControl.java +++ b/presto-main/src/main/java/com/facebook/presto/security/DenyAllAccessControl.java @@ -60,6 +60,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyShowRoles; import static com.facebook.presto.spi.security.AccessDeniedException.denyShowSchemas; import static com.facebook.presto.spi.security.AccessDeniedException.denyShowTablesMetadata; +import static com.facebook.presto.spi.security.AccessDeniedException.denyTruncateTable; import static com.google.common.collect.ImmutableSet.toImmutableSet; public class DenyAllAccessControl @@ -179,6 +180,12 @@ public void checkCanDeleteFromTable(TransactionId transactionId, Identity identi denyDeleteTable(tableName.toString()); } + @Override + public void checkCanTruncateTable(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName tableName) + { + denyTruncateTable(tableName.toString()); + } + @Override public void checkCanCreateView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { 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 ddff9a855309c..1b097369c431d 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 @@ -63,6 +63,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; import static com.facebook.presto.spi.security.AccessDeniedException.denySetUser; +import static com.facebook.presto.spi.security.AccessDeniedException.denyTruncateTable; import static com.google.common.base.Preconditions.checkState; import static com.google.common.base.Suppliers.memoizeWithExpiration; import static java.lang.String.format; @@ -276,6 +277,14 @@ public void checkCanDropTable(Identity identity, AccessControlContext context, C } } + @Override + public void checkCanTruncateTable(Identity identity, AccessControlContext context, CatalogSchemaTableName table) + { + if (!canAccessCatalog(identity, table.getCatalogName(), ALL)) { + denyTruncateTable(table.toString()); + } + } + @Override public void checkCanRenameTable(Identity identity, AccessControlContext context, CatalogSchemaTableName table, CatalogSchemaTableName newTable) { 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 cf1997eebde6e..b9436ddc005aa 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 @@ -140,6 +140,7 @@ import com.facebook.presto.sql.tree.Statement; import com.facebook.presto.sql.tree.Table; import com.facebook.presto.sql.tree.TableSubquery; +import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.sql.tree.Union; import com.facebook.presto.sql.tree.Unnest; import com.facebook.presto.sql.tree.Use; @@ -904,6 +905,12 @@ protected Scope visitProperty(Property node, Optional scope) return createAndAssignScope(node, scope); } + @Override + protected Scope visitTruncateTable(TruncateTable node, Optional scope) + { + return createAndAssignScope(node, scope); + } + @Override protected Scope visitDropTable(DropTable 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 ab9fa7541948b..21f412b5e360c 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 @@ -72,6 +72,7 @@ import com.facebook.presto.execution.StartTransactionTask; import com.facebook.presto.execution.TaskManagerConfig; import com.facebook.presto.execution.TaskSource; +import com.facebook.presto.execution.TruncateTableTask; import com.facebook.presto.execution.resourceGroups.NoOpResourceGroupManager; import com.facebook.presto.execution.scheduler.LegacyNetworkTopology; import com.facebook.presto.execution.scheduler.NodeScheduler; @@ -191,6 +192,7 @@ import com.facebook.presto.sql.tree.SetSession; import com.facebook.presto.sql.tree.StartTransaction; import com.facebook.presto.sql.tree.Statement; +import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.testing.PageConsumerOperator.PageConsumerOutputFactory; import com.facebook.presto.tracing.TracingConfig; import com.facebook.presto.transaction.InMemoryTransactionManager; @@ -499,6 +501,7 @@ private LocalQueryRunner(Session defaultSession, FeaturesConfig featuresConfig, .put(DropFunction.class, new DropFunctionTask(sqlParser)) .put(DropTable.class, new DropTableTask()) .put(DropView.class, new DropViewTask()) + .put(TruncateTable.class, new TruncateTableTask()) .put(DropMaterializedView.class, new DropMaterializedViewTask()) .put(RenameColumn.class, new RenameColumnTask()) .put(RenameTable.class, new RenameTableTask()) 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 1c0667a6b79ee..332bc32bcced1 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 @@ -51,6 +51,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denySetCatalogSessionProperty; import static com.facebook.presto.spi.security.AccessDeniedException.denySetSystemSessionProperty; import static com.facebook.presto.spi.security.AccessDeniedException.denySetUser; +import static com.facebook.presto.spi.security.AccessDeniedException.denyTruncateTable; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.ADD_COLUMN; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.CREATE_SCHEMA; import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.CREATE_TABLE; @@ -68,6 +69,7 @@ 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_USER; +import static com.facebook.presto.testing.TestingAccessControlManager.TestingPrivilegeType.TRUNCATE_TABLE; import static com.google.common.base.MoreObjects.toStringHelper; import static com.google.common.collect.ImmutableSet.toImmutableSet; import static java.util.Objects.requireNonNull; @@ -230,6 +232,17 @@ public void checkCanDeleteFromTable(TransactionId transactionId, Identity identi } } + @Override + public void checkCanTruncateTable(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName tableName) + { + if (shouldDenyPrivilege(identity.getUser(), tableName.getObjectName(), TRUNCATE_TABLE)) { + denyTruncateTable(tableName.toString()); + } + if (denyPrivileges.isEmpty()) { + super.checkCanDeleteFromTable(transactionId, identity, context, tableName); + } + } + @Override public void checkCanCreateView(TransactionId transactionId, Identity identity, AccessControlContext context, QualifiedObjectName viewName) { @@ -317,7 +330,7 @@ public enum TestingPrivilegeType { SET_USER, CREATE_SCHEMA, DROP_SCHEMA, RENAME_SCHEMA, - CREATE_TABLE, DROP_TABLE, RENAME_TABLE, INSERT_TABLE, DELETE_TABLE, + CREATE_TABLE, DROP_TABLE, RENAME_TABLE, INSERT_TABLE, DELETE_TABLE, TRUNCATE_TABLE, ADD_COLUMN, DROP_COLUMN, RENAME_COLUMN, SELECT_COLUMN, CREATE_VIEW, DROP_VIEW, CREATE_VIEW_WITH_SELECT_COLUMNS, SET_SESSION diff --git a/presto-main/src/main/java/com/facebook/presto/util/StatementUtils.java b/presto-main/src/main/java/com/facebook/presto/util/StatementUtils.java index cb6502e6f263e..fddf64920dfc9 100644 --- a/presto-main/src/main/java/com/facebook/presto/util/StatementUtils.java +++ b/presto-main/src/main/java/com/facebook/presto/util/StatementUtils.java @@ -68,6 +68,7 @@ import com.facebook.presto.sql.tree.ShowTables; import com.facebook.presto.sql.tree.StartTransaction; import com.facebook.presto.sql.tree.Statement; +import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.sql.tree.Use; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -124,6 +125,7 @@ private StatementUtils() {} builder.put(DropColumn.class, QueryType.DATA_DEFINITION); builder.put(DropTable.class, QueryType.DATA_DEFINITION); builder.put(CreateView.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); builder.put(DropMaterializedView.class, QueryType.DATA_DEFINITION); 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 7a942b3aa167b..e3cc119d08bd9 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 @@ -252,6 +252,12 @@ public void dropTable(Session session, TableHandle tableHandle) throw new UnsupportedOperationException(); } + @Override + public void truncateTable(Session session, TableHandle tableHandle) + { + throw new UnsupportedOperationException(); + } + @Override public Optional getNewTableLayout(Session session, String catalogName, ConnectorTableMetadata tableMetadata) { diff --git a/presto-main/src/test/java/com/facebook/presto/security/TestFileBasedSystemAccessControl.java b/presto-main/src/test/java/com/facebook/presto/security/TestFileBasedSystemAccessControl.java index f0084ceb7e519..4611a70d1d0b3 100644 --- a/presto-main/src/test/java/com/facebook/presto/security/TestFileBasedSystemAccessControl.java +++ b/presto-main/src/test/java/com/facebook/presto/security/TestFileBasedSystemAccessControl.java @@ -334,6 +334,7 @@ public void testTableOperations() accessControlManager.checkCanCreateTable(transactionId, alice, context, aliceTable); accessControlManager.checkCanDropTable(transactionId, alice, context, aliceTable); + accessControlManager.checkCanTruncateTable(transactionId, alice, context, aliceTable); accessControlManager.checkCanSelectFromColumns(transactionId, alice, context, aliceTable, ImmutableSet.of()); accessControlManager.checkCanInsertIntoTable(transactionId, alice, context, aliceTable); accessControlManager.checkCanDeleteFromTable(transactionId, alice, context, aliceTable); @@ -368,6 +369,10 @@ public void testTableOperationsReadOnly() accessControlManager.checkCanDropTable(transactionId, alice, context, aliceTable); })); + assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> { + accessControlManager.checkCanTruncateTable(transactionId, alice, context, aliceTable); + })); + assertThrows(AccessDeniedException.class, () -> transaction(transactionManager, accessControlManager).execute(transactionId -> { accessControlManager.checkCanInsertIntoTable(transactionId, alice, context, aliceTable); })); 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 029c67ab71091..9f429660d8975 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 @@ -49,6 +49,7 @@ statement | DROP TABLE (IF EXISTS)? qualifiedName #dropTable | INSERT INTO qualifiedName columnAliases? query #insertInto | DELETE FROM qualifiedName (WHERE booleanExpression)? #delete + | TRUNCATE TABLE qualifiedName #truncateTable | ALTER TABLE (IF EXISTS)? from=qualifiedName RENAME TO to=qualifiedName #renameTable | ALTER TABLE (IF EXISTS)? tableName=qualifiedName @@ -576,7 +577,7 @@ nonReserved | RANGE | READ | REFRESH | RENAME | REPEATABLE | REPLACE | RESET | RESPECT | RESTRICT | RETURN | RETURNS | REVOKE | ROLE | ROLES | ROLLBACK | ROW | ROWS | SCHEMA | SCHEMAS | SECOND | SECURITY | SERIALIZABLE | SESSION | SET | SETS | SQL | SHOW | SOME | START | STATS | SUBSTRING | SYSTEM - | TABLES | TABLESAMPLE | TEMPORARY | TEXT | TIME | TIMESTAMP | TO | TRANSACTION | TRY_CAST | TYPE + | TABLES | TABLESAMPLE | TEMPORARY | TEXT | TIME | TIMESTAMP | TO | TRANSACTION | TRUNCATE | TRY_CAST | TYPE | UNBOUNDED | UNCOMMITTED | USE | USER | VALIDATE | VERBOSE | VIEW | WORK | WRITE @@ -766,6 +767,7 @@ TIMESTAMP: 'TIMESTAMP'; TO: 'TO'; TRANSACTION: 'TRANSACTION'; TRUE: 'TRUE'; +TRUNCATE: 'TRUNCATE'; TRY_CAST: 'TRY_CAST'; TYPE: 'TYPE'; UESCAPE: 'UESCAPE'; 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 d677404ba132a..ccad31c0db7a0 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 @@ -109,6 +109,7 @@ import com.facebook.presto.sql.tree.TableSubquery; import com.facebook.presto.sql.tree.TransactionAccessMode; import com.facebook.presto.sql.tree.TransactionMode; +import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.sql.tree.Union; import com.facebook.presto.sql.tree.Unnest; import com.facebook.presto.sql.tree.Use; @@ -897,6 +898,15 @@ protected Void visitDelete(Delete node, Integer context) return null; } + @Override + protected Void visitTruncateTable(TruncateTable node, Integer indent) + { + builder.append("TRUNCATE TABLE "); + builder.append(formatName(node.getTableName())); + + return null; + } + @Override protected Void visitCreateSchema(CreateSchema 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 e7d51af5a1604..7368d0e0d6053 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 @@ -168,6 +168,7 @@ import com.facebook.presto.sql.tree.TimestampLiteral; import com.facebook.presto.sql.tree.TransactionAccessMode; import com.facebook.presto.sql.tree.TransactionMode; +import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.sql.tree.TryExpression; import com.facebook.presto.sql.tree.Union; import com.facebook.presto.sql.tree.Unnest; @@ -413,6 +414,12 @@ public Node visitDelete(SqlBaseParser.DeleteContext context) visitIfPresent(context.booleanExpression(), Expression.class)); } + @Override + public Node visitTruncateTable(SqlBaseParser.TruncateTableContext context) + { + return new TruncateTable(getLocation(context), getQualifiedName(context.qualifiedName())); + } + @Override public Node visitRenameTable(SqlBaseParser.RenameTableContext 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 1fe582042fafc..d57546c449220 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 @@ -652,6 +652,11 @@ protected R visitDelete(Delete node, C context) return visitStatement(node, context); } + protected R visitTruncateTable(TruncateTable node, C context) + { + return visitStatement(node, context); + } + protected R visitStartTransaction(StartTransaction node, C context) { return visitStatement(node, context); diff --git a/presto-parser/src/main/java/com/facebook/presto/sql/tree/TruncateTable.java b/presto-parser/src/main/java/com/facebook/presto/sql/tree/TruncateTable.java new file mode 100644 index 0000000000000..6b61acfa5ecac --- /dev/null +++ b/presto-parser/src/main/java/com/facebook/presto/sql/tree/TruncateTable.java @@ -0,0 +1,88 @@ +/* + * 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; + +public class TruncateTable + extends Statement +{ + private final QualifiedName tableName; + + public TruncateTable(QualifiedName tableName) + { + this(Optional.empty(), tableName); + } + + public TruncateTable(NodeLocation location, QualifiedName tableName) + { + this(Optional.of(location), tableName); + } + + private TruncateTable(Optional location, QualifiedName tableName) + { + super(location); + this.tableName = tableName; + } + + public QualifiedName getTableName() + { + return tableName; + } + + @Override + public R accept(AstVisitor visitor, C context) + { + return visitor.visitTruncateTable(this, context); + } + + @Override + public List getChildren() + { + return ImmutableList.of(); + } + + @Override + public int hashCode() + { + return Objects.hash(tableName); + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) { + return true; + } + if ((obj == null) || (getClass() != obj.getClass())) { + return false; + } + TruncateTable o = (TruncateTable) obj; + return Objects.equals(tableName, o.tableName); + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("tableName", tableName) + .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 ef659818d3ed5..722c981f84b3c 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 @@ -142,6 +142,7 @@ import com.facebook.presto.sql.tree.TimeLiteral; import com.facebook.presto.sql.tree.TimestampLiteral; import com.facebook.presto.sql.tree.TransactionAccessMode; +import com.facebook.presto.sql.tree.TruncateTable; import com.facebook.presto.sql.tree.Union; import com.facebook.presto.sql.tree.Unnest; import com.facebook.presto.sql.tree.Use; @@ -1461,6 +1462,15 @@ public void testDropTable() assertStatement("DROP TABLE IF EXISTS a.b.c", new DropTable(QualifiedName.of("a", "b", "c"), true)); } + @Test + public void testTruncateTable() + throws Exception + { + assertStatement("TRUNCATE TABLE a", new TruncateTable(QualifiedName.of("a"))); + assertStatement("TRUNCATE TABLE a.b", new TruncateTable(QualifiedName.of("a", "b"))); + assertStatement("TRUNCATE TABLE a.b.c", new TruncateTable(QualifiedName.of("a", "b", "c"))); + } + @Test public void testDropView() { diff --git a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java index 9f42892f234da..637a6176e5228 100644 --- a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java +++ b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestSqlParserErrorHandling.java @@ -40,10 +40,10 @@ public Object[][] getStatements() return new Object[][] { {"", "line 1:1: mismatched input ''. Expecting: 'ALTER', 'ANALYZE', 'CALL', 'COMMIT', 'CREATE', 'DEALLOCATE', 'DELETE', 'DESC', 'DESCRIBE', 'DROP', 'EXECUTE', 'EXPLAIN', 'GRANT', " + - "'INSERT', 'PREPARE', 'REFRESH', 'RESET', 'REVOKE', 'ROLLBACK', 'SET', 'SHOW', 'START', 'USE', "}, + "'INSERT', 'PREPARE', 'REFRESH', 'RESET', 'REVOKE', 'ROLLBACK', 'SET', 'SHOW', 'START', 'TRUNCATE', 'USE', "}, {"@select", "line 1:1: mismatched input '@'. Expecting: 'ALTER', 'ANALYZE', 'CALL', 'COMMIT', 'CREATE', 'DEALLOCATE', 'DELETE', 'DESC', 'DESCRIBE', 'DROP', 'EXECUTE', 'EXPLAIN', 'GRANT', " + - "'INSERT', 'PREPARE', 'REFRESH', 'RESET', 'REVOKE', 'ROLLBACK', 'SET', 'SHOW', 'START', 'USE', "}, + "'INSERT', 'PREPARE', 'REFRESH', 'RESET', 'REVOKE', 'ROLLBACK', 'SET', 'SHOW', 'START', 'TRUNCATE', 'USE', "}, {"select * from foo where @what", "line 1:25: mismatched input '@'. Expecting: "}, {"select * from 'oops", diff --git a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java index b5c1fea0f4532..ade7f5a17bf09 100644 --- a/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java +++ b/presto-parser/src/test/java/com/facebook/presto/sql/parser/TestStatementBuilder.java @@ -156,6 +156,8 @@ public void testStatementBuilder() printStatement("delete from foo"); printStatement("delete from foo where a = b"); + printStatement("truncate table foo"); + printStatement("values ('a', 1, 2.2), ('b', 2, 3.3)"); printStatement("table foo"); 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 4eb959ebac879..ea682b17af422 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 @@ -110,6 +110,11 @@ public void checkCanDeleteFromTable(ConnectorTransactionHandle transaction, Conn { } + @Override + public void checkCanTruncateTable(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + } + @Override public void checkCanCreateView(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 3946ef7c0acf8..84bc4f819f8a1 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 @@ -55,6 +55,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; 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.denyTruncateTable; public class FileBasedAccessControl implements ConnectorAccessControl @@ -188,6 +189,14 @@ public void checkCanDeleteFromTable(ConnectorTransactionHandle transaction, Conn } } + @Override + public void checkCanTruncateTable(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + if (!checkTablePermission(identity, tableName, DELETE)) { + denyTruncateTable(tableName.toString()); + } + } + @Override public void checkCanCreateView(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 694ce4344bfb6..736c7d328cbcc 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 @@ -142,6 +142,12 @@ public void checkCanDeleteFromTable(ConnectorTransactionHandle transactionHandle delegate().checkCanDeleteFromTable(transactionHandle, identity, context, tableName); } + @Override + public void checkCanTruncateTable(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + delegate().checkCanTruncateTable(transactionHandle, identity, context, tableName); + } + @Override public void checkCanCreateView(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 36f5a3b0bbbcd..be73a09f6d982 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 @@ -173,6 +173,12 @@ public void checkCanDeleteFromTable(Identity identity, AccessControlContext cont delegate().checkCanDeleteFromTable(identity, context, table); } + @Override + public void checkCanTruncateTable(Identity identity, AccessControlContext context, CatalogSchemaTableName table) + { + delegate().checkCanTruncateTable(identity, context, table); + } + @Override public void checkCanCreateView(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 b0715cea349f6..ce64b1df71235 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 @@ -36,6 +36,7 @@ import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameColumn; import static com.facebook.presto.spi.security.AccessDeniedException.denyRenameTable; import static com.facebook.presto.spi.security.AccessDeniedException.denyRevokeTablePrivilege; +import static com.facebook.presto.spi.security.AccessDeniedException.denyTruncateTable; public class ReadOnlyAccessControl implements ConnectorAccessControl @@ -116,6 +117,12 @@ public void checkCanDeleteFromTable(ConnectorTransactionHandle transaction, Conn denyDeleteTable(tableName.toString()); } + @Override + public void checkCanTruncateTable(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + denyTruncateTable(tableName.toString()); + } + @Override public void checkCanCreateView(ConnectorTransactionHandle transaction, ConnectorIdentity identity, AccessControlContext context, SchemaTableName viewName) { 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 42b35fa4ff27b..c89a750e57e97 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 @@ -70,6 +70,15 @@ public void testTableRules() assertDenied(() -> accessControl.checkCanCreateViewWithSelectFromColumns(TRANSACTION_HANDLE, user("joe"), CONTEXT, new SchemaTableName("bobschema", "bobtable"), ImmutableSet.of())); } + @Test + public void testTableRulesForCheckCanTruncateTable() + throws IOException + { + ConnectorAccessControl accessControl = createAccessControl("table.json"); + accessControl.checkCanTruncateTable(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "bobtable")); + assertDenied(() -> accessControl.checkCanTruncateTable(TRANSACTION_HANDLE, user("bob"), CONTEXT, new SchemaTableName("bobschema", "test"))); + } + @Test public void testSessionPropertyRules() throws IOException 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 79a783cf47412..497768a58e538 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 @@ -225,6 +225,16 @@ default void checkCanDeleteFromTable(ConnectorTransactionHandle transactionHandl denyDeleteTable(tableName.toString()); } + /** + * Check if identity is allowed to truncate the specified table in this catalog. + * + * @throws com.facebook.presto.spi.security.AccessDeniedException if not allowed + */ + default void checkCanTruncateTable(ConnectorTransactionHandle transactionHandle, ConnectorIdentity identity, AccessControlContext context, SchemaTableName tableName) + { + denyDeleteTable(tableName.toString()); + } + /** * Check if identity is allowed to create 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 55b44dcd9f545..d14b2e185fdc2 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 @@ -323,6 +323,16 @@ default void dropTable(ConnectorSession session, ConnectorTableHandle tableHandl throw new PrestoException(NOT_SUPPORTED, "This connector does not support dropping tables"); } + /** + * Truncates the specified table + * + * @throws RuntimeException if the table cannot be truncated or table handle is no longer valid + */ + default void truncateTable(ConnectorSession session, ConnectorTableHandle tableHandle) + { + throw new PrestoException(NOT_SUPPORTED, "This connector does not support truncating tables"); + } + /** * Rename the specified table */ 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 25db749bc16b9..abfb3ba414098 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 @@ -373,6 +373,14 @@ public void dropTable(ConnectorSession session, ConnectorTableHandle tableHandle } } + @Override + public void truncateTable(ConnectorSession session, ConnectorTableHandle tableHandle) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + delegate.truncateTable(session, tableHandle); + } + } + @Override public void renameColumn(ConnectorSession session, ConnectorTableHandle tableHandle, ColumnHandle source, String target) { 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 bdf220546edc9..d1bbf60fdacac 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 @@ -196,6 +196,16 @@ public static void denyDeleteTable(String tableName, String extraInfo) throw new AccessDeniedException(format("Cannot delete from table %s%s", tableName, formatExtraInfo(extraInfo))); } + public static void denyTruncateTable(String tableName) + { + denyTruncateTable(tableName, null); + } + + public static void denyTruncateTable(String tableName, String extraInfo) + { + throw new AccessDeniedException(format("Cannot truncate table %s%s", tableName, formatExtraInfo(extraInfo))); + } + public static void denyCreateView(String viewName) { denyCreateView(viewName, null); 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 12b1999a417a5..c52f7d08a3819 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 @@ -249,6 +249,16 @@ default void checkCanDeleteFromTable(Identity identity, AccessControlContext con denyDeleteTable(table.toString()); } + /** + * Check if identity is allowed to truncate the specified table in a catalog. + * + * @throws com.facebook.presto.spi.security.AccessDeniedException if not allowed + */ + default void checkCanTruncateTable(Identity identity, AccessControlContext context, CatalogSchemaTableName table) + { + denyDeleteTable(table.toString()); + } + /** * Check if identity is allowed to create the specified view in a catalog. *