Skip to content

HIVE-30195: Perform the table-level metastore filter hook check in get_partitions_with_specs - #6888

Open
wangyum wants to merge 2 commits into
apache:masterfrom
wangyum:hive.metastore.filter.hook.check
Open

wangyum wants to merge 2 commits into
apache:masterfrom
wangyum:hive.metastore.filter.hook.check

Conversation

@wangyum

@wangyum wangyum commented Oct 16, 2026

Copy link
Copy Markdown
Member

JIRA: HIVE-30195 (umbrella epic: HIVE-19715 · related: HIVE-30196 — same class of gap, the limit-check enforcement in the same RPC, handled in a separate PR)

Problem

get_partitions_with_specs (introduced by HIVE-20306) skipped FilterUtils.checkDbAndTableFilters — the table-level metastore filter hook check that every regular partition listing RPC performs upfront in GetPartitionsHandler.beforeExecute() → authorizeTableForPartitionMetadata().

With metastore.server.filter.enabled=true and a filter hook that hides the table from the caller (server-side result filtering, e.g. masking), the regular listing RPCs (get_partitions, get_partitions_by_names, get_partitions_with_auth, get_partitions_by_filter, get_partitions_by_expr, get_partitions_ps, get_partitions_ps_with_auth) fail fast with NoSuchObjectException, while get_partitions_with_specs still fetched all matching partitions and only applied partition-level filtering (FilterUtils.filterPartitionsIfEnabled) afterwards — whether rows are exposed depends on the hook's filterPartitions implementation.

Note: deployments that configure HiveMetaStoreAuthorizer as a pre-event listener are not affected — authorization is enforced by the pre-event; this gap affects the filter-hook (visibility/masking) mechanism specifically.

What this change does

Adds the missing check in HMSHandler.get_partitions_with_specs, right after firePreEvent(new PreReadTableEvent(table, this)), mirroring GetPartitionsHandler.beforeExecute() (including identifier normalization):

FilterUtils.checkDbAndTableFilters(isServerFilterEnabled, filterHook, normalizeIdentifier(catName),
    normalizeIdentifier(parsedDbName[DB_NAME]), normalizeIdentifier(tableName));

Authorization ordering is preserved: the pre-event listeners still run before the filter hook check, and the partition-level post-fetch filtering is unchanged.

Behavior change (only when metastore.server.filter.enabled=true; default is unchanged)

When the filter hook filters out the database or the table, get_partitions_with_specs now fails fast with NoSuchObjectException, matching every regular partition listing RPC. Previously it fetched all matching partitions and relied on the hook's post-fetch filterPartitions to trim the response.

Tests

TestFilterHooks.testFilterForPartition (server-side branch): added the getPartitionsWithSpecs assertion next to the existing getPartitionsByNames one — with the filter hook blocking the table, the RPC must fail with NoSuchObjectException like its siblings. Verified red→green: the new assertion fails without the fix (getPartitionsWithSpecs() should fail with blocking mode at server side) and passes with it.

Full verification on this branch: TestFilterHooks 4/4, TestHmsServerAuthorization 5/5 (authorization failure still precedes the filter check), TestGetPartitionsUsingProjectionAndFilterSpecs 25/25, TestRemoteHiveMetaStore#testGetPartitionsWithSpec and TestEmbeddedHiveMetaStore#testGetPartitionsWithSpec pass.

…t_partitions_with_specs

get_partitions_with_specs skipped FilterUtils.checkDbAndTableFilters - the
table-level metastore filter hook check every regular partition listing RPC
performs upfront in GetPartitionsHandler.authorizeTableForPartitionMetadata().
With metastore.server.filter.enabled=true and a filter hook that hides the
table, the regular listing RPCs fail fast with NoSuchObjectException while
get_partitions_with_specs still fetched all matching partitions and only
applied partition-level filtering afterwards.

Perform the same check (on normalized identifiers, mirroring
GetPartitionsHandler.beforeExecute()) right after the PreReadTableEvent, so
authorization ordering is preserved: the pre-event listeners still run first.

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

