Skip to content

fix(spark): fetch schema from HMS when it is not found in FileSystem - #19536

Open
nada-attia wants to merge 2 commits into
apache:masterfrom
nada-attia:nada-attia/cherrypick-hms-schema-fetch
Open

fix(spark): fetch schema from HMS when it is not found in FileSystem#19536
nada-attia wants to merge 2 commits into
apache:masterfrom
nada-attia:nada-attia/cherrypick-hms-schema-fetch

Conversation

@nada-attia

Copy link
Copy Markdown
Contributor

Describe the issue this Pull Request addresses

In HoodieBaseRelation, schema is assumed to always be resolvable from the table's commit metadata or data files. For datasets that contain no hudi partitions (only non-hudi partitions), or that have no data written yet, schema cannot be
resolved that way. Fall back to fetching the schema from HMS in that case instead of throwing.

Adds a unit test covering select on a freshly created, empty table.

Summary and Changelog

Reads on a Hudi table would throw when TableSchemaResolver could not resolve a schema from commit metadata or data files on the file system — e.g. a table with no hudi partitions (only non-hudi partitions), or one with no data written yet.
HoodieBaseRelation now catches that failure and falls back to reading the table's schema from the Hive Metastore (HMS) catalog entry instead of throwing.

  • HoodieBaseRelation.scala: on TableSchemaResolver.getTableSchema failure, fetch the catalog table via sparkSession.sessionState.catalog.externalCatalog.getTable(...) and convert its schema instead of propagating the exception.
  • TestCreateTable.scala: added Test Select On Empty Table Falls Back To HMS Schema, which creates a table with no data written and verifies a select on it succeeds (schema resolved from HMS) rather than throwing.

Impact

No public API changes. Read behavior change: a select on a table whose schema can't be resolved from the file system (e.g. empty table, or non-hudi-only partitions) now succeeds using the HMS-registered schema instead of throwing.

Risk Level

low

Documentation Update

none

Contributor's checklist

  • Read through contributor's guide
  • Enough context is provided in the sections above
  • Adequate tests were added if applicable

In HoodieBaseRelation, schema is assumed to always be resolvable from
the table's commit metadata or data files. For datasets that contain
no hudi partitions (only non-hudi partitions), or that have no data
written yet, schema cannot be resolved that way. Fall back to fetching
the schema from HMS in that case instead of throwing.

Adds a unit test covering select on a freshly created, empty table.
@github-actions github-actions Bot added the size:S PR with lines of changes in (10, 100] label Aug 5, 2026

@hudi-agent hudi-agent left a comment

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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for working on this! The PR makes HoodieBaseRelation fall back to the HMS catalog schema when TableSchemaResolver can't resolve a schema from commit metadata or data files, so reads on empty (or non-Hudi-partition-only) tables no longer throw. A couple of edge cases around the breadth of the fallback and path-based / HMS-absent tables are worth double-checking in the inline comments. Please take a look at any inline comments, and this should be ready for a Hudi committer or PMC member to take it from here. One minor redundancy below; overall the change is clean and the test comment is genuinely useful.

Try(schemaResolver.getTableSchema) match {
case Success(schema) => schema
case Failure(e) => throw e
case Failure(_) =>

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.

🤖 This falls back to HMS on any getTableSchema failure, not just the intended empty/no-data case. If the table actually has data but resolution fails for another reason (transient IO, corruption, or a schema evolved after create), we'd silently read with the possibly-stale HMS create-schema instead of failing loudly. Could we gate the fallback on the table genuinely having no resolvable schema (e.g. no completed commits / no base files), and chain the original exception into anything we rethrow so the root cause isn't lost?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

case Failure(_) =>
// Schema not found on the dataset, so fetching schema from HMS.
logWarning(s"Schema not found on the dataset for $tableName, so fetching schema from HMS.")
val catalogTable = sparkSession.sessionState.catalog.externalCatalog.getTable(metaClient.getDbName, metaClient.getTableName)

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.

🤖 For path-based reads (or tables not registered in the current session catalog), metaClient.getDbName / getTableName may not correspond to an HMS entry, and externalCatalog.getTable(...) will throw NoSuchDatabase/NoSuchTable, replacing the original schema error with a more confusing one. Is the fallback guaranteed to run only when the table is HMS-registered? It may be worth catching the not-found case and rethrowing the original resolver failure.

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

// Schema not found on the dataset, so fetching schema from HMS.
logWarning(s"Schema not found on the dataset for $tableName, so fetching schema from HMS.")
val catalogTable = sparkSession.sessionState.catalog.externalCatalog.getTable(metaClient.getDbName, metaClient.getTableName)
convertToHoodieSchema(catalogTable.schema, tableName)

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.

🤖 The normal getTableSchema() path includes the hoodie* meta fields (when populateMetaFields is true). Spark-SQL-created tables do persist meta fields in the HMS schema, so this works for the added test, but tables registered via Hive sync / DataSource writer may store the catalog schema without meta fields. Have you checked that catalogTable.schema here always includes the meta fields, so tableStructSchema stays consistent with the non-fallback path?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

case Failure(e) => throw e
case Failure(_) =>
// Schema not found on the dataset, so fetching schema from HMS.
logWarning(s"Schema not found on the dataset for $tableName, so fetching schema from HMS.")

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.

🤖 nit: the inline comment repeats the log message verbatim — could you drop one of them? The logWarning already serves as self-documentation here.

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@codecov-commenter

codecov-commenter commented Aug 5, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 0% with 9 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.50%. Comparing base (1f8e53a) to head (42c8811).
⚠️ Report is 24 commits behind head on master.

Files with missing lines Patch % Lines
...ain/scala/org/apache/hudi/HoodieBaseRelation.scala 0.00% 9 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19536      +/-   ##
============================================
+ Coverage     77.17%   77.50%   +0.33%     
+ Complexity    33948    33102     -846     
============================================
  Files          2575     2522      -53     
  Lines        143397   139187    -4210     
  Branches      17579    16896     -683     
============================================
- Hits         110662   107878    -2784     
+ Misses        24476    23742     -734     
+ Partials       8259     7567     -692     
Components Coverage Δ
hudi-common 83.29% <100.00%> (+0.88%) ⬆️
hudi-client 82.71% <ø> (+0.15%) ⬆️
hudi-flink 85.81% <69.23%> (+1.84%) ⬆️
hudi-spark-datasource 70.58% <0.00%> (-4.52%) ⬇️
hudi-utilities 73.66% <ø> (+0.02%) ⬆️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 68.96% <ø> (+5.21%) ⬆️
hudi-sync 75.11% <ø> (+4.16%) ⬆️
hudi-io 79.37% <ø> (-0.23%) ⬇️
hudi-timeline-service 83.44% <ø> (ø)
hudi-cloud 64.06% <100.00%> (+0.06%) ⬆️
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 50.84% <0.00%> (+1.28%) ⬆️
flink-integration-tests 49.30% <ø> (+0.50%) ⬆️
hadoop-mr-java-client 43.84% <ø> (+0.13%) ⬆️
integration-tests 13.62% <0.00%> (+0.03%) ⬆️
spark-client-hadoop-common 50.46% <ø> (+0.82%) ⬆️
spark-java-tests 51.69% <0.00%> (+0.23%) ⬆️
spark-scala-tests 46.07% <0.00%> (-1.36%) ⬇️
utilities 36.64% <0.00%> (+0.04%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...ain/scala/org/apache/hudi/HoodieBaseRelation.scala 77.46% <0.00%> (-2.60%) ⬇️

... and 372 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

case Failure(_) =>
// Schema not found on the dataset, so fetching schema from HMS.
logWarning(s"Schema not found on the dataset for $tableName, so fetching schema from HMS.")
val catalogTable = sparkSession.sessionState.catalog.externalCatalog.getTable(metaClient.getDbName, metaClient.getTableName)

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.

if the table does not exist in the catalog, we should throw appropriate msg.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes sure updated the PR

@hudi-agent hudi-agent left a comment

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.

⚠️ 🤖 This review was generated by an AI agent and may contain mistakes. Please verify any suggestions before applying.

Thanks for addressing the feedback! The updated diff now guards the HMS fallback with externalCatalog.tableExists(...) and throws a HoodieException that chains the original resolver exception when the table isn't in the catalog — this resolves the path-based/HMS-absent concern raised by @nsivabalan and in the prior review. One prior concern remains partially open: when the table does exist in the catalog, the fallback still triggers on any resolution failure (not just the empty/no-data case), which could silently substitute a stale HMS schema — flagged inline. The redundant inline comment (nit) and the Hive-synced meta-fields question from the last pass are also still open. Please take a look at any inline comments, and this should be ready for a Hudi committer or PMC member to take it from here.

case Failure(e) =>
// Schema not found on the dataset, so fetching schema from HMS.
logWarning(s"Schema not found on the dataset for $tableName, so fetching schema from HMS.")
val databaseName = metaClient.getTableConfig.getDatabaseName

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.

🤖 Nice improvement chaining the original exception on the not-found path. One thing still open from last round: when the table does exist in the catalog, we fall back to the HMS schema on any getTableSchema failure — not just the empty/no-data case. If a table with real data fails resolution transiently (IO) or after a write-path schema evolution, we'd silently read under the (possibly stale) HMS create-schema instead of surfacing e. Could we gate the fallback on the table genuinely having no resolvable schema (e.g. no completed commits / no base files)?

⚠️ AI-generated; verify before applying. React 👍/👎 to flag quality.

@hudi-bot

Copy link
Copy Markdown
Collaborator

CI report:

Bot commands @hudi-bot supports the following commands:
  • @hudi-bot run azure re-run the last Azure build

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

Labels

size:S PR with lines of changes in (10, 100]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants