This is an automated email from the ASF dual-hosted git repository.
yiguolei pushed a commit to branch branch-4.1
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/branch-4.1 by this push:
new 688e3ce5e41 [opt](paimon) support prune complex type in paimon jni
reader (#66573)
688e3ce5e41 is described below
commit 688e3ce5e41b191faa310040f2aeaf3f83bf5594
Author: zhangstar333 <[email protected]>
AuthorDate: Sat Aug 8 01:42:33 2026 +0800
[opt](paimon) support prune complex type in paimon jni reader (#66573)
### What problem does this PR solve?
Problem Summary:
before disable prune complex struct/array/map type in paimon. now
support those in paimon jni reader, so could enable it.
```
| 0:VPAIMON_SCAN_NODE(116)
|
| table:
test_paimon_variant.test_paimon_spark.jni_complex_column_pruning
|
| predicates: (element_at(profile[#1], 'zip') >= 200000),
(element_at(element_at(events[#2], 1), 'score') >= 90),
(element_at(element_at(attributes[#3], 'primary'), 'code') = 20) |
| inputSplitNum=1, totalFileSize=0, scanRanges=1
|
| partition=1/0
|
| cardinality=2, numNodes=1
|
| nested columns:
|
| profile:
|
| origin type: struct<city:text,zip:int,street:text>
|
| pruned type: struct<city:text,zip:int>
|
| all access paths: [profile.city, profile.zip]
|
| predicate access paths: [profile.zip]
```
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [x] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
.../create_preinstalled_scripts/paimon/run14.sql | 35 ++++++
.../org/apache/doris/paimon/PaimonJniScanner.java | 19 ++--
.../doris/paimon/PaimonReadTypeProjection.java | 126 +++++++++++++++++++++
.../doris/paimon/PaimonReadTypeProjectionTest.java | 93 +++++++++++++++
.../nereids/rules/rewrite/SlotTypeReplacer.java | 8 +-
.../trees/plans/logical/LogicalFileScan.java | 12 --
.../plans/logical/SupportPruneNestedColumn.java | 7 --
.../trees/plans/logical/LogicalFileScanTest.java | 14 +--
.../test_paimon_jni_complex_column_pruning.out | 16 +++
.../test_paimon_jni_complex_column_pruning.groovy | 106 +++++++++++++++++
10 files changed, 391 insertions(+), 45 deletions(-)
diff --git
a/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run14.sql
b/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run14.sql
new file mode 100644
index 00000000000..392b0c99382
--- /dev/null
+++
b/docker/thirdparties/docker-compose/iceberg/scripts/create_preinstalled_scripts/paimon/run14.sql
@@ -0,0 +1,35 @@
+use paimon;
+create database if not exists test_paimon_spark;
+use test_paimon_spark;
+
+-- Dedicated append-only table for Doris Paimon JNI nested-column pruning
regressions.
+drop table if exists jni_complex_column_pruning;
+create table jni_complex_column_pruning (
+ id BIGINT,
+ profile STRUCT<city: STRING, zip: INT, street: STRING>,
+ events ARRAY<STRUCT<name: STRING, score: INT, note: STRING>>,
+ attributes MAP<STRING, STRUCT<code: INT, label: STRING, note: STRING>>
+) using paimon
+tblproperties (
+ 'file.format' = 'parquet'
+);
+
+insert into jni_complex_column_pruning values
+ (
+ 1,
+ struct('beijing', 100000, 'road-a'),
+ array(struct('login', 90, 'web'), struct('purchase', 70, 'app')),
+ map(
+ 'primary', struct(10, 'alpha', 'keep-primary'),
+ 'backup', struct(11, 'alpha-backup', 'keep-backup')
+ )
+ ),
+ (
+ 2,
+ struct('shanghai', 200000, 'road-b'),
+ array(struct('purchase', 95, 'app'), struct('logout', 60, 'web')),
+ map(
+ 'primary', struct(20, 'beta', 'keep-primary'),
+ 'backup', struct(21, 'beta-backup', 'keep-backup')
+ )
+ );
diff --git
a/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java
b/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java
index 03a12bc8245..2ef37f7645a 100644
---
a/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java
+++
b/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonJniScanner.java
@@ -203,7 +203,7 @@ public class PaimonJniScanner extends JniScanner {
int[] projected = getProjected();
List<DataField> readFields = new ArrayList<>(projected.length);
variantProjections = new ArrayList<>(projected.length);
- boolean hasVariantProjection = false;
+ boolean hasReadTypeProjection = false;
for (int outputIndex = 0; outputIndex < projected.length;
outputIndex++) {
DataField tableField =
table.rowType().getFields().get(projected[outputIndex]);
PaimonVariantProjection projection = tableField.type() instanceof
VariantType
@@ -212,17 +212,22 @@ public class PaimonJniScanner extends JniScanner {
: null;
variantProjections.add(projection);
if (projection == null) {
- readFields.add(tableField);
+ DataType projectedType = PaimonReadTypeProjection.project(
+ tableField.type(), types[outputIndex]);
+ if (!projectedType.equals(tableField.type())) {
+ hasReadTypeProjection = true;
+ }
+ readFields.add(tableField.newType(projectedType));
} else {
- hasVariantProjection = true;
+ hasReadTypeProjection = true;
readFields.add(tableField.newType(
projection.readType().copy(tableField.type().isNullable())));
}
}
- if (hasVariantProjection) {
- // Paimon recognizes a metadata-marked RowType as a list of
Variant extraction fields.
- // It prunes matching shredded Parquet fields per file and reads
the raw Variant value
- // as a correctness fallback for unshredded or non-matching files.
+ if (hasReadTypeProjection) {
+ // For static complex types, the recursively pruned read type
tells Paimon which nested
+ // ROW/ARRAY/MAP fields to read. For Variant, Paimon recognizes a
metadata-marked RowType
+ // as a list of extraction fields and falls back to the raw value
when needed.
RowType requestedReadType = new RowType(readFields);
readBuilder.withReadType(requestedReadType);
} else {
diff --git
a/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonReadTypeProjection.java
b/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonReadTypeProjection.java
new file mode 100644
index 00000000000..6304bfdabbe
--- /dev/null
+++
b/fe/be-java-extensions/paimon-connector/src/main/java/org/apache/doris/paimon/PaimonReadTypeProjection.java
@@ -0,0 +1,126 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.paimon;
+
+import org.apache.doris.common.jni.vec.ColumnType;
+
+import org.apache.paimon.types.ArrayType;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataType;
+import org.apache.paimon.types.MapType;
+import org.apache.paimon.types.RowType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Builds the Paimon read type that matches the nested column shape requested
by Doris.
+ *
+ * <p>The BE applies access paths to the JNI {@link ColumnType} before
creating this scanner. This
+ * class mirrors that shape with Paimon's types so {@code withReadType} can
push the same projection
+ * through ROW, ARRAY, and MAP readers while retaining Paimon's field IDs and
nullability.
+ */
+final class PaimonReadTypeProjection {
+ private PaimonReadTypeProjection() {
+ }
+
+ static DataType project(DataType tableType, ColumnType requiredType) {
+ if (requiredType.isStruct()) {
+ return projectRow(tableType, requiredType);
+ }
+ if (requiredType.isArray()) {
+ return projectArray(tableType, requiredType);
+ }
+ if (requiredType.isMap()) {
+ return projectMap(tableType, requiredType);
+ }
+ // Doris' scalar type can differ from Paimon's logical type (for
example timestamp
+ // precision). Keep the table type and only use ColumnType to describe
nested shape.
+ return tableType;
+ }
+
+ private static DataType projectRow(DataType tableType, ColumnType
requiredType) {
+ if (!(tableType instanceof RowType)) {
+ throw incompatibleType(requiredType, tableType);
+ }
+ RowType rowType = (RowType) tableType;
+ List<String> childNames = requiredType.getChildNames();
+ List<ColumnType> childTypes = requiredType.getChildTypes();
+ if (childNames == null || childTypes == null || childNames.size() !=
childTypes.size()) {
+ throw new IllegalArgumentException(
+ "Invalid Doris STRUCT projection for column " +
requiredType.getName());
+ }
+
+ List<DataField> projectedFields = new ArrayList<>(childNames.size());
+ for (int i = 0; i < childNames.size(); i++) {
+ DataField tableField = findField(rowType, childNames.get(i));
+ if (tableField == null) {
+ throw new IllegalArgumentException(String.format(
+ "Doris requested nested field '%s' which does not
exist in Paimon type %s",
+ childNames.get(i), rowType.asSQLString()));
+ }
+ DataType projectedType = project(tableField.type(),
childTypes.get(i));
+ projectedFields.add(tableField.newType(projectedType));
+ }
+ return rowType.copy(projectedFields);
+ }
+
+ private static DataType projectArray(DataType tableType, ColumnType
requiredType) {
+ if (!(tableType instanceof ArrayType)) {
+ throw incompatibleType(requiredType, tableType);
+ }
+ List<ColumnType> childTypes = requiredType.getChildTypes();
+ if (childTypes == null || childTypes.size() != 1) {
+ throw new IllegalArgumentException(
+ "Invalid Doris ARRAY projection for column " +
requiredType.getName());
+ }
+ ArrayType arrayType = (ArrayType) tableType;
+ return arrayType.newElementType(project(arrayType.getElementType(),
childTypes.get(0)));
+ }
+
+ private static DataType projectMap(DataType tableType, ColumnType
requiredType) {
+ if (!(tableType instanceof MapType)) {
+ throw incompatibleType(requiredType, tableType);
+ }
+ List<ColumnType> childTypes = requiredType.getChildTypes();
+ if (childTypes == null || childTypes.size() != 2) {
+ throw new IllegalArgumentException(
+ "Invalid Doris MAP projection for column " +
requiredType.getName());
+ }
+ MapType mapType = (MapType) tableType;
+ return mapType.newKeyValueType(
+ project(mapType.getKeyType(), childTypes.get(0)),
+ project(mapType.getValueType(), childTypes.get(1)));
+ }
+
+ private static DataField findField(RowType rowType, String requiredName) {
+ for (DataField field : rowType.getFields()) {
+ if (field.name().equalsIgnoreCase(requiredName)) {
+ return field;
+ }
+ }
+ return null;
+ }
+
+ private static IllegalArgumentException incompatibleType(
+ ColumnType requiredType, DataType tableType) {
+ return new IllegalArgumentException(String.format(
+ "Doris requested %s for column '%s', but the Paimon type is
%s",
+ requiredType.getType(), requiredType.getName(),
tableType.asSQLString()));
+ }
+}
diff --git
a/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonReadTypeProjectionTest.java
b/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonReadTypeProjectionTest.java
new file mode 100644
index 00000000000..bc4acf2e1bc
--- /dev/null
+++
b/fe/be-java-extensions/paimon-connector/src/test/java/org/apache/doris/paimon/PaimonReadTypeProjectionTest.java
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.doris.paimon;
+
+import org.apache.doris.common.jni.vec.ColumnType;
+
+import org.apache.paimon.types.ArrayType;
+import org.apache.paimon.types.DataField;
+import org.apache.paimon.types.DataTypes;
+import org.apache.paimon.types.MapType;
+import org.apache.paimon.types.RowType;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+public class PaimonReadTypeProjectionTest {
+ @Test
+ public void testProjectNestedRowArrayAndMap() {
+ RowType profileType = new RowType(false, Arrays.asList(
+ new DataField(2, "city", DataTypes.STRING(), "city
description"),
+ new DataField(3, "zip", DataTypes.INT())));
+ RowType eventType = new RowType(false, Arrays.asList(
+ new DataField(5, "score", DataTypes.INT()),
+ new DataField(6, "detail", DataTypes.STRING())));
+ RowType attributeType = new RowType(false, Arrays.asList(
+ new DataField(8, "code", DataTypes.BIGINT()),
+ new DataField(9, "detail", DataTypes.STRING())));
+ RowType tableType = new RowType(false, Arrays.asList(
+ new DataField(1, "profile", profileType, "profile
description"),
+ new DataField(4, "events", new ArrayType(false, eventType)),
+ new DataField(7, "attributes",
+ new MapType(false, DataTypes.STRING(),
attributeType))));
+ String requestedType = "struct<PROFILE:struct<city:string>,"
+ + "events:array<struct<score:int>>,"
+ + "attributes:map<string,struct<code:bigint>>>";
+ ColumnType requiredType = ColumnType.parseType("root", requestedType);
+
+ RowType projected = (RowType)
PaimonReadTypeProjection.project(tableType, requiredType);
+
+ Assert.assertFalse(projected.isNullable());
+ Assert.assertEquals(Arrays.asList("profile", "events", "attributes"),
+ projected.getFieldNames());
+
+ DataField profile = projected.getFields().get(0);
+ Assert.assertEquals(1, profile.id());
+ Assert.assertEquals("profile description", profile.description());
+ RowType projectedProfile = (RowType) profile.type();
+ Assert.assertFalse(projectedProfile.isNullable());
+ Assert.assertEquals(Arrays.asList("city"),
projectedProfile.getFieldNames());
+ Assert.assertEquals(2, projectedProfile.getFields().get(0).id());
+ Assert.assertEquals("city description",
+ projectedProfile.getFields().get(0).description());
+
+ ArrayType projectedEvents = (ArrayType) projected.getTypeAt(1);
+ Assert.assertFalse(projectedEvents.isNullable());
+ Assert.assertEquals(Arrays.asList("score"),
+ ((RowType) projectedEvents.getElementType()).getFieldNames());
+
+ MapType projectedAttributes = (MapType) projected.getTypeAt(2);
+ Assert.assertFalse(projectedAttributes.isNullable());
+ Assert.assertEquals(DataTypes.STRING(),
projectedAttributes.getKeyType());
+ Assert.assertEquals(Arrays.asList("code"),
+ ((RowType)
projectedAttributes.getValueType()).getFieldNames());
+ }
+
+ @Test
+ public void testRejectMissingNestedField() {
+ RowType tableType = new RowType(Arrays.asList(
+ new DataField(1, "known", DataTypes.INT())));
+ ColumnType requiredType = ColumnType.parseType("root",
"struct<missing:int>");
+
+ IllegalArgumentException exception = Assert.assertThrows(
+ IllegalArgumentException.class,
+ () -> PaimonReadTypeProjection.project(tableType,
requiredType));
+ Assert.assertTrue(exception.getMessage().contains("missing"));
+ }
+}
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
index de3de9adef6..c90d85d55e6 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/rules/rewrite/SlotTypeReplacer.java
@@ -723,18 +723,14 @@ public class SlotTypeReplacer extends
DefaultPlanRewriter<Void> {
}
private void tryRecordReplaceSlots(Plan plan, Object checkObj,
Set<Integer> shouldReplaceSlots) {
- if (checkObj instanceof SupportPruneNestedColumn) {
- SupportPruneNestedColumn supportPruneNestedColumn =
(SupportPruneNestedColumn) checkObj;
- if (!supportPruneNestedColumn.supportPruneNestedColumn()) {
- return;
- }
+ if (checkObj instanceof SupportPruneNestedColumn
+ && ((SupportPruneNestedColumn)
checkObj).supportPruneNestedColumn()) {
List<Slot> output = plan.getOutput();
boolean shouldPrune = false;
for (Slot slot : output) {
int slotId = slot.getExprId().asInt();
if ((slot.getDataType() instanceof NestedColumnPrunable
|| slot.getDataType().isVariantType())
- &&
supportPruneNestedColumn.supportPruneNestedColumn(slot.getDataType())
&& replacedDataTypes.containsKey(slotId)) {
shouldReplaceSlots.add(slotId);
shouldPrune = true;
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
index c6727bf93c1..53480d193ba 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScan.java
@@ -43,7 +43,6 @@ import org.apache.doris.nereids.trees.plans.Plan;
import org.apache.doris.nereids.trees.plans.PlanType;
import org.apache.doris.nereids.trees.plans.RelationId;
import org.apache.doris.nereids.trees.plans.visitor.PlanVisitor;
-import org.apache.doris.nereids.types.DataType;
import org.apache.doris.nereids.util.Utils;
import org.apache.doris.qe.ConnectContext;
import org.apache.doris.qe.SessionVariable;
@@ -358,17 +357,6 @@ public class LogicalFileScan extends
LogicalCatalogRelation implements SupportPr
return false;
}
- @Override
- public boolean supportPruneNestedColumn(DataType dataType) {
- ExternalTable table = getTable();
- if (table instanceof PaimonExternalTable || table instanceof
PaimonSysExternalTable) {
- // Paimon JNI currently supports nested projection only for
Variant. Its static complex
- // types still return the full value, which would misalign pruned
ROW/ARRAY/MAP slots.
- return dataType.isVariantType();
- }
- return supportPruneNestedColumn();
- }
-
private boolean hasSameSnapshot(Optional<TableSnapshot> left,
Optional<TableSnapshot> right) {
if (!left.isPresent() || !right.isPresent()) {
return left.isPresent() == right.isPresent();
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/SupportPruneNestedColumn.java
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/SupportPruneNestedColumn.java
index 9776bb920a5..44f1b733dd1 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/SupportPruneNestedColumn.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/nereids/trees/plans/logical/SupportPruneNestedColumn.java
@@ -17,15 +17,8 @@
package org.apache.doris.nereids.trees.plans.logical;
-import org.apache.doris.nereids.types.DataType;
-
/** SupportPruneNestedColumn */
public interface SupportPruneNestedColumn {
// return false will not prune the nested column
boolean supportPruneNestedColumn();
-
- // Allows a scan implementation to restrict pruning to selected root types.
- default boolean supportPruneNestedColumn(DataType dataType) {
- return supportPruneNestedColumn();
- }
}
diff --git
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScanTest.java
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScanTest.java
index 5fc6d422e09..eaaa24ebaf0 100644
---
a/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScanTest.java
+++
b/fe/fe-core/src/test/java/org/apache/doris/nereids/trees/plans/logical/LogicalFileScanTest.java
@@ -34,12 +34,6 @@ import org.apache.doris.nereids.trees.expressions.Slot;
import org.apache.doris.nereids.trees.expressions.StatementScopeIdGenerator;
import org.apache.doris.nereids.trees.plans.RelationId;
import
org.apache.doris.nereids.trees.plans.logical.LogicalFileScan.SelectedPartitions;
-import org.apache.doris.nereids.types.ArrayType;
-import org.apache.doris.nereids.types.IntegerType;
-import org.apache.doris.nereids.types.MapType;
-import org.apache.doris.nereids.types.StructField;
-import org.apache.doris.nereids.types.StructType;
-import org.apache.doris.nereids.types.VariantType;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@@ -105,7 +99,7 @@ public class LogicalFileScanTest {
}
@Test
- public void testPaimonSupportsOnlyVariantNestedColumnPruning() {
+ public void testPaimonSupportsNestedColumnPruning() {
PaimonExternalTable table = Mockito.mock(PaimonExternalTable.class);
Mockito.when(table.getName()).thenReturn("paimon_tbl");
TableScanParams scanParams = new TableScanParams(
@@ -119,12 +113,6 @@ public class LogicalFileScanTest {
Optional.empty(), Optional.empty(), Optional.of(scanParams),
Optional.empty());
Assertions.assertTrue(scan.supportPruneNestedColumn());
-
Assertions.assertTrue(scan.supportPruneNestedColumn(VariantType.INSTANCE));
-
Assertions.assertFalse(scan.supportPruneNestedColumn(ArrayType.of(IntegerType.INSTANCE)));
- Assertions.assertFalse(scan.supportPruneNestedColumn(
- MapType.of(IntegerType.INSTANCE, IntegerType.INSTANCE)));
- Assertions.assertFalse(scan.supportPruneNestedColumn(new
StructType(Collections.singletonList(
- new StructField("field", IntegerType.INSTANCE, true, "")))));
}
@Test
diff --git
a/regression-test/data/external_table_p0/paimon/test_paimon_jni_complex_column_pruning.out
b/regression-test/data/external_table_p0/paimon/test_paimon_jni_complex_column_pruning.out
new file mode 100644
index 00000000000..cfddfee825c
--- /dev/null
+++
b/regression-test/data/external_table_p0/paimon/test_paimon_jni_complex_column_pruning.out
@@ -0,0 +1,16 @@
+-- This file is automatically generated. You should know what you did if you
want to edit this
+-- !struct_projection --
+1 beijing
+2 shanghai
+
+-- !array_projection --
+1 90
+2 95
+
+-- !map_projection --
+1 10
+2 20
+
+-- !combined_projection --
+2 shanghai purchase beta
+
diff --git
a/regression-test/suites/external_table_p0/paimon/test_paimon_jni_complex_column_pruning.groovy
b/regression-test/suites/external_table_p0/paimon/test_paimon_jni_complex_column_pruning.groovy
new file mode 100644
index 00000000000..ac407998525
--- /dev/null
+++
b/regression-test/suites/external_table_p0/paimon/test_paimon_jni_complex_column_pruning.groovy
@@ -0,0 +1,106 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+suite("test_paimon_jni_complex_column_pruning",
+ "p0,external,doris,external_docker,external_docker_doris") {
+ String enabled = context.config.otherConfigs.get("enablePaimonTest")
+ if (enabled == null || !enabled.equalsIgnoreCase("true")) {
+ logger.info("disable paimon test")
+ return
+ }
+
+ String minioPort = context.config.otherConfigs.get("iceberg_minio_port")
+ String externalEnvIp = context.config.otherConfigs.get("externalEnvIp")
+ String catalogName = "test_paimon_jni_complex_column_pruning"
+
+ try {
+ sql "drop catalog if exists ${catalogName}"
+ sql """
+ create catalog ${catalogName} properties (
+ 'type' = 'paimon',
+ 'paimon.catalog.type' = 'filesystem',
+ 'warehouse' = 's3://warehouse/wh',
+ 's3.endpoint' = 'http://${externalEnvIp}:${minioPort}',
+ 's3.access_key' = 'admin',
+ 's3.secret_key' = 'password',
+ 's3.region' = 'us-east-1',
+ 's3.path.style.access' = 'true'
+ )
+ """
+ sql "use `${catalogName}`.`test_paimon_spark`"
+ sql "set force_jni_scanner = true"
+
+ explain {
+ sql "select id, profile.city from jni_complex_column_pruning order
by id"
+ contains "paimonNativeReadSplits=0/1"
+ contains "pruned type:"
+ contains "all access paths: [profile.city]"
+ }
+
+ explain {
+ sql "select id, events[1].score from jni_complex_column_pruning
order by id"
+ contains "paimonNativeReadSplits=0/1"
+ contains "pruned type:"
+ contains "all access paths: [events.*.score]"
+ }
+
+ explain {
+ sql """
+ select id, element_at(attributes, 'primary').code
+ from jni_complex_column_pruning
+ order by id
+ """
+ contains "paimonNativeReadSplits=0/1"
+ contains "pruned type:"
+ contains "all access paths: [attributes.*.code]"
+ }
+
+ order_qt_struct_projection """
+ select id, profile.city
+ from jni_complex_column_pruning
+ order by id
+ """
+
+ order_qt_array_projection """
+ select id, events[1].score
+ from jni_complex_column_pruning
+ order by id
+ """
+
+ order_qt_map_projection """
+ select id, element_at(attributes, 'primary').code
+ from jni_complex_column_pruning
+ order by id
+ """
+
+ // Different projected and predicate children must be combined into
one requested read type.
+ order_qt_combined_projection """
+ select id,
+ profile.city,
+ events[1].name,
+ element_at(attributes, 'primary').label
+ from jni_complex_column_pruning
+ where profile.zip >= 200000
+ and events[1].score >= 90
+ and element_at(attributes, 'primary').code = 20
+ order by id
+ """
+ } finally {
+ sql "set force_jni_scanner = false"
+ sql "drop catalog if exists ${catalogName}"
+ }
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]