This is an automated email from the ASF dual-hosted git repository.

korlov pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git


The following commit(s) were added to refs/heads/main by this push:
     new c0e44e66946 IGNITE-28469 Sql. Partition pruning doesn't work for 
temporal types and dynamic params (#7941)
c0e44e66946 is described below

commit c0e44e669463ab67b6d4aba3028559e6aeafab20
Author: korlov42 <[email protected]>
AuthorDate: Thu Apr 9 12:01:57 2026 +0300

    IGNITE-28469 Sql. Partition pruning doesn't work for temporal types and 
dynamic params (#7941)
---
 .../pruning/PartitionPruningMetadataExtractor.java |   9 +
 .../sql/engine/exec/PartitionPruningTest.java      | 186 +++++++++++++++++++++
 .../sql/engine/exec/TransactionEnlistTest.java     |  76 +++++----
 .../internal/sql/engine/util/SqlTestUtils.java     |  33 ++++
 4 files changed, 269 insertions(+), 35 deletions(-)

diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
index d6372817252..ac6cdbd06fb 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/prepare/pruning/PartitionPruningMetadataExtractor.java
@@ -54,6 +54,7 @@ import 
org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
 import org.apache.ignite.internal.sql.engine.schema.IgniteTable;
 import org.apache.ignite.internal.sql.engine.trait.IgniteDistribution;
 import org.apache.ignite.internal.sql.engine.util.Commons;
+import org.apache.ignite.internal.sql.engine.util.RexUtils;
 import org.apache.ignite.internal.tostring.S;
 import org.jetbrains.annotations.Nullable;
 import org.jetbrains.annotations.VisibleForTesting;
@@ -348,6 +349,10 @@ public class PartitionPruningMetadataExtractor extends 
IgniteRelShuttle {
                     rhs = operands.get(0);
                 }
 
+                if (RexUtils.isLosslessCast(rhs)) {
+                    rhs = ((RexCall) rhs).getOperands().get(0);
+                }
+
                 if (isColocationKey(lhs, keys) && isValueExpr(rhs)) {
                     if (negate) {
                         return Result.UNKNOWN;
@@ -376,6 +381,10 @@ public class PartitionPruningMetadataExtractor extends 
IgniteRelShuttle {
                     rhs = operands.get(0);
                 }
 
+                if (RexUtils.isLosslessCast(rhs)) {
+                    rhs = ((RexCall) rhs).getOperands().get(0);
+                }
+
                 if (isColocationKey(lhs, keys) && isValueExpr(rhs)) {
                     // NOT(colo_key != <val>) => colo_key = <val>
                     if (negate) {
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/PartitionPruningTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/PartitionPruningTest.java
new file mode 100644
index 00000000000..d5e232bf9d8
--- /dev/null
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/PartitionPruningTest.java
@@ -0,0 +1,186 @@
+/*
+ * 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.ignite.internal.sql.engine.exec;
+
+import static org.apache.ignite.internal.lang.IgniteStringFormatter.format;
+import static 
org.apache.ignite.internal.sql.engine.exec.TransactionEnlistTest.blackhole;
+import static 
org.apache.ignite.internal.sql.engine.util.SqlTestUtils.toSqlType;
+import static org.apache.ignite.internal.sql.engine.util.TypeUtils.toInternal;
+import static org.apache.ignite.internal.testframework.IgniteTestUtils.await;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.argThat;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.ignite.internal.replicator.ZonePartitionId;
+import org.apache.ignite.internal.sql.engine.framework.DataProvider;
+import org.apache.ignite.internal.sql.engine.framework.NoOpTransaction;
+import org.apache.ignite.internal.sql.engine.framework.TestBuilders;
+import org.apache.ignite.internal.sql.engine.framework.TestCluster;
+import org.apache.ignite.internal.sql.engine.framework.TestNode;
+import org.apache.ignite.internal.sql.engine.planner.datatypes.utils.Types;
+import org.apache.ignite.internal.sql.engine.schema.PartitionCalculator;
+import org.apache.ignite.internal.sql.engine.util.QueryCheckerExtension;
+import org.apache.ignite.internal.sql.engine.util.SqlTestUtils;
+import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
+import org.apache.ignite.internal.type.NativeType;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.mockito.ArgumentMatcher;
+import org.mockito.Mockito;
+
+/**
+ * Various tests regarding partition pruning.
+ */
+@ExtendWith(QueryCheckerExtension.class)
+public class PartitionPruningTest extends BaseIgniteAbstractTest {
+    private static final List<String> DATA_NODES = List.of("DATA_1", "DATA_2");
+    private static final String GATEWAY_NODE_NAME = "gateway";
+    private static final int PARTITIONS_COUNT = 5;
+
+    private TestCluster cluster;
+
+    @BeforeAll
+    static void warmUpCluster() throws Exception {
+        TestBuilders.warmupTestCluster();
+    }
+
+    @BeforeEach
+    void startCluster() {
+        cluster = TestBuilders.cluster()
+                .nodes(GATEWAY_NODE_NAME, DATA_NODES.toArray(new String[0]))
+                .build();
+
+        cluster.start();
+    }
+
+    @AfterEach
+    void stopCluster() throws Exception {
+        cluster.stop();
+    }
+
+    private static Stream<Arguments> temporalTypes() {
+        return Stream.of(Types.TIME_0, Types.TIME_3, Types.TIMESTAMP_0, 
Types.TIMESTAMP_3, Types.TIMESTAMP_WLTZ_0, Types.TIMESTAMP_WLTZ_3)
+                .map(Arguments::of);
+    }
+
+    @ParameterizedTest
+    @MethodSource("temporalTypes")
+    void testPartitionPruningForScanOnlyWithTemporalTypes(NativeType type) {
+        TestNode gatewayNode = cluster.node(GATEWAY_NODE_NAME);
+        String tableName = prepareTemporalTable(type);
+        Object key = 
Objects.requireNonNull(SqlTestUtils.generateValueByType(type));
+
+        int expectedPartition = expectedPartition(key, type);
+
+        // Override data provider to throw an exception whenever unexpected 
partition is requested.
+        cluster.setDataProvider(tableName, TestBuilders.tableScan((nodeName, 
partId) -> {
+            if (expectedPartition != partId) {
+                throw new RuntimeException("Requested unexpected partition 
[expectedPartition="
+                        + expectedPartition + ", requestedPartition=" + partId 
+ "]");
+            }
+
+            return Collections.singleton(new Object[]{partId, toInternal(key, 
type.spec()), nodeName});
+        }));
+
+        // Expect no exception to be thrown.
+        await(gatewayNode.executeQuery(
+                "SELECT node FROM " + tableName + " WHERE id_ts = CAST(? AS " 
+ toSqlType(type) + ")", key
+        ).requestNextAsync(128));
+    }
+
+    @ParameterizedTest
+    @MethodSource("temporalTypes")
+    void testPartitionPruningForUpdateWithTemporalTypes(NativeType type) {
+        TestNode gatewayNode = cluster.node(GATEWAY_NODE_NAME);
+        String tableName = prepareTemporalTable(type);
+        NoOpTransaction tx = Mockito.spy(NoOpTransaction.readWrite("t1", 
false));
+        Object key = 
Objects.requireNonNull(SqlTestUtils.generateValueByType(type));
+
+        await(gatewayNode.executeQuery(
+                tx,
+                "UPDATE " + tableName + " /*+ no_index */ SET node = 
UPPER(node) WHERE id_ts = CAST(? AS " + toSqlType(type) + ")",
+                key
+        ).requestNextAsync(1));
+
+        int expectedPartition = expectedPartition(key, type);
+        {
+            ArgumentMatcher<ZonePartitionId> partitionIdMatch =
+                    zonePartitionId -> zonePartitionId.partitionId() == 
expectedPartition;
+            // We expect commit partitions to be assigned once for given 
transaction.
+            Mockito.verify(tx, times(1))
+                    .assignCommitPartition(argThat(partitionIdMatch));
+            // Individual partition on the other hand will be enlisted for 
every source.
+            // In this particular case -- first time for scan and second for 
Modify node.
+            Mockito.verify(tx, times(2))
+                    .enlist(argThat(partitionIdMatch), anyInt(), any(), 
anyLong());
+        }
+
+        {
+            // Due to partition pruning we don't expect any more enlistment.
+            // We should not try to assign other partition as commit partition 
as well.
+            ArgumentMatcher<ZonePartitionId> partitionIdMismatch =
+                    zonePartitionId -> zonePartitionId.partitionId() != 
expectedPartition;
+            Mockito.verify(tx, never())
+                    .assignCommitPartition(argThat(partitionIdMismatch));
+            Mockito.verify(tx, never())
+                    .enlist(argThat(partitionIdMismatch), anyInt(), any(), 
anyLong());
+        }
+    }
+
+    private static int expectedPartition(Object key, NativeType type) {
+        var calculator = new PartitionCalculator(PARTITIONS_COUNT, new 
NativeType[] {type});
+        calculator.append(key);
+        return calculator.partition();
+    }
+
+    private String prepareTemporalTable(NativeType pkColumnType) {
+        String tableName = (pkColumnType.spec() + 
"_table").toUpperCase(Locale.ROOT);
+
+        //noinspection ConcatenationWithEmptyString
+        cluster.node(GATEWAY_NODE_NAME).initSchema(""
+                + "CREATE ZONE my_zone (partitions " + PARTITIONS_COUNT + ") 
STORAGE PROFILES ['default'];"
+                + format(
+                        "CREATE TABLE {} (id int, id_ts {}, node VARCHAR(128), 
PRIMARY KEY (id, id_ts))",
+                        tableName, toSqlType(pkColumnType)
+                )
+                + " COLOCATE BY (id_ts) ZONE my_zone;");
+
+        cluster.setAssignmentsProvider(tableName, (partitionCount, b) -> 
IntStream.range(0, partitionCount)
+                .mapToObj(i -> DATA_NODES)
+                .collect(Collectors.toList()));
+        cluster.setDataProvider(tableName, 
TestBuilders.tableScan(DataProvider.fromCollection(List.of())));
+        cluster.setUpdatableTable(tableName, blackhole());
+
+        return tableName;
+    }
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/TransactionEnlistTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/TransactionEnlistTest.java
index 88a4e5848ea..92e47b9c812 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/TransactionEnlistTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/exec/TransactionEnlistTest.java
@@ -74,12 +74,14 @@ public class TransactionEnlistTest extends 
BaseIgniteAbstractTest {
     @InjectQueryCheckerFactory
     private static QueryCheckerFactory queryCheckerFactory;
 
-    private static final TestCluster CLUSTER = TestBuilders.cluster()
-            .nodes(NODE_NAME1)
-            .build(); // add method use table partitions
+    private static TestCluster CLUSTER;
 
     @BeforeAll
      static void startCluster() {
+        CLUSTER = TestBuilders.cluster()
+                .nodes(NODE_NAME1)
+                .build();
+
         CLUSTER.start();
 
         //noinspection ConcatenationWithEmptyString
@@ -91,38 +93,7 @@ public class TransactionEnlistTest extends 
BaseIgniteAbstractTest {
                 .mapToObj(i -> List.of("N1"))
                 .collect(Collectors.toList()));
         CLUSTER.setDataProvider("T1", 
TestBuilders.tableScan(DataProvider.fromCollection(List.of())));
-        CLUSTER.setUpdatableTable("T1", new UpdatableTable() {
-            @Override
-            public TableDescriptor descriptor() {
-                return null;
-            }
-
-            @Override
-            public <RowT> CompletableFuture<?> 
insertAll(ExecutionContext<RowT> ectx, List<RowT> rows, ColocationGroup 
colocationGroup) {
-                return nullCompletedFuture();
-            }
-
-            @Override
-            public <RowT> CompletableFuture<Void> insert(@Nullable 
InternalTransaction explicitTx, ExecutionContext<RowT> ectx, RowT row) {
-                return nullCompletedFuture();
-            }
-
-            @Override
-            public <RowT> CompletableFuture<?> 
upsertAll(ExecutionContext<RowT> ectx, List<RowT> rows, ColocationGroup 
colocationGroup) {
-                return nullCompletedFuture();
-            }
-
-            @Override
-            public <RowT> CompletableFuture<Boolean> delete(@Nullable 
InternalTransaction explicitTx, ExecutionContext<RowT> ectx,
-                    RowT key) {
-                return nullCompletedFuture();
-            }
-
-            @Override
-            public <RowT> CompletableFuture<?> 
deleteAll(ExecutionContext<RowT> ectx, List<RowT> rows, ColocationGroup 
colocationGroup) {
-                return nullCompletedFuture();
-            }
-        });
+        CLUSTER.setUpdatableTable("T1", blackhole());
     }
 
     @AfterAll
@@ -267,4 +238,39 @@ public class TransactionEnlistTest extends 
BaseIgniteAbstractTest {
         calculator.append(key);
         return calculator.partition();
     }
+
+    static UpdatableTable blackhole() {
+        return new UpdatableTable() {
+            @Override
+            public TableDescriptor descriptor() {
+                return null;
+            }
+
+            @Override
+            public <RowT> CompletableFuture<?> 
insertAll(ExecutionContext<RowT> ectx, List<RowT> rows, ColocationGroup 
colocationGroup) {
+                return nullCompletedFuture();
+            }
+
+            @Override
+            public <RowT> CompletableFuture<Void> insert(@Nullable 
InternalTransaction explicitTx, ExecutionContext<RowT> ectx, RowT row) {
+                return nullCompletedFuture();
+            }
+
+            @Override
+            public <RowT> CompletableFuture<?> 
upsertAll(ExecutionContext<RowT> ectx, List<RowT> rows, ColocationGroup 
colocationGroup) {
+                return nullCompletedFuture();
+            }
+
+            @Override
+            public <RowT> CompletableFuture<Boolean> delete(@Nullable 
InternalTransaction explicitTx, ExecutionContext<RowT> ectx,
+                    RowT key) {
+                return nullCompletedFuture();
+            }
+
+            @Override
+            public <RowT> CompletableFuture<?> 
deleteAll(ExecutionContext<RowT> ectx, List<RowT> rows, ColocationGroup 
colocationGroup) {
+                return nullCompletedFuture();
+            }
+        };
+    }
 }
diff --git 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
index bad795d27b1..8a92df313be 100644
--- 
a/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
+++ 
b/modules/sql-engine/src/testFixtures/java/org/apache/ignite/internal/sql/engine/util/SqlTestUtils.java
@@ -236,6 +236,39 @@ public class SqlTestUtils {
         return type.getSpaceName();
     }
 
+    /**
+     * Convert {@link NativeType} to string representation of SQL type.
+     *
+     * @param igniteType Ignite type.
+     * @return String representation of SQL type.
+     */
+    public static String toSqlType(NativeType igniteType) {
+        SqlTypeName type = 
COLUMN_TYPE_TO_SQL_TYPE_NAME_MAP.get(igniteType.spec());
+
+        if (type == null) {
+            throw new IllegalArgumentException("Unsupported type " + 
igniteType);
+        }
+
+        if (type == SqlTypeName.TIMESTAMP_WITH_LOCAL_TIME_ZONE) {
+            return format("TIMESTAMP({}) WITH LOCAL TIME ZONE", 
((TemporalNativeType) igniteType).precision());
+        }
+
+        if (igniteType instanceof DecimalNativeType) {
+            var decimalType = (DecimalNativeType) igniteType;
+            return format("{}({},{})", type.getSpaceName(), 
decimalType.precision(), decimalType.scale());
+        }
+
+        if (igniteType instanceof TemporalNativeType) {
+            return format("{}({})", type.getSpaceName(), ((TemporalNativeType) 
igniteType).precision());
+        }
+
+        if (igniteType instanceof VarlenNativeType) {
+            return format("{}({})", type.getSpaceName(), ((VarlenNativeType) 
igniteType).length());
+        }
+
+        return type.getSpaceName();
+    }
+
     /**
      * Generate random value for given type.
      *

Reply via email to