Skip to content

fix(spark-sql): reject creating a table that pairs a non partitioned key generator with partition columns - #19505

Open
deepakpanda93 wants to merge 1 commit into
apache:masterfrom
deepakpanda93:fix/HUDI-5263-nonpartitioned-keygen-partitioned-by
Open

fix(spark-sql): reject creating a table that pairs a non partitioned key generator with partition columns#19505
deepakpanda93 wants to merge 1 commit into
apache:masterfrom
deepakpanda93:fix/HUDI-5263-nonpartitioned-keygen-partitioned-by

Conversation

@deepakpanda93

Copy link
Copy Markdown
Collaborator

Describe the issue this Pull Request addresses

Closes #15589 / HUDI-5263.

Spark SQL accepts a CREATE TABLE that declares partition columns and, at the same
time, configures a key generator that never produces a partition path:

create table hudi_cow_pt_tbl (
  id bigint, name string, ts bigint, dt string
) using hudi
tblproperties (
  type = 'cow',
  primaryKey = 'id',
  hoodie.table.keygenerator.class = 'org.apache.hudi.keygen.NonpartitionedKeyGenerator'
)
partitioned by (dt)

The statement succeeds and persists a table config that disagrees with itself. The
partition fields are taken from the partition columns in initHoodieTable, while the
key generator is taken from the table properties, which override the value
extraTableConfig inferred. Nothing cross checks the two.

The resulting table cannot be written to at all. Running against master, every write
path is rejected identically:

create table:                SUCCEEDED (inconsistent config persisted)
sql insert:                  FAILED - Config conflict: PartitionPath: <empty> vs dt
dsWrite(partitionpath=dt):   FAILED - Config conflict: PartitionPath: <empty> vs dt
dsWrite(nonpartitioned):     FAILED - Config conflict: PartitionPath: <empty> vs dt

So the behaviour has moved on from the original report: the null partition column is
no longer reachable, because a later guard blocks the write. What remains is that the
broken table is still created, and the eventual failure names neither the key
generator nor the partition columns, which are the two things actually in conflict.

Summary and Changelog

Creating a table with a non partitioned key generator and partition columns now fails
immediately, with a message that names both, instead of producing a table that cannot
be written to.

  • Adds validateKeyGeneratorForPartitionColumns to HoodieCatalogTable, invoked from
    parseSchemaAndConfigs on the path that creates a table.
  • Extracts resolvePartitionColumns from initHoodieTable so the validation and the
    value that gets persisted are derived the same way and cannot drift apart.
  • Covers NonpartitionedKeyGenerator and NonpartitionedAvroKeyGenerator, resolved
    through KeyGeneratorType.getKeyGeneratorClassName so the key generator class and
    key generator type properties are both honoured.
  • The check is deliberately limited to a table being created. An existing table already
    in this state stays readable and can still be registered in the catalog, so nobody is
    locked out of data they already have.
  • Tests added to TestCreateTable: the rejection, for both non partitioned key
    generator classes, and a companion test asserting a genuinely non partitioned table
    is still created and still writable, so the guard cannot over fire.

The new error reads:

Cannot create table 'spark_catalog.default.hudi_cow_pt_tbl' partitioned by 'dt' using
key generator 'org.apache.hudi.keygen.NonpartitionedKeyGenerator', which does not
generate a partition path. Either drop the partition columns, or configure a
partitioned key generator through 'hoodie.table.keygenerator.class'.

Impact

A CREATE TABLE that previously succeeded now fails. This breaks no working workflow:
a table created that way is rejected on every subsequent write, as shown above, so the
statement only ever produced an unusable table. The failure simply moves to the
statement that causes it.

Confined to the Spark SQL table creation path in HoodieCatalogTable. No change to the
read path, the write path, or the datasource API. Tables that already exist in this
state are untouched.

Risk Level

low

The validation runs only while creating a table, and only fires on a combination that
is provably unusable. Verified that no existing test pairs a non partitioned key
generator with partition columns; the whole org.apache.spark.sql.hudi.ddl package
passes.

Documentation Update

None. No new config, no public API change.

Contributor's checklist

  • Read through contributor's guide
  • Change Logs and Impact were stated clearly
  • Adequate tests were added if applicable
  • CI passed

…key generator with partition columns

