Skip to content

Commit 4419ceb

Browse files
authored
IGNITE-28510 Sql. Table hints are not applied. (#7972)
1 parent 6d97e9d commit 4419ceb

4 files changed

Lines changed: 165 additions & 12 deletions

File tree

modules/sql-engine/src/main/java/org/apache/ignite/internal/sql/engine/util/Commons.java

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import static org.apache.calcite.rel.hint.HintPredicates.AGGREGATE;
2121
import static org.apache.calcite.rel.hint.HintPredicates.JOIN;
22+
import static org.apache.calcite.rel.hint.HintPredicates.TABLE_SCAN;
2223
import static org.apache.ignite.internal.sql.engine.prepare.PlanningContext.CLUSTER;
2324
import static org.apache.ignite.internal.util.CollectionUtils.nullOrEmpty;
2425
import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
@@ -55,7 +56,10 @@
5556
import org.apache.calcite.rel.RelCollationTraitDef;
5657
import org.apache.calcite.rel.RelNode;
5758
import org.apache.calcite.rel.core.JoinInfo;
59+
import org.apache.calcite.rel.hint.HintPredicate;
60+
import org.apache.calcite.rel.hint.HintPredicates;
5861
import org.apache.calcite.rel.hint.HintStrategyTable;
62+
import org.apache.calcite.rel.hint.Hintable;
5963
import org.apache.calcite.rel.logical.LogicalJoin;
6064
import org.apache.calcite.rel.type.RelDataType;
6165
import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -93,7 +97,6 @@
9397
import org.apache.ignite.internal.sql.engine.prepare.IgniteTypeCoercion;
9498
import org.apache.ignite.internal.sql.engine.prepare.PlanningContext;
9599
import org.apache.ignite.internal.sql.engine.rel.IgniteProject;
96-
import org.apache.ignite.internal.sql.engine.rel.logical.IgniteLogicalTableScan;
97100
import org.apache.ignite.internal.sql.engine.sql.IgniteSqlCommitTransaction;
98101
import org.apache.ignite.internal.sql.engine.sql.IgniteSqlConformance;
99102
import org.apache.ignite.internal.sql.engine.sql.IgniteSqlKill;
@@ -164,8 +167,8 @@ public final class Commons {
164167
.hintStrategy(IgniteHint.ENFORCE_JOIN_ORDER.name(), JOIN)
165168
.hintStrategy(IgniteHint.DISABLE_RULE.name(), (hint, rel) -> true)
166169
.hintStrategy(IgniteHint.EXPAND_DISTINCT_AGG.name(), AGGREGATE)
167-
.hintStrategy(IgniteHint.NO_INDEX.name(), (hint, rel) -> rel instanceof IgniteLogicalTableScan)
168-
.hintStrategy(IgniteHint.FORCE_INDEX.name(), (hint, rel) -> rel instanceof IgniteLogicalTableScan)
170+
.hintStrategy(IgniteHint.NO_INDEX.name(), indexHintPropagationStrategy())
171+
.hintStrategy(IgniteHint.FORCE_INDEX.name(), indexHintPropagationStrategy())
169172
.hintStrategy(IgniteHint.DISABLE_DECORRELATION.name(), (hint, rel) -> true)
170173
.build()
171174
)
@@ -188,6 +191,18 @@ public final class Commons {
188191
.traitDefs(DISTRIBUTED_TRAITS_SET)
189192
.build();
190193

194+
private static HintPredicate indexHintPropagationStrategy() {
195+
return HintPredicates.and(
196+
TABLE_SCAN,
197+
(hint, rel) -> ((Hintable) rel).getHints().stream()
198+
.filter(h -> h.hintName.equals(IgniteHint.FORCE_INDEX.name())
199+
|| h.hintName.equals(IgniteHint.NO_INDEX.name()))
200+
// Ignore all that overlaps existed hint.
201+
// See RelHint javadoc.
202+
.filter(h -> h.inheritPath.size() < hint.inheritPath.size())
203+
.findAny().isEmpty());
204+
}
205+
191206
private static volatile @Nullable Boolean fastOptimizationsEnabled = null;
192207

193208
private Commons() {
@@ -330,7 +345,7 @@ private static void populateParameters(Map<String, Object> dst, Object[] params)
330345
/**
331346
* Flattens a list of lists into a single list containing all elements from the nested lists.
332347
*
333-
* <p>This method takes a source list where each element is itself a list and combines
348+
* <p>This method takes a source list where each element is itself a list and combines
334349
* all the nested lists into a single list containing all their elements in order.
335350
*
336351
* <p>For example:
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package org.apache.ignite.internal.sql.engine.planner.hints;
19+
20+
import java.util.function.UnaryOperator;
21+
import org.apache.ignite.internal.sql.engine.framework.TestBuilders.TableBuilder;
22+
import org.apache.ignite.internal.sql.engine.planner.AbstractPlannerTest;
23+
import org.apache.ignite.internal.sql.engine.rel.AbstractIgniteJoin;
24+
import org.apache.ignite.internal.sql.engine.rel.IgniteTableScan;
25+
import org.apache.ignite.internal.sql.engine.schema.IgniteSchema;
26+
import org.apache.ignite.internal.sql.engine.trait.IgniteDistributions;
27+
import org.apache.ignite.internal.type.NativeTypes;
28+
import org.junit.jupiter.api.BeforeAll;
29+
import org.junit.jupiter.api.Test;
30+
31+
/**
32+
* Planner tests for index/no_index hints propagation.
33+
*/
34+
public class HintPropagationPlannerTest extends AbstractPlannerTest {
35+
private static IgniteSchema SCHEMA;
36+
37+
private static final String TBL1 = "TBL1";
38+
39+
private static final String TBL2 = "TBL2";
40+
41+
@BeforeAll
42+
public static void setup() {
43+
SCHEMA = createSchemaFrom(
44+
createSimpleTable(TBL1, 100)
45+
.andThen(addHashIndex("ID"))
46+
.andThen(addSortIndex("VAL1"))
47+
.andThen(addSortIndex("VAL2", "VAL3"))
48+
.andThen(addSortIndex("VAL3")),
49+
createSimpleTable(TBL2, 100_000)
50+
.andThen(addHashIndex("ID"))
51+
.andThen(addSortIndex("VAL1"))
52+
.andThen(addSortIndex("VAL2"))
53+
.andThen(addSortIndex("VAL3"))
54+
);
55+
}
56+
57+
@Test
58+
public void usingDifferentIndexesForSameTable() throws Exception {
59+
var sql = "SELECT * FROM"
60+
+ " (SELECT t1.val1, t1.val2, t1.val3 FROM tbl1 /*+ NO_INDEX */ as t1"
61+
+ " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val3) */ as t2 ON (t1.val2 = t2.val2 AND t2.val2 > 'x')) as t"
62+
+ " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2) */ as t3 ON (t.val3 = t3.val3 AND t3.val3 < 'a')";
63+
64+
assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
65+
.and(input(0, nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
66+
.and(input(0, nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
67+
.and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL3")
68+
.and(scan -> scan.searchBounds() == null)
69+
.and(scan -> ">($t0, _UTF-8'x')".equals(scan.condition().toString()))
70+
)))
71+
)))
72+
.and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL2")
73+
.and(scan -> scan.searchBounds() == null)
74+
.and(scan -> "<($t3, _UTF-8'a')".equals(scan.condition().toString()))
75+
)))
76+
);
77+
78+
sql = "SELECT * FROM"
79+
+ " (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"
80+
+ " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2) */ as t3 ON (t.val3 = t3.val3)";
81+
82+
assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
83+
.and(input(0, nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
84+
.and(input(0, nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
85+
.and(input(1, nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
86+
)))
87+
.and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL2"))))
88+
);
89+
}
90+
91+
@Test
92+
public void testHintOverriding() throws Exception {
93+
var sql = "SELECT /*+ NO_INDEX */ * FROM"
94+
+ " (SELECT t1.val1, t1.val2, t1.val3 FROM tbl1 as t1 LEFT JOIN tbl2 as t2 ON (t1.val2 = t2.val2)) as t"
95+
+ " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2_val3) */ as t3 ON (t.val3 = t3.val3)";
96+
97+
assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
98+
.and(input(0, nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
99+
.and(input(0, nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
100+
.and(input(1, nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
101+
)))
102+
.and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "idx_val3"))))
103+
);
104+
105+
sql = "SELECT /*+ FORCE_INDEX(idx_val3) */ * FROM"
106+
+ " (SELECT t1.val1, t1.val2, t1.val3 FROM tbl1 /*+ NO_INDEX */ as t1 "
107+
+ " LEFT JOIN tbl2 as t2 ON (t1.val2 = t2.val2)) as t"
108+
+ " LEFT JOIN tbl2 /*+ FORCE_INDEX(idx_val2) */as t3 ON (t.val3 = t3.val3)";
109+
110+
assertPlan(sql, SCHEMA, isInstanceOf(AbstractIgniteJoin.class)
111+
.and(input(0, nodeOrAnyChild(isInstanceOf(AbstractIgniteJoin.class)
112+
.and(input(0, nodeOrAnyChild(isInstanceOf(IgniteTableScan.class))))
113+
.and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL3"))))
114+
)))
115+
.and(input(1, nodeOrAnyChild(isIndexScan(TBL2, "IDX_VAL2"))))
116+
);
117+
}
118+
119+
private static UnaryOperator<TableBuilder> createSimpleTable(String name, int sz) {
120+
return t -> t.name(name)
121+
.size(sz)
122+
.distribution(IgniteDistributions.single())
123+
.addKeyColumn("ID", NativeTypes.INT32)
124+
.addColumn("VAL1", NativeTypes.INT32)
125+
.addColumn("VAL2", NativeTypes.STRING)
126+
.addColumn("VAL3", NativeTypes.STRING);
127+
}
128+
}

modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/IndexHintPlannerTest.java

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,15 +148,21 @@ public void testWrongIndexName(boolean force) {
148148

149149
@Test
150150
public void testSingleTable() throws Exception {
151-
var sql = "SELECT /*+ FORCE_INDEX({}) */ * FROM TBL1 WHERE val2 = 'v' AND val3 = 'v'";
151+
var sql1 = "SELECT /*+ FORCE_INDEX({}) */ * FROM TBL1 WHERE val2 = 'v' AND val3 = 'v'";
152+
var sql2 = "SELECT * FROM TBL1 /*+ FORCE_INDEX({}) */ WHERE val2 = 'v' AND val3 = 'v'";
152153

153-
assertCertainIndex(format(sql, ""), TBL1, "IDX_VAL2_VAL3");
154+
assertCertainIndex(format(sql1, ""), TBL1, "IDX_VAL2_VAL3");
155+
assertCertainIndex(format(sql2, ""), TBL1, "IDX_VAL2_VAL3");
154156

155-
assertPlan(format(sql, "IDX_VAL2_VAL3, IDX_VAL3"), SCHEMA, nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL2_VAL3")
157+
assertPlan(format(sql1, "IDX_VAL1, IDX_VAL3"), SCHEMA, nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL1")
158+
.or(isIndexScan(TBL1, "IDX_VAL3"))));
159+
assertPlan(format(sql2, "IDX_VAL1, IDX_VAL3"), SCHEMA, nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL1")
156160
.or(isIndexScan(TBL1, "IDX_VAL3"))));
157161

158-
assertPlan("SELECT /*+ FORCE_INDEX(IDX_VAL2_VAL3), FORCE_INDEX(IDX_VAL3) */ * FROM TBL1 WHERE val2 = 'v' AND val3 = 'v'", SCHEMA,
159-
nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL2_VAL3").or(isIndexScan(TBL1, "IDX_VAL3"))));
162+
assertPlan("SELECT /*+ FORCE_INDEX(IDX_VAL1), FORCE_INDEX(IDX_VAL3) */ * FROM TBL1 WHERE val2 = 'v' AND val3 = 'v'", SCHEMA,
163+
nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL1").or(isIndexScan(TBL1, "IDX_VAL3"))));
164+
assertPlan("SELECT * FROM TBL1 /*+ FORCE_INDEX(IDX_VAL1), FORCE_INDEX(IDX_VAL3) */ WHERE val2 = 'v' AND val3 = 'v'", SCHEMA,
165+
nodeOrAnyChild(isIndexScan(TBL1, "IDX_VAL1").or(isIndexScan(TBL1, "IDX_VAL3"))));
160166
}
161167

162168
@Test

modules/sql-engine/src/test/java/org/apache/ignite/internal/sql/engine/planner/hints/NoIndexHintPlannerTest.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,17 @@ public static void setup() {
6060
@Test
6161
public void testSingleTable() throws Exception {
6262
assertNoAnyIndex("SELECT /*+ NO_INDEX */ * FROM TBL1 WHERE id = 0");
63+
assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX */ WHERE id = 0");
6364

64-
var sql = "SELECT /*+ NO_INDEX({}) */ * FROM TBL1 WHERE id = 0";
65-
66-
assertNoAnyIndex(format(sql, ""));
65+
assertNoAnyIndex("SELECT /*+ NO_INDEX() */ * FROM TBL1 WHERE id = 0");
66+
assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX() */ WHERE id = 0");
6767

6868
assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX_VAL1, IDX_VAL2_VAL3) */ * FROM TBL1 WHERE val1=1 and val2='v'");
6969
assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX_VAL1), NO_INDEX(IDX_VAL2_VAL3) */ * FROM TBL1 WHERE val1=1 and val2='v'");
70+
71+
assertNoAnyIndex(format("SELECT * FROM TBL1 /*+ NO_INDEX({}) */ WHERE id = 0", ""));
72+
assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX(IDX_VAL1, IDX_VAL2_VAL3) */ WHERE val1=1 and val2='v'");
73+
assertNoAnyIndex("SELECT * FROM TBL1 /*+ NO_INDEX(IDX_VAL1), NO_INDEX(IDX_VAL2_VAL3) */ WHERE val1=1 and val2='v'");
7074
}
7175

7276
@Test

0 commit comments

Comments
 (0)