From ba4d36fcb15368f1f1b070237b2fcc3a7aa1a2bb Mon Sep 17 00:00:00 2001 From: xiarixiaoyao Date: Wed, 2 Nov 2022 16:19:56 +0800 Subject: [PATCH 1/6] Support DataSkipping and schema evolution for hudi connector --- .../presto/hudi/HudiFileSkippingManager.java | 344 ++++++++++++++++++ .../facebook/presto/hudi/HudiPageSource.java | 84 ++++- .../presto/hudi/HudiPageSourceProvider.java | 15 +- .../presto/hudi/HudiPartitionManager.java | 219 +++++++++++ .../facebook/presto/hudi/HudiPredicates.java | 68 ++++ .../presto/hudi/HudiSchemaEvolutionUtils.java | 192 ++++++++++ .../presto/hudi/HudiSessionProperties.java | 12 + .../com/facebook/presto/hudi/HudiSplit.java | 11 +- .../presto/hudi/HudiSplitManager.java | 77 +++- .../presto/hudi/SchemaEvolutionContext.java | 57 +++ 10 files changed, 1063 insertions(+), 16 deletions(-) create mode 100644 presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java create mode 100644 presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java create mode 100644 presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java create mode 100644 presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java new file mode 100644 index 000000000000..e3c88859ded1 --- /dev/null +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java @@ -0,0 +1,344 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.facebook.airlift.log.Logger; +import com.facebook.presto.common.predicate.Domain; +import com.facebook.presto.common.predicate.Range; +import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.common.predicate.ValueSet; +import com.facebook.presto.common.type.Type; +import com.facebook.presto.parquet.predicate.TupleDomainParquetPredicate; +import com.facebook.presto.spi.ColumnHandle; +import com.facebook.presto.spi.ConnectorSession; +import io.airlift.slice.Slice; +import io.airlift.slice.Slices; +import org.apache.hadoop.fs.FileStatus; +import org.apache.hadoop.fs.Path; +import org.apache.hudi.avro.model.HoodieMetadataColumnStats; +import org.apache.hudi.common.config.HoodieMetadataConfig; +import org.apache.hudi.common.engine.HoodieEngineContext; +import org.apache.hudi.common.fs.FSUtils; +import org.apache.hudi.common.model.BaseFile; +import org.apache.hudi.common.model.FileSlice; +import org.apache.hudi.common.model.HoodieTableQueryType; +import org.apache.hudi.common.model.HoodieTableType; +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.table.view.HoodieTableFileSystemView; +import org.apache.hudi.common.util.Option; +import org.apache.hudi.common.util.hash.ColumnIndexID; +import org.apache.hudi.metadata.HoodieTableMetadata; +import org.apache.hudi.metadata.HoodieTableMetadataUtil; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static com.facebook.presto.common.type.BigintType.BIGINT; +import static com.facebook.presto.common.type.BooleanType.BOOLEAN; +import static com.facebook.presto.common.type.DateType.DATE; +import static com.facebook.presto.common.type.DoubleType.DOUBLE; +import static com.facebook.presto.common.type.IntegerType.INTEGER; +import static com.facebook.presto.common.type.RealType.REAL; +import static com.facebook.presto.common.type.SmallintType.SMALLINT; +import static com.facebook.presto.common.type.TinyintType.TINYINT; +import static com.facebook.presto.common.type.Varchars.isVarcharType; +import static com.facebook.presto.parquet.predicate.PredicateUtils.isStatisticsOverflow; +import static java.lang.Float.floatToRawIntBits; + +/** + * MetaDataTable based filesManager. + * list Hudi Table contents based on the + * + * + * + * Support dataSkipping. + */ +public class HudiFileSkippingManager +{ + private static final Logger log = Logger.get(HudiFileSkippingManager.class); + + private final HoodieTableQueryType queryType; + private final Option specifiedQueryInstant; + private final HoodieTableMetaClient metaClient; + private final HoodieEngineContext engineContext; + private final String spillableDir; + private final HoodieTableType tableType; + protected final HoodieMetadataConfig metadataConfig; + private final HoodieTableMetadata metadataTable; + + protected transient volatile Map> allInputFileSlices; + + private transient volatile HoodieTableFileSystemView fileSystemView; + + public HudiFileSkippingManager( + List partitions, + String spillableDir, + HoodieEngineContext engineContext, + HoodieTableMetaClient metaClient, + HoodieTableQueryType queryType, + Option specifiedQueryInstant) + { + this.queryType = queryType; + this.specifiedQueryInstant = specifiedQueryInstant; + this.metaClient = metaClient; + this.engineContext = engineContext; + this.spillableDir = spillableDir; + this.metadataConfig = HoodieMetadataConfig.newBuilder().withMetadataIndexBloomFilter(true).enable(true).build(); + this.tableType = metaClient.getTableConfig().getTableType(); + this.metadataTable = HoodieTableMetadata + .create(engineContext, this.metadataConfig, metaClient.getBasePathV2().toString(), spillableDir, true); + prepareAllInputFileSlices(partitions); + } + + private void prepareAllInputFileSlices(List partitions) + { + long startTime = System.currentTimeMillis(); + Path tablePath = metaClient.getBasePathV2(); + List fullPartitionPath = partitions.stream().map(p -> + p.isEmpty() ? tablePath.toString() : new Path(tablePath, p).toString()).collect(Collectors.toList()); + Map fetchedPartitionToFiles = FSUtils.getFilesInPartitions( + engineContext, + metadataConfig, + metaClient.getBasePathV2().toString(), + fullPartitionPath.toArray(new String[0]), + spillableDir); + FileStatus[] allFiles = fetchedPartitionToFiles.values().stream().flatMap(Arrays::stream).toArray(FileStatus[]::new); + + HoodieTimeline activeTimeline = metaClient.reloadActiveTimeline(); + Option latestInstant = activeTimeline.lastInstant(); + // build system view. + fileSystemView = new HoodieTableFileSystemView(metaClient, activeTimeline, allFiles); + Option queryInstant = specifiedQueryInstant.or(() -> latestInstant.map(HoodieInstant::getTimestamp)); + if (tableType.equals(HoodieTableType.MERGE_ON_READ) && queryType.equals(HoodieTableQueryType.SNAPSHOT)) { + allInputFileSlices = partitions.stream().collect(Collectors.toMap(Function.identity(), + partitionPath -> + queryInstant.map(instant -> + fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get()) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()))); + } + else { + allInputFileSlices = partitions.stream().collect(Collectors.toMap(Function.identity(), partition -> + queryInstant.map(instant -> + fileSystemView.getLatestFileSlicesBeforeOrOn(partition, instant, true)) + .orElse(fileSystemView.getLatestFileSlices(partition)) + .collect(Collectors.toList()))); + } + + long duration = System.currentTimeMillis() - startTime; + + log.info(String.format("prepare query files for table %s, spent: %d ms", metaClient.getTableConfig().getTableName(), duration)); + } + + public static HoodieTableQueryType getQueryType(ConnectorSession session, String inputFormat) + { + // TODO support incremental query + switch (inputFormat) { + case "org.apache.hudi.hadoop.HoodieParquetInputFormat": + case "com.uber.hoodie.hadoop.HoodieInputFormat": + // cow table/ mor ro table + return HoodieTableQueryType.READ_OPTIMIZED; + case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": + case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": + // mor rt table + return HoodieTableQueryType.SNAPSHOT; + default: + throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", inputFormat)); + } + } + + public Map> listQueryFiles(TupleDomain tupleDomain) + { + // do file skipping by MetadataTable + Map> candidateFileSlices = allInputFileSlices; + try { + if (!tupleDomain.isAll()) { + candidateFileSlices = lookupCandidateFilesInMetadataTable(candidateFileSlices, tupleDomain); + } + } + catch (Exception e) { + // Should not throw exception, just log this Exception. + log.warn(String.format("failed to do data skipping for table: %s, fallback to all files scan", metaClient.getBasePathV2().toString()), e); + candidateFileSlices = allInputFileSlices; + } + int candidateFileSize = candidateFileSlices.entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, (n1, n2) -> n1 + n2); + int totalFiles = allInputFileSlices.entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, (n1, n2) -> n1 + n2); + double skippingPercent = totalFiles == 0 ? 0.0d : (totalFiles - candidateFileSize) / (totalFiles + 0.0d); + log.info(String.format("Total files: %s; candidate files after data skipping: %s; skipping percent %s", + totalFiles, candidateFileSize, skippingPercent)); + return candidateFileSlices; + } + + public Map> lookupCandidateFilesInMetadataTable(Map> inputFileSlices, TupleDomain tupleDomain) + { + // split regular column predicates + TupleDomain regularTupleDomain = HudiPredicates.from(tupleDomain).getRegularColumnPredicates(); + TupleDomain regularColumnPredicates = regularTupleDomain.transform(HudiColumnHandle::getName); + if (regularColumnPredicates.isAll()) { + return inputFileSlices; + } + List regularColumns = regularColumnPredicates.getDomains().get().entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList()); + // get filter columns + List encodedTargetColumnNames = regularColumns.stream().map(col -> new ColumnIndexID(col).asBase64EncodedString()).collect(Collectors.toList()); + Map> statsByFileName = metadataTable.getRecordsByKeyPrefixes( + encodedTargetColumnNames, + HoodieTableMetadataUtil.PARTITION_NAME_COLUMN_STATS, true) + .collectAsList() + .stream() + .filter(f -> f.getData().getColumnStatMetadata() + .isPresent()) + .map(f -> f.getData().getColumnStatMetadata().get()).collect(Collectors.groupingBy(HoodieMetadataColumnStats::getFileName)); + + // prune files. + return inputFileSlices.entrySet().parallelStream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> { + return entry.getValue().stream().filter(fileSlice -> { + String fileSliceName = fileSlice.getBaseFile().map(BaseFile::getFileName).orElse(""); + // no stats found + if (!statsByFileName.containsKey(fileSliceName)) { + return true; + } + List stats = statsByFileName.get(fileSliceName); + if (!evaluateStatisticPredicate(regularColumnPredicates, stats, regularColumns)) { + return false; + } + return true; + }).collect(Collectors.toList()); + })); + } + + private boolean evaluateStatisticPredicate(TupleDomain regularColumnPredicates, List stats, List regularColumns) + { + if (regularColumnPredicates.isNone()) { + return true; + } + for (String regularColumn : regularColumns) { + Domain columnPredicate = regularColumnPredicates.getDomains().get().get(regularColumn); + Optional currentColumnStats = stats.stream().filter(s -> s.getColumnName().equals(regularColumn)).findFirst(); + if (!currentColumnStats.isPresent()) { + // no stats for column + } + else { + Domain domain = getDomain(regularColumn, columnPredicate.getType(), currentColumnStats.get()); + if (columnPredicate.intersect(domain).isNone()) { + return false; + } + } + } + return true; + } + + private static Domain getDomain(String colName, Type type, HoodieMetadataColumnStats statistics) + { + if (statistics == null) { + return Domain.all(type); + } + boolean hasNullValue = statistics.getNullCount() != 0L; + boolean hasNonNullValue = statistics.getValueCount() - statistics.getNullCount() > 0; + if (!hasNonNullValue || statistics.getMaxValue() == null || statistics.getMinValue() == null) { + return Domain.create(ValueSet.all(type), hasNullValue); + } + return getDomain(colName, type, ((org.apache.hudi.org.apache.avro.generic.GenericRecord) statistics.getMinValue()).get(0), + ((org.apache.hudi.org.apache.avro.generic.GenericRecord) statistics.getMaxValue()).get(0), hasNullValue); + } + + /** + * Get a domain for the ranges defined by each pair of elements from {@code minimums} and {@code maximums}. + * Both arrays must have the same length. + */ + private static Domain getDomain(String colName, Type type, Object minimum, Object maximum, boolean hasNullValue) + { + List ranges = new ArrayList<>(); + try { + if (type.equals(BOOLEAN)) { + boolean hasTrueValue = (boolean) minimum || (boolean) maximum; + boolean hasFalseValue = !(boolean) minimum || !(boolean) maximum; + if (hasTrueValue && hasFalseValue) { + return Domain.all(type); + } + if (hasTrueValue) { + return Domain.create(ValueSet.of(type, true), hasNullValue); + } + if (hasFalseValue) { + return Domain.create(ValueSet.of(type, false), hasNullValue); + } + // No other case, since all null case is handled earlier. + } + + if ((type.equals(BIGINT) || type.equals(TINYINT) || type.equals(SMALLINT) || type.equals(INTEGER))) { + long minValue = TupleDomainParquetPredicate.asLong(minimum); + long maxValue = TupleDomainParquetPredicate.asLong(maximum); + if (isStatisticsOverflow(type, minValue, maxValue)) { + return Domain.create(ValueSet.all(type), hasNullValue); + } + ranges.add(Range.range(type, minValue, true, maxValue, true)); + return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + } + + if (type.equals(REAL)) { + Float minValue = (Float) minimum; + Float maxValue = (Float) maximum; + if (minValue.isNaN() || maxValue.isNaN()) { + return Domain.create(ValueSet.all(type), hasNullValue); + } + ranges.add(Range.range(type, (long) floatToRawIntBits(minValue), true, (long) floatToRawIntBits(maxValue), true)); + return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + } + + if (type.equals(DOUBLE)) { + Double minValue = (Double) minimum; + Double maxValue = (Double) maximum; + if (minValue.isNaN() || maxValue.isNaN()) { + return Domain.create(ValueSet.all(type), hasNullValue); + } + ranges.add(Range.range(type, minValue, true, maxValue, true)); + return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + } + + if (isVarcharType(type)) { + Slice min = Slices.utf8Slice((String) minimum); + Slice max = Slices.utf8Slice((String) maximum); + ranges.add(Range.range(type, min, true, max, true)); + return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + } + + if (type.equals(DATE)) { + long min = TupleDomainParquetPredicate.asLong(minimum); + long max = TupleDomainParquetPredicate.asLong(maximum); + if (isStatisticsOverflow(type, min, max)) { + return Domain.create(ValueSet.all(type), hasNullValue); + } + ranges.add(Range.range(type, min, true, max, true)); + return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + } + return Domain.create(ValueSet.all(type), hasNullValue); + } + catch (Exception e) { + log.warn(String.format("failed to create Domain for column: %s which type is: %s", colName, type.toString())); + return Domain.create(ValueSet.all(type), hasNullValue); + } + } +} diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java index 373ecfdf703b..b89bc929d5a5 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java @@ -17,6 +17,8 @@ import com.facebook.presto.common.Page; import com.facebook.presto.common.Utils; import com.facebook.presto.common.block.Block; +import com.facebook.presto.common.block.LazyBlock; +import com.facebook.presto.common.block.LazyBlockLoader; import com.facebook.presto.common.block.RunLengthEncodedBlock; import com.facebook.presto.common.type.DecimalType; import com.facebook.presto.common.type.Decimals; @@ -25,8 +27,11 @@ import com.facebook.presto.common.type.TypeManager; import com.facebook.presto.common.type.VarbinaryType; import com.facebook.presto.common.type.VarcharType; +import com.facebook.presto.hive.HiveCoercer; +import com.facebook.presto.hive.HiveType; import com.facebook.presto.spi.ConnectorPageSource; import com.facebook.presto.spi.PrestoException; +import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.UncheckedIOException; @@ -34,8 +39,11 @@ import java.math.BigInteger; import java.time.LocalDate; import java.time.format.DateTimeFormatter; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.Function; import static com.facebook.presto.common.type.BigintType.BIGINT; import static com.facebook.presto.common.type.BooleanType.BOOLEAN; @@ -66,20 +74,25 @@ public class HudiPageSource private final Block[] prefilledBlocks; private final int[] delegateIndexes; private final ConnectorPageSource delegate; + private final List columns; + private final List>> coercers; public HudiPageSource( List columns, Map partitionKeys, ConnectorPageSource delegate, TimeZoneKey timeZoneKey, - TypeManager typeManager) + TypeManager typeManager, + Map columnCoercions) { int size = requireNonNull(columns, "columns is null").size(); requireNonNull(partitionKeys, "partitionKeys is null"); + this.columns = requireNonNull(columns, "columns is null"); this.delegate = requireNonNull(delegate, "delegate is null"); prefilledBlocks = new Block[size]; delegateIndexes = new int[size]; + ImmutableList.Builder>> coercersBuilder = ImmutableList.builder(); int outputIndex = 0; int delegateIndex = 0; @@ -98,8 +111,19 @@ public HudiPageSource( delegateIndex++; } outputIndex++; + + // create type convert + if (!columnCoercions.isEmpty() && + columnCoercions.containsKey(column.getName()) && + !column.getHiveType().equals(columnCoercions.get(column.getName()))) { + coercersBuilder.add(Optional.of(HiveCoercer.createCoercer(typeManager, columnCoercions.get(column.getName()), column.getHiveType()))); + } + else { + coercersBuilder.add(Optional.empty()); + } } this.hasPrefilledBlocks = hasPrefilledBlocks; + this.coercers = coercersBuilder.build(); } @Override @@ -135,7 +159,7 @@ public Page getNextPage() return null; } if (!hasPrefilledBlocks) { - return dataPage; + return reAlignDataPage(dataPage, columns, coercers); } int batchSize = dataPage.getPositionCount(); Block[] blocks = new Block[prefilledBlocks.length]; @@ -144,7 +168,9 @@ public Page getNextPage() blocks[i] = new RunLengthEncodedBlock(prefilledBlocks[i], batchSize); } else { - blocks[i] = dataPage.getBlock(delegateIndexes[i]); + Block block = dataPage.getBlock(delegateIndexes[i]); + Optional> coercer = coercers.isEmpty() ? Optional.empty() : coercers.get(i); + blocks[i] = reAlignDataBlock(batchSize, block, coercer); } } return new Page(batchSize, blocks); @@ -251,4 +277,56 @@ static Object deserializePartitionValue(Type type, String valueString, String na // Hudi tables don't partition by non-primitive-type columns. throw new PrestoException(NOT_SUPPORTED, "Invalid partition type " + type); } + + private static Page reAlignDataPage( + Page dataPage, + List columns, + List>> coercers) + { + int batchSize = dataPage.getPositionCount(); + List blocks = new ArrayList<>(); + for (int fieldId = 0; fieldId < columns.size(); fieldId++) { + Block block = dataPage.getBlock(fieldId); + Optional> coercer = coercers.isEmpty() ? Optional.empty() : coercers.get(fieldId); + if (coercer.isPresent()) { + block = new LazyBlock(batchSize, new CoercionLazyBlockLoader(block, coercer.get())); + } + blocks.add(block); + } + return new Page(batchSize, blocks.toArray(new Block[0])); + } + + private static Block reAlignDataBlock(int batchSize, Block dataBlock, Optional> coercer) + { + if (coercer.isPresent()) { + return new LazyBlock(batchSize, new CoercionLazyBlockLoader(dataBlock, coercer.get())); + } + return dataBlock; + } + + private static final class CoercionLazyBlockLoader + implements LazyBlockLoader + { + private final Function coercer; + private Block block; + + public CoercionLazyBlockLoader(Block block, Function coercer) + { + this.block = requireNonNull(block, "block is null"); + this.coercer = requireNonNull(coercer, "coercer is null"); + } + + @Override + public void load(LazyBlock lazyBlock) + { + if (block == null) { + return; + } + + lazyBlock.setBlock(coercer.apply(block.getLoadedBlock())); + + // clear reference to loader to free resources, since load was successful + block = null; + } + } } diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java index b512575978e8..99af0252d3f6 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java @@ -14,12 +14,12 @@ package com.facebook.presto.hudi; -import com.facebook.presto.common.predicate.TupleDomain; import com.facebook.presto.common.type.Type; import com.facebook.presto.common.type.TypeManager; import com.facebook.presto.hive.FileFormatDataSourceStats; import com.facebook.presto.hive.HdfsContext; import com.facebook.presto.hive.HdfsEnvironment; +import com.facebook.presto.hive.HiveType; import com.facebook.presto.hive.metastore.Column; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ConnectorPageSource; @@ -32,13 +32,16 @@ import com.facebook.presto.spi.SplitContext; import com.facebook.presto.spi.connector.ConnectorPageSourceProvider; import com.facebook.presto.spi.connector.ConnectorTransactionHandle; +import com.google.common.collect.ImmutableMap; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; +import org.apache.hudi.common.util.collection.Pair; import javax.inject.Inject; import java.time.ZoneId; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -84,6 +87,7 @@ public ConnectorPageSource createPageSource( List dataColumns = hudiColumnHandles.stream().filter(HudiColumnHandle::isRegularColumn).collect(toList()); final ConnectorPageSource dataColumnPageSource; + Map columnCoercions = ImmutableMap.of(); if (tableType == HudiTableType.COW) { HudiFile baseFile = hudiSplit.getBaseFile().orElseThrow(() -> new PrestoException(HUDI_CANNOT_OPEN_SPLIT, "Split without base file is invalid")); @@ -95,6 +99,10 @@ public ConnectorPageSource createPageSource( baseFile.getPath(), false), path); + // embed schema evolution. + Pair, Map> pair = HudiSchemaEvolutionUtils.doEvolution(hudiSplit, dataColumns, layout.getTable().getPath(), configuration); + dataColumns = pair.getLeft(); + columnCoercions = pair.getRight(); dataColumnPageSource = createParquetPageSource( typeManager, hdfsEnvironment, @@ -104,7 +112,7 @@ public ConnectorPageSource createPageSource( baseFile.getStart(), baseFile.getLength(), dataColumns, - TupleDomain.all(), // TODO: predicates + HudiPredicates.from(layout.getTupleDomain()).getRegularColumnPredicates(), fileFormatDataSourceStats); } else if (tableType == HudiTableType.MOR) { @@ -139,7 +147,8 @@ else if (tableType == HudiTableType.MOR) { hudiSplit.getPartition().getKeyValues(), dataColumnPageSource, session.getSqlFunctionProperties().getTimeZoneKey(), - typeManager); + typeManager, + columnCoercions); } private static List toMetastoreColumns(List hudiColumnHandles) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java index f66b47d0d2f8..5ee29d49d5c9 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java @@ -14,8 +14,12 @@ package com.facebook.presto.hudi; +import com.facebook.airlift.log.Logger; import com.facebook.presto.common.predicate.Domain; import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.common.predicate.ValueSet; +import com.facebook.presto.common.type.StandardTypes; +import com.facebook.presto.common.type.Type; import com.facebook.presto.common.type.TypeManager; import com.facebook.presto.hive.metastore.Column; import com.facebook.presto.hive.metastore.ExtendedHiveMetastore; @@ -23,25 +27,52 @@ import com.facebook.presto.hive.metastore.Table; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ConnectorSession; +import com.facebook.presto.spi.PrestoException; import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import io.airlift.slice.Slice; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hudi.common.config.HoodieMetadataConfig; +import org.apache.hudi.common.engine.HoodieLocalEngineContext; +import org.apache.hudi.common.fs.FSUtils; +import org.apache.hudi.common.table.HoodieTableMetaClient; import javax.inject.Inject; +import java.sql.Timestamp; +import java.time.LocalDate; +import java.util.Arrays; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import static com.facebook.presto.hudi.HudiErrorCode.HUDI_INVALID_PARTITION_VALUE; import static com.facebook.presto.hudi.HudiMetadata.fromPartitionColumns; import static com.facebook.presto.hudi.HudiMetadata.toMetastoreContext; +import static com.facebook.presto.hudi.HudiSessionProperties.isHudiMetadataTableEnabled; +import static io.airlift.slice.Slices.utf8Slice; +import static java.lang.Double.doubleToRawLongBits; +import static java.lang.Double.parseDouble; +import static java.lang.Float.floatToRawIntBits; +import static java.lang.Float.parseFloat; +import static java.lang.Long.parseLong; +import static java.lang.String.format; import static java.util.Objects.requireNonNull; public class HudiPartitionManager { + private static final Logger log = Logger.get(HudiPartitionManager.class); + private final TypeManager typeManager; + private static final Pattern HIVE_PARTITION_NAME_PATTERN = Pattern.compile("([^/]+)=([^/]+)"); + @Inject public HudiPartitionManager(TypeManager typeManager) { @@ -51,6 +82,7 @@ public HudiPartitionManager(TypeManager typeManager) public List getEffectivePartitions( ConnectorSession connectorSession, ExtendedHiveMetastore metastore, + HoodieTableMetaClient metaClient, String schemaName, String tableName, TupleDomain tupleDomain) @@ -59,10 +91,197 @@ public List getEffectivePartitions( Optional table = metastore.getTable(metastoreContext, schemaName, tableName); Verify.verify(table.isPresent()); List partitionColumns = table.get().getPartitionColumns(); + + if (partitionColumns.isEmpty()) { + return ImmutableList.of(""); + } + + boolean metaTableEnabled = isHudiMetadataTableEnabled(connectorSession); + + return metaTableEnabled ? prunePartitionByMetaDataTable(connectorSession, metaClient, schemaName, tableName, partitionColumns, tupleDomain) : + prunePartitionByMetaStore(metastore, metastoreContext, schemaName, tableName, partitionColumns, tupleDomain); + } + + private List prunePartitionByMetaDataTable( + ConnectorSession connectorSession, + HoodieTableMetaClient metaClient, + String schemaName, + String tableName, + List partitionColumns, + TupleDomain tupleDomain) + { + // non-partition table if (partitionColumns.isEmpty()) { return ImmutableList.of(""); } + Configuration conf = metaClient.getHadoopConf(); + HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(conf); + HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(isHudiMetadataTableEnabled(connectorSession)).build(); + // Load all the partition path from the basePath + List matchedPartitionPaths = FSUtils.getAllPartitionPaths(engineContext, metadataConfig, metaClient.getBasePathV2().toString()); + + // Extract partition columns predicate + TupleDomain partitionPredicate = tupleDomain.transform(hudiColumnHandle -> { + if (((HudiColumnHandle) hudiColumnHandle).getColumnType() != HudiColumnHandle.ColumnType.PARTITION_KEY) { + return null; + } + return ((HudiColumnHandle) hudiColumnHandle).getName(); + }); + + if (partitionPredicate.isAll()) { + return matchedPartitionPaths; + } + + if (partitionPredicate.isNone()) { + return ImmutableList.of(""); + } + + List partitionColumnHandles = fromPartitionColumns(partitionColumns); + + List result = prunePartitions(partitionPredicate, partitionColumnHandles, getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), matchedPartitionPaths)); + log.info(format("Total partition size is %s, after partition prune size is %s.", matchedPartitionPaths.size(), result.size())); + return result; + } + + /** + * Returns the partition path key and values as a list of map. + * For example: + * partition keys: [p1, p2, p3], + * partition paths: + * p1=val1/p2=val2/p3=val3 (hive style partition) + * p1=val4/p2=val5/p3=val6 (hive style partition) + * return values {p1=val1/p2=val2/p3=val3 -> {p1 -> val1, p2 -> value2, p3 -> value3}}, {p1=val4/p2=val5/p3=val6 -> {p1 -> val4, p2 -> value5, p3 -> value6}} + * + * @param partitionKey The partition key list + * @param partitionPaths partition path list + */ + public static Map> getPartitions(List partitionKey, List partitionPaths) + { + Map> result = new HashMap<>(); + if (partitionPaths.isEmpty() || partitionKey.isEmpty()) { + return result; + } + // try to infer hive style + boolean hiveStylePartition = HIVE_PARTITION_NAME_PATTERN.matcher(partitionPaths.get(0).split(Path.SEPARATOR)[0]).matches(); + for (String partitionPath : partitionPaths) { + String[] pathParts = partitionPath.split(Path.SEPARATOR); + Map partitionMapping = new LinkedHashMap<>(); + if (hiveStylePartition) { + Arrays.stream(pathParts).forEach(p -> { + String[] keyValue = p.split("="); + if (keyValue.length == 2) { + partitionMapping.put(keyValue[0], keyValue[1]); + } + }); + } + else { + for (int i = 0; i < partitionKey.size(); i++) { + partitionMapping.put(partitionKey.get(i), pathParts[i]); + } + } + result.put(partitionPath, partitionMapping); + } + return result; + } + + private List prunePartitions( + TupleDomain partitionPredicate, + List partitionColumnHandles, + Map> candidatePartitionPaths) + { + return candidatePartitionPaths.entrySet().stream().filter(f -> { + Map partitionMapping = f.getValue(); + return partitionMapping.entrySet().stream().allMatch(p -> evaluatePartitionPredice(partitionPredicate, partitionColumnHandles, p.getValue(), p.getKey())); + }).map(entry -> entry.getKey()).collect(Collectors.toList()); + } + + private boolean evaluatePartitionPredice(TupleDomain partitionPredicate, List partitionColumnHandles, String partitionPathValue, String partitionName) + { + Optional columnHandleOpt = partitionColumnHandles.stream().filter(f -> f.getName().equals(partitionName)).findFirst(); + if (columnHandleOpt.isPresent()) { + Domain domain = getDomain(columnHandleOpt.get(), partitionPathValue); + Domain columnPredicate = partitionPredicate.getDomains().get().get(partitionName); + // no predicate on current partitionName + if (columnPredicate == null) { + return true; + } + + // For null partition, hive will produce a default value for current partition. + if (partitionPathValue.equals("default")) { + return false; + } + + if (columnPredicate.intersect(domain).isNone()) { + return false; + } + return true; + } + else { + // Should not happen + throw new IllegalArgumentException(format("Mismatched partition information found," + + " partition: %s from Hudi metadataTable is not included by the partitions from HMS: %s", + partitionName, partitionColumnHandles.stream().map(f -> f.getName()).collect(Collectors.joining(",")))); + } + } + + private Domain getDomain(HudiColumnHandle columnHandle, String partitionValue) + { + Type type = columnHandle.getHiveType().getType(typeManager); + if (partitionValue == null) { + return Domain.onlyNull(type); + } + try { + switch (columnHandle.getHiveType().getTypeSignature().getBase()) { + case StandardTypes.TINYINT: + case StandardTypes.SMALLINT: + case StandardTypes.INTEGER: + case StandardTypes.BIGINT: + Long intValue = parseLong(partitionValue); + return Domain.create(ValueSet.of(type, intValue), false); + case StandardTypes.REAL: + Long realValue = (long) floatToRawIntBits(parseFloat(partitionValue)); + return Domain.create(ValueSet.of(type, realValue), false); + case StandardTypes.DOUBLE: + Long doubleValue = doubleToRawLongBits(parseDouble(partitionValue)); + return Domain.create(ValueSet.of(type, doubleValue), false); + case StandardTypes.VARCHAR: + case StandardTypes.VARBINARY: + Slice sliceValue = utf8Slice(partitionValue); + return Domain.create(ValueSet.of(type, sliceValue), false); + case StandardTypes.DATE: + Long dateValue = LocalDate.parse(partitionValue, java.time.format.DateTimeFormatter.ISO_LOCAL_DATE).toEpochDay(); + return Domain.create(ValueSet.of(type, dateValue), false); + case StandardTypes.TIMESTAMP: + Long timestampValue = Timestamp.valueOf(partitionValue).getTime(); + return Domain.create(ValueSet.of(type, timestampValue), false); + case StandardTypes.BOOLEAN: + Boolean booleanValue = Boolean.valueOf(partitionValue); + return Domain.create(ValueSet.of(type, booleanValue), false); + default: + throw new PrestoException(HUDI_INVALID_PARTITION_VALUE, format( + "partition data type '%s' is unsupported for partition key: %s", + columnHandle.getHiveType(), + columnHandle.getName())); + } + } + catch (IllegalArgumentException e) { + throw new PrestoException(HUDI_INVALID_PARTITION_VALUE, format( + "Invalid partition value '%s' for %s partition key: %s", + partitionValue, + type.getDisplayName(), + columnHandle.getName())); + } + } + + private List prunePartitionByMetaStore( + ExtendedHiveMetastore metastore, + MetastoreContext metastoreContext, + String schemaName, + String tableName, + List partitionColumns, + TupleDomain tupleDomain) + { Map partitionPredicate = new HashMap<>(); Map domains = tupleDomain.getDomains().orElse(ImmutableMap.of()); diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java new file mode 100644 index 000000000000..bdc7796e3771 --- /dev/null +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java @@ -0,0 +1,68 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.facebook.presto.common.predicate.Domain; +import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.spi.ColumnHandle; + +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +public class HudiPredicates +{ + private final TupleDomain partitionColumnPredicates; + private final TupleDomain regularColumnPredicates; + + public static HudiPredicates from(TupleDomain predicate) + { + Map partitionColumnPredicates = new HashMap<>(); + Map regularColumnPredicates = new HashMap<>(); + + Optional> domains = predicate.getDomains(); + domains.ifPresent(columnHandleDomainMap -> columnHandleDomainMap.forEach((key, value) -> { + HudiColumnHandle columnHandle = (HudiColumnHandle) key; + if (columnHandle.getColumnType() == HudiColumnHandle.ColumnType.PARTITION_KEY) { + partitionColumnPredicates.put(columnHandle, value); + } + else { + regularColumnPredicates.put(columnHandle, value); + } + })); + + return new HudiPredicates( + TupleDomain.withColumnDomains(partitionColumnPredicates), + TupleDomain.withColumnDomains(regularColumnPredicates)); + } + + private HudiPredicates( + TupleDomain partitionColumnPredicates, + TupleDomain regularColumnPredicates) + { + this.partitionColumnPredicates = partitionColumnPredicates; + this.regularColumnPredicates = regularColumnPredicates; + } + + public TupleDomain getPartitionColumnPredicates() + { + return partitionColumnPredicates; + } + + public TupleDomain getRegularColumnPredicates() + { + return regularColumnPredicates; + } +} diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java new file mode 100644 index 000000000000..453ba138441c --- /dev/null +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java @@ -0,0 +1,192 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.facebook.airlift.log.Logger; +import com.facebook.presto.hive.HiveType; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.Path; +import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; +import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo; +import org.apache.hadoop.hive.serde2.typeinfo.MapTypeInfo; +import org.apache.hadoop.hive.serde2.typeinfo.StructTypeInfo; +import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo; +import org.apache.hudi.common.fs.FSUtils; +import org.apache.hudi.common.model.HoodieTableType; +import org.apache.hudi.common.table.HoodieTableMetaClient; +import org.apache.hudi.common.table.TableSchemaResolver; +import org.apache.hudi.common.table.timeline.HoodieInstant; +import org.apache.hudi.common.table.timeline.HoodieTimeline; +import org.apache.hudi.common.util.InternalSchemaCache; +import org.apache.hudi.common.util.collection.Pair; +import org.apache.hudi.internal.schema.InternalSchema; +import org.apache.hudi.internal.schema.Type; +import org.apache.hudi.internal.schema.Types; +import org.apache.hudi.internal.schema.action.InternalSchemaMerger; +import org.apache.hudi.internal.schema.utils.InternalSchemaUtils; +import org.apache.hudi.internal.schema.utils.SerDeHelper; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.stream.Collectors; + +import static com.facebook.presto.hive.HiveType.toHiveType; + +public final class HudiSchemaEvolutionUtils +{ + private static final Logger log = Logger.get(HudiSchemaEvolutionUtils.class); + + private HudiSchemaEvolutionUtils() + { + } + + public static SchemaEvolutionContext createSchemaEvolutionContext(HoodieTableMetaClient metaClient) + { + // no need to do schema evolution for mor table, since hudi kernel will do it. + if (metaClient.getTableType() == HoodieTableType.MERGE_ON_READ || metaClient == null) { + return new SchemaEvolutionContext("", ""); + } + + try { + TableSchemaResolver schemaUtil = new TableSchemaResolver(metaClient); + String internalSchema = schemaUtil.getTableInternalSchemaFromCommitMetadata().map(SerDeHelper::toJson).orElse(""); + HoodieTimeline hoodieTimeline = metaClient.getCommitsAndCompactionTimeline().filterCompletedInstants(); + String validCommits = hoodieTimeline.getInstants().map(HoodieInstant::getFileName).collect(Collectors.joining(",")); + return new SchemaEvolutionContext(internalSchema, validCommits); + } + catch (Exception e) { + log.warn(String.format("failed to get internal Schema from hudi tableļ¼š%s , fallback to original logical", metaClient.getBasePathV2()), e); + } + return new SchemaEvolutionContext("", ""); + } + + public static Pair, Map> doEvolution(HudiSplit hudiSplit, List oldColumnHandle, String tablePath, Configuration hadoopConf) + { + SchemaEvolutionContext schemaEvolutionContext = hudiSplit.getSchemaEvolutionContext(); + InternalSchema internalSchema = SerDeHelper.fromJson(schemaEvolutionContext.getRequiredSchema()).orElse(InternalSchema.getEmptyInternalSchema()); + if (internalSchema.isEmptySchema() || !hudiSplit.getBaseFile().isPresent()) { + return Pair.of(oldColumnHandle, ImmutableMap.of()); + } + // prune internalSchema: columns prune + InternalSchema prunedSchema = InternalSchemaUtils.pruneInternalSchema(internalSchema, oldColumnHandle.stream().map(HudiColumnHandle::getName).collect(Collectors.toList())); + + Path baseFilePath = new Path(hudiSplit.getBaseFile().get().getPath()); + String commitTime = FSUtils.getCommitTime(baseFilePath.getName()); + InternalSchema fileSchema = InternalSchemaCache.getInternalSchemaByVersionId(Long.parseUnsignedLong(commitTime), tablePath, hadoopConf, schemaEvolutionContext.getValidCommits()); + log.debug(String.format(Locale.ENGLISH, "File schema from hudi base file is %s", fileSchema)); + // + InternalSchema mergedSchema = new InternalSchemaMerger(fileSchema, prunedSchema, true, true).mergeSchema(); + ImmutableList.Builder builder = ImmutableList.builder(); + for (int i = 0; i < oldColumnHandle.size(); i++) { + HiveType hiveType = constructPrestoTypeFromType(mergedSchema.columns().get(i).type()); + HudiColumnHandle hudiColumnHandle = oldColumnHandle.get(i); + builder.add(new HudiColumnHandle(hudiColumnHandle.getId(), mergedSchema.columns().get(i).name(), hiveType, hudiColumnHandle.getComment(), hudiColumnHandle.getColumnType())); + } + return Pair.of(builder.build(), collectTypeChangedCols(prunedSchema, mergedSchema)); + } + + private static Map collectTypeChangedCols(InternalSchema schema, InternalSchema oldSchema) + { + return InternalSchemaUtils + .collectTypeChangedCols(schema, oldSchema) + .entrySet() + .stream() + .collect(Collectors.toMap(e -> oldSchema.columns().get(e.getKey()).name(), e -> constructPrestoTypeFromType(e.getValue().getRight()))); + } + + private static HiveType constructPrestoTypeFromType(Type type) + { + switch (type.typeId()) { + case BOOLEAN: + return HiveType.HIVE_BOOLEAN; + case INT: + return HiveType.HIVE_INT; + case LONG: + return HiveType.HIVE_LONG; + case FLOAT: + return HiveType.HIVE_FLOAT; + case DOUBLE: + return HiveType.HIVE_DOUBLE; + case DATE: + return HiveType.HIVE_DATE; + case TIMESTAMP: + return HiveType.HIVE_TIMESTAMP; + case STRING: + return HiveType.HIVE_STRING; + case UUID: + return HiveType.HIVE_STRING; + case FIXED: + return HiveType.HIVE_BINARY; + case BINARY: + return HiveType.HIVE_BINARY; + case DECIMAL: + Types.DecimalType decimal = (Types.DecimalType) type; + DecimalTypeInfo decimalTypeInfo = new DecimalTypeInfo(); + decimalTypeInfo.setPrecision(decimal.precision()); + decimalTypeInfo.setScale(decimal.scale()); + return toHiveType(decimalTypeInfo); + case RECORD: + return getPrestoTypeFromRecord(type); + case ARRAY: + return getPrestoTypeFromArray(type); + case MAP: + return getPrestoTypeFromMap(type); + case TIME: + throw new UnsupportedOperationException(String.format("Cannot convert %s type to Presto", type)); + default: + throw new UnsupportedOperationException(String.format("Cannot convert unknown type: %s to Presto", type)); + } + } + + private static HiveType getPrestoTypeFromRecord(Type type) + { + Types.RecordType record = (Types.RecordType) type; + List fields = record.fields(); + ArrayList fieldNames = new ArrayList<>(); + ArrayList fieldTypeInfos = new ArrayList<>(); + for (Types.Field f : fields) { + fieldNames.add(f.name()); + fieldTypeInfos.add(constructPrestoTypeFromType(f.type()).getTypeInfo()); + } + StructTypeInfo structTypeInfo = new StructTypeInfo(); + structTypeInfo.setAllStructFieldNames(fieldNames); + structTypeInfo.setAllStructFieldTypeInfos(fieldTypeInfos); + return toHiveType(structTypeInfo); + } + + private static HiveType getPrestoTypeFromArray(Type type) + { + Types.ArrayType array = (Types.ArrayType) type; + HiveType elementType = constructPrestoTypeFromType(array.elementType()); + ListTypeInfo listTypeInfo = new ListTypeInfo(); + listTypeInfo.setListElementTypeInfo(elementType.getTypeInfo()); + return toHiveType(listTypeInfo); + } + + private static HiveType getPrestoTypeFromMap(Type type) + { + Types.MapType map = (Types.MapType) type; + HiveType keyDataType = constructPrestoTypeFromType(map.keyType()); + HiveType valueDataType = constructPrestoTypeFromType(map.valueType()); + MapTypeInfo mapTypeInfo = new MapTypeInfo(); + mapTypeInfo.setMapKeyTypeInfo(keyDataType.getTypeInfo()); + mapTypeInfo.setMapValueTypeInfo(valueDataType.getTypeInfo()); + return toHiveType(mapTypeInfo); + } +} diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSessionProperties.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSessionProperties.java index a598f19c1c6e..21aae189defc 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSessionProperties.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSessionProperties.java @@ -31,6 +31,7 @@ import static com.facebook.presto.spi.StandardErrorCode.INVALID_SESSION_PROPERTY; import static com.facebook.presto.spi.session.PropertyMetadata.booleanProperty; import static com.facebook.presto.spi.session.PropertyMetadata.dataSizeProperty; +import static com.facebook.presto.spi.session.PropertyMetadata.stringProperty; import static java.lang.String.format; public class HudiSessionProperties @@ -47,6 +48,7 @@ public class HudiSessionProperties private static final String STANDARD_SPLIT_WEIGHT_SIZE = "standard_split_weight_size"; private static final String MINIMUM_ASSIGNED_SPLIT_WEIGHT = "minimum_assigned_split_weight"; public static final String READ_MASKED_VALUE_ENABLED = "read_null_masked_parquet_encrypted_value_enabled"; + private static final String HOODIE_FILESYSTEM_VIEW_SPILLABLE_DIR = "hoodie_filesystem_view_spillable_dir"; @Inject public HudiSessionProperties(HiveClientConfig hiveClientConfig, HudiConfig hudiConfig) @@ -107,6 +109,11 @@ public HudiSessionProperties(HiveClientConfig hiveClientConfig, HudiConfig hudiC READ_MASKED_VALUE_ENABLED, "Return null when access is denied for an encrypted parquet column", hiveClientConfig.getReadNullMaskedParquetEncryptedValue(), + false), + stringProperty( + HOODIE_FILESYSTEM_VIEW_SPILLABLE_DIR, + "Path on local storage to use, when file system view is held in a spillable map.", + "/tmp/", false)); } @@ -154,4 +161,9 @@ public static boolean getReadNullMaskedParquetEncryptedValue(ConnectorSession se { return session.getProperty(READ_MASKED_VALUE_ENABLED, Boolean.class); } + + public static String getHoodieFilesystemViewSpillableDir(ConnectorSession session) + { + return session.getProperty(HOODIE_FILESYSTEM_VIEW_SPILLABLE_DIR, String.class); + } } diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java index c5a6ef4398e7..d84396d26b09 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java @@ -41,6 +41,7 @@ public class HudiSplit private final List addresses; private final NodeSelectionStrategy nodeSelectionStrategy; private final SplitWeight splitWeight; + private final SchemaEvolutionContext schemaEvolutionContext; @JsonCreator public HudiSplit( @@ -51,7 +52,8 @@ public HudiSplit( @JsonProperty("logFiles") List logFiles, @JsonProperty("addresses") List addresses, @JsonProperty("nodeSelectionStrategy") NodeSelectionStrategy nodeSelectionStrategy, - @JsonProperty("splitWeight") SplitWeight splitWeight) + @JsonProperty("splitWeight") SplitWeight splitWeight, + @JsonProperty("schemaEvolutionContext") SchemaEvolutionContext schemaEvolutionContext) { this.table = requireNonNull(table, "table is null"); this.instantTime = requireNonNull(instantTime, "instantTime is null"); @@ -61,6 +63,7 @@ public HudiSplit( this.addresses = requireNonNull(addresses, "addresses is null"); this.nodeSelectionStrategy = requireNonNull(nodeSelectionStrategy, "nodeSelectionStrategy is null"); this.splitWeight = requireNonNull(splitWeight, "splitWeight is null"); + this.schemaEvolutionContext = requireNonNull(schemaEvolutionContext, "schemaEvolutionContext is null"); } @JsonProperty @@ -99,6 +102,12 @@ public List getAddresses() return addresses; } + @JsonProperty + public SchemaEvolutionContext getSchemaEvolutionContext() + { + return schemaEvolutionContext; + } + @JsonProperty @Override public NodeSelectionStrategy getNodeSelectionStrategy() diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java index aa7b80eaa703..bda3236271b4 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java @@ -17,6 +17,7 @@ import com.facebook.presto.hive.HdfsContext; import com.facebook.presto.hive.HdfsEnvironment; import com.facebook.presto.hive.filesystem.ExtendedFileSystem; +import com.facebook.presto.hive.metastore.Column; import com.facebook.presto.hive.metastore.ExtendedHiveMetastore; import com.facebook.presto.hive.metastore.MetastoreContext; import com.facebook.presto.hive.metastore.Partition; @@ -31,6 +32,7 @@ import com.facebook.presto.spi.connector.ConnectorSplitManager; import com.facebook.presto.spi.connector.ConnectorTransactionHandle; import com.facebook.presto.spi.schedule.NodeSelectionStrategy; +import com.google.common.base.Verify; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Streams; @@ -45,6 +47,7 @@ import org.apache.hudi.common.table.timeline.HoodieInstant; import org.apache.hudi.common.table.timeline.HoodieTimeline; import org.apache.hudi.common.table.view.HoodieTableFileSystemView; +import org.apache.hudi.common.util.Option; import javax.inject.Inject; @@ -52,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; import static com.facebook.presto.hive.metastore.MetastoreUtil.extractPartitionValues; import static com.facebook.presto.hudi.HudiErrorCode.HUDI_FILESYSTEM_ERROR; @@ -98,24 +102,59 @@ public ConnectorSplitSource getSplits( HudiTableLayoutHandle layout = (HudiTableLayoutHandle) layoutHandle; HudiTableHandle table = layout.getTable(); + // Load Hudi metadata + ExtendedFileSystem fs = getFileSystem(session, table); + boolean hudiMetadataTableEnabled = isHudiMetadataTableEnabled(session); + HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(hudiMetadataTableEnabled).build(); + Configuration conf = fs.getConf(); + HoodieTableMetaClient metaClient = HoodieTableMetaClient + .builder() + .setConf(conf) + .setBasePath(table.getPath()) + .build(); + // Retrieve and prune partitions - List partitions = hudiPartitionManager.getEffectivePartitions(session, metastore, table.getSchemaName(), table.getTableName(), layout.getTupleDomain()); + List partitions = hudiPartitionManager.getEffectivePartitions(session, metastore, metaClient, table.getSchemaName(), table.getTableName(), layout.getTupleDomain()); if (partitions.isEmpty()) { return new FixedSplitSource(ImmutableList.of()); } - // Load Hudi metadata - ExtendedFileSystem fs = getFileSystem(session, table); - HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(isHudiMetadataTableEnabled(session)).build(); - Configuration conf = fs.getConf(); - HoodieTableMetaClient metaClient = HoodieTableMetaClient.builder().setConf(conf).setBasePath(table.getPath()).build(); + // load timeline HoodieTimeline timeline = metaClient.getActiveTimeline().getCommitsTimeline().filterCompletedInstants(); String timestamp = timeline.lastInstant().map(HoodieInstant::getTimestamp).orElse(null); if (timestamp == null) { // no completed instant for current table return new FixedSplitSource(ImmutableList.of()); } + // prepare schema evolution + SchemaEvolutionContext schemaEvolutionContext = HudiSchemaEvolutionUtils.createSchemaEvolutionContext(metaClient); + // prepare splits HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(conf); + // if metadata table enabled, support dataskipping + if (hudiMetadataTableEnabled) { + MetastoreContext metastoreContext = toMetastoreContext(session); + Optional
hiveTableOpt = metastore.getTable(metastoreContext, table.getSchemaName(), table.getTableName()); + Verify.verify(hiveTableOpt.isPresent()); + HudiFileSkippingManager hudiFileSkippingManager = new HudiFileSkippingManager( + partitions, + HudiSessionProperties.getHoodieFilesystemViewSpillableDir(session), + engineContext, + metaClient, + HudiFileSkippingManager.getQueryType(session, hiveTableOpt.get().getStorage().getStorageFormat().getInputFormat()), + Option.empty()); + ImmutableList.Builder splitsBuilder = ImmutableList.builder(); + Map hudiPartitionMap = getHudiPartitions(hiveTableOpt.get(), layout, partitions); + hudiFileSkippingManager.listQueryFiles(layout.getTupleDomain()) + .entrySet() + .stream() + .flatMap(entry -> entry.getValue().stream().map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartitionMap.get(entry.getKey()), splitWeightProvider, schemaEvolutionContext))) + .filter(Optional::isPresent) + .map(Optional::get) + .forEach(splitsBuilder::add); + List splitsList = splitsBuilder.build(); + return splitsList.isEmpty() ? new FixedSplitSource(ImmutableList.of()) : new FixedSplitSource(splitsList); + } + HoodieTableFileSystemView fsView = createInMemoryFileSystemViewWithTimeline(engineContext, metaClient, metadataConfig, timeline); // Construct Presto splits @@ -127,7 +166,7 @@ public ConnectorSplitSource getSplits( Path partitionPath = new Path(hudiPartition.getStorage().getLocation()); String relativePartitionPath = FSUtils.getRelativePartitionPath(tablePath, partitionPath); fsView.getLatestFileSlicesBeforeOrOn(relativePartitionPath, timestamp, false) - .map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartition, splitWeightProvider)) + .map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartition, splitWeightProvider, schemaEvolutionContext)) .filter(Optional::isPresent) .map(Optional::get) .forEach(builder::add); @@ -156,7 +195,8 @@ private Optional createHudiSplit( FileSlice slice, String timestamp, HudiPartition partition, - HudiSplitWeightProvider splitWeightProvider) + HudiSplitWeightProvider splitWeightProvider, + SchemaEvolutionContext schemaEvolutionContext) { HudiFile hudiFile = slice.getBaseFile().map(f -> new HudiFile(f.getPath(), 0, f.getFileLen())).orElse(null); if (null == hudiFile && table.getTableType() == HudiTableType.COW) { @@ -176,7 +216,26 @@ private Optional createHudiSplit( logFiles, ImmutableList.of(), NodeSelectionStrategy.NO_PREFERENCE, - splitWeightProvider.calculateSplitWeight(sizeInBytes))); + splitWeightProvider.calculateSplitWeight(sizeInBytes), + schemaEvolutionContext)); + } + + private Map getHudiPartitions(Table table, HudiTableLayoutHandle tableLayout, List partitions) + { + List partitionColumns = table.getPartitionColumns(); + + Map> partitionMap = HudiPartitionManager + .getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), partitions); + if (partitions.size() == 1 && partitions.get(0).isEmpty()) { + // non-non-partitioned + return ImmutableMap.of(partitions.get(0), new HudiPartition(partitions.get(0), ImmutableList.of(), ImmutableMap.of(), table.getStorage(), tableLayout.getDataColumns())); + } + ImmutableMap.Builder builder = ImmutableMap.builder(); + partitionMap.entrySet().stream().map(entry -> { + List partitionValues = extractPartitionValues(entry.getKey()); + return new HudiPartition(entry.getKey(), partitionValues, entry.getValue(), table.getStorage(), fromDataColumns(table.getDataColumns())); + }).forEach(p -> builder.put(p.getName(), p)); + return builder.build(); } private static HudiPartition getHudiPartition(ExtendedHiveMetastore metastore, MetastoreContext context, HudiTableLayoutHandle tableLayout, String partitionName) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java new file mode 100644 index 000000000000..56bb0c92f6ed --- /dev/null +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java @@ -0,0 +1,57 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonProperty; + +import static com.google.common.base.MoreObjects.toStringHelper; +import static java.util.Objects.requireNonNull; + +public class SchemaEvolutionContext +{ + private final String requiredSchema; + private final String validCommits; + + @JsonCreator + public SchemaEvolutionContext( + @JsonProperty("requiredSchema") String requiredSchema, + @JsonProperty("validCommits") String validCommits) + { + this.requiredSchema = requireNonNull(requiredSchema, "requiredSchema is null"); + this.validCommits = requireNonNull(validCommits, "validCommits is null"); + } + + @JsonProperty + public String getRequiredSchema() + { + return requiredSchema; + } + + @JsonProperty + public String getValidCommits() + { + return validCommits; + } + + @Override + public String toString() + { + return toStringHelper(this) + .add("requiredSchema", requiredSchema) + .add("validCommits", validCommits) + .toString(); + } +} From 36b10ed2ed8c1beaa2a3b05999f1e4cb3e13bd8e Mon Sep 17 00:00:00 2001 From: xiarixiaoyao Date: Thu, 17 Nov 2022 16:53:29 +0800 Subject: [PATCH 2/6] address comments and add UT --- presto-hudi/pom.xml | 19 + .../facebook/presto/hudi/HudiErrorCode.java | 1 + .../presto/hudi/HudiFileSkippingManager.java | 82 ++-- .../presto/hudi/HudiPartitionManager.java | 13 +- .../facebook/presto/hudi/HudiPredicates.java | 5 - .../presto/hudi/HudiSchemaEvolutionUtils.java | 37 +- .../presto/hudi/HudiSplitManager.java | 24 +- .../presto/hudi/SchemaEvolutionContext.java | 24 +- .../AbstractHudiDistributedQueryTestBase.java | 380 ++++++++++++++++++ .../hudi/TestHudiSkippingAndEvolution.java | 153 +++++++ .../resources/hudi-skipping-schema-data.zip | Bin 0 -> 219435 bytes 11 files changed, 644 insertions(+), 94 deletions(-) create mode 100644 presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java create mode 100644 presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java create mode 100644 presto-hudi/src/test/resources/hudi-skipping-schema-data.zip diff --git a/presto-hudi/pom.xml b/presto-hudi/pom.xml index cbd81df8097a..8abdfb431c70 100644 --- a/presto-hudi/pom.xml +++ b/presto-hudi/pom.xml @@ -222,5 +222,24 @@ units provided + + + + org.lz4 + lz4-java + 1.8.0 + + + + com.github.ben-manes.caffeine + caffeine + 2.9.1 + + + com.google.errorprone + error_prone_annotations + + + diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java index 3dcb576a8f7b..7c74a61ae24e 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java @@ -30,6 +30,7 @@ public enum HudiErrorCode HUDI_FILESYSTEM_ERROR(0x40, EXTERNAL), HUDI_CANNOT_OPEN_SPLIT(0x41, EXTERNAL), HUDI_CURSOR_ERROR(0x42, EXTERNAL), + HUDI_SCHEMA_MISMATCH(0x43, EXTERNAL) /**/; private final ErrorCode errorCode; diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java index e3c88859ded1..e216af6e7ada 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java @@ -22,15 +22,12 @@ import com.facebook.presto.common.type.Type; import com.facebook.presto.parquet.predicate.TupleDomainParquetPredicate; import com.facebook.presto.spi.ColumnHandle; -import com.facebook.presto.spi.ConnectorSession; import io.airlift.slice.Slice; import io.airlift.slice.Slices; -import org.apache.hadoop.fs.FileStatus; -import org.apache.hadoop.fs.Path; import org.apache.hudi.avro.model.HoodieMetadataColumnStats; +import org.apache.hudi.common.config.HoodieCommonConfig; import org.apache.hudi.common.config.HoodieMetadataConfig; import org.apache.hudi.common.engine.HoodieEngineContext; -import org.apache.hudi.common.fs.FSUtils; import org.apache.hudi.common.model.BaseFile; import org.apache.hudi.common.model.FileSlice; import org.apache.hudi.common.model.HoodieTableQueryType; @@ -38,14 +35,15 @@ 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.table.view.HoodieTableFileSystemView; +import org.apache.hudi.common.table.view.FileSystemViewManager; +import org.apache.hudi.common.table.view.FileSystemViewStorageConfig; +import org.apache.hudi.common.table.view.SyncableFileSystemView; import org.apache.hudi.common.util.Option; import org.apache.hudi.common.util.hash.ColumnIndexID; import org.apache.hudi.metadata.HoodieTableMetadata; import org.apache.hudi.metadata.HoodieTableMetadataUtil; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -84,15 +82,12 @@ public class HudiFileSkippingManager private final HoodieTableQueryType queryType; private final Option specifiedQueryInstant; private final HoodieTableMetaClient metaClient; - private final HoodieEngineContext engineContext; - private final String spillableDir; private final HoodieTableType tableType; - protected final HoodieMetadataConfig metadataConfig; private final HoodieTableMetadata metadataTable; protected transient volatile Map> allInputFileSlices; - private transient volatile HoodieTableFileSystemView fileSystemView; + private transient volatile SyncableFileSystemView fileSystemView; public HudiFileSkippingManager( List partitions, @@ -105,33 +100,25 @@ public HudiFileSkippingManager( this.queryType = queryType; this.specifiedQueryInstant = specifiedQueryInstant; this.metaClient = metaClient; - this.engineContext = engineContext; - this.spillableDir = spillableDir; - this.metadataConfig = HoodieMetadataConfig.newBuilder().withMetadataIndexBloomFilter(true).enable(true).build(); + HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(true).build(); this.tableType = metaClient.getTableConfig().getTableType(); this.metadataTable = HoodieTableMetadata - .create(engineContext, this.metadataConfig, metaClient.getBasePathV2().toString(), spillableDir, true); - prepareAllInputFileSlices(partitions); + .create(engineContext, metadataConfig, metaClient.getBasePathV2().toString(), spillableDir, true); + prepareAllInputFileSlices(partitions, engineContext, metadataConfig, spillableDir); } - private void prepareAllInputFileSlices(List partitions) + private void prepareAllInputFileSlices(List partitions, HoodieEngineContext engineContext, HoodieMetadataConfig metadataConfig, String spillableDir) { long startTime = System.currentTimeMillis(); - Path tablePath = metaClient.getBasePathV2(); - List fullPartitionPath = partitions.stream().map(p -> - p.isEmpty() ? tablePath.toString() : new Path(tablePath, p).toString()).collect(Collectors.toList()); - Map fetchedPartitionToFiles = FSUtils.getFilesInPartitions( - engineContext, - metadataConfig, - metaClient.getBasePathV2().toString(), - fullPartitionPath.toArray(new String[0]), - spillableDir); - FileStatus[] allFiles = fetchedPartitionToFiles.values().stream().flatMap(Arrays::stream).toArray(FileStatus[]::new); - HoodieTimeline activeTimeline = metaClient.reloadActiveTimeline(); Option latestInstant = activeTimeline.lastInstant(); // build system view. - fileSystemView = new HoodieTableFileSystemView(metaClient, activeTimeline, allFiles); + fileSystemView = FileSystemViewManager + .createViewManager(engineContext, metadataConfig, + FileSystemViewStorageConfig.newBuilder().withBaseStoreDir(spillableDir).build(), + HoodieCommonConfig.newBuilder().build(), + () -> metadataTable) + .getFileSystemView(metaClient); Option queryInstant = specifiedQueryInstant.or(() -> latestInstant.map(HoodieInstant::getTimestamp)); if (tableType.equals(HoodieTableType.MERGE_ON_READ) && queryType.equals(HoodieTableQueryType.SNAPSHOT)) { allInputFileSlices = partitions.stream().collect(Collectors.toMap(Function.identity(), @@ -154,23 +141,6 @@ private void prepareAllInputFileSlices(List partitions) log.info(String.format("prepare query files for table %s, spent: %d ms", metaClient.getTableConfig().getTableName(), duration)); } - public static HoodieTableQueryType getQueryType(ConnectorSession session, String inputFormat) - { - // TODO support incremental query - switch (inputFormat) { - case "org.apache.hudi.hadoop.HoodieParquetInputFormat": - case "com.uber.hoodie.hadoop.HoodieInputFormat": - // cow table/ mor ro table - return HoodieTableQueryType.READ_OPTIMIZED; - case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": - case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": - // mor rt table - return HoodieTableQueryType.SNAPSHOT; - default: - throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", inputFormat)); - } - } - public Map> listQueryFiles(TupleDomain tupleDomain) { // do file skipping by MetadataTable @@ -185,20 +155,21 @@ public Map> listQueryFiles(TupleDomain tup log.warn(String.format("failed to do data skipping for table: %s, fallback to all files scan", metaClient.getBasePathV2().toString()), e); candidateFileSlices = allInputFileSlices; } - int candidateFileSize = candidateFileSlices.entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, (n1, n2) -> n1 + n2); - int totalFiles = allInputFileSlices.entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, (n1, n2) -> n1 + n2); - double skippingPercent = totalFiles == 0 ? 0.0d : (totalFiles - candidateFileSize) / (totalFiles + 0.0d); - log.info(String.format("Total files: %s; candidate files after data skipping: %s; skipping percent %s", - totalFiles, candidateFileSize, skippingPercent)); + if (log.isDebugEnabled()) { + int candidateFileSize = candidateFileSlices.values().stream().map(List::size).reduce(0, Integer::sum); + int totalFiles = allInputFileSlices.values().stream().map(List::size).reduce(0, Integer::sum); + double skippingPercent = totalFiles == 0 ? 0.0d : (totalFiles - candidateFileSize) / (totalFiles + 0.0d); + log.debug(String.format("Total files: %s; candidate files after data skipping: %s; skipping percent %s", totalFiles, candidateFileSize, skippingPercent)); + } return candidateFileSlices; } - public Map> lookupCandidateFilesInMetadataTable(Map> inputFileSlices, TupleDomain tupleDomain) + private Map> lookupCandidateFilesInMetadataTable(Map> inputFileSlices, TupleDomain tupleDomain) { // split regular column predicates TupleDomain regularTupleDomain = HudiPredicates.from(tupleDomain).getRegularColumnPredicates(); TupleDomain regularColumnPredicates = regularTupleDomain.transform(HudiColumnHandle::getName); - if (regularColumnPredicates.isAll()) { + if (regularColumnPredicates.isAll() || !regularColumnPredicates.getDomains().isPresent()) { return inputFileSlices; } List regularColumns = regularColumnPredicates.getDomains().get().entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList()); @@ -222,17 +193,14 @@ public Map> lookupCandidateFilesInMetadataTable(Map stats = statsByFileName.get(fileSliceName); - if (!evaluateStatisticPredicate(regularColumnPredicates, stats, regularColumns)) { - return false; - } - return true; + return evaluateStatisticPredicate(regularColumnPredicates, stats, regularColumns); }).collect(Collectors.toList()); })); } private boolean evaluateStatisticPredicate(TupleDomain regularColumnPredicates, List stats, List regularColumns) { - if (regularColumnPredicates.isNone()) { + if (regularColumnPredicates.isNone() || !regularColumnPredicates.getDomains().isPresent()) { return true; } for (String regularColumn : regularColumns) { diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java index 5ee29d49d5c9..007bd88c526c 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java @@ -98,15 +98,13 @@ public List getEffectivePartitions( boolean metaTableEnabled = isHudiMetadataTableEnabled(connectorSession); - return metaTableEnabled ? prunePartitionByMetaDataTable(connectorSession, metaClient, schemaName, tableName, partitionColumns, tupleDomain) : + return metaTableEnabled ? prunePartitionByMetaDataTable(connectorSession, metaClient, partitionColumns, tupleDomain) : prunePartitionByMetaStore(metastore, metastoreContext, schemaName, tableName, partitionColumns, tupleDomain); } private List prunePartitionByMetaDataTable( ConnectorSession connectorSession, HoodieTableMetaClient metaClient, - String schemaName, - String tableName, List partitionColumns, TupleDomain tupleDomain) { @@ -192,15 +190,18 @@ private List prunePartitions( { return candidatePartitionPaths.entrySet().stream().filter(f -> { Map partitionMapping = f.getValue(); - return partitionMapping.entrySet().stream().allMatch(p -> evaluatePartitionPredice(partitionPredicate, partitionColumnHandles, p.getValue(), p.getKey())); + return partitionMapping.entrySet().stream().allMatch(p -> evaluatePartitionPredicate(partitionPredicate, partitionColumnHandles, p.getValue(), p.getKey())); }).map(entry -> entry.getKey()).collect(Collectors.toList()); } - private boolean evaluatePartitionPredice(TupleDomain partitionPredicate, List partitionColumnHandles, String partitionPathValue, String partitionName) + private boolean evaluatePartitionPredicate(TupleDomain partitionPredicate, List partitionColumnHandles, String partitionPathValue, String partitionName) { Optional columnHandleOpt = partitionColumnHandles.stream().filter(f -> f.getName().equals(partitionName)).findFirst(); if (columnHandleOpt.isPresent()) { Domain domain = getDomain(columnHandleOpt.get(), partitionPathValue); + if (!partitionPredicate.getDomains().isPresent()) { + return true; + } Domain columnPredicate = partitionPredicate.getDomains().get().get(partitionName); // no predicate on current partitionName if (columnPredicate == null) { @@ -209,7 +210,7 @@ private boolean evaluatePartitionPredice(TupleDomain partitionPredicate, // For null partition, hive will produce a default value for current partition. if (partitionPathValue.equals("default")) { - return false; + return true; } if (columnPredicate.intersect(domain).isNone()) { diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java index bdc7796e3771..44a332c17e05 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java @@ -56,11 +56,6 @@ private HudiPredicates( this.regularColumnPredicates = regularColumnPredicates; } - public TupleDomain getPartitionColumnPredicates() - { - return partitionColumnPredicates; - } - public TupleDomain getRegularColumnPredicates() { return regularColumnPredicates; diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java index 453ba138441c..76c20f08f32d 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java @@ -16,6 +16,7 @@ import com.facebook.airlift.log.Logger; import com.facebook.presto.hive.HiveType; +import com.facebook.presto.spi.PrestoException; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import org.apache.hadoop.conf.Configuration; @@ -44,9 +45,11 @@ import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import static com.facebook.presto.hive.HiveType.toHiveType; +import static java.lang.String.format; public final class HudiSchemaEvolutionUtils { @@ -56,22 +59,22 @@ private HudiSchemaEvolutionUtils() { } - public static SchemaEvolutionContext createSchemaEvolutionContext(HoodieTableMetaClient metaClient) + public static SchemaEvolutionContext createSchemaEvolutionContext(Optional metaClientOpt) { // no need to do schema evolution for mor table, since hudi kernel will do it. - if (metaClient.getTableType() == HoodieTableType.MERGE_ON_READ || metaClient == null) { + if (!metaClientOpt.isPresent() || metaClientOpt.get().getTableType() == HoodieTableType.MERGE_ON_READ) { return new SchemaEvolutionContext("", ""); } try { - TableSchemaResolver schemaUtil = new TableSchemaResolver(metaClient); + TableSchemaResolver schemaUtil = new TableSchemaResolver(metaClientOpt.get()); String internalSchema = schemaUtil.getTableInternalSchemaFromCommitMetadata().map(SerDeHelper::toJson).orElse(""); - HoodieTimeline hoodieTimeline = metaClient.getCommitsAndCompactionTimeline().filterCompletedInstants(); + HoodieTimeline hoodieTimeline = metaClientOpt.get().getCommitsAndCompactionTimeline().filterCompletedInstants(); String validCommits = hoodieTimeline.getInstants().map(HoodieInstant::getFileName).collect(Collectors.joining(",")); return new SchemaEvolutionContext(internalSchema, validCommits); } catch (Exception e) { - log.warn(String.format("failed to get internal Schema from hudi tableļ¼š%s , fallback to original logical", metaClient.getBasePathV2()), e); + log.warn(String.format("failed to get internal Schema from hudi tableļ¼š%s , fallback to original logical", metaClientOpt.get().getBasePathV2()), e); } return new SchemaEvolutionContext("", ""); } @@ -79,7 +82,7 @@ public static SchemaEvolutionContext createSchemaEvolutionContext(HoodieTableMet public static Pair, Map> doEvolution(HudiSplit hudiSplit, List oldColumnHandle, String tablePath, Configuration hadoopConf) { SchemaEvolutionContext schemaEvolutionContext = hudiSplit.getSchemaEvolutionContext(); - InternalSchema internalSchema = SerDeHelper.fromJson(schemaEvolutionContext.getRequiredSchema()).orElse(InternalSchema.getEmptyInternalSchema()); + InternalSchema internalSchema = SerDeHelper.fromJson(schemaEvolutionContext.getLatestSchema()).orElse(InternalSchema.getEmptyInternalSchema()); if (internalSchema.isEmptySchema() || !hudiSplit.getBaseFile().isPresent()) { return Pair.of(oldColumnHandle, ImmutableMap.of()); } @@ -88,10 +91,15 @@ public static Pair, Map> doEvolution(Hu Path baseFilePath = new Path(hudiSplit.getBaseFile().get().getPath()); String commitTime = FSUtils.getCommitTime(baseFilePath.getName()); - InternalSchema fileSchema = InternalSchemaCache.getInternalSchemaByVersionId(Long.parseUnsignedLong(commitTime), tablePath, hadoopConf, schemaEvolutionContext.getValidCommits()); + InternalSchema fileSchema = InternalSchemaCache.getInternalSchemaByVersionId(Long.parseUnsignedLong(commitTime), tablePath, hadoopConf, schemaEvolutionContext.getValidCommitFiles()); log.debug(String.format(Locale.ENGLISH, "File schema from hudi base file is %s", fileSchema)); - // + InternalSchema mergedSchema = new InternalSchemaMerger(fileSchema, prunedSchema, true, true).mergeSchema(); + + if (mergedSchema.columns().size() != oldColumnHandle.size()) { + throw new PrestoException(HudiErrorCode.HUDI_SCHEMA_MISMATCH, format("Found mismatch schema, pls sync latest hudi meta to hive")); + } + ImmutableList.Builder builder = ImmutableList.builder(); for (int i = 0; i < oldColumnHandle.size(); i++) { HiveType hiveType = constructPrestoTypeFromType(mergedSchema.columns().get(i).type()); @@ -101,13 +109,20 @@ public static Pair, Map> doEvolution(Hu return Pair.of(builder.build(), collectTypeChangedCols(prunedSchema, mergedSchema)); } - private static Map collectTypeChangedCols(InternalSchema schema, InternalSchema oldSchema) + /** + * Collect all type changed columns + * + * @param schema schema hold latest column type. + * @param querySchema schema hold old column type. + * @return a map: (columnName -> oldColumnType) + */ + private static Map collectTypeChangedCols(InternalSchema schema, InternalSchema querySchema) { return InternalSchemaUtils - .collectTypeChangedCols(schema, oldSchema) + .collectTypeChangedCols(schema, querySchema) .entrySet() .stream() - .collect(Collectors.toMap(e -> oldSchema.columns().get(e.getKey()).name(), e -> constructPrestoTypeFromType(e.getValue().getRight()))); + .collect(Collectors.toMap(e -> querySchema.columns().get(e.getKey()).name(), e -> constructPrestoTypeFromType(e.getValue().getRight()))); } private static HiveType constructPrestoTypeFromType(Type type) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java index bda3236271b4..ee58d467ff94 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java @@ -43,6 +43,7 @@ import org.apache.hudi.common.engine.HoodieLocalEngineContext; import org.apache.hudi.common.fs.FSUtils; import org.apache.hudi.common.model.FileSlice; +import org.apache.hudi.common.model.HoodieTableQueryType; import org.apache.hudi.common.table.HoodieTableMetaClient; import org.apache.hudi.common.table.timeline.HoodieInstant; import org.apache.hudi.common.table.timeline.HoodieTimeline; @@ -127,7 +128,7 @@ public ConnectorSplitSource getSplits( return new FixedSplitSource(ImmutableList.of()); } // prepare schema evolution - SchemaEvolutionContext schemaEvolutionContext = HudiSchemaEvolutionUtils.createSchemaEvolutionContext(metaClient); + SchemaEvolutionContext schemaEvolutionContext = HudiSchemaEvolutionUtils.createSchemaEvolutionContext(Optional.of(metaClient)); // prepare splits HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(conf); // if metadata table enabled, support dataskipping @@ -140,7 +141,7 @@ public ConnectorSplitSource getSplits( HudiSessionProperties.getHoodieFilesystemViewSpillableDir(session), engineContext, metaClient, - HudiFileSkippingManager.getQueryType(session, hiveTableOpt.get().getStorage().getStorageFormat().getInputFormat()), + getQueryType(hiveTableOpt.get().getStorage().getStorageFormat().getInputFormat()), Option.empty()); ImmutableList.Builder splitsBuilder = ImmutableList.builder(); Map hudiPartitionMap = getHudiPartitions(hiveTableOpt.get(), layout, partitions); @@ -227,7 +228,7 @@ private Map getHudiPartitions(Table table, HudiTableLayou Map> partitionMap = HudiPartitionManager .getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), partitions); if (partitions.size() == 1 && partitions.get(0).isEmpty()) { - // non-non-partitioned + // non-partitioned return ImmutableMap.of(partitions.get(0), new HudiPartition(partitions.get(0), ImmutableList.of(), ImmutableMap.of(), table.getStorage(), tableLayout.getDataColumns())); } ImmutableMap.Builder builder = ImmutableMap.builder(); @@ -279,4 +280,21 @@ private static HudiSplitWeightProvider createSplitWeightProvider(ConnectorSessio } return HudiSplitWeightProvider.uniformStandardWeightProvider(); } + + private static HoodieTableQueryType getQueryType(String inputFormat) + { + // TODO support incremental query + switch (inputFormat) { + case "org.apache.hudi.hadoop.HoodieParquetInputFormat": + case "com.uber.hoodie.hadoop.HoodieInputFormat": + // cow table/ mor ro table + return HoodieTableQueryType.READ_OPTIMIZED; + case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": + case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": + // mor rt table + return HoodieTableQueryType.SNAPSHOT; + default: + throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", inputFormat)); + } + } } diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java index 56bb0c92f6ed..97245c4d40b9 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java @@ -22,36 +22,36 @@ public class SchemaEvolutionContext { - private final String requiredSchema; - private final String validCommits; + private final String latestSchema; + private final String validCommitFiles; @JsonCreator public SchemaEvolutionContext( - @JsonProperty("requiredSchema") String requiredSchema, - @JsonProperty("validCommits") String validCommits) + @JsonProperty("latestSchema") String latestSchema, + @JsonProperty("validCommitFiles") String validCommitFiles) { - this.requiredSchema = requireNonNull(requiredSchema, "requiredSchema is null"); - this.validCommits = requireNonNull(validCommits, "validCommits is null"); + this.latestSchema = requireNonNull(latestSchema, "latestSchema is null"); + this.validCommitFiles = requireNonNull(validCommitFiles, "validCommitFiles is null"); } @JsonProperty - public String getRequiredSchema() + public String getLatestSchema() { - return requiredSchema; + return latestSchema; } @JsonProperty - public String getValidCommits() + public String getValidCommitFiles() { - return validCommits; + return validCommitFiles; } @Override public String toString() { return toStringHelper(this) - .add("requiredSchema", requiredSchema) - .add("validCommits", validCommits) + .add("latestSchema", latestSchema) + .add("validCommitFiles", validCommitFiles) .toString(); } } diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java new file mode 100644 index 000000000000..7d04ab153289 --- /dev/null +++ b/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java @@ -0,0 +1,380 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.facebook.presto.Session; +import com.facebook.presto.hive.HdfsConfiguration; +import com.facebook.presto.hive.HdfsConfigurationInitializer; +import com.facebook.presto.hive.HdfsEnvironment; +import com.facebook.presto.hive.HiveClientConfig; +import com.facebook.presto.hive.HiveColumnConverterProvider; +import com.facebook.presto.hive.HiveHdfsConfiguration; +import com.facebook.presto.hive.HiveType; +import com.facebook.presto.hive.MetastoreClientConfig; +import com.facebook.presto.hive.authentication.NoHdfsAuthentication; +import com.facebook.presto.hive.metastore.Column; +import com.facebook.presto.hive.metastore.Database; +import com.facebook.presto.hive.metastore.ExtendedHiveMetastore; +import com.facebook.presto.hive.metastore.MetastoreContext; +import com.facebook.presto.hive.metastore.PrestoTableType; +import com.facebook.presto.hive.metastore.PrincipalPrivileges; +import com.facebook.presto.hive.metastore.Storage; +import com.facebook.presto.hive.metastore.StorageFormat; +import com.facebook.presto.hive.metastore.Table; +import com.facebook.presto.hive.metastore.file.FileHiveMetastore; +import com.facebook.presto.spi.ConnectorId; +import com.facebook.presto.spi.ConnectorSession; +import com.facebook.presto.spi.security.PrincipalType; +import com.facebook.presto.testing.QueryRunner; +import com.facebook.presto.tests.AbstractTestQueryFramework; +import com.facebook.presto.tests.DistributedQueryRunner; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Streams; +import com.google.common.io.Resources; +import org.apache.hadoop.hive.ql.io.parquet.MapredParquetOutputFormat; +import org.apache.hadoop.hive.ql.io.parquet.serde.ParquetHiveSerDe; +import org.apache.hudi.common.model.HoodieTableType; +import org.apache.hudi.hadoop.HoodieParquetInputFormat; +import org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat; +import org.testng.annotations.AfterClass; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +import static com.facebook.presto.hive.HiveType.HIVE_BINARY; +import static com.facebook.presto.hive.HiveType.HIVE_BOOLEAN; +import static com.facebook.presto.hive.HiveType.HIVE_DATE; +import static com.facebook.presto.hive.HiveType.HIVE_DOUBLE; +import static com.facebook.presto.hive.HiveType.HIVE_FLOAT; +import static com.facebook.presto.hive.HiveType.HIVE_INT; +import static com.facebook.presto.hive.HiveType.HIVE_LONG; +import static com.facebook.presto.hive.HiveType.HIVE_STRING; +import static com.facebook.presto.testing.TestingSession.testSessionBuilder; +import static java.nio.file.Files.createDirectories; +import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; + +public abstract class AbstractHudiDistributedQueryTestBase + extends AbstractTestQueryFramework +{ + public static final String HUDI_CATALOG = "hudi"; + + public static final String HUDI_SCHEMA = "testing"; // Schema in Hive which has test hudi tables + + protected static ExtendedHiveMetastore metastore; + + private static final String OWNER_PUBLIC = "public"; + protected static final MetastoreContext METASTORE_CONTEXT = new MetastoreContext("test_user", "test_queryId", Optional.empty(), Optional.empty(), Optional.empty(), false, HiveColumnConverterProvider.DEFAULT_COLUMN_CONVERTER_PROVIDER); + private static final PrincipalPrivileges PRINCIPAL_PRIVILEGES = new PrincipalPrivileges(ImmutableMultimap.of(), ImmutableMultimap.of()); + + private static final StorageFormat STORAGE_FORMAT_COPY_ON_WRITE = StorageFormat.create( + ParquetHiveSerDe.class.getName(), + HoodieParquetInputFormat.class.getName(), + MapredParquetOutputFormat.class.getName()); + private static final StorageFormat STORAGE_FORMAT_MERGE_ON_READ = StorageFormat.create( + ParquetHiveSerDe.class.getName(), + HoodieParquetRealtimeInputFormat.class.getName(), + MapredParquetOutputFormat.class.getName()); + + // spark.sql( + // """create table data_partition_prune + // |(id int, comb int, col0 int, col1 bigint, col2 float, col3 double, + // | col4 string, col5 date, col6 boolean, col7 binary, year int, month int, day int) + // | using hudi + // | partitioned by (year,month,day) + // | options( + // | type='cow', primaryKey='id', preCombineField='comb', + // | hoodie.metadata.index.column.stats.enable = "true", + // | hoodie.metadata.index.column.stats.file.group.count = "1", + // | hoodie.metadata.index.column.stats.column.list = 'col0,col3,col4,col5', + // | 'hoodie.metadata.enable'='true')""".stripMargin) + // + // spark.sql( + // s""" + // | insert into data_partition_prune values + // | (1,1,99,1111111,101.01,1001.0001,'x000001','2021-12-25',true,'a01',2022, 11, 12), + // | (2,2,99,1111111,102.02,1002.0002,'x000002','2021-12-25',true,'a02',2022, 10, 30), + // | (3,3,99,1111111,103.03,1003.0003,'x000003','2021-12-25',false,'a03',2021, 10, 11), + // | (4,4,99,1111111,104.04,1004.0004,'x000004','2021-12-26',true,'a04',2021, 11, 12) + // |""".stripMargin) + public static final List DATA_COLUMNS = ImmutableList.of( + column("id", HIVE_INT), + column("comb", HIVE_INT), + column("col0", HIVE_INT), + column("col1", HIVE_LONG), + column("col2", HIVE_FLOAT), + column("col3", HIVE_DOUBLE), + column("col4", HIVE_STRING), + column("col5", HIVE_DATE), + column("col6", HIVE_BOOLEAN), + column("col7", HIVE_BINARY)); + + // simulate drop column + rename column + add column. + // spark.sql( + // """create table data_column_rename_drop_add + // |(id int, comb int, col0 int, col1 bigint, col2 float, col3 double, + // | col4 string, col5 date, col6 boolean, col7 binary, year int, month int, day int) + // | using hudi + // | partitioned by (year,month,day) + // | options( + // | type='cow', primaryKey='id', preCombineField='comb', + // | 'hoodie.metadata.enable'='true')""".stripMargin) + // + // spark.sql( + // s""" + // | insert into data_column_rename_drop_add values + // | (1,1,99,1111111,101.01,1001.0001,'x000001','2021-12-25',true,'a01',2022, 11, 12), + // | (2,2,99,1111111,102.02,1002.0002,'x000002','2021-12-25',true,'a02',2022, 10, 30), + // | (3,3,99,1111111,103.03,1003.0003,'x000003','2021-12-25',false,'a03',2021, 10, 11), + // | (4,4,99,1111111,104.04,1004.0004,'x000004','2021-12-26',true,'a04',2021, 11, 12) + // |""".stripMargin) + // + // spark.sql("set hoodie.schema.evolution.enable=true") + // + // spark.sql("alter table data_column_rename_drop_add drop column col1") + // + // spark.sql("alter table data_column_rename_drop_add rename column col3 to col3_rename") + // + // spark.sql("alter table data_column_rename_drop_add add columns(col4_new string comment 'add ext1' after col4)") + public static final List DATA_COLUMNS1 = ImmutableList.of( + column("id", HIVE_INT), + column("comb", HIVE_INT), + column("col0", HIVE_INT), // drop col1 + column("col2", HIVE_FLOAT), + column("col3_rename", HIVE_DOUBLE), // rename col3 to col3_rename + column("col4", HIVE_STRING), + column("col4_new", HIVE_STRING), // new add column + column("col5", HIVE_DATE), + column("col6", HIVE_BOOLEAN), + column("col7", HIVE_BINARY)); + + // simulate change column type. + // spark.sql( + // """create table data_column_type_change + // |(id int, comb int, col0 int, col1 bigint, col2 float, col3 double, + // | col4 string, col5 date, col6 boolean, col7 binary, year int, month int, day int) + // | using hudi + // | partitioned by (year,month,day) + // | options( + // | type='cow', primaryKey='id', preCombineField='comb', + // | 'hoodie.metadata.enable'='true')""".stripMargin) + // + // spark.sql( + // s""" + // | insert into data_column_type_change values + // | (1,1,99,1111111,101.01,1001.0001,'1','2021-12-25',true,'a01',2022, 11, 12), + // | (2,2,99,1111111,102.02,1002.0002,'2','2021-12-25',true,'a02',2022, 10, 30), + // | (3,3,99,1111111,103.03,1003.0003,'3','2021-12-25',false,'a03',2021, 10, 11), + // | (4,4,99,1111111,104.04,1004.0004,'4','2021-12-26',true,'a04',2021, 11, 12) + // |""".stripMargin) + // + // spark.sql("set hoodie.schema.evolution.enable=true") + // + // spark.sql("alter table data_column_type_change alter column col0 type long") + // spark.sql("alter table data_column_type_change alter column col2 type double") + // spark.sql("alter table data_column_type_change alter column col1 type string") + public static final List DATA_COLUMNS2 = ImmutableList.of( + column("id", HIVE_INT), + column("comb", HIVE_INT), + column("col0", HIVE_LONG), // int -> long + column("col1", HIVE_STRING), // long -> string + column("col2", HIVE_DOUBLE), // float -> double + column("col3", HIVE_DOUBLE), + column("col4", HIVE_STRING), + column("col5", HIVE_DATE), + column("col6", HIVE_BOOLEAN), + column("col7", HIVE_BINARY)); + + public static final List PARTITION_COLUMNS = ImmutableList.of(column("year", HIVE_INT), column("month", HIVE_INT), column("day", HIVE_INT)); + public static final List HUDI_META_COLUMNS = ImmutableList.of( + column("_hoodie_commit_time", HiveType.HIVE_STRING), + column("_hoodie_commit_seqno", HiveType.HIVE_STRING), + column("_hoodie_record_key", HiveType.HIVE_STRING), + column("_hoodie_partition_path", HiveType.HIVE_STRING), + column("_hoodie_file_name", HiveType.HIVE_STRING)); + + /** + * List of tables present in the test resources directory. + * used to test dataskipping/partition prune. + */ + protected static final String HUDI_SKIPPING_TABLE = "data_partition_prune"; + protected static final String HUDI_COLUMN_CHANGE_TABLE = "data_column_rename_drop_add"; + protected static final String HUDI_COLUMN_TYPE_CHANGE_TABLE = "data_column_type_change"; + protected static ConnectorSession connectorSession; + + @Override + protected QueryRunner createQueryRunner() + throws Exception + { + return createHudiQueryRunner(ImmutableMap.of()); + } + + @AfterClass + public void deleteTestHudiTables() + { + QueryRunner queryRunner = getQueryRunner(); + if (queryRunner != null) { + // Remove the test hudi tables from HMS + metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_SKIPPING_TABLE, false); + metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_COLUMN_CHANGE_TABLE, false); + metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_COLUMN_TYPE_CHANGE_TABLE, false); + } + } + + protected static String getTablePath(String tableName, Path dataDir) + { + return "file://" + dataDir.resolve(tableName); + } + + private static DistributedQueryRunner createHudiQueryRunner(Map extraProperties) + throws Exception + { + Session session = testSessionBuilder() + .setCatalog(HUDI_CATALOG) + .setSchema(HUDI_SCHEMA) + .setCatalogSessionProperty(HUDI_CATALOG, "hudi_metadata_table_enabled", "true") + .setConnectionProperty(new ConnectorId("hudi"), "hudi_metadata_table_enabled", "true") + .build(); + + DistributedQueryRunner queryRunner = DistributedQueryRunner.builder(session) + .setExtraProperties(extraProperties) + .build(); + + // setup file metastore + + Path catalogDirectory = queryRunner.getCoordinator().getDataDirectory().resolve("catalog"); + + metastore = createFileHiveMetastore(catalogDirectory.toString()); + + // create database + Database database = Database.builder() + .setDatabaseName(HUDI_SCHEMA) + .setOwnerName(OWNER_PUBLIC) + .setOwnerType(PrincipalType.ROLE) + .build(); + metastore.createDatabase(METASTORE_CONTEXT, database); + + Path testingDataDir = queryRunner.getCoordinator().getDataDirectory().resolve("data"); + + try (InputStream stream = Resources.getResource("hudi-skipping-schema-data.zip").openStream()) { + unzip(stream, testingDataDir); + } + catch (IOException e) { + throw new UncheckedIOException(e); + } + + // Create the test hudi tables for dataSkipping/partition prune in HMS + registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_SKIPPING_TABLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS.stream()).collect(Collectors.toList())); + + // Create the test hudi table for column rename/drop/add in HMS + registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_COLUMN_CHANGE_TABLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS1.stream()).collect(Collectors.toList())); + + // create the test hudi table for column type change in HMS + registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_COLUMN_TYPE_CHANGE_TABLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS2.stream()).collect(Collectors.toList())); + + // Install a hudi connector catalog + queryRunner.installPlugin(new HudiPlugin("hudi", Optional.of(metastore))); + Map hudiProperties = ImmutableMap.builder().build(); + queryRunner.createCatalog(HUDI_CATALOG, "hudi", hudiProperties); + + connectorSession = queryRunner.getDefaultSession().toConnectorSession(new ConnectorId(session.getCatalog().get())); + + return queryRunner; + } + + private static ExtendedHiveMetastore createFileHiveMetastore(String catalogDir) + { + HiveClientConfig hiveClientConfig = new HiveClientConfig(); + MetastoreClientConfig metastoreClientConfig = new MetastoreClientConfig(); + HdfsConfiguration hdfsConfiguration = new HiveHdfsConfiguration( + new HdfsConfigurationInitializer(hiveClientConfig, metastoreClientConfig), + ImmutableSet.of(), hiveClientConfig); + HdfsEnvironment hdfsEnvironment = new HdfsEnvironment(hdfsConfiguration, metastoreClientConfig, new NoHdfsAuthentication()); + return new FileHiveMetastore(hdfsEnvironment, catalogDir, "test"); + } + + private static Column column(String name, HiveType type) + { + return new Column(name, type, Optional.empty(), Optional.empty()); + } + + private static void registerHudiTableInHMS(HoodieTableType type, String name, Path dataDir, List dataColumns) + { + // ref: org.apache.hudi.hive.ddl.HMSDDLExecutor#createTable + Table table = Table.builder() + .setDatabaseName(HUDI_SCHEMA) + .setTableName(name) + .setTableType(PrestoTableType.EXTERNAL_TABLE) + .setOwner(OWNER_PUBLIC) + .setDataColumns(dataColumns) + .setPartitionColumns(PARTITION_COLUMNS) + .setParameters(ImmutableMap.of("serialization.format", "1", "EXTERNAL", "TRUE")) + .withStorage(buildingStorage(type, getTablePath(name, dataDir))) + .build(); + metastore.createTable(METASTORE_CONTEXT, table, PRINCIPAL_PRIVILEGES); + } + + private static Consumer buildingStorage(HoodieTableType tableType, String location) + { + return storageBuilder -> storageBuilder.setStorageFormat(getStorageFormat(tableType)).setLocation(location); + } + + private static StorageFormat getStorageFormat(HoodieTableType tableType) + { + if (tableType == HoodieTableType.COPY_ON_WRITE) { + return STORAGE_FORMAT_COPY_ON_WRITE; + } + if (tableType == HoodieTableType.MERGE_ON_READ) { + return STORAGE_FORMAT_MERGE_ON_READ; + } + throw new IllegalArgumentException("Unsupported table type " + tableType); + } + + private static void unzip(InputStream inputStream, Path destination) + throws IOException + { + createDirectories(destination); + try (ZipInputStream zipStream = new ZipInputStream(inputStream)) { + while (true) { + ZipEntry zipEntry = zipStream.getNextEntry(); + if (zipEntry == null) { + break; + } + + Path entryPath = destination.resolve(zipEntry.getName()); + if (zipEntry.isDirectory()) { + createDirectories(entryPath); + } + else { + createDirectories(entryPath.getParent()); + Files.copy(zipStream, entryPath, REPLACE_EXISTING); + } + zipStream.closeEntry(); + } + } + } +} diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java new file mode 100644 index 000000000000..66deff5b87d6 --- /dev/null +++ b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java @@ -0,0 +1,153 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.facebook.presto.common.predicate.Domain; +import com.facebook.presto.common.predicate.Range; +import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.common.predicate.ValueSet; +import com.facebook.presto.common.type.DoubleType; +import com.facebook.presto.common.type.IntegerType; +import com.facebook.presto.hive.metastore.Table; +import com.facebook.presto.spi.ColumnHandle; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import net.jpountz.xxhash.XXHashFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hudi.common.config.HoodieMetadataConfig; +import org.apache.hudi.common.engine.HoodieLocalEngineContext; +import org.apache.hudi.common.model.HoodieTableQueryType; +import org.apache.hudi.common.table.HoodieTableMetaClient; +import org.apache.hudi.common.util.Option; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static com.facebook.presto.hudi.HudiSessionProperties.isHudiMetadataTableEnabled; +import static java.lang.String.format; +import static org.testng.Assert.assertEquals; + +/** + * Integration tests for reading Delta tables. + */ +public class TestHudiSkippingAndEvolution + extends AbstractHudiDistributedQueryTestBase +{ + @Test + public void testPartitionPruneAndFileSkipping() + { + Optional
table = metastore.getTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_SKIPPING_TABLE); + HudiPartitionManager hudiPartitionManager = new HudiPartitionManager(getQueryRunner().getMetadata().getFunctionAndTypeManager()); + boolean hudiMetadataTableEnabled = isHudiMetadataTableEnabled(connectorSession); + HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(hudiMetadataTableEnabled).build(); + + HoodieTableMetaClient metaClient = HoodieTableMetaClient + .builder() + .setConf(new Configuration()) + .setBasePath(table.get().getStorage().getLocation()) + .build(); + // test partition prune by mdt + // create domain + List partitionColumns = HudiMetadata.fromPartitionColumns(table.get().getPartitionColumns()); + // year=2022 and month=11 and day=12 + TupleDomain predicate = TupleDomain.withColumnDomains(ImmutableMap.of( + partitionColumns.get(0), Domain.create(ValueSet.of(IntegerType.INTEGER, 2022L), false), + partitionColumns.get(1), Domain.create(ValueSet.of(IntegerType.INTEGER, 11L), false), + partitionColumns.get(2), Domain.create(ValueSet.of(IntegerType.INTEGER, 12L), false))); + + List parts = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), predicate); + + assertEquals(parts.size(), 1); + + // month = 11 + TupleDomain predicate1 = TupleDomain.withColumnDomains(ImmutableMap.of( + partitionColumns.get(1), Domain.create(ValueSet.of(IntegerType.INTEGER, 11L), false))); + + List parts1 = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), predicate1); + + assertEquals(parts1.size(), 2); + + // test file skipping + List dataColumns = HudiMetadata.fromDataColumns(table.get().getDataColumns()); + List partitions = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), TupleDomain.all()); + HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(metaClient.getHadoopConf()); + HudiFileSkippingManager hudiFileSkippingManager = new HudiFileSkippingManager( + partitions, + HudiSessionProperties.getHoodieFilesystemViewSpillableDir(connectorSession), + engineContext, + metaClient, + getQueryType(table.get().getStorage().getStorageFormat().getInputFormat()), + Option.empty()); + // case1: no filter + assertEquals(hudiFileSkippingManager.listQueryFiles(TupleDomain.all()).entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 4); + // case2: where col0 > 99, should skip all files + assertEquals(hudiFileSkippingManager + .listQueryFiles(TupleDomain.withColumnDomains(ImmutableMap.of(dataColumns.get(7), Domain.create(ValueSet.ofRanges(Range.greaterThan(IntegerType.INTEGER, 99L)), false)))) + .entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 0); + // case3: where col0<=99 and col3 > 1001.0002 + assertEquals(hudiFileSkippingManager + .listQueryFiles(TupleDomain.withColumnDomains(ImmutableMap.of( + dataColumns.get(7), Domain.create(ValueSet.ofRanges(Range.lessThanOrEqual(IntegerType.INTEGER, 99L)), false), + dataColumns.get(10), Domain.create(ValueSet.ofRanges(Range.greaterThan(DoubleType.DOUBLE, 1002.0002d)), false)))) + .entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 2); + } + + @Test + public void testSchemaEvolutionWithColumnRenameDeleteAdd() + { + String testQuery = format("SELECT col0,col3_rename,col4_new FROM data_column_rename_drop_add where year=2022 and month=11"); + List expRows = new ArrayList<>(); + expRows.add("SELECT 99,cast(1001.0001 as double),null"); + String expResultsQuery = Joiner.on(" UNION ").join(expRows); + assertQuery(testQuery, expResultsQuery); + } + + @Test + public void testSchemaEvolutionWithColumnTypeChanged() + { + String testQuery = format("SELECT col0,col1,col2 FROM data_column_type_change where year=2022 and month=11"); + List expRows = new ArrayList<>(); + expRows.add("SELECT cast(99 as long),'1111111',cast(101.01 as double)"); + String expResultsQuery = Joiner.on(" UNION ").join(expRows); + assertQuery(testQuery, expResultsQuery); + } + + private HoodieTableQueryType getQueryType(String hudiInputFormat) + { + switch (hudiInputFormat) { + case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": + case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": + // mor rt table + return HoodieTableQueryType.SNAPSHOT; + case "org.apache.hudi.hadoop.HoodieParquetInputFormat": + case "com.uber.hoodie.hadoop.HoodieInputFormat": + // cow table/ mor ro table + return HoodieTableQueryType.READ_OPTIMIZED; + default: + throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", hudiInputFormat)); + } + } + + // should remove this function, once we bump hudi to 0.13.0. + // old hudi-presto-bundle has not include lz4 and caffeine jar which is used by schema evolution and data-skipping. + private void shouldRemoved() + { + XXHashFactory.fastestInstance(); + Caffeine.newBuilder(); + } +} diff --git a/presto-hudi/src/test/resources/hudi-skipping-schema-data.zip b/presto-hudi/src/test/resources/hudi-skipping-schema-data.zip new file mode 100644 index 0000000000000000000000000000000000000000..e605fcf1996a3553f00ca0252188944a20a82114 GIT binary patch literal 219435 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

