vinishjail97 commented on code in PR #19869:
URL: https://github.com/apache/hudi/pull/19869#discussion_r3974592001
##########
hudi-common/src/main/java/org/apache/hudi/common/util/FileFormatUtils.java:
##########
@@ -252,6 +266,22 @@ public abstract Map<String, String>
readFooter(HoodieStorage storage, boolean re
*/
public abstract Set<Pair<String, Long>> filterRowKeys(HoodieStorage storage,
StoragePath filePath, Set<String> filter);
+ /**
+ * Read the rowKey list matching the given filter, from the given data file.
+ * If the filter is empty, then this will return all the row keys and
corresponding positions.
+ * Formats that support data files written outside Hudi override this method
to key every row of such a file,
+ * which carries no record key, by the file path relative to the table base
path and the row position.
+ *
+ * @param storage {@link HoodieStorage} instance.
+ * @param filePath the data file path.
+ * @param basePath the table base path.
+ * @param filter record keys filter.
+ * @return set of pairs of row key and position matching candidateRecordKeys.
+ */
+ public Set<Pair<String, Long>> filterRowKeys(HoodieStorage storage,
StoragePath filePath, StoragePath basePath, Set<String> filter) {
+ return filterRowKeys(storage, filePath, filter);
Review Comment:
Applied in 9085866, and the new `getRowKeyIterator` default throws the same
`UnsupportedOperationException`.
##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/index/record/BaseRecordIndexer.java:
##########
@@ -258,11 +260,29 @@ protected static <T> HoodieData<HoodieRecord>
readRecordKeysFromFileSliceSnapsho
engineContext.setJobStatus(activeModule, "Record Index: reading record
keys from " + fileSlices.size() + " file slices");
final int parallelism = Math.min(fileSlices.size(),
recordIndexMaxParallelism);
+ // a table without record keys, e.g. one that registers files written
outside Hudi, keys every row by the path of
+ // its base file relative to the table and the row position, the same way
the record index update path does
+ final boolean generateRecordKeys =
!metaClient.getTableConfig().hasRecordKey();
+ final int fileIdEncoding = dataWriteConfig.getWritesFileIdEncoding();
ReaderContextFactory<T> readerContextFactory =
engineContext.getReaderContextFactory(metaClient);
return engineContext.parallelize(fileSlices,
parallelism).flatMap(partitionAndFileSlice -> {
final String partition = partitionAndFileSlice.getPartitionPath();
final FileSlice fileSlice = partitionAndFileSlice.getFileSlice();
final String fileId = fileSlice.getFileId();
+ long baseFileInstantTimeMillis =
HoodieMetadataPayload.parseRecordIndexInstantTime(fileSlice.getBaseInstantTime());
+ if (generateRecordKeys) {
Review Comment:
Added the unit tests in 9085866: `TestRecordIndexer` covers the initialize
guard through `readRecordKeysFromFileSliceSnapshot`, and
`TestSecondaryIndexRecordGenerationUtils` covers the secondary index twin
through `getRecordKeyToSecondaryKey`, both with a log file on a `hasRecordKey()
== false` slice. I left the up-front enablement gate out: a table converted
from another format never has log files, so the guard is a consistency check
rather than a user-facing configuration error, and rejecting the index configs
would also block the metadata table for tables that already carry them.
##########
hudi-client/hudi-client-common/src/test/java/org/apache/hudi/metadata/index/secondary/TestSecondaryIndexer.java:
##########
@@ -171,6 +176,51 @@ void testBuildUpdateForCompactReturnsEmpty() {
commitMetadata)).isEmpty());
}
+ @Test
+ void testBuildUpdateForReplaceCommitFromExternalWriterWithoutWriteStats() {
+ // files written outside Hudi are registered through replace commits
without a known operation type. A fresh
+ // HoodieCommitMetadata carries UNKNOWN; a writer may also leave the type
null. A commit that only drops files has
+ // no write stats but still removes the records of the replaced file
groups from the index.
+ HoodieEngineContext engineContext = new
HoodieLocalEngineContext(getDefaultStorageConf());
+ HoodieWriteConfig writeConfig = mock(HoodieWriteConfig.class);
+ HoodieMetadataConfig metadataConfig = mock(HoodieMetadataConfig.class);
+ HoodieTableMetaClient metaClient = mock(HoodieTableMetaClient.class);
+ HoodieTableConfig tableConfig = mock(HoodieTableConfig.class);
+ HoodieIndexMetadata indexMetadata = mock(HoodieIndexMetadata.class);
+ HoodieIndexDefinition indexDefinition = mock(HoodieIndexDefinition.class);
+
+ when(writeConfig.getMetadataConfig()).thenReturn(metadataConfig);
+ when(metaClient.getIndexMetadata()).thenReturn(Option.of(indexMetadata));
+ when(metaClient.getTableConfig()).thenReturn(tableConfig);
+
when(metaClient.getIndexForMetadataPartition("secondary_index_idx")).thenReturn(Option.of(indexDefinition));
+
when(tableConfig.getMetadataPartitions()).thenReturn(Collections.singleton("secondary_index_idx"));
+
when(indexMetadata.getIndexDefinitions()).thenReturn(Collections.singletonMap("secondary_index_idx",
indexDefinition));
+ when(indexDefinition.getIndexName()).thenReturn("secondary_index_idx");
+
+ HoodieReplaceCommitMetadata commitMetadata = new
HoodieReplaceCommitMetadata();
+ commitMetadata.setOperationType(null);
+ commitMetadata.addReplaceFileId("p1", "file_1.parquet");
+
+ HoodieData<HoodieRecord> deletes =
engineContext.parallelize(Collections.singletonList(
+
HoodieMetadataPayload.createSecondaryIndexRecord("p1/file_1.parquet_0",
"alice", "secondary_index_idx", true)), 1);
+ try (MockedStatic<SecondaryIndexRecordGenerationUtils>
mockedGenerationUtils = mockStatic(SecondaryIndexRecordGenerationUtils.class)) {
+ mockedGenerationUtils.when(() ->
SecondaryIndexRecordGenerationUtils.convertWriteStatsToSecondaryIndexRecords(
+ eq(Collections.emptyList()), eq("016"), eq(indexDefinition),
eq(metadataConfig), eq(metaClient), eq(engineContext), eq(writeConfig),
eq(commitMetadata)))
+ .thenReturn(deletes);
+
+ SecondaryIndexer indexer = new SecondaryIndexer(engineContext,
writeConfig, metaClient);
+ List<IndexPartitionAndRecords> result =
indexer.buildUpdate(IndexUpdateContext.of(
+ "016",
+ mock(HoodieBackedTableMetadata.class),
+ Lazy.lazily(() -> mock(HoodieTableFileSystemView.class)),
+ commitMetadata));
+
+ assertEquals(1, result.size());
+ assertEquals("secondary_index_idx", result.get(0).indexPartitionName());
+ assertEquals(1, result.get(0).indexRecords().collectAsList().size());
+ }
Review Comment:
Done in 9085866. The test is parameterized over the replace map: with a
replaced file id the stubbed deletes come back, with an empty map the result is
empty.
##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/index/partitionstats/PartitionStatsIndexer.java:
##########
@@ -114,7 +114,7 @@ public List<IndexPartitionAndRecords>
buildUpdate(IndexUpdateContext context) {
checkState(MetadataPartitionType.COLUMN_STATS.isMetadataPartitionAvailable(dataTableMetaClient),
"Column stats partition must be enabled to generate partition stats.
Please enable: " +
HoodieMetadataConfig.ENABLE_METADATA_INDEX_COLUMN_STATS.key());
// Generate Hoodie Pair data of partition name and list of column range
metadata for all the files in that partition
- boolean isDeletePartition =
context.commitMetadata().getOperationType().equals(WriteOperationType.DELETE_PARTITION);
+ boolean isDeletePartition =
WriteOperationType.DELETE_PARTITION.equals(context.commitMetadata().getOperationType());
Review Comment:
Done in 9085866, parameterized with `@NullSource` and `@EnumSource(names =
"UPSERT")`.
##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/SecondaryIndexRecordGenerationUtils.java:
##########
@@ -258,7 +318,8 @@ public static <T> HoodieData<HoodieRecord>
readSecondaryKeysFromFileSlices(Hoodi
return engineContext.parallelize(fileSlices,
parallelism).flatMap(partitionAndBaseFile -> {
final String partition = partitionAndBaseFile.getPartitionPath();
Review Comment:
Removed both in 9085866.
##########
hudi-common/src/main/java/org/apache/hudi/metadata/BaseFileRecordParsingUtils.java:
##########
@@ -65,16 +69,35 @@ public static Iterator<HoodieRecord>
generateRLIMetadataHoodieRecordsForBaseFile
String instantTime,
HoodieStorage storage,
boolean isPartitionedRLI) {
+ return generateRLIMetadataHoodieRecordsForBaseFile(basePath, writeStat,
writesFileIdEncoding, instantTime, storage, isPartitionedRLI, false);
Review Comment:
Removed in 9085866; the three test call sites pass `false`.
##########
hudi-client/hudi-client-common/src/main/java/org/apache/hudi/metadata/SecondaryIndexRecordGenerationUtils.java:
##########
@@ -205,6 +227,44 @@ public static <T> HoodieData<HoodieRecord>
convertWriteStatsToSecondaryIndexReco
return HoodieTableMetadataUtil.reduceByKeys(secondaryIndexRecords,
parallelism, false);
}
+ /**
+ * Generates delete records for every record of the file groups that the
given replace commit replaces without
+ * writing to them again, e.g. files written outside Hudi that are
superseded by newer files. Records that the same
Review Comment:
Reworded in 9085866. The javadoc now names the keyed-table case for the
`reduceByKeys` precedence and states that a keyless table never writes a file
group it replaces.
##########
hudi-spark-datasource/hudi-spark/src/test/java/org/apache/hudi/client/functional/TestExternalFileRecordAndSecondaryIndex.java:
##########
@@ -0,0 +1,342 @@
+/*
+ * 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.client.functional;
+
+import org.apache.hudi.client.WriteStatus;
+import org.apache.hudi.common.config.HoodieMetadataConfig;
+import org.apache.hudi.common.model.HoodieDeltaWriteStat;
+import org.apache.hudi.common.model.HoodieRecordGlobalLocation;
+import org.apache.hudi.common.model.WriteOperationType;
+import org.apache.hudi.common.schema.HoodieSchema;
+import org.apache.hudi.common.schema.HoodieSchemaField;
+import org.apache.hudi.common.schema.HoodieSchemaType;
+import org.apache.hudi.common.table.HoodieTableConfig;
+import org.apache.hudi.common.table.HoodieTableMetaClient;
+import org.apache.hudi.common.table.timeline.HoodieInstant;
+import org.apache.hudi.common.table.timeline.HoodieTimeline;
+import org.apache.hudi.common.util.ExternalFilePathUtil;
+import org.apache.hudi.common.util.Option;
+import org.apache.hudi.common.util.collection.Pair;
+import org.apache.hudi.config.HoodieIndexConfig;
+import org.apache.hudi.config.HoodieWriteConfig;
+import org.apache.hudi.data.HoodieJavaRDD;
+import org.apache.hudi.metadata.HoodieBackedTableMetadata;
+import org.apache.hudi.metadata.HoodieTableMetadataUtil;
+import org.apache.hudi.testutils.HoodieClientTestBase;
+
+import org.apache.avro.generic.GenericData;
+import org.apache.avro.generic.GenericRecord;
+import org.apache.hadoop.conf.Configuration;
+import org.apache.hadoop.fs.Path;
+import org.apache.parquet.avro.AvroParquetWriter;
+import org.apache.parquet.hadoop.ParquetWriter;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+
+import static
org.apache.hudi.common.testutils.HoodieTestUtils.INSTANT_GENERATOR;
+import static org.apache.hudi.index.HoodieIndex.IndexType.INMEMORY;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Asserts that the record index and the secondary index are maintained for
parquet files written outside Hudi
+ * and registered in the table through replace commits, the way tables
converted from other formats are.
+ * Such a table has no record key, so every row is keyed by the file path
relative to the table and the row position.
+ * Some writers, e.g. Paimon, place their files in a directory below the
partition; that prefix is part of the file id.
+ */
+public class TestExternalFileRecordAndSecondaryIndex extends
HoodieClientTestBase {
+
+ private static final String ID_FIELD = "id";
+ private static final String NAME_FIELD = "name";
+ private static final String PARTITION = "americas/brazil";
+ private static final String PREFIX = "bucket-0";
+ private static final HoodieSchema SCHEMA =
HoodieSchema.createRecord("external", null, null, false, Arrays.asList(
+ HoodieSchemaField.of(ID_FIELD,
HoodieSchema.create(HoodieSchemaType.INT)),
+ HoodieSchemaField.of(NAME_FIELD,
HoodieSchema.create(HoodieSchemaType.STRING))));
+
+ /** Partitioned and unpartitioned tables, with the files placed directly in
the partition or below a prefix. */
+ static Stream<Arguments> partitionsAndPrefixes() {
+ return Stream.of(
+ Arguments.of(PARTITION, Option.empty()),
+ Arguments.of("", Option.empty()),
+ Arguments.of(PARTITION, Option.of(PREFIX)),
+ Arguments.of("", Option.of(PREFIX)));
+ }
+
+ /** The shapes above with a global record index, plus a partitioned record
index, which the secondary index does not build on. */
+ static Stream<Arguments> partitionsPrefixesAndRecordIndexKinds() {
+ return Stream.concat(
+ partitionsAndPrefixes().map(arguments ->
Arguments.of(arguments.get()[0], arguments.get()[1], false)),
+ Stream.of(Arguments.of(PARTITION, Option.empty(), true)));
+ }
+
+ @ParameterizedTest
+ @MethodSource("partitionsPrefixesAndRecordIndexKinds")
+ public void testRecordAndSecondaryIndexForExternalFiles(String
partitionPath, Option<String> prefix, boolean partitionedRecordIndex) throws
Exception {
+ initExternalTable();
+ HoodieWriteConfig writeConfig = writeConfig(true, partitionedRecordIndex);
+ writeClient = getHoodieWriteClient(writeConfig);
+ writeClient.setOperationType(WriteOperationType.UNKNOWN);
+ Option<String> recordIndexPartition = partitionedRecordIndex ?
Option.of(partitionPath) : Option.empty();
+
+ // first commit registers one file with three rows
+ ExternalFile file1 = new ExternalFile(partitionPath, prefix,
"file_1.parquet");
+ commitReplace(Collections.singletonList(Pair.of(file1, rows(1, "alice", 2,
"bob", 3, "alice"))), Collections.emptyMap());
+
+ HoodieBackedTableMetadata tableMetadata = tableMetadata(writeConfig);
+ assertRecordIndex(tableMetadata, recordIndexPartition, file1, 3);
+ if (!partitionedRecordIndex) {
+ assertEquals(mapOf("alice", setOf(file1.key(0), file1.key(2)), "bob",
setOf(file1.key(1))),
+ readSecondaryIndex(tableMetadata, secondaryIndexPartition(),
Arrays.asList("alice", "bob", "carol")));
+ }
+
+ // second commit replaces the file with one that keeps bob and adds carol
+ ExternalFile file2 = new ExternalFile(partitionPath, prefix,
"file_2.parquet");
+ commitReplace(Collections.singletonList(Pair.of(file2, rows(2, "bob", 4,
"carol"))),
+ Collections.singletonMap(partitionPath,
Collections.singletonList(file1.fileId())));
+
+ tableMetadata = tableMetadata(writeConfig);
+ assertTrue(readRecordIndex(tableMetadata, recordIndexPartition,
file1.keys(3)).isEmpty());
+ assertRecordIndex(tableMetadata, recordIndexPartition, file2, 2);
+ if (!partitionedRecordIndex) {
+ assertEquals(mapOf("bob", setOf(file2.key(0)), "carol",
setOf(file2.key(1))),
+ readSecondaryIndex(tableMetadata, secondaryIndexPartition(),
Arrays.asList("alice", "bob", "carol")));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("partitionsAndPrefixes")
+ public void testIndexesAreBuiltFromRegisteredFilesWhenEnabledLater(String
partitionPath, Option<String> prefix) throws Exception {
+ initExternalTable();
+ // the first file is registered while both indexes are off
+ writeClient = getHoodieWriteClient(writeConfig(false, false));
+ writeClient.setOperationType(WriteOperationType.UNKNOWN);
+ ExternalFile file1 = new ExternalFile(partitionPath, prefix,
"file_1.parquet");
+ commitReplace(Collections.singletonList(Pair.of(file1, rows(1, "alice", 2,
"bob", 3, "alice"))), Collections.emptyMap());
+
+ // the second file is registered with both indexes on, which first builds
them from the registered file
+ HoodieWriteConfig indexedWriteConfig = writeConfig(true, false);
+ writeClient = getHoodieWriteClient(indexedWriteConfig);
+ writeClient.setOperationType(WriteOperationType.UNKNOWN);
+ ExternalFile file2 = new ExternalFile(partitionPath, prefix,
"file_2.parquet");
+ commitReplace(Collections.singletonList(Pair.of(file2, rows(4, "carol"))),
Collections.emptyMap());
+
+ HoodieBackedTableMetadata tableMetadata =
tableMetadata(indexedWriteConfig);
+ assertRecordIndex(tableMetadata, Option.empty(), file1, 3);
+ assertRecordIndex(tableMetadata, Option.empty(), file2, 1);
+ String secondaryIndexPartition = secondaryIndexPartition();
+ assertEquals(mapOf("alice", setOf(file1.key(0), file1.key(2)), "bob",
setOf(file1.key(1)), "carol", setOf(file2.key(0))),
+ readSecondaryIndex(tableMetadata, secondaryIndexPartition,
Arrays.asList("alice", "bob", "carol")));
+
+ // the third commit only drops the first file
+ commitReplace(Collections.emptyList(),
Collections.singletonMap(partitionPath,
Collections.singletonList(file1.fileId())));
+
+ tableMetadata = tableMetadata(indexedWriteConfig);
+ assertTrue(readRecordIndex(tableMetadata, Option.empty(),
file1.keys(3)).isEmpty());
+ assertRecordIndex(tableMetadata, Option.empty(), file2, 1);
+ assertEquals(mapOf("carol", setOf(file2.key(0))),
+ readSecondaryIndex(tableMetadata, secondaryIndexPartition,
Arrays.asList("alice", "bob", "carol")));
+ }
+
+ /** Tables registered from other formats carry neither meta fields nor
record key fields. */
+ private void initExternalTable() throws IOException {
+ Properties tableProperties = new Properties();
+ tableProperties.setProperty(HoodieTableConfig.POPULATE_META_FIELDS.key(),
"false");
+ tableProperties.setProperty(HoodieTableConfig.RECORDKEY_FIELDS.key(), "");
+ initMetaClient(tableProperties);
+ }
+
+ private HoodieWriteConfig writeConfig(boolean withIndexes, boolean
partitionedRecordIndex) {
+ HoodieMetadataConfig.Builder metadataConfig =
HoodieMetadataConfig.newBuilder()
+ .enable(true)
+ .withMetadataIndexColumnStats(false);
+ if (withIndexes && partitionedRecordIndex) {
+ // the secondary index builds on the global record index only
+ metadataConfig.withEnableRecordLevelIndex(true);
+ } else if (withIndexes) {
+ metadataConfig.withEnableGlobalRecordLevelIndex(true)
+ .withSecondaryIndexEnabled(true)
+ .withSecondaryIndexForColumn(NAME_FIELD);
+ }
+ return HoodieWriteConfig.newBuilder()
+ .withPath(metaClient.getBasePath())
+ .withSchema(SCHEMA.toString())
+ .withPopulateMetaFields(false)
+ // file ids of external files are file names, not UUIDs, so the record
index stores them as raw strings
+ .withWritesFileIdEncoding(1)
+
.withIndexConfig(HoodieIndexConfig.newBuilder().withIndexType(INMEMORY).build())
+ .withEmbeddedTimelineServerEnabled(false)
+ .withMetadataConfig(metadataConfig.build())
+ .build();
+ }
+
+ /**
+ * Registers the given files in one replace commit, the way an external
writer does: the files are written to
+ * their location below the table, and the commit records them with the
external file marker.
+ */
+ private void commitReplace(List<Pair<ExternalFile, List<Pair<Integer,
String>>>> newFiles,
+ Map<String, List<String>>
partitionToReplacedFileIds) throws IOException {
+ String instantTime =
writeClient.startCommit(HoodieTimeline.REPLACE_COMMIT_ACTION, metaClient);
+ List<WriteStatus> writeStatuses = new ArrayList<>();
+ for (Pair<ExternalFile, List<Pair<Integer, String>>> fileAndRows :
newFiles) {
+ ExternalFile file = fileAndRows.getLeft();
+ long fileSize = writeParquetFile(new
Path(metaClient.getBasePath().toString(), file.relativePath()),
fileAndRows.getRight());
+ WriteStatus writeStatus = new WriteStatus();
+ writeStatus.setFileId(file.fileId());
+ writeStatus.setPartitionPath(file.partitionPath);
+ HoodieDeltaWriteStat writeStat = new HoodieDeltaWriteStat();
+ writeStat.setFileId(file.fileId());
+ writeStat.setPath(file.markedPath(instantTime));
+ writeStat.setPartitionPath(file.partitionPath);
+ writeStat.setNumWrites(fileAndRows.getRight().size());
+ writeStat.setNumInserts(fileAndRows.getRight().size());
+ writeStat.setTotalWriteBytes(fileSize);
+ writeStat.setFileSizeInBytes(fileSize);
+ writeStatus.setStat(writeStat);
+ writeStatuses.add(writeStatus);
+ }
+ metaClient.getActiveTimeline().transitionReplaceRequestedToInflight(
+ INSTANT_GENERATOR.createNewInstant(HoodieInstant.State.REQUESTED,
HoodieTimeline.REPLACE_COMMIT_ACTION, instantTime), Option.empty());
+ writeClient.commit(instantTime, jsc.parallelize(writeStatuses, 1),
Option.empty(),
+ HoodieTimeline.REPLACE_COMMIT_ACTION, partitionToReplacedFileIds);
+ metaClient = HoodieTableMetaClient.reload(metaClient);
+ }
+
+ private long writeParquetFile(Path path, List<Pair<Integer, String>> rows)
throws IOException {
+ Configuration conf =
metaClient.getStorageConf().unwrapAs(Configuration.class);
+ try (ParquetWriter<GenericRecord> writer =
AvroParquetWriter.<GenericRecord>builder(path)
+ .withSchema(SCHEMA.toAvroSchema()).withConf(conf).build()) {
+ for (Pair<Integer, String> row : rows) {
+ GenericRecord record = new GenericData.Record(SCHEMA.toAvroSchema());
+ record.put(ID_FIELD, row.getLeft());
+ record.put(NAME_FIELD, row.getRight());
+ writer.write(record);
+ }
+ }
+ return path.getFileSystem(conf).getFileStatus(path).getLen();
+ }
+
+ private HoodieBackedTableMetadata tableMetadata(HoodieWriteConfig
writeConfig) {
+ return new HoodieBackedTableMetadata(context, metaClient.getStorage(),
writeConfig.getMetadataConfig(), writeConfig.getBasePath(), true);
+ }
+
+ private String secondaryIndexPartition() {
+ return
metaClient.getIndexMetadata().get().getIndexDefinitions().keySet().stream()
+ .filter(indexName ->
indexName.startsWith(HoodieTableMetadataUtil.PARTITION_NAME_SECONDARY_INDEX_PREFIX))
+ .findFirst().get();
+ }
+
+ /** Asserts that every row of the file is in the record index and points at
the file. */
+ private void assertRecordIndex(HoodieBackedTableMetadata tableMetadata,
Option<String> recordIndexPartition, ExternalFile file, int rowCount) {
+ Map<String, HoodieRecordGlobalLocation> locations =
readRecordIndex(tableMetadata, recordIndexPartition, file.keys(rowCount));
+ assertEquals(rowCount, locations.size());
+ locations.values().forEach(location -> {
+ assertEquals(file.partitionPath, location.getPartitionPath());
+ assertEquals(file.fileId(), location.getFileId());
+ });
+ }
+
+ private Map<String, HoodieRecordGlobalLocation>
readRecordIndex(HoodieBackedTableMetadata tableMetadata, Option<String>
recordIndexPartition,
+ List<String>
recordKeys) {
+ return
tableMetadata.readRecordIndexLocationsWithKeys(HoodieJavaRDD.of(jsc.parallelize(recordKeys,
1)), recordIndexPartition).collectAsList().stream()
Review Comment:
Fixed in 9085866. `RecordLevelIndexSupport.filterCandidateFiles` now takes
the pruned file slices and matches `fileSlice.getFileId` against the lookup
results, and `SecondaryIndexSupport` and both RLI supports go through it;
`getPrunedStoragePaths` is gone. The functional test reads the table through
Spark with `where(name = ...)` and asserts the rows and a `numFiles` of one out
of two registered files. That read runs on the unpartitioned shapes, because
the test harness declares a `partition_path` partition field that the schema of
the external files does not carry.
--
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]