nsivabalan commented on code in PR #19205:
URL: https://github.com/apache/hudi/pull/19205#discussion_r3782117821


##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieWriteConfig.java:
##########
@@ -3587,11 +3619,64 @@ public Builder withCanIgnorePostCommitFailures(boolean 
canIgnorePostCommitFailur
       return this;
     }
 
+    /**
+     * @deprecated since 1.3.0, use {@link 
#withMetaFieldsMode(MetaFieldsMode)} instead
+     * ({@code true} maps to {@link MetaFieldsMode#ALL}, {@code false} to 
{@link MetaFieldsMode#NONE}).
+     */
+    @Deprecated
     public Builder withPopulateMetaFields(boolean populateMetaFields) {
       writeConfig.setValue(HoodieTableConfig.POPULATE_META_FIELDS, 
Boolean.toString(populateMetaFields));
       return this;
     }
 
+    public Builder withMetaFieldsMode(MetaFieldsMode metaFieldsMode) {
+      // Leaving the mode unset defers to the deprecated populate.meta.fields 
boolean. The legacy
+      // boolean is derived from the mode in build() rather than here, so the 
two cannot be made to
+      // disagree by calling the setters in either order.
+      writeConfig.setValue(HoodieTableConfig.META_FIELDS_MODE,
+          metaFieldsMode == null ? "" : metaFieldsMode.name());
+      return this;
+    }
+
+    /**
+     * Rewrite the deprecated {@code populate.meta.fields} boolean from {@code 
meta.fields.mode}
+     * whenever a mode is set, so the two can never disagree on the resulting 
config.
+     *
+     * <p>Done at build time, not in the setter: {@code 
withPopulateMetaFields} does not re-derive
+     * the mode, so deriving in {@link #withMetaFieldsMode} alone would make 
the invariant depend on
+     * call order. {@code 
withMetaFieldsMode(COMMIT_TIME_ONLY).withPopulateMetaFields(true)} would
+     * leave a selective mode sitting next to {@code 
populate.meta.fields=true} — a config that
+     * resolves correctly (the mode wins) but carries the contradiction to 
disk on any path that
+     * copies raw write-config props into {@code hoodie.properties}, 
misleading pre-1.3.0 readers
+     * into treating the table as ALL.
+     */
+    private void deriveLegacyPopulateMetaFieldsFromMode() {
+      String rawMode = 
writeConfig.getString(HoodieTableConfig.META_FIELDS_MODE);
+      if (StringUtils.isNullOrEmpty(rawMode)) {
+        return;
+      }
+      boolean derived = 
MetaFieldsMode.parse(rawMode).toLegacyPopulateMetaFields();
+      // A caller that explicitly set the boolean to something the mode 
contradicts is rejected rather
+      // than silently overridden — otherwise half their request is discarded 
without a word. Only a
+      // genuine contradiction fails; restating the derived value (ALL + true, 
NONE + false) passes.
+      // An absent boolean is the ordinary case and simply takes the derived 
value.
+      checkArgument(

Review Comment:
   Confirmed and fixed in 85fe7035. You were right on every link, including the 
asymmetry: `derived=false` matches the explicit `false` for selective modes, so 
it is `ALL` -- the default -- that broke.
   
   Root cause was as you diagnosed: the check fired on 
`writeConfig.contains(...)`, which cannot tell a value the caller stated on 
this builder from one that arrived through `withProperties`. Those mean 
different things -- stating both and contradicting yourself is an error; 
stating one while the other rides in on an inherited blob is an override.
   
   Implemented your suggestion: the builder tracks `statedPopulateMetaFields` / 
`statedMetaFieldsMode` and only rejects a caller contradicting itself. An 
explicit boolean now narrows an inherited mode; an explicitly-stated mode 
overrides an inherited boolean.
   
   Tests: three cases in `TestHoodieWriteConfigMetaFieldsMode` covering both 
inherited directions plus the still-rejected self-contradiction, and 
`TestHoodieTimelineArchiver#lsmWriterConfigSurvivesAnUpgradedTablesInheritedMode`
 reproducing the LSM config shape on an upgraded table. Verified non-vacuous -- 
neutralising the fix reproduces your reported throw.
   
   On `SevenToEightUpgradeHandler`: it does not build a config, it passes one 
to `LSMTimelineWriter`, so the same fix covers it.
   



##########
hudi-common/src/main/java/org/apache/hudi/common/table/HoodieTableMetaClient.java:
##########
@@ -1535,7 +1570,50 @@ public Properties build() {
           
tableConfig.setValue(HoodieTableConfig.CDC_SUPPLEMENTAL_LOGGING_MODE, 
cdcSupplementalLoggingMode);
         }
       }
-      if (null != populateMetaFields) {
+      // hoodie.meta.fields.mode is the source of truth, and hoodie.properties 
must never contradict
+      // it: a table written selectively that still recorded 
populate.meta.fields=true would be read
+      // as ALL by a pre-1.3.0 reader, which ignores the mode property 
entirely. For NONE that is
+      // actively unsafe — an older incremental reader would run against 
all-null commit times and
+      // silently return no rows.
+      //
+      // A caller that states both and disagrees is rejected rather than 
silently overridden. Half
+      // their request would otherwise be discarded without a word, and it 
would be inconsistent with
+      // BaseHoodieWriteClient#validateAgainstTableProperties, which already 
rejects an explicitly-set
+      // boolean that disagrees with the table. Only a genuine contradiction 
fails: ALL + true and
+      // NONE + false are coherent restatements and pass.
+      if (null != metaFieldsMode) {

Review Comment:
   Confirmed and fixed in 633496ac. `TypedProperties.getBoolean(String, 
boolean)` returns a primitive, so the argument was never null and any selective 
mode was rejected at table creation.
   
   Both sites now use the pattern you quoted, matching `StreamSync:482` and 
`HoodieSparkSqlWriter`, which already carried it with a comment explaining 
exactly this trap. These two were missed.
   
   Added `unstatedBooleanFromPropsDoesNotContradictAnExplicitMode` to pin the 
props-to-builder shape both bootstrap paths use.
   



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/common/model/HoodieSparkRecord.java:
##########
@@ -296,9 +307,14 @@ public HoodieRecord 
wrapIntoHoodieRecordPayloadWithKeyGen(HoodieSchema recordSch
     StructType structType = 
HoodieInternalRowUtils.getCachedSchema(recordSchema);
     String key;
     String partition;
-    boolean populateMetaFields = 
Boolean.parseBoolean(props.getOrDefault(POPULATE_META_FIELDS.key(),
-        POPULATE_META_FIELDS.defaultValue().toString()).toString());
-    if (!populateMetaFields && keyGen.isPresent()) {
+    // Resolve via hoodie.meta.fields.mode — reading the deprecated boolean 
alone would report
+    // "populated" for a selective-mode table (whose _hoodie_record_key column 
is null), sending us
+    // down the meta-column branch below and NPE-ing on the null ordinal.
+    boolean recordKeyPopulated = MetaFieldsMode.resolve(

Review Comment:
   Both confirmed and fixed in 668e57b6, resolving through `MetaFieldsMode` as 
you suggested.
   
   `AutoRecordKeyGenerationUtils` is the serious one and your reasoning is 
exactly right: bypassing that guard lets the write proceed into 
`HoodieDatasetBulkInsertHelper`'s non-populate branch, which never runs the key 
generator, so the rows land with no identity. I verified the invariant you 
cited -- `TestAutoGenerationOfRecordKeys:174` asserts the boolean case throws, 
and stating `NONE` walked straight past it.
   
   `@CsvSource` extended with `hoodie.meta.fields.mode,NONE` and 
`COMMIT_TIME_ONLY` as you asked.
   
   One follow-on worth flagging: that extension immediately caught a second bug 
of mine. The assertion checks the message contains `configKey + " is not 
supported with auto generation of record keys"` as one contiguous phrase, and 
my new message broke it with a parenthetical -- for the pre-existing boolean 
row too. Fixed in 88b3b559; the message now names whichever of the two 
properties the caller actually stated.
   



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