H}b?}RNtALQD%AF}DL7S`?8 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�r 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 YP_ITur?*0H#flGfbenvD0~@;k1E|6aOaK4? literal 0 HcmV?d00001 From 03cde971176b4688990c1a28f3cb16c1f4aae9f7 Mon Sep 17 00:00:00 2001 From: xiarixiaoyao Date: Mon, 21 Nov 2022 21:00:22 +0800 Subject: [PATCH 3/6] fix comments --- .../presto/hudi/HudiFileSkippingManager.java | 26 +++++-------------- .../presto/hudi/HudiPartitionManager.java | 12 ++++----- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java index e216af6e7ada..c8d80bdb0f0a 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java @@ -31,7 +31,6 @@ import org.apache.hudi.common.model.BaseFile; import org.apache.hudi.common.model.FileSlice; import org.apache.hudi.common.model.HoodieTableQueryType; -import org.apache.hudi.common.model.HoodieTableType; import org.apache.hudi.common.table.HoodieTableMetaClient; import org.apache.hudi.common.table.timeline.HoodieInstant; import org.apache.hudi.common.table.timeline.HoodieTimeline; @@ -39,16 +38,15 @@ import org.apache.hudi.common.table.view.FileSystemViewStorageConfig; import org.apache.hudi.common.table.view.SyncableFileSystemView; import org.apache.hudi.common.util.Option; +import org.apache.hudi.common.util.collection.Pair; import org.apache.hudi.common.util.hash.ColumnIndexID; import org.apache.hudi.metadata.HoodieTableMetadata; import org.apache.hudi.metadata.HoodieTableMetadataUtil; import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; import java.util.stream.Collectors; import static com.facebook.presto.common.type.BigintType.BIGINT; @@ -82,7 +80,6 @@ public class HudiFileSkippingManager private final HoodieTableQueryType queryType; private final Option specifiedQueryInstant; private final HoodieTableMetaClient metaClient; - private final HoodieTableType tableType; private final HoodieTableMetadata metadataTable; protected transient volatile Map> allInputFileSlices; @@ -101,7 +98,6 @@ public HudiFileSkippingManager( this.specifiedQueryInstant = specifiedQueryInstant; this.metaClient = metaClient; HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(true).build(); - this.tableType = metaClient.getTableConfig().getTableType(); this.metadataTable = HoodieTableMetadata .create(engineContext, metadataConfig, metaClient.getBasePathV2().toString(), spillableDir, true); prepareAllInputFileSlices(partitions, engineContext, metadataConfig, spillableDir); @@ -120,21 +116,11 @@ private void prepareAllInputFileSlices(List partitions, HoodieEngineCont () -> metadataTable) .getFileSystemView(metaClient); Option queryInstant = specifiedQueryInstant.or(() -> latestInstant.map(HoodieInstant::getTimestamp)); - if (tableType.equals(HoodieTableType.MERGE_ON_READ) && queryType.equals(HoodieTableQueryType.SNAPSHOT)) { - allInputFileSlices = partitions.stream().collect(Collectors.toMap(Function.identity(), - partitionPath -> - queryInstant.map(instant -> - fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get()) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()))); - } - else { - allInputFileSlices = partitions.stream().collect(Collectors.toMap(Function.identity(), partition -> - queryInstant.map(instant -> - fileSystemView.getLatestFileSlicesBeforeOrOn(partition, instant, true)) - .orElse(fileSystemView.getLatestFileSlices(partition)) - .collect(Collectors.toList()))); - } + + allInputFileSlices = engineContext.mapToPair(partitions, partitionPath -> Pair.of(partitionPath, queryInstant.map(instant -> + fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get())) + .orElse(fileSystemView.getLatestFileSlices(partitionPath)) + .collect(Collectors.toList())), partitions.size()); long duration = System.currentTimeMillis() - startTime; diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java index 007bd88c526c..12b2adc79997 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java @@ -114,10 +114,10 @@ private List prunePartitionByMetaDataTable( } Configuration conf = metaClient.getHadoopConf(); HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(conf); - HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(isHudiMetadataTableEnabled(connectorSession)).build(); + HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(true).build(); // Load all the partition path from the basePath - List matchedPartitionPaths = FSUtils.getAllPartitionPaths(engineContext, metadataConfig, metaClient.getBasePathV2().toString()); + List allPartitions = FSUtils.getAllPartitionPaths(engineContext, metadataConfig, metaClient.getBasePathV2().toString()); // Extract partition columns predicate TupleDomain partitionPredicate = tupleDomain.transform(hudiColumnHandle -> { @@ -128,7 +128,7 @@ private List prunePartitionByMetaDataTable( }); if (partitionPredicate.isAll()) { - return matchedPartitionPaths; + return allPartitions; } if (partitionPredicate.isNone()) { @@ -137,9 +137,9 @@ private List prunePartitionByMetaDataTable( List partitionColumnHandles = fromPartitionColumns(partitionColumns); - List result = prunePartitions(partitionPredicate, partitionColumnHandles, getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), matchedPartitionPaths)); - log.info(format("Total partition size is %s, after partition prune size is %s.", matchedPartitionPaths.size(), result.size())); - return result; + List matchedPartitionPaths = prunePartitions(partitionPredicate, partitionColumnHandles, getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), allPartitions)); + log.info(format("Total partition size is %s, after partition prune size is %s.", allPartitions.size(), matchedPartitionPaths.size())); + return matchedPartitionPaths; } /** From f53b9bda69689aa1a940b665566d536871322ddf Mon Sep 17 00:00:00 2001 From: xiarixiaoyao Date: Mon, 5 Dec 2022 21:36:40 +0800 Subject: [PATCH 4/6] remove scheame evolution,and address comments --- presto-hudi/pom.xml | 12 - .../facebook/presto/hudi/HudiErrorCode.java | 1 - .../presto/hudi/HudiFileSkippingManager.java | 69 +++--- .../facebook/presto/hudi/HudiPageSource.java | 84 +------ .../presto/hudi/HudiPageSourceProvider.java | 12 +- .../presto/hudi/HudiPartitionManager.java | 20 ++ .../presto/hudi/HudiSchemaEvolutionUtils.java | 207 ------------------ .../com/facebook/presto/hudi/HudiSplit.java | 11 +- .../presto/hudi/HudiSplitManager.java | 22 +- .../presto/hudi/SchemaEvolutionContext.java | 57 ----- .../AbstractHudiDistributedQueryTestBase.java | 89 +------- .../presto/hudi/TestHudiSkipping.java | 141 ++++++++++++ .../hudi/TestHudiSkippingAndEvolution.java | 153 ------------- .../resources/hudi-skipping-schema-data.zip | Bin 219435 -> 114609 bytes 14 files changed, 211 insertions(+), 667 deletions(-) delete mode 100644 presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java delete mode 100644 presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java create mode 100644 presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java delete mode 100644 presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java diff --git a/presto-hudi/pom.xml b/presto-hudi/pom.xml index 8abdfb431c70..5cb9de91bcf9 100644 --- a/presto-hudi/pom.xml +++ b/presto-hudi/pom.xml @@ -229,17 +229,5 @@ lz4-java 1.8.0 - - - com.github.ben-manes.caffeine - caffeine - 2.9.1 - - - com.google.errorprone - error_prone_annotations - - - diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java index 7c74a61ae24e..3dcb576a8f7b 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiErrorCode.java @@ -30,7 +30,6 @@ public enum HudiErrorCode HUDI_FILESYSTEM_ERROR(0x40, EXTERNAL), HUDI_CANNOT_OPEN_SPLIT(0x41, EXTERNAL), HUDI_CURSOR_ERROR(0x42, EXTERNAL), - HUDI_SCHEMA_MISMATCH(0x43, EXTERNAL) /**/; private final ErrorCode errorCode; diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java index c8d80bdb0f0a..290a7ba118b6 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java @@ -37,7 +37,6 @@ import org.apache.hudi.common.table.view.FileSystemViewManager; import org.apache.hudi.common.table.view.FileSystemViewStorageConfig; import org.apache.hudi.common.table.view.SyncableFileSystemView; -import org.apache.hudi.common.util.Option; import org.apache.hudi.common.util.collection.Pair; import org.apache.hudi.common.util.hash.ColumnIndexID; import org.apache.hudi.metadata.HoodieTableMetadata; @@ -60,31 +59,18 @@ import static com.facebook.presto.common.type.Varchars.isVarcharType; import static com.facebook.presto.parquet.predicate.PredicateUtils.isStatisticsOverflow; import static java.lang.Float.floatToRawIntBits; +import static java.util.Objects.requireNonNull; -/** - * MetaDataTable based filesManager. - * list Hudi Table contents based on the - * - *

- * - * Support dataSkipping. - */ public class HudiFileSkippingManager { private static final Logger log = Logger.get(HudiFileSkippingManager.class); private final HoodieTableQueryType queryType; - private final Option specifiedQueryInstant; + private final Optional specifiedQueryInstant; private final HoodieTableMetaClient metaClient; private final HoodieTableMetadata metadataTable; - protected transient volatile Map> allInputFileSlices; - - private transient volatile SyncableFileSystemView fileSystemView; + protected final Map> allInputFileSlices; public HudiFileSkippingManager( List partitions, @@ -92,39 +78,50 @@ public HudiFileSkippingManager( HoodieEngineContext engineContext, HoodieTableMetaClient metaClient, HoodieTableQueryType queryType, - Option specifiedQueryInstant) + Optional specifiedQueryInstant) { - this.queryType = queryType; - this.specifiedQueryInstant = specifiedQueryInstant; - this.metaClient = metaClient; + requireNonNull(partitions, "partitions is null"); + requireNonNull(spillableDir, "spillableDir is null"); + requireNonNull(engineContext, "engineContext is null"); + this.queryType = requireNonNull(queryType, "queryType is null"); + this.specifiedQueryInstant = requireNonNull(specifiedQueryInstant, "specifiedQueryInstant is null"); + this.metaClient = requireNonNull(metaClient, "metaClient is null"); + HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(true).build(); this.metadataTable = HoodieTableMetadata .create(engineContext, metadataConfig, metaClient.getBasePathV2().toString(), spillableDir, true); - prepareAllInputFileSlices(partitions, engineContext, metadataConfig, spillableDir); + this.allInputFileSlices = prepareAllInputFileSlices(partitions, engineContext, metadataConfig, spillableDir); } - private void prepareAllInputFileSlices(List partitions, HoodieEngineContext engineContext, HoodieMetadataConfig metadataConfig, String spillableDir) + private Map> prepareAllInputFileSlices(List partitions, HoodieEngineContext engineContext, HoodieMetadataConfig metadataConfig, String spillableDir) { long startTime = System.currentTimeMillis(); HoodieTimeline activeTimeline = metaClient.reloadActiveTimeline(); - Option latestInstant = activeTimeline.lastInstant(); + Optional latestInstant = activeTimeline.lastInstant().toJavaOptional(); // build system view. - fileSystemView = FileSystemViewManager + SyncableFileSystemView fileSystemView = FileSystemViewManager .createViewManager(engineContext, metadataConfig, FileSystemViewStorageConfig.newBuilder().withBaseStoreDir(spillableDir).build(), HoodieCommonConfig.newBuilder().build(), () -> metadataTable) .getFileSystemView(metaClient); - Option queryInstant = specifiedQueryInstant.or(() -> latestInstant.map(HoodieInstant::getTimestamp)); + Optional queryInstant = specifiedQueryInstant.isPresent() ? specifiedQueryInstant : latestInstant.map(HoodieInstant::getTimestamp); - allInputFileSlices = engineContext.mapToPair(partitions, partitionPath -> Pair.of(partitionPath, queryInstant.map(instant -> - fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get())) - .orElse(fileSystemView.getLatestFileSlices(partitionPath)) - .collect(Collectors.toList())), partitions.size()); + Map> allInputFileSlices = engineContext + .mapToPair(partitions, partitionPath -> Pair.of(partitionPath, getLatestFileSlices(partitionPath, fileSystemView, queryInstant)), partitions.size()); long duration = System.currentTimeMillis() - startTime; - log.info(String.format("prepare query files for table %s, spent: %d ms", metaClient.getTableConfig().getTableName(), duration)); + log.info("prepare query files for table %s, spent: %d ms", metaClient.getTableConfig().getTableName(), duration); + return allInputFileSlices; + } + + private List getLatestFileSlices(String partitionPath, SyncableFileSystemView fileSystemView, Optional queryInstant) + { + return queryInstant.map(instant -> + fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get())) + .orElse(fileSystemView.getLatestFileSlices(partitionPath)) + .collect(Collectors.toList()); } public Map> listQueryFiles(TupleDomain tupleDomain) @@ -138,14 +135,14 @@ public Map> listQueryFiles(TupleDomain tup } catch (Exception e) { // Should not throw exception, just log this Exception. - log.warn(String.format("failed to do data skipping for table: %s, fallback to all files scan", metaClient.getBasePathV2().toString()), e); + log.warn(e, "failed to do data skipping for table: %s, fallback to all files scan", metaClient.getBasePathV2()); candidateFileSlices = allInputFileSlices; } if (log.isDebugEnabled()) { - int candidateFileSize = candidateFileSlices.values().stream().map(List::size).reduce(0, Integer::sum); - int totalFiles = allInputFileSlices.values().stream().map(List::size).reduce(0, Integer::sum); + int candidateFileSize = candidateFileSlices.values().stream().mapToInt(List::size).sum(); + int totalFiles = allInputFileSlices.values().stream().mapToInt(List::size).sum(); double skippingPercent = totalFiles == 0 ? 0.0d : (totalFiles - candidateFileSize) / (totalFiles + 0.0d); - log.debug(String.format("Total files: %s; candidate files after data skipping: %s; skipping percent %s", totalFiles, candidateFileSize, skippingPercent)); + log.debug("Total files: %s; candidate files after data skipping: %s; skipping percent %s", totalFiles, candidateFileSize, skippingPercent); } return candidateFileSlices; } @@ -291,7 +288,7 @@ private static Domain getDomain(String colName, Type type, Object minimum, Objec return Domain.create(ValueSet.all(type), hasNullValue); } catch (Exception e) { - log.warn(String.format("failed to create Domain for column: %s which type is: %s", colName, type.toString())); + log.warn("failed to create Domain for column: %s which type is: %s", colName, type.toString()); return Domain.create(ValueSet.all(type), hasNullValue); } } diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java index b89bc929d5a5..373ecfdf703b 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSource.java @@ -17,8 +17,6 @@ import com.facebook.presto.common.Page; import com.facebook.presto.common.Utils; import com.facebook.presto.common.block.Block; -import com.facebook.presto.common.block.LazyBlock; -import com.facebook.presto.common.block.LazyBlockLoader; import com.facebook.presto.common.block.RunLengthEncodedBlock; import com.facebook.presto.common.type.DecimalType; import com.facebook.presto.common.type.Decimals; @@ -27,11 +25,8 @@ import com.facebook.presto.common.type.TypeManager; import com.facebook.presto.common.type.VarbinaryType; import com.facebook.presto.common.type.VarcharType; -import com.facebook.presto.hive.HiveCoercer; -import com.facebook.presto.hive.HiveType; import com.facebook.presto.spi.ConnectorPageSource; import com.facebook.presto.spi.PrestoException; -import com.google.common.collect.ImmutableList; import java.io.IOException; import java.io.UncheckedIOException; @@ -39,11 +34,8 @@ import java.math.BigInteger; import java.time.LocalDate; import java.time.format.DateTimeFormatter; -import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.function.Function; import static com.facebook.presto.common.type.BigintType.BIGINT; import static com.facebook.presto.common.type.BooleanType.BOOLEAN; @@ -74,25 +66,20 @@ public class HudiPageSource private final Block[] prefilledBlocks; private final int[] delegateIndexes; private final ConnectorPageSource delegate; - private final List columns; - private final List>> coercers; public HudiPageSource( List columns, Map partitionKeys, ConnectorPageSource delegate, TimeZoneKey timeZoneKey, - TypeManager typeManager, - Map columnCoercions) + TypeManager typeManager) { int size = requireNonNull(columns, "columns is null").size(); requireNonNull(partitionKeys, "partitionKeys is null"); - this.columns = requireNonNull(columns, "columns is null"); this.delegate = requireNonNull(delegate, "delegate is null"); prefilledBlocks = new Block[size]; delegateIndexes = new int[size]; - ImmutableList.Builder>> coercersBuilder = ImmutableList.builder(); int outputIndex = 0; int delegateIndex = 0; @@ -111,19 +98,8 @@ public HudiPageSource( delegateIndex++; } outputIndex++; - - // create type convert - if (!columnCoercions.isEmpty() && - columnCoercions.containsKey(column.getName()) && - !column.getHiveType().equals(columnCoercions.get(column.getName()))) { - coercersBuilder.add(Optional.of(HiveCoercer.createCoercer(typeManager, columnCoercions.get(column.getName()), column.getHiveType()))); - } - else { - coercersBuilder.add(Optional.empty()); - } } this.hasPrefilledBlocks = hasPrefilledBlocks; - this.coercers = coercersBuilder.build(); } @Override @@ -159,7 +135,7 @@ public Page getNextPage() return null; } if (!hasPrefilledBlocks) { - return reAlignDataPage(dataPage, columns, coercers); + return dataPage; } int batchSize = dataPage.getPositionCount(); Block[] blocks = new Block[prefilledBlocks.length]; @@ -168,9 +144,7 @@ public Page getNextPage() blocks[i] = new RunLengthEncodedBlock(prefilledBlocks[i], batchSize); } else { - Block block = dataPage.getBlock(delegateIndexes[i]); - Optional> coercer = coercers.isEmpty() ? Optional.empty() : coercers.get(i); - blocks[i] = reAlignDataBlock(batchSize, block, coercer); + blocks[i] = dataPage.getBlock(delegateIndexes[i]); } } return new Page(batchSize, blocks); @@ -277,56 +251,4 @@ static Object deserializePartitionValue(Type type, String valueString, String na // Hudi tables don't partition by non-primitive-type columns. throw new PrestoException(NOT_SUPPORTED, "Invalid partition type " + type); } - - private static Page reAlignDataPage( - Page dataPage, - List columns, - List>> coercers) - { - int batchSize = dataPage.getPositionCount(); - List blocks = new ArrayList<>(); - for (int fieldId = 0; fieldId < columns.size(); fieldId++) { - Block block = dataPage.getBlock(fieldId); - Optional> coercer = coercers.isEmpty() ? Optional.empty() : coercers.get(fieldId); - if (coercer.isPresent()) { - block = new LazyBlock(batchSize, new CoercionLazyBlockLoader(block, coercer.get())); - } - blocks.add(block); - } - return new Page(batchSize, blocks.toArray(new Block[0])); - } - - private static Block reAlignDataBlock(int batchSize, Block dataBlock, Optional> coercer) - { - if (coercer.isPresent()) { - return new LazyBlock(batchSize, new CoercionLazyBlockLoader(dataBlock, coercer.get())); - } - return dataBlock; - } - - private static final class CoercionLazyBlockLoader - implements LazyBlockLoader - { - private final Function coercer; - private Block block; - - public CoercionLazyBlockLoader(Block block, Function coercer) - { - this.block = requireNonNull(block, "block is null"); - this.coercer = requireNonNull(coercer, "coercer is null"); - } - - @Override - public void load(LazyBlock lazyBlock) - { - if (block == null) { - return; - } - - lazyBlock.setBlock(coercer.apply(block.getLoadedBlock())); - - // clear reference to loader to free resources, since load was successful - block = null; - } - } } diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java index 99af0252d3f6..8ec5ed341a83 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPageSourceProvider.java @@ -19,7 +19,6 @@ import com.facebook.presto.hive.FileFormatDataSourceStats; import com.facebook.presto.hive.HdfsContext; import com.facebook.presto.hive.HdfsEnvironment; -import com.facebook.presto.hive.HiveType; import com.facebook.presto.hive.metastore.Column; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ConnectorPageSource; @@ -32,16 +31,13 @@ import com.facebook.presto.spi.SplitContext; import com.facebook.presto.spi.connector.ConnectorPageSourceProvider; import com.facebook.presto.spi.connector.ConnectorTransactionHandle; -import com.google.common.collect.ImmutableMap; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; -import org.apache.hudi.common.util.collection.Pair; import javax.inject.Inject; import java.time.ZoneId; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Properties; @@ -87,7 +83,6 @@ public ConnectorPageSource createPageSource( List dataColumns = hudiColumnHandles.stream().filter(HudiColumnHandle::isRegularColumn).collect(toList()); final ConnectorPageSource dataColumnPageSource; - Map columnCoercions = ImmutableMap.of(); if (tableType == HudiTableType.COW) { HudiFile baseFile = hudiSplit.getBaseFile().orElseThrow(() -> new PrestoException(HUDI_CANNOT_OPEN_SPLIT, "Split without base file is invalid")); @@ -99,10 +94,6 @@ public ConnectorPageSource createPageSource( baseFile.getPath(), false), path); - // embed schema evolution. - Pair, Map> pair = HudiSchemaEvolutionUtils.doEvolution(hudiSplit, dataColumns, layout.getTable().getPath(), configuration); - dataColumns = pair.getLeft(); - columnCoercions = pair.getRight(); dataColumnPageSource = createParquetPageSource( typeManager, hdfsEnvironment, @@ -147,8 +138,7 @@ else if (tableType == HudiTableType.MOR) { hudiSplit.getPartition().getKeyValues(), dataColumnPageSource, session.getSqlFunctionProperties().getTimeZoneKey(), - typeManager, - columnCoercions); + typeManager); } private static List toMetastoreColumns(List hudiColumnHandles) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java index 12b2adc79997..e60850123404 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java @@ -24,6 +24,7 @@ import com.facebook.presto.hive.metastore.Column; import com.facebook.presto.hive.metastore.ExtendedHiveMetastore; import com.facebook.presto.hive.metastore.MetastoreContext; +import com.facebook.presto.hive.metastore.MetastoreUtil; import com.facebook.presto.hive.metastore.Table; import com.facebook.presto.spi.ColumnHandle; import com.facebook.presto.spi.ConnectorSession; @@ -56,6 +57,7 @@ import static com.facebook.presto.hudi.HudiMetadata.fromPartitionColumns; import static com.facebook.presto.hudi.HudiMetadata.toMetastoreContext; import static com.facebook.presto.hudi.HudiSessionProperties.isHudiMetadataTableEnabled; +import static com.google.common.base.Preconditions.checkArgument; import static io.airlift.slice.Slices.utf8Slice; import static java.lang.Double.doubleToRawLongBits; import static java.lang.Double.parseDouble; @@ -183,6 +185,24 @@ public static Map> getPartitions(List partit return result; } + public static List extractPartitionValues(String partitionName, Optional> partitionColumnNames) + { + boolean hiveStylePartition = HIVE_PARTITION_NAME_PATTERN.matcher(partitionName).matches(); + if (!hiveStylePartition) { + if (!partitionColumnNames.isPresent() || partitionColumnNames.get().size() == 1) { + return ImmutableList.of(partitionName); + } + else { + String[] partitionValues = partitionName.split(Path.SEPARATOR); + checkArgument(partitionValues.length == partitionColumnNames.get().size(), + "Invalid partition spec: {partitionName: %s, partitionColumnNames: %s}", partitionName, partitionColumnNames.get()); + return Arrays.asList(partitionValues); + } + } + + return MetastoreUtil.extractPartitionValues(partitionName, partitionColumnNames); + } + private List prunePartitions( TupleDomain partitionPredicate, List partitionColumnHandles, diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java deleted file mode 100644 index 76c20f08f32d..000000000000 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSchemaEvolutionUtils.java +++ /dev/null @@ -1,207 +0,0 @@ -/* - * Licensed 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 com.facebook.presto.hudi; - -import com.facebook.airlift.log.Logger; -import com.facebook.presto.hive.HiveType; -import com.facebook.presto.spi.PrestoException; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hive.serde2.typeinfo.DecimalTypeInfo; -import org.apache.hadoop.hive.serde2.typeinfo.ListTypeInfo; -import org.apache.hadoop.hive.serde2.typeinfo.MapTypeInfo; -import org.apache.hadoop.hive.serde2.typeinfo.StructTypeInfo; -import org.apache.hadoop.hive.serde2.typeinfo.TypeInfo; -import org.apache.hudi.common.fs.FSUtils; -import org.apache.hudi.common.model.HoodieTableType; -import org.apache.hudi.common.table.HoodieTableMetaClient; -import org.apache.hudi.common.table.TableSchemaResolver; -import org.apache.hudi.common.table.timeline.HoodieInstant; -import org.apache.hudi.common.table.timeline.HoodieTimeline; -import org.apache.hudi.common.util.InternalSchemaCache; -import org.apache.hudi.common.util.collection.Pair; -import org.apache.hudi.internal.schema.InternalSchema; -import org.apache.hudi.internal.schema.Type; -import org.apache.hudi.internal.schema.Types; -import org.apache.hudi.internal.schema.action.InternalSchemaMerger; -import org.apache.hudi.internal.schema.utils.InternalSchemaUtils; -import org.apache.hudi.internal.schema.utils.SerDeHelper; - -import java.util.ArrayList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.Optional; -import java.util.stream.Collectors; - -import static com.facebook.presto.hive.HiveType.toHiveType; -import static java.lang.String.format; - -public final class HudiSchemaEvolutionUtils -{ - private static final Logger log = Logger.get(HudiSchemaEvolutionUtils.class); - - private HudiSchemaEvolutionUtils() - { - } - - public static SchemaEvolutionContext createSchemaEvolutionContext(Optional metaClientOpt) - { - // no need to do schema evolution for mor table, since hudi kernel will do it. - if (!metaClientOpt.isPresent() || metaClientOpt.get().getTableType() == HoodieTableType.MERGE_ON_READ) { - return new SchemaEvolutionContext("", ""); - } - - try { - TableSchemaResolver schemaUtil = new TableSchemaResolver(metaClientOpt.get()); - String internalSchema = schemaUtil.getTableInternalSchemaFromCommitMetadata().map(SerDeHelper::toJson).orElse(""); - HoodieTimeline hoodieTimeline = metaClientOpt.get().getCommitsAndCompactionTimeline().filterCompletedInstants(); - String validCommits = hoodieTimeline.getInstants().map(HoodieInstant::getFileName).collect(Collectors.joining(",")); - return new SchemaEvolutionContext(internalSchema, validCommits); - } - catch (Exception e) { - log.warn(String.format("failed to get internal Schema from hudi tableļ¼š%s , fallback to original logical", metaClientOpt.get().getBasePathV2()), e); - } - return new SchemaEvolutionContext("", ""); - } - - public static Pair, Map> doEvolution(HudiSplit hudiSplit, List oldColumnHandle, String tablePath, Configuration hadoopConf) - { - SchemaEvolutionContext schemaEvolutionContext = hudiSplit.getSchemaEvolutionContext(); - InternalSchema internalSchema = SerDeHelper.fromJson(schemaEvolutionContext.getLatestSchema()).orElse(InternalSchema.getEmptyInternalSchema()); - if (internalSchema.isEmptySchema() || !hudiSplit.getBaseFile().isPresent()) { - return Pair.of(oldColumnHandle, ImmutableMap.of()); - } - // prune internalSchema: columns prune - InternalSchema prunedSchema = InternalSchemaUtils.pruneInternalSchema(internalSchema, oldColumnHandle.stream().map(HudiColumnHandle::getName).collect(Collectors.toList())); - - Path baseFilePath = new Path(hudiSplit.getBaseFile().get().getPath()); - String commitTime = FSUtils.getCommitTime(baseFilePath.getName()); - InternalSchema fileSchema = InternalSchemaCache.getInternalSchemaByVersionId(Long.parseUnsignedLong(commitTime), tablePath, hadoopConf, schemaEvolutionContext.getValidCommitFiles()); - log.debug(String.format(Locale.ENGLISH, "File schema from hudi base file is %s", fileSchema)); - - InternalSchema mergedSchema = new InternalSchemaMerger(fileSchema, prunedSchema, true, true).mergeSchema(); - - if (mergedSchema.columns().size() != oldColumnHandle.size()) { - throw new PrestoException(HudiErrorCode.HUDI_SCHEMA_MISMATCH, format("Found mismatch schema, pls sync latest hudi meta to hive")); - } - - ImmutableList.Builder builder = ImmutableList.builder(); - for (int i = 0; i < oldColumnHandle.size(); i++) { - HiveType hiveType = constructPrestoTypeFromType(mergedSchema.columns().get(i).type()); - HudiColumnHandle hudiColumnHandle = oldColumnHandle.get(i); - builder.add(new HudiColumnHandle(hudiColumnHandle.getId(), mergedSchema.columns().get(i).name(), hiveType, hudiColumnHandle.getComment(), hudiColumnHandle.getColumnType())); - } - return Pair.of(builder.build(), collectTypeChangedCols(prunedSchema, mergedSchema)); - } - - /** - * Collect all type changed columns - * - * @param schema schema hold latest column type. - * @param querySchema schema hold old column type. - * @return a map: (columnName -> oldColumnType) - */ - private static Map collectTypeChangedCols(InternalSchema schema, InternalSchema querySchema) - { - return InternalSchemaUtils - .collectTypeChangedCols(schema, querySchema) - .entrySet() - .stream() - .collect(Collectors.toMap(e -> querySchema.columns().get(e.getKey()).name(), e -> constructPrestoTypeFromType(e.getValue().getRight()))); - } - - private static HiveType constructPrestoTypeFromType(Type type) - { - switch (type.typeId()) { - case BOOLEAN: - return HiveType.HIVE_BOOLEAN; - case INT: - return HiveType.HIVE_INT; - case LONG: - return HiveType.HIVE_LONG; - case FLOAT: - return HiveType.HIVE_FLOAT; - case DOUBLE: - return HiveType.HIVE_DOUBLE; - case DATE: - return HiveType.HIVE_DATE; - case TIMESTAMP: - return HiveType.HIVE_TIMESTAMP; - case STRING: - return HiveType.HIVE_STRING; - case UUID: - return HiveType.HIVE_STRING; - case FIXED: - return HiveType.HIVE_BINARY; - case BINARY: - return HiveType.HIVE_BINARY; - case DECIMAL: - Types.DecimalType decimal = (Types.DecimalType) type; - DecimalTypeInfo decimalTypeInfo = new DecimalTypeInfo(); - decimalTypeInfo.setPrecision(decimal.precision()); - decimalTypeInfo.setScale(decimal.scale()); - return toHiveType(decimalTypeInfo); - case RECORD: - return getPrestoTypeFromRecord(type); - case ARRAY: - return getPrestoTypeFromArray(type); - case MAP: - return getPrestoTypeFromMap(type); - case TIME: - throw new UnsupportedOperationException(String.format("Cannot convert %s type to Presto", type)); - default: - throw new UnsupportedOperationException(String.format("Cannot convert unknown type: %s to Presto", type)); - } - } - - private static HiveType getPrestoTypeFromRecord(Type type) - { - Types.RecordType record = (Types.RecordType) type; - List fields = record.fields(); - ArrayList fieldNames = new ArrayList<>(); - ArrayList fieldTypeInfos = new ArrayList<>(); - for (Types.Field f : fields) { - fieldNames.add(f.name()); - fieldTypeInfos.add(constructPrestoTypeFromType(f.type()).getTypeInfo()); - } - StructTypeInfo structTypeInfo = new StructTypeInfo(); - structTypeInfo.setAllStructFieldNames(fieldNames); - structTypeInfo.setAllStructFieldTypeInfos(fieldTypeInfos); - return toHiveType(structTypeInfo); - } - - private static HiveType getPrestoTypeFromArray(Type type) - { - Types.ArrayType array = (Types.ArrayType) type; - HiveType elementType = constructPrestoTypeFromType(array.elementType()); - ListTypeInfo listTypeInfo = new ListTypeInfo(); - listTypeInfo.setListElementTypeInfo(elementType.getTypeInfo()); - return toHiveType(listTypeInfo); - } - - private static HiveType getPrestoTypeFromMap(Type type) - { - Types.MapType map = (Types.MapType) type; - HiveType keyDataType = constructPrestoTypeFromType(map.keyType()); - HiveType valueDataType = constructPrestoTypeFromType(map.valueType()); - MapTypeInfo mapTypeInfo = new MapTypeInfo(); - mapTypeInfo.setMapKeyTypeInfo(keyDataType.getTypeInfo()); - mapTypeInfo.setMapValueTypeInfo(valueDataType.getTypeInfo()); - return toHiveType(mapTypeInfo); - } -} diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java index d84396d26b09..c5a6ef4398e7 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplit.java @@ -41,7 +41,6 @@ public class HudiSplit private final List addresses; private final NodeSelectionStrategy nodeSelectionStrategy; private final SplitWeight splitWeight; - private final SchemaEvolutionContext schemaEvolutionContext; @JsonCreator public HudiSplit( @@ -52,8 +51,7 @@ public HudiSplit( @JsonProperty("logFiles") List logFiles, @JsonProperty("addresses") List addresses, @JsonProperty("nodeSelectionStrategy") NodeSelectionStrategy nodeSelectionStrategy, - @JsonProperty("splitWeight") SplitWeight splitWeight, - @JsonProperty("schemaEvolutionContext") SchemaEvolutionContext schemaEvolutionContext) + @JsonProperty("splitWeight") SplitWeight splitWeight) { this.table = requireNonNull(table, "table is null"); this.instantTime = requireNonNull(instantTime, "instantTime is null"); @@ -63,7 +61,6 @@ public HudiSplit( this.addresses = requireNonNull(addresses, "addresses is null"); this.nodeSelectionStrategy = requireNonNull(nodeSelectionStrategy, "nodeSelectionStrategy is null"); this.splitWeight = requireNonNull(splitWeight, "splitWeight is null"); - this.schemaEvolutionContext = requireNonNull(schemaEvolutionContext, "schemaEvolutionContext is null"); } @JsonProperty @@ -102,12 +99,6 @@ public List getAddresses() return addresses; } - @JsonProperty - public SchemaEvolutionContext getSchemaEvolutionContext() - { - return schemaEvolutionContext; - } - @JsonProperty @Override public NodeSelectionStrategy getNodeSelectionStrategy() diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java index ee58d467ff94..4b52b8be5f21 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java @@ -17,7 +17,6 @@ import com.facebook.presto.hive.HdfsContext; import com.facebook.presto.hive.HdfsEnvironment; import com.facebook.presto.hive.filesystem.ExtendedFileSystem; -import com.facebook.presto.hive.metastore.Column; import com.facebook.presto.hive.metastore.ExtendedHiveMetastore; import com.facebook.presto.hive.metastore.MetastoreContext; import com.facebook.presto.hive.metastore.Partition; @@ -48,7 +47,6 @@ import org.apache.hudi.common.table.timeline.HoodieInstant; import org.apache.hudi.common.table.timeline.HoodieTimeline; import org.apache.hudi.common.table.view.HoodieTableFileSystemView; -import org.apache.hudi.common.util.Option; import javax.inject.Inject; @@ -127,8 +125,6 @@ public ConnectorSplitSource getSplits( // no completed instant for current table return new FixedSplitSource(ImmutableList.of()); } - // prepare schema evolution - SchemaEvolutionContext schemaEvolutionContext = HudiSchemaEvolutionUtils.createSchemaEvolutionContext(Optional.of(metaClient)); // prepare splits HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(conf); // if metadata table enabled, support dataskipping @@ -142,13 +138,13 @@ public ConnectorSplitSource getSplits( engineContext, metaClient, getQueryType(hiveTableOpt.get().getStorage().getStorageFormat().getInputFormat()), - Option.empty()); + Optional.empty()); ImmutableList.Builder splitsBuilder = ImmutableList.builder(); Map hudiPartitionMap = getHudiPartitions(hiveTableOpt.get(), layout, partitions); hudiFileSkippingManager.listQueryFiles(layout.getTupleDomain()) .entrySet() .stream() - .flatMap(entry -> entry.getValue().stream().map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartitionMap.get(entry.getKey()), splitWeightProvider, schemaEvolutionContext))) + .flatMap(entry -> entry.getValue().stream().map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartitionMap.get(entry.getKey()), splitWeightProvider))) .filter(Optional::isPresent) .map(Optional::get) .forEach(splitsBuilder::add); @@ -167,7 +163,7 @@ public ConnectorSplitSource getSplits( Path partitionPath = new Path(hudiPartition.getStorage().getLocation()); String relativePartitionPath = FSUtils.getRelativePartitionPath(tablePath, partitionPath); fsView.getLatestFileSlicesBeforeOrOn(relativePartitionPath, timestamp, false) - .map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartition, splitWeightProvider, schemaEvolutionContext)) + .map(fileSlice -> createHudiSplit(table, fileSlice, timestamp, hudiPartition, splitWeightProvider)) .filter(Optional::isPresent) .map(Optional::get) .forEach(builder::add); @@ -196,8 +192,7 @@ private Optional createHudiSplit( FileSlice slice, String timestamp, HudiPartition partition, - HudiSplitWeightProvider splitWeightProvider, - SchemaEvolutionContext schemaEvolutionContext) + HudiSplitWeightProvider splitWeightProvider) { HudiFile hudiFile = slice.getBaseFile().map(f -> new HudiFile(f.getPath(), 0, f.getFileLen())).orElse(null); if (null == hudiFile && table.getTableType() == HudiTableType.COW) { @@ -217,23 +212,22 @@ private Optional createHudiSplit( logFiles, ImmutableList.of(), NodeSelectionStrategy.NO_PREFERENCE, - splitWeightProvider.calculateSplitWeight(sizeInBytes), - schemaEvolutionContext)); + splitWeightProvider.calculateSplitWeight(sizeInBytes))); } private Map getHudiPartitions(Table table, HudiTableLayoutHandle tableLayout, List partitions) { - List partitionColumns = table.getPartitionColumns(); + List partitionColumnNames = table.getPartitionColumns().stream().map(f -> f.getName()).collect(Collectors.toList()); Map> partitionMap = HudiPartitionManager - .getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), partitions); + .getPartitions(partitionColumnNames, partitions); if (partitions.size() == 1 && partitions.get(0).isEmpty()) { // non-partitioned return ImmutableMap.of(partitions.get(0), new HudiPartition(partitions.get(0), ImmutableList.of(), ImmutableMap.of(), table.getStorage(), tableLayout.getDataColumns())); } ImmutableMap.Builder builder = ImmutableMap.builder(); partitionMap.entrySet().stream().map(entry -> { - List partitionValues = extractPartitionValues(entry.getKey()); + List partitionValues = HudiPartitionManager.extractPartitionValues(entry.getKey(), Optional.of(partitionColumnNames)); return new HudiPartition(entry.getKey(), partitionValues, entry.getValue(), table.getStorage(), fromDataColumns(table.getDataColumns())); }).forEach(p -> builder.put(p.getName(), p)); return builder.build(); diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java deleted file mode 100644 index 97245c4d40b9..000000000000 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/SchemaEvolutionContext.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Licensed 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 com.facebook.presto.hudi; - -import com.fasterxml.jackson.annotation.JsonCreator; -import com.fasterxml.jackson.annotation.JsonProperty; - -import static com.google.common.base.MoreObjects.toStringHelper; -import static java.util.Objects.requireNonNull; - -public class SchemaEvolutionContext -{ - private final String latestSchema; - private final String validCommitFiles; - - @JsonCreator - public SchemaEvolutionContext( - @JsonProperty("latestSchema") String latestSchema, - @JsonProperty("validCommitFiles") String validCommitFiles) - { - this.latestSchema = requireNonNull(latestSchema, "latestSchema is null"); - this.validCommitFiles = requireNonNull(validCommitFiles, "validCommitFiles is null"); - } - - @JsonProperty - public String getLatestSchema() - { - return latestSchema; - } - - @JsonProperty - public String getValidCommitFiles() - { - return validCommitFiles; - } - - @Override - public String toString() - { - return toStringHelper(this) - .add("latestSchema", latestSchema) - .add("validCommitFiles", validCommitFiles) - .toString(); - } -} diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java index 7d04ab153289..c61b14cc8c6f 100644 --- a/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java +++ b/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java @@ -133,82 +133,6 @@ public abstract class AbstractHudiDistributedQueryTestBase column("col6", HIVE_BOOLEAN), column("col7", HIVE_BINARY)); - // simulate drop column + rename column + add column. - // spark.sql( - // """create table data_column_rename_drop_add - // |(id int, comb int, col0 int, col1 bigint, col2 float, col3 double, - // | col4 string, col5 date, col6 boolean, col7 binary, year int, month int, day int) - // | using hudi - // | partitioned by (year,month,day) - // | options( - // | type='cow', primaryKey='id', preCombineField='comb', - // | 'hoodie.metadata.enable'='true')""".stripMargin) - // - // spark.sql( - // s""" - // | insert into data_column_rename_drop_add values - // | (1,1,99,1111111,101.01,1001.0001,'x000001','2021-12-25',true,'a01',2022, 11, 12), - // | (2,2,99,1111111,102.02,1002.0002,'x000002','2021-12-25',true,'a02',2022, 10, 30), - // | (3,3,99,1111111,103.03,1003.0003,'x000003','2021-12-25',false,'a03',2021, 10, 11), - // | (4,4,99,1111111,104.04,1004.0004,'x000004','2021-12-26',true,'a04',2021, 11, 12) - // |""".stripMargin) - // - // spark.sql("set hoodie.schema.evolution.enable=true") - // - // spark.sql("alter table data_column_rename_drop_add drop column col1") - // - // spark.sql("alter table data_column_rename_drop_add rename column col3 to col3_rename") - // - // spark.sql("alter table data_column_rename_drop_add add columns(col4_new string comment 'add ext1' after col4)") - public static final List DATA_COLUMNS1 = ImmutableList.of( - column("id", HIVE_INT), - column("comb", HIVE_INT), - column("col0", HIVE_INT), // drop col1 - column("col2", HIVE_FLOAT), - column("col3_rename", HIVE_DOUBLE), // rename col3 to col3_rename - column("col4", HIVE_STRING), - column("col4_new", HIVE_STRING), // new add column - column("col5", HIVE_DATE), - column("col6", HIVE_BOOLEAN), - column("col7", HIVE_BINARY)); - - // simulate change column type. - // spark.sql( - // """create table data_column_type_change - // |(id int, comb int, col0 int, col1 bigint, col2 float, col3 double, - // | col4 string, col5 date, col6 boolean, col7 binary, year int, month int, day int) - // | using hudi - // | partitioned by (year,month,day) - // | options( - // | type='cow', primaryKey='id', preCombineField='comb', - // | 'hoodie.metadata.enable'='true')""".stripMargin) - // - // spark.sql( - // s""" - // | insert into data_column_type_change values - // | (1,1,99,1111111,101.01,1001.0001,'1','2021-12-25',true,'a01',2022, 11, 12), - // | (2,2,99,1111111,102.02,1002.0002,'2','2021-12-25',true,'a02',2022, 10, 30), - // | (3,3,99,1111111,103.03,1003.0003,'3','2021-12-25',false,'a03',2021, 10, 11), - // | (4,4,99,1111111,104.04,1004.0004,'4','2021-12-26',true,'a04',2021, 11, 12) - // |""".stripMargin) - // - // spark.sql("set hoodie.schema.evolution.enable=true") - // - // spark.sql("alter table data_column_type_change alter column col0 type long") - // spark.sql("alter table data_column_type_change alter column col2 type double") - // spark.sql("alter table data_column_type_change alter column col1 type string") - public static final List DATA_COLUMNS2 = ImmutableList.of( - column("id", HIVE_INT), - column("comb", HIVE_INT), - column("col0", HIVE_LONG), // int -> long - column("col1", HIVE_STRING), // long -> string - column("col2", HIVE_DOUBLE), // float -> double - column("col3", HIVE_DOUBLE), - column("col4", HIVE_STRING), - column("col5", HIVE_DATE), - column("col6", HIVE_BOOLEAN), - column("col7", HIVE_BINARY)); - public static final List PARTITION_COLUMNS = ImmutableList.of(column("year", HIVE_INT), column("month", HIVE_INT), column("day", HIVE_INT)); public static final List HUDI_META_COLUMNS = ImmutableList.of( column("_hoodie_commit_time", HiveType.HIVE_STRING), @@ -222,8 +146,7 @@ public abstract class AbstractHudiDistributedQueryTestBase * used to test dataskipping/partition prune. */ protected static final String HUDI_SKIPPING_TABLE = "data_partition_prune"; - protected static final String HUDI_COLUMN_CHANGE_TABLE = "data_column_rename_drop_add"; - protected static final String HUDI_COLUMN_TYPE_CHANGE_TABLE = "data_column_type_change"; + protected static final String HUDI_SKIPPING_TABLE_NON_HIVE_STYLE = "data_partition_prune_non_hive_style_partition"; protected static ConnectorSession connectorSession; @Override @@ -240,8 +163,7 @@ public void deleteTestHudiTables() if (queryRunner != null) { // Remove the test hudi tables from HMS metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_SKIPPING_TABLE, false); - metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_COLUMN_CHANGE_TABLE, false); - metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_COLUMN_TYPE_CHANGE_TABLE, false); + metastore.dropTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_SKIPPING_TABLE_NON_HIVE_STYLE, false); } } @@ -290,11 +212,8 @@ private static DistributedQueryRunner createHudiQueryRunner(Map // Create the test hudi tables for dataSkipping/partition prune in HMS registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_SKIPPING_TABLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS.stream()).collect(Collectors.toList())); - // Create the test hudi table for column rename/drop/add in HMS - registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_COLUMN_CHANGE_TABLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS1.stream()).collect(Collectors.toList())); - - // create the test hudi table for column type change in HMS - registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_COLUMN_TYPE_CHANGE_TABLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS2.stream()).collect(Collectors.toList())); + // Create the test hudi tables with non_hive_style_partitions for dataSkipping/partition prune in HMS + registerHudiTableInHMS(HoodieTableType.COPY_ON_WRITE, HUDI_SKIPPING_TABLE_NON_HIVE_STYLE, testingDataDir, Streams.concat(HUDI_META_COLUMNS.stream(), DATA_COLUMNS.stream()).collect(Collectors.toList())); // Install a hudi connector catalog queryRunner.installPlugin(new HudiPlugin("hudi", Optional.of(metastore))); diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java new file mode 100644 index 000000000000..c00c56a7e37e --- /dev/null +++ b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java @@ -0,0 +1,141 @@ +/* + * Licensed 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 com.facebook.presto.hudi; + +import com.facebook.presto.common.predicate.Domain; +import com.facebook.presto.common.predicate.Range; +import com.facebook.presto.common.predicate.TupleDomain; +import com.facebook.presto.common.predicate.ValueSet; +import com.facebook.presto.common.type.DoubleType; +import com.facebook.presto.common.type.IntegerType; +import com.facebook.presto.hive.metastore.Table; +import com.facebook.presto.spi.ColumnHandle; +import com.google.common.base.Joiner; +import com.google.common.collect.ImmutableMap; +import net.jpountz.xxhash.XXHashFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hudi.common.engine.HoodieLocalEngineContext; +import org.apache.hudi.common.model.HoodieTableQueryType; +import org.apache.hudi.common.table.HoodieTableMetaClient; +import org.testng.annotations.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +import static java.lang.String.format; +import static org.testng.Assert.assertEquals; + +/** + * Integration tests for reading Delta tables. + */ +public class TestHudiSkipping + extends AbstractHudiDistributedQueryTestBase +{ + @Test + public void testPartitionPruneAndFileSkipping() + { + Arrays.stream(new String[] {HUDI_SKIPPING_TABLE, HUDI_SKIPPING_TABLE_NON_HIVE_STYLE}).forEach(f -> { + Optional
table = metastore.getTable(METASTORE_CONTEXT, HUDI_SCHEMA, f); + HudiPartitionManager hudiPartitionManager = new HudiPartitionManager(getQueryRunner().getMetadata().getFunctionAndTypeManager()); + + HoodieTableMetaClient metaClient = HoodieTableMetaClient + .builder() + .setConf(new Configuration()) + .setBasePath(table.get().getStorage().getLocation()) + .build(); + // test partition prune by mdt + // create domain + List partitionColumns = HudiMetadata.fromPartitionColumns(table.get().getPartitionColumns()); + // year=2022 and month=11 and day=12 + TupleDomain predicate = TupleDomain.withColumnDomains(ImmutableMap.of( + partitionColumns.get(0), Domain.create(ValueSet.of(IntegerType.INTEGER, 2022L), false), + partitionColumns.get(1), Domain.create(ValueSet.of(IntegerType.INTEGER, 11L), false), + partitionColumns.get(2), Domain.create(ValueSet.of(IntegerType.INTEGER, 12L), false))); + + List parts = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), predicate); + + assertEquals(parts.size(), 1); + + // month = 11 + TupleDomain predicate1 = TupleDomain.withColumnDomains(ImmutableMap.of( + partitionColumns.get(1), Domain.create(ValueSet.of(IntegerType.INTEGER, 11L), false))); + + List parts1 = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), predicate1); + + assertEquals(parts1.size(), 2); + + // test file skipping + List dataColumns = HudiMetadata.fromDataColumns(table.get().getDataColumns()); + List partitions = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), TupleDomain.all()); + HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(metaClient.getHadoopConf()); + HudiFileSkippingManager hudiFileSkippingManager = new HudiFileSkippingManager( + partitions, + HudiSessionProperties.getHoodieFilesystemViewSpillableDir(connectorSession), + engineContext, + metaClient, + getQueryType(table.get().getStorage().getStorageFormat().getInputFormat()), + Optional.empty()); + // case1: no filter + assertEquals(hudiFileSkippingManager.listQueryFiles(TupleDomain.all()).entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 4); + // case2: where col0 > 99, should skip all files + assertEquals(hudiFileSkippingManager + .listQueryFiles(TupleDomain.withColumnDomains(ImmutableMap.of(dataColumns.get(7), Domain.create(ValueSet.ofRanges(Range.greaterThan(IntegerType.INTEGER, 99L)), false)))) + .entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 0); + // case3: where col0<=99 and col3 > 1001.0002 + assertEquals(hudiFileSkippingManager + .listQueryFiles(TupleDomain.withColumnDomains(ImmutableMap.of( + dataColumns.get(7), Domain.create(ValueSet.ofRanges(Range.lessThanOrEqual(IntegerType.INTEGER, 99L)), false), + dataColumns.get(10), Domain.create(ValueSet.ofRanges(Range.greaterThan(DoubleType.DOUBLE, 1002.0002d)), false)))) + .entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 2); + }); + } + + @Test + public void testSkippingResult() + { + Arrays.stream(new String[] {HUDI_SKIPPING_TABLE, HUDI_SKIPPING_TABLE_NON_HIVE_STYLE}).forEach(f -> { + String testQuery = format("SELECT col0,col3,col4 FROM %s where year=2022 and month=11", f); + List expRows = new ArrayList<>(); + expRows.add("SELECT 99,cast(1001.0001 as double),'x000001'"); + String expResultsQuery = Joiner.on(" UNION ").join(expRows); + assertQuery(testQuery, expResultsQuery); + }); + } + + private HoodieTableQueryType getQueryType(String hudiInputFormat) + { + switch (hudiInputFormat) { + case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": + case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": + // mor rt table + return HoodieTableQueryType.SNAPSHOT; + case "org.apache.hudi.hadoop.HoodieParquetInputFormat": + case "com.uber.hoodie.hadoop.HoodieInputFormat": + // cow table/ mor ro table + return HoodieTableQueryType.READ_OPTIMIZED; + default: + throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", hudiInputFormat)); + } + } + + // should remove this function, once we bump hudi to 0.13.0. + // old hudi-presto-bundle has not include lz4 which is used by data-skipping. + private void shouldRemoved() + { + XXHashFactory.fastestInstance(); + } +} diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java deleted file mode 100644 index 66deff5b87d6..000000000000 --- a/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkippingAndEvolution.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Licensed 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 com.facebook.presto.hudi; - -import com.facebook.presto.common.predicate.Domain; -import com.facebook.presto.common.predicate.Range; -import com.facebook.presto.common.predicate.TupleDomain; -import com.facebook.presto.common.predicate.ValueSet; -import com.facebook.presto.common.type.DoubleType; -import com.facebook.presto.common.type.IntegerType; -import com.facebook.presto.hive.metastore.Table; -import com.facebook.presto.spi.ColumnHandle; -import com.github.benmanes.caffeine.cache.Caffeine; -import com.google.common.base.Joiner; -import com.google.common.collect.ImmutableMap; -import net.jpountz.xxhash.XXHashFactory; -import org.apache.hadoop.conf.Configuration; -import org.apache.hudi.common.config.HoodieMetadataConfig; -import org.apache.hudi.common.engine.HoodieLocalEngineContext; -import org.apache.hudi.common.model.HoodieTableQueryType; -import org.apache.hudi.common.table.HoodieTableMetaClient; -import org.apache.hudi.common.util.Option; -import org.testng.annotations.Test; - -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; - -import static com.facebook.presto.hudi.HudiSessionProperties.isHudiMetadataTableEnabled; -import static java.lang.String.format; -import static org.testng.Assert.assertEquals; - -/** - * Integration tests for reading Delta tables. - */ -public class TestHudiSkippingAndEvolution - extends AbstractHudiDistributedQueryTestBase -{ - @Test - public void testPartitionPruneAndFileSkipping() - { - Optional
table = metastore.getTable(METASTORE_CONTEXT, HUDI_SCHEMA, HUDI_SKIPPING_TABLE); - HudiPartitionManager hudiPartitionManager = new HudiPartitionManager(getQueryRunner().getMetadata().getFunctionAndTypeManager()); - boolean hudiMetadataTableEnabled = isHudiMetadataTableEnabled(connectorSession); - HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(hudiMetadataTableEnabled).build(); - - HoodieTableMetaClient metaClient = HoodieTableMetaClient - .builder() - .setConf(new Configuration()) - .setBasePath(table.get().getStorage().getLocation()) - .build(); - // test partition prune by mdt - // create domain - List partitionColumns = HudiMetadata.fromPartitionColumns(table.get().getPartitionColumns()); - // year=2022 and month=11 and day=12 - TupleDomain predicate = TupleDomain.withColumnDomains(ImmutableMap.of( - partitionColumns.get(0), Domain.create(ValueSet.of(IntegerType.INTEGER, 2022L), false), - partitionColumns.get(1), Domain.create(ValueSet.of(IntegerType.INTEGER, 11L), false), - partitionColumns.get(2), Domain.create(ValueSet.of(IntegerType.INTEGER, 12L), false))); - - List parts = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), predicate); - - assertEquals(parts.size(), 1); - - // month = 11 - TupleDomain predicate1 = TupleDomain.withColumnDomains(ImmutableMap.of( - partitionColumns.get(1), Domain.create(ValueSet.of(IntegerType.INTEGER, 11L), false))); - - List parts1 = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), predicate1); - - assertEquals(parts1.size(), 2); - - // test file skipping - List dataColumns = HudiMetadata.fromDataColumns(table.get().getDataColumns()); - List partitions = hudiPartitionManager.getEffectivePartitions(connectorSession, metastore, metaClient, table.get().getDatabaseName(), table.get().getTableName(), TupleDomain.all()); - HoodieLocalEngineContext engineContext = new HoodieLocalEngineContext(metaClient.getHadoopConf()); - HudiFileSkippingManager hudiFileSkippingManager = new HudiFileSkippingManager( - partitions, - HudiSessionProperties.getHoodieFilesystemViewSpillableDir(connectorSession), - engineContext, - metaClient, - getQueryType(table.get().getStorage().getStorageFormat().getInputFormat()), - Option.empty()); - // case1: no filter - assertEquals(hudiFileSkippingManager.listQueryFiles(TupleDomain.all()).entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 4); - // case2: where col0 > 99, should skip all files - assertEquals(hudiFileSkippingManager - .listQueryFiles(TupleDomain.withColumnDomains(ImmutableMap.of(dataColumns.get(7), Domain.create(ValueSet.ofRanges(Range.greaterThan(IntegerType.INTEGER, 99L)), false)))) - .entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 0); - // case3: where col0<=99 and col3 > 1001.0002 - assertEquals(hudiFileSkippingManager - .listQueryFiles(TupleDomain.withColumnDomains(ImmutableMap.of( - dataColumns.get(7), Domain.create(ValueSet.ofRanges(Range.lessThanOrEqual(IntegerType.INTEGER, 99L)), false), - dataColumns.get(10), Domain.create(ValueSet.ofRanges(Range.greaterThan(DoubleType.DOUBLE, 1002.0002d)), false)))) - .entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 2); - } - - @Test - public void testSchemaEvolutionWithColumnRenameDeleteAdd() - { - String testQuery = format("SELECT col0,col3_rename,col4_new FROM data_column_rename_drop_add where year=2022 and month=11"); - List expRows = new ArrayList<>(); - expRows.add("SELECT 99,cast(1001.0001 as double),null"); - String expResultsQuery = Joiner.on(" UNION ").join(expRows); - assertQuery(testQuery, expResultsQuery); - } - - @Test - public void testSchemaEvolutionWithColumnTypeChanged() - { - String testQuery = format("SELECT col0,col1,col2 FROM data_column_type_change where year=2022 and month=11"); - List expRows = new ArrayList<>(); - expRows.add("SELECT cast(99 as long),'1111111',cast(101.01 as double)"); - String expResultsQuery = Joiner.on(" UNION ").join(expRows); - assertQuery(testQuery, expResultsQuery); - } - - private HoodieTableQueryType getQueryType(String hudiInputFormat) - { - switch (hudiInputFormat) { - case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": - case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": - // mor rt table - return HoodieTableQueryType.SNAPSHOT; - case "org.apache.hudi.hadoop.HoodieParquetInputFormat": - case "com.uber.hoodie.hadoop.HoodieInputFormat": - // cow table/ mor ro table - return HoodieTableQueryType.READ_OPTIMIZED; - default: - throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", hudiInputFormat)); - } - } - - // should remove this function, once we bump hudi to 0.13.0. - // old hudi-presto-bundle has not include lz4 and caffeine jar which is used by schema evolution and data-skipping. - private void shouldRemoved() - { - XXHashFactory.fastestInstance(); - Caffeine.newBuilder(); - } -} diff --git a/presto-hudi/src/test/resources/hudi-skipping-schema-data.zip b/presto-hudi/src/test/resources/hudi-skipping-schema-data.zip index e605fcf1996a3553f00ca0252188944a20a82114..70b7d9230a2faa8fce4a19a9f7f30566c42c5236 100644 GIT binary patch delta 48844 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 literal 219435 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 YP_ITur?*0H#flGfbenvD0~@;k1E|6aOaK4? From d716114565fc609b13acb88a663cd07ae9c87a87 Mon Sep 17 00:00:00 2001 From: xiarixiaoyao Date: Wed, 7 Dec 2022 10:22:55 +0800 Subject: [PATCH 5/6] fix code style --- .../presto/hudi/HudiFileSkippingManager.java | 141 +++++++++++------- .../presto/hudi/HudiPartitionManager.java | 43 ++++-- .../facebook/presto/hudi/HudiPredicates.java | 10 +- 3 files changed, 122 insertions(+), 72 deletions(-) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java index 290a7ba118b6..c00f0800a045 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiFileSkippingManager.java @@ -22,6 +22,7 @@ import com.facebook.presto.common.type.Type; import com.facebook.presto.parquet.predicate.TupleDomainParquetPredicate; import com.facebook.presto.spi.ColumnHandle; +import com.google.common.collect.ImmutableList; import io.airlift.slice.Slice; import io.airlift.slice.Slices; import org.apache.hudi.avro.model.HoodieMetadataColumnStats; @@ -42,7 +43,6 @@ import org.apache.hudi.metadata.HoodieTableMetadata; import org.apache.hudi.metadata.HoodieTableMetadataUtil; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -70,7 +70,7 @@ public class HudiFileSkippingManager private final HoodieTableMetaClient metaClient; private final HoodieTableMetadata metadataTable; - protected final Map> allInputFileSlices; + private final Map> allInputFileSlices; public HudiFileSkippingManager( List partitions, @@ -93,35 +93,49 @@ public HudiFileSkippingManager( this.allInputFileSlices = prepareAllInputFileSlices(partitions, engineContext, metadataConfig, spillableDir); } - private Map> prepareAllInputFileSlices(List partitions, HoodieEngineContext engineContext, HoodieMetadataConfig metadataConfig, String spillableDir) + private Map> prepareAllInputFileSlices( + List partitions, + HoodieEngineContext engineContext, + HoodieMetadataConfig metadataConfig, + String spillableDir) { long startTime = System.currentTimeMillis(); HoodieTimeline activeTimeline = metaClient.reloadActiveTimeline(); Optional latestInstant = activeTimeline.lastInstant().toJavaOptional(); // build system view. SyncableFileSystemView fileSystemView = FileSystemViewManager - .createViewManager(engineContext, metadataConfig, + .createViewManager(engineContext, + metadataConfig, FileSystemViewStorageConfig.newBuilder().withBaseStoreDir(spillableDir).build(), HoodieCommonConfig.newBuilder().build(), () -> metadataTable) .getFileSystemView(metaClient); - Optional queryInstant = specifiedQueryInstant.isPresent() ? specifiedQueryInstant : latestInstant.map(HoodieInstant::getTimestamp); + Optional queryInstant = specifiedQueryInstant.isPresent() ? + specifiedQueryInstant : latestInstant.map(HoodieInstant::getTimestamp); Map> allInputFileSlices = engineContext - .mapToPair(partitions, partitionPath -> Pair.of(partitionPath, getLatestFileSlices(partitionPath, fileSystemView, queryInstant)), partitions.size()); + .mapToPair( + partitions, + partitionPath -> Pair.of( + partitionPath, + getLatestFileSlices(partitionPath, fileSystemView, queryInstant)), + partitions.size()); long duration = System.currentTimeMillis() - startTime; - - log.info("prepare query files for table %s, spent: %d ms", metaClient.getTableConfig().getTableName(), duration); + log.debug("prepare query files for table %s, spent: %d ms", metaClient.getTableConfig().getTableName(), duration); return allInputFileSlices; } - private List getLatestFileSlices(String partitionPath, SyncableFileSystemView fileSystemView, Optional queryInstant) + private List getLatestFileSlices( + String partitionPath, + SyncableFileSystemView fileSystemView, + Optional queryInstant) { - return queryInstant.map(instant -> - fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get())) - .orElse(fileSystemView.getLatestFileSlices(partitionPath)) - .collect(Collectors.toList()); + return queryInstant + .map(instant -> + fileSystemView.getLatestMergedFileSlicesBeforeOrOn(partitionPath, queryInstant.get())) + .orElse(fileSystemView.getLatestFileSlices(partitionPath)) + .collect(Collectors.toList()); } public Map> listQueryFiles(TupleDomain tupleDomain) @@ -142,12 +156,17 @@ public Map> listQueryFiles(TupleDomain tup int candidateFileSize = candidateFileSlices.values().stream().mapToInt(List::size).sum(); int totalFiles = allInputFileSlices.values().stream().mapToInt(List::size).sum(); double skippingPercent = totalFiles == 0 ? 0.0d : (totalFiles - candidateFileSize) / (totalFiles + 0.0d); - log.debug("Total files: %s; candidate files after data skipping: %s; skipping percent %s", totalFiles, candidateFileSize, skippingPercent); + log.debug("Total files: %s; candidate files after data skipping: %s; skipping percent %s", + totalFiles, + candidateFileSize, + skippingPercent); } return candidateFileSlices; } - private Map> lookupCandidateFilesInMetadataTable(Map> inputFileSlices, TupleDomain tupleDomain) + private Map> lookupCandidateFilesInMetadataTable( + Map> inputFileSlices, + TupleDomain tupleDomain) { // split regular column predicates TupleDomain regularTupleDomain = HudiPredicates.from(tupleDomain).getRegularColumnPredicates(); @@ -155,40 +174,60 @@ private Map> lookupCandidateFilesInMetadataTable(Map regularColumns = regularColumnPredicates.getDomains().get().entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList()); + List regularColumns = regularColumnPredicates + .getDomains().get().entrySet().stream().map(Map.Entry::getKey).collect(Collectors.toList()); // get filter columns - List encodedTargetColumnNames = regularColumns.stream().map(col -> new ColumnIndexID(col).asBase64EncodedString()).collect(Collectors.toList()); + List encodedTargetColumnNames = regularColumns + .stream() + .map(col -> new ColumnIndexID(col).asBase64EncodedString()).collect(Collectors.toList()); Map> statsByFileName = metadataTable.getRecordsByKeyPrefixes( encodedTargetColumnNames, HoodieTableMetadataUtil.PARTITION_NAME_COLUMN_STATS, true) .collectAsList() .stream() - .filter(f -> f.getData().getColumnStatMetadata() - .isPresent()) - .map(f -> f.getData().getColumnStatMetadata().get()).collect(Collectors.groupingBy(HoodieMetadataColumnStats::getFileName)); + .filter(f -> f.getData().getColumnStatMetadata().isPresent()) + .map(f -> f.getData().getColumnStatMetadata().get()) + .collect(Collectors.groupingBy(HoodieMetadataColumnStats::getFileName)); // prune files. - return inputFileSlices.entrySet().parallelStream().collect(Collectors.toMap(entry -> entry.getKey(), entry -> { - return entry.getValue().stream().filter(fileSlice -> { - String fileSliceName = fileSlice.getBaseFile().map(BaseFile::getFileName).orElse(""); - // no stats found - if (!statsByFileName.containsKey(fileSliceName)) { - return true; - } - List stats = statsByFileName.get(fileSliceName); - return evaluateStatisticPredicate(regularColumnPredicates, stats, regularColumns); - }).collect(Collectors.toList()); - })); + return inputFileSlices + .entrySet() + .stream() + .collect(Collectors + .toMap(entry -> entry.getKey(), entry -> entry + .getValue() + .stream() + .filter(fileSlice -> pruneFiles(fileSlice, statsByFileName, regularColumnPredicates, regularColumns)) + .collect(Collectors.toList()))); + } + + private boolean pruneFiles( + FileSlice fileSlice, + Map> statsByFileName, + TupleDomain regularColumnPredicates, + List regularColumns) + { + String fileSliceName = fileSlice.getBaseFile().map(BaseFile::getFileName).orElse(""); + // no stats found + if (!statsByFileName.containsKey(fileSliceName)) { + return true; + } + List stats = statsByFileName.get(fileSliceName); + return evaluateStatisticPredicate(regularColumnPredicates, stats, regularColumns); } - private boolean evaluateStatisticPredicate(TupleDomain regularColumnPredicates, List stats, List regularColumns) + private boolean evaluateStatisticPredicate( + TupleDomain regularColumnPredicates, + List stats, + List regularColumns) { if (regularColumnPredicates.isNone() || !regularColumnPredicates.getDomains().isPresent()) { return true; } for (String regularColumn : regularColumns) { Domain columnPredicate = regularColumnPredicates.getDomains().get().get(regularColumn); - Optional currentColumnStats = stats.stream().filter(s -> s.getColumnName().equals(regularColumn)).findFirst(); + Optional currentColumnStats = stats + .stream().filter(s -> s.getColumnName().equals(regularColumn)).findFirst(); if (!currentColumnStats.isPresent()) { // no stats for column } @@ -212,6 +251,10 @@ private static Domain getDomain(String colName, Type type, HoodieMetadataColumnS if (!hasNonNullValue || statistics.getMaxValue() == null || statistics.getMinValue() == null) { return Domain.create(ValueSet.all(type), hasNullValue); } + if (!(statistics.getMinValue() instanceof org.apache.hudi.org.apache.avro.generic.GenericRecord) || + !(statistics.getMaxValue() instanceof org.apache.hudi.org.apache.avro.generic.GenericRecord)) { + return Domain.all(type); + } return getDomain(colName, type, ((org.apache.hudi.org.apache.avro.generic.GenericRecord) statistics.getMinValue()).get(0), ((org.apache.hudi.org.apache.avro.generic.GenericRecord) statistics.getMaxValue()).get(0), hasNullValue); } @@ -222,7 +265,6 @@ private static Domain getDomain(String colName, Type type, HoodieMetadataColumnS */ private static Domain getDomain(String colName, Type type, Object minimum, Object maximum, boolean hasNullValue) { - List ranges = new ArrayList<>(); try { if (type.equals(BOOLEAN)) { boolean hasTrueValue = (boolean) minimum || (boolean) maximum; @@ -239,14 +281,13 @@ private static Domain getDomain(String colName, Type type, Object minimum, Objec // No other case, since all null case is handled earlier. } - if ((type.equals(BIGINT) || type.equals(TINYINT) || type.equals(SMALLINT) || type.equals(INTEGER))) { + if ((type.equals(BIGINT) || type.equals(TINYINT) || type.equals(SMALLINT) || type.equals(INTEGER) || type.equals(DATE))) { long minValue = TupleDomainParquetPredicate.asLong(minimum); long maxValue = TupleDomainParquetPredicate.asLong(maximum); if (isStatisticsOverflow(type, minValue, maxValue)) { return Domain.create(ValueSet.all(type), hasNullValue); } - ranges.add(Range.range(type, minValue, true, maxValue, true)); - return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + return ofMinMax(type, minValue, maxValue, hasNullValue); } if (type.equals(REAL)) { @@ -255,8 +296,7 @@ private static Domain getDomain(String colName, Type type, Object minimum, Objec if (minValue.isNaN() || maxValue.isNaN()) { return Domain.create(ValueSet.all(type), hasNullValue); } - ranges.add(Range.range(type, (long) floatToRawIntBits(minValue), true, (long) floatToRawIntBits(maxValue), true)); - return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + return ofMinMax(type, (long) floatToRawIntBits(minValue), (long) floatToRawIntBits(maxValue), hasNullValue); } if (type.equals(DOUBLE)) { @@ -265,25 +305,13 @@ private static Domain getDomain(String colName, Type type, Object minimum, Objec if (minValue.isNaN() || maxValue.isNaN()) { return Domain.create(ValueSet.all(type), hasNullValue); } - ranges.add(Range.range(type, minValue, true, maxValue, true)); - return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + return ofMinMax(type, minValue, maxValue, hasNullValue); } if (isVarcharType(type)) { Slice min = Slices.utf8Slice((String) minimum); Slice max = Slices.utf8Slice((String) maximum); - ranges.add(Range.range(type, min, true, max, true)); - return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); - } - - if (type.equals(DATE)) { - long min = TupleDomainParquetPredicate.asLong(minimum); - long max = TupleDomainParquetPredicate.asLong(maximum); - if (isStatisticsOverflow(type, min, max)) { - return Domain.create(ValueSet.all(type), hasNullValue); - } - ranges.add(Range.range(type, min, true, max, true)); - return Domain.create(ValueSet.ofRanges(ranges), hasNullValue); + return ofMinMax(type, min, max, hasNullValue); } return Domain.create(ValueSet.all(type), hasNullValue); } @@ -292,4 +320,11 @@ private static Domain getDomain(String colName, Type type, Object minimum, Objec return Domain.create(ValueSet.all(type), hasNullValue); } } + + private static Domain ofMinMax(Type type, Object min, Object max, boolean hasNullValue) + { + Range range = Range.range(type, min, true, max, true); + ValueSet vs = ValueSet.ofRanges(ImmutableList.of(range)); + return Domain.create(vs, hasNullValue); + } } diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java index e60850123404..8e459dfbd0a7 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java @@ -100,12 +100,12 @@ public List getEffectivePartitions( boolean metaTableEnabled = isHudiMetadataTableEnabled(connectorSession); - return metaTableEnabled ? prunePartitionByMetaDataTable(connectorSession, metaClient, partitionColumns, tupleDomain) : + return metaTableEnabled ? + prunePartitionByMetaDataTable(metaClient, partitionColumns, tupleDomain) : prunePartitionByMetaStore(metastore, metastoreContext, schemaName, tableName, partitionColumns, tupleDomain); } private List prunePartitionByMetaDataTable( - ConnectorSession connectorSession, HoodieTableMetaClient metaClient, List partitionColumns, TupleDomain tupleDomain) @@ -119,7 +119,10 @@ private List prunePartitionByMetaDataTable( HoodieMetadataConfig metadataConfig = HoodieMetadataConfig.newBuilder().enable(true).build(); // Load all the partition path from the basePath - List allPartitions = FSUtils.getAllPartitionPaths(engineContext, metadataConfig, metaClient.getBasePathV2().toString()); + List allPartitions = FSUtils.getAllPartitionPaths( + engineContext, + metadataConfig, + metaClient.getBasePathV2().toString()); // Extract partition columns predicate TupleDomain partitionPredicate = tupleDomain.transform(hudiColumnHandle -> { @@ -139,8 +142,14 @@ private List prunePartitionByMetaDataTable( List partitionColumnHandles = fromPartitionColumns(partitionColumns); - List matchedPartitionPaths = prunePartitions(partitionPredicate, partitionColumnHandles, getPartitions(partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), allPartitions)); - log.info(format("Total partition size is %s, after partition prune size is %s.", allPartitions.size(), matchedPartitionPaths.size())); + List matchedPartitionPaths = prunePartitions( + partitionPredicate, + partitionColumnHandles, + getPartitions( + partitionColumns.stream().map(f -> f.getName()).collect(Collectors.toList()), + allPartitions)); + log.debug(format("Total partition size is %s, after partition prune size is %s.", + allPartitions.size(), matchedPartitionPaths.size())); return matchedPartitionPaths; } @@ -151,7 +160,8 @@ private List prunePartitionByMetaDataTable( * partition paths: * p1=val1/p2=val2/p3=val3 (hive style partition) * p1=val4/p2=val5/p3=val6 (hive style partition) - * return values {p1=val1/p2=val2/p3=val3 -> {p1 -> val1, p2 -> value2, p3 -> value3}}, {p1=val4/p2=val5/p3=val6 -> {p1 -> val4, p2 -> value5, p3 -> value6}} + * return values {p1=val1/p2=val2/p3=val3 -> {p1 -> val1, p2 -> value2, p3 -> value3}}, + * {p1=val4/p2=val5/p3=val6 -> {p1 -> val4, p2 -> value5, p3 -> value6}} * * @param partitionKey The partition key list * @param partitionPaths partition path list @@ -194,8 +204,11 @@ public static List extractPartitionValues(String partitionName, Optional } else { String[] partitionValues = partitionName.split(Path.SEPARATOR); - checkArgument(partitionValues.length == partitionColumnNames.get().size(), - "Invalid partition spec: {partitionName: %s, partitionColumnNames: %s}", partitionName, partitionColumnNames.get()); + checkArgument( + partitionValues.length == partitionColumnNames.get().size(), + "Invalid partition spec: {partitionName: %s, partitionColumnNames: %s}", + partitionName, + partitionColumnNames.get()); return Arrays.asList(partitionValues); } } @@ -210,13 +223,21 @@ private List prunePartitions( { return candidatePartitionPaths.entrySet().stream().filter(f -> { Map partitionMapping = f.getValue(); - return partitionMapping.entrySet().stream().allMatch(p -> evaluatePartitionPredicate(partitionPredicate, partitionColumnHandles, p.getValue(), p.getKey())); + return partitionMapping + .entrySet() + .stream() + .allMatch(p -> evaluatePartitionPredicate(partitionPredicate, partitionColumnHandles, p.getValue(), p.getKey())); }).map(entry -> entry.getKey()).collect(Collectors.toList()); } - private boolean evaluatePartitionPredicate(TupleDomain partitionPredicate, List partitionColumnHandles, String partitionPathValue, String partitionName) + private boolean evaluatePartitionPredicate( + TupleDomain partitionPredicate, + List partitionColumnHandles, + String partitionPathValue, + String partitionName) { - Optional columnHandleOpt = partitionColumnHandles.stream().filter(f -> f.getName().equals(partitionName)).findFirst(); + Optional columnHandleOpt = + partitionColumnHandles.stream().filter(f -> f.getName().equals(partitionName)).findFirst(); if (columnHandleOpt.isPresent()) { Domain domain = getDomain(columnHandleOpt.get(), partitionPathValue); if (!partitionPredicate.getDomains().isPresent()) { diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java index 44a332c17e05..5c80422fff70 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPredicates.java @@ -24,7 +24,6 @@ public class HudiPredicates { - private final TupleDomain partitionColumnPredicates; private final TupleDomain regularColumnPredicates; public static HudiPredicates from(TupleDomain predicate) @@ -43,16 +42,11 @@ public static HudiPredicates from(TupleDomain predicate) } })); - return new HudiPredicates( - TupleDomain.withColumnDomains(partitionColumnPredicates), - TupleDomain.withColumnDomains(regularColumnPredicates)); + return new HudiPredicates(TupleDomain.withColumnDomains(regularColumnPredicates)); } - private HudiPredicates( - TupleDomain partitionColumnPredicates, - TupleDomain regularColumnPredicates) + private HudiPredicates(TupleDomain regularColumnPredicates) { - this.partitionColumnPredicates = partitionColumnPredicates; this.regularColumnPredicates = regularColumnPredicates; } From 8ed791f698905aa55cf3014a21be3b99e632ca1f Mon Sep 17 00:00:00 2001 From: xiarixiaoyao Date: Wed, 11 Jan 2023 16:52:39 +0800 Subject: [PATCH 6/6] rebase code, address comments --- .../presto/hudi/HudiPartitionManager.java | 6 +----- .../presto/hudi/HudiSplitManager.java | 2 +- .../AbstractHudiDistributedQueryTestBase.java | 3 --- .../presto/hudi/TestHudiSkipping.java | 21 ++----------------- 4 files changed, 4 insertions(+), 28 deletions(-) diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java index 8e459dfbd0a7..c4b03540d683 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiPartitionManager.java @@ -253,11 +253,7 @@ private boolean evaluatePartitionPredicate( if (partitionPathValue.equals("default")) { return true; } - - if (columnPredicate.intersect(domain).isNone()) { - return false; - } - return true; + return !columnPredicate.intersect(domain).isNone(); } else { // Should not happen diff --git a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java index 4b52b8be5f21..39679e9883f3 100644 --- a/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java +++ b/presto-hudi/src/main/java/com/facebook/presto/hudi/HudiSplitManager.java @@ -275,7 +275,7 @@ private static HudiSplitWeightProvider createSplitWeightProvider(ConnectorSessio return HudiSplitWeightProvider.uniformStandardWeightProvider(); } - private static HoodieTableQueryType getQueryType(String inputFormat) + public static HoodieTableQueryType getQueryType(String inputFormat) { // TODO support incremental query switch (inputFormat) { diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java index c61b14cc8c6f..52659e78e3ed 100644 --- a/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java +++ b/presto-hudi/src/test/java/com/facebook/presto/hudi/AbstractHudiDistributedQueryTestBase.java @@ -187,11 +187,8 @@ private static DistributedQueryRunner createHudiQueryRunner(Map .build(); // setup file metastore - Path catalogDirectory = queryRunner.getCoordinator().getDataDirectory().resolve("catalog"); - metastore = createFileHiveMetastore(catalogDirectory.toString()); - // create database Database database = Database.builder() .setDatabaseName(HUDI_SCHEMA) diff --git a/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java index c00c56a7e37e..ecc7467d9412 100644 --- a/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java +++ b/presto-hudi/src/test/java/com/facebook/presto/hudi/TestHudiSkipping.java @@ -27,7 +27,6 @@ import net.jpountz.xxhash.XXHashFactory; import org.apache.hadoop.conf.Configuration; import org.apache.hudi.common.engine.HoodieLocalEngineContext; -import org.apache.hudi.common.model.HoodieTableQueryType; import org.apache.hudi.common.table.HoodieTableMetaClient; import org.testng.annotations.Test; @@ -40,7 +39,7 @@ import static org.testng.Assert.assertEquals; /** - * Integration tests for reading Delta tables. + * Integration tests for reading hudi tables. */ public class TestHudiSkipping extends AbstractHudiDistributedQueryTestBase @@ -87,7 +86,7 @@ public void testPartitionPruneAndFileSkipping() HudiSessionProperties.getHoodieFilesystemViewSpillableDir(connectorSession), engineContext, metaClient, - getQueryType(table.get().getStorage().getStorageFormat().getInputFormat()), + HudiSplitManager.getQueryType(table.get().getStorage().getStorageFormat().getInputFormat()), Optional.empty()); // case1: no filter assertEquals(hudiFileSkippingManager.listQueryFiles(TupleDomain.all()).entrySet().stream().map(entry -> entry.getValue().size()).reduce(0, Integer::sum), 4); @@ -116,22 +115,6 @@ public void testSkippingResult() }); } - private HoodieTableQueryType getQueryType(String hudiInputFormat) - { - switch (hudiInputFormat) { - case "org.apache.hudi.hadoop.realtime.HoodieParquetRealtimeInputFormat": - case "com.uber.hoodie.hadoop.realtime.HoodieRealtimeInputFormat": - // mor rt table - return HoodieTableQueryType.SNAPSHOT; - case "org.apache.hudi.hadoop.HoodieParquetInputFormat": - case "com.uber.hoodie.hadoop.HoodieInputFormat": - // cow table/ mor ro table - return HoodieTableQueryType.READ_OPTIMIZED; - default: - throw new IllegalArgumentException(String.format("failed to infer query type for current inputFormat: %s", hudiInputFormat)); - } - } - // should remove this function, once we bump hudi to 0.13.0. // old hudi-presto-bundle has not include lz4 which is used by data-skipping. private void shouldRemoved()

H}b?}RNtALQD%AF}DL7S`?8 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�r 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