linliu-code commented on code in PR #19908:
URL: https://github.com/apache/hudi/pull/19908#discussion_r4007918544


##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieCreateRecordUtils.scala:
##########
@@ -155,7 +155,17 @@ object HoodieCreateRecordUtils {
             } else {
               avroRecWithoutMeta
             }
-            val hoodieRecord = if (shouldCombine && !orderingFields.isEmpty) {
+            // `shouldCombine` says whether the incoming batch needs 
de-duplicating, which is not the
+            // same question as whether this record needs an ordering value. 
Prepped Spark SQL writes
+            // emit one row per key and so set it false, which used to leave 
the record with no
+            // ordering value on tables that order by event time.
+            // Deletes are excluded from the widened branch so their semantics 
do not change: a
+            // delete carrying the default ordering value is treated as commit 
time ordered by
+            // BufferedRecordMergerFactory#deltaMergeDeleteRecord, and giving 
it a real value would
+            // make a delete lose to a stored record with a higher ordering 
value. Deletes still get
+            // an ordering value when `shouldCombine` is true, exactly as 
before.
+            val computeOrderingValue = shouldCombine || (requiresOrderingValue 
&& !isDelete)

Review Comment:
   Good catch, and yes it is intended. Added to Impact: a non-delete row with a 
null value in the ordering column now fails the write with 
`IllegalArgumentException: Ordering field '<field>' has null value`, where such 
rows previously passed through carrying the default.
   
   Worth noting it is not a new rule so much as an alignment: 
`getOrderingValue` already threw that for `requiresOrderingValue` writes, and 
the `shouldCombine` path already reached it. This change makes the prepped and 
non-prepped paths agree. Deletes are exempt, since a delete may carry only its 
key.



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieCreateRecordUtils.scala:
##########
@@ -155,7 +155,17 @@ object HoodieCreateRecordUtils {
             } else {
               avroRecWithoutMeta
             }
-            val hoodieRecord = if (shouldCombine && !orderingFields.isEmpty) {
+            // `shouldCombine` says whether the incoming batch needs 
de-duplicating, which is not the
+            // same question as whether this record needs an ordering value. 
Prepped Spark SQL writes
+            // emit one row per key and so set it false, which used to leave 
the record with no
+            // ordering value on tables that order by event time.
+            // Deletes are excluded from the widened branch so their semantics 
do not change: a
+            // delete carrying the default ordering value is treated as commit 
time ordered by
+            // BufferedRecordMergerFactory#deltaMergeDeleteRecord, and giving 
it a real value would
+            // make a delete lose to a stored record with a higher ordering 
value. Deletes still get

Review Comment:
   Done — it reads `shouldComputeOrderingValue` now.



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieCreateRecordUtils.scala:
##########
@@ -155,9 +155,20 @@ object HoodieCreateRecordUtils {
             } else {
               avroRecWithoutMeta
             }
-            val hoodieRecord = if (shouldCombine && !orderingFields.isEmpty) {
+            // `shouldCombine` says whether the incoming batch needs 
de-duplicating, which is not the
+            // same question as whether this record needs an ordering value. 
Prepped Spark SQL writes
+            // emit one row per key and so set it false, which used to leave 
the record with no
+            // ordering value on tables that order by event time.
+            // Deletes take the ordering value too. A delete left on the 
default is treated as commit
+            // time ordered by 
BufferedRecordMergerFactory#shouldKeepNewerRecord, so excluding them
+            // here would let a stale delete remove a record with a higher 
ordering value, while the
+            // same delete written through a path where `shouldCombine` is 
true would correctly lose.
+            // A null ordering field stays tolerated for a delete, which may 
carry only its key: the
+            // value falls back to the default rather than failing the write.
+            val computeOrderingValue = shouldCombine || requiresOrderingValue
+            val hoodieRecord = if (computeOrderingValue && 
!orderingFields.isEmpty) {
               val orderingVal = getOrderingValue(orderingFields, avroRec, 
hoodieKey.getRecordKey,
-                consistentLogicalTimestampEnabled, requiresOrderingValue)
+                consistentLogicalTimestampEnabled, requiresOrderingValue && 
!isDelete)

Review Comment:
   Description updated. The code and the body now agree: deletes take the 
ordering value, and a delete left on the default would be treated as commit 
time ordered by `shouldKeepNewerRecord`, which is precisely why excluding them 
would let a stale delete remove a newer record. Both the Change Logs and Impact 
sections say so.



##########
hudi-spark-datasource/hudi-spark-common/src/main/scala/org/apache/hudi/HoodieCreateRecordUtils.scala:
##########
@@ -155,9 +155,20 @@ object HoodieCreateRecordUtils {
             } else {
               avroRecWithoutMeta
             }
-            val hoodieRecord = if (shouldCombine && !orderingFields.isEmpty) {
+            // `shouldCombine` says whether the incoming batch needs 
de-duplicating, which is not the
+            // same question as whether this record needs an ordering value. 
Prepped Spark SQL writes
+            // emit one row per key and so set it false, which used to leave 
the record with no
+            // ordering value on tables that order by event time.
+            // Deletes take the ordering value too. A delete left on the 
default is treated as commit
+            // time ordered by 
BufferedRecordMergerFactory#shouldKeepNewerRecord, so excluding them

Review Comment:
   Aligned — both the inline comment and the test javadoc now reference 
`BufferedRecordMergerFactory#shouldKeepNewerRecord`, so the 
default-means-commit-time rule has one landing place.



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