hudi-agent commented on code in PR #19509:
URL: https://github.com/apache/hudi/pull/19509#discussion_r3915687302


##########
hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/TestDataSourceUtils.java:
##########
@@ -247,6 +262,65 @@ public void 
testCreateRDDCustomColumnsSortPartitionerWithValidPartitioner() thro
     assertThat(partitioner.isPresent(), is(true));
   }
 
+  /**
+   * Every out of the box bulk insert partitioner has to be usable as a user 
defined partitioner.
+   * One is instantiated by reflection with only the write config, so each has 
to expose a
+   * constructor taking only a {@link HoodieWriteConfig}. See HUDI-7526.
+   */
+  @ParameterizedTest
+  @ValueSource(classes = {
+      NonSortPartitioner.class,
+      GlobalSortPartitioner.class,
+      RDDPartitionSortPartitioner.class,
+      RDDCustomColumnsSortPartitioner.class,
+      PartitionPathRepartitionPartitioner.class,
+      PartitionPathRepartitionAndSortPartitioner.class,
+      NonSortPartitionerWithRows.class,
+      GlobalSortPartitionerWithRows.class,
+      PartitionSortPartitionerWithRows.class,
+      RowCustomColumnsSortPartitioner.class,
+      RowSpatialCurveSortPartitioner.class,
+      PartitionPathRepartitionPartitionerWithRows.class,
+      PartitionPathRepartitionAndSortPartitionerWithRows.class
+  })
+  public void 
testBuiltInPartitionersAreUsableAsUserDefinedPartitioners(Class<?> 
partitionerClass) {
+    Map<String, String> props = new HashMap<>();
+    // required by the spatial curve partitioner, ignored by the rest
+    props.put(HoodieClusteringConfig.PLAN_STRATEGY_SORT_COLUMNS.key(), 
"column1,column2");
+    config = HoodieWriteConfig.newBuilder()
+        .withPath("/")
+        .withUserDefinedBulkInsertPartitionerClass(partitionerClass.getName())
+        .withUserDefinedBulkInsertPartitionerSortColumns("column1,column2")
+        .withSchema(avroSchemaString)
+        .withProps(props)
+        .build();
+
+    
assertThat(DataSourceUtils.createUserDefinedBulkInsertPartitioner(config).isPresent(),
 is(true));
+    
assertThat(DataSourceUtils.createUserDefinedBulkInsertPartitionerWithRows(config).isPresent(),
 is(true));
+  }
+
+  /**
+   * The partition path partitioners take the flag from the table when built 
by the factory, so
+   * check the write config only constructor derives it from the configured 
partition path field.

Review Comment:
   🤖 nit: the method name 
`testPartitionPathRepartitionPartitionerDerivesIsTablePartitioned` points to a 
concrete class, but the body is really exercising 
`BulkInsertPartitioner.isTablePartitioned()` — could you rename it to something 
like `testIsTablePartitionedDerivesFromPartitionPathField` so it stays accurate 
if the helper ever moves?
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/BulkInsertPartitioner.java:
##########
@@ -71,4 +74,22 @@ default String getFileIdPfx(int partitionId) {
   default Option<WriteHandleFactory> getWriteHandleFactory(int partitionId) {
     return Option.empty();
   }
+
+  /**
+   * Whether the records being written carry a partition path, derived from 
the write config alone.
+   * <p>
+   * A partitioner named through
+   * {@code HoodieWriteConfig.BULKINSERT_USER_DEFINED_PARTITIONER_CLASS_NAME} 
is instantiated by
+   * reflection with only the write config, so an implementation that 
otherwise takes the flag from
+   * the {@link HoodieTable} has nothing else to derive it from. The write 
side partition path field
+   * governs whether records end up with a non-empty partition path, which is 
what those
+   * implementations branch on.
+   *
+   * @param config Write config.
+   * @return {@code true} if a partition path field is configured; {@code 
false} otherwise.
+   */
+  static boolean isTablePartitioned(HoodieWriteConfig config) {
+    return !StringUtils.isNullOrEmpty(

Review Comment:
   🤖 Confirmed the two sources genuinely differ: the factory path resolves 
`table.isPartitioned()` → `HoodieTableConfig.isTablePartitioned()` (backed by 
`hoodie.table.partition.fields`), whereas this new method reads 
`hoodie.datasource.write.partitionpath.field`. One thing worth noting on 
severity: the built-in sort-mode path is untouched — it still threads 
`table.isPartitioned()` through the two-arg constructor — so the divergence 
only affects the new reflection path, and since each record already carries its 
own partition path, a wrong `false` here degrades `partitionBy` to `coalesce()` 
(a shuffle/layout + parallelism difference, not data loss). That said, 
reconciling or at least documenting the intended precedence still seems 
worthwhile, since a custom key generator can produce non-empty partition paths 
without `partitionpath.field` being set, and there the reflection path would 
repartition differently from the built-in sort mode.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to