Skip to content

feat(plugin-iceberg): Add support for materialized views with Hive catalog#26958

Merged
tdcmeehan merged 3 commits intoprestodb:masterfrom
tdcmeehan:mv-ice-hive
Jan 30, 2026
Merged

feat(plugin-iceberg): Add support for materialized views with Hive catalog#26958
tdcmeehan merged 3 commits intoprestodb:masterfrom
tdcmeehan:mv-ice-hive

Conversation

@tdcmeehan
Copy link
Copy Markdown
Contributor

@tdcmeehan tdcmeehan commented Jan 13, 2026

Description

Add support for materialized views with Hive catalog

Motivation and Context

So users of the Hive metastore catalog can use Materialized Views as well

Impact

Now all catalogs will support materialized views

Test Plan

I've migrated tests to use both HMS and REST catalogs

Contributor checklist

  • Please make sure your submission complies with our contributing guide, in particular code style and commit standards.
  • PR description addresses the issue accurately and concisely. If the change is non-trivial, a GitHub Issue is referenced.
  • Documented new properties (with its default value), SQL syntax, functions, or other functionality.
  • If release notes are required, they follow the release notes guidelines.
  • Adequate tests were added if applicable.
  • CI passed.
  • If adding new dependencies, verified they have an OpenSSF Scorecard score of 5.0 or higher (or obtained explicit TSC approval for lower scores).

Release Notes

Please follow release notes guidelines and fill in the release notes below.

== RELEASE NOTES ==

Iceberg Connector Changes
* Add support for Materialized Views in Iceberg catalog

Summary by Sourcery

Add a shared Iceberg view metadata abstraction and extend the Iceberg connector to support materialized views in both REST and Hive catalogs.

New Features:

  • Enable creation, listing, and management of Iceberg materialized views when using the Hive metastore catalog.
  • Provide unified handling of Iceberg view and materialized view metadata across different Iceberg catalog implementations.

Enhancements:

  • Refactor Iceberg metadata access to use a catalog-agnostic IcebergViewMetadata wrapper instead of directly exposing native Iceberg views.

Tests:

  • Introduce an abstract materialized view test base and add concrete suites for both REST and Hive Iceberg catalogs to validate materialized view behavior.

@tdcmeehan tdcmeehan requested review from a team, ZacBlanco and hantangwangd as code owners January 13, 2026 21:15
@prestodb-ci prestodb-ci added the from:IBM PR from IBM label Jan 13, 2026
@prestodb-ci prestodb-ci requested review from a team, bibith4 and infvg and removed request for a team January 13, 2026 21:15
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai bot commented Jan 13, 2026

Reviewer's Guide

Refactors Iceberg view handling to use a shared IcebergViewMetadata abstraction and adds full Hive-metastore catalog support for Iceberg-backed materialized views, with tests split to validate both REST and Hive catalogs.

Sequence diagram for creating an Iceberg materialized view with Hive catalog

sequenceDiagram
    actor User
    participant PrestoEngine
    participant IcebergHiveMetadata
    participant Metastore

    User->>PrestoEngine: CREATE MATERIALIZED VIEW sql
    PrestoEngine->>IcebergHiveMetadata: createMaterializedView(session, viewName, definition)
    IcebergHiveMetadata->>IcebergHiveMetadata: createIcebergView(session, viewName, columns, viewSql, properties)
    IcebergHiveMetadata->>IcebergHiveMetadata: build tableProperties with PRESTO_MATERIALIZED_VIEW_FLAG and metadata
    IcebergHiveMetadata->>Metastore: createTable(metastoreContext, table, privileges, partitions)
    Metastore-->>IcebergHiveMetadata: table created
    IcebergHiveMetadata->>IcebergHiveMetadata: tableCache.invalidate(viewName)
    IcebergHiveMetadata-->>PrestoEngine: materialized view created
    PrestoEngine-->>User: success
Loading

Class diagram for Iceberg view metadata handling and materialized views

