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

amashenkov 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 4419ceb7768 IGNITE-28510 Sql. Table hints are not applied. (#7972)
4419ceb7768 is described below

commit 4419ceb7768c31625150585dec6fff7ff163847e
Author: amashenkov <[email protected]>
AuthorDate: Tue Apr 14 13:03:32 2026 +0300

    IGNITE-28510 Sql. Table hints are not applied. (#7972)
---
 .../ignite/internal/sql/engine/util/Commons.java   |  23 +++-
 .../planner/hints/HintPropagationPlannerTest.java  | 128 +++++++++++++++++++++
 .../engine/planner/hints/IndexHintPlannerTest.java |  16 ++-
 .../planner/hints/NoIndexHintPlannerTest.java      |  10 +-
 4 files changed, 165 insertions(+), 12 deletions(-)

diff --git 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
index 1fde9c85a07..42fb9d26a07 100644
--- 
a/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
+++ 
b/modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java
@@ -19,6 +19,7 @@ package org.apache.ignite.internal.sql.engine.util;
 
 import static org.apache.calcite.rel.hint.HintPredicates.AGGREGATE;
 import static org.apache.calcite.rel.hint.HintPredicates.JOIN;
+import static org.apache.calcite.rel.hint.HintPredicates.TABLE_SCAN;
 import static 
org.apache.ignite.internal.sql.engine.prepare.PlanningContext.CLUSTER;
 import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
 import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
@@ -55,7 +56,10 @@ import org.apache.calcite.plan.RelTraitSet;
 import org.apache.calcite.rel.RelCollationTraitDef;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.JoinInfo;
+import org.apache.calcite.rel.hint.HintPredicate;
+import org.apache.calcite.rel.hint.HintPredicates;
 import org.apache.calcite.rel.hint.HintStrategyTable;
+import org.apache.calcite.rel.hint.Hintable;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -93,7 +97,6 @@ import 
org.apache.ignite.internal.sql.engine.prepare.IgniteConvertletTable;
 import org.apache.ignite.internal.sql.engine.prepare.IgniteTypeCoercion;
 import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
 import org.apache.ignite.internal.sql.engine.rel.IgniteProject;
-import 
org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlCommitTransaction;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlConformance;
 import org.apache.ignite.internal.sql.engine.sql.IgniteSqlKill;
@@ -164,8 +167,8 @@ public final class Commons {
                                     
.hintStrategy(IgniteHint.ENFORCE_JOIN_ORDER.name(), JOIN)
                                     
.hintStrategy(IgniteHint.DISABLE_RULE.name(), (hint, rel) -> true)
                                     
.hintStrategy(IgniteHint.EXPAND_DISTINCT_AGG.name(), AGGREGATE)
-                                    .hintStrategy(IgniteHint.NO_INDEX.name(), 
(hint, rel) -> rel instanceof IgniteLogicalTableScan)
-                                    
.hintStrategy(IgniteHint.FORCE_INDEX.name(), (hint, rel) -> rel instanceof 
IgniteLogicalTableScan)
+                                    .hintStrategy(IgniteHint.NO_INDEX.name(), 
indexHintPropagationStrategy())
+                                    
.hintStrategy(IgniteHint.FORCE_INDEX.name(), indexHintPropagationStrategy())
                                     
.hintStrategy(IgniteHint.DISABLE_DECORRELATION.name(), (hint, rel) -> true)
                                     .build()
                     )
@@ -188,6 +191,18 @@ public final class Commons {
             .traitDefs(DISTRIBUTED_TRAITS_SET)
             .build();
 
+    private static HintPredicate indexHintPropagationStrategy() {
+        return HintPredicates.and(
+                TABLE_SCAN,
+                (hint, rel) -> ((Hintable) rel).getHints().stream()
+                        .filter(h -> 
h.hintName.equals(IgniteHint.FORCE_INDEX.name())
+                                || 
h.hintName.equals(IgniteHint.NO_INDEX.name()))
+                        // Ignore all that overlaps existed hint.
+                        // See RelHint javadoc.
+                        .filter(h -> h.inheritPath.size() < 
hint.inheritPath.size())
+                        .findAny().isEmpty());
+    }
+
     private static volatile @Nullable Boolean fastOptimizationsEnabled = null;
 
     private Commons() {
@@ -330,7 +345,7 @@ public final class Commons {
     /**
      * Flattens a list of lists into a single list containing all elements 
from the nested lists.
      *
-     * <p>This method takes a source list where each element is itself a list 
and combines 
+     * <p>This method takes a source list where each element is itself a list 
and combines
      * all the nested lists into a single list containing all their elements 
in order.
      *
      * <p>For example:
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/HintPropagationPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/HintPropagationPlannerTest.java
new file mode 100644
index 00000000000..dac98725514
--- /dev/null
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/HintPropagationPlannerTest.java
@@ -0,0 +1,128 @@
+/*
+ * 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.planner.hints;
+
+import java.util.function.UnaryOperator;
+import 
org.apache.ignite.internal.sql.engine.framework.TestBuilders.TableBuilder;
+import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest;
+import org.apache.ignite.internal.sql.engine.rel.AbstractIgniteJoin;
+import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
+import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
+import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
+import org.apache.ignite.internal.type.NativeTypes;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Planner tests for index/no_index hints propagation.
+ */
+public class HintPropagationPlannerTest extends AbstractPlannerTest {
+    private static IgniteSchema SCHEMA;
+
+    private static final String TBL1 = "TBL1";
+
+    private static final String TBL2 = "TBL2";
+
+    @BeforeAll
+    public static void setup() {
+        SCHEMA = createSchemaFrom(
+                createSimpleTable(TBL1, 100)
+                        .andThen(addHashIndex("ID"))
+                        .andThen(addSortIndex("VAL1"))
+                        .andThen(addSortIndex("VAL2", "VAL3"))
+                        .andThen(addSortIndex("VAL3")),
+                createSimpleTable(TBL2, 100_000)
+                        .andThen(addHashIndex("ID"))
+                        .andThen(addSortIndex("VAL1"))
+                        .andThen(addSortIndex("VAL2"))
+                        .andThen(addSortIndex("VAL3"))
+        );
+    }
+
+    @Test
+    public void usingDifferentIndexesForSameTable() throws Exception {
+        var sql = "SELECT * FROM"
+                + " (SELECT t1.val1, t1.val2, t1.val3 FROM tbl1 /*+ NO_INDEX 
*/ as t1"
+                + " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val3) */ as t2 ON 
(t1.val2 = t2.val2 AND t2.val2 > 'x')) as t"
+                + " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2) */ as t3 ON 
(t.val3 = t3.val3 AND t3.val3 < 'a')";
+
+        assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
+                .and(input(0, 
nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
+                        .and(input(0, 
nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
+                        .and(input(1, nodeOrAnyChild(isIndexScan(TBL2, 
"IDX_VAL3")
+                                .and(scan -> scan.searchBounds() == null)
+                                .and(scan -> ">($t0, 
_UTF-8'x')".equals(scan.condition().toString()))
+                        )))
+                )))
+                .and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL2")
+                        .and(scan -> scan.searchBounds() == null)
+                        .and(scan -> "<($t3, 
_UTF-8'a')".equals(scan.condition().toString()))
+                )))
+        );
+
+        sql = "SELECT * FROM"
+                + " (SELECT /*+ NO_INDEX */ t1.val1, t1.val2, t1.val3 FROM 
tbl1 as t1 LEFT JOIN tbl2 as t2 ON (t1.val2 = t2.val2)) as t"
+                + " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2) */ as t3 ON 
(t.val3 = t3.val3)";
+
+        assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
+                .and(input(0, 
nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
+                        .and(input(0, 
nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
+                        .and(input(1, 
nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
+                )))
+                .and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL2"))))
+        );
+    }
+
+    @Test
+    public void testHintOverriding() throws Exception {
+        var sql = "SELECT /*+ NO_INDEX */ * FROM"
+                + " (SELECT t1.val1, t1.val2, t1.val3 FROM tbl1 as t1 LEFT 
JOIN tbl2 as t2 ON (t1.val2 = t2.val2)) as t"
+                + " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2_val3) */ as t3 ON 
(t.val3 = t3.val3)";
+
+        assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
+                .and(input(0, 
nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
+                        .and(input(0, 
nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
+                        .and(input(1, 
nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
+                )))
+                .and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "idx_val3"))))
+        );
+
+        sql = "SELECT /*+ FORCE_INDEX(idx_val3) */ * FROM"
+                + " (SELECT t1.val1, t1.val2, t1.val3 FROM tbl1 /*+ NO_INDEX 
*/ as t1 "
+                + " LEFT JOIN tbl2 as t2 ON (t1.val2 = t2.val2)) as t"
+                + " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2) */as t3 ON 
(t.val3 = t3.val3)";
+
+        assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
+                .and(input(0, 
nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
+                        .and(input(0, 
nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
+                        .and(input(1, nodeOrAnyChild(isIndexScan(TBL2, 
"IDX_VAL3"))))
+                )))
+                .and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL2"))))
+        );
+    }
+
+    private static UnaryOperator<TableBuilder> createSimpleTable(String name, 
int sz) {
+        return t -> t.name(name)
+                .size(sz)
+                .distribution(IgniteDistributions.single())
+                .addKeyColumn("ID", NativeTypes.INT32)
+                .addColumn("VAL1", NativeTypes.INT32)
+                .addColumn("VAL2", NativeTypes.STRING)
+                .addColumn("VAL3", NativeTypes.STRING);
+    }
+}
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/IndexHintPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/IndexHintPlannerTest.java
index 3c1b662fb29..510a1b9c02f 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/IndexHintPlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/IndexHintPlannerTest.java
@@ -148,15 +148,21 @@ public class IndexHintPlannerTest extends 
AbstractPlannerTest {
 
     @Test
     public void testSingleTable() throws Exception {
-        var sql = "SELECT /*+ FORCE_INDEX({}) */ * FROM TBL1 WHERE val2 = 'v' 
AND val3 = 'v'";
+        var sql1 = "SELECT /*+ FORCE_INDEX({}) */ * FROM TBL1 WHERE val2 = 'v' 
AND val3 = 'v'";
+        var sql2 = "SELECT * FROM TBL1 /*+ FORCE_INDEX({}) */ WHERE val2 = 'v' 
AND val3 = 'v'";
 
-        assertCertainIndex(format(sql, ""), TBL1, "IDX_VAL2_VAL3");
+        assertCertainIndex(format(sql1, ""), TBL1, "IDX_VAL2_VAL3");
+        assertCertainIndex(format(sql2, ""), TBL1, "IDX_VAL2_VAL3");
 
-        assertPlan(format(sql, "IDX_VAL2_VAL3, IDX_VAL3"), SCHEMA, 
nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL2_VAL3")
+        assertPlan(format(sql1, "IDX_VAL1, IDX_VAL3"), SCHEMA, 
nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL1")
+                .or(isIndexScan(TBL1, "IDX_VAL3"))));
+        assertPlan(format(sql2, "IDX_VAL1, IDX_VAL3"), SCHEMA, 
nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL1")
                 .or(isIndexScan(TBL1, "IDX_VAL3"))));
 
-        assertPlan("SELECT /*+ FORCE_INDEX(IDX_VAL2_VAL3), 
FORCE_INDEX(IDX_VAL3) */ * FROM TBL1 WHERE val2 = 'v' AND val3 = 'v'", SCHEMA,
-                nodeOrAnyChild(isIndexScan(TBL1, 
"IDX_VAL2_VAL3").or(isIndexScan(TBL1, "IDX_VAL3"))));
+        assertPlan("SELECT /*+ FORCE_INDEX(IDX_VAL1), FORCE_INDEX(IDX_VAL3) */ 
* FROM TBL1 WHERE val2 = 'v' AND val3 = 'v'", SCHEMA,
+                nodeOrAnyChild(isIndexScan(TBL1, 
"IDX_VAL1").or(isIndexScan(TBL1, "IDX_VAL3"))));
+        assertPlan("SELECT * FROM TBL1 /*+ FORCE_INDEX(IDX_VAL1), 
FORCE_INDEX(IDX_VAL3) */  WHERE val2 = 'v' AND val3 = 'v'", SCHEMA,
+                nodeOrAnyChild(isIndexScan(TBL1, 
"IDX_VAL1").or(isIndexScan(TBL1, "IDX_VAL3"))));
     }
 
     @Test
