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


##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieWriteConfig.java:
##########
@@ -3883,6 +3932,27 @@ private void validate() {
       checkArgument(ttlStatsMaxParallelism > 0,
           String.format("%s must be positive, but was %d",
               HoodieTTLConfig.STATS_MAX_PARALLELISM.key(), 
ttlStatsMaxParallelism));
+
+      // hoodie.meta.fields.mode is the source of truth for meta-column 
population; the deprecated
+      // populate.meta.fields boolean is consulted only when the mode is 
absent. There is therefore
+      // no ambiguous combination to reject here — MetaFieldsMode.resolve 
throws on unrecognized
+      // values.
+      MetaFieldsMode metaFieldsMode = writeConfig.getMetaFieldsMode();

Review Comment:
   Fixed in `664ff2ec23ae`, and the forward-compat consequence you described is 
the part that mattered.
   
   Whenever an explicit mode is supplied, the legacy boolean is **derived from 
it** (`ALL -> true`, everything else `-> false`) rather than taken from the 
caller, at both chokepoints: `HoodieTableMetaClient.TableBuilder` (which every 
creation path goes through, including `HoodieSparkSqlWriter` and `StreamSync`) 
and `HoodieWriteConfig.Builder`. So `hoodie.properties` can never contradict 
the mode, and a pre-1.3.0 reader — which sees only the boolean — reads `false` 
for a selective table rather than assuming meta columns that are null.
   
   I went with "always persist the derived value" over "reject the combination" 
because rejecting would make `populate.meta.fields=true` + `ALL` an error, 
which is a coherent request, and because the derived write also covers the case 
where the boolean is simply absent. The resolution table is now spelled out in 
the PR description.
   
   On the test: you were right that `populateTrueWithSelectiveModeIsRejected` 
was failing, and I should own how that happened — CI flagged it and I rewrote 
it to accept the new behavior, reading it as stale rather than as the signal it 
was. It is now `selectiveModeWinsOverLegacyPopulateTrue` and additionally 
asserts the persisted boolean is `false`, which is the invariant that was 
actually broken.
   



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/config/HoodieWriteConfig.java:
##########
@@ -1772,8 +1773,40 @@ public int getSmallFileGroupCandidatesLimit() {
     return getInt(MERGE_SMALL_FILE_GROUP_CANDIDATES_LIMIT);
   }
 
+  /**
+   * @return true when every meta column is populated.
+   *
+   * <p>Derived from {@link #getMetaFieldsMode()} so that call sites still 
written against the
+   * deprecated {@code hoodie.populate.meta.fields} boolean observe the same 
answer as the enum:
+   * only {@link MetaFieldsMode#ALL} populates every meta column.
+   */
   public boolean populateMetaFields() {
-    return getBooleanOrDefault(HoodieTableConfig.POPULATE_META_FIELDS);
+    return getMetaFieldsMode().toLegacyPopulateMetaFields();
+  }
+
+  /**
+   * @return the {@link MetaFieldsMode} resolved from the write config.
+   * {@code hoodie.meta.fields.mode} is the source of truth; configs written 
before that property
+   * existed fall back to {@link MetaFieldsMode#ALL} or {@link 
MetaFieldsMode#NONE} based on the
+   * deprecated {@code hoodie.populate.meta.fields} boolean.
+   */
+  public MetaFieldsMode getMetaFieldsMode() {
+    return 
MetaFieldsMode.resolve(getStringOrDefault(HoodieTableConfig.META_FIELDS_MODE),

Review Comment:
   Fixed in `c266b8286b4e`, and strengthened further in the latest push.
   
   `validateAgainstTableProperties` compares the full `MetaFieldsMode` rather 
than the collapsed booleans, so your scenario — a direct `SparkRDDWriteClient` 
opening a persisted `COMMIT_TIME_ONLY` table with `populate=false` and no mode 
— is caught. You were right that this is a *narrowing*, not a widening, so the 
`isWiderThan` guard alone would not have caught it: `NONE` is not wider than 
`COMMIT_TIME_ONLY`.
   
   What changed since: the writer no longer resolves to `NONE` there at all. A 
writer that states neither meta-field property now **inherits** the table's 
mode, because the mode is a table property that only hudi-cli or an upgrade may 
change. A writer that states either is compared and rejected in both 
directions. So your case now either inherits `COMMIT_TIME_ONLY` (if nothing was 
stated) or fails loudly (if `populate=false` was stated) — never silently 
writes null commit times.
   
   `TestBaseHoodieWriteClient` has 110 tests covering these combinations, 
including the exact one you described.
   



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/upgrade/TenToNineDowngradeHandler.java:
##########
@@ -18,25 +18,61 @@
 
 package org.apache.hudi.table.upgrade;
 
+import org.apache.hudi.common.config.ConfigProperty;
 import org.apache.hudi.common.engine.HoodieEngineContext;
+import org.apache.hudi.common.model.MetaFieldsMode;
 import org.apache.hudi.common.table.HoodieTableConfig;
 import org.apache.hudi.config.HoodieWriteConfig;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Version 10 writes native log files by default. Downgrading to version 9 
requires
  * full compaction of native data/delete logs before the downgrade completes.
+ *
+ * <p>Version 10 also introduced {@code hoodie.meta.fields.mode}. Version 9 
does not understand it,
+ * so the property is dropped here while {@code hoodie.populate.meta.fields} 
is left exactly as it
+ * stands — {@code ALL} and {@code NONE} tables round-trip unchanged because 
those are precisely the
+ * two states the legacy boolean can express. Selective modes cannot be 
expressed in version 9, so
+ * the table degrades to what its legacy boolean says (which is {@code false}, 
i.e. NONE) and we warn.
  */
 public class TenToNineDowngradeHandler implements DowngradeHandler {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(TenToNineDowngradeHandler.class);
+
   @Override
   public UpgradeDowngrade.TableConfigChangeSet downgrade(
       HoodieWriteConfig config,
       HoodieEngineContext context,
       String instantTime,
       SupportsUpgradeDowngrade upgradeDowngradeHelper) {
+    Set<ConfigProperty> propertiesToDelete = new HashSet<>();
+    propertiesToDelete.add(HoodieTableConfig.TABLE_STORAGE_LAYOUT);
+
+    // The warning is best-effort: dropping the property is what matters, and 
the helper is not
+    // always available (some callers drive the change set directly).
+    MetaFieldsMode metaFieldsMode = upgradeDowngradeHelper == null
+        ? MetaFieldsMode.ALL
+        : upgradeDowngradeHelper.getTable(config, 
context).getMetaClient().getTableConfig().getMetaFieldsMode();
+    if (metaFieldsMode != MetaFieldsMode.ALL && metaFieldsMode != 
MetaFieldsMode.NONE) {
+      LOG.warn("Table is using {}={}, which table version 9 cannot express. 
The property is being "
+              + "removed and the table will behave as {}=false (no meta 
columns) to version 9 readers. "
+              + "Already-written files keep their populated meta columns, but 
incremental queries that "
+              + "relied on {} will stop returning rows. Recreate the table if 
you need that behavior back.",
+          HoodieTableConfig.META_FIELDS_MODE.key(), metaFieldsMode,
+          HoodieTableConfig.POPULATE_META_FIELDS.key(), metaFieldsMode);
+    }
+    // hoodie.populate.meta.fields is deliberately left untouched: whatever 
the table recorded before
+    // the downgrade stays, so ALL and NONE tables are bit-identical 
afterwards.
+    propertiesToDelete.add(HoodieTableConfig.META_FIELDS_MODE);
+
     return new UpgradeDowngrade.TableConfigChangeSet(
         Collections.emptyMap(),
-        Collections.singleton(HoodieTableConfig.TABLE_STORAGE_LAYOUT));
+        propertiesToDelete);

Review Comment:
   Implemented in `69a0906419d8`, and then taken further in the latest push — 
worth re-reading, since your closing suggestion is now what the code does.
   
   The write-back is in: downgrade derives `POPULATE_META_FIELDS` from the mode 
(`ALL -> true`, everything else `-> false`) and deletes the mode, mirroring 
`NineToEightDowngradeHandler:116-117`. For `ALL`/`NONE` that is a no-op 
restatement; what it closes is the case @voonhous described, where a table 
carrying only the mode would fall back to the `true` default and downgrade to 
`ALL`.
   
   On the one-way concern you both raised: **selective modes now throw** rather 
than being destroyed with a `LOG.warn`. You were right that no other handler 
pair in this package is one-way, and that a warning is not an acknowledgement. 
Since `ALL` and `NONE` are exactly the two states v9 can express, a selective 
table is now told to rewrite to one of them before downgrading — which makes 
the unrecoverable state unreachable rather than documented.
   
   Also fixed the adjacent issue: with no `SupportsUpgradeDowngrade` helper the 
handler assumed `ALL` and wrote a derived boolean from that guess. It now 
leaves the boolean untouched.
   



##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/table/upgrade/TenToNineDowngradeHandler.java:
##########
@@ -18,25 +18,61 @@
 
 package org.apache.hudi.table.upgrade;
 
+import org.apache.hudi.common.config.ConfigProperty;
 import org.apache.hudi.common.engine.HoodieEngineContext;
+import org.apache.hudi.common.model.MetaFieldsMode;
 import org.apache.hudi.common.table.HoodieTableConfig;
 import org.apache.hudi.config.HoodieWriteConfig;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Version 10 writes native log files by default. Downgrading to version 9 
requires
  * full compaction of native data/delete logs before the downgrade completes.
+ *
+ * <p>Version 10 also introduced {@code hoodie.meta.fields.mode}. Version 9 
does not understand it,
+ * so the property is dropped here while {@code hoodie.populate.meta.fields} 
is left exactly as it
+ * stands — {@code ALL} and {@code NONE} tables round-trip unchanged because 
those are precisely the
+ * two states the legacy boolean can express. Selective modes cannot be 
expressed in version 9, so
+ * the table degrades to what its legacy boolean says (which is {@code false}, 
i.e. NONE) and we warn.
  */
 public class TenToNineDowngradeHandler implements DowngradeHandler {
+
+  private static final Logger LOG = 
LoggerFactory.getLogger(TenToNineDowngradeHandler.class);
+
   @Override
   public UpgradeDowngrade.TableConfigChangeSet downgrade(
       HoodieWriteConfig config,
       HoodieEngineContext context,
       String instantTime,
       SupportsUpgradeDowngrade upgradeDowngradeHelper) {
+    Set<ConfigProperty> propertiesToDelete = new HashSet<>();
+    propertiesToDelete.add(HoodieTableConfig.TABLE_STORAGE_LAYOUT);
+
+    // The warning is best-effort: dropping the property is what matters, and 
the helper is not
+    // always available (some callers drive the change set directly).
+    MetaFieldsMode metaFieldsMode = upgradeDowngradeHelper == null
+        ? MetaFieldsMode.ALL
+        : upgradeDowngradeHelper.getTable(config, 
context).getMetaClient().getTableConfig().getMetaFieldsMode();
+    if (metaFieldsMode != MetaFieldsMode.ALL && metaFieldsMode != 
MetaFieldsMode.NONE) {
+      LOG.warn("Table is using {}={}, which table version 9 cannot express. 
The property is being "
+              + "removed and the table will behave as {}=false (no meta 
columns) to version 9 readers. "
+              + "Already-written files keep their populated meta columns, but 
incremental queries that "
+              + "relied on {} will stop returning rows. Recreate the table if 
you need that behavior back.",
+          HoodieTableConfig.META_FIELDS_MODE.key(), metaFieldsMode,
+          HoodieTableConfig.POPULATE_META_FIELDS.key(), metaFieldsMode);
+    }
+    // hoodie.populate.meta.fields is deliberately left untouched: whatever 
the table recorded before
+    // the downgrade stays, so ALL and NONE tables are bit-identical 
afterwards.
+    propertiesToDelete.add(HoodieTableConfig.META_FIELDS_MODE);

Review Comment:
   Implemented in `69a0906419d8`, and then taken further in the latest push — 
worth re-reading, since your closing suggestion is now what the code does.
   
   The write-back is in: downgrade derives `POPULATE_META_FIELDS` from the mode 
(`ALL -> true`, everything else `-> false`) and deletes the mode, mirroring 
`NineToEightDowngradeHandler:116-117`. For `ALL`/`NONE` that is a no-op 
restatement; what it closes is the case @voonhous described, where a table 
carrying only the mode would fall back to the `true` default and downgrade to 
`ALL`.
   
   On the one-way concern you both raised: **selective modes now throw** rather 
than being destroyed with a `LOG.warn`. You were right that no other handler 
pair in this package is one-way, and that a warning is not an acknowledgement. 
Since `ALL` and `NONE` are exactly the two states v9 can express, a selective 
table is now told to rewrite to one of them before downgrading — which makes 
the unrecoverable state unreachable rather than documented.
   
   Also fixed the adjacent issue: with no `SupportsUpgradeDowngrade` helper the 
handler assumed `ALL` and wrote a derived boolean from that guess. It now 
leaves the boolean untouched.
   



##########
hudi-client/hudi-spark-client/src/main/java/org/apache/hudi/io/storage/HoodieSparkFileWriterFactory.java:
##########
@@ -56,7 +56,9 @@ public HoodieSparkFileWriterFactory(HoodieStorage storage) {
   protected HoodieFileWriter newParquetFileWriter(
       String instantTime, StoragePath path, HoodieConfig config, HoodieSchema 
schema,
       TaskContextSupplier taskContextSupplier) throws IOException {
-    boolean populateMetaFields = 
config.getBooleanOrDefault(HoodieTableConfig.POPULATE_META_FIELDS);
+    org.apache.hudi.common.model.MetaFieldsMode metaFieldsMode =

Review Comment:
   Fixed in `5f397915d1d8` — imported in both `HoodieSparkFileWriterFactory` 
and `HoodieAvroFileWriterFactory`, and the two `resolve(config)` call sites use 
the simple name.
   
   Note the stacked #19378 adds two more fully-qualified references in this 
file (the Lance and Vortex paths); I will fold those into the simple name there 
so the file does not end up mixed.
   



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