nsivabalan commented on code in PR #19205:
URL: https://github.com/apache/hudi/pull/19205#discussion_r3767704945
##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/client/BaseHoodieWriteClient.java:
##########
@@ -1544,13 +1550,83 @@ protected boolean loadActiveTimelineOnTableInit() {
return true;
}
+ /**
+ * Adopt the table's {@code hoodie.meta.fields.mode} when this writer did
not state one.
+ *
+ * <p>The mode is a table property: it is settable at table creation,
through hudi-cli, or by an
+ * upgrade, and never by an ordinary write. Callers routinely build a write
config without
+ * restating the table's meta-field settings — table services do, and so
does a restarted
+ * StreamSync — and such a writer must write what the table already
advertises rather than silently
+ * narrowing it. Without this, an unstated writer resolves to {@code NONE}
(via the deprecated
+ * {@code hoodie.populate.meta.fields} fallback) and writes null meta
columns into a table whose
+ * earlier files have them populated.
+ *
+ * <p>Inheritance applies only when the writer states <em>neither</em>
property. If it explicitly
+ * set the mode or the deprecated boolean, that value is left alone so the
comparison below can
+ * reject it on a mismatch: a user who deliberately passed {@code
populate.meta.fields=false}
+ * against an {@code ALL} table should be told the setting conflicts, not
have it silently
+ * overridden.
+ *
+ * <p>Called explicitly from {@link #initTable} rather than from
+ * {@link #validateAgainstTableProperties}: resolving what this writer will
use is a separate
+ * concern from checking it, and a method named "validate" must not quietly
rewrite the config it
+ * is handed. Read-only entry points that only validate (marker deletion,
dry-run partition
+ * listing) therefore do not resolve, which is correct -- they write no
records.
Review Comment:
Good catch, and confirmed — this was a real false positive, now fixed in
a3c753d.
You are right that it is worse than the inheritance question: those two
commands write no records, so failing them on meta-field disagreement is pure
noise. It also reaches further than the two you named —
`SparkMain#upgradeOrDowngradeTable` shares the same `getWriteConfig`, so an
upgrade or downgrade of a selective table would have hit it too.
The fix is to give them a config that reflects the table rather than to
exempt them from the check. Both have the table in hand:
- `SparkMain#getWriteConfig` now reads the mode off the table config (`jsc`
threaded through for the storage conf), so every command built on it — marker
cleanup, upgrade/downgrade, rollback, clean — inherits the table's mode.
- `HoodieDropPartitionsTool` seeds it into `props` in its constructor, where
a `metaClient` already exists, covering both `dryRun` and the delete path.
Neither overrides a mode the operator named explicitly, and both fall back
to the default if the table cannot be read (the base path not being a Hudi
table is the command's own error to report, not this lookup's).
Worth stating the distinction this draws, since it is the reason the
ingestion path does *not* get the same treatment: an ingestion writer must
state the mode, because the write path reads it again in handles and writer
factories — three of the eight `getFileWriter` call sites hold no table config
at all, so nothing there can repeat a lookup. A CLI command runs once against a
table it can already read, so it can afford one. Reading the table config to
build a write config is fine for maintenance tooling and wrong as a general
mechanism.
##########
hudi-client/hudi-client-common/src/test/java/org/apache/hudi/table/upgrade/TestNineToTenUpgradeHandler.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.hudi.table.upgrade;
+
+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.common.table.HoodieTableMetaClient;
+import org.apache.hudi.config.HoodieWriteConfig;
+import org.apache.hudi.table.HoodieTable;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+/**
+ * Version 9 tables predate {@code hoodie.meta.fields.mode}, so the upgrade
records the value
+ * derived from the deprecated {@code hoodie.populate.meta.fields} boolean.
This makes an upgraded
+ * table describe its meta-field layout the same way a freshly created version
10 table does,
+ * instead of relying on the legacy fallback at every read.
+ */
+class TestNineToTenUpgradeHandler {
+
+ private static SupportsUpgradeDowngrade helperFor(MetaFieldsMode
resolvedMode) {
+ HoodieTable table = mock(HoodieTable.class, RETURNS_DEEP_STUBS);
+ HoodieTableMetaClient metaClient = mock(HoodieTableMetaClient.class,
RETURNS_DEEP_STUBS);
+ HoodieTableConfig tableConfig = mock(HoodieTableConfig.class);
+ when(tableConfig.getMetaFieldsMode()).thenReturn(resolvedMode);
+ when(metaClient.getTableConfig()).thenReturn(tableConfig);
+ when(table.getMetaClient()).thenReturn(metaClient);
+
+ SupportsUpgradeDowngrade helper = mock(SupportsUpgradeDowngrade.class);
+
when(helper.getTable(org.mockito.ArgumentMatchers.any(HoodieWriteConfig.class),
+
org.mockito.ArgumentMatchers.any(HoodieEngineContext.class))).thenReturn(table);
+ return helper;
+ }
+
+ @ParameterizedTest
+ @CsvSource({"ALL", "NONE"})
+ void upgradeRecordsTheModeDerivedFromTheLegacyBoolean(String modeName) {
+ MetaFieldsMode expected = MetaFieldsMode.valueOf(modeName);
+ UpgradeDowngrade.TableConfigChangeSet changeSet = new
NineToTenUpgradeHandler().upgrade(
+ mock(HoodieWriteConfig.class), mock(HoodieEngineContext.class), "001",
helperFor(expected));
+
+ assertTrue(changeSet.propertiesToDelete().isEmpty());
Review Comment:
Done in 59eb54a — static import, matching `TestTenToNineDowngradeHandler`.
--
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]