voonhous commented on code in PR #19463:
URL: https://github.com/apache/hudi/pull/19463#discussion_r3832454731


##########
hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/utils/HoodieRealtimeInputFormatUtils.java:
##########
@@ -128,16 +128,33 @@ public static boolean canAddProjectionToJobConf(final 
RealtimeSplit realtimeSpli
   }
 
   /**
-   * Hive will append read columns' ids to old columns' ids during 
getRecordReader. In some cases, e.g. SELECT COUNT(*),
-   * the read columns' id is an empty string and Hive will combine it with 
Hoodie required projection ids and becomes
-   * e.g. ",2,0,3" and will cause an error. Actually this method is a 
temporary solution because the real bug is from
-   * Hive. Hive has fixed this bug after 3.0.0, but the version before that 
would still face this problem. (HIVE-22438)
+   * Drops blank entries from the read-column id list held in {@code conf}.
+   *
+   * <p>For {@code SELECT COUNT(*)} on Hive before 3.0.0 the read-column ids 
arrive empty and Hive combines
+   * them into e.g. {@code ",2,0,3"} (HIVE-22438). Every consumer parses those 
ids with
+   * {@code Integer#parseInt}, so a blank entry fails with a bare {@code 
NumberFormatException} that carries
+   * none of the projection lists: {@code 
SchemaEvolutionContext#setColumnTypeList},
+   * {@code HoodieColumnProjectionUtils#getReadColumnIDs} and
+   * {@code HoodieRealtimeRecordReaderUtils#orderFields} all do this. Cleaning 
the conf once here covers all
+   * of them, including the bootstrap path that never reaches {@code 
orderFields}.
+   *
+   * <p>This is a workaround: the underlying bug is in Hive, fixed after 
3.0.0, but earlier versions still
+   * hit it. Stripping a single leading comma is not enough. Hive prepends ids 
while appending names, so repeated
+   * empty appends give {@code ",,2,0"}, and an id prepended after an empty 
one gives {@code "3,,2,0"} where
+   * the blank is interior and no amount of leading-comma stripping reaches it.
    */
   public static void cleanProjectionColumnIds(Configuration conf) {
-    String columnIds = 
conf.get(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR);
-    if (!columnIds.isEmpty() && columnIds.charAt(0) == ',') {
-      conf.set(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR, 
columnIds.substring(1));
-      LOG.debug("The projection Ids: {{}} start with ','. First comma is 
removed", columnIds);
+    String columnIds = 
conf.get(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR, "");
+    if (columnIds.isEmpty()) {
+      return;
+    }

Review Comment:
   Dead once `conf.get` has a default: `"".split(",")` gives `[""]`, the filter 
drops it, the join yields `""`, and `"".equals(columnIds)` skips the write 
anyway. Deleting these three lines keeps 
`testCleanProjectionColumnIdsWithUnsetKey` and the `clean("")` case green.
   
   Separately, the write-back guard at `:155` is unpinned. A mutant that always 
calls `conf.set` leaves the suite green, so the "should be left alone" messages 
at `TestHoodieRealtimeInputFormatUtils:72-73` assert nothing.
   
   Delete the early return and reword those two messages, or pin the guard with 
a mockito spy (`verify(spied, never()).set(...)`; mockito is already on this 
module's test classpath).



##########
hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/utils/HoodieRealtimeRecordReaderUtils.java:
##########
@@ -273,15 +273,25 @@ public static List<String> orderFields(String 
fieldNameCsv, String fieldOrderCsv
     // /org/apache/hadoop/hive/serde2/ColumnProjectionUtils.java#L188}
     // Field Names -> {@link 
https://github.com/apache/hive/blob/f37c5de6c32b9395d1b34fa3c02ed06d1bfbf6eb/serde/src/java
     // /org/apache/hadoop/hive/serde2/ColumnProjectionUtils.java#L229}
-    String[] fieldOrdersWithDups = fieldOrderCsv.isEmpty() ? new String[0] : 
fieldOrderCsv.split(",");
+    // Defence in depth. 
HoodieRealtimeInputFormatUtils#cleanProjectionColumnIds now drops blank ids 
from the
+    // JobConf before any reader runs, which is what HIVE-22438 produces for 
SELECT COUNT(*) on Hive before
+    // 3.0.0, so blanks should no longer arrive here. Callers that assemble 
the csv without going through that
+    // conf still can, and a blank token would otherwise reach 
Integer.parseInt below and fail with a bare
+    // NumberFormatException carrying neither projection list. Trim before 
filtering so a padded id parses and
+    // de-duplicates as the same entry rather than as a distinct one.
+    String[] fieldOrdersWithDups = Arrays.stream(fieldOrderCsv.split(","))
+        .map(String::trim).filter(id -> !id.isEmpty()).toArray(String[]::new);

Review Comment:
   This trim and its sibling at `HoodieRealtimeInputFormatUtils:152` are pinned 
by nothing. No test input contains whitespace, so deleting both leaves the 
suite green. It matters more here: without it `Integer.parseInt(" 0")` at 
`:299` throws the bare NFE this PR removes, and `"2"` / `" 2"` count as 
distinct set entries.
   
   One assertion each side, both verified on this branch:
   
   ```java
   assertEquals(Arrays.asList("c", "b"),
       HoodieRealtimeRecordReaderUtils.orderFields("b,c", " 2 , 0 ", 
Collections.emptyList()));
   assertEquals("2,0", clean(" 2 , 0 "));   // 
TestHoodieRealtimeInputFormatUtils
   ```



##########
hudi-hadoop-mr/src/test/java/org/apache/hudi/hadoop/utils/TestHoodieRealtimeInputFormatUtils.java:
##########
@@ -45,4 +50,38 @@ public void testAddProjectionField() {
     hadoopConf.set(hive_metastoreConstants.META_TABLE_PARTITION_COLUMNS, "");
     HoodieRealtimeInputFormatUtils.addProjectionField(hadoopConf, 
hadoopConf.get(hive_metastoreConstants.META_TABLE_PARTITION_COLUMNS, 
"").split("/"));
   }
+
+  private String clean(String columnIds) {
+    hadoopConf.set(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR, columnIds);
+    HoodieRealtimeInputFormatUtils.cleanProjectionColumnIds(hadoopConf);
+    return hadoopConf.get(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR);
+  }
+
+  /**
+   * HIVE-22438: for {@code SELECT COUNT(*)} on Hive before 3.0.0 the 
read-column ids arrive empty and Hive
+   * combines them into e.g. {@code ",2,0,3"}. Every consumer of this conf 
value parses the ids with
+   * {@code Integer#parseInt}, so any blank entry left behind fails with a 
bare {@code NumberFormatException}.
+   */
+  @Test
+  public void testCleanProjectionColumnIdsDropsBlankEntries() {

Review Comment:
   Nothing exercises the two changed methods together, so the wiring this PR 
rests on is unpinned. `addProjectionToJobConf` has no tests at all.
   
   No new fixture needed. `TestHoodieRealtimeRecordReader` already sets 
`FILE_GROUP_READER_ENABLED=false` at `:126` and drives 
`HoodieParquetRealtimeInputFormat.getRecordReader` at `:730` and `:778`. After 
`setHiveColumnNameProps(...)` add:
   
   ```java
   newJobConf.set(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR,
       ",," + newJobConf.get(ColumnProjectionUtils.READ_COLUMN_IDS_CONF_STR));
   ```
   
   then assert the rows still read. Two blanks, not one: master strips a single 
leading comma at `:137`.



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