Copy link
Copy Markdown

@wangyum

wangyum commented Oct 17, 2026

Copy link
Copy Markdown
Member Author

cc @dengzhhu653

firePreEvent(new PreReadTableEvent(table, this));
// Perform the same table-level filter hook check the regular partition listing RPCs
// perform in GetPartitionsHandler.authorizeTableForPartitionMetadata()
FilterUtils.checkDbAndTableFilters(isServerFilterEnabled, filterHook, normalizeIdentifier(catName),

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.

This is not needed I think, as the PreReadTableEvent should have checked the permission, fire another check looks redundant. As the GetPartitionsHandler.authorizeTableForPartitionMetadata(), I think we can remove it, what do you think?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Thanks for looking at this! I don't think it's redundant — PreReadTableEvent and FilterUtils.checkDbAndTableFilters are two separate mechanisms:

  • firePreEvent(new PreReadTableEvent(...)) triggers pre-event listeners like AuthorizationPreEventListener, which does privilege-based authorization (throws AuthorizationException if the caller lacks SELECT).
  • FilterUtils.checkDbAndTableFilters calls the configured MetaStoreFilterHook (e.g. Ranger) for visibility filtering — it hides objects the caller shouldn't even know exist, by throwing NoSuchObjectException.

These aren't substitutes for each other, and the existing GetPartitionsHandler.beforeExecute() already calls both back-to-back (firePreEvent then authorizeTableForPartitionMetadata()), so this PR is just matching that established pattern rather than introducing a new one.

Concrete example: say Ranger is configured as the filter hook (metastore.server.filter.enabled=true), and user bob has no SELECT grant on sales.transactions.

  • Calling get_partitions today: authorizeTableForPartitionMetadata() filters the table name via the Ranger hook, gets an empty result, and fails fast with NoSuchObjectException — before touching any partition data.
  • Calling get_partitions_with_specs before this fix: HMS fetches the Table object, fires PreReadTableEvent (a no-op if no legacy AuthorizationPreEventListener is configured, which is common in Ranger-only setups), then goes ahead and fetches all matching partitions from the RawStore, and only filters them out afterward at the partition level. bob gets back an empty list instead of NoSuchObjectException — inconsistent with every other partition-listing RPC, and wasteful if the table has a large number of partitions.

This PR closes that gap by performing the same table-level check get_partitions already does, right after the PreReadTableEvent, so authorization ordering is preserved (pre-event listeners still run first) but the filter hook now also gets a chance to fail fast.

The test added in TestFilterHooks.testFilterForPartition covers exactly this: with a blocking filter hook, it asserts getPartitionsWithSpecs() throws NoSuchObjectException at the server side — which would fail if this check were removed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

One more reason this consistency matters: Apache Spark is about to start relying on getPartitionsWithSpecs more heavily. See apache/spark#58984, which switches write (InsertIntoHadoopFsRelationCommand), truncate (TruncateTableCommand), repair (RepairTableCommand), and metadata-only query call sites to a new listPartitionLocations API backed by this exact RPC (Shim_v4_0 on Hive 4+).

Those are write/DDL-adjacent code paths, not just read-only listing — so it's important that getPartitionsWithSpecs enforces the same table-level filter-hook check as every other partition-listing RPC. If this RPC behaved differently (e.g. silently returning an empty/partial result instead of failing with NoSuchObjectException when the caller shouldn't even see the table), Spark callers built on top of it could end up with inconsistent or unsafe behavior for users who are supposed to be filtered out. Keeping this check in place is what lets downstream consumers like Spark adopt the API with confidence that it behaves the same as the rest of the partition-listing surface.

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.

To get a full Ranger support, HMS should enable both the filter hook and pre-event listener, losing either of them can lead to the security gaps. The HiveMetaStoreAuthorizer is an example for both reacting to the pre-event and filtering the result based on authorization checks.
If a table passed on pre-event check, it doesn't need to check the filter hook again, as it's always green in such case.
The filter hooks is more for filtering the result before responding it to client, other than validating the request input.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants