Skip to content

fix(bulk-insert): add write config constructors to all sort mode partitioners - #19509

Open
deepakpanda93 wants to merge 1 commit into
apache:masterfrom
deepakpanda93:fix/HUDI-7526-bulk-insert-partitioner-constructors
Open

fix(bulk-insert): add write config constructors to all sort mode partitioners#19509
deepakpanda93 wants to merge 1 commit into
apache:masterfrom
deepakpanda93:fix/HUDI-7526-bulk-insert-partitioner-constructors

Conversation

@deepakpanda93

Copy link
Copy Markdown
Collaborator

Describe the issue this Pull Request addresses

Closes #16423 / HUDI-7526.

A partitioner named through hoodie.bulkinsert.user.defined.partitioner.class is
instantiated by reflection:

// DataSourceUtils#createUserDefinedBulkInsertPartitioner
Option.of((BulkInsertPartitioner) ReflectionUtils.loadClass(bulkInsertPartitionerClass, config));

ReflectionUtils.loadClass(clazz, Object...) infers the constructor argument types from
the instances it is handed, so it resolves getConstructor(HoodieWriteConfig.class). A
class is therefore usable as a user defined partitioner only if it exposes a public
constructor taking exactly one HoodieWriteConfig.

Twelve partitioners back an out of the box BulkInsertSortMode. Eight of them did not
have such a constructor, and failed with Unable to instantiate class ...:

Sort mode Spark RDD Spark row writer Java client
NONE NonSortPartitioner (fixed) NonSortPartitionerWithRows (fixed) JavaNonSortPartitioner (fixed)
GLOBAL_SORT GlobalSortPartitioner GlobalSortPartitionerWithRows JavaGlobalSortPartitioner (fixed)
PARTITION_SORT RDDPartitionSortPartitioner PartitionSortPartitionerWithRows not supported
PARTITION_PATH_REPARTITION PartitionPathRepartitionPartitioner (fixed) PartitionPathRepartitionPartitionerWithRows (fixed) not supported
PARTITION_PATH_REPARTITION_AND_SORT PartitionPathRepartitionAndSortPartitioner (fixed) PartitionPathRepartitionAndSortPartitionerWithRows (fixed) not supported

NonSortPartitioner, backing BulkInsertSortMode.NONE, is the example named in the
ticket.

Summary and Changelog

Every partitioner that an out of the box bulk insert sort mode maps to can now be named
through hoodie.bulkinsert.user.defined.partitioner.class.

  • Adds a public (HoodieWriteConfig) constructor to the eight partitioners marked above.
  • NonSortPartitioner, NonSortPartitionerWithRows, JavaNonSortPartitioner and
    JavaGlobalSortPartitioner have nothing configurable, so the new constructor behaves
    as the default one does. The value used for enforceNumOutputPartitions is false,
    matching what BulkInsertInternalPartitionerFactory.get(table, config) and
    BulkInsertInternalPartitionerWithRowsFactory.get(config, isTablePartitioned) pass on
    the bulk insert write path.
  • The two java client partitioners relied on the implicit no-arg constructor. Declaring
    any constructor removes it, and JavaBulkInsertInternalPartitionerFactory calls it, so
    the no-arg constructor is now declared explicitly alongside the new one.
  • The partition path repartition partitioners take the table partitioned flag from the
    HoodieTable when built by the factory, which reflection cannot supply. The new
    constructor derives it from the configured partition path field, added as
    BulkInsertPartitioner.isTablePartitioned. Those partitioners branch on whether
    records carry a non-empty partition path, and the write side partition path field is
    what governs that.
  • Purely additive. No existing constructor is changed or removed, so no existing caller
    is affected.

Left alone, because no sort mode maps to them:

  • RDDSpatialCurveSortPartitioner and RowSpatialCurveSortPartitioner are clustering
    layout partitioners, not sort mode partitioners. The RDD one also needs a
    HoodieSparkEngineContext that cannot be derived from a write config.
  • JavaCustomColumnsSortPartitioner is not selected by any sort mode.

Tests:

  • TestDataSourceUtils gains a parameterized test over all thirteen constructible spark
    partitioners, asserting each loads through both
    createUserDefinedBulkInsertPartitioner and
    createUserDefinedBulkInsertPartitionerWithRows. It covers the ones that already
    worked, so the whole contract is pinned rather than only the classes being fixed. It
    also gains a test pinning isTablePartitioned for a configured partition path field,
    an explicitly empty one, and an unset one.
  • TestJavaBulkInsertInternalPartitionerFactory gains a test that both java client sort
    mode partitioners load from a write config, and a test that both still expose a no-arg
    constructor, which guards the implicit constructor removal described above.

Reverting only the new constructors makes those tests fail for exactly the classes
concerned and nothing else.

Impact

None for existing users. The change only adds constructors, so every current call site
resolves as before, and the factories are unaffected. What changes is that eight built in
partitioners can now be named as a user defined partitioner where that previously failed
at instantiation.

Risk Level

low

Additive only, with no signature changed or removed. The one piece of new logic,
isTablePartitioned, is used solely by the new constructors; the existing
(boolean, HoodieWriteConfig) constructors still take the flag from the caller. Verified
end to end by running a bulk insert with each fixed partitioner named as the user defined
partitioner, over both the RDD and row writer paths, and on both a partitioned and a non
partitioned table so both branches of the derived flag are exercised.

Documentation Update

None. No new config, and no change to an existing config's meaning or default.

Contributor's checklist

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

…itioners

A partitioner named through hoodie.bulkinsert.user.defined.partitioner.class is
instantiated by reflection with only the write config, so it needs a public
constructor taking a single HoodieWriteConfig. Eight of the twelve partitioners
that a BulkInsertSortMode maps to did not have one.

NonSortPartitioner, NonSortPartitionerWithRows, JavaNonSortPartitioner and
JavaGlobalSortPartitioner take nothing configurable, so the new constructor
behaves as the default one does. The two java client partitioners relied on the
implicit no-arg constructor, which declaring another one removes, so that is now
declared explicitly to keep JavaBulkInsertInternalPartitionerFactory working.

The partition path repartition partitioners take the table partitioned flag from
the table when built by the factory. The new constructor derives it from the
configured partition path field, which is what governs whether records end up
with a non-empty partition path.

Partitioners not reachable from a sort mode are left alone: the spatial curve
partitioners are clustering layout partitioners, and JavaCustomColumnsSortPartitioner
is not selected by any sort mode.

@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! This PR adds (HoodieWriteConfig) constructors to the bulk-insert sort-mode partitioners so each can be named through hoodie.bulkinsert.user.defined.partitioner.class, plus a shared BulkInsertPartitioner.isTablePartitioned(config) helper for the partition-path variants. The additive constructors delegate to existing ones and preserve the factory's no-arg instantiation path, and I traced the isTablePartitioned derivation against the factory's table.isPartitioned() source and it's a sound, well-documented fallback for the config-only reflection path. 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. A few naming and simplification suggestions below — mostly the copy-pasted constructor Javadoc across six files; everything else looks clean.

cc @yihua

this(false);
}

/**

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 same Javadoc block ("Constructor taking the write config, so this partitioner can also be named through…") is copy-pasted verbatim across six or so constructors. Could you shorten these to a one-liner like // Required for reflection-based instantiation via BULKINSERT_USER_DEFINED_PARTITIONER_CLASS_NAME and leave the full rationale in the BulkInsertPartitioner.isTablePartitioned Javadoc (or a single shared location), so there's only one place to update if the explanation ever changes?

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

@github-actions github-actions Bot added the size:M PR with lines of changes in (100, 300] label Aug 4, 2026
@voonhous voonhous closed this Aug 4, 2026
@voonhous voonhous reopened this Aug 4, 2026
@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 (2f81a6e).
⚠️ Report is 19 commits behind head on master.

Additional details and impacted files
@@             Coverage Diff              @@
##             master   #19509      +/-   ##
============================================
- Coverage     77.03%   76.99%   -0.05%     
- Complexity    33863    33882      +19     
============================================
  Files          2575     2575              
  Lines        143379   143417      +38     
  Branches      17574    17579       +5     
============================================
- Hits         110451   110419      -32     
- Misses        24666    24735      +69     
- Partials       8262     8263       +1     
Components Coverage Δ
hudi-common 82.28% <100.00%> (-0.01%) ⬇️
hudi-client 81.84% <100.00%> (-0.26%) ⬇️
hudi-flink 83.97% <ø> (+0.01%) ⬆️
hudi-spark-datasource 75.10% <ø> (-0.01%) ⬇️
hudi-utilities 73.63% <ø> (-0.05%) ⬇️
hudi-cli 15.32% <ø> (ø)
hudi-hadoop 63.72% <100.00%> (+0.18%) ⬆️
hudi-sync 70.95% <90.90%> (+0.04%) ⬆️
hudi-io 79.60% <ø> (-0.10%) ⬇️
hudi-timeline-service 84.23% <ø> (+0.78%) ⬆️
hudi-cloud 64.00% <ø> (ø)
hudi-kafka-connect 53.20% <ø> (ø)
Flag Coverage Δ
common-and-other-modules 49.54% <0.00%> (-0.01%) ⬇️
flink-integration-tests 48.79% <0.00%> (+<0.01%) ⬆️
hadoop-mr-java-client 43.76% <80.00%> (-0.01%) ⬇️
integration-tests 13.58% <0.00%> (-0.01%) ⬇️
spark-client-hadoop-common 48.69% <0.00%> (+0.01%) ⬆️
spark-java-tests 51.34% <63.63%> (-0.09%) ⬇️
spark-scala-tests 47.39% <0.00%> (-0.02%) ⬇️
utilities 36.58% <0.00%> (+<0.01%) ⬆️

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

Files with missing lines Coverage Δ
...a/org/apache/hudi/table/BulkInsertPartitioner.java 100.00% <100.00%> (ø)
...xecution/bulkinsert/JavaGlobalSortPartitioner.java 28.57% <100.00%> (+19.48%) ⬆️
...i/execution/bulkinsert/JavaNonSortPartitioner.java 83.33% <100.00%> (+16.66%) ⬆️
.../hudi/execution/bulkinsert/NonSortPartitioner.java 81.81% <100.00%> (+4.04%) ⬆️
...ecution/bulkinsert/NonSortPartitionerWithRows.java 100.00% <100.00%> (ø)
...rt/PartitionPathRepartitionAndSortPartitioner.java 94.11% <100.00%> (+0.78%) ⬆️
...tionPathRepartitionAndSortPartitionerWithRows.java 100.00% <100.00%> (ø)
...ulkinsert/PartitionPathRepartitionPartitioner.java 94.11% <100.00%> (+0.78%) ⬆️
...t/PartitionPathRepartitionPartitionerWithRows.java 100.00% <100.00%> (ø)

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

@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

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.

Fix constructors for all bulk insert sort partitioners to ensure we could use it as user defined partitioners

5 participants