diff --git 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/NoIndexHintPlannerTest.java
 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/NoIndexHintPlannerTest.java
index 313dd52ab5a..17a27a550e0 100644
--- 
a/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/NoIndexHintPlannerTest.java
+++ 
b/modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/NoIndexHintPlannerTest.java
@@ -60,13 +60,17 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
     @Test
     public void testSingleTable() throws Exception {
         assertNoAnyIndex("SELECT /*+ NO_INDEX */ * FROM TBL1 WHERE id = 0");
+        assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX */ WHERE id = 0");
 
-        var sql = "SELECT /*+ NO_INDEX({}) */ * FROM TBL1 WHERE id = 0";
-
-        assertNoAnyIndex(format(sql, ""));
+        assertNoAnyIndex("SELECT /*+ NO_INDEX() */ * FROM TBL1 WHERE id = 0");
+        assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX() */ WHERE id = 0");
 
         assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX_VAL1, IDX_VAL2_VAL3) */ * 
FROM TBL1 WHERE val1=1 and val2='v'");
         assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX_VAL1), 
NO_INDEX(IDX_VAL2_VAL3) */ * FROM TBL1 WHERE val1=1 and val2='v'");
+
+        assertNoAnyIndex(format("SELECT * FROM TBL1 /*+ NO_INDEX({}) */  WHERE 
id = 0", ""));
+        assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX(IDX_VAL1, 
IDX_VAL2_VAL3) */ WHERE val1=1 and val2='v'");
+        assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX(IDX_VAL1), 
NO_INDEX(IDX_VAL2_VAL3) */ WHERE val1=1 and val2='v'");
     }
 
     @Test

Reply via email to