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


##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieCreateRecordUtils.scala:
##########
@@ -290,27 +291,41 @@ object HoodieCreateRecordUtils {
    * null values are allowed and a default ordering value is used.
    * Otherwise, throws IllegalArgumentException if any ordering field has a 
null value.
    */
-  private def getOrderingValue(orderingFields: java.util.List[String],
-                               avroRec: GenericRecord,
-                               recordKey: String,
-                               consistentLogicalTimestampEnabled: Boolean,
-                               requiresOrderingValue: Boolean): Comparable[_] 
= {
-    OrderingValues.create(
-      orderingFields,
-      JFunction.toJavaFunction[String, Comparable[_]](field => {
-        val fieldVal = HoodieAvroUtils.getNestedFieldVal(avroRec, field, 
false, consistentLogicalTimestampEnabled)
-        if (fieldVal == null) {
-          if (requiresOrderingValue) {
-            throw new IllegalArgumentException(
-              s"Ordering field '$field' has null value for record key 
'$recordKey'. " +
-                s"Please ensure all records have non-null values for the 
ordering field, " +
-                s"or use a payload class that doesn't require ordering (e.g., 
OverwriteWithLatestAvroPayload).")
-          }
-          // Return default ordering value for payloads that don't require 
ordering
-          OrderingValues.getDefault.asInstanceOf[Comparable[_]]
-        } else {
-          fieldVal.asInstanceOf[Comparable[_]]
-        }
-      }))
+  /**
+   * Returns the function that reads the ordering value off a record, chosen 
once for the write
+   * rather than per record: when an ordering value is required a missing one 
is an error, and when
+   * it is not (COMMIT_TIME_ORDERING or OverwriteWithLatestAvroPayload) it is 
replaced by the
+   * default ordering value.
+   */
+  private def orderingValueExtractor(orderingFields: java.util.List[String],
+                                     consistentLogicalTimestampEnabled: 
Boolean,
+                                     requiresOrderingValue: Boolean): 
(GenericRecord, String) => Comparable[_] = {
+    if (requiresOrderingValue) {
+      (avroRec, recordKey) =>
+        OrderingValues.create(

Review Comment:
   🤖 nit: would it make sense to share the field-reading and 
`OrderingValues.create` code between the two branches, so only the 
missing-value handling differs?
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-utilities/src/main/java/org/apache/hudi/utilities/streamer/HoodieStreamerUtils.java:
##########
@@ -94,9 +96,20 @@ public static Option<JavaRDD<HoodieRecord>> 
createHoodieRecords(HoodieStreamer.C
     boolean useConsistentLogicalTimestamp = ConfigUtils.getBooleanWithAltKeys(
         props, 
KeyGeneratorOptions.KEYGENERATOR_CONSISTENT_LOGICAL_TIMESTAMP_ENABLED);
     Set<String> partitionColumns = getPartitionColumns(props);
-    String payloadClassName = StringUtils.isNullOrEmpty(cfg.payloadClassName)
-        ? HoodieRecordPayload.getAvroPayloadForMergeMode(cfg.recordMergeMode, 
cfg.payloadClassName)
-        : cfg.payloadClassName;
+    // Prefer the table, like orderingFieldsStr above. cfg.recordMergeMode is 
inferred from the
+    // streamer's own arguments and never reconciled with the table, so a run 
that omits
+    // --source-ordering-fields on an event-time table reads 
COMMIT_TIME_ORDERING here and leaves the
+    // ordering value unchecked. The payload has to follow the same mode, or a 
stale
+    // COMMIT_TIME_ORDERING resolves it to OverwriteWithLatestAvroPayload and 
skips the check anyway.
+    RecordMergeMode mergeMode = tableConfig.getRecordMergeMode() != null
+        ? tableConfig.getRecordMergeMode() : cfg.recordMergeMode;
+    String payloadClassName = tableConfig.getPayloadClassIfPresent()

Review Comment:
   🤖 I think `payloadClassName` can now resolve to null. Take a v9 table with 
`CUSTOM` merge mode backed by a custom merger, so no payload class is 
persisted, and a run without `--payload-class`. `mergeMode` becomes CUSTOM, 
`getAvroPayloadForMergeMode(CUSTOM, null)` returns null, and every record then 
NPEs in `HoodieRecordUtils.loadPayload(null, ...)`. With an error table each 
record is quarantined; without one the batch fails. Before this change, the 
mode inferred from the CLI resolved to a built-in payload. Could you fall back 
to a non-null payload here (e.g. 
`HoodieTableConfig.getDefaultPayloadClassName()`), or use the table-preferred 
payload only for the `requiresOrderingValue` gate?
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieCreateRecordUtils.scala:
##########
@@ -290,27 +291,41 @@ object HoodieCreateRecordUtils {
    * null values are allowed and a default ordering value is used.
    * Otherwise, throws IllegalArgumentException if any ordering field has a 
null value.
    */
-  private def getOrderingValue(orderingFields: java.util.List[String],
-                               avroRec: GenericRecord,
-                               recordKey: String,
-                               consistentLogicalTimestampEnabled: Boolean,
-                               requiresOrderingValue: Boolean): Comparable[_] 
= {
-    OrderingValues.create(
-      orderingFields,
-      JFunction.toJavaFunction[String, Comparable[_]](field => {
-        val fieldVal = HoodieAvroUtils.getNestedFieldVal(avroRec, field, 
false, consistentLogicalTimestampEnabled)
-        if (fieldVal == null) {
-          if (requiresOrderingValue) {
-            throw new IllegalArgumentException(
-              s"Ordering field '$field' has null value for record key 
'$recordKey'. " +
-                s"Please ensure all records have non-null values for the 
ordering field, " +
-                s"or use a payload class that doesn't require ordering (e.g., 
OverwriteWithLatestAvroPayload).")
-          }
-          // Return default ordering value for payloads that don't require 
ordering
-          OrderingValues.getDefault.asInstanceOf[Comparable[_]]
-        } else {
-          fieldVal.asInstanceOf[Comparable[_]]
-        }
-      }))
+  /**

Review Comment:
   🤖 nit: could you drop the old Javadoc above and keep only the new one? The 
stale `getOrderingValue` comment is now stacked directly above it.
   
   <sub><i>⚠️ AI-generated; verify before applying. React 👍/👎 to flag 
quality.</i></sub>



##########
hudi-utilities/src/main/java/org/apache/hudi/utilities/streamer/HoodieStreamerUtils.java:
##########
@@ -130,6 +135,13 @@ public static Option<JavaRDD<HoodieRecord>> 
createHoodieRecords(HoodieStreamer.C
                       ? OrderingValues.create(orderingFieldsStr.split(","),
                          field -> (Comparable) 
HoodieAvroUtils.getNestedFieldVal(gr, field, false, 
useConsistentLogicalTimestamp))
                       : null;
+                  if (requiresOrderingValue && 
OrderingValues.isMissing(orderingValue)) {
+                    throw new IllegalArgumentException(
+                        "Ordering fields '" + orderingFieldsStr + "' resolved 
to a null value for record key '"
+                            + hoodieKey.getRecordKey() + "'. Please ensure all 
records carry non-null values for "
+                            + "the ordering fields, or use a merge mode or 
payload class that does not order "
+                            + "(e.g., COMMIT_TIME_ORDERING or 
OverwriteWithLatestAvroPayload).");

Review Comment:
   🤖 Exempting deletes here would make the streamer differ from the Spark 
datasource write path, which already throws for a `_hoodie_is_deleted` record 
with a null ordering field when `requiresOrderingValue` holds 
(`HoodieCreateRecordUtils` builds the ordering value regardless of `isDelete`), 
so the quarantine looks consistent; the PR description's MOR note now calls 
this out too.



-- 
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