diff --git a/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java b/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java index eb6ea59912b76..7ab6d4d2d0266 100644 --- a/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java +++ b/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java @@ -1494,6 +1494,24 @@ public void createMaterializedView( } } + @Override + public List listMaterializedViews(ConnectorSession session, String schemaName) + { + ImmutableList.Builder materializedViews = ImmutableList.builder(); + + List views = listViews(session, Optional.of(schemaName)); + + for (SchemaTableName viewName : views) { + View icebergView = getIcebergView(session, viewName); + Map properties = icebergView.properties(); + if (properties.containsKey(PRESTO_MATERIALIZED_VIEW_FORMAT_VERSION)) { + materializedViews.add(viewName); + } + } + + return materializedViews.build(); + } + @Override public Optional getMaterializedView(ConnectorSession session, SchemaTableName viewName) { diff --git a/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java b/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java index 5fb9569b5222e..ff72eb48b7def 100644 --- a/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java +++ b/presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java @@ -473,6 +473,12 @@ public List listViews(ConnectorSession session, Optional listMaterializedViews(ConnectorSession session, String schemaName) + { + return ImmutableList.of(); + } + @Override public Map getViews(ConnectorSession session, SchemaTablePrefix prefix) { diff --git a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViews.java b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViews.java index 638732f6b2bda..458800e2bc4a2 100644 --- a/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViews.java +++ b/presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViews.java @@ -188,8 +188,9 @@ public void testMaterializedViewMetadata() assertUpdate("CREATE MATERIALIZED VIEW test_mv_metadata AS SELECT id, name FROM test_mv_metadata_base WHERE id > 0"); - assertQueryReturnsEmptyResult("SELECT table_name FROM information_schema.tables " + - "WHERE table_schema = 'test_schema' AND table_name = 'test_mv_metadata' AND table_type = 'MATERIALIZED VIEW'"); + assertQuery("SELECT table_name, table_type FROM information_schema.tables " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_mv_metadata'", + "VALUES ('test_mv_metadata', 'MATERIALIZED VIEW')"); assertUpdate("DROP MATERIALIZED VIEW test_mv_metadata"); assertUpdate("DROP TABLE test_mv_metadata_base"); @@ -1400,4 +1401,123 @@ public void testCreateMaterializedViewWithSameNameAsExistingTable() assertUpdate("DROP TABLE existing_table_name"); assertUpdate("DROP TABLE test_mv_base"); } + + @Test + public void testInformationSchemaMaterializedViews() + { + assertUpdate("CREATE TABLE test_is_mv_base1 (id BIGINT, name VARCHAR, value BIGINT)"); + assertUpdate("CREATE TABLE test_is_mv_base2 (category VARCHAR, amount BIGINT)"); + + assertUpdate("INSERT INTO test_is_mv_base1 VALUES (1, 'Alice', 100), (2, 'Bob', 200)", 2); + assertUpdate("INSERT INTO test_is_mv_base2 VALUES ('A', 50), ('B', 75)", 2); + + assertUpdate("CREATE MATERIALIZED VIEW test_is_mv1 AS SELECT id, name, value FROM test_is_mv_base1 WHERE id > 0"); + assertUpdate("CREATE MATERIALIZED VIEW test_is_mv2 AS SELECT category, SUM(amount) as total FROM test_is_mv_base2 GROUP BY category"); + + assertQuery( + "SELECT table_name FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name IN ('test_is_mv1', 'test_is_mv2') " + + "ORDER BY table_name", + "VALUES ('test_is_mv1'), ('test_is_mv2')"); + + assertQuery( + "SELECT table_catalog, table_schema, table_name, storage_schema, storage_table_name, base_tables " + + "FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv1'", + "SELECT 'iceberg', 'test_schema', 'test_is_mv1', 'test_schema', '__mv_storage__test_is_mv1', 'iceberg.test_schema.test_is_mv_base1'"); + + assertQuery( + "SELECT COUNT(*) FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv1' " + + "AND view_definition IS NOT NULL AND length(view_definition) > 0", + "SELECT 1"); + + assertQuery( + "SELECT table_name FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv2'", + "VALUES ('test_is_mv2')"); + + assertQuery( + "SELECT COUNT(*) FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv1' " + + "AND view_owner IS NOT NULL", + "SELECT 1"); + + assertQuery( + "SELECT COUNT(*) FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv1' " + + "AND view_security IS NOT NULL", + "SELECT 1"); + + assertQuery( + "SELECT base_tables FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv2'", + "VALUES ('iceberg.test_schema.test_is_mv_base2')"); + + assertUpdate("DROP MATERIALIZED VIEW test_is_mv1"); + assertUpdate("DROP MATERIALIZED VIEW test_is_mv2"); + assertUpdate("DROP TABLE test_is_mv_base1"); + assertUpdate("DROP TABLE test_is_mv_base2"); + + assertQuery( + "SELECT COUNT(*) FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name IN ('test_is_mv1', 'test_is_mv2')", + "VALUES 0"); + } + + @Test + public void testInformationSchemaMaterializedViewsAfterRefresh() + { + assertUpdate("CREATE TABLE test_is_mv_refresh_base (id BIGINT, value BIGINT)"); + assertUpdate("INSERT INTO test_is_mv_refresh_base VALUES (1, 100), (2, 200)", 2); + assertUpdate("CREATE MATERIALIZED VIEW test_is_mv_refresh AS SELECT id, value FROM test_is_mv_refresh_base"); + + assertQuery( + "SELECT freshness_state FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "SELECT 'NOT_MATERIALIZED'"); + + assertUpdate("REFRESH MATERIALIZED VIEW test_is_mv_refresh", 2); + + assertQuery( + "SELECT freshness_state FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "SELECT 'FULLY_MATERIALIZED'"); + + assertUpdate("INSERT INTO test_is_mv_refresh_base VALUES (3, 300)", 1); + + assertQuery( + "SELECT freshness_state FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "SELECT 'PARTIALLY_MATERIALIZED'"); + + assertUpdate("UPDATE test_is_mv_refresh_base SET value = 250 WHERE id = 2", 1); + + assertQuery( + "SELECT freshness_state FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "SELECT 'PARTIALLY_MATERIALIZED'"); + + assertUpdate("DELETE FROM test_is_mv_refresh_base WHERE id = 1", 1); + + assertQuery( + "SELECT freshness_state FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "SELECT 'PARTIALLY_MATERIALIZED'"); + + assertUpdate("REFRESH MATERIALIZED VIEW test_is_mv_refresh", 2); + + assertQuery( + "SELECT freshness_state FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "SELECT 'FULLY_MATERIALIZED'"); + + assertUpdate("DROP MATERIALIZED VIEW test_is_mv_refresh"); + assertUpdate("DROP TABLE test_is_mv_refresh_base"); + + assertQuery( + "SELECT COUNT(*) FROM information_schema.materialized_views " + + "WHERE table_schema = 'test_schema' AND table_name = 'test_is_mv_refresh'", + "VALUES 0"); + } } diff --git a/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java b/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java index 7c742d3235ab3..5867793cdae9e 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java +++ b/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaMetadata.java @@ -74,6 +74,7 @@ public class InformationSchemaMetadata public static final SchemaTableName TABLE_COLUMNS = new SchemaTableName(INFORMATION_SCHEMA, "columns"); public static final SchemaTableName TABLE_TABLES = new SchemaTableName(INFORMATION_SCHEMA, "tables"); public static final SchemaTableName TABLE_VIEWS = new SchemaTableName(INFORMATION_SCHEMA, "views"); + public static final SchemaTableName TABLE_MATERIALIZED_VIEWS = new SchemaTableName(INFORMATION_SCHEMA, "materialized_views"); public static final SchemaTableName TABLE_SCHEMATA = new SchemaTableName(INFORMATION_SCHEMA, "schemata"); public static final SchemaTableName TABLE_TABLE_PRIVILEGES = new SchemaTableName(INFORMATION_SCHEMA, "table_privileges"); public static final SchemaTableName TABLE_ROLES = new SchemaTableName(INFORMATION_SCHEMA, "roles"); @@ -109,6 +110,18 @@ public class InformationSchemaMetadata .column("view_owner", createUnboundedVarcharType()) .column("view_definition", createUnboundedVarcharType()) .build()) + .table(tableMetadataBuilder(TABLE_MATERIALIZED_VIEWS) + .column("table_catalog", createUnboundedVarcharType()) + .column("table_schema", createUnboundedVarcharType()) + .column("table_name", createUnboundedVarcharType()) + .column("view_definition", createUnboundedVarcharType()) + .column("view_owner", createUnboundedVarcharType()) + .column("view_security", createUnboundedVarcharType()) + .column("storage_schema", createUnboundedVarcharType()) + .column("storage_table_name", createUnboundedVarcharType()) + .column("base_tables", createUnboundedVarcharType()) + .column("freshness_state", createUnboundedVarcharType()) + .build()) .table(tableMetadataBuilder(TABLE_SCHEMATA) .column("catalog_name", createUnboundedVarcharType()) .column("schema_name", createUnboundedVarcharType()) @@ -258,7 +271,7 @@ public ConnectorTableLayoutResult getTableLayoutForConstraint(ConnectorSession s private boolean isTablesEnumeratingTable(SchemaTableName schemaTableName) { - return ImmutableSet.of(TABLE_COLUMNS, TABLE_VIEWS, TABLE_TABLES, TABLE_TABLE_PRIVILEGES).contains(schemaTableName); + return ImmutableSet.of(TABLE_COLUMNS, TABLE_VIEWS, TABLE_MATERIALIZED_VIEWS, TABLE_TABLES, TABLE_TABLE_PRIVILEGES).contains(schemaTableName); } private Set calculatePrefixesWithSchemaName( @@ -304,8 +317,10 @@ public Set calculatePrefixesWithTableName( return prefixes.stream() .flatMap(prefix -> Stream.concat( - metadata.listTables(session, prefix).stream(), - metadata.listViews(session, prefix).stream())) + Stream.concat( + metadata.listTables(session, prefix).stream(), + metadata.listViews(session, prefix).stream()), + metadata.listMaterializedViews(session, prefix).stream())) .filter(objectName -> !predicate.isPresent() || predicate.get().test(asFixedValues(objectName))) .map(value -> toQualifiedTablePrefix(new QualifiedObjectName( value.getCatalogName(), diff --git a/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaPageSourceProvider.java b/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaPageSourceProvider.java index 4213231b04b7b..2c3931035291e 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaPageSourceProvider.java +++ b/presto-main-base/src/main/java/com/facebook/presto/connector/informationSchema/InformationSchemaPageSourceProvider.java @@ -18,6 +18,7 @@ import com.facebook.presto.common.Page; import com.facebook.presto.common.QualifiedObjectName; import com.facebook.presto.common.block.Block; +import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.CharType; import com.facebook.presto.common.type.DecimalType; import com.facebook.presto.common.type.Type; @@ -31,6 +32,8 @@ import com.facebook.presto.spi.ConnectorSession; import com.facebook.presto.spi.ConnectorSplit; import com.facebook.presto.spi.FixedPageSource; +import com.facebook.presto.spi.MaterializedViewDefinition; +import com.facebook.presto.spi.MaterializedViewStatus; import com.facebook.presto.spi.SchemaTableName; import com.facebook.presto.spi.SplitContext; import com.facebook.presto.spi.analyzer.ViewDefinition; @@ -57,6 +60,7 @@ import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_APPLICABLE_ROLES; import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_COLUMNS; import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_ENABLED_ROLES; +import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_MATERIALIZED_VIEWS; import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_ROLES; import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_SCHEMATA; import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_TABLES; @@ -64,6 +68,7 @@ import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.TABLE_VIEWS; import static com.facebook.presto.connector.informationSchema.InformationSchemaMetadata.informationSchemaTableColumns; import static com.facebook.presto.connector.system.jdbc.ColumnJdbcTable.decimalDigits; +import static com.facebook.presto.metadata.MetadataListing.listMaterializedViews; import static com.facebook.presto.metadata.MetadataListing.listSchemas; import static com.facebook.presto.metadata.MetadataListing.listTableColumns; import static com.facebook.presto.metadata.MetadataListing.listTablePrivileges; @@ -138,6 +143,9 @@ public InternalTable getInformationSchemaTable(Session session, String catalog, if (table.equals(TABLE_VIEWS)) { return buildViews(session, prefixes); } + if (table.equals(TABLE_MATERIALIZED_VIEWS)) { + return buildMaterializedViews(session, prefixes); + } if (table.equals(TABLE_SCHEMATA)) { return buildSchemata(session, catalog); } @@ -195,10 +203,19 @@ private InternalTable buildTables(Session session, Set pre for (QualifiedTablePrefix prefix : prefixes) { Set tables = listTables(session, metadata, accessControl, prefix); Set views = listViews(session, metadata, accessControl, prefix); + Set materializedViews = listMaterializedViews(session, metadata, accessControl, prefix); - for (SchemaTableName name : union(tables, views)) { - // if table and view names overlap, the view wins - String type = views.contains(name) ? "VIEW" : "BASE TABLE"; + for (SchemaTableName name : union(union(tables, views), materializedViews)) { + String type; + if (materializedViews.contains(name)) { + type = "MATERIALIZED VIEW"; + } + else if (views.contains(name)) { + type = "VIEW"; + } + else { + type = "BASE TABLE"; + } table.add( prefix.getCatalogName(), name.getSchemaName(), @@ -247,6 +264,39 @@ private InternalTable buildViews(Session session, Set pref return table.build(); } + private InternalTable buildMaterializedViews(Session session, Set prefixes) + { + InternalTable.Builder table = InternalTable.builder(informationSchemaTableColumns(TABLE_MATERIALIZED_VIEWS)); + + for (QualifiedTablePrefix prefix : prefixes) { + for (Entry entry : metadata.getMaterializedViews(session, prefix).entrySet()) { + QualifiedObjectName viewName = entry.getKey(); + MaterializedViewDefinition definition = entry.getValue(); + + String baseTablesStr = definition.getBaseTables().stream() + .map(baseTable -> viewName.getCatalogName() + "." + baseTable.getSchemaName() + "." + baseTable.getTableName()) + .collect(java.util.stream.Collectors.joining(", ")); + + MaterializedViewStatus status = metadata.getMaterializedViewStatus(session, viewName, TupleDomain.all()); + String freshnessState = status.getMaterializedViewState().name(); + + table.add( + viewName.getCatalogName(), + viewName.getSchemaName(), + viewName.getObjectName(), + definition.getOriginalSql(), + definition.getOwner().orElse(null), + definition.getSecurityMode().map(Object::toString).orElse(null), + definition.getSchema(), + definition.getTable(), + baseTablesStr, + freshnessState); + } + } + + return table.build(); + } + private InternalTable buildSchemata(Session session, String catalogName) { InternalTable.Builder table = InternalTable.builder(informationSchemaTableColumns(TABLE_SCHEMATA)); diff --git a/presto-main-base/src/main/java/com/facebook/presto/connector/system/jdbc/TableJdbcTable.java b/presto-main-base/src/main/java/com/facebook/presto/connector/system/jdbc/TableJdbcTable.java index b425eb0b38704..8bf503bc1f959 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/connector/system/jdbc/TableJdbcTable.java +++ b/presto-main-base/src/main/java/com/facebook/presto/connector/system/jdbc/TableJdbcTable.java @@ -37,6 +37,7 @@ import static com.facebook.presto.connector.system.jdbc.FilterUtil.stringFilter; import static com.facebook.presto.connector.system.jdbc.FilterUtil.tablePrefix; import static com.facebook.presto.metadata.MetadataListing.listCatalogs; +import static com.facebook.presto.metadata.MetadataListing.listMaterializedViews; import static com.facebook.presto.metadata.MetadataListing.listTables; import static com.facebook.presto.metadata.MetadataListing.listViews; import static com.facebook.presto.metadata.MetadataUtil.TableMetadataBuilder.tableMetadataBuilder; @@ -97,9 +98,17 @@ public RecordCursor cursor(ConnectorTransactionHandle transactionHandle, Connect } } + Set materializedViews = ImmutableSet.of(); + if (FilterUtil.emptyOrEquals(typeFilter, "MATERIALIZED VIEW")) { + materializedViews = ImmutableSet.copyOf(listMaterializedViews(session, metadata, accessControl, prefix)); + for (SchemaTableName name : materializedViews) { + table.addRow(tableRow(catalog, name, "MATERIALIZED VIEW")); + } + } + if (FilterUtil.emptyOrEquals(typeFilter, "TABLE")) { for (SchemaTableName name : listTables(session, metadata, accessControl, prefix)) { - if (!views.contains(name)) { + if (!views.contains(name) && !materializedViews.contains(name)) { table.addRow(tableRow(catalog, name, "TABLE")); } } diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java index 00df5ab44af7a..ef4ab7c7bc499 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/DelegatingMetadataManager.java @@ -26,6 +26,7 @@ import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.Constraint; import com.facebook.presto.spi.MaterializedViewDefinition; +import com.facebook.presto.spi.MaterializedViewStatus; import com.facebook.presto.spi.MergeHandle; import com.facebook.presto.spi.NewTableLayout; import com.facebook.presto.spi.SystemTable; @@ -475,6 +476,26 @@ public Map getViews(Session session, Qualif return delegate.getViews(session, prefix); } + @Override + public List listMaterializedViews(Session session, QualifiedTablePrefix prefix) + { + return delegate.listMaterializedViews(session, prefix); + } + + @Override + public Map getMaterializedViews( + Session session, + QualifiedTablePrefix prefix) + { + return delegate.getMaterializedViews(session, prefix); + } + + @Override + public MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName viewName, TupleDomain baseQueryDomain) + { + return delegate.getMaterializedViewStatus(session, viewName, baseQueryDomain); + } + @Override public void createView(Session session, String catalogName, ConnectorTableMetadata viewMetadata, String viewData, boolean replace) { diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java index 0ab1c5a3a70e3..666c337bd9a10 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/Metadata.java @@ -28,6 +28,7 @@ import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.Constraint; import com.facebook.presto.spi.MaterializedViewDefinition; +import com.facebook.presto.spi.MaterializedViewStatus; import com.facebook.presto.spi.MergeHandle; import com.facebook.presto.spi.NewTableLayout; import com.facebook.presto.spi.PrestoException; @@ -439,6 +440,19 @@ default Map getCatalogNamesWithConnectorContext(Session */ void dropMaterializedView(Session session, QualifiedObjectName viewName); + /** + * List materialized views in the specified schema prefix. + */ + List listMaterializedViews(Session session, QualifiedTablePrefix prefix); + + /** + * Get materialized view definitions for all materialized views matching the prefix. + * This is used by information_schema to efficiently retrieve view definitions. + */ + Map getMaterializedViews( + Session session, + QualifiedTablePrefix prefix); + /** * Begin refresh materialized view */ @@ -454,6 +468,11 @@ default Map getCatalogNamesWithConnectorContext(Session */ List getReferencedMaterializedViews(Session session, QualifiedObjectName tableName); + /** + * Gets the status of a materialized view (freshness state) + */ + MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName viewName, TupleDomain baseQueryDomain); + /** * Try to locate a table index that can lookup results by indexableColumns and provide the requested outputColumns. */ diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataListing.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataListing.java index c5a2532466a53..d71bafb9adf05 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataListing.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataListing.java @@ -91,6 +91,14 @@ public static Set listViews(Session session, Metadata metadata, return accessControl.filterTables(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), prefix.getCatalogName(), tableNames); } + public static Set listMaterializedViews(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) + { + Set tableNames = metadata.listMaterializedViews(session, prefix).stream() + .map(MetadataUtil::toSchemaTableName) + .collect(toImmutableSet()); + return accessControl.filterTables(session.getRequiredTransactionId(), session.getIdentity(), session.getAccessControlContext(), prefix.getCatalogName(), tableNames); + } + public static Set listTablePrivileges(Session session, Metadata metadata, AccessControl accessControl, QualifiedTablePrefix prefix) { List grants = metadata.listTablePrivileges(session, prefix); diff --git a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java index 6eb0abbb3024c..aaae02f1f82dd 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java +++ b/presto-main-base/src/main/java/com/facebook/presto/metadata/MetadataManager.java @@ -147,6 +147,7 @@ import static java.lang.String.format; import static java.util.Locale.ENGLISH; import static java.util.Objects.requireNonNull; +import static java.util.stream.Collectors.groupingBy; public class MetadataManager implements Metadata @@ -1218,7 +1219,113 @@ public void dropMaterializedView(Session session, QualifiedObjectName viewName) metadata.dropMaterializedView(session.toConnectorSession(connectorId), toSchemaTableName(viewName.getSchemaName(), viewName.getObjectName())); } - private MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName materializedViewName, TupleDomain baseQueryDomain) + @Override + public List listMaterializedViews(Session session, QualifiedTablePrefix prefix) + { + requireNonNull(prefix, "prefix is null"); + + Optional catalog = getOptionalCatalogMetadata(session, transactionManager, prefix.getCatalogName()); + Set materializedViews = new LinkedHashSet<>(); + if (catalog.isPresent()) { + CatalogMetadata catalogMetadata = catalog.get(); + + for (ConnectorId connectorId : catalogMetadata.listConnectorIds()) { + ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); + ConnectorSession connectorSession = session.toConnectorSession(connectorId); + + List viewNames; + if (prefix.getSchemaName().isPresent()) { + viewNames = metadata.listMaterializedViews(connectorSession, prefix.getSchemaName().get()); + } + else { + viewNames = new ArrayList<>(); + for (String schemaName : metadata.listSchemaNames(connectorSession)) { + viewNames.addAll(metadata.listMaterializedViews(connectorSession, schemaName)); + } + } + + // Convert to QualifiedObjectName + for (SchemaTableName viewName : viewNames) { + materializedViews.add(new QualifiedObjectName( + prefix.getCatalogName(), + viewName.getSchemaName(), + viewName.getTableName())); + } + } + } + + return ImmutableList.copyOf(materializedViews); + } + + @Override + public Map getMaterializedViews( + Session session, + QualifiedTablePrefix prefix) + { + requireNonNull(prefix, "prefix is null"); + + Optional catalog = getOptionalCatalogMetadata(session, transactionManager, prefix.getCatalogName()); + Map views = new LinkedHashMap<>(); + + if (catalog.isPresent()) { + CatalogMetadata catalogMetadata = catalog.get(); + + for (ConnectorId connectorId : catalogMetadata.listConnectorIds()) { + ConnectorMetadata metadata = catalogMetadata.getMetadataFor(connectorId); + ConnectorSession connectorSession = session.toConnectorSession(connectorId); + + List viewNames; + if (prefix.getSchemaName().isPresent()) { + viewNames = metadata.listMaterializedViews(connectorSession, prefix.getSchemaName().get()); + + if (prefix.getTableName().isPresent()) { + String tableName = prefix.getTableName().get(); + viewNames = viewNames.stream() + .filter(name -> name.getTableName().equals(tableName)) + .collect(toImmutableList()); + } + } + else { + viewNames = new ArrayList<>(); + for (String schemaName : metadata.listSchemaNames(connectorSession)) { + viewNames.addAll(metadata.listMaterializedViews(connectorSession, schemaName)); + } + } + + // Bulk retrieve definitions + if (!viewNames.isEmpty()) { + Map definitions; + + if (prefix.getSchemaName().isPresent()) { + String schemaName = prefix.getSchemaName().get(); + definitions = metadata.getMaterializedViews(connectorSession, schemaName, viewNames); + } + else { + definitions = new HashMap<>(); + viewNames.stream() + .collect(groupingBy(SchemaTableName::getSchemaName)) + .forEach((schema, names) -> { + definitions.putAll(metadata.getMaterializedViews(connectorSession, schema, names)); + }); + } + + definitions.forEach((viewName, definition) -> { + views.put( + new QualifiedObjectName( + prefix.getCatalogName(), + viewName.getSchemaName(), + viewName.getTableName()), + definition); + }); + } + } + } + + return ImmutableMap.copyOf(views); + } + + @Override + public MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName materializedViewName, TupleDomain baseQueryDomain) { Optional materializedViewHandle = getOptionalTableHandle(session, transactionManager, materializedViewName, Optional.empty()); diff --git a/presto-main-base/src/main/java/com/facebook/presto/sql/planner/iterative/rule/MaterializedViewRewrite.java b/presto-main-base/src/main/java/com/facebook/presto/sql/planner/iterative/rule/MaterializedViewRewrite.java index 05f96f1c08884..41468f91528e5 100644 --- a/presto-main-base/src/main/java/com/facebook/presto/sql/planner/iterative/rule/MaterializedViewRewrite.java +++ b/presto-main-base/src/main/java/com/facebook/presto/sql/planner/iterative/rule/MaterializedViewRewrite.java @@ -15,6 +15,7 @@ import com.facebook.presto.Session; import com.facebook.presto.common.QualifiedObjectName; +import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.matching.Captures; import com.facebook.presto.matching.Pattern; import com.facebook.presto.metadata.Metadata; @@ -100,7 +101,7 @@ private boolean isUseDataTable(MaterializedViewScanNode node, MetadataResolver m checkState(materializedViewDefinition.isPresent(), "Materialized view definition not found for: %s", node.getMaterializedViewName()); // Security mode defaults to INVOKER for legacy materialized views created without explicitly specifying it ViewSecurity securityMode = materializedViewDefinition.get().getSecurityMode().orElse(INVOKER); - MaterializedViewStatus status = metadataResolver.getMaterializedViewStatus(node.getMaterializedViewName()); + MaterializedViewStatus status = metadataResolver.getMaterializedViewStatus(node.getMaterializedViewName(), TupleDomain.all()); if (!status.isFullyMaterialized()) { return false; diff --git a/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java b/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java index 4f3c78b985629..b832c6745a18d 100644 --- a/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java +++ b/presto-main-base/src/test/java/com/facebook/presto/metadata/AbstractMockMetadata.java @@ -26,6 +26,7 @@ import com.facebook.presto.spi.ConnectorTableMetadata; import com.facebook.presto.spi.Constraint; import com.facebook.presto.spi.MaterializedViewDefinition; +import com.facebook.presto.spi.MaterializedViewStatus; import com.facebook.presto.spi.MergeHandle; import com.facebook.presto.spi.NewTableLayout; import com.facebook.presto.spi.SystemTable; @@ -529,6 +530,24 @@ public Map getViews(Session session, Qualif throw new UnsupportedOperationException(); } + @Override + public List listMaterializedViews(Session session, QualifiedTablePrefix prefix) + { + throw new UnsupportedOperationException(); + } + + @Override + public Map getMaterializedViews(Session session, QualifiedTablePrefix prefix) + { + throw new UnsupportedOperationException(); + } + + @Override + public MaterializedViewStatus getMaterializedViewStatus(Session session, QualifiedObjectName viewName, TupleDomain baseQueryDomain) + { + throw new UnsupportedOperationException(); + } + @Override public void createView(Session session, String catalogName, ConnectorTableMetadata viewMetadata, String viewData, boolean replace) { diff --git a/presto-main-base/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestMaterializedViewRewrite.java b/presto-main-base/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestMaterializedViewRewrite.java index 227d8caa9f556..3ae040f09f2f9 100644 --- a/presto-main-base/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestMaterializedViewRewrite.java +++ b/presto-main-base/src/test/java/com/facebook/presto/sql/planner/iterative/rule/TestMaterializedViewRewrite.java @@ -294,7 +294,7 @@ public Optional getMaterializedView(QualifiedObjectN } @Override - public MaterializedViewStatus getMaterializedViewStatus(QualifiedObjectName materializedViewName, TupleDomain baseQueryDomain) + public MaterializedViewStatus getMaterializedViewStatus(QualifiedObjectName materializedViewName, TupleDomain baseTableConstraint) { return new MaterializedViewStatus(isFullyMaterialized ? FULLY_MATERIALIZED : PARTIALLY_MATERIALIZED); } diff --git a/presto-product-tests/src/main/resources/sql-tests/testcases/system/selectInformationSchemaColumns.result b/presto-product-tests/src/main/resources/sql-tests/testcases/system/selectInformationSchemaColumns.result index 4103c03301025..deaba8887b4e7 100644 --- a/presto-product-tests/src/main/resources/sql-tests/testcases/system/selectInformationSchemaColumns.result +++ b/presto-product-tests/src/main/resources/sql-tests/testcases/system/selectInformationSchemaColumns.result @@ -17,6 +17,16 @@ system| information_schema| columns| precision| bigint| YES| null| null| system| information_schema| columns| scale| bigint| YES| null| null| system| information_schema| columns| length| bigint| YES| null| null| system| information_schema| enabled_roles| role_name| varchar| YES| null| null| +system| information_schema| materialized_views| table_catalog| varchar| YES| null| null| +system| information_schema| materialized_views| table_schema| varchar| YES| null| null| +system| information_schema| materialized_views| table_name| varchar| YES| null| null| +system| information_schema| materialized_views| view_definition| varchar| YES| null| null| +system| information_schema| materialized_views| view_owner| varchar| YES| null| null| +system| information_schema| materialized_views| view_security| varchar| YES| null| null| +system| information_schema| materialized_views| storage_schema| varchar| YES| null| null| +system| information_schema| materialized_views| storage_table_name| varchar| YES| null| null| +system| information_schema| materialized_views| base_tables| varchar| YES| null| null| +system| information_schema| materialized_views| freshness_state| varchar| YES| null| null| system| information_schema| roles| role_name| varchar| YES| null| null| system| information_schema| schemata| catalog_name| varchar| YES| null| null| system| information_schema| schemata| schema_name| varchar| YES| null| null| diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/analyzer/MetadataResolver.java b/presto-spi/src/main/java/com/facebook/presto/spi/analyzer/MetadataResolver.java index 7c6f4edc5ecf0..768b67485ae3a 100644 --- a/presto-spi/src/main/java/com/facebook/presto/spi/analyzer/MetadataResolver.java +++ b/presto-spi/src/main/java/com/facebook/presto/spi/analyzer/MetadataResolver.java @@ -116,13 +116,4 @@ default MaterializedViewStatus getMaterializedViewStatus(QualifiedObjectName mat { throw new UnsupportedOperationException("getMaterializedViewStatus is not supported"); } - - /** - * Get the materialized view status to inform the engine how much data has been materialized in the view - * @param materializedViewName materialized view name - */ - default MaterializedViewStatus getMaterializedViewStatus(QualifiedObjectName materializedViewName) - { - return getMaterializedViewStatus(materializedViewName, TupleDomain.all()); - } } 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 b4cc0b5ada522..5ebb507b5e0d5 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 @@ -56,6 +56,7 @@ import io.airlift.slice.Slice; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; @@ -723,6 +724,34 @@ default Optional getMaterializedView(ConnectorSessio return Optional.empty(); } + /** + * List materialized view names in the specified schema. + * This method is used to populate information_schema.materialized_views. + */ + default List listMaterializedViews(ConnectorSession session, String schemaName) + { + // Default implementation returns empty list (connectors without MV support) + return emptyList(); + } + + /** + * Get multiple materialized view definitions at once. + * Connectors can override this for more efficient bulk retrieval. + * Default implementation calls getMaterializedView() for each view. + */ + default Map getMaterializedViews( + ConnectorSession session, + String schemaName, + List viewNames) + { + Map result = new HashMap<>(); + for (SchemaTableName viewName : viewNames) { + getMaterializedView(session, viewName).ifPresent(definition -> + result.put(viewName, definition)); + } + return result; + } + /** * Create the specified materialized view. The data for the materialized view is opaque to the connector. */ @@ -750,11 +779,6 @@ default MaterializedViewStatus getMaterializedViewStatus(ConnectorSession sessio throw new PrestoException(NOT_SUPPORTED, "This connector does not support getting materialized views status"); } - default MaterializedViewStatus getMaterializedViewStatus(ConnectorSession session, SchemaTableName materializedViewName) - { - throw new PrestoException(NOT_SUPPORTED, "This connector does not support getting materialized views status"); - } - /** * Begin refresh materialized view */ diff --git a/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java b/presto-spi/src/main/java/com/facebook/presto/spi/connector/classloader/ClassLoaderSafeConnectorMetadata.java index 54e6a6e54638f..a02b5ee4d92da 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 @@ -538,6 +538,25 @@ public List listViews(ConnectorSession session, String schemaNa } } + @Override + public List listMaterializedViews(ConnectorSession session, String schemaName) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.listMaterializedViews(session, schemaName); + } + } + + @Override + public Map getMaterializedViews( + ConnectorSession session, + String schemaName, + List viewNames) + { + try (ThreadContextClassLoader ignored = new ThreadContextClassLoader(classLoader)) { + return delegate.getMaterializedViews(session, schemaName, viewNames); + } + } + @Override public Map getViews(ConnectorSession session, SchemaTablePrefix prefix) { @@ -570,12 +589,6 @@ public void dropMaterializedView(ConnectorSession session, SchemaTableName viewN } } - @Override - public MaterializedViewStatus getMaterializedViewStatus(ConnectorSession session, SchemaTableName materializedViewName) - { - return delegate.getMaterializedViewStatus(session, materializedViewName); - } - @Override public MaterializedViewStatus getMaterializedViewStatus(ConnectorSession session, SchemaTableName materializedViewName, TupleDomain baseQueryDomain) {