classDiagram
    class IcebergAbstractMetadata {
        <<abstract>>
        +Table getIcebergTable(ConnectorSession session, SchemaTableName schemaTableName)
        +ConnectorTableMetadata getTableOrViewMetadata(ConnectorSession session, SchemaTableName schemaTableName)
        +List~SchemaTableName~ listMaterializedViews(ConnectorSession session, String schemaName)
        +Optional~MaterializedViewDefinition~ getMaterializedView(ConnectorSession session, SchemaTableName viewName)
        +MaterializedViewStatus getMaterializedViewStatus(ConnectorSession session, SchemaTableName materializedViewName)
        #Table getRawIcebergTable(ConnectorSession session, SchemaTableName schemaTableName)
        #Optional~IcebergViewMetadata~ getViewMetadata(ConnectorSession session, SchemaTableName viewName)
        #void createIcebergView(ConnectorSession session, SchemaTableName viewName, List~ViewColumn~ columns, String viewSql, Map~String,String~ properties)
        #void dropIcebergView(ConnectorSession session, SchemaTableName schemaTableName)
        #void updateIcebergViewProperties(ConnectorSession session, SchemaTableName viewName, Map~String,String~ properties)
        -boolean viewExists(ConnectorSession session, ConnectorTableMetadata viewMetadata)
    }

    class IcebergNativeMetadata {
        +Table getRawIcebergTable(ConnectorSession session, SchemaTableName schemaTableName)
        -View getIcebergView(ConnectorSession session, SchemaTableName schemaTableName)
        +Optional~IcebergViewMetadata~ getViewMetadata(ConnectorSession session, SchemaTableName viewName)
        +boolean tableExists(ConnectorSession session, SchemaTableName schemaTableName)
        -Map~SchemaTableName,View~ icebergViews
        -CatalogFactory catalogFactory
    }

    class IcebergHiveMetadata {
        +Table getRawIcebergTable(ConnectorSession session, SchemaTableName schemaTableName)
        +Optional~IcebergViewMetadata~ getViewMetadata(ConnectorSession session, SchemaTableName viewName)
        +boolean tableExists(ConnectorSession session, SchemaTableName schemaTableName)
        +List~SchemaTableName~ listMaterializedViews(ConnectorSession session, String schemaName)
        +void createIcebergView(ConnectorSession session, SchemaTableName viewName, List~ViewColumn~ columns, String viewSql, Map~String,String~ properties)
        +void dropIcebergView(ConnectorSession session, SchemaTableName schemaTableName)
        +void updateIcebergViewProperties(ConnectorSession session, SchemaTableName viewName, Map~String,String~ properties)
        -boolean isIcebergMaterializedView(Table table)
        -Optional~Table~ getHiveTable(ConnectorSession session, SchemaTableName schemaTableName)
        -Metastore metastore
        -TableCache tableCache
    }

    class IcebergViewMetadata {
        -Map~String,String~ properties
        -ConnectorTableMetadata tableMetadata
        +IcebergViewMetadata(Map~String,String~ properties, ConnectorTableMetadata tableMetadata)
        +Map~String,String~ getProperties()
        +ConnectorTableMetadata getTableMetadata()
        +boolean isMaterializedView()
    }

    class ConnectorTableMetadata {
        +SchemaTableName getTable()
        +List~ColumnMetadata~ getColumns()
        +Map~String,Object~ getProperties()
        +Optional~String~ getComment()
    }

    class Table {
        +Map~String,String~ getParameters()
        +List~HiveColumn~ getDataColumns()
        +String getOwner()
        +static Table builder(Table table)
        +Table setParameters(Map~String,String~ parameters)
        +Table build()
    }

    class Metastore {
        +Optional~List~String~~ getAllViews(MetastoreContext context, String schemaName)
        +void createTable(MetastoreContext context, Table table, PrincipalPrivileges privileges, List~Partition~ partitions)
        +void dropTable(MetastoreContext context, String schemaName, String tableName, boolean deleteData)
        +void replaceTable(MetastoreContext context, String schemaName, String tableName, Table table, PrincipalPrivileges privileges)
    }

    class CatalogFactory {
        +Catalog getCatalog(ConnectorSession session)
    }

    class Catalog {
    }

    class ViewCatalog {
    }

    IcebergAbstractMetadata <|-- IcebergNativeMetadata
    IcebergAbstractMetadata <|-- IcebergHiveMetadata

    IcebergAbstractMetadata --> IcebergViewMetadata : uses
    IcebergNativeMetadata --> IcebergViewMetadata : constructs
    IcebergHiveMetadata --> IcebergViewMetadata : constructs

    IcebergViewMetadata --> ConnectorTableMetadata : wraps

    IcebergNativeMetadata --> CatalogFactory
    CatalogFactory --> Catalog
    ViewCatalog ..|> Catalog

    IcebergHiveMetadata --> Metastore
    IcebergHiveMetadata --> Table

    IcebergHiveMetadata --> TableCache
Loading

File-Level Changes

Change Details Files
Refactor Iceberg abstract metadata to work with a catalog-agnostic IcebergViewMetadata wrapper instead of directly returning Iceberg View objects, and update materialized view logic to use it.
  • Replace the abstract getIcebergView method with getViewMetadata returning Optional and update callers accordingly
  • Change table-or-view metadata resolution to derive ConnectorTableMetadata from IcebergViewMetadata, throwing TableNotFoundException when absent
  • Rewrite listMaterializedViews, getMaterializedView, getMaterializedViewStatus, and viewExists to use IcebergViewMetadata and its properties rather than raw Iceberg View instances
  • Introduce IcebergViewMetadata class to encapsulate view properties and ConnectorTableMetadata and expose an isMaterializedView helper
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergViewMetadata.java
Implement view metadata and materialized view operations for the Hive catalog so Iceberg materialized views are stored and managed via the Hive metastore.
  • Implement getViewMetadata for the Hive catalog by loading the Hive table, verifying it is an Iceberg materialized view, and mapping Hive columns and parameters into ConnectorTableMetadata and properties
  • Update table existence checks to treat Iceberg materialized views as non-tables and continue to validate Iceberg table types
  • Implement listMaterializedViews by enumerating Hive views in the schema and filtering those recognized as Iceberg materialized views
  • Implement createIcebergView to create a Hive table representing the materialized view with the required Presto/Iceberg view properties and encoded view definition
  • Implement dropIcebergView and updateIcebergViewProperties to drop or replace the underlying Hive table and invalidate the Iceberg table cache
  • Add an isIcebergMaterializedView helper that detects Iceberg materialized views via materialized view format version properties
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java
Adapt native Iceberg catalog view handling to the new IcebergViewMetadata abstraction while keeping its existing view caching behavior.
  • Make the existing getIcebergView method private and keep using the per-view cache
  • Add getViewMetadata implementation that, when the catalog supports views, wraps the loaded Iceberg View into IcebergViewMetadata with computed column metadata, properties, and comments
  • Return empty when the catalog does not implement ViewCatalog or the view is missing
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergNativeMetadata.java
Restructure and extend materialized view tests to cover both REST and Hive Iceberg catalogs using a shared base class.
  • Extract common materialized view tests into an abstract base class that holds the warehouse location and shared test methods
  • Create REST-backed test class that starts a TestingHttpServer, wires a REST IcebergQueryRunner with appropriate connector properties, and creates the test schema
  • Create Hive-backed test class that configures a Hive IcebergQueryRunner using a temporary warehouse directory and creates the test schema
  • Ensure test environments disable legacy materialized views via connector properties
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViewsBase.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/rest/TestIcebergMaterializedViewsRest.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergMaterializedViewsHive.java

Possibly linked issues

  • #: The PR implements the requested basic Iceberg materialized view support, including metadata handling and catalog integration.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • The isIcebergMaterializedView helper checks for PRESTO_MATERIALIZED_VIEW_FORMAT_VERSION, but createIcebergView only sets PRESTO_MATERIALIZED_VIEW_FLAG and other properties; aligning the detection logic with the properties written at creation time will avoid misclassifying views.
  • dropIcebergView unconditionally drops the Hive table without checking isIcebergMaterializedView, whereas updateIcebergViewProperties and getIcebergView validate the type; consider adding a similar guard to avoid dropping non-materialized-view tables via this path.
  • In TestIcebergMaterializedViewsBase, the catalogType field and constructor parameter are currently unused; either remove them or use them in the subclasses’ createQueryRunner to avoid dead code.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The `isIcebergMaterializedView` helper checks for `PRESTO_MATERIALIZED_VIEW_FORMAT_VERSION`, but `createIcebergView` only sets `PRESTO_MATERIALIZED_VIEW_FLAG` and other properties; aligning the detection logic with the properties written at creation time will avoid misclassifying views.
- `dropIcebergView` unconditionally drops the Hive table without checking `isIcebergMaterializedView`, whereas `updateIcebergViewProperties` and `getIcebergView` validate the type; consider adding a similar guard to avoid dropping non-materialized-view tables via this path.
- In `TestIcebergMaterializedViewsBase`, the `catalogType` field and constructor parameter are currently unused; either remove them or use them in the subclasses’ `createQueryRunner` to avoid dead code.

## Individual Comments

