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

alexpl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 062d293f766 IGNITE-21162 SQL Calcite: Use table level hints - Fixes 
#11175.
062d293f766 is described below

commit 062d293f766067a24ec76d50827784219715c4ee
Author: Vladimir Steshin <[email protected]>
AuthorDate: Fri Mar 1 21:27:05 2024 +0300

    IGNITE-21162 SQL Calcite: Use table level hints - Fixes #11175.
    
    Signed-off-by: Aleksey Plekhanov <[email protected]>
---
 docs/_docs/SQL/sql-calcite.adoc                    | 39 ++++++---
 .../query/calcite/hint/HintDefinition.java         | 18 ++--
 .../processors/query/calcite/hint/HintUtils.java   | 25 +++++-
 .../rel/logical/IgniteLogicalTableScan.java        |  2 +-
 .../rule/AbstractIgniteJoinConverterRule.java      | 70 ++++++++++------
 .../query/calcite/schema/CacheTableImpl.java       |  7 +-
 .../query/calcite/schema/IgniteTable.java          |  9 +-
 .../query/calcite/schema/SystemViewTableImpl.java  |  7 +-
 .../query/calcite/planner/TestTable.java           |  7 +-
 .../planner/hints/ForceIndexHintPlannerTest.java   | 44 ++++++++++
 .../planner/hints/JoinTypeHintPlannerTest.java     | 98 +++++++++++++++++++---
 .../planner/hints/NoIndexHintPlannerTest.java      | 44 ++++++++++
 12 files changed, 299 insertions(+), 71 deletions(-)

diff --git a/docs/_docs/SQL/sql-calcite.adoc b/docs/_docs/SQL/sql-calcite.adoc
index 90a9a910aa4..ddeaab14640 100644
--- a/docs/_docs/SQL/sql-calcite.adoc
+++ b/docs/_docs/SQL/sql-calcite.adoc
@@ -242,14 +242,16 @@ SQL hints are optional to apply and might be skipped in 
some cases.
 ====
 
 === Hints format
-SQL hints are defined by a special comment +++/*+ HINT */+++ reffered as a 
_hint block_. Spaces before and after the
-hint name are required. The hint block is placed right after a relation 
operator, often after _SELECT_. Several hint
-blocks for one relation operator *are not allowed*.
+SQL hints are defined by a special comment +++/*+ HINT */+++ reffered as _hint 
block_. Spaces before and after the
+hint name are required. The hint block is placed right after _SELECT_ or after 
a table name. Several hint blocks for
+one _SELECT_ or one table *are not allowed*. Several hints in one hint block 
are separated with comma.
 
 Example:
 [source, SQL]
 ----
-SELECT /*+ NO_INDEX */ T1.* FROM TBL1 where T1.V1=? and T1.V2=?
+SELECT /*+ FORCE_INDEX(IDX_TBL1_V2), EXPAND_DISTINCT_AGG */ V2, AVG(DISTINCT 
V3) FROM TBL1 WHERE V1=? and V2=? GROUP BY V2
+
+SELECT * FROM TBL1 /*+ FORCE_INDEX(IDX_TBL1_V2) */ where V1=? and V2=?
 ----
 
 It is allowed to define several hints for the same relation operator. To use 
several hints, separate them by comma
@@ -258,7 +260,7 @@ It is allowed to define several hints for the same relation 
operator. To use sev
 Example:
 [source, SQL]
 ----
-SELECT /*+ NO_INDEX, EXPAND_DISTINCT_AGG */ SUM(DISTINCT V1), AVG(DISTINCT V2) 
FROM TBL1 GROUP BY V3 WHERE V3=?
+SELECT /*+ NO_INDEX, EXPAND_DISTINCT_AGG */ SUM(DISTINCT V1), AVG(DISTINCT V2) 
FROM TBL1 WHERE V3=? GROUP BY V3
 ----
 
 ==== Hint parameters
@@ -276,9 +278,9 @@ SELECT /*+ FORCE_INDEX('TBL2_idx1') */ T1.V1, T2.V1 FROM 
TBL1 T1, TBL2 T2 WHERE
 ----
 
 === Hint scope
-Hints are defined for SELECT relation operator and are "visible" for the 
following operators, queries and subqueries.
-The hints defined in the subquery are "visible" only for this subquery and its 
subqueries. Hint is not "visible" to
-the previous relation operator if it is defined after it.
+Hints of a _SELECT_ are "visible" for this operation and the following 
relation operators, queries and subqueries.
+Hints in a subquery have effective scope only for this subquery and its 
subqueries. Hint, defined for a table, is
+effective only for this table.
 
 Example:
 [source, SQL]
@@ -286,8 +288,9 @@ Example:
 SELECT /*+ NO_INDEX(TBL1_IDX2), FORCE_INDEX(TBL2_IDX2) */ T1.V1 FROM TBL1 T1 
WHERE T1.V2 IN (SELECT T2.V2 FROM TBL2 T2 WHERE T2.V1=? AND T2.V2=?);
 
 SELECT T1.V1 FROM TBL1 T1 WHERE T1.V2 IN (SELECT /*+ FORCE_INDEX(TBL2_IDX2) */ 
T2.V2 FROM TBL2 T2 WHERE T2.V1=? AND T2.V2=?);
-----
 
+SELECT T1.V1 FROM TBL1 T1 JOIN TBL2 /*+ MERGE_JOIN */ T2 ON T1.V2=T2.V2 and 
T1.V3=T2.V3 and T2.V3=?;
+----
 Note that only the first query has a hint in such a case as:
 [source, SQL]
 ----
@@ -295,11 +298,23 @@ SELECT /*+ FORCE_INDEX */ V1 FROM TBL1 WHERE V1=? AND V2=?
 UNION ALL
 SELECT V1 FROM TBL1 WHERE V3>?
 ----
-
 But *there are exceptions*: hints of engine or optimizer level, such as 
link:#hint_disable_rule[_DISABLE_RULE_] or
 link:#hint_query_engine[_QUERY_ENGINE_]. Such hints should be defined at the 
beginning of the query and are related to
 the whole query.
 
+=== Hints priority
+Hints, defined in subqueries or in the following _SELECTs_, have priority over 
the preceding ones. In the following example,
+an index for _TBL2_ is actually applied.
+[source, SQL]
+----
+SELECT /*+ NO_INDEX */ * FROM TBL1 T1 WHERE T1.V1 = (SELECT /*+ FORCE_INDEX */ 
T2.V1 FROM TBL2 T2 where T2.V2=? and T2.V3=?)
+----
+Table hints usually have a bigger priority. In the following example, an index 
for _TBL1_ is actually applied.
+[source, SQL]
+----
+SELECT /*+ NO_INDEX */ * FROM TBL /*+ FORCE_INDEX(IDX_TBL1_V2) */ where V1=? 
and V2=? and V3=?;
+----
+
 === Hints errors
 The optimizer tries to apply every hint and its parameters, if possible. But 
it skips the hint or hint parameter if:
 
@@ -341,6 +356,8 @@ SELECT /*+ FORCE_INDEX(TBL1_IDX2, TBL2_IDX1) */ T1.V1, 
T2.V1 FROM TBL1 T1, TBL2
 SELECT /*+ NO_INDEX */ T1.* FROM TBL1 T1 WHERE T1.V1 = T2.V1 AND T1.V2 > ?;
 
 SELECT /*+ NO_INDEX(TBL1_IDX2, TBL2_IDX1) */ T1.V1, T2.V1 FROM TBL1 T1, TBL2 
T2 WHERE T1.V1 = T2.V1 AND T1.V2 > ? AND T2.V2 > ?;
+
+SELECT T1.V1, T2.V2 FROM TBL1 t1 JOIN TBL2 /*+ FORCE_INDEX(IDX2_2) */ T2 on 
T1.V3=T2.V3 and T1.V1=T2.V2 and T2.V1=?";
 ----
 
 ==== ENFORCE_JOIN_ORDER
@@ -371,6 +388,8 @@ SELECT /*+ MERGE_JOIN */ t1.v1, t2.v2 FROM TBL1 t1, TBL2 t2 
WHERE t1.v3=t2.v3
 SELECT /*+ NL_JOIN(TBL3,TBL1) */ t4.v1, t2.v2 FROM TBL1 t4 JOIN TBL2 t2 on 
t1.v3=t2.v3 WHERE t2.v1 in (SELECT t3.v3 FROM TBL3 t3 JOIN TBL1 t4 on 
t3.v2=t4.v2)
 
 SELECT t1.v1, t2.v2 FROM TBL2 t1 JOIN TBL1 t2 on t1.v3=t2.v3 WHERE t2.v3 in 
(SELECT /*+ NO_CNL_JOIN(TBL4) */ t3.v3 FROM TBL3 t3 JOIN TBL4 t4 on t3.v1=t4.v1)
+
+SELECT t4.v1, t2.v2 FROM TBL1 t4 JOIN TBL2 t2 on t1.v3=t2.v3 WHERE t2.v1 in 
(SELECT t3.v3 FROM TBL3 t3 JOIN TBL1 /*+ NL_JOIN */ t4 on t3.v2=t4.v2)
 ----
 
 ==== EXPAND_DISTINCT_AGG
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintDefinition.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintDefinition.java
index c8d4e2f2b4c..949c2990bbf 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintDefinition.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintDefinition.java
@@ -25,7 +25,6 @@ import org.apache.calcite.rel.hint.HintPredicate;
 import org.apache.calcite.rel.hint.HintPredicates;
 import org.apache.calcite.rel.rules.CoreRules;
 import org.apache.calcite.rel.rules.JoinPushThroughJoinRule;
-import 
org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;
 
 /**
  * Holds supported SQL hints and their settings.
@@ -76,7 +75,7 @@ public enum HintDefinition {
     NO_INDEX {
         /** {@inheritDoc} */
         @Override public HintPredicate predicate() {
-            return (hint, rel) -> rel instanceof IgniteLogicalTableScan;
+            return HintPredicates.TABLE_SCAN;
         }
 
         /** {@inheritDoc} */
@@ -102,7 +101,7 @@ public enum HintDefinition {
     MERGE_JOIN {
         /** {@inheritDoc} */
         @Override public HintPredicate predicate() {
-            return HintPredicates.JOIN;
+            return joinHintPredicate();
         }
 
         /** {@inheritDoc} */
@@ -128,7 +127,7 @@ public enum HintDefinition {
     NL_JOIN {
         /** {@inheritDoc} */
         @Override public HintPredicate predicate() {
-            return HintPredicates.JOIN;
+            return joinHintPredicate();
         }
 
         /** {@inheritDoc} */
@@ -154,7 +153,7 @@ public enum HintDefinition {
     CNL_JOIN {
         /** {@inheritDoc} */
         @Override public HintPredicate predicate() {
-            return HintPredicates.JOIN;
+            return joinHintPredicate();
         }
 
         /** {@inheritDoc} */
@@ -176,6 +175,15 @@ public enum HintDefinition {
         }
     };
 
+    /**
+     * @return Hint predicate for join hints.
+     */
+    private static HintPredicate joinHintPredicate() {
+        // HintPredicates.VALUES might be mentioned too. But 
RelShuttleImpl#visit(LogicalValues) does nothing and ignores
+        // setting any hints.
+        return HintPredicates.or(HintPredicates.JOIN, 
HintPredicates.TABLE_SCAN);
+    }
+
     /**
      * @return Hint predicate which limits redundant hint copying and reduces 
mem/cpu consumption.
      */
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java
index 7cc01556719..40c267ded09 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java
@@ -61,13 +61,21 @@ public final class HintUtils {
     }
 
     /**
-     * @return Hints filtered with {@code hintDefs} and suitable for {@code 
rel}.
+     * @return Hints of {@code rel} filtered with {@code hintDefs}.
      * @see HintStrategyTable#apply(List, RelNode)
      * @see #filterHints(RelNode, Collection, List)
      */
     public static List<RelHint> hints(RelNode rel, HintDefinition... hintDefs) 
{
-        return rel.getCluster().getHintStrategies()
-            .apply(filterHints(rel, allRelHints(rel), 
Arrays.asList(hintDefs)), rel);
+        return hints(rel, allRelHints(rel), hintDefs);
+    }
+
+    /**
+     * @return Hints filtered with {@code hintDefs} and suitable for {@code 
rel}.
+     * @see HintStrategyTable#apply(List, RelNode)
+     * @see #filterHints(RelNode, Collection, List)
+     */
+    public static List<RelHint> hints(RelNode rel, Collection<RelHint> hints, 
HintDefinition... hintDefs) {
+        return rel.getCluster().getHintStrategies().apply(filterHints(rel, 
hints, Arrays.asList(hintDefs)), rel);
     }
 
     /**
@@ -78,6 +86,17 @@ public final class HintUtils {
         return rel instanceof Hintable ? ((Hintable)rel).getHints() : 
Collections.emptyList();
     }
 
+    /**
+     * @return Only noninherited hints of {@code rel} if it is a {@code 
Hintable}. If is not or has no hints, empty
+     * collection.
+     * @see Hintable#getHints()
+     */
+    public static List<RelHint> nonInheritedRelHints(RelNode rel) {
+        return rel instanceof Hintable
+            ? ((Hintable)rel).getHints().stream().filter(hint -> 
hint.inheritPath.isEmpty()).collect(Collectors.toList())
+            : Collections.emptyList();
+    }
+
     /**
      * @return Distinct hints within {@code hints} filtered with {@code 
hintDefs}, {@link HintOptionsChecker} and
      * removed inherit pathes.
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalTableScan.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalTableScan.java
index ed973c4ec36..caa08a90aef 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalTableScan.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rel/logical/IgniteLogicalTableScan.java
@@ -36,7 +36,7 @@ public class IgniteLogicalTableScan extends 
ProjectableFilterableTableScan {
         RelOptCluster cluster,
         RelTraitSet traits,
         RelOptTable tbl,
-        List<RelHint> hints,
+        @Nullable List<RelHint> hints,
         @Nullable List<RexNode> proj,
         @Nullable RexNode cond,
         @Nullable ImmutableBitSet requiredColumns
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java
index 5a135b3a51e..a79322b0711 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/rule/AbstractIgniteJoinConverterRule.java
@@ -19,21 +19,24 @@ package 
org.apache.ignite.internal.processors.query.calcite.rule;
 
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.EnumMap;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.LinkedHashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.calcite.plan.RelOptRuleCall;
 import org.apache.calcite.plan.volcano.RelSubset;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.rel.core.Join;
+import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.logical.LogicalJoin;
 import org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition;
 import org.apache.ignite.internal.processors.query.calcite.hint.HintUtils;
+import org.apache.ignite.internal.util.typedef.F;
 
 import static org.apache.calcite.util.Util.last;
 import static 
org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.CNL_JOIN;
@@ -82,57 +85,70 @@ abstract class AbstractIgniteJoinConverterRule extends 
AbstractIgniteConverterRu
 
     /** */
     private boolean disabledByHints(LogicalJoin join) {
-        if (HintUtils.allRelHints(join).isEmpty())
+        Collection<TableScan> joinTables = joinTables(join);
+
+        Collection<RelHint> rawHints = new ArrayList<>();
+
+        // Table hints have a bigger priority and go first.
+        joinTables.forEach(t -> 
rawHints.addAll(HintUtils.nonInheritedRelHints(t)));
+
+        rawHints.addAll(HintUtils.allRelHints(join));
+
+        if (rawHints.isEmpty())
             return false;
 
         boolean ruleDisabled = false;
 
         Map<String, Collection<HintDefinition>> hintedTables = new HashMap<>();
 
-        Set<String> joinTbls = joinTblNames(join);
+        Set<String> joinTblNames = F.isEmpty(joinTables)
+            ? Collections.emptySet()
+            : joinTables.stream().map(t -> 
last(t.getTable().getQualifiedName())).collect(Collectors.toSet());
 
-        assert joinTbls.size() < 3;
+        Set<String> matchedTbls;
 
-        for (RelHint hint : HintUtils.hints(join, ALL_HINTS)) {
-            Set<String> matchedTbls = hint.listOptions.isEmpty() ? joinTbls : 
new HashSet<>(hint.listOptions);
+        for (RelHint hint : HintUtils.hints(join, rawHints, ALL_HINTS)) {
+            if (hint.listOptions.isEmpty())
+                matchedTbls = joinTblNames;
+            else {
+                matchedTbls = new HashSet<>(hint.listOptions);
 
-            if (!hint.listOptions.isEmpty())
-                matchedTbls.retainAll(joinTbls);
+                matchedTbls.retainAll(joinTblNames);
 
-            if (matchedTbls.isEmpty())
-                continue;
+                // Do not skip if the hint has no option. It can be a 
'global', request-level hint.
+                if (matchedTbls.isEmpty())
+                    continue;
+            }
 
             HintDefinition curHintDef = HintDefinition.valueOf(hint.hintName);
             boolean curHintIsDisable = !HINTS.containsKey(curHintDef);
-            boolean unableToProc = false;
+            boolean skipHint = false;
 
-            for (String tbl : joinTbls) {
+            for (String tbl : joinTblNames) {
                 Collection<HintDefinition> prevTblHints = 
hintedTables.get(tbl);
 
                 if (prevTblHints == null)
                     continue;
 
-                Set<HintDefinition> disabled = null;
+                Set<HintDefinition> allDisables = new HashSet<>();
+
+                if (curHintIsDisable)
+                    allDisables.add(curHintDef);
 
                 for (HintDefinition prevTblHint : prevTblHints) {
                     boolean prevHintIsDisable = 
!HINTS.containsKey(prevTblHint);
 
-                    if (prevHintIsDisable) {
-                        if (disabled == null)
-                            disabled = new HashSet<>();
-
-                        disabled.add(prevTblHint);
-                    }
+                    if (prevHintIsDisable)
+                        allDisables.add(prevTblHint);
 
                     // Prohibited: disabling all join types, combinations of 
forcing and disabling same join type,
                     // forcing of different join types.
-                    if (curHintIsDisable && (disabled != null && 
disabled.size() == HINTS.size() - 1)
-                        || isMutuallyExclusive(curHintDef, prevTblHint))
-                        unableToProc = true;
+                    if (curHintIsDisable && allDisables.size() == HINTS.size() 
|| isMutuallyExclusive(curHintDef, prevTblHint))
+                        skipHint = true;
                 }
             }
 
-            if (unableToProc) {
+            if (skipHint) {
                 HintUtils.skippedHint(join, hint, "This join type is already 
disabled or forced to use before " +
                     "by previous hints");
 
@@ -164,15 +180,15 @@ abstract class AbstractIgniteJoinConverterRule extends 
AbstractIgniteConverterRu
     }
 
     /** */
-    protected static Set<String> joinTblNames(Join join) {
-        Set<String> res = new LinkedHashSet<>();
+    protected static Collection<TableScan> joinTables(Join join) {
+        Collection<TableScan> res = new ArrayList<>(2);
 
         for (RelNode in : join.getInputs()) {
             if (in instanceof RelSubset)
                 in = ((RelSubset)in).getOriginal();
 
-            if (in.getTable() != null)
-                res.add(last(in.getTable().getQualifiedName()));
+            if (in instanceof TableScan)
+                res.add((TableScan)in);
         }
 
         return res;
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableImpl.java
index 1fc923767fd..f53d1d1fed9 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/CacheTableImpl.java
@@ -24,6 +24,7 @@ import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexNode;
@@ -99,10 +100,10 @@ public class CacheTableImpl extends AbstractTable 
implements IgniteCacheTable {
         RelOptTable relOptTbl,
         @Nullable List<RexNode> proj,
         @Nullable RexNode cond,
-        @Nullable ImmutableBitSet requiredColumns
+        @Nullable ImmutableBitSet requiredColumns,
+        @Nullable List<RelHint> hints
     ) {
-        return IgniteLogicalTableScan.create(cluster, cluster.traitSet(), 
relOptTbl, Collections.emptyList(), proj,
-            cond, requiredColumns);
+        return IgniteLogicalTableScan.create(cluster, cluster.traitSet(), 
relOptTbl, hints, proj, cond, requiredColumns);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
index 492fca9c445..ca1afb2d221 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteTable.java
@@ -21,6 +21,7 @@ import java.util.Map;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.core.TableScan;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexNode;
@@ -56,8 +57,8 @@ public interface IgniteTable extends TranslatableTable {
     RelDataType getRowType(RelDataTypeFactory typeFactory, ImmutableBitSet 
requiredColumns);
 
     /** {@inheritDoc} */
-    @Override default TableScan toRel(RelOptTable.ToRelContext context, 
RelOptTable relOptTable) {
-        return toRel(context.getCluster(), relOptTable, null, null, null);
+    @Override default TableScan toRel(RelOptTable.ToRelContext ctx, 
RelOptTable relOptTable) {
+        return toRel(ctx.getCluster(), relOptTable, null, null, null, 
ctx.getTableHints());
     }
 
     /**
@@ -68,6 +69,7 @@ public interface IgniteTable extends TranslatableTable {
      * @param proj List of required projections.
      * @param cond Conditions to filter rows.
      * @param requiredColumns Set of columns to extract from original row.
+     * @param hints Table hints.
      * @return Table relational expression.
      */
     IgniteLogicalTableScan toRel(
@@ -75,7 +77,8 @@ public interface IgniteTable extends TranslatableTable {
         RelOptTable relOptTbl,
         @Nullable List<RexNode> proj,
         @Nullable RexNode cond,
-        @Nullable ImmutableBitSet requiredColumns
+        @Nullable ImmutableBitSet requiredColumns,
+        @Nullable List<RelHint> hints
     );
 
     /**
diff --git 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewTableImpl.java
 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewTableImpl.java
index 132f663b647..2ac2a70bab3 100644
--- 
a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewTableImpl.java
+++ 
b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/SystemViewTableImpl.java
@@ -24,6 +24,7 @@ import com.google.common.collect.ImmutableList;
 import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.RelCollation;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rex.RexNode;
@@ -92,10 +93,10 @@ public class SystemViewTableImpl extends AbstractTable 
implements IgniteTable {
         RelOptTable relOptTbl,
         @Nullable List<RexNode> proj,
         @Nullable RexNode cond,
-        @Nullable ImmutableBitSet requiredColumns
+        @Nullable ImmutableBitSet requiredColumns,
+        @Nullable List<RelHint> hints
     ) {
-        return IgniteLogicalTableScan.create(cluster, cluster.traitSet(), 
relOptTbl, Collections.emptyList(), proj,
-            cond, requiredColumns);
+        return IgniteLogicalTableScan.create(cluster, cluster.traitSet(), 
relOptTbl, hints, proj, cond, requiredColumns);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
index d34841c9453..d8b9617b5ca 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/TestTable.java
@@ -31,6 +31,7 @@ import org.apache.calcite.plan.RelOptCluster;
 import org.apache.calcite.plan.RelOptTable;
 import org.apache.calcite.rel.RelCollation;
 import org.apache.calcite.rel.RelFieldCollation;
+import org.apache.calcite.rel.hint.RelHint;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.rel.type.RelDataTypeField;
@@ -140,10 +141,10 @@ public class TestTable implements IgniteCacheTable {
         RelOptTable relOptTbl,
         @Nullable List<RexNode> proj,
         @Nullable RexNode cond,
-        @Nullable ImmutableBitSet requiredColumns
+        @Nullable ImmutableBitSet requiredColumns,
+        @Nullable List<RelHint> hints
     ) {
-        return IgniteLogicalTableScan.create(cluster, cluster.traitSet(), 
relOptTbl, Collections.emptyList(), proj,
-            cond, requiredColumns);
+        return IgniteLogicalTableScan.create(cluster, cluster.traitSet(), 
relOptTbl, hints, proj, cond, requiredColumns);
     }
 
     /** {@inheritDoc} */
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/ForceIndexHintPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/ForceIndexHintPlannerTest.java
index 3283436df5b..fa88732a1b6 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/ForceIndexHintPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/ForceIndexHintPlannerTest.java
@@ -76,6 +76,17 @@ public class ForceIndexHintPlannerTest extends 
AbstractPlannerTest {
         assertPlan("SELECT /*+ FORCE_INDEX(IDX2_3) */ * FROM TBL2 WHERE 
val23=1 and val21=2 and val22=3",
             schema, nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
 
+        assertPlan("SELECT * FROM TBL2 /*+ FORCE_INDEX(IDX2_3) */ WHERE 
val23=1 and val21=2 and val22=3",
+            schema, nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
+
+        // Table hint has a bigger priority.
+        assertPlan("SELECT /*+ FORCE_INDEX(IDX2_3) */ * FROM TBL2 /*+ 
FORCE_INDEX(IDX2_2) */ WHERE val23=1 and " +
+            "val21=2 and val22=3", schema, nodeOrAnyChild(isIndexScan("TBL2", 
"IDX2_2")));
+
+        // First table hint has a bigger priority.
+        assertPlan("SELECT * FROM TBL2 /*+ FORCE_INDEX(IDX2_2), 
FORCE_INDEX(IDX2_3) */ WHERE val23=1 and " +
+            "val21=2 and val22=3", schema, nodeOrAnyChild(isIndexScan("TBL2", 
"IDX2_2")));
+
         assertPlan("SELECT /*+ FORCE_INDEX(UNEXISTING,IDX2_3,UNEXISTING) */ * 
FROM TBL2 WHERE val23=1 and val21=2 " +
             "and val22=3", schema, nodeOrAnyChild(isIndexScan("TBL2", 
"IDX2_3")));
 
@@ -87,6 +98,10 @@ public class ForceIndexHintPlannerTest extends 
AbstractPlannerTest {
             schema, nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2"))
                     .or(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3"))));
 
+        assertPlan("SELECT * FROM TBL2 /*+ FORCE_INDEX(IDX2_2,IDX2_3) */ WHERE 
val23=1 and val21=2 and val22=3",
+            schema, nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2"))
+                .or(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3"))));
+
         assertPlan("SELECT /*+ FORCE_INDEX(IDX2_3), FORCE_INDEX(IDX2_3) */ * 
FROM TBL2 WHERE val23=1 and val21=2 " +
             "and val22=3", schema, nodeOrAnyChild(isIndexScan("TBL2", 
"IDX2_3")));
     }
@@ -105,6 +120,16 @@ public class ForceIndexHintPlannerTest extends 
AbstractPlannerTest {
                 "t2 on t1.val3=t2.val23 and t1.val1=t2.val22", schema,
             nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2")));
 
+        assertPlan("SELECT t1.val1, t2.val22 FROM TBL1 /*+ FORCE_INDEX(IDX1_3) 
*/ t1 " + jt
+                + " JOIN TBL2 /*+ FORCE_INDEX(IDX2_2) */ t2 on 
t1.val3=t2.val23 and t1.val1=t2.val22", schema,
+            nodeOrAnyChild(isIndexScan("TBL1", "IDX1_3"))
+                .and(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2"))));
+
+        assertPlan("SELECT t1.val1, t2.val22 FROM TBL1 t1 " + jt + " JOIN TBL2 
/*+ FORCE_INDEX */ t2 on " +
+                "t1.val3=t2.val23 and t1.val1=t2.val22", schema,
+            nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2"))
+                .or(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3"))));
+
         assertPlan("SELECT /*+ FORCE_INDEX(IDX2_3) */ t1.val1, t2.val22 FROM 
TBL1 t1 " + jt + " JOIN TBL2 " +
                 "t2 on t1.val3=t2.val23 and t1.val1=t2.val22", schema,
             nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
@@ -180,8 +205,18 @@ public class ForceIndexHintPlannerTest extends 
AbstractPlannerTest {
         assertPlan("SELECT /*+ NO_INDEX(IDX2_1), FORCE_INDEX(IDX2_1), 
FORCE_INDEX(IDX2_3) */ * FROM TBL2 where " +
             "val21=1 and val22=2 and val23=3", schema, 
nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
 
+        assertPlan("SELECT /*+ NO_INDEX */ t1.val1 FROM TBL1 t1 where t1.val2 
= " +
+            "(SELECT t2.val23 from TBL2 /*+ FORCE_INDEX(IDX2_3) */ t2 where 
t2.val21=10 and t2.val23=10 and " +
+            "t2.val21=10)", schema, nodeOrAnyChild(isIndexScan("TBL2", 
"IDX2_3")));
+
         assertPlan("SELECT /*+ FORCE_INDEX(IDX2_3), NO_INDEX */ * FROM TBL2 
where " +
             "val21=1 and val22=2 and val23=3", schema, 
nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
+
+        // Table hint has a bigger priority.
+        assertPlan("SELECT /*+ FORCE_INDEX(IDX2_1), FORCE_INDEX(IDX2_3) */ * 
FROM TBL2 /*+ NO_INDEX(IDX2_1) */ where " +
+            "val21=1 and val22=2 and val23=3", schema, 
nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
+        assertPlan("SELECT /*+ NO_INDEX */ * FROM TBL2 /*+ FORCE_INDEX(IDX2_3) 
*/ where " +
+            "val21=1 and val22=2 and val23=3", schema, 
nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
     }
 
     /** */
@@ -195,6 +230,10 @@ public class ForceIndexHintPlannerTest extends 
AbstractPlannerTest {
         assertPlan("SELECT t1.val1 FROM TBL1 t1 where t1.val2 = (SELECT /*+ 
FORCE_INDEX(IDX1_2,IDX2_3) */ " +
                 "t2.val23 from TBL2 t2 where t2.val21=10 and t2.val23=10 and 
t2.val21=10)", schema,
             nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
+
+        assertPlan("SELECT t1.val1 FROM TBL1 t1 where t1.val2 = (SELECT 
t2.val23 from TBL2 " +
+                "/*+ FORCE_INDEX(IDX2_3) */ t2 where t2.val21=10 and 
t2.val23=10 and t2.val21=10)", schema,
+            nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")));
     }
 
     /** */
@@ -204,5 +243,10 @@ public class ForceIndexHintPlannerTest extends 
AbstractPlannerTest {
             "val2=val22 and val3=val23", schema, 
nodeOrAnyChild(isIndexScan("TBL1", "IDX1_1"))
             .and(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_1")
                 .or(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2"))))));
+
+        assertPlan("SELECT val1 FROM TBL1 /*+ FORCE_INDEX(IDX1_1) */, TBL2 /*+ 
FORCE_INDEX(IDX2_1,IDX2_2) */ WHERE " +
+            "val1=val21 and val2=val22 and val3=val23", schema, 
nodeOrAnyChild(isIndexScan("TBL1", "IDX1_1"))
+            .and(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_1")
+                .or(nodeOrAnyChild(isIndexScan("TBL2", "IDX2_2"))))));
     }
 }
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java
index 06e6b4e25cc..b03ba932e72 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/JoinTypeHintPlannerTest.java
@@ -167,6 +167,18 @@ public class JoinTypeHintPlannerTest extends 
AbstractPlannerTest {
             "t2.v2 FROM TBL1 t1 JOIN TBL2 t2 on t1.v3=t2.v3", schema);
 
         assertTrue(!lsnr.check());
+
+        // Next check: ensures that joins missing table scan inputs are 
processed correctly too.
+        lsnrLog.clearListeners();
+
+        lsnr = LogListener.matches("Skipped hint '" + NL_JOIN + '\'').build();
+
+        lsnrLog.registerListener(lsnr);
+
+        physicalPlan("SELECT /*+ " + MERGE_JOIN + ',' + NL_JOIN + " */ t1.v1, 
t2.v2 FROM TBL1 t1 JOIN TBL2 t2 " +
+            "on t1.v3=t2.v3 where t2.v1 in (SELECT t3.v3 from TBL3 t3 JOIN 
TBL1 t4 on t3.v2=t4.v2)", schema);
+
+        assertTrue(lsnr.check());
     }
 
     /**
@@ -361,6 +373,37 @@ public class JoinTypeHintPlannerTest extends 
AbstractPlannerTest {
             nodeOrAnyChild(isInstanceOf(joinRel)).negate(), disabledRules);
     }
 
+    /**
+     * Test table join hints.
+     */
+    @Test
+    public void testTableHints() throws Exception {
+        String sqlTpl = "SELECT %s A2.A, T3.V3, T1.V2 FROM (SELECT 1 AS A, 2 
AS B) A2 JOIN TBL3 %s T3 ON A2.B=A2.B " +
+            "JOIN TBL1 %s T1 on T3.V3=T1.V1 where T1.V2=5";
+
+        assertPlan(String.format(sqlTpl, "", "/*+ " + NL_JOIN + " */", "/*+ " 
+ MERGE_JOIN + " */"), schema,
+            
nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(input(1, 
isTableScan("TBL3"))))
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                    .and(input(1, nodeOrAnyChild(isTableScan("TBL1")))))), 
CORE_JOIN_REORDER_RULES);
+
+        // Table hint has a bigger priority. Leading CNL_JOIN is ignored.
+        assertPlan(String.format(sqlTpl, "/*+ " + CNL_JOIN + " */", "/*+ " + 
NL_JOIN + " */", "/*+ " + MERGE_JOIN + " */"),
+            schema, 
nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(input(1, 
isTableScan("TBL3"))))
+                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+                    .and(input(1, nodeOrAnyChild(isTableScan("TBL1")))))), 
CORE_JOIN_REORDER_RULES);
+
+        // Leading query hint works only for the second join.
+        assertPlan(String.format(sqlTpl, "/*+ " + CNL_JOIN + " */", "/*+ " + 
NL_JOIN + " */", ""), schema,
+            
nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(input(1, 
isTableScan("TBL3"))))
+                
.and(nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                    .and(input(1, nodeOrAnyChild(isTableScan("TBL1")))))), 
CORE_JOIN_REORDER_RULES);
+
+        // Table hint with wrong table name is ignored.
+        assertPlan(String.format(sqlTpl, "", "/*+ " + NL_JOIN + "(TBL1), " + 
CNL_JOIN + " */", ""), schema,
+            nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                .and(input(1, isTableScan("TBL3")))), CORE_JOIN_REORDER_RULES);
+    }
+
     /**
      * Tests disable-join-hint works for a sub-query.
      */
@@ -403,17 +446,47 @@ public class JoinTypeHintPlannerTest extends 
AbstractPlannerTest {
     /** */
     @Test
     public void testNestedHintOverrides() throws Exception {
-        assertPlan("SELECT /*+ " + MERGE_JOIN + "(TBL1) */ t1.v1, t2.v2 FROM 
TBL1 t1 JOIN TBL2 t2 on t1.v3=t2.v3 " +
+        assertPlan("SELECT /*+ " + MERGE_JOIN + " */ t1.v1, t2.v2 FROM TBL1 t1 
JOIN TBL2 t2 on t1.v3=t2.v3 " +
                 "where t2.v1 in (SELECT t3.v3 from TBL3 t3 JOIN TBL1 t4 on 
t3.v2=t4.v2)", schema,
             nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class)).negate()
-                .and(nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class))), 
CORE_JOIN_REORDER_RULES);
+                
.and(nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)).negate()),
 CORE_JOIN_REORDER_RULES);
 
-        assertPlan("SELECT /*+ " + MERGE_JOIN + "(TBL1) */ t1.v1, t2.v2 FROM 
TBL1 " +
-            "t1 JOIN TBL2 t2 on t1.v3=t2.v3 where t2.v1 in " +
-            "(SELECT /*+ " + CNL_JOIN + "(TBL1) */ t3.v3 from TBL3 t3 JOIN 
TBL1 t4 on t3.v2=t4.v2)", schema,
-            
nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class).and(hasNestedTableScan("TBL1")))
-                
.and(nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
-                    
.and(hasNestedTableScan("TBL3")).and(hasNestedTableScan("TBL1")))), 
CORE_JOIN_REORDER_RULES);
+        assertPlan("SELECT /*+ " + CNL_JOIN + "(TBL1)," + MERGE_JOIN + " */ 
t1.v1, t2.v2 FROM TBL1 t1 " +
+                "JOIN TBL2 t2 on t1.v3=t2.v3 where t2.v1 in (SELECT t3.v3 from 
TBL3 t3 JOIN TBL1 t4 on t3.v2=t4.v2)",
+            schema, predicateForNestedHintOverrides(false), 
CORE_JOIN_REORDER_RULES);
+
+        assertPlan("SELECT /*+ " + MERGE_JOIN + " */ t1.v1, t2.v2 FROM TBL1 t1 
JOIN TBL2 t2 on t1.v3=t2.v3 " +
+                "where t2.v1 in (SELECT /*+ " + CNL_JOIN + "(TBL1) */ t3.v3 
from TBL3 t3 JOIN TBL1 t4 on t3.v2=t4.v2)",
+            schema, predicateForNestedHintOverrides(true), 
CORE_JOIN_REORDER_RULES);
+
+        assertPlan("SELECT /*+ " + MERGE_JOIN + " */ t1.v1, t2.v2 FROM TBL1 t1 
JOIN TBL2 t2 on t1.v3=t2.v3 " +
+                "where t2.v1 in (SELECT t3.v3 from TBL3 /*+ " + CNL_JOIN + " 
*/ t3 JOIN TBL1 t4 on t3.v2=t4.v2)",
+            schema, predicateForNestedHintOverrides(true), 
CORE_JOIN_REORDER_RULES);
+
+        assertPlan("SELECT /*+ " + MERGE_JOIN + " */ t1.v1, t2.v2 FROM TBL1 t1 
JOIN TBL2 t2 on t1.v3=t2.v3 " +
+                "where t2.v1 in (SELECT t3.v3 from TBL3 t3 JOIN TBL1 /*+ " + 
CNL_JOIN + " */ t4 on t3.v2=t4.v2)",
+            schema, predicateForNestedHintOverrides(true), 
CORE_JOIN_REORDER_RULES);
+    }
+
+    /**
+     * @return A {@link Predicate} for {@link #testNestedHintOverrides()}
+     */
+    private Predicate<RelNode> predicateForNestedHintOverrides(boolean 
t1MergeT2) {
+        Predicate<RelNode> res = 
nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class)).negate()
+            
.and(nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+                
.and(hasNestedTableScan("TBL3")).and(hasNestedTableScan("TBL1"))));
+
+        Predicate<RelNode> t1MergeT2Pred = 
nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class)
+            .and(input(0, nodeOrAnyChild(isTableScan("TBL1")))
+                .and(input(1, nodeOrAnyChild(isTableScan("TBL2"))))));
+
+        Predicate<RelNode> t1CnlT2Pred = 
nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
+            .and(input(0, nodeOrAnyChild(isTableScan("TBL1")))
+                .and(input(1, nodeOrAnyChild(isTableScan("TBL2"))))));
+
+        return t1MergeT2
+            ? res.and(t1MergeT2Pred).and(t1CnlT2Pred.negate())
+            : res.and(t1MergeT2Pred.negate()).and(t1CnlT2Pred);
     }
 
     /**
@@ -461,14 +534,13 @@ public class JoinTypeHintPlannerTest extends 
AbstractPlannerTest {
                     .and(hasNestedTableScan("TBL2")))), 
CORE_JOIN_REORDER_RULES);
 
         // Check many duplicated disables doesn't erase other disables.
-        sqlTpl = "SELECT %s t1.v1, t2.v2, t3.v3, t4.v1 FROM TBL1 t1, TBL2 t2, 
TBL3 t3, TBL4 t4 where " +
-            "t1.v1=t2.v1 and t1.v2=t2.v2 and t1.v3=t3.v3 and t1.v1=t4.v1";
+        sqlTpl = "SELECT %s t1.v1, t2.v2 FROM TBL1 t1, TBL2 t2 where 
t1.v1=t2.v1";
 
-        String hints = "/*+ " + NO_CNL_JOIN + ',' + NO_CNL_JOIN + "(TBL1), " + 
NO_CNL_JOIN + "(TBL1,TBL2), " + NO_CNL_JOIN
-            + "(TBL1,TBL4), " + NO_MERGE_JOIN + "(TBL1) */";
+        String hints = "/*+ " + NO_CNL_JOIN + ',' + NO_CNL_JOIN + "(TBL1), " + 
NO_CNL_JOIN + "(TBL1,TBL2), "
+            + NO_NL_JOIN + "(TBL1) */";
 
         assertPlan(String.format(sqlTpl, hints), schema,
-            
nodeOrAnyChild(isInstanceOf(IgniteMergeJoin.class).and(hasChildThat(isTableScan("TBL1")))).negate()
+            
nodeOrAnyChild(isInstanceOf(IgniteNestedLoopJoin.class).and(hasChildThat(isTableScan("TBL1")))).negate()
                 
.and(nodeOrAnyChild(isInstanceOf(IgniteCorrelatedNestedLoopJoin.class)
                     .and(hasChildThat(isTableScan("TBL1")))).negate()),
             CORE_JOIN_REORDER_RULES);
diff --git 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/NoIndexHintPlannerTest.java
 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/NoIndexHintPlannerTest.java
index 39b8d9e7548..e79a766f7a5 100644
--- 
a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/NoIndexHintPlannerTest.java
+++ 
b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/planner/hints/NoIndexHintPlannerTest.java
@@ -92,15 +92,28 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
         physicalPlan("SELECT /*+ NO_INDEX, NO_INDEX(IDX2_1) */ * FROM TBL2 
WHERE val2='v'", schema);
 
         assertTrue(lsnr.check());
+
+        lsnrLog.clearListeners();
+
+        lsnr = LogListener.matches("Skipped hint 'NO_INDEX' with options 
'IDX2_1'").times(1).build();
+
+        lsnrLog.registerListener(lsnr);
+
+        // Table hint has a bigger priority.
+        physicalPlan("SELECT /*+ NO_INDEX(IDX2_1) */ * FROM TBL2 /*+ NO_INDEX 
*/ WHERE val2='v'", schema);
+
+        assertTrue(lsnr.check());
     }
 
     /** */
     @Test
     public void testCertainIndex() throws Exception {
         assertNoAnyIndex("SELECT /*+ NO_INDEX */ * FROM TBL2 WHERE val2='v'");
+        assertNoAnyIndex("SELECT * FROM TBL2 /*+ NO_INDEX */ WHERE val2='v'");
 
         // Checks lower-case idx name.
         assertCertainIndex("SELECT /*+ NO_INDEX('idx1_1') */ * FROM TBL1 WHERE 
val1='v'", "TBL1", "IDX1_1");
+        assertCertainIndex("SELECT * FROM TBL1 /*+ NO_INDEX('idx1_1') */ WHERE 
val1='v'", "TBL1", "IDX1_1");
 
         // Without quotes, Calcite's parser makes lower-case upper.
         assertNoCertainIndex("SELECT /*+ NO_INDEX(idx1_1) */ * FROM TBL1 WHERE 
val1='v'", "TBL1", "IDX1_1");
@@ -121,10 +134,16 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
         // Dedicated hint for each index.
         assertNoAnyIndex("SELECT /*+ NO_INDEX('idx1_1'), NO_INDEX(IDX1_1), 
NO_INDEX(IDX1_23), NO_INDEX(IDX1_3) */ * " +
             "FROM TBL1 WHERE val1='v' and val2='v' and val3='v'");
+        assertNoAnyIndex("SELECT /*+ NO_INDEX('idx1_1'), NO_INDEX(IDX1_1) */ * 
FROM TBL1 " +
+            "/*+ NO_INDEX(IDX1_23), NO_INDEX(IDX1_3) */  WHERE val1='v' and 
val2='v' and val3='v'");
 
         // Index of the second table.
         assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX2_3) */ t1.val3, t2.val3 
FROM TBL1 t1, TBL2 t2 WHERE " +
             "t1.val3='v' and t2.val3='v'", "TBL2", "IDX2_3");
+        assertNoCertainIndex("SELECT t1.val3, t2.val3 FROM TBL1 t1, TBL2 /*+ 
NO_INDEX(IDX2_3) */ t2 WHERE " +
+            "t1.val3='v' and t2.val3='v'", "TBL2", "IDX2_3");
+        assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX2_1) */ t1.val3, t2.val3 
FROM TBL1  t1, " +
+            "TBL2 /*+ NO_INDEX(IDX2_3) */ t2 WHERE t1.val3='v' and t2.val3='v' 
and t2.val1='v'", "TBL2", "IDX2_3");
     }
 
     /** */
@@ -140,6 +159,8 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
         // Not-root hint.
         assertNoCertainIndex("SELECT * FROM TBL1 t1, (select /*+ 
NO_INDEX(IDX2_3) */ * FROM TBL2 WHERE " +
             "val3='v') t2 WHERE t1.val2='v'", "TBL2", "IDX2_3");
+        assertNoCertainIndex("SELECT * FROM TBL1 t1, (select * FROM TBL2 /*+ 
NO_INDEX(IDX2_3) */ WHERE " +
+            "val3='v') t2 WHERE t1.val2='v'", "TBL2", "IDX2_3");
     }
 
     /** */
@@ -158,6 +179,7 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
     @Test
     public void testOrderBy() throws Exception {
         assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX1_23) */ val3 FROM TBL1 
order by val2, val3", "TBL1", "IDX1_23");
+        assertNoCertainIndex("SELECT val3 FROM TBL1 /*+ NO_INDEX(IDX1_23) */ 
order by val2, val3", "TBL1", "IDX1_23");
     }
 
     /** */
@@ -173,7 +195,11 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
     private void doTestAggregate(String op) throws Exception {
         assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX2_3) */ " + op + "(val1) 
FROM TBL2 group by val3", "TBL2", "IDX2_3");
 
+        assertNoCertainIndex("SELECT " + op + "(val1) FROM TBL2 /*+ 
NO_INDEX(IDX2_3) */ group by val3", "TBL2", "IDX2_3");
+
         assertNoAnyIndex("SELECT /*+ NO_INDEX */ " + op + "(val1) FROM TBL2 
group by val3");
+
+        assertNoAnyIndex("SELECT " + op + "(val1) FROM TBL2 /*+ NO_INDEX */ 
group by val3");
     }
 
     /** */
@@ -181,11 +207,15 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
     public void testJoins() throws Exception {
         assertNoAnyIndex("SELECT /*+ NO_INDEX */ t1.val1, t2.val2 FROM TBL1 
t1, TBL2 t2 where " +
             "t2.val3=t1.val3");
+        assertNoAnyIndex("SELECT t1.val1, t2.val2 FROM TBL1 /*+ NO_INDEX */ 
t1, TBL2 /*+ NO_INDEX */ t2 where " +
+            "t2.val3=t1.val3");
 
         assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX1_3,IDX2_3) */ t1.val1, 
t2.val2 FROM TBL1 t1, TBL2 t2 where " +
             "t2.val3=t1.val3", "TBL1", "IDX1_3");
         assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX1_3,IDX2_3) */ t1.val1, 
t2.val2 FROM TBL1 t1, TBL2 t2 where " +
             "t2.val3=t1.val3", "TBL2", "IDX2_3");
+        assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX1_3) */ t1.val1, t2.val2 
FROM TBL1 t1, " +
+            "TBL2 /*+ NO_INDEX(IDX2_3) */ t2 where t2.val3=t1.val3", "TBL2", 
"IDX2_3");
 
         assertNoCertainIndex("SELECT /*+ NO_INDEX(IDX1_3) */ t1.val1, t2.val2 
FROM TBL1 t1 JOIN TBL2 t2 on " +
             "t1.val3=t2.val3", "TBL1", "IDX1_3");
@@ -227,11 +257,21 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
             nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3"))
                 .and(nodeOrAnyChild(isIndexScan("TBL1", "IDX1_23")).negate()));
 
+        assertPlan(String.format("SELECT t1.* FROM TBL1 /*+ NO_INDEX */ t1 
where t1.val2='v' %s " +
+                "SELECT t2.* FROM TBL2 t2 where t2.val3='v'", operation), 
schema,
+            nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3"))
+                .and(nodeOrAnyChild(isIndexScan("TBL1", "IDX1_23")).negate()));
+
         assertPlan(String.format("SELECT t1.* FROM TBL1 t1 where t1.val2='v' 
%s " +
                 "SELECT /*+ NO_INDEX(IDX2_3) */ t2.* FROM TBL2 t2 where 
t2.val3='v'", operation), schema,
             nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")).negate()
                 .and(nodeOrAnyChild(isIndexScan("TBL1", "IDX1_23"))));
 
+        assertPlan(String.format("SELECT t1.* FROM TBL1 t1 where t1.val2='v' 
%s " +
+                "SELECT t2.* FROM TBL2 /*+ NO_INDEX(IDX2_3) */ t2 where 
t2.val3='v'", operation), schema,
+            nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")).negate()
+                .and(nodeOrAnyChild(isIndexScan("TBL1", "IDX1_23"))));
+
         assertPlan(String.format("SELECT t1.* FROM TBL1 t1 where t1.val2='v' 
%s " +
                 "SELECT /*+ NO_INDEX */ t2.* FROM TBL2 t2 where t2.val3='v'", 
operation), schema,
             nodeOrAnyChild(isIndexScan("TBL2", "IDX2_3")).negate()
@@ -263,12 +303,16 @@ public class NoIndexHintPlannerTest extends 
AbstractPlannerTest {
 
         assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX1_3), NO_INDEX(IDX2_3) */ * 
FROM TBL1 t1 WHERE t1.val3 = " +
             "(SELECT val2 from TBL2 WHERE val3=" + valueOfT2Val3 + ')');
+        assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX1_3) */ * FROM TBL1 t1 WHERE 
t1.val3 = " +
+            "(SELECT val2 from TBL2 /*+ NO_INDEX */  WHERE val3=" + 
valueOfT2Val3 + ')');
         assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX1_3, IDX2_3) */ * FROM TBL1 
t1 WHERE t1.val3 = " +
             "(SELECT val2 from TBL2 WHERE val3=" + valueOfT2Val3 + ')');
         assertNoAnyIndex("SELECT /*+ NO_INDEX */ * FROM TBL1 t1 WHERE t1.val3 
= " +
             "(SELECT val2 from TBL2 WHERE val3=" + valueOfT2Val3 + ')');
         assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX1_3) */ * FROM TBL1 t1 WHERE 
t1.val3 = " +
             "(SELECT /*+ NO_INDEX(IDX2_3) */ val2 from TBL2 WHERE val3=" + 
valueOfT2Val3 + ')');
+        assertNoAnyIndex("SELECT /*+ NO_INDEX(IDX1_3) */ * FROM TBL1 t1 WHERE 
t1.val3 = " +
+            "(SELECT val2 from TBL2 /*+ NO_INDEX(IDX2_3) */ WHERE val3=" + 
valueOfT2Val3 + ')');
     }
 
     /** */


Reply via email to