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