From 95f56ff56365d21fb3fcf40dc318e5f954a13da8 Mon Sep 17 00:00:00 2001 From: Michiel De Smet Date: Fri, 25 Nov 2022 21:12:34 +0100 Subject: [PATCH 1/3] Use `randomNameSuffix` instead of local method --- .../glue/TestDeltaLakeGlueMetastore.java | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java index f5d4fab2ae48..ffdf9566ea8c 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeGlueMetastore.java @@ -61,7 +61,6 @@ import java.util.Map; import java.util.Optional; import java.util.Set; -import java.util.UUID; import java.util.function.Consumer; import static com.google.common.base.Verify.verify; @@ -80,8 +79,8 @@ import static io.trino.plugin.hive.metastore.StorageFormat.fromHiveStorageFormat; import static io.trino.spi.security.PrincipalType.ROLE; import static io.trino.testing.TestingConnectorSession.SESSION; +import static io.trino.testing.TestingNames.randomNameSuffix; import static java.lang.String.format; -import static java.util.Locale.ENGLISH; import static org.apache.hadoop.hive.metastore.TableType.EXTERNAL_TABLE; import static org.apache.hadoop.hive.metastore.TableType.VIRTUAL_VIEW; import static org.assertj.core.api.Assertions.assertThat; @@ -144,7 +143,7 @@ public void setUp() .collect(toImmutableList())) .build(); - databaseName = "test_delta_glue" + randomName(); + databaseName = "test_delta_glue" + randomNameSuffix(); metastoreClient.createDatabase(Database.builder() .setDatabaseName(databaseName) .setOwnerName(Optional.of("public")) @@ -175,10 +174,10 @@ public void tearDown() public void testHideNonDeltaLakeTable() throws Exception { - SchemaTableName deltaLakeTable = new SchemaTableName(databaseName, "delta_lake_table_" + randomName()); - SchemaTableName nonDeltaLakeTable1 = new SchemaTableName(databaseName, "hive_table_" + randomName()); - SchemaTableName nonDeltaLakeTable2 = new SchemaTableName(databaseName, "hive_table_" + randomName()); - SchemaTableName nonDeltaLakeView1 = new SchemaTableName(databaseName, "hive_view_" + randomName()); + SchemaTableName deltaLakeTable = new SchemaTableName(databaseName, "delta_lake_table_" + randomNameSuffix()); + SchemaTableName nonDeltaLakeTable1 = new SchemaTableName(databaseName, "hive_table_" + randomNameSuffix()); + SchemaTableName nonDeltaLakeTable2 = new SchemaTableName(databaseName, "hive_table_" + randomNameSuffix()); + SchemaTableName nonDeltaLakeView1 = new SchemaTableName(databaseName, "hive_view_" + randomNameSuffix()); String deltaLakeTableLocation = tableLocation(deltaLakeTable); createTable(deltaLakeTable, deltaLakeTableLocation, tableBuilder -> { @@ -313,9 +312,4 @@ private void createView(SchemaTableName viewName, String tableLocation, Consumer PrincipalPrivileges principalPrivileges = new PrincipalPrivileges(ImmutableMultimap.of(), ImmutableMultimap.of()); metastoreClient.createTable(table.build(), principalPrivileges); } - - private static String randomName() - { - return UUID.randomUUID().toString().toLowerCase(ENGLISH).replace("-", ""); - } } From b803b4711e829b996e6335a656b996c4144b8600 Mon Sep 17 00:00:00 2001 From: Michiel De Smet Date: Fri, 28 Oct 2022 21:15:54 +0200 Subject: [PATCH 2/3] Extract Trino View functionality --- .../plugin/hive/TrinoViewHiveMetastore.java | 176 ++++++++++++++++++ .../io/trino/plugin/hive/TrinoViewUtil.java | 86 +++++++++ .../metastore/glue/GlueHiveMetastore.java | 12 +- .../iceberg/catalog/AbstractTrinoCatalog.java | 64 +------ .../catalog/glue/TrinoGlueCatalog.java | 24 ++- .../iceberg/catalog/hms/TrinoHiveCatalog.java | 87 ++------- .../catalog/hms/TrinoHiveCatalogFactory.java | 8 +- .../iceberg/TestIcebergMergeAppend.java | 7 +- .../TestIcebergOrcMetricsCollection.java | 7 +- .../iceberg/TestIcebergSplitSource.java | 7 +- .../trino/plugin/iceberg/TestIcebergV2.java | 7 +- ...TestTrinoHiveCatalogWithFileMetastore.java | 7 +- ...TestTrinoHiveCatalogWithHiveMetastore.java | 7 +- 13 files changed, 338 insertions(+), 161 deletions(-) create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java create mode 100644 plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java new file mode 100644 index 000000000000..08ac71168857 --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewHiveMetastore.java @@ -0,0 +1,176 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.hive; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import io.trino.plugin.hive.metastore.Column; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.PrincipalPrivileges; +import io.trino.spi.TrinoException; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorViewDefinition; +import io.trino.spi.connector.SchemaTableName; +import io.trino.spi.connector.TableNotFoundException; +import io.trino.spi.connector.ViewNotFoundException; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Stream; + +import static com.google.common.collect.ImmutableList.toImmutableList; +import static io.trino.plugin.hive.HiveMetadata.PRESTO_VIEW_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.PRESTO_VIEW_EXPANDED_TEXT_MARKER; +import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveType.HIVE_STRING; +import static io.trino.plugin.hive.TrinoViewUtil.createViewProperties; +import static io.trino.plugin.hive.ViewReaderUtil.encodeViewData; +import static io.trino.plugin.hive.ViewReaderUtil.isPrestoView; +import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; +import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; +import static io.trino.plugin.hive.metastore.StorageFormat.VIEW_STORAGE_FORMAT; +import static io.trino.plugin.hive.util.HiveUtil.isHiveSystemSchema; +import static io.trino.spi.StandardErrorCode.TABLE_NOT_FOUND; +import static java.util.Objects.requireNonNull; +import static org.apache.hadoop.hive.metastore.TableType.VIRTUAL_VIEW; + +public final class TrinoViewHiveMetastore +{ + private final boolean isUsingSystemSecurity; + private final HiveMetastore metastore; + private final String trinoVersion; + private final String connectorName; + + public TrinoViewHiveMetastore(HiveMetastore metastore, boolean isUsingSystemSecurity, String trinoVersion, String connectorName) + { + this.metastore = requireNonNull(metastore, "metastore is null"); + this.isUsingSystemSecurity = isUsingSystemSecurity; + this.trinoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); + this.connectorName = requireNonNull(connectorName, "connectorName is null"); + } + + public void createView(ConnectorSession session, SchemaTableName schemaViewName, ConnectorViewDefinition definition, boolean replace) + { + if (isUsingSystemSecurity) { + definition = definition.withoutOwner(); + } + + io.trino.plugin.hive.metastore.Table.Builder tableBuilder = io.trino.plugin.hive.metastore.Table.builder() + .setDatabaseName(schemaViewName.getSchemaName()) + .setTableName(schemaViewName.getTableName()) + .setOwner(isUsingSystemSecurity ? Optional.empty() : Optional.of(session.getUser())) + .setTableType(VIRTUAL_VIEW.name()) + .setDataColumns(ImmutableList.of(new Column("dummy", HIVE_STRING, Optional.empty()))) + .setPartitionColumns(ImmutableList.of()) + .setParameters(createViewProperties(session, trinoVersion, connectorName)) + .setViewOriginalText(Optional.of(encodeViewData(definition))) + .setViewExpandedText(Optional.of(PRESTO_VIEW_EXPANDED_TEXT_MARKER)); + + tableBuilder.getStorageBuilder() + .setStorageFormat(VIEW_STORAGE_FORMAT) + .setLocation(""); + io.trino.plugin.hive.metastore.Table table = tableBuilder.build(); + PrincipalPrivileges principalPrivileges = isUsingSystemSecurity ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser()); + + Optional existing = metastore.getTable(schemaViewName.getSchemaName(), schemaViewName.getTableName()); + if (existing.isPresent()) { + if (!replace || !isPrestoView(existing.get())) { + throw new ViewAlreadyExistsException(schemaViewName); + } + + metastore.replaceTable(schemaViewName.getSchemaName(), schemaViewName.getTableName(), table, principalPrivileges); + return; + } + + try { + metastore.createTable(table, principalPrivileges); + } + catch (TableAlreadyExistsException e) { + throw new ViewAlreadyExistsException(e.getTableName()); + } + } + + public void dropView(SchemaTableName schemaViewName) + { + if (getView(schemaViewName).isEmpty()) { + throw new ViewNotFoundException(schemaViewName); + } + + try { + metastore.dropTable(schemaViewName.getSchemaName(), schemaViewName.getTableName(), true); + } + catch (TableNotFoundException e) { + throw new ViewNotFoundException(e.getTableName()); + } + } + + public List listViews(Optional database) + { + return listDatabases(database).stream() + .flatMap(this::listViews) + .collect(toImmutableList()); + } + + private List listDatabases(Optional database) + { + if (database.isPresent()) { + if (isHiveSystemSchema(database.get())) { + return ImmutableList.of(); + } + return ImmutableList.of(database.get()); + } + return metastore.getAllDatabases(); + } + + public Map getViews(Optional schemaName) + { + ImmutableMap.Builder views = ImmutableMap.builder(); + for (SchemaTableName name : listViews(schemaName)) { + try { + getView(name).ifPresent(view -> views.put(name, view)); + } + catch (TrinoException e) { + if (e.getErrorCode().equals(TABLE_NOT_FOUND.toErrorCode())) { + // Ignore view that was dropped during query execution (race condition) + } + else { + throw e; + } + } + } + return views.buildOrThrow(); + } + + private Stream listViews(String schema) + { + // Filter on PRESTO_VIEW_COMMENT to distinguish from materialized views + return metastore.getTablesWithParameter(schema, TABLE_COMMENT, PRESTO_VIEW_COMMENT).stream() + .map(table -> new SchemaTableName(schema, table)); + } + + public Optional getView(SchemaTableName viewName) + { + if (isHiveSystemSchema(viewName.getSchemaName())) { + return Optional.empty(); + } + return metastore.getTable(viewName.getSchemaName(), viewName.getTableName()) + .flatMap(view -> TrinoViewUtil.getView( + viewName, + view.getViewOriginalText(), + view.getTableType(), + view.getParameters(), + view.getOwner())); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java new file mode 100644 index 000000000000..189b2caa354d --- /dev/null +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/TrinoViewUtil.java @@ -0,0 +1,86 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.hive; + +import com.google.common.collect.ImmutableMap; +import io.trino.spi.connector.ConnectorSession; +import io.trino.spi.connector.ConnectorViewDefinition; +import io.trino.spi.connector.SchemaTableName; + +import java.util.Map; +import java.util.Optional; + +import static com.google.common.base.Preconditions.checkArgument; +import static io.trino.plugin.hive.HiveMetadata.PRESTO_QUERY_ID_NAME; +import static io.trino.plugin.hive.HiveMetadata.PRESTO_VERSION_NAME; +import static io.trino.plugin.hive.HiveMetadata.PRESTO_VIEW_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; +import static io.trino.plugin.hive.HiveMetadata.TRINO_CREATED_BY; +import static io.trino.plugin.hive.ViewReaderUtil.PRESTO_VIEW_FLAG; +import static io.trino.plugin.hive.ViewReaderUtil.isHiveOrPrestoView; +import static io.trino.plugin.hive.ViewReaderUtil.isPrestoView; + +public final class TrinoViewUtil +{ + private TrinoViewUtil() {} + + public static Optional getView( + SchemaTableName viewName, + Optional viewOriginalText, + String tableType, + Map tableParameters, + Optional tableOwner) + { + if (!isView(tableType, tableParameters)) { + // Filter out Tables and Materialized Views + return Optional.empty(); + } + + if (!isPrestoView(tableParameters)) { + // Hive views are not compatible + throw new HiveViewNotSupportedException(viewName); + } + + checkArgument(viewOriginalText.isPresent(), "viewOriginalText must be present"); + ConnectorViewDefinition definition = ViewReaderUtil.PrestoViewReader.decodeViewData(viewOriginalText.get()); + // use owner from table metadata if it exists + if (tableOwner.isPresent() && !definition.isRunAsInvoker()) { + definition = new ConnectorViewDefinition( + definition.getOriginalSql(), + definition.getCatalog(), + definition.getSchema(), + definition.getColumns(), + definition.getComment(), + tableOwner, + false); + } + return Optional.of(definition); + } + + private static boolean isView(String tableType, Map tableParameters) + { + return isHiveOrPrestoView(tableType) && PRESTO_VIEW_COMMENT.equals(tableParameters.get(TABLE_COMMENT)); + } + + public static Map createViewProperties(ConnectorSession session, String trinoVersion, String connectorName) + { + return ImmutableMap.builder() + .put(PRESTO_VIEW_FLAG, "true") + .put(TRINO_CREATED_BY, connectorName) + .put(PRESTO_VERSION_NAME, trinoVersion) + .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) + .put(TABLE_COMMENT, PRESTO_VIEW_COMMENT) + .buildOrThrow(); + } +} diff --git a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java index e446641f9e86..580cf6cc423f 100644 --- a/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java +++ b/plugin/trino-hive/src/main/java/io/trino/plugin/hive/metastore/glue/GlueHiveMetastore.java @@ -126,6 +126,7 @@ import java.util.function.Function; import java.util.function.Predicate; +import static com.google.common.base.MoreObjects.firstNonNull; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Strings.isNullOrEmpty; import static com.google.common.base.Verify.verify; @@ -168,6 +169,7 @@ public class GlueHiveMetastore private static final int BATCH_UPDATE_PARTITION_MAX_PAGE_SIZE = 100; private static final int AWS_GLUE_GET_PARTITIONS_MAX_RESULTS = 1000; private static final Comparator> PARTITION_VALUE_COMPARATOR = lexicographical(String.CASE_INSENSITIVE_ORDER); + private static final Predicate VIEWS_FILTER = table -> VIRTUAL_VIEW.name().equals(table.getTableType()); private final HdfsEnvironment hdfsEnvironment; private final HdfsContext hdfsContext; @@ -441,12 +443,16 @@ public List getAllTables(String databaseName) @Override public synchronized List getTablesWithParameter(String databaseName, String parameterKey, String parameterValue) { - // TODO - throw new UnsupportedOperationException("getTablesWithParameter for GlueHiveMetastore is not implemented"); + return getAllViews(databaseName, table -> parameterValue.equals(firstNonNull(table.getParameters(), ImmutableMap.of()).get(parameterKey))); } @Override public List getAllViews(String databaseName) + { + return getAllViews(databaseName, table -> true); + } + + private List getAllViews(String databaseName, Predicate additionalFilter) { try { List views = getPaginatedResults( @@ -458,7 +464,7 @@ public List getAllViews(String databaseName) stats.getGetTables()) .map(GetTablesResult::getTableList) .flatMap(List::stream) - .filter(table -> VIRTUAL_VIEW.name().equals(table.getTableType())) + .filter(VIEWS_FILTER.and(additionalFilter)) .map(com.amazonaws.services.glue.model.Table::getName) .collect(toImmutableList()); return views; diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java index fd4bfbde91c9..78387a4518bf 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/AbstractTrinoCatalog.java @@ -17,8 +17,6 @@ import io.trino.filesystem.TrinoFileSystem; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.HiveMetadata; -import io.trino.plugin.hive.HiveViewNotSupportedException; -import io.trino.plugin.hive.ViewReaderUtil; import io.trino.plugin.iceberg.ColumnIdentity; import io.trino.plugin.iceberg.IcebergMaterializedViewDefinition; import io.trino.plugin.iceberg.IcebergUtil; @@ -48,15 +46,12 @@ import java.util.Map; import java.util.Optional; -import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Throwables.throwIfUnchecked; import static com.google.common.collect.ImmutableList.toImmutableList; import static io.trino.plugin.hive.HiveMetadata.STORAGE_TABLE; import static io.trino.plugin.hive.HiveMetadata.TABLE_COMMENT; import static io.trino.plugin.hive.ViewReaderUtil.ICEBERG_MATERIALIZED_VIEW_COMMENT; import static io.trino.plugin.hive.ViewReaderUtil.PRESTO_VIEW_FLAG; -import static io.trino.plugin.hive.ViewReaderUtil.isHiveOrPrestoView; -import static io.trino.plugin.hive.ViewReaderUtil.isPrestoView; import static io.trino.plugin.iceberg.IcebergErrorCode.ICEBERG_FILESYSTEM_ERROR; import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.STORAGE_SCHEMA; import static io.trino.plugin.iceberg.IcebergMaterializedViewAdditionalProperties.getStorageSchema; @@ -74,31 +69,24 @@ public abstract class AbstractTrinoCatalog implements TrinoCatalog { - // Be compatible with views defined by the Hive connector, which can be useful under certain conditions. + public static final String TRINO_CREATED_BY_VALUE = "Trino Iceberg connector"; protected static final String TRINO_CREATED_BY = HiveMetadata.TRINO_CREATED_BY; - protected static final String TRINO_CREATED_BY_VALUE = "Trino Iceberg connector"; - protected static final String PRESTO_VIEW_COMMENT = HiveMetadata.PRESTO_VIEW_COMMENT; - protected static final String PRESTO_VERSION_NAME = HiveMetadata.PRESTO_VERSION_NAME; protected static final String PRESTO_QUERY_ID_NAME = HiveMetadata.PRESTO_QUERY_ID_NAME; - protected static final String PRESTO_VIEW_EXPANDED_TEXT_MARKER = HiveMetadata.PRESTO_VIEW_EXPANDED_TEXT_MARKER; private final CatalogName catalogName; private final TypeManager typeManager; protected final IcebergTableOperationsProvider tableOperationsProvider; - private final String trinoVersion; private final boolean useUniqueTableLocation; protected AbstractTrinoCatalog( CatalogName catalogName, TypeManager typeManager, IcebergTableOperationsProvider tableOperationsProvider, - String trinoVersion, boolean useUniqueTableLocation) { this.catalogName = requireNonNull(catalogName, "catalogName is null"); this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.tableOperationsProvider = requireNonNull(tableOperationsProvider, "tableOperationsProvider is null"); - this.trinoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); this.useUniqueTableLocation = useUniqueTableLocation; } @@ -199,56 +187,6 @@ protected void deleteTableDirectory(TrinoFileSystem fileSystem, SchemaTableName } } - protected Optional getView( - SchemaTableName viewName, - Optional viewOriginalText, - String tableType, - Map tableParameters, - Optional tableOwner) - { - if (!isView(tableType, tableParameters)) { - // Filter out Tables and Materialized Views - return Optional.empty(); - } - - if (!isPrestoView(tableParameters)) { - // Hive views are not compatible - throw new HiveViewNotSupportedException(viewName); - } - - checkArgument(viewOriginalText.isPresent(), "viewOriginalText must be present"); - ConnectorViewDefinition definition = ViewReaderUtil.PrestoViewReader.decodeViewData(viewOriginalText.get()); - // use owner from table metadata if it exists - if (tableOwner.isPresent() && !definition.isRunAsInvoker()) { - definition = new ConnectorViewDefinition( - definition.getOriginalSql(), - definition.getCatalog(), - definition.getSchema(), - definition.getColumns(), - definition.getComment(), - tableOwner, - false); - } - return Optional.of(definition); - } - - private static boolean isView(String tableType, Map tableParameters) - - { - return isHiveOrPrestoView(tableType) && PRESTO_VIEW_COMMENT.equals(tableParameters.get(TABLE_COMMENT)); - } - - protected Map createViewProperties(ConnectorSession session) - { - return ImmutableMap.builder() - .put(PRESTO_VIEW_FLAG, "true") // Ensures compatibility with views created by the Hive connector - .put(TRINO_CREATED_BY, TRINO_CREATED_BY_VALUE) - .put(PRESTO_VERSION_NAME, trinoVersion) - .put(PRESTO_QUERY_ID_NAME, session.getQueryId()) - .put(TABLE_COMMENT, PRESTO_VIEW_COMMENT) - .buildOrThrow(); - } - protected SchemaTableName createMaterializedViewStorageTable(ConnectorSession session, SchemaTableName viewName, ConnectorMaterializedViewDefinition definition) { // Generate a storage table name and create a storage table. The properties in the definition are table properties for the diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java index 94a5b4c4142c..b023a42b3b44 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/glue/TrinoGlueCatalog.java @@ -38,6 +38,7 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.SchemaAlreadyExistsException; +import io.trino.plugin.hive.TrinoViewUtil; import io.trino.plugin.hive.ViewAlreadyExistsException; import io.trino.plugin.hive.metastore.glue.GlueMetastoreStats; import io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog; @@ -83,6 +84,7 @@ import static io.trino.plugin.hive.HiveErrorCode.HIVE_DATABASE_LOCATION_ERROR; import static io.trino.plugin.hive.HiveErrorCode.HIVE_METASTORE_ERROR; import static io.trino.plugin.hive.HiveMetadata.STORAGE_TABLE; +import static io.trino.plugin.hive.TrinoViewUtil.createViewProperties; import static io.trino.plugin.hive.ViewReaderUtil.encodeViewData; import static io.trino.plugin.hive.ViewReaderUtil.isPrestoView; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; @@ -119,6 +121,7 @@ public class TrinoGlueCatalog { private static final Logger LOG = Logger.get(TrinoGlueCatalog.class); + private final String trinoVersion; private final TrinoFileSystemFactory fileSystemFactory; private final Optional defaultSchemaLocation; private final AWSGlueAsync glueClient; @@ -139,7 +142,8 @@ public TrinoGlueCatalog( Optional defaultSchemaLocation, boolean useUniqueTableLocation) { - super(catalogName, typeManager, tableOperationsProvider, trinoVersion, useUniqueTableLocation); + super(catalogName, typeManager, tableOperationsProvider, useUniqueTableLocation); + this.trinoVersion = requireNonNull(trinoVersion, "trinoVersion is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.glueClient = requireNonNull(glueClient, "glueClient is null"); this.stats = requireNonNull(stats, "stats is null"); @@ -460,7 +464,7 @@ else if (isPrestoView(parameters) && !viewCache.containsKey(schemaTableName)) { } try { - getView(schemaTableName, + TrinoViewUtil.getView(schemaTableName, Optional.ofNullable(table.getViewOriginalText()), table.getTableType(), parameters, @@ -537,7 +541,11 @@ public void setTablePrincipal(ConnectorSession session, SchemaTableName schemaTa public void createView(ConnectorSession session, SchemaTableName schemaViewName, ConnectorViewDefinition definition, boolean replace) { // If a view is created between listing the existing view and calling createTable, retry - TableInput viewTableInput = getViewTableInput(schemaViewName.getTableName(), encodeViewData(definition), session.getUser(), createViewProperties(session)); + TableInput viewTableInput = getViewTableInput( + schemaViewName.getTableName(), + encodeViewData(definition), + session.getUser(), + createViewProperties(session, trinoVersion, TRINO_CREATED_BY_VALUE)); Failsafe.with(new RetryPolicy<>() .withMaxRetries(3) .withDelay(Duration.ofMillis(100)) @@ -584,7 +592,7 @@ public void renameView(ConnectorSession session, SchemaTableName source, SchemaT target.getTableName(), existingView.getViewOriginalText(), existingView.getOwner(), - createViewProperties(session)); + createViewProperties(session, trinoVersion, TRINO_CREATED_BY_VALUE)); CreateTableRequest createTableRequest = new CreateTableRequest() .withDatabaseName(target.getSchemaName()) .withTableInput(viewTableInput); @@ -678,7 +686,7 @@ public Optional getView(ConnectorSession session, Schem return Optional.empty(); } com.amazonaws.services.glue.model.Table viewDefinition = table.get(); - return getView( + return TrinoViewUtil.getView( viewName, Optional.ofNullable(viewDefinition.getViewOriginalText()), viewDefinition.getTableType(), @@ -724,7 +732,11 @@ public void updateViewColumnComment(ConnectorSession session, SchemaTableName vi private void updateView(ConnectorSession session, SchemaTableName viewName, ConnectorViewDefinition newDefinition) { - TableInput viewTableInput = getViewTableInput(viewName.getTableName(), encodeViewData(newDefinition), session.getUser(), createViewProperties(session)); + TableInput viewTableInput = getViewTableInput( + viewName.getTableName(), + encodeViewData(newDefinition), + session.getUser(), + createViewProperties(session, trinoVersion, TRINO_CREATED_BY_VALUE)); try { stats.getUpdateTable().call(() -> diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java index eaf6e969b543..45de0751e5f9 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalog.java @@ -19,8 +19,8 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.HiveSchemaProperties; -import io.trino.plugin.hive.TableAlreadyExistsException; -import io.trino.plugin.hive.ViewAlreadyExistsException; +import io.trino.plugin.hive.TrinoViewHiveMetastore; +import io.trino.plugin.hive.TrinoViewUtil; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; import io.trino.plugin.hive.metastore.HivePrincipal; @@ -58,7 +58,6 @@ import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import java.util.stream.Stream; import static com.google.common.base.Preconditions.checkState; import static com.google.common.collect.ImmutableList.toImmutableList; @@ -70,7 +69,6 @@ import static io.trino.plugin.hive.ViewReaderUtil.ICEBERG_MATERIALIZED_VIEW_COMMENT; import static io.trino.plugin.hive.ViewReaderUtil.encodeViewData; import static io.trino.plugin.hive.ViewReaderUtil.isHiveOrPrestoView; -import static io.trino.plugin.hive.ViewReaderUtil.isPrestoView; import static io.trino.plugin.hive.ViewReaderUtil.isTrinoMaterializedView; import static io.trino.plugin.hive.metastore.MetastoreUtil.buildInitialPrivilegeSet; import static io.trino.plugin.hive.metastore.PrincipalPrivileges.NO_PRIVILEGES; @@ -109,6 +107,7 @@ public class TrinoHiveCatalog public static final String DEPENDS_ON_TABLES = "dependsOnTables"; private final CachingHiveMetastore metastore; + private final TrinoViewHiveMetastore trinoViewHiveMetastore; private final TrinoFileSystemFactory fileSystemFactory; private final boolean isUsingSystemSecurity; private final boolean deleteSchemaLocationsFallback; @@ -118,16 +117,17 @@ public class TrinoHiveCatalog public TrinoHiveCatalog( CatalogName catalogName, CachingHiveMetastore metastore, + TrinoViewHiveMetastore trinoViewHiveMetastore, TrinoFileSystemFactory fileSystemFactory, TypeManager typeManager, IcebergTableOperationsProvider tableOperationsProvider, - String trinoVersion, boolean useUniqueTableLocation, boolean isUsingSystemSecurity, boolean deleteSchemaLocationsFallback) { - super(catalogName, typeManager, tableOperationsProvider, trinoVersion, useUniqueTableLocation); + super(catalogName, typeManager, tableOperationsProvider, useUniqueTableLocation); this.metastore = requireNonNull(metastore, "metastore is null"); + this.trinoViewHiveMetastore = requireNonNull(trinoViewHiveMetastore, "trinoViewHiveMetastore is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); this.isUsingSystemSecurity = isUsingSystemSecurity; this.deleteSchemaLocationsFallback = deleteSchemaLocationsFallback; @@ -348,7 +348,7 @@ public void updateViewComment(ConnectorSession session, SchemaTableName viewName io.trino.plugin.hive.metastore.Table view = metastore.getTable(viewName.getSchemaName(), viewName.getTableName()) .orElseThrow(() -> new ViewNotFoundException(viewName)); - ConnectorViewDefinition definition = getView(viewName, view.getViewOriginalText(), view.getTableType(), view.getParameters(), view.getOwner()) + ConnectorViewDefinition definition = TrinoViewUtil.getView(viewName, view.getViewOriginalText(), view.getTableType(), view.getParameters(), view.getOwner()) .orElseThrow(() -> new ViewNotFoundException(viewName)); ConnectorViewDefinition newDefinition = new ConnectorViewDefinition( definition.getOriginalSql(), @@ -368,7 +368,7 @@ public void updateViewColumnComment(ConnectorSession session, SchemaTableName vi io.trino.plugin.hive.metastore.Table view = metastore.getTable(viewName.getSchemaName(), viewName.getTableName()) .orElseThrow(() -> new ViewNotFoundException(viewName)); - ConnectorViewDefinition definition = getView(viewName, view.getViewOriginalText(), view.getTableType(), view.getParameters(), view.getOwner()) + ConnectorViewDefinition definition = TrinoViewUtil.getView(viewName, view.getViewOriginalText(), view.getTableType(), view.getParameters(), view.getOwner()) .orElseThrow(() -> new ViewNotFoundException(viewName)); ConnectorViewDefinition newDefinition = new ConnectorViewDefinition( definition.getOriginalSql(), @@ -421,43 +421,7 @@ public void setTablePrincipal(ConnectorSession session, SchemaTableName schemaTa @Override public void createView(ConnectorSession session, SchemaTableName schemaViewName, ConnectorViewDefinition definition, boolean replace) { - if (isUsingSystemSecurity) { - definition = definition.withoutOwner(); - } - - io.trino.plugin.hive.metastore.Table.Builder tableBuilder = io.trino.plugin.hive.metastore.Table.builder() - .setDatabaseName(schemaViewName.getSchemaName()) - .setTableName(schemaViewName.getTableName()) - .setOwner(isUsingSystemSecurity ? Optional.empty() : Optional.of(session.getUser())) - .setTableType(org.apache.hadoop.hive.metastore.TableType.VIRTUAL_VIEW.name()) - .setDataColumns(ImmutableList.of(new Column("dummy", HIVE_STRING, Optional.empty()))) - .setPartitionColumns(ImmutableList.of()) - .setParameters(createViewProperties(session)) - .setViewOriginalText(Optional.of(encodeViewData(definition))) - .setViewExpandedText(Optional.of(PRESTO_VIEW_EXPANDED_TEXT_MARKER)); - - tableBuilder.getStorageBuilder() - .setStorageFormat(VIEW_STORAGE_FORMAT) - .setLocation(""); - io.trino.plugin.hive.metastore.Table table = tableBuilder.build(); - PrincipalPrivileges principalPrivileges = isUsingSystemSecurity ? NO_PRIVILEGES : buildInitialPrivilegeSet(session.getUser()); - - Optional existing = metastore.getTable(schemaViewName.getSchemaName(), schemaViewName.getTableName()); - if (existing.isPresent()) { - if (!replace || !isPrestoView(existing.get())) { - throw new ViewAlreadyExistsException(schemaViewName); - } - - metastore.replaceTable(schemaViewName.getSchemaName(), schemaViewName.getTableName(), table, principalPrivileges); - return; - } - - try { - metastore.createTable(table, principalPrivileges); - } - catch (TableAlreadyExistsException e) { - throw new ViewAlreadyExistsException(e.getTableName()); - } + trinoViewHiveMetastore.createView(session, schemaViewName, definition, replace); } @Override @@ -477,46 +441,19 @@ public void setViewPrincipal(ConnectorSession session, SchemaTableName schemaVie @Override public void dropView(ConnectorSession session, SchemaTableName schemaViewName) { - if (getView(session, schemaViewName).isEmpty()) { - throw new ViewNotFoundException(schemaViewName); - } - - try { - metastore.dropTable(schemaViewName.getSchemaName(), schemaViewName.getTableName(), true); - } - catch (TableNotFoundException e) { - throw new ViewNotFoundException(e.getTableName()); - } + trinoViewHiveMetastore.dropView(schemaViewName); } @Override public List listViews(ConnectorSession session, Optional namespace) { - return listNamespaces(session, namespace).stream() - .flatMap(this::listViews) - .collect(toImmutableList()); - } - - private Stream listViews(String schema) - { - // Filter on PRESTO_VIEW_COMMENT to distinguish from materialized views - return metastore.getTablesWithParameter(schema, TABLE_COMMENT, PRESTO_VIEW_COMMENT).stream() - .map(table -> new SchemaTableName(schema, table)); + return trinoViewHiveMetastore.listViews(namespace); } @Override public Optional getView(ConnectorSession session, SchemaTableName viewName) { - if (isHiveSystemSchema(viewName.getSchemaName())) { - return Optional.empty(); - } - return metastore.getTable(viewName.getSchemaName(), viewName.getTableName()) - .flatMap(view -> getView( - viewName, - view.getViewOriginalText(), - view.getTableType(), - view.getParameters(), - view.getOwner())); + return trinoViewHiveMetastore.getView(viewName); } @Override diff --git a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java index 162ebe1bcf33..c86f633f6704 100644 --- a/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java +++ b/plugin/trino-iceberg/src/main/java/io/trino/plugin/iceberg/catalog/hms/TrinoHiveCatalogFactory.java @@ -16,7 +16,9 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.IcebergConfig; import io.trino.plugin.iceberg.IcebergSecurityConfig; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; @@ -31,6 +33,7 @@ import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; import static io.trino.plugin.iceberg.IcebergSecurityConfig.IcebergSecurity.SYSTEM; +import static io.trino.plugin.iceberg.catalog.AbstractTrinoCatalog.TRINO_CREATED_BY_VALUE; import static java.util.Objects.requireNonNull; public class TrinoHiveCatalogFactory @@ -71,13 +74,14 @@ public TrinoHiveCatalogFactory( @Override public TrinoCatalog create(ConnectorIdentity identity) { + CachingHiveMetastore metastore = memoizeMetastore(metastoreFactory.createMetastore(Optional.of(identity)), 1000); return new TrinoHiveCatalog( catalogName, - memoizeMetastore(metastoreFactory.createMetastore(Optional.of(identity)), 1000), + metastore, + new TrinoViewHiveMetastore(metastore, isUsingSystemSecurity, trinoVersion, TRINO_CREATED_BY_VALUE), fileSystemFactory, typeManager, tableOperationsProvider, - trinoVersion, isUniqueTableLocation, isUsingSystemSecurity, deleteSchemaLocationsFallback); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java index 74a35a398f56..db6f73eb386a 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergMergeAppend.java @@ -17,7 +17,9 @@ import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.file.FileHiveMetastore; import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; @@ -61,13 +63,14 @@ protected QueryRunner createQueryRunner() throws Exception .setMetastoreUser("test")); TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(HDFS_ENVIRONMENT); tableOperationsProvider = new FileMetastoreTableOperationsProvider(fileSystemFactory); + CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); trinoCatalog = new TrinoHiveCatalog( new CatalogName("catalog"), - memoizeMetastore(metastore, 1000), + cachingHiveMetastore, + new TrinoViewHiveMetastore(cachingHiveMetastore, false, "trino-version", "test"), fileSystemFactory, new TestingTypeManager(), tableOperationsProvider, - "trino-version", false, false, false); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java index a7256aed5846..063fc4404db2 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergOrcMetricsCollection.java @@ -18,8 +18,10 @@ import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.plugin.base.CatalogName; import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreConfig; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.file.FileHiveMetastore; import io.trino.plugin.hive.metastore.file.FileHiveMetastoreConfig; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; @@ -91,13 +93,14 @@ protected QueryRunner createQueryRunner() .setMetastoreUser("test")); TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(HDFS_ENVIRONMENT); tableOperationsProvider = new FileMetastoreTableOperationsProvider(fileSystemFactory); + CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); trinoCatalog = new TrinoHiveCatalog( new CatalogName("catalog"), - memoizeMetastore(metastore, 1000), + cachingHiveMetastore, + new TrinoViewHiveMetastore(cachingHiveMetastore, false, "trino-version", "test"), fileSystemFactory, new TestingTypeManager(), tableOperationsProvider, - "trino-version", false, false, false); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java index ca8b9d1134b9..bb8c9651cd30 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergSplitSource.java @@ -21,7 +21,9 @@ import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.hdfs.HdfsContext; import io.trino.plugin.base.CatalogName; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.file.FileMetastoreTableOperationsProvider; import io.trino.plugin.iceberg.catalog.hms.TrinoHiveCatalog; @@ -84,13 +86,14 @@ protected QueryRunner createQueryRunner() this.metastoreDir = new File(tempDir, "iceberg_data"); HiveMetastore metastore = createTestingFileHiveMetastore(metastoreDir); TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(HDFS_ENVIRONMENT); + CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); this.catalog = new TrinoHiveCatalog( new CatalogName("hive"), - memoizeMetastore(metastore, 1000), + cachingHiveMetastore, + new TrinoViewHiveMetastore(cachingHiveMetastore, false, "trino-version", "test"), fileSystemFactory, new TestingTypeManager(), new FileMetastoreTableOperationsProvider(fileSystemFactory), - "test", false, false, false); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java index 75bf3b869932..64f74e0053d0 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/TestIcebergV2.java @@ -26,6 +26,7 @@ import io.trino.hdfs.HdfsEnvironment; import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.plugin.base.CatalogName; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.IcebergTableOperationsProvider; @@ -70,6 +71,7 @@ import static com.google.common.io.MoreFiles.deleteRecursively; import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; +import static io.trino.plugin.hive.metastore.cache.CachingHiveMetastore.memoizeMetastore; import static io.trino.plugin.hive.metastore.file.FileHiveMetastore.createTestingFileHiveMetastore; import static io.trino.plugin.iceberg.IcebergUtil.loadIcebergTable; import static io.trino.testing.TestingConnectorSession.SESSION; @@ -539,13 +541,14 @@ private BaseTable loadTable(String tableName) { TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(HDFS_ENVIRONMENT); IcebergTableOperationsProvider tableOperationsProvider = new FileMetastoreTableOperationsProvider(fileSystemFactory); + CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); TrinoCatalog catalog = new TrinoHiveCatalog( new CatalogName("hive"), - CachingHiveMetastore.memoizeMetastore(metastore, 1000), + cachingHiveMetastore, + new TrinoViewHiveMetastore(cachingHiveMetastore, false, "trino-version", "test"), fileSystemFactory, new TestingTypeManager(), tableOperationsProvider, - "test", false, false, false); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java index 91c90bb6dc00..42e5ca9b4dcd 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/file/TestTrinoHiveCatalogWithFileMetastore.java @@ -16,7 +16,9 @@ import io.trino.filesystem.TrinoFileSystemFactory; import io.trino.filesystem.hdfs.HdfsFileSystemFactory; import io.trino.plugin.base.CatalogName; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.iceberg.catalog.BaseTrinoCatalogTest; import io.trino.plugin.iceberg.catalog.TrinoCatalog; import io.trino.plugin.iceberg.catalog.hms.TrinoHiveCatalog; @@ -62,13 +64,14 @@ public void tearDown() protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) { TrinoFileSystemFactory fileSystemFactory = new HdfsFileSystemFactory(HDFS_ENVIRONMENT); + CachingHiveMetastore cachingHiveMetastore = memoizeMetastore(metastore, 1000); return new TrinoHiveCatalog( new CatalogName("catalog"), - memoizeMetastore(metastore, 1000), + cachingHiveMetastore, + new TrinoViewHiveMetastore(cachingHiveMetastore, false, "trino-version", "test"), fileSystemFactory, new TestingTypeManager(), new FileMetastoreTableOperationsProvider(fileSystemFactory), - "trino-version", useUniqueTableLocations, false, false); diff --git a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java index 3956893259bd..5034a93d7818 100644 --- a/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java +++ b/plugin/trino-iceberg/src/test/java/io/trino/plugin/iceberg/catalog/hms/TestTrinoHiveCatalogWithHiveMetastore.java @@ -23,7 +23,9 @@ import io.trino.hdfs.HdfsEnvironment; import io.trino.hdfs.authentication.NoHdfsAuthentication; import io.trino.plugin.base.CatalogName; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.containers.HiveMinioDataLake; +import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.BridgingHiveMetastore; import io.trino.plugin.hive.metastore.thrift.ThriftMetastore; import io.trino.plugin.hive.metastore.thrift.ThriftMetastoreConfig; @@ -98,9 +100,11 @@ protected TrinoCatalog createTrinoCatalog(boolean useUniqueTableLocations) .setMetastoreTimeout(new Duration(1, MINUTES))) .metastoreClient(dataLake.getHiveHadoop().getHiveMetastoreEndpoint()) .build(); + CachingHiveMetastore metastore = memoizeMetastore(new BridgingHiveMetastore(thriftMetastore), 1000); return new TrinoHiveCatalog( new CatalogName("catalog"), - memoizeMetastore(new BridgingHiveMetastore(thriftMetastore), 1000), + metastore, + new TrinoViewHiveMetastore(metastore, false, "trino-version", "Test"), fileSystemFactory, new TestingTypeManager(), new HiveMetastoreTableOperationsProvider(fileSystemFactory, new ThriftMetastoreFactory() @@ -118,7 +122,6 @@ public ThriftMetastore createMetastore(Optional identity) return thriftMetastore; } }), - "trino-version", useUniqueTableLocations, false, false); From 94226a3573dde2ac87ba7cadb5cf607174f4bf8f Mon Sep 17 00:00:00 2001 From: Michiel De Smet Date: Fri, 28 Oct 2022 21:16:05 +0200 Subject: [PATCH 3/3] Implement Delta Lake views support --- plugin/trino-delta-lake/pom.xml | 4 + .../plugin/deltalake/DeltaLakeMetadata.java | 35 ++++ .../deltalake/DeltaLakeMetadataFactory.java | 17 +- .../BaseDeltaLakeConnectorSmokeTest.java | 3 + .../BaseDeltaLakeMinioConnectorTest.java | 1 + ...BaseDeltaLakeSharedMetastoreViewsTest.java | 166 ++++++++++++++++++ ...TestDeltaLakeSharedFileMetastoreViews.java | 30 ++++ ...TestDeltaLakeSharedGlueMetastoreViews.java | 47 +++++ ...estDeltaLakeMetastoreAccessOperations.java | 18 +- .../glue/TestDeltaLakeViewsGlueMetastore.java | 108 ++++++++++++ .../TestHiveAndDeltaLakeCompatibility.java | 4 +- 11 files changed, 424 insertions(+), 9 deletions(-) create mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java create mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedFileMetastoreViews.java create mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java create mode 100644 plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java diff --git a/plugin/trino-delta-lake/pom.xml b/plugin/trino-delta-lake/pom.xml index 1e189a5cabc3..84646b248e97 100644 --- a/plugin/trino-delta-lake/pom.xml +++ b/plugin/trino-delta-lake/pom.xml @@ -423,10 +423,12 @@ **/TestDeltaLakeAdlsConnectorSmokeTest.java **/TestDeltaLakeGlueMetastore.java **/TestDeltaLakeCleanUpGlueMetastore.java + **/TestDeltaLakeSharedGlueMetastoreViews.java **/TestDeltaLakeSharedGlueMetastoreWithTableRedirections.java **/TestDeltaLakeTableWithCustomLocationUsingGlueMetastore.java **/TestDeltaLakeRenameToWithGlueMetastore.java **/TestDeltaLakeRegisterTableProcedureWithGlue.java + **/TestDeltaLakeViewsGlueMetastore.java **/TestDeltaLakeGcsConnectorSmokeTest.java @@ -474,10 +476,12 @@ **/TestDeltaLakeAdlsConnectorSmokeTest.java **/TestDeltaLakeGlueMetastore.java **/TestDeltaLakeCleanUpGlueMetastore.java + **/TestDeltaLakeSharedGlueMetastoreViews.java **/TestDeltaLakeSharedGlueMetastoreWithTableRedirections.java **/TestDeltaLakeTableWithCustomLocationUsingGlueMetastore.java **/TestDeltaLakeRenameToWithGlueMetastore.java **/TestDeltaLakeRegisterTableProcedureWithGlue.java + **/TestDeltaLakeViewsGlueMetastore.java diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java index 5748370105f1..c0e5e68d51c1 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadata.java @@ -53,6 +53,7 @@ import io.trino.plugin.hive.HiveType; import io.trino.plugin.hive.SchemaAlreadyExistsException; import io.trino.plugin.hive.TableAlreadyExistsException; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.Column; import io.trino.plugin.hive.metastore.Database; import io.trino.plugin.hive.metastore.HivePrincipal; @@ -82,6 +83,7 @@ import io.trino.spi.connector.ConnectorTableLayout; import io.trino.spi.connector.ConnectorTableMetadata; import io.trino.spi.connector.ConnectorTableProperties; +import io.trino.spi.connector.ConnectorViewDefinition; import io.trino.spi.connector.Constraint; import io.trino.spi.connector.ConstraintApplicationResult; import io.trino.spi.connector.ProjectionApplicationResult; @@ -284,6 +286,7 @@ public class DeltaLakeMetadata private final HdfsEnvironment hdfsEnvironment; private final TypeManager typeManager; private final AccessControlMetadata accessControlMetadata; + private final TrinoViewHiveMetastore trinoViewHiveMetastore; private final CheckpointWriterManager checkpointWriterManager; private final long defaultCheckpointInterval; private final int domainCompactionThreshold; @@ -307,6 +310,7 @@ public DeltaLakeMetadata( HdfsEnvironment hdfsEnvironment, TypeManager typeManager, AccessControlMetadata accessControlMetadata, + TrinoViewHiveMetastore trinoViewHiveMetastore, int domainCompactionThreshold, boolean unsafeWritesEnabled, JsonCodec dataFileInfoCodec, @@ -327,6 +331,7 @@ public DeltaLakeMetadata( this.hdfsEnvironment = requireNonNull(hdfsEnvironment, "hdfsEnvironment is null"); this.typeManager = requireNonNull(typeManager, "typeManager is null"); this.accessControlMetadata = requireNonNull(accessControlMetadata, "accessControlMetadata is null"); + this.trinoViewHiveMetastore = requireNonNull(trinoViewHiveMetastore, "trinoViewHiveMetastore is null"); this.domainCompactionThreshold = domainCompactionThreshold; this.unsafeWritesEnabled = unsafeWritesEnabled; this.dataFileInfoCodec = requireNonNull(dataFileInfoCodec, "dataFileInfoCodec is null"); @@ -2075,6 +2080,36 @@ public Map getSchemaProperties(ConnectorSession session, Catalog return db.map(DeltaLakeSchemaProperties::fromDatabase).orElseThrow(() -> new SchemaNotFoundException(schema)); } + @Override + public void createView(ConnectorSession session, SchemaTableName viewName, ConnectorViewDefinition definition, boolean replace) + { + trinoViewHiveMetastore.createView(session, viewName, definition, replace); + } + + @Override + public void dropView(ConnectorSession session, SchemaTableName viewName) + { + trinoViewHiveMetastore.dropView(viewName); + } + + @Override + public List listViews(ConnectorSession session, Optional schemaName) + { + return trinoViewHiveMetastore.listViews(schemaName); + } + + @Override + public Map getViews(ConnectorSession session, Optional schemaName) + { + return trinoViewHiveMetastore.getViews(schemaName); + } + + @Override + public Optional getView(ConnectorSession session, SchemaTableName viewName) + { + return trinoViewHiveMetastore.getView(viewName); + } + @Override public void createRole(ConnectorSession session, String role, Optional grantor) { diff --git a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java index 419e1c025563..49e0e77735f8 100644 --- a/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java +++ b/plugin/trino-delta-lake/src/main/java/io/trino/plugin/deltalake/DeltaLakeMetadataFactory.java @@ -21,8 +21,11 @@ import io.trino.plugin.deltalake.transactionlog.TransactionLogAccess; import io.trino.plugin.deltalake.transactionlog.checkpoint.CheckpointWriterManager; import io.trino.plugin.deltalake.transactionlog.writer.TransactionLogWriterFactory; +import io.trino.plugin.hive.NodeVersion; +import io.trino.plugin.hive.TrinoViewHiveMetastore; import io.trino.plugin.hive.metastore.HiveMetastoreFactory; import io.trino.plugin.hive.metastore.cache.CachingHiveMetastore; +import io.trino.plugin.hive.security.AccessControlMetadata; import io.trino.spi.NodeManager; import io.trino.spi.security.ConnectorIdentity; import io.trino.spi.type.TypeManager; @@ -58,6 +61,7 @@ public class DeltaLakeMetadataFactory private final boolean useUniqueTableLocation; private final boolean allowManagedTableRename; + private final String trinoVersion; @Inject public DeltaLakeMetadataFactory( @@ -76,7 +80,8 @@ public DeltaLakeMetadataFactory( CheckpointWriterManager checkpointWriterManager, DeltaLakeRedirectionsProvider deltaLakeRedirectionsProvider, CachingExtendedStatisticsAccess statisticsAccess, - @AllowDeltaLakeManagedTableRename boolean allowManagedTableRename) + @AllowDeltaLakeManagedTableRename boolean allowManagedTableRename, + NodeVersion nodeVersion) { this.hiveMetastoreFactory = requireNonNull(hiveMetastoreFactory, "hiveMetastore is null"); this.fileSystemFactory = requireNonNull(fileSystemFactory, "fileSystemFactory is null"); @@ -99,6 +104,7 @@ public DeltaLakeMetadataFactory( this.deleteSchemaLocationsFallback = deltaLakeConfig.isDeleteSchemaLocationsFallback(); this.useUniqueTableLocation = deltaLakeConfig.isUniqueTableLocation(); this.allowManagedTableRename = allowManagedTableRename; + this.trinoVersion = requireNonNull(nodeVersion, "nodeVersion is null").toString(); } public DeltaLakeMetadata create(ConnectorIdentity identity) @@ -107,18 +113,25 @@ public DeltaLakeMetadata create(ConnectorIdentity identity) CachingHiveMetastore cachingHiveMetastore = memoizeMetastore( hiveMetastoreFactory.createMetastore(Optional.of(identity)), perTransactionMetastoreCacheMaximumSize); + AccessControlMetadata accessControlMetadata = accessControlMetadataFactory.create(cachingHiveMetastore); HiveMetastoreBackedDeltaLakeMetastore deltaLakeMetastore = new HiveMetastoreBackedDeltaLakeMetastore( cachingHiveMetastore, transactionLogAccess, typeManager, statisticsAccess, fileSystemFactory); + TrinoViewHiveMetastore trinoViewHiveMetastore = new TrinoViewHiveMetastore( + cachingHiveMetastore, + accessControlMetadata.isUsingSystemSecurity(), + trinoVersion, + "Trino Delta Lake connector"); return new DeltaLakeMetadata( deltaLakeMetastore, fileSystemFactory, hdfsEnvironment, typeManager, - accessControlMetadataFactory.create(cachingHiveMetastore), + accessControlMetadata, + trinoViewHiveMetastore, domainCompactionThreshold, unsafeWritesEnabled, dataFileInfoCodec, diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java index 2a43c3ed842d..94290ae288cd 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeConnectorSmokeTest.java @@ -190,6 +190,9 @@ protected QueryRunner createQueryRunner() protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) { switch (connectorBehavior) { + case SUPPORTS_CREATE_VIEW: + return true; + case SUPPORTS_RENAME_SCHEMA: return false; diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeMinioConnectorTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeMinioConnectorTest.java index fa39e3c6b0b7..d56b2611b23b 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeMinioConnectorTest.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeMinioConnectorTest.java @@ -118,6 +118,7 @@ protected boolean hasBehavior(TestingConnectorBehavior connectorBehavior) case SUPPORTS_DELETE: case SUPPORTS_UPDATE: case SUPPORTS_MERGE: + case SUPPORTS_CREATE_VIEW: return true; default: diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java new file mode 100644 index 000000000000..9c88a40c67ea --- /dev/null +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/BaseDeltaLakeSharedMetastoreViewsTest.java @@ -0,0 +1,166 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.deltalake; + +import com.google.common.collect.ImmutableMap; +import io.trino.Session; +import io.trino.plugin.deltalake.metastore.TestingDeltaLakeMetastoreModule; +import io.trino.plugin.hive.TestingHivePlugin; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.DistributedQueryRunner; +import io.trino.testing.QueryRunner; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static java.lang.String.format; + +/** + * Tests querying views on a schema which has a mix of Hive and Delta Lake tables. + */ +public abstract class BaseDeltaLakeSharedMetastoreViewsTest + extends AbstractTestQueryFramework +{ + protected static final String DELTA_CATALOG_NAME = "delta_lake"; + protected static final String HIVE_CATALOG_NAME = "hive"; + protected static final String SCHEMA = "test_shared_schema_views_" + randomNameSuffix(); + + private String dataDirectory; + private HiveMetastore metastore; + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + Session session = testSessionBuilder() + .setCatalog(DELTA_CATALOG_NAME) + .setSchema(SCHEMA) + .build(); + DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session).build(); + + this.dataDirectory = queryRunner.getCoordinator().getBaseDataDir().resolve("delta_lake_data").toString(); + this.metastore = createTestMetastore(dataDirectory); + + queryRunner.installPlugin(new TestingDeltaLakePlugin(Optional.of(new TestingDeltaLakeMetastoreModule(metastore)), EMPTY_MODULE)); + queryRunner.createCatalog(DELTA_CATALOG_NAME, "delta-lake"); + + queryRunner.installPlugin(new TestingHivePlugin(metastore)); + + ImmutableMap hiveProperties = ImmutableMap.builder() + .put("hive.allow-drop-table", "true") + .buildOrThrow(); + + queryRunner.createCatalog(HIVE_CATALOG_NAME, "hive", hiveProperties); + queryRunner.execute("CREATE SCHEMA " + SCHEMA); + + return queryRunner; + } + + protected abstract HiveMetastore createTestMetastore(String dataDirectory); + + @Test + public void testViewWithLiteralColumnCreatedInDeltaLakeIsReadableInHive() + { + String deltaViewName = "delta_view_" + randomNameSuffix(); + String deltaView = format("%s.%s.%s", DELTA_CATALOG_NAME, SCHEMA, deltaViewName); + String deltaViewOnHiveCatalog = format("%s.%s.%s", HIVE_CATALOG_NAME, SCHEMA, deltaViewName); + try { + assertUpdate(format("CREATE VIEW %s AS SELECT 1 bee", deltaView)); + assertQuery(format("SELECT * FROM %s", deltaView), "VALUES 1"); + assertQuery(format("SELECT * FROM %s", deltaViewOnHiveCatalog), "VALUES 1"); + assertQuery(format("SELECT table_type FROM %s.information_schema.tables WHERE table_name = '%s' AND table_schema='%s'", HIVE_CATALOG_NAME, deltaViewName, SCHEMA), "VALUES 'VIEW'"); + } + finally { + assertUpdate(format("DROP VIEW IF EXISTS %s", deltaView)); + } + } + + @Test + public void testViewOnDeltaLakeTableCreatedInDeltaLakeIsReadableInHive() + { + String deltaTableName = "delta_table_" + randomNameSuffix(); + String deltaTable = format("%s.%s.%s", DELTA_CATALOG_NAME, SCHEMA, deltaTableName); + String deltaViewName = "delta_view_" + randomNameSuffix(); + String deltaView = format("%s.%s.%s", DELTA_CATALOG_NAME, SCHEMA, deltaViewName); + String deltaViewOnHiveCatalog = format("%s.%s.%s", HIVE_CATALOG_NAME, SCHEMA, deltaViewName); + try { + assertUpdate(format("CREATE TABLE %s AS SELECT 1 bee", deltaTable), 1); + assertUpdate(format("CREATE VIEW %s AS SELECT * from %s", deltaView, deltaTable)); + assertQuery(format("SELECT * FROM %s", deltaView), "VALUES 1"); + assertQuery(format("SELECT * FROM %s", deltaViewOnHiveCatalog), "VALUES 1"); + assertQuery(format("SELECT table_type FROM %s.information_schema.tables WHERE table_name = '%s' AND table_schema='%s'", HIVE_CATALOG_NAME, deltaViewName, SCHEMA), "VALUES 'VIEW'"); + } + finally { + assertUpdate(format("DROP TABLE IF EXISTS %s", deltaTable)); + assertUpdate(format("DROP VIEW IF EXISTS %s", deltaView)); + } + } + + @Test + public void testViewWithLiteralColumnCreatedInHiveIsReadableInDeltaLake() + { + String trinoViewOnHiveName = "trino_view_on_hive_" + randomNameSuffix(); + String trinoViewOnHive = format("%s.%s.%s", HIVE_CATALOG_NAME, SCHEMA, trinoViewOnHiveName); + String trinoViewOnHiveOnDeltaCatalog = format("%s.%s.%s", DELTA_CATALOG_NAME, SCHEMA, trinoViewOnHiveName); + try { + assertUpdate(format("CREATE VIEW %s AS SELECT 1 bee", trinoViewOnHive)); + assertQuery(format("SELECT * FROM %s", trinoViewOnHive), "VALUES 1"); + assertQuery(format("SELECT * FROM %s", trinoViewOnHiveOnDeltaCatalog), "VALUES 1"); + assertQuery(format("SELECT table_type FROM %s.information_schema.tables WHERE table_name = '%s' AND table_schema='%s'", HIVE_CATALOG_NAME, trinoViewOnHiveName, SCHEMA), "VALUES 'VIEW'"); + } + finally { + assertUpdate(format("DROP VIEW IF EXISTS %s", trinoViewOnHive)); + } + } + + @Test + public void testViewOnHiveTableCreatedInHiveIsReadableInDeltaLake() + { + String hiveTableName = "hive_table_" + randomNameSuffix(); + String hiveTable = format("%s.%s.%s", HIVE_CATALOG_NAME, SCHEMA, hiveTableName); + String trinoViewOnHiveName = "trino_view_on_hive_" + randomNameSuffix(); + String trinoViewOnHive = format("%s.%s.%s", HIVE_CATALOG_NAME, SCHEMA, trinoViewOnHiveName); + String trinoViewOnHiveOnDeltaCatalog = format("%s.%s.%s", DELTA_CATALOG_NAME, SCHEMA, trinoViewOnHiveName); + try { + assertUpdate(format("CREATE TABLE %s AS SELECT 1 bee", hiveTable), 1); + assertUpdate(format("CREATE VIEW %s AS SELECT 1 bee", trinoViewOnHive)); + assertQuery(format("SELECT * FROM %s", trinoViewOnHive), "VALUES 1"); + assertQuery(format("SELECT * FROM %s", trinoViewOnHiveOnDeltaCatalog), "VALUES 1"); + assertQuery(format("SELECT table_type FROM %s.information_schema.tables WHERE table_name = '%s' AND table_schema='%s'", DELTA_CATALOG_NAME, trinoViewOnHiveName, SCHEMA), "VALUES 'VIEW'"); + } + finally { + assertUpdate(format("DROP TABLE IF EXISTS %s", hiveTable)); + assertUpdate(format("DROP VIEW IF EXISTS %s", trinoViewOnHive)); + } + } + + @AfterClass(alwaysRun = true) + public void cleanup() + throws IOException + { + if (metastore != null) { + metastore.dropDatabase(SCHEMA, false); + deleteRecursively(Path.of(dataDirectory), ALLOW_INSECURE); + } + } +} diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedFileMetastoreViews.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedFileMetastoreViews.java new file mode 100644 index 000000000000..f41a91d836d4 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedFileMetastoreViews.java @@ -0,0 +1,30 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.deltalake; + +import io.trino.plugin.hive.metastore.HiveMetastore; + +import java.io.File; + +import static io.trino.plugin.hive.metastore.file.FileHiveMetastore.createTestingFileHiveMetastore; + +public class TestDeltaLakeSharedFileMetastoreViews + extends BaseDeltaLakeSharedMetastoreViewsTest +{ + @Override + protected HiveMetastore createTestMetastore(String dataDirectory) + { + return createTestingFileHiveMetastore(new File(dataDirectory)); + } +} diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java new file mode 100644 index 000000000000..2e2008d8ac77 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/TestDeltaLakeSharedGlueMetastoreViews.java @@ -0,0 +1,47 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.deltalake; + +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.glue.DefaultGlueColumnStatisticsProviderFactory; +import io.trino.plugin.hive.metastore.glue.GlueHiveMetastore; +import io.trino.plugin.hive.metastore.glue.GlueHiveMetastoreConfig; + +import java.util.Optional; + +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; + +/** + * Requires AWS credentials, which can be provided any way supported by the DefaultProviderChain + * See https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default + */ +public class TestDeltaLakeSharedGlueMetastoreViews + extends BaseDeltaLakeSharedMetastoreViewsTest +{ + @Override + protected HiveMetastore createTestMetastore(String dataDirectory) + { + return new GlueHiveMetastore( + HDFS_ENVIRONMENT, + new GlueHiveMetastoreConfig() + .setDefaultWarehouseDir(dataDirectory), + DefaultAWSCredentialsProviderChain.getInstance(), + directExecutor(), + new DefaultGlueColumnStatisticsProviderFactory(directExecutor(), directExecutor()), + Optional.empty(), + table -> true); + } +} diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java index 129943d81f61..c45c8584e143 100644 --- a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/TestDeltaLakeMetastoreAccessOperations.java @@ -153,18 +153,24 @@ public void testSelectWithFilter() public void testSelectFromView() { assertUpdate("CREATE TABLE test_select_view_table (id VARCHAR, age INT)"); - assertQueryFails( - "CREATE VIEW test_select_view_view AS SELECT id, age FROM test_select_view_table", - "This connector does not support creating views"); + assertUpdate("CREATE VIEW test_select_view_view AS SELECT id, age FROM test_select_view_table"); + + assertMetastoreInvocations("SELECT * FROM test_select_view_view", + ImmutableMultiset.builder() + .addCopies(GET_TABLE, 2) + .build()); } @Test public void testSelectFromViewWithFilter() { assertUpdate("CREATE TABLE test_select_view_where_table AS SELECT 2 as age", 1); - assertQueryFails( - "CREATE VIEW test_select_view_where_view AS SELECT age FROM test_select_view_where_table", - "This connector does not support creating views"); + assertUpdate("CREATE VIEW test_select_view_where_view AS SELECT age FROM test_select_view_where_table"); + + assertMetastoreInvocations("SELECT * FROM test_select_view_where_view WHERE age = 2", + ImmutableMultiset.builder() + .addCopies(GET_TABLE, 2) + .build()); } @Test diff --git a/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java new file mode 100644 index 000000000000..1f3c31b84745 --- /dev/null +++ b/plugin/trino-delta-lake/src/test/java/io/trino/plugin/deltalake/metastore/glue/TestDeltaLakeViewsGlueMetastore.java @@ -0,0 +1,108 @@ +/* + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.trino.plugin.deltalake.metastore.glue; + +import com.amazonaws.auth.DefaultAWSCredentialsProviderChain; +import io.trino.Session; +import io.trino.plugin.deltalake.TestingDeltaLakePlugin; +import io.trino.plugin.deltalake.metastore.TestingDeltaLakeMetastoreModule; +import io.trino.plugin.hive.metastore.HiveMetastore; +import io.trino.plugin.hive.metastore.glue.DefaultGlueColumnStatisticsProviderFactory; +import io.trino.plugin.hive.metastore.glue.GlueHiveMetastore; +import io.trino.plugin.hive.metastore.glue.GlueHiveMetastoreConfig; +import io.trino.testing.AbstractTestQueryFramework; +import io.trino.testing.DistributedQueryRunner; +import io.trino.testing.QueryRunner; +import io.trino.testing.sql.TestTable; +import io.trino.testing.sql.TestView; +import org.testng.annotations.AfterClass; +import org.testng.annotations.Test; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Optional; + +import static com.google.common.io.MoreFiles.deleteRecursively; +import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE; +import static com.google.common.util.concurrent.MoreExecutors.directExecutor; +import static com.google.inject.util.Modules.EMPTY_MODULE; +import static io.trino.plugin.hive.HiveTestUtils.HDFS_ENVIRONMENT; +import static io.trino.testing.TestingNames.randomNameSuffix; +import static io.trino.testing.TestingSession.testSessionBuilder; +import static java.lang.String.format; + +public class TestDeltaLakeViewsGlueMetastore + extends AbstractTestQueryFramework +{ + private static final String SCHEMA = "test_delta_lake_glue_views_" + randomNameSuffix(); + private static final String CATALOG_NAME = "test_delta_lake_glue_views"; + private String dataDirectory; + private HiveMetastore metastore; + + private HiveMetastore createTestMetastore(String dataDirectory) + { + return new GlueHiveMetastore( + HDFS_ENVIRONMENT, + new GlueHiveMetastoreConfig() + .setDefaultWarehouseDir(dataDirectory), + DefaultAWSCredentialsProviderChain.getInstance(), + directExecutor(), + new DefaultGlueColumnStatisticsProviderFactory(directExecutor(), directExecutor()), + Optional.empty(), + table -> true); + } + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + Session deltaLakeSession = testSessionBuilder() + .setCatalog(CATALOG_NAME) + .setSchema(SCHEMA) + .build(); + + DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(deltaLakeSession).build(); + + dataDirectory = queryRunner.getCoordinator().getBaseDataDir().resolve("data_delta_lake_views").toString(); + metastore = createTestMetastore(dataDirectory); + + queryRunner.installPlugin(new TestingDeltaLakePlugin(Optional.of(new TestingDeltaLakeMetastoreModule(metastore)), EMPTY_MODULE)); + queryRunner.createCatalog(CATALOG_NAME, "delta-lake"); + + queryRunner.execute("CREATE SCHEMA " + SCHEMA); + return queryRunner; + } + + @Test + public void testCreateView() + { + String tableName = "test_glue_table_" + randomNameSuffix(); + String viewName = "test_glue_view_" + randomNameSuffix(); + try (TestTable table = new TestTable(getQueryRunner()::execute, tableName, "AS SELECT 'test' x"); + TestView view = new TestView(getQueryRunner()::execute, viewName, "SELECT * FROM " + table.getName())) { + assertQuery(format("SELECT * FROM %s", view.getName()), "VALUES 'test'"); + assertQuery(format("SELECT table_type FROM information_schema.tables WHERE table_name = '%s' AND table_schema='%s'", view.getName(), SCHEMA), "VALUES 'VIEW'"); + } + } + + @AfterClass(alwaysRun = true) + public void cleanup() + throws IOException + { + if (metastore != null) { + metastore.dropDatabase(SCHEMA, false); + deleteRecursively(Path.of(dataDirectory), ALLOW_INSECURE); + } + } +} diff --git a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeCompatibility.java b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeCompatibility.java index 673f3a2051f8..85614f96b486 100644 --- a/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeCompatibility.java +++ b/testing/trino-product-tests/src/main/java/io/trino/tests/product/deltalake/TestHiveAndDeltaLakeCompatibility.java @@ -17,6 +17,7 @@ import io.trino.testng.services.Flaky; import org.testng.annotations.Test; +import static io.trino.tempto.assertions.QueryAssert.Row.row; import static io.trino.tempto.assertions.QueryAssert.assertThat; import static io.trino.testing.TestingNames.randomNameSuffix; import static io.trino.tests.product.TestGroups.DELTA_LAKE_DATABRICKS; @@ -44,7 +45,8 @@ public void testInformationSchemaColumnsOnPresenceOfHiveView() onTrino().executeQuery("CREATE VIEW " + hiveViewQualifiedName + " AS SELECT 1 AS col_one"); try { - assertThat(onTrino().executeQuery(format("SELECT table_name FROM delta.information_schema.columns WHERE table_schema = '%s'", schemaName))).hasNoRows(); + assertThat(onTrino().executeQuery(format("SELECT table_name FROM delta.information_schema.columns WHERE table_schema = '%s'", schemaName))) + .containsOnly(row(hiveViewName)); } finally { onTrino().executeQuery("DROP VIEW IF EXISTS " + hiveViewQualifiedName);