wombatu-kun commented on code in PR #19467:
URL: https://github.com/apache/hudi/pull/19467#discussion_r3859250209


##########
hudi-trino/src/test/java/io/trino/plugin/hudi/TestHudiEvolvedColumnPredicates.java:
##########
@@ -0,0 +1,388 @@
+/*
+ * 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 io.trino.plugin.hudi;
+
+import io.airlift.slice.Slices;
+import io.trino.filesystem.local.LocalInputFile;
+import io.trino.metastore.HiveType;
+import io.trino.parquet.ParquetReaderOptions;
+import io.trino.plugin.base.metrics.FileFormatDataSourceStats;
+import io.trino.plugin.hive.HiveColumnHandle;
+import io.trino.plugin.hive.parquet.ParquetReaderConfig;
+import io.trino.plugin.hudi.file.HudiBaseFile;
+import io.trino.spi.SplitWeight;
+import io.trino.spi.connector.ColumnHandle;
+import io.trino.spi.connector.ConnectorPageSource;
+import io.trino.spi.connector.ConnectorSession;
+import io.trino.spi.connector.DynamicFilter;
+import io.trino.spi.predicate.Domain;
+import io.trino.spi.predicate.Range;
+import io.trino.spi.predicate.TupleDomain;
+import io.trino.spi.predicate.ValueSet;
+import io.trino.spi.type.Type;
+import io.trino.testing.MaterializedResult;
+import io.trino.testing.TestingConnectorSession;
+import org.apache.parquet.conf.PlainParquetConfiguration;
+import org.apache.parquet.example.data.Group;
+import org.apache.parquet.example.data.simple.SimpleGroupFactory;
+import org.apache.parquet.hadoop.ParquetFileReader;
+import org.apache.parquet.hadoop.ParquetWriter;
+import org.apache.parquet.hadoop.example.ExampleParquetWriter;
+import org.apache.parquet.io.LocalOutputFile;
+import org.apache.parquet.schema.LogicalTypeAnnotation;
+import org.apache.parquet.schema.MessageType;
+import org.apache.parquet.schema.PrimitiveType;
+import org.apache.parquet.schema.Types;
+import org.joda.time.DateTimeZone;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.OptionalLong;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+
+import static io.trino.plugin.hive.HiveColumnHandle.createBaseColumn;
+import static io.trino.plugin.hudi.HudiPageSourceProvider.createPageSource;
+import static io.trino.spi.type.BigintType.BIGINT;
+import static io.trino.spi.type.DoubleType.DOUBLE;
+import static io.trino.spi.type.IntegerType.INTEGER;
+import static io.trino.spi.type.VarcharType.VARCHAR;
+import static io.trino.testing.MaterializedResult.materializeSourceDataStream;
+import static org.apache.hudi.common.model.HoodieRecord.HOODIE_META_COLUMNS;
+import static org.apache.parquet.schema.Type.Repetition.OPTIONAL;
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Covers what a pushed-down predicate does to a base file written before the 
column it constrains was evolved.
+ * <p>
+ * Hudi lets a column's type widen and hive sync then reports the NEW type, 
while every base file written before the
+ * evolution keeps storing the old one. The parquet reader copes with that on 
its own -- {@code ColumnReaderFactory}
+ * decodes {@code FLOAT} into {@code DOUBLE} and {@code INT32} into {@code 
BIGINT}, and
+ * {@code ParquetTypeTranslator.createCoercer} handles the rest -- but the 
statistics do not: {@code
+ * TupleDomainParquetPredicate.getDomain} reads them as whatever the DOMAIN's 
type says, so a {@code DOUBLE} domain
+ * over a {@code FLOAT} column casts a {@code Float} to a {@code Double} and 
fails the whole split with {@code
+ * HUDI_BAD_DATA}. See apache/hudi#19457.
+ * <p>
+ * The fixture writes every data column as the type it had BEFORE the 
evolution and every handle carries the type the
+ * metastore reports AFTER it, which is exactly the state an unrewritten base 
file is in. Values grow with the row
+ * index so pruning stays observable: a predicate that survives pushdown reads 
fewer rows than the file holds, and one
+ * that was dropped reads all of them. Do not "simplify" that to asserting the 
matching rows alone -- both a working
+ * pushdown and no pushdown at all produce the same matching rows, since the 
connector's pushdown is an optimization
+ * and the engine re-applies the predicate above the scan.
+ */
+class TestHudiEvolvedColumnPredicates
+{
+    private static final String STABLE_COLUMN = "stable_int";
+    /** Written as parquet FLOAT, reported by the metastore as double. */
+    private static final String FLOAT_TO_DOUBLE_COLUMN = "evolved_double";
+    /** Written as parquet INT32, reported by the metastore as bigint. */
+    private static final String INT_TO_BIGINT_COLUMN = "evolved_bigint";
+    /** Written as parquet INT32, reported by the metastore as string. */
+    private static final String INT_TO_VARCHAR_COLUMN = "evolved_varchar";
+
+    private static final int ROW_COUNT = 1000;
+    private static final long THRESHOLD = 900;
+    private static final int MATCHING_ROW_COUNT = (int) (ROW_COUNT - THRESHOLD 
- 1);
+
+    @TempDir
+    static Path tempDir;
+
+    private static Path baseFile;
+
+    @BeforeAll
+    static void writeBaseFile()

Review Comment:
   Done 44c2344b18e4. The second file also carries the bloom filters, so it 
doubles as the control that a post-evolution column still prunes on the same 
predicate the pre-evolution one cannot.



##########
hudi-trino/src/main/java/io/trino/plugin/hudi/util/ParquetStatisticsDomains.java:
##########
@@ -0,0 +1,172 @@
+/*
+ * 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 io.trino.plugin.hudi.util;
+
+import io.airlift.log.Logger;
+import io.trino.spi.predicate.Domain;
+import io.trino.spi.predicate.TupleDomain;
+import io.trino.spi.type.DecimalType;
+import io.trino.spi.type.TimestampType;
+import io.trino.spi.type.Type;
+import io.trino.spi.type.VarcharType;
+import org.apache.parquet.column.ColumnDescriptor;
+import org.apache.parquet.schema.LogicalTypeAnnotation;
+import 
org.apache.parquet.schema.LogicalTypeAnnotation.DecimalLogicalTypeAnnotation;
+import 
org.apache.parquet.schema.LogicalTypeAnnotation.TimestampLogicalTypeAnnotation;
+import org.apache.parquet.schema.PrimitiveType;
+import org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import static io.trino.spi.type.BigintType.BIGINT;
+import static io.trino.spi.type.BooleanType.BOOLEAN;
+import static io.trino.spi.type.DateType.DATE;
+import static io.trino.spi.type.DoubleType.DOUBLE;
+import static io.trino.spi.type.IntegerType.INTEGER;
+import static io.trino.spi.type.RealType.REAL;
+import static io.trino.spi.type.SmallintType.SMALLINT;
+import static io.trino.spi.type.TinyintType.TINYINT;
+import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.BINARY;
+import static 
org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.FIXED_LEN_BYTE_ARRAY;
+import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.FLOAT;
+import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT32;
+import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT64;
+import static org.apache.parquet.schema.PrimitiveType.PrimitiveTypeName.INT96;
+
+/**
+ * Keeps a pushed-down predicate from being matched against statistics it 
cannot be compared with.
+ * <p>
+ * {@code TupleDomainParquetPredicate.getDomain} selects its branch on the 
type of the pushed-down DOMAIN and then
+ * reads the parquet statistics as that type - {@code Double min = (Double) 
minimums.get(i)} and so on. The domain's
+ * type comes from the metastore while the statistics come from the file, and 
Hudi's type evolution is exactly what
+ * makes those two disagree: after a column evolves and the metastore is 
synced, every base file written before the
+ * evolution still stores the old physical type. Handing such a domain to the 
parquet predicate either fails the
+ * whole split with {@code Malformed Parquet file. Corrupted statistics for 
column ...} wrapping a
+ * {@link ClassCastException}, or - where the two types happen to share a 
representation, as a decimal and a varchar
+ * both do through {@code Slice} - silently prunes row groups on a comparison 
that means nothing.
+ * <p>
+ * The read path has no such problem: {@code ColumnReaderFactory} decodes 
parquet {@code FLOAT} into Trino
+ * {@code DOUBLE} and {@code INT32} into {@code BIGINT} natively, and {@code 
ParquetTypeTranslator.createCoercer}
+ * covers the rest of the promotions the page source is asked for. Only the 
statistics side is blind, so only the
+ * statistics side needs the guard.

Review Comment:
   Done 44c2344b18e4.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to