A non partitioned key generator never produces a partition path, so pairing
one with PARTITIONED BY describes a table that cannot exist. CREATE TABLE
accepted it and persisted a table config that disagrees with itself: the
partition fields came from the partition columns while the key generator
came from the table properties, and nothing cross checked the two.

Every subsequent write to such a table is rejected for a partition path
conflict that names neither the key generator nor the partition columns,
so the table is unusable from the moment it is created.

Reject the combination while the statement that introduced it is still in
hand. The check applies only to a table being created, so an existing table
already in this state stays readable and can still be registered.
@github-actions github-actions Bot added the size:M PR with lines of changes in (100, 300] label Aug 4, 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 rejects CREATE TABLE statements that pair a non-partitioned key generator with partition columns, failing fast with a message naming both instead of persisting a self-contradictory table config that no write can succeed against. I traced the validation path (it's on the shared (_, false) create arm covering both managed and external tables), confirmed resolvePartitionColumns is a behavior-preserving extraction, and checked that the guard only fires on the genuinely contradictory case (non-empty partition columns + NON_PARTITION/NON_PARTITION_AVRO keygen resolved from either class name or type), so there are no false positives. No correctness issues found. A few style/readability suggestions in the inline comments. Please take a look, and this should be ready for a Hudi committer or PMC member to take it from here. One small Scala style nit below; the logic and naming are otherwise clean.

cc @yihua

val partitionColumns = resolvePartitionColumns(tableConfigs)
if (!StringUtils.isNullOrEmpty(partitionColumns)) {
val keyGenerator = KeyGeneratorType.getKeyGeneratorClassName(tableConfigs.asJava)
if (KeyGeneratorType.NON_PARTITION.getClassName.equals(keyGenerator)

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: could you use Scala's == here instead of .equals()? KeyGeneratorType.NON_PARTITION.getClassName == keyGenerator || KeyGeneratorType.NON_PARTITION_AVRO.getClassName == keyGenerator reads more naturally in Scala and is equivalent for String.

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

@voonhous voonhous closed this Aug 4, 2026
@voonhous voonhous reopened this Aug 4, 2026
@voonhous voonhous closed this Aug 4, 2026
@voonhous voonhous reopened this Aug 4, 2026
@hudi-bot

hudi-bot commented Aug 4, 2026

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

@codecov-commenter

codecov-commenter commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 76.99%. Comparing base (55c7a30) to head (aed5adb).
⚠️ Report is 31 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19505      +/-   ##
============================================
- Coverage     77.03%   76.99%   -0.04%     
- Complexity    33863    33883      +20     
============================================
  Files          2575     2575              
  Lines        143379   143409      +30     
  Branches      17574    17581       +7     
============================================
- Hits         110451   110424      -27     
- Misses        24666    24730      +64     
+ Partials       8262     8255       -7     
Components Coverage Δ
hudi-common 82.30% <100.00%> (+0.02%) ⬆️
hudi-client 81.84% <100.00%> (-0.27%) ⬇️
hudi-flink 83.97% <ø> (+0.01%) ⬆️
hudi-spark-datasource 75.11% <100.00%> (+0.01%) ⬆️
hudi-utilities 73.65% <ø> (-0.02%) ⬇️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.72% <100.00%> (+0.18%) ⬆️
hudi-sync 71.00% <90.90%> (+0.09%) ⬆️
hudi-io 79.60% <ø> (-0.10%) ⬇️
hudi-timeline-service 83.83% <ø> (+0.39%) ⬆️
hudi-cloud 64.00% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 49.55% <0.00%> (-0.01%) ⬇️
flink-integration-tests 48.79% <ø> (+<0.01%) ⬆️
hadoop-mr-java-client 43.77% <ø> (-0.01%) ⬇️
integration-tests 13.58% <0.00%> (-0.01%) ⬇️
spark-client-hadoop-common 48.70% <ø> (+0.02%) ⬆️
spark-java-tests 51.36% <64.70%> (-0.07%) ⬇️
spark-scala-tests 47.43% <100.00%> (+0.02%) ⬆️
utilities 36.60% <29.41%> (+0.02%) ⬆️

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

Files with missing lines Coverage Δ
...park/sql/catalyst/catalog/HoodieCatalogTable.scala 78.46% <100.00%> (+1.41%) ⬆️

... and 26 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.

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

Labels

size:M PR with lines of changes in (100, 300]

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Setting partitioned by (partition_path) with nonpartitioned keygenerator in spark-sql will cause the colum to be null

5 participants