Skip to content

fix(plugin-iceberg): Do not check access control for MV data table#27728

Open
tdcmeehan wants to merge 2 commits intoprestodb:masterfrom
tdcmeehan:mvac
Open

fix(plugin-iceberg): Do not check access control for MV data table#27728
tdcmeehan wants to merge 2 commits intoprestodb:masterfrom
tdcmeehan:mvac

Conversation

@tdcmeehan
Copy link
Copy Markdown
Contributor

@tdcmeehan tdcmeehan commented May 6, 2026

Description

Treat Iceberg materialized view storage tables as system-managed:

  • Skip access control on the storage table during MV expansion.
  • Re-point analyzer output fields at the MV name so column refs are recorded against
    the MV, not the storage table.
  • Use the MV's own schema (not the storage schema) for the view-query and refresh
    sessions.
  • Add iceberg.materialized-view-default-storage-schema config to place storage
    tables in a single schema.

Direct queries against a storage table by name still go through normal access
control. Legacy materialized views are unchanged.

Motivation and Context

The non-legacy MV path checked SELECT on the storage table against the session user.
That let an unprivileged user bypass SECURITY DEFINER by querying
__mv_storage__<view> directly, and prevented operators from locking down the
storage schema without breaking legitimate MV reads.

Storage tables are not user-facing — operators should be able to lock down the
storage schema and have that be the entire access-control story.

Impact

  • New config iceberg.materialized-view-default-storage-schema (default: unset).
    Per-MV storage_schema still overrides.
  • Non-legacy MV reads no longer require any privilege on the storage table.
  • View-query and refresh sessions use the MV's schema for unqualified name resolution.

Test Plan

Test integration tests have been added.

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.
  • Documented new properties.
  • Release notes follow the guidelines.
  • Adequate tests added.
  • CI passed.
  • No new dependencies.

Release Notes

== RELEASE NOTES ==

Iceberg Connector Changes
* Fix access control for materialized view storage tables when ``legacy_materialized_views=false``: storage-table access control is bypassed during MV expansion, while direct queries by name still go through access control.
* Add ``iceberg.materialized-view-default-storage-schema`` config to route storage tables into a single schema. Defaults to the materialized view's own schema; per-MV ``storage_schema`` overrides.

Summary by Sourcery

Treat Iceberg materialized view storage tables as system-managed resources and adjust analysis, configuration, and tests accordingly.

New Features:

  • Add configurable default schema for Iceberg materialized view storage tables via the iceberg.materialized-view-default-storage-schema property.

Bug Fixes:

  • Bypass access control checks on Iceberg materialized view storage tables during materialized view expansion while preserving access control for direct storage-table queries.
  • Align materialized view analysis to resolve unqualified names and record column references against the materialized view schema instead of the underlying storage schema.

Enhancements:

  • Extend statement analysis for refresh and query of materialized views to use the view’s schema for owner sessions when legacy materialized views are disabled.

Documentation:

  • Document the new iceberg.materialized-view-default-storage-schema configuration option in the Iceberg connector documentation.

Tests:

  • Add integration-style tests covering default storage schema behavior, SECURITY DEFINER access without storage privileges, and end-to-end access control when the storage schema is locked down.

@prestodb-ci prestodb-ci added the from:IBM PR from IBM label May 6, 2026
@prestodb-ci prestodb-ci requested review from a team, agrawalreetika and namya28 and removed request for a team May 6, 2026 20:37
@sourcery-ai
Copy link
Copy Markdown
Contributor

sourcery-ai Bot commented May 6, 2026

Reviewer's Guide

Adjusts Iceberg materialized view handling so storage tables are treated as system-managed: analyzer uses the MV schema for view query/refresh, bypasses access control when expanding the storage table and rewrites output field origins to the MV, introduces a configurable default storage schema for storage tables, and adds tests covering access control and schema placement behavior.

Sequence diagram for non-legacy Iceberg materialized view query expansion