### Comment 1
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java:794-803` </location>
<code_context>
     }

     @Override
     protected void dropIcebergView(ConnectorSession session, SchemaTableName schemaTableName)
     {
-        throw new PrestoException(NOT_SUPPORTED, "Iceberg Hive catalog does not support native Iceberg views for materialized views.");
+        MetastoreContext metastoreContext = getMetastoreContext(session);
+
+        try {
+            metastore.dropTable(
+                    metastoreContext,
+                    schemaTableName.getSchemaName(),
+                    schemaTableName.getTableName(),
+                    true);
+        }
+        catch (TableNotFoundException e) {
+            throw new PrestoException(NOT_FOUND, "Materialized view not found: " + schemaTableName);
+        }
+
</code_context>

<issue_to_address>
**issue (bug_risk):** dropIcebergView drops any table with the given name without verifying it is an Iceberg materialized view.

To align with `updateIcebergViewProperties` and `getIcebergView`, this method should first load the table, verify `isIcebergMaterializedView`, and only then call `dropTable`. If the table is missing or not an Iceberg materialized view, it should throw a NOT_FOUND (or similar) error instead of dropping it.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 794 to +803
protected void dropIcebergView(ConnectorSession session, SchemaTableName schemaTableName)
{
throw new PrestoException(NOT_SUPPORTED, "Iceberg Hive catalog does not support native Iceberg views for materialized views.");
MetastoreContext metastoreContext = getMetastoreContext(session);

try {
metastore.dropTable(
metastoreContext,
schemaTableName.getSchemaName(),
schemaTableName.getTableName(),
true);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): dropIcebergView drops any table with the given name without verifying it is an Iceberg materialized view.

To align with updateIcebergViewProperties and getIcebergView, this method should first load the table, verify isIcebergMaterializedView, and only then call dropTable. If the table is missing or not an Iceberg materialized view, it should throw a NOT_FOUND (or similar) error instead of dropping it.

@tdcmeehan tdcmeehan requested review from aaneja and zation99 January 14, 2026 01:29
@tdcmeehan tdcmeehan closed this Jan 16, 2026
@tdcmeehan tdcmeehan reopened this Jan 16, 2026
@tdcmeehan tdcmeehan marked this pull request as draft January 16, 2026 03:05
@tdcmeehan tdcmeehan force-pushed the mv-ice-hive branch 4 times, most recently from aa6ea71 to ca9238b Compare January 22, 2026 18:43
@tdcmeehan tdcmeehan marked this pull request as ready for review January 22, 2026 20:59
@prestodb-ci prestodb-ci requested a review from a team January 22, 2026 20:59
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 3 issues, and left some high level feedback:

  • The change in IcebergAbstractMetadata.getTableOrViewMetadata from getIcebergView throwing NoSuchViewException/NOT_SUPPORTED to getViewMetadata returning Optional.empty() means catalogs that don’t implement ViewCatalog will now surface TableNotFoundException instead of a more explicit NOT_SUPPORTED error for views; consider whether this behavioral change is acceptable or if a NOT_SUPPORTED path should be preserved.
  • In IcebergHiveMetadata.getViewMetadata, the ConnectorTableMetadata is built with ImmutableMap.of() as properties, ignoring table.getParameters() (other than comment); if any of these parameters are expected downstream (e.g., for compatibility with native view metadata), consider passing through relevant parameters or aligning with createViewMetadataProperties used in the native path.
  • The Hive-specific isIcebergMaterializedView and listMaterializedViews logic is keyed solely off PRESTO_MATERIALIZED_VIEW_FORMAT_VERSION and duplicates some of the generic materialized-view detection; consider centralizing this in IcebergViewMetadata.isMaterializedView()/getViewMetadata to reduce the chance of divergence between Hive and native catalogs over time.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- The change in `IcebergAbstractMetadata.getTableOrViewMetadata` from `getIcebergView` throwing `NoSuchViewException`/`NOT_SUPPORTED` to `getViewMetadata` returning `Optional.empty()` means catalogs that don’t implement `ViewCatalog` will now surface `TableNotFoundException` instead of a more explicit NOT_SUPPORTED error for views; consider whether this behavioral change is acceptable or if a NOT_SUPPORTED path should be preserved.
- In `IcebergHiveMetadata.getViewMetadata`, the `ConnectorTableMetadata` is built with `ImmutableMap.of()` as properties, ignoring `table.getParameters()` (other than comment); if any of these parameters are expected downstream (e.g., for compatibility with native view metadata), consider passing through relevant parameters or aligning with `createViewMetadataProperties` used in the native path.
- The Hive-specific `isIcebergMaterializedView` and `listMaterializedViews` logic is keyed solely off `PRESTO_MATERIALIZED_VIEW_FORMAT_VERSION` and duplicates some of the generic materialized-view detection; consider centralizing this in `IcebergViewMetadata.isMaterializedView()`/`getViewMetadata` to reduce the chance of divergence between Hive and native catalogs over time.

## Individual Comments

### Comment 1
<location> `presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergHiveMetadata.java:843-849` </location>
<code_context>
+                .setParameters(mergedProperties.buildKeepingLast())
+                .build();
+
+        PrincipalPrivileges privileges = buildInitialPrivilegeSet(table.getOwner());
+        metastore.replaceTable(
+                metastoreContext,
+                viewName.getSchemaName(),
+                viewName.getTableName(),
+                updatedTable,
+                privileges);
+
+        tableCache.invalidate(viewName);
</code_context>

<issue_to_address>
**🚨 issue (security):** Updating view properties may unintentionally reset privileges instead of preserving existing ones.

In `updateIcebergViewProperties`, calling `buildInitialPrivilegeSet(table.getOwner())` before `replaceTable` will reinitialize privileges and discard any existing fine-grained grants on the view. If this method is only meant to change properties, consider preserving the current privileges (or passing `Optional.empty()`/an equivalent no-op per the metastore API) so that access control is not unintentionally altered by a properties-only update.
</issue_to_address>

### Comment 2
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/hive/TestIcebergMaterializedViewsHive.java:32` </location>
<code_context>
+import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
+
+@Test(singleThreaded = true)
+public class TestIcebergMaterializedViewsHive
+        extends TestIcebergMaterializedViewsBase
+{
</code_context>

<issue_to_address>
**suggestion (testing):** Consider adding a Hive-specific test that asserts `SHOW MATERIALIZED VIEWS`/`listMaterializedViews` only returns Iceberg materialized views, not regular Iceberg tables or views

Since the Hive implementation of `listMaterializedViews` relies on `metastore.getAllViews` and `isIcebergMaterializedView`, it would be good to add a Hive-specific assertion in this test class. For example:

- Create an Iceberg materialized view plus a regular Iceberg table and/or non-materialized view in `test_schema`.
- Assert that `SHOW MATERIALIZED VIEWS FROM test_schema` (or the equivalent `information_schema` query) returns only the materialized view.

This will exercise the Hive metastore wiring and `isIcebergMaterializedView` behavior and guard against regressions in how HMS identifies materialized views.
</issue_to_address>

### Comment 3
<location> `presto-iceberg/src/test/java/com/facebook/presto/iceberg/rest/TestIcebergMaterializedViewsRest.java:35` </location>
<code_context>
+import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;
+
+@Test(singleThreaded = true)
+public class TestIcebergMaterializedViewsRest
+        extends TestIcebergMaterializedViewsBase
+{
</code_context>

<issue_to_address>
**suggestion (testing):** Add a REST-catalog-specific assertion around materialized view metadata retrieval failures (e.g., invalid or missing MV properties)

Since the REST catalog still uses native Iceberg views (vs. Hive’s HMS tables), the new `getMaterializedView` logic that depends on `IcebergViewMetadata` isn’t fully exercised by the shared base tests. Please add a REST-specific test in this class that corrupts or removes materialized view properties (via direct catalog/metastore manipulation or `ALTER TABLE/VIEW`, if possible) and verifies that:

- `SHOW CREATE MATERIALIZED VIEW` or `SELECT * FROM system.metadata.materialized_views` fails with `ICEBERG_INVALID_MATERIALIZED_VIEW`, or
- `getMaterializedView` returns empty when required properties are missing.

This will directly test the REST-specific `IcebergViewMetadata` wiring and error handling.

Suggested implementation:

```java
import java.util.Optional;

import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.rest.RESTCatalog;
import org.apache.iceberg.view.View;
import org.apache.iceberg.view.ViewUpdate;

import static com.facebook.presto.iceberg.CatalogType.REST;
import static com.facebook.presto.iceberg.IcebergQueryRunner.builder;
import static com.facebook.presto.iceberg.rest.IcebergRestTestUtil.getRestServer;
import static com.facebook.presto.iceberg.rest.IcebergRestTestUtil.restConnectorProperties;
import static com.google.common.io.MoreFiles.deleteRecursively;
import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;

```

To fully implement the REST-catalog-specific assertions for invalid/missing materialized view metadata, add the following to `TestIcebergMaterializedViewsRest`:

1. **Helper to corrupt MV properties in the REST/Iceberg catalog**

   Inside `TestIcebergMaterializedViewsRest` (as a `private` method), add something along these lines, using the REST catalog that backs `restServer`:

   ```java
   private void corruptMaterializedViewProperties(String schema, String name)
   {
       // Obtain the underlying Iceberg REST catalog from the test REST server.
       // Adjust this depending on the actual type returned by getRestServer().
       RESTCatalog catalog = (RESTCatalog) getRestServer(restServer).getCatalog();

       TableIdentifier identifier = TableIdentifier.of(schema, name);
       View view = catalog.loadView(identifier);

       // Copy and corrupt properties by removing / altering MV-specific ones
       // (these keys must match what IcebergViewMetadata expects)
       java.util.Map<String, String> properties = new java.util.HashMap<>(view.properties());
       properties.remove("presto.materialized-view.target");
       properties.remove("presto.materialized-view.query");
       // You can also deliberately corrupt required properties instead of removing them:
       // properties.put("presto.materialized-view.target", "invalid-target");

       ViewUpdate update = catalog.updateView(identifier);
       update.setProperties(properties);
       update.commit();
   }
   ```

   You will likely need to adjust `getRestServer(restServer).getCatalog()` to match the actual API. The important part is:
   - obtain the `RESTCatalog`,
   - load the Iceberg `View` corresponding to the Presto materialized view,
   - remove or corrupt the properties that `IcebergViewMetadata` depends on.

2. **REST-specific test: SHOW CREATE MATERIALIZED VIEW fails with ICEBERG_INVALID_MATERIALIZED_VIEW**

   Still in `TestIcebergMaterializedViewsRest`, add a new test method:

   ```java
   @Test
   public void testShowCreateMaterializedViewFailsOnInvalidMetadata()
   {
       String schema = "tpch"; // adjust if the base class uses a different default schema
       String mvName = "test_invalid_mv_metadata_rest";

       assertUpdate("CREATE MATERIALIZED VIEW " + mvName + " AS SELECT 1 x");

       // Corrupt the underlying Iceberg view metadata via REST catalog
       corruptMaterializedViewProperties(schema, mvName);

       // SHOW CREATE MATERIALIZED VIEW should now fail with ICEBERG_INVALID_MATERIALIZED_VIEW
       assertQueryFails(
               "SHOW CREATE MATERIALIZED VIEW " + mvName,
               ".*ICEBERG_INVALID_MATERIALIZED_VIEW.*");
   }
   ```

   This directly exercises the `IcebergViewMetadata` lookup path used by `getMaterializedView` for REST and verifies that invalid metadata is surfaced as `ICEBERG_INVALID_MATERIALIZED_VIEW`.

3. **(Optional but recommended) REST-specific test via `system.metadata.materialized_views`**

   To additionally cover the `getMaterializedView` wiring used by `system.metadata.materialized_views`, add:

   ```java
   @Test
   public void testSystemMetadataMaterializedViewsFailsOnInvalidMetadata()
   {
       String schema = "tpch";
       String mvName = "test_invalid_mv_metadata_system_rest";

       assertUpdate("CREATE MATERIALIZED VIEW " + mvName + " AS SELECT 1 x");

       corruptMaterializedViewProperties(schema, mvName);

       // Accessing MV metadata via system table should also fail with the same error
       assertQueryFails(
               "SELECT * FROM system.metadata.materialized_views WHERE catalog_name = 'iceberg' AND schema_name = '" + schema + "' AND name = '" + mvName + "'",
               ".*ICEBERG_INVALID_MATERIALIZED_VIEW.*");
   }
   ```

4. **If `getMaterializedView` is exposed directly in the base test infrastructure**

   If the base test class exposes a helper that calls `ConnectorMetadata.getMaterializedView`, add an additional test that calls that helper after corrupting the properties and verifies that it returns `Optional.empty()` when the properties are missing rather than malformed. For example:

   ```java
   @Test
   public void testGetMaterializedViewReturnsEmptyWhenPropertiesMissing()
   {
       String schema = "tpch";
       String mvName = "test_missing_mv_properties_rest";

       assertUpdate("CREATE MATERIALIZED VIEW " + mvName + " AS SELECT 1 x");

       // Adjust the helper to *remove* all MV-identifying properties so that metadata lookup treats it as absent
       corruptMaterializedViewProperties(schema, mvName);

       Optional<?> materializedView = getMaterializedView("iceberg", schema, mvName);
       assertEquals(materializedView, Optional.empty());
   }
   ```

   You’ll need to align the call to `getMaterializedView(...)` with whatever helper the base class provides (catalog name, schema name, and MV name parameters).

These additions will ensure that the REST-specific `IcebergViewMetadata` path is exercised and that invalid or missing materialized view metadata is correctly surfaced as `ICEBERG_INVALID_MATERIALIZED_VIEW` or as an empty result from `getMaterializedView`, as requested in your review comment.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +843 to +849
PrincipalPrivileges privileges = buildInitialPrivilegeSet(table.getOwner());
metastore.replaceTable(
metastoreContext,
viewName.getSchemaName(),
viewName.getTableName(),
updatedTable,
privileges);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 issue (security): Updating view properties may unintentionally reset privileges instead of preserving existing ones.

In updateIcebergViewProperties, calling buildInitialPrivilegeSet(table.getOwner()) before replaceTable will reinitialize privileges and discard any existing fine-grained grants on the view. If this method is only meant to change properties, consider preserving the current privileges (or passing Optional.empty()/an equivalent no-op per the metastore API) so that access control is not unintentionally altered by a properties-only update.

import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;

@Test(singleThreaded = true)
public class TestIcebergMaterializedViewsHive
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding a Hive-specific test that asserts SHOW MATERIALIZED VIEWS/listMaterializedViews only returns Iceberg materialized views, not regular Iceberg tables or views

Since the Hive implementation of listMaterializedViews relies on metastore.getAllViews and isIcebergMaterializedView, it would be good to add a Hive-specific assertion in this test class. For example:

  • Create an Iceberg materialized view plus a regular Iceberg table and/or non-materialized view in test_schema.
  • Assert that SHOW MATERIALIZED VIEWS FROM test_schema (or the equivalent information_schema query) returns only the materialized view.

This will exercise the Hive metastore wiring and isIcebergMaterializedView behavior and guard against regressions in how HMS identifies materialized views.

import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;

@Test(singleThreaded = true)
public class TestIcebergMaterializedViewsRest
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add a REST-catalog-specific assertion around materialized view metadata retrieval failures (e.g., invalid or missing MV properties)

Since the REST catalog still uses native Iceberg views (vs. Hive’s HMS tables), the new getMaterializedView logic that depends on IcebergViewMetadata isn’t fully exercised by the shared base tests. Please add a REST-specific test in this class that corrupts or removes materialized view properties (via direct catalog/metastore manipulation or ALTER TABLE/VIEW, if possible) and verifies that:

  • SHOW CREATE MATERIALIZED VIEW or SELECT * FROM system.metadata.materialized_views fails with ICEBERG_INVALID_MATERIALIZED_VIEW, or
  • getMaterializedView returns empty when required properties are missing.

This will directly test the REST-specific IcebergViewMetadata wiring and error handling.

Suggested implementation:

import java.util.Optional;

import org.apache.iceberg.catalog.TableIdentifier;
import org.apache.iceberg.rest.RESTCatalog;
import org.apache.iceberg.view.View;
import org.apache.iceberg.view.ViewUpdate;

import static com.facebook.presto.iceberg.CatalogType.REST;
import static com.facebook.presto.iceberg.IcebergQueryRunner.builder;
import static com.facebook.presto.iceberg.rest.IcebergRestTestUtil.getRestServer;
import static com.facebook.presto.iceberg.rest.IcebergRestTestUtil.restConnectorProperties;
import static com.google.common.io.MoreFiles.deleteRecursively;
import static com.google.common.io.RecursiveDeleteOption.ALLOW_INSECURE;

To fully implement the REST-catalog-specific assertions for invalid/missing materialized view metadata, add the following to TestIcebergMaterializedViewsRest:

  1. Helper to corrupt MV properties in the REST/Iceberg catalog

    Inside TestIcebergMaterializedViewsRest (as a private method), add something along these lines, using the REST catalog that backs restServer:

    private void corruptMaterializedViewProperties(String schema, String name)
    {
        // Obtain the underlying Iceberg REST catalog from the test REST server.
        // Adjust this depending on the actual type returned by getRestServer().
        RESTCatalog catalog = (RESTCatalog) getRestServer(restServer).getCatalog();
    
        TableIdentifier identifier = TableIdentifier.of(schema, name);
        View view = catalog.loadView(identifier);
    
        // Copy and corrupt properties by removing / altering MV-specific ones
        // (these keys must match what IcebergViewMetadata expects)
        java.util.Map<String, String> properties = new java.util.HashMap<>(view.properties());
        properties.remove("presto.materialized-view.target");
        properties.remove("presto.materialized-view.query");
        // You can also deliberately corrupt required properties instead of removing them:
        // properties.put("presto.materialized-view.target", "invalid-target");
    
        ViewUpdate update = catalog.updateView(identifier);
        update.setProperties(properties);
        update.commit();
    }

    You will likely need to adjust getRestServer(restServer).getCatalog() to match the actual API. The important part is:

    • obtain the RESTCatalog,
    • load the Iceberg View corresponding to the Presto materialized view,
    • remove or corrupt the properties that IcebergViewMetadata depends on.
  2. REST-specific test: SHOW CREATE MATERIALIZED VIEW fails with ICEBERG_INVALID_MATERIALIZED_VIEW

    Still in TestIcebergMaterializedViewsRest, add a new test method:

    @Test
    public void testShowCreateMaterializedViewFailsOnInvalidMetadata()
    {
        String schema = "tpch"; // adjust if the base class uses a different default schema
        String mvName = "test_invalid_mv_metadata_rest";
    
        assertUpdate("CREATE MATERIALIZED VIEW " + mvName + " AS SELECT 1 x");
    
        // Corrupt the underlying Iceberg view metadata via REST catalog
        corruptMaterializedViewProperties(schema, mvName);
    
        // SHOW CREATE MATERIALIZED VIEW should now fail with ICEBERG_INVALID_MATERIALIZED_VIEW
        assertQueryFails(
                "SHOW CREATE MATERIALIZED VIEW " + mvName,
                ".*ICEBERG_INVALID_MATERIALIZED_VIEW.*");
    }

    This directly exercises the IcebergViewMetadata lookup path used by getMaterializedView for REST and verifies that invalid metadata is surfaced as ICEBERG_INVALID_MATERIALIZED_VIEW.

  3. (Optional but recommended) REST-specific test via system.metadata.materialized_views

    To additionally cover the getMaterializedView wiring used by system.metadata.materialized_views, add:

    @Test
    public void testSystemMetadataMaterializedViewsFailsOnInvalidMetadata()
    {
        String schema = "tpch";
        String mvName = "test_invalid_mv_metadata_system_rest";
    
        assertUpdate("CREATE MATERIALIZED VIEW " + mvName + " AS SELECT 1 x");
    
        corruptMaterializedViewProperties(schema, mvName);
    
        // Accessing MV metadata via system table should also fail with the same error
        assertQueryFails(
                "SELECT * FROM system.metadata.materialized_views WHERE catalog_name = 'iceberg' AND schema_name = '" + schema + "' AND name = '" + mvName + "'",
                ".*ICEBERG_INVALID_MATERIALIZED_VIEW.*");
    }
  4. If getMaterializedView is exposed directly in the base test infrastructure

    If the base test class exposes a helper that calls ConnectorMetadata.getMaterializedView, add an additional test that calls that helper after corrupting the properties and verifies that it returns Optional.empty() when the properties are missing rather than malformed. For example:

    @Test
    public void testGetMaterializedViewReturnsEmptyWhenPropertiesMissing()
    {
        String schema = "tpch";
        String mvName = "test_missing_mv_properties_rest";
    
        assertUpdate("CREATE MATERIALIZED VIEW " + mvName + " AS SELECT 1 x");
    
        // Adjust the helper to *remove* all MV-identifying properties so that metadata lookup treats it as absent
        corruptMaterializedViewProperties(schema, mvName);
    
        Optional<?> materializedView = getMaterializedView("iceberg", schema, mvName);
        assertEquals(materializedView, Optional.empty());
    }

    You’ll need to align the call to getMaterializedView(...) with whatever helper the base class provides (catalog name, schema name, and MV name parameters).

These additions will ensure that the REST-specific IcebergViewMetadata path is exercised and that invalid or missing materialized view metadata is correctly surfaced as ICEBERG_INVALID_MATERIALIZED_VIEW or as an empty result from getMaterializedView, as requested in your review comment.

Copy link
Copy Markdown
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tdcmeehan for this feature, overall looks good! I've left some quick questions and points for discussion.

Copy link
Copy Markdown
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @tdcmeehan for the fix and clarification. Just a few nits left, otherwise looks good to me!

@tdcmeehan
Copy link
Copy Markdown
Contributor Author

As always, thanks @hantangwangd for your thorough review. I've updated based on your feedback.

Copy link
Copy Markdown
Member

@hantangwangd hantangwangd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lgtm! Thanks for the fix @tdcmeehan.

@tdcmeehan tdcmeehan merged commit 32207b9 into prestodb:master Jan 30, 2026
172 of 176 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

from:IBM PR from IBM

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants