voonhous commented on code in PR #19510:
URL: https://github.com/apache/hudi/pull/19510#discussion_r3774251713
##########
hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/HoodieParquetInputFormat.java:
##########
@@ -233,4 +234,34 @@ private RecordReader<NullWritable, ArrayWritable>
createBootstrappingRecordReade
true);
}
}
+
+ /**
+ * The single file backing this read, or empty when both files are needed
and have to be stitched.
+ *
+ * <p>A bootstrap split carries two paths: the split itself is the skeleton
file, which lives inside the
+ * table root, and {@code getBootstrapFileSplit()} is the external source
file, which does not.
+ *
+ * <p>The two "only one file is needed" cases both apply when a query
projects no columns at all, as
+ * {@code SELECT COUNT(*)} does, so the order they are tested in decides
which file is read. Prefer the
+ * skeleton: it is inside the table root, and bootstrap keeps a one-to-one
row correspondence with the
+ * external file, so a count over it is identical. Handing Hive a path
outside the table root breaks its
+ * vectorized reader, which derives partition values by looking the split
path up in
+ * {@code pathToPartitionInfo} (HUDI-5526).
+ *
+ * @param split the bootstrap split.
+ * @param anyHoodieColProjected whether the query projects any Hudi meta
column.
+ * @param anyExternalColProjected whether the query projects any column from
the external file.
Review Comment:
The PR body's closing note -- "`INPUT__FILE__NAME` now reports the skeleton
path rather than the external path" -- describes a behaviour change that does
not happen, and it is worth removing because reviewers will go looking for it.
Hive takes that value from the outer split, not from whichever inner split
Hudi opens: `HiveInputFormat.getRecordReader` calls
`HiveContextAwareRecordReader.initIOContext(hsplit, job, ifClass,
innerReader)`, which does `IOContext.setInputPath(split.getPath())` on the
`HiveInputSplit`. That path is `BootstrapBaseFileSplit.getPath()`, i.e. the
skeleton, both before and after this PR.
Please drop that paragraph from the PR body.
##########
hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/HoodieParquetInputFormat.java:
##########
@@ -233,4 +234,34 @@ private RecordReader<NullWritable, ArrayWritable>
createBootstrappingRecordReade
true);
}
}
+
+ /**
+ * The single file backing this read, or empty when both files are needed
and have to be stitched.
+ *
+ * <p>A bootstrap split carries two paths: the split itself is the skeleton
file, which lives inside the
+ * table root, and {@code getBootstrapFileSplit()} is the external source
file, which does not.
+ *
+ * <p>The two "only one file is needed" cases both apply when a query
projects no columns at all, as
+ * {@code SELECT COUNT(*)} does, so the order they are tested in decides
which file is read. Prefer the
+ * skeleton: it is inside the table root, and bootstrap keeps a one-to-one
row correspondence with the
+ * external file, so a count over it is identical. Handing Hive a path
outside the table root breaks its
+ * vectorized reader, which derives partition values by looking the split
path up in
+ * {@code pathToPartitionInfo} (HUDI-5526).
+ *
+ * @param split the bootstrap split.
+ * @param anyHoodieColProjected whether the query projects any Hudi meta
column.
+ * @param anyExternalColProjected whether the query projects any column from
the external file.
+ */
+ @VisibleForTesting
+ static Option<FileSplit> resolveSingleFileSplit(BootstrapBaseFileSplit split,
+ boolean
anyHoodieColProjected,
+ boolean
anyExternalColProjected) {
+ if (!anyExternalColProjected) {
+ return Option.of(split);
+ } else if (!anyHoodieColProjected) {
+ return Option.of(split.getBootstrapFileSplit());
Review Comment:
This branch still hands Hive a path outside the table root, so HUDI-5526 is
only partly fixed. Vectorization is selected by a job-level flag, not by the
split: `MapredParquetInputFormat.getRecordReader` branches on
`Utilities.getIsVectorized(job)`, and
`VectorizedParquetRecordReader.initPartitionValues` is gated only on
`rbCtx.getPartitionColumnCount() > 0`. So `SELECT <data_col> FROM
bootstrap_tbl` on a partitioned table throws the identical `cannot find dir`
after this change, and so does the stitch branch, which passes `rightSplit`
(the external split) into `getRecordReaderInternal` at line 232.
You established this yourself on the issue: "This is not actually specific
to COUNT ... a plain projection of data columns should take the same branch and
fail the same way on Hive 3."
Two things to do: scope the PR body's Impact section to the no-projection
shape instead of "Hive 3 `SELECT COUNT(*)` ... stops failing", and file a
follow-up issue for the external-only and stitch branches, so `Closes #15676`
does not bury the part that is still broken. Worth recording the `set
hive.vectorized.execution.enabled=false` workaround there.
##########
hudi-hadoop-mr/src/test/java/org/apache/hudi/hadoop/TestHoodieParquetInputFormatBootstrapSplitSelection.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.hadoop;
+
+import org.apache.hudi.common.util.Option;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.FileSplit;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Which of a bootstrap split's two files backs the read, per projection.
+ *
+ * <p>A bootstrap split carries the skeleton file as its own path - inside the
table root - and the external
+ * source file separately, outside it. Handing Hive a path outside the table
root breaks its vectorized
+ * parquet reader, which derives partition values by looking the split path up
in {@code pathToPartitionInfo}
+ * and fails with {@code cannot find dir = [...] in pathToPartitionInfo:
[...]} (HUDI-5526, #15676). Hive 2
+ * never vectorized this path, which is why the same query worked there.
+ *
+ * <p>This is the first coverage of that selection: nothing in the tree
referenced
+ * {@code BootstrapBaseFileSplit} or the reader built from it.
+ */
+class TestHoodieParquetInputFormatBootstrapSplitSelection {
+
+ private static final Path SKELETON = new
Path("s3://bucket/hudi-table/tbl/event_type=two/skeleton.parquet");
+ private static final Path EXTERNAL = new
Path("s3://bucket/source-tables/tbl/event_type=two/part-0.parquet");
+
+ private static BootstrapBaseFileSplit split() throws IOException {
+ return new BootstrapBaseFileSplit(
+ new FileSplit(SKELETON, 0, 100, (String[]) null),
+ new FileSplit(EXTERNAL, 0, 100, (String[]) null));
+ }
+
+ /**
+ * {@code SELECT COUNT(*)} projects nothing, so both "only one file is
needed" cases apply at once and the
+ * order they are tested in decides the answer. It has to be the skeleton:
it is inside the table root, and
+ * bootstrap keeps a one-to-one row correspondence, so the count is the same
either way.
+ */
+ @Test
+ void testCountStarReadsSkeletonSoSplitPathStaysInsideTable() throws
IOException {
+ BootstrapBaseFileSplit split = split();
+
+ Option<FileSplit> resolved =
HoodieParquetInputFormat.resolveSingleFileSplit(split, false, false);
+
+ assertSame(split, resolved.get(),
+ "a query projecting no columns must read the skeleton, whose path is
inside the table root");
+ assertEquals(SKELETON, resolved.get().getPath());
+ }
+
+ /** Only meta columns projected: the external file is not needed. */
+ @Test
+ void testMetaColumnsOnlyReadsTheSkeleton() throws IOException {
Review Comment:
Nit, feel free to ignore. Only
`testCountStarReadsSkeletonSoSplitPathStaysInsideTable` discriminates the fix;
these three pass identically before and after the reorder, and the whole input
domain is a four-row truth table. Collapsing them into one `@ParameterizedTest`
over `(anyHoodieCol, anyExternalCol, expected)` and keeping the count(*) case
as its own named test with the HUDI-5526 comment would read better. The module
already uses `@MethodSource` for this --
`TestHoodieRealtimeRecordReader.java:170`.
Same for the standalone class. The "would otherwise be re-run by
`TestGloballyConsistentTimeStampFilteringInputFormat`" justification is
mechanically true but costs nothing: that subclass already re-runs 18 IO-heavy
inherited tests in 0.823s total, so four boolean assertions are free. Given the
standing preference against new test files, folding these into
`TestHoodieParquetInputFormat` is the safer default -- and it is where the
reader-level test from my other comment would live anyway.
##########
hudi-hadoop-mr/src/test/java/org/apache/hudi/hadoop/TestHoodieParquetInputFormatBootstrapSplitSelection.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.hadoop;
+
+import org.apache.hudi.common.util.Option;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.FileSplit;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Which of a bootstrap split's two files backs the read, per projection.
+ *
+ * <p>A bootstrap split carries the skeleton file as its own path - inside the
table root - and the external
+ * source file separately, outside it. Handing Hive a path outside the table
root breaks its vectorized
+ * parquet reader, which derives partition values by looking the split path up
in {@code pathToPartitionInfo}
+ * and fails with {@code cannot find dir = [...] in pathToPartitionInfo:
[...]} (HUDI-5526, #15676). Hive 2
+ * never vectorized this path, which is why the same query worked there.
+ *
+ * <p>This is the first coverage of that selection: nothing in the tree
referenced
+ * {@code BootstrapBaseFileSplit} or the reader built from it.
+ */
+class TestHoodieParquetInputFormatBootstrapSplitSelection {
+
+ private static final Path SKELETON = new
Path("s3://bucket/hudi-table/tbl/event_type=two/skeleton.parquet");
+ private static final Path EXTERNAL = new
Path("s3://bucket/source-tables/tbl/event_type=two/part-0.parquet");
+
+ private static BootstrapBaseFileSplit split() throws IOException {
+ return new BootstrapBaseFileSplit(
+ new FileSplit(SKELETON, 0, 100, (String[]) null),
+ new FileSplit(EXTERNAL, 0, 100, (String[]) null));
+ }
+
+ /**
+ * {@code SELECT COUNT(*)} projects nothing, so both "only one file is
needed" cases apply at once and the
+ * order they are tested in decides the answer. It has to be the skeleton:
it is inside the table root, and
+ * bootstrap keeps a one-to-one row correspondence, so the count is the same
either way.
+ */
+ @Test
+ void testCountStarReadsSkeletonSoSplitPathStaysInsideTable() throws
IOException {
+ BootstrapBaseFileSplit split = split();
+
+ Option<FileSplit> resolved =
HoodieParquetInputFormat.resolveSingleFileSplit(split, false, false);
+
+ assertSame(split, resolved.get(),
+ "a query projecting no columns must read the skeleton, whose path is
inside the table root");
+ assertEquals(SKELETON, resolved.get().getPath());
Review Comment:
Nit, feel free to ignore: once the `assertSame` above holds, this adds
nothing, and it leaves the file with three different assertion styles (identity
here, path-only at lines 80 and 90). Pick identity -- it is the stronger claim
for this helper -- and drop this line.
Separately, none of the four tests assert `isPresent()` before calling
`get()`, so a regression surfaces as a bare `NoSuchElementException` rather
than the messages you wrote.
##########
hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/HoodieParquetInputFormat.java:
##########
@@ -233,4 +234,34 @@ private RecordReader<NullWritable, ArrayWritable>
createBootstrappingRecordReade
true);
}
}
+
+ /**
+ * The single file backing this read, or empty when both files are needed
and have to be stitched.
+ *
+ * <p>A bootstrap split carries two paths: the split itself is the skeleton
file, which lives inside the
+ * table root, and {@code getBootstrapFileSplit()} is the external source
file, which does not.
+ *
+ * <p>The two "only one file is needed" cases both apply when a query
projects no columns at all, as
+ * {@code SELECT COUNT(*)} does, so the order they are tested in decides
which file is read. Prefer the
+ * skeleton: it is inside the table root, and bootstrap keeps a one-to-one
row correspondence with the
+ * external file, so a count over it is identical. Handing Hive a path
outside the table root breaks its
+ * vectorized reader, which derives partition values by looking the split
path up in
+ * {@code pathToPartitionInfo} (HUDI-5526).
+ *
+ * @param split the bootstrap split.
+ * @param anyHoodieColProjected whether the query projects any Hudi meta
column.
+ * @param anyExternalColProjected whether the query projects any column from
the external file.
+ */
+ @VisibleForTesting
+ static Option<FileSplit> resolveSingleFileSplit(BootstrapBaseFileSplit split,
+ boolean
anyHoodieColProjected,
+ boolean
anyExternalColProjected) {
+ if (!anyExternalColProjected) {
Review Comment:
The PR body's "Applies to both COW and MOR bootstrap tables" does not hold
for the MOR case that matters. When a `HoodieRealtimeBootstrapBaseFileSplit`
has delta log paths, `HoodieParquetRealtimeInputFormat.addProjectionToJobConf`
calls `addVirtualKeysProjection` (`HoodieParquetRealtimeInputFormat.java:113`),
which injects `_hoodie_record_key` / `_hoodie_commit_time` /
`_hoodie_partition_path` through the private 3-arg `addProjectionField`
(`HoodieRealtimeInputFormatUtils.java:101-103`) -- the overload that does not
consult `LIST_COLUMNS` and so always succeeds. `hoodieColsProjected` is
therefore never empty on that path and this branch is unreachable; if the table
also has ordering fields configured they get added too, which pushes `COUNT(*)`
into the stitch branch and the crash described above.
The change reaches MOR only for a bootstrap file slice with no log files.
Please narrow that sentence in the PR body to say exactly that.
##########
hudi-hadoop-mr/src/test/java/org/apache/hudi/hadoop/TestHoodieParquetInputFormatBootstrapSplitSelection.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.hadoop;
+
+import org.apache.hudi.common.util.Option;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.FileSplit;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Which of a bootstrap split's two files backs the read, per projection.
+ *
+ * <p>A bootstrap split carries the skeleton file as its own path - inside the
table root - and the external
+ * source file separately, outside it. Handing Hive a path outside the table
root breaks its vectorized
+ * parquet reader, which derives partition values by looking the split path up
in {@code pathToPartitionInfo}
+ * and fails with {@code cannot find dir = [...] in pathToPartitionInfo:
[...]} (HUDI-5526, #15676). Hive 2
+ * never vectorized this path, which is why the same query worked there.
Review Comment:
`hive-exec-2.3.10` ships the identical
`VectorizedParquetRecordReader.initPartitionValues` with the same
`getPartitionValues` -> `getFromPathRecursively` chain:
```
javap -p -c -cp
~/.m2/repository/org/apache/hive/hive-exec/2.3.10/hive-exec-2.3.10.jar \
org.apache.hadoop.hive.ql.io.parquet.vector.VectorizedParquetRecordReader
| grep initPartitionValues
```
The difference is the `hive.vectorized.execution.enabled` default: `false`
in `hive-common-2.3.10`, `true` in `3.1.3` (`javap` on `HiveConf$ConfVars`
shows `iconst_0` vs `iconst_1`). That matters for users -- a Hive 2 deployment
with vectorization turned on hits this bug too, so the guard is config-driven,
not version-gated.
The same sentence is in the PR body; please fix both.
```suggestion
* and fails with {@code cannot find dir = [...] in pathToPartitionInfo:
[...]} (HUDI-5526, #15676). Hive 2.3
* ships the same reader but defaults {@code
hive.vectorized.execution.enabled} to false, where Hive 3
* defaults it to true, which is why the same query worked there.
```
##########
hudi-hadoop-mr/src/test/java/org/apache/hudi/hadoop/TestHoodieParquetInputFormatBootstrapSplitSelection.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.hadoop;
+
+import org.apache.hudi.common.util.Option;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.FileSplit;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Which of a bootstrap split's two files backs the read, per projection.
+ *
+ * <p>A bootstrap split carries the skeleton file as its own path - inside the
table root - and the external
+ * source file separately, outside it. Handing Hive a path outside the table
root breaks its vectorized
+ * parquet reader, which derives partition values by looking the split path up
in {@code pathToPartitionInfo}
+ * and fails with {@code cannot find dir = [...] in pathToPartitionInfo:
[...]} (HUDI-5526, #15676). Hive 2
+ * never vectorized this path, which is why the same query worked there.
+ *
+ * <p>This is the first coverage of that selection: nothing in the tree
referenced
+ * {@code BootstrapBaseFileSplit} or the reader built from it.
+ */
+class TestHoodieParquetInputFormatBootstrapSplitSelection {
+
+ private static final Path SKELETON = new
Path("s3://bucket/hudi-table/tbl/event_type=two/skeleton.parquet");
+ private static final Path EXTERNAL = new
Path("s3://bucket/source-tables/tbl/event_type=two/part-0.parquet");
+
+ private static BootstrapBaseFileSplit split() throws IOException {
+ return new BootstrapBaseFileSplit(
+ new FileSplit(SKELETON, 0, 100, (String[]) null),
+ new FileSplit(EXTERNAL, 0, 100, (String[]) null));
+ }
+
+ /**
+ * {@code SELECT COUNT(*)} projects nothing, so both "only one file is
needed" cases apply at once and the
+ * order they are tested in decides the answer. It has to be the skeleton:
it is inside the table root, and
+ * bootstrap keeps a one-to-one row correspondence, so the count is the same
either way.
+ */
+ @Test
+ void testCountStarReadsSkeletonSoSplitPathStaysInsideTable() throws
IOException {
Review Comment:
The four tests assert a three-line pure function over two booleans, but the
bug is in how those booleans get derived and in what the reader then opens.
Neither `COUNT(*)` producing `(false, false)` nor "the resulting reader reads
the skeleton" is exercised, so this suite would not have caught HUDI-5526 and
will not catch a regression in it.
A reader-level test is cheap here and needs no Hudi table:
`shouldUseFilegroupReader` ends with `&& !(split instanceof
BootstrapBaseFileSplit)` (`HoodieInputFormatUtils.java:571`), so
`inputFormat.getRecordReader(bootstrapSplit, jobConf, null)` falls straight
through to `createBootstrappingRecordReader` -- no metaclient, no bootstrap
index, no listing. `TestHoodieParquetInputFormat.java:779`
(`testHoodieParquetInputFormatReadTimeType`) already has the whole pattern:
`AvroParquetWriter`, `IOConstants.COLUMNS`, `READ_COLUMN_NAMES_CONF_STR`,
`getRecordReader`.
Please add one test that goes through `getRecordReader`: write a skeleton
file with N rows and an external file with M != N rows, wrap them in a
`BootstrapBaseFileSplit`, set `READ_COLUMN_NAMES_CONF_STR` to empty, and assert
the reader yields N. That fails on master (yields M) and passes here, which is
the assertion this PR actually needs.
##########
hudi-hadoop-mr/src/test/java/org/apache/hudi/hadoop/TestHoodieParquetInputFormatBootstrapSplitSelection.java:
##########
@@ -0,0 +1,102 @@
+/*
+ * 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.hadoop;
+
+import org.apache.hudi.common.util.Option;
+
+import org.apache.hadoop.fs.Path;
+import org.apache.hadoop.mapred.FileSplit;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+/**
+ * Which of a bootstrap split's two files backs the read, per projection.
+ *
+ * <p>A bootstrap split carries the skeleton file as its own path - inside the
table root - and the external
+ * source file separately, outside it. Handing Hive a path outside the table
root breaks its vectorized
+ * parquet reader, which derives partition values by looking the split path up
in {@code pathToPartitionInfo}
+ * and fails with {@code cannot find dir = [...] in pathToPartitionInfo:
[...]} (HUDI-5526, #15676). Hive 2
+ * never vectorized this path, which is why the same query worked there.
+ *
+ * <p>This is the first coverage of that selection: nothing in the tree
referenced
+ * {@code BootstrapBaseFileSplit} or the reader built from it.
Review Comment:
Accurate about the class name, misleading about coverage.
`TestBootstrap.checkBootstrapResults` drives three of these four branches end
to end on a METADATA_ONLY bootstrap table: full projection reaches the stitch
branch, "RO Input Format Read - Project only Hoodie Columns" is at
`TestBootstrap.java:613`, "Project only non-hoodie column" at `:633`.
`TestOrcBootstrap` does the same. They simply never run -- both are
`@Disabled("HUDI-7353")` (`TestBootstrap.java:125`), disabled by `565e7c566ed9`
(#10551, 2024-01-29).
Worth stating accurately because it changes what is new here: only the
`(false, false)` case is genuinely uncovered behaviour.
```suggestion
* <p>Only the no-projection case is new behaviour: nothing in the tree
referenced
* {@code BootstrapBaseFileSplit}, but TestBootstrap and TestOrcBootstrap
cover the other three branches
* end to end -- they have just been disabled (HUDI-7353) since #10551.
```
##########
hudi-hadoop-mr/src/main/java/org/apache/hudi/hadoop/HoodieParquetInputFormat.java:
##########
@@ -210,10 +211,10 @@ private RecordReader<NullWritable, ArrayWritable>
createBootstrappingRecordReade
LOG.info("colNameWithTypes ={}, Num Entries ={}", colNameWithTypes,
colNameWithTypes.size());
- if (hoodieColsProjected.isEmpty()) {
- return getRecordReaderInternal(eSplit.getBootstrapFileSplit(), job,
reporter);
- } else if (externalColsProjected.isEmpty()) {
- return getRecordReaderInternal(split, job, reporter);
+ Option<FileSplit> singleSplit = resolveSingleFileSplit(eSplit,
!hoodieColsProjected.isEmpty(),
Review Comment:
Optional, and arguably a separate PR, but you are refactoring exactly this
decision so it is worth flagging: the `Integer` half of
`projectedColsWithIndex` (lines 194-205) is never read -- both lists are
consumed only via `isEmpty()`, here and nowhere else.
The zip that builds it is a live crash source. It pairs `rawColIds`, which
`getReadColumnIDs` de-duplicates (`HoodieColumnProjectionUtils.java:88-97`),
with `rawColNames`, which `getReadColumnNames` does not, and parses each id
with `Integer.parseInt`. Both hazards reproduce today: ids `",2,5"` -- the
HIVE-22438 shape that `cleanProjectionColumnIds` scrubs on the realtime path
but never on this one -- throws `NumberFormatException`, and a duplicated name
makes `rawColNames[idx]` either misalign or throw
`ArrayIndexOutOfBoundsException`. Hive itself tolerates mismatched lists; it
only logs `Read column counts do not match`.
Deriving the two booleans from names alone deletes lines 193-205 and both
hazards, with no behaviour change:
```java
boolean anyHoodieCol =
Arrays.stream(rawColNames).anyMatch(HoodieRecord.HOODIE_META_COLUMNS::contains);
boolean anyExternalCol = Arrays.stream(rawColNames).anyMatch(n ->
!HoodieRecord.HOODIE_META_COLUMNS.contains(n) &&
!HoodieHiveUtils.VIRTUAL_COLUMN_NAMES.contains(n));
Option<FileSplit> singleSplit = resolveSingleFileSplit(eSplit, anyHoodieCol,
anyExternalCol);
```
Both imports are already present. If you would rather keep this PR minimal,
#19506 is the natural home for it.
--
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]