sequenceDiagram
    actor User
    participant Coordinator as Coordinator
    participant Analyzer as StatementAnalyzer_main
    participant MVAnalyzer as StatementAnalyzer_materializedView
    participant StorageAnalyzer as StatementAnalyzer_storageTable_AllowAll

    User->>Coordinator: submit SELECT from materialized_view
    Coordinator->>Analyzer: analyze query

    Analyzer->>MVAnalyzer: analyze viewQuery with MV schema
    MVAnalyzer-->>Analyzer: viewQuery analysis complete

    Analyzer->>StorageAnalyzer: analyze storage dataTable with AllowAllAccessControl
    StorageAnalyzer-->>Analyzer: Scope with storage table fields

    Analyzer->>StorageAnalyzer: rewrite Field origins to materialized_view name
    StorageAnalyzer-->>Analyzer: RelationType with MV-qualified fields

    Analyzer-->>Coordinator: analysis with column references bound to MV
    Coordinator-->>User: execute plan (storage table access control bypassed during expansion)
Loading

Sequence diagram for Iceberg materialized view refresh session schema selection

sequenceDiagram
    actor Admin
    participant Coordinator as Coordinator
    participant Analyzer as StatementAnalyzer_refresh

    Admin->>Coordinator: REFRESH MATERIALIZED VIEW view_name
    Coordinator->>Analyzer: visitRefreshMaterializedView

    alt legacy_materialized_views is true
        Analyzer->>Analyzer: ownerSessionSchema = view.getSchema()
    else non-legacy path
        Analyzer->>Analyzer: ownerSessionSchema = viewName.getSchemaName()
    end

    Analyzer->>Analyzer: buildOwnerSession(ownerSessionSchema)
    Analyzer->>Analyzer: analyze refreshQuery with owner session
    Analyzer-->>Coordinator: refresh analysis complete
    Coordinator-->>Admin: refresh executed using MV schema for name resolution
Loading

Updated class diagram for IcebergConfig and related materialized view storage schema handling

classDiagram
    class IcebergConfig {
        -String materializedViewStoragePrefix
        -String materializedViewDefaultStorageSchema
        +String getMaterializedViewStoragePrefix()
        +IcebergConfig setMaterializedViewStoragePrefix(String materializedViewStoragePrefix)
        +String getMaterializedViewDefaultStorageSchema()
        +IcebergConfig setMaterializedViewDefaultStorageSchema(String materializedViewDefaultStorageSchema)
        +int getMaterializedViewMaxChangedPartitions()
        +IcebergConfig setMaterializedViewMaxChangedPartitions(int materializedViewMaxChangedPartitions)
    }

    class IcebergSessionProperties {
        <<utility>>
        +String MATERIALIZED_VIEW_STORAGE_PREFIX
        +String MATERIALIZED_VIEW_DEFAULT_STORAGE_SCHEMA
        +String MATERIALIZED_VIEW_MAX_CHANGED_PARTITIONS
        +IcebergSessionProperties(IcebergConfig icebergConfig)
        +static String getMaterializedViewStoragePrefix(ConnectorSession session)
        +static Optional~String~ getMaterializedViewDefaultStorageSchema(ConnectorSession session)
        +static int getMaterializedViewMaxChangedPartitions(ConnectorSession session)
    }

    class IcebergAbstractMetadata {
        -SchemaTableName getStorageTableName(ConnectorSession session, SchemaTableName viewName, PropertiesMap properties)
    }

    class ConnectorSession
    class SchemaTableName {
        +String getSchemaName()
        +String getTableName()
    }

    class PropertiesMap {
    }

    IcebergSessionProperties --> IcebergConfig
    IcebergSessionProperties ..> ConnectorSession
    IcebergAbstractMetadata ..> ConnectorSession
    IcebergAbstractMetadata ..> SchemaTableName
    IcebergAbstractMetadata ..> IcebergSessionProperties
    IcebergAbstractMetadata ..> PropertiesMap
Loading

File-Level Changes

Change Details Files
Change how refresh and expansion of materialized views determine the session schema and analyze the storage table so that access control checks apply to the MV, not the underlying storage table.
  • In refreshMaterializedView analysis, derive the owner session schema from the materialized view name when legacy materialized views are disabled instead of always using the view definition schema.
  • In processMaterializedView, build the materialized view session using the MV name’s schema rather than the storage table schema for unqualified name resolution.
  • During MV expansion, analyze the storage table using a StatementAnalyzer wired with AllowAllAccessControl so that access to the system-managed storage table is not checked during expansion.
  • Rebuild the RelationType for the storage table so each Field is re-qualified with the MV name and origin table set to the MV, ensuring column references are attributed to the MV and not to the storage table.
presto-main-base/src/main/java/com/facebook/presto/sql/analyzer/StatementAnalyzer.java
Introduce a connector and session configuration property to control the default schema used for Iceberg materialized view storage tables and wire it into name resolution.
  • Add materializedViewDefaultStorageSchema field and getter/setter in IcebergConfig, including a documented config key iceberg.materialized-view-default-storage-schema.
  • Expose MATERIALIZED_VIEW_DEFAULT_STORAGE_SCHEMA session property in IcebergSessionProperties with a stringProperty whose default comes from IcebergConfig and whose getter returns an Optional.
  • Modify getStorageTableName in IcebergAbstractMetadata to derive the storage table schema from the storage_schema table property if present, otherwise from the new default-storage-schema session property, falling back to the MV’s own schema.
  • Extend TestIcebergConfig defaults and explicit property mappings to cover the new configuration key and ensure it is correctly wired.
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergConfig.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergSessionProperties.java
presto-iceberg/src/main/java/com/facebook/presto/iceberg/IcebergAbstractMetadata.java
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergConfig.java
Add Iceberg integration tests to validate default storage schema behavior and access control semantics around materialized view storage tables, including SECURITY DEFINER/INVOKER behavior and locked-down schemas.
  • Add testDefaultStorageSchemaConfig to verify that when materialized_view_default_storage_schema is set, the MV storage table is created in the specified schema, is not visible at the default schema, and the MV reads and refreshes work correctly while row counts in the storage table match expectations.
  • Add testSecurityDefinerDoesNotRequireStorageAccessForReader to assert that a SECURITY DEFINER MV can be queried by a restricted user without SELECT privileges on the storage table or base table, while direct access to the storage table by name is denied.
  • Add testStorageSchemaLockdownEndToEnd to simulate locking down a dedicated storage schema: deny SELECT on storage tables to all identities, refresh SECURITY DEFINER and SECURITY INVOKER MVs successfully, confirm both privileged and restricted users can read from the MVs, and ensure direct storage-table queries fail for both.
  • Ensure each test cleans up created schemas, tables, materialized views, and resets the test access control state in finally blocks.
presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViewsBase.java
Update Iceberg connector documentation to describe the new default storage schema configuration and changed semantics for materialized view storage tables when legacy materialized views are disabled.
  • Extend Iceberg connector docs to note that materialized view storage tables are treated as system-managed and that access control is bypassed during MV expansion when legacy_materialized_views=false, while direct storage-table queries still require privileges.
  • Document the new iceberg.materialized-view-default-storage-schema configuration property, its default behavior, and how it interacts with per-MV storage_schema overrides and locked-down schemas.
presto-docs/src/main/sphinx/connector/iceberg.rst

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 2 issues, and left some high level feedback:

  • In StatementAnalyzer.processMaterializedView, consider avoiding new AllowAllAccessControl() per analysis invocation (e.g., use a shared instance or a more targeted access-control wrapper) to reduce object churn and make the intent of bypassing only storage-table checks clearer.
  • The new materializedViewDefaultStorageSchema config is treated as nullable (and wrapped in an Optional later); adding explicit @Nullable annotations on the field and getter would make the null contract clearer and help static analysis.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `StatementAnalyzer.processMaterializedView`, consider avoiding `new AllowAllAccessControl()` per analysis invocation (e.g., use a shared instance or a more targeted access-control wrapper) to reduce object churn and make the intent of bypassing only storage-table checks clearer.
- The new `materializedViewDefaultStorageSchema` config is treated as nullable (and wrapped in an `Optional` later); adding explicit `@Nullable` annotations on the field and getter would make the null contract clearer and help static analysis.

## Individual Comments

### Comment 1
<location path="presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViewsBase.java" line_range="4923-4932" />
<code_context>
+    public void testDefaultStorageSchemaConfig()
</code_context>
<issue_to_address>
**suggestion (testing):** Add a test case where explicit `storage_schema` table property overrides the default-storage-schema config

To fully cover the precedence rules, please add a complementary test where the materialized view is created with an explicit `storage_schema` table property. That test should assert that:

- The storage table is created in the explicitly configured schema rather than the one from `materialized_view_default_storage_schema`.
- Queries and refresh operate correctly in this setup, including enforcing access control on the explicitly chosen storage schema.

This will help catch regressions where the connector mishandles the per-MV `storage_schema` vs. the default schema.

Suggested implementation:

```java
    @Test
    public void testStorageSchemaTablePropertyOverridesDefault()
    {
        assertUpdate("CREATE SCHEMA IF NOT EXISTS mv_storage_isolated");
        assertUpdate("CREATE SCHEMA IF NOT EXISTS mv_storage_explicit");
        assertUpdate("CREATE TABLE test_storage_schema_property_base (id BIGINT, name VARCHAR)");
        assertUpdate("INSERT INTO test_storage_schema_property_base VALUES (1, 'Alice'), (2, 'Bob')", 2);

        Session sessionWithDefaultSchema = Session.builder(getSession())
                .setCatalogSessionProperty("iceberg", "materialized_view_default_storage_schema", "mv_storage_isolated")
                .build();

        assertUpdate(
                sessionWithDefaultSchema,
                "CREATE MATERIALIZED VIEW test_storage_schema_property_mv " +
                        "WITH (storage_schema = 'mv_storage_explicit') AS " +
                        "SELECT id, name FROM test_storage_schema_property_base");

        // Ensure that the underlying storage table is created in the explicit schema,
        // not in the default storage schema.
        assertQuery(
                "SELECT table_schema " +
                        "FROM iceberg.information_schema.tables " +
                        "WHERE table_name LIKE 'test_storage_schema_property_mv$%'",
                "VALUES 'mv_storage_explicit'");

        // Queries against the materialized view should work correctly.
        assertQuery(
                sessionWithDefaultSchema,
                "SELECT * FROM test_storage_schema_property_mv",
                "SELECT * FROM test_storage_schema_property_base");

        // Refresh should also work correctly with the explicit storage schema.
        assertUpdate(
                sessionWithDefaultSchema,
                "INSERT INTO test_storage_schema_property_base VALUES (3, 'Charlie')",
                1);
        assertUpdate(
                sessionWithDefaultSchema,
                "REFRESH MATERIALIZED VIEW test_storage_schema_property_mv",
                1);

        assertQuery(
            sessionWithDefaultSchema,
            "SELECT * FROM test_storage_schema_property_mv",
            "SELECT * FROM test_storage_schema_property_base");
    }

    @Test
    public void testDefaultStorageSchemaConfig()

```

To also cover access control behavior on the explicit storage schema (as mentioned in your review comment), you will likely need to:

1. Reuse the existing access control setup patterns in this test class (for example, using `getQueryRunner().getAccessControl()` with `deny`/`reset` and the imported `privilege` / `SELECT_COLUMN` helpers) to:
   - Deny the appropriate privilege on the storage table (or schema, depending on how other MV tests are structured) in `mv_storage_explicit`.
   - Assert that queries/refresh on `test_storage_schema_property_mv` fail with the expected access control error when that privilege is denied.
2. Add the corresponding `try/finally` or cleanup to ensure access control state is reset after the test, following the conventions of the other tests in `TestIcebergMaterializedViewsBase`.

Since the exact privilege helper usage and object naming for the storage table likely mirror other tests in this file (e.g., other MV tests that check access control on storage tables), you should adapt the deny/assert logic to match those existing patterns.
</issue_to_address>

### Comment 2
<location path="presto-iceberg/src/test/java/com/facebook/presto/iceberg/TestIcebergMaterializedViewsBase.java" line_range="4990-4999" />
<code_context>
+    public void testStorageSchemaLockdownEndToEnd()
</code_context>
<issue_to_address>
**suggestion (testing):** Consider extending the lockdown test to cover explicit `storage_schema` usage and legacy MVs

The current end-to-end scenario validates the default `materialized_view_default_storage_schema` path. To fully exercise the new behavior, please also add tests for:

1) A MV that specifies an explicit `storage_schema` in a locked-down schema, to confirm the same access-control behavior when the storage location is set per-view.
2) The `legacy_materialized_views=true` configuration (if supported in this test base), to verify legacy MVs keep their existing semantics and are not affected by the new bypass/rewiring logic.

Even lightweight tests here would improve confidence that both configuration modes and MV types behave as intended.

Suggested implementation:

```java
        assertUpdate("CREATE TABLE storage_lockdown_base (id BIGINT, value BIGINT)");
        assertUpdate("INSERT INTO storage_lockdown_base VALUES (1, 100), (2, 200), (3, 300)", 3);

        // Materialized view that explicitly sets its storage_schema to the locked-down schema
        assertUpdate("CREATE MATERIALIZED VIEW storage_lockdown_explicit_storage_mv " +
                "WITH (storage_schema = 'mv_locked_storage') " +
                "SECURITY DEFINER AS " +
                "SELECT id, value FROM storage_lockdown_base");

        // Legacy materialized view, to ensure legacy semantics are preserved and not affected by rewiring
        Session legacyMvSession = Session.builder(getSession())
                .setCatalogSessionProperty("iceberg", "legacy_materialized_views", "true")
                .build();
        assertUpdate(legacyMvSession, "CREATE MATERIALIZED VIEW storage_lockdown_legacy_mv AS " +
                "SELECT id, value FROM storage_lockdown_base");

        Session sessionWithDefaultSchema = Session.builder(getSession())
                .setCatalogSessionProperty("iceberg", "materialized_view_default_storage_schema", "mv_locked_storage")
                .build();

        assertUpdate(sessionWithDefaultSchema, "CREATE MATERIALIZED VIEW storage_lockdown_definer_mv SECURITY DEFINER AS " +

```

1. Wherever this test currently asserts behavior for `storage_lockdown_definer_mv` (access control on the locked-down storage schema, refresh behavior, query behavior, etc.), add equivalent assertions for:
   - `storage_lockdown_explicit_storage_mv` to prove that explicitly setting `WITH (storage_schema = 'mv_locked_storage')` follows the same lockdown/bypass semantics.
   - `storage_lockdown_legacy_mv` to prove that enabling `legacy_materialized_views=true` preserves legacy behavior and is not impacted by the new rewiring logic.
2. In the cleanup section of this test (likely a `finally` block), ensure you also drop the new objects:
   - `DROP MATERIALIZED VIEW storage_lockdown_explicit_storage_mv`
   - `DROP MATERIALIZED VIEW storage_lockdown_legacy_mv`
   - (Existing cleanup for `storage_lockdown_definer_mv`, `storage_lockdown_base`, and `mv_locked_storage` should remain.)
3. If the test currently configures a custom `AccessControl` or specific privileges for the locked-down schema, confirm that those checks are exercised against all three MVs (default storage schema MV, explicit storage_schema MV, and legacy MV).
</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 +4923 to +4932
public void testDefaultStorageSchemaConfig()
{
assertUpdate("CREATE SCHEMA IF NOT EXISTS mv_storage_isolated");
assertUpdate("CREATE TABLE test_default_storage_schema_base (id BIGINT, name VARCHAR)");
assertUpdate("INSERT INTO test_default_storage_schema_base VALUES (1, 'Alice'), (2, 'Bob')", 2);

Session sessionWithDefaultSchema = Session.builder(getSession())
.setCatalogSessionProperty("iceberg", "materialized_view_default_storage_schema", "mv_storage_isolated")
.build();

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 test case where explicit storage_schema table property overrides the default-storage-schema config

To fully cover the precedence rules, please add a complementary test where the materialized view is created with an explicit storage_schema table property. That test should assert that:

  • The storage table is created in the explicitly configured schema rather than the one from materialized_view_default_storage_schema.
  • Queries and refresh operate correctly in this setup, including enforcing access control on the explicitly chosen storage schema.

This will help catch regressions where the connector mishandles the per-MV storage_schema vs. the default schema.

Suggested implementation:

    @Test
    public void testStorageSchemaTablePropertyOverridesDefault()
    {
        assertUpdate("CREATE SCHEMA IF NOT EXISTS mv_storage_isolated");
        assertUpdate("CREATE SCHEMA IF NOT EXISTS mv_storage_explicit");
        assertUpdate("CREATE TABLE test_storage_schema_property_base (id BIGINT, name VARCHAR)");
        assertUpdate("INSERT INTO test_storage_schema_property_base VALUES (1, 'Alice'), (2, 'Bob')", 2);

        Session sessionWithDefaultSchema = Session.builder(getSession())
                .setCatalogSessionProperty("iceberg", "materialized_view_default_storage_schema", "mv_storage_isolated")
                .build();

        assertUpdate(
                sessionWithDefaultSchema,
                "CREATE MATERIALIZED VIEW test_storage_schema_property_mv " +
                        "WITH (storage_schema = 'mv_storage_explicit') AS " +
                        "SELECT id, name FROM test_storage_schema_property_base");

        // Ensure that the underlying storage table is created in the explicit schema,
        // not in the default storage schema.
        assertQuery(
                "SELECT table_schema " +
                        "FROM iceberg.information_schema.tables " +
                        "WHERE table_name LIKE 'test_storage_schema_property_mv$%'",
                "VALUES 'mv_storage_explicit'");

        // Queries against the materialized view should work correctly.
        assertQuery(
                sessionWithDefaultSchema,
                "SELECT * FROM test_storage_schema_property_mv",
                "SELECT * FROM test_storage_schema_property_base");

        // Refresh should also work correctly with the explicit storage schema.
        assertUpdate(
                sessionWithDefaultSchema,
                "INSERT INTO test_storage_schema_property_base VALUES (3, 'Charlie')",
                1);
        assertUpdate(
                sessionWithDefaultSchema,
                "REFRESH MATERIALIZED VIEW test_storage_schema_property_mv",
                1);

        assertQuery(
            sessionWithDefaultSchema,
            "SELECT * FROM test_storage_schema_property_mv",
            "SELECT * FROM test_storage_schema_property_base");
    }

    @Test
    public void testDefaultStorageSchemaConfig()

To also cover access control behavior on the explicit storage schema (as mentioned in your review comment), you will likely need to:

  1. Reuse the existing access control setup patterns in this test class (for example, using getQueryRunner().getAccessControl() with deny/reset and the imported privilege / SELECT_COLUMN helpers) to:
    • Deny the appropriate privilege on the storage table (or schema, depending on how other MV tests are structured) in mv_storage_explicit.
    • Assert that queries/refresh on test_storage_schema_property_mv fail with the expected access control error when that privilege is denied.
  2. Add the corresponding try/finally or cleanup to ensure access control state is reset after the test, following the conventions of the other tests in TestIcebergMaterializedViewsBase.

Since the exact privilege helper usage and object naming for the storage table likely mirror other tests in this file (e.g., other MV tests that check access control on storage tables), you should adapt the deny/assert logic to match those existing patterns.

Comment on lines +4990 to +4999
public void testStorageSchemaLockdownEndToEnd()
{
assertUpdate("CREATE SCHEMA IF NOT EXISTS mv_locked_storage");
assertUpdate("CREATE TABLE storage_lockdown_base (id BIGINT, value BIGINT)");
assertUpdate("INSERT INTO storage_lockdown_base VALUES (1, 100), (2, 200), (3, 300)", 3);

Session sessionWithDefaultSchema = Session.builder(getSession())
.setCatalogSessionProperty("iceberg", "materialized_view_default_storage_schema", "mv_locked_storage")
.build();

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 extending the lockdown test to cover explicit storage_schema usage and legacy MVs

The current end-to-end scenario validates the default materialized_view_default_storage_schema path. To fully exercise the new behavior, please also add tests for:

  1. A MV that specifies an explicit storage_schema in a locked-down schema, to confirm the same access-control behavior when the storage location is set per-view.
  2. The legacy_materialized_views=true configuration (if supported in this test base), to verify legacy MVs keep their existing semantics and are not affected by the new bypass/rewiring logic.

Even lightweight tests here would improve confidence that both configuration modes and MV types behave as intended.

Suggested implementation:

        assertUpdate("CREATE TABLE storage_lockdown_base (id BIGINT, value BIGINT)");
        assertUpdate("INSERT INTO storage_lockdown_base VALUES (1, 100), (2, 200), (3, 300)", 3);

        // Materialized view that explicitly sets its storage_schema to the locked-down schema
        assertUpdate("CREATE MATERIALIZED VIEW storage_lockdown_explicit_storage_mv " +
                "WITH (storage_schema = 'mv_locked_storage') " +
                "SECURITY DEFINER AS " +
                "SELECT id, value FROM storage_lockdown_base");

        // Legacy materialized view, to ensure legacy semantics are preserved and not affected by rewiring
        Session legacyMvSession = Session.builder(getSession())
                .setCatalogSessionProperty("iceberg", "legacy_materialized_views", "true")
                .build();
        assertUpdate(legacyMvSession, "CREATE MATERIALIZED VIEW storage_lockdown_legacy_mv AS " +
                "SELECT id, value FROM storage_lockdown_base");

        Session sessionWithDefaultSchema = Session.builder(getSession())
                .setCatalogSessionProperty("iceberg", "materialized_view_default_storage_schema", "mv_locked_storage")
                .build();

        assertUpdate(sessionWithDefaultSchema, "CREATE MATERIALIZED VIEW storage_lockdown_definer_mv SECURITY DEFINER AS " +
  1. Wherever this test currently asserts behavior for storage_lockdown_definer_mv (access control on the locked-down storage schema, refresh behavior, query behavior, etc.), add equivalent assertions for:
    • storage_lockdown_explicit_storage_mv to prove that explicitly setting WITH (storage_schema = 'mv_locked_storage') follows the same lockdown/bypass semantics.
    • storage_lockdown_legacy_mv to prove that enabling legacy_materialized_views=true preserves legacy behavior and is not impacted by the new rewiring logic.
  2. In the cleanup section of this test (likely a finally block), ensure you also drop the new objects:
    • DROP MATERIALIZED VIEW storage_lockdown_explicit_storage_mv
    • DROP MATERIALIZED VIEW storage_lockdown_legacy_mv
    • (Existing cleanup for storage_lockdown_definer_mv, storage_lockdown_base, and mv_locked_storage should remain.)
  3. If the test currently configures a custom AccessControl or specific privileges for the locked-down schema, confirm that those checks are exercised against all three MVs (default storage schema MV, explicit storage_schema MV, and legacy MV).

Copy link
Copy Markdown
Contributor

@steveburnett steveburnett left a comment

Choose a reason for hiding this comment

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

Something in the formatting of the table is causing it to not be displayed. See screenshot.

Image

jja725
jja725 previously approved these changes May 7, 2026
Copy link
Copy Markdown
Contributor

@jja725 jja725 left a comment

Choose a reason for hiding this comment

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

change make sense, thanks for the fix

@steveburnett
Copy link
Copy Markdown
Contributor

Thanks for the release note! Please revise the release note entry to start with a keyword from the Order of changes in the Release Notes Guidelines. See the error in the failing CI check.

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, lgtm! Just one nit about the docs.

================================================== ============================================================= ========================
``iceberg.materialized-view-storage-prefix`` Prefix applied to auto-generated storage table names. ``__mv_storage__``

``iceberg.materialized-view-default-storage-schema`` Schema in which storage tables are created when the (the view's own
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: looks like this line is not aligned, causing the table to fail to display.

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.

5 participants