Skip to content

Commit

Permalink
IGNITE-20136 SQL Calcite: Add FORCE_INDEX/NO_INDEX hints - Fixes #10902.
Browse files Browse the repository at this point in the history
Signed-off-by: Aleksey Plekhanov <plehanov.alex@gmail.com>
  • Loading branch information
Vladsz83 authored and alex-plekhanov committed Sep 28, 2023
1 parent 1185941 commit 9f70c2a
Show file tree
Hide file tree
Showing 29 changed files with 1,246 additions and 99 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,6 @@
import org.apache.calcite.plan.ConventionTraitDef;
import org.apache.calcite.plan.RelTraitDef;
import org.apache.calcite.rel.RelCollationTraitDef;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.hint.HintStrategyTable;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDynamicParam;
Expand Down Expand Up @@ -77,6 +75,7 @@
import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecutor;
import org.apache.ignite.internal.processors.query.calcite.exec.QueryTaskExecutorImpl;
import org.apache.ignite.internal.processors.query.calcite.exec.exp.RexExecutorImpl;
import org.apache.ignite.internal.processors.query.calcite.hint.HintsConfig;
import org.apache.ignite.internal.processors.query.calcite.message.MessageService;
import org.apache.ignite.internal.processors.query.calcite.message.MessageServiceImpl;
import org.apache.ignite.internal.processors.query.calcite.metadata.AffinityService;
Expand Down Expand Up @@ -143,14 +142,7 @@ public class CalciteQueryProcessor extends GridProcessorAdapter implements Query
.withInSubQueryThreshold(Integer.MAX_VALUE)
.withDecorrelationEnabled(true)
.withExpand(false)
.withHintStrategyTable(
HintStrategyTable.builder()
.hintStrategy("DISABLE_RULE", (hint, rel) -> true)
.hintStrategy("EXPAND_DISTINCT_AGG", (hint, rel) -> rel instanceof Aggregate)
// QUERY_ENGINE hint preprocessed by regexp, but to avoid warnings should be also in HintStrategyTable.
.hintStrategy("QUERY_ENGINE", (hint, rel) -> true)
.build()
)
.withHintStrategyTable(HintsConfig.buildHintTable())
)
.convertletTable(IgniteConvertletTable.INSTANCE)
.parserConfig(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*
* 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.processors.query.calcite.hint;

import org.apache.calcite.rel.hint.HintPredicate;
import org.apache.calcite.rel.hint.HintPredicates;
import org.apache.ignite.internal.processors.query.calcite.rel.logical.IgniteLogicalTableScan;

/**
* Holds supported SQL hints and their settings.
*/
public enum HintDefinition {
/** Sets the query engine like H2 or Calcite. Is preprocessed by regexp. */
QUERY_ENGINE,

/** Disables planner rules. */
DISABLE_RULE,

/** Forces expanding of distinct aggregates to join. */
EXPAND_DISTINCT_AGG {
/** {@inheritDoc} */
@Override public HintPredicate predicate() {
return HintPredicates.AGGREGATE;
}

/** {@inheritDoc} */
@Override public HintOptionsChecker optionsChecker() {
return HintsConfig.OPTS_CHECK_EMPTY;
}
},

/** Disables indexes. */
NO_INDEX {
/** {@inheritDoc} */
@Override public HintPredicate predicate() {
return (hint, rel) -> rel instanceof IgniteLogicalTableScan;
}

/** {@inheritDoc} */
@Override public HintOptionsChecker optionsChecker() {
return HintsConfig.OPTS_CHECK_NO_KV;
}
},

/** Forces index usage. */
FORCE_INDEX {
/** {@inheritDoc} */
@Override public HintPredicate predicate() {
return NO_INDEX.predicate();
}

/** {@inheritDoc} */
@Override public HintOptionsChecker optionsChecker() {
return NO_INDEX.optionsChecker();
}
};

/**
* @return Hint predicate which limits redundant hint copying and reduces mem/cpu consumption.
*/
HintPredicate predicate() {
return HintPredicates.SET_VAR;
}

/**
* @return {@link HintOptionsChecker}.
*/
HintOptionsChecker optionsChecker() {
return HintsConfig.OPTS_CHECK_PLAIN;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* 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.processors.query.calcite.hint;

import java.util.function.Function;
import org.apache.calcite.rel.hint.RelHint;
import org.jetbrains.annotations.Nullable;

/**
* Hint options validator. Returns error text if the rel hint can't accept passed options.
* Otherwise, returns {@code null}.
*/
interface HintOptionsChecker extends Function<RelHint, @Nullable String> {
/* No-op. */
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/*
* 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.processors.query.calcite.hint;

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.calcite.plan.RelOptUtil;
import org.apache.calcite.rel.AbstractRelNode;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.RelWriter;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.hint.HintStrategyTable;
import org.apache.calcite.rel.hint.Hintable;
import org.apache.calcite.rel.hint.RelHint;
import org.apache.calcite.rel.logical.LogicalAggregate;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlExplainLevel;
import org.apache.ignite.IgniteLogger;
import org.apache.ignite.internal.processors.query.calcite.prepare.BaseQueryContext;
import org.apache.ignite.internal.processors.query.calcite.util.Commons;
import org.apache.ignite.internal.util.typedef.F;

import static org.apache.ignite.internal.processors.query.calcite.hint.HintDefinition.EXPAND_DISTINCT_AGG;

/**
* Base class for working with Calcite's SQL hints.
*/
public final class HintUtils {
/** */
private HintUtils() {
// No-op.
}

/**
* @return Combined list options of all {@code hints} filtered with {@code hintDef}.
* @see #filterHints(RelNode, Collection, List)
*/
public static Collection<String> options(RelNode rel, Collection<RelHint> hints, HintDefinition hintDef) {
return F.flatCollections(filterHints(rel, hints, Collections.singletonList(hintDef)).stream()
.map(h -> h.listOptions).collect(Collectors.toList()));
}

/**
* @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, HintDefinition... hintDefs) {
return rel.getCluster().getHintStrategies()
.apply(filterHints(rel, allRelHints(rel), Arrays.asList(hintDefs)), rel);
}

/**
* @return 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> allRelHints(RelNode rel) {
return rel instanceof Hintable ? ((Hintable)rel).getHints() : Collections.emptyList();
}

/**
* @return Distinct hints within {@code hints} filtered with {@code hintDefs}, {@link HintOptionsChecker} and
* removed inherit pathes.
* @see HintOptionsChecker
* @see RelHint#inheritPath
*/
private static List<RelHint> filterHints(RelNode rel, Collection<RelHint> hints, List<HintDefinition> hintDefs) {
Set<String> requiredHintDefs = hintDefs.stream().map(Enum::name).collect(Collectors.toSet());

List<RelHint> res = hints.stream().filter(h -> requiredHintDefs.contains(h.hintName))
.map(h -> {
RelHint.Builder rb = RelHint.builder(h.hintName);

if (!h.listOptions.isEmpty())
rb.hintOptions(h.listOptions);
else if (!h.kvOptions.isEmpty())
rb.hintOptions(h.kvOptions);

return rb.build();
}).distinct().collect(Collectors.toList());

// Validate hint options.
Iterator<RelHint> it = res.iterator();

while (it.hasNext()) {
RelHint hint = it.next();

String optsErr = HintDefinition.valueOf(hint.hintName).optionsChecker().apply(hint);

if (!F.isEmpty(optsErr)) {
skippedHint(rel, hint, optsErr);

it.remove();
}
}

return res;
}

/**
* @return {@code True} if {@code rel} is hinted with {@link HintDefinition#EXPAND_DISTINCT_AGG}.
* {@code False} otherwise.
*/
public static boolean isExpandDistinctAggregate(LogicalAggregate rel) {
return !hints(rel, EXPAND_DISTINCT_AGG).isEmpty()
&& rel.getAggCallList().stream().anyMatch(AggregateCall::isDistinct);
}

/**
* Logs skipped hint.
*/
public static void skippedHint(RelNode relNode, RelHint hint, String reason) {
IgniteLogger log = Commons.context(relNode).unwrap(BaseQueryContext.class).logger();

if (log.isDebugEnabled()) {
String hintOptions = hint.listOptions.isEmpty() ? "" : "with options "
+ hint.listOptions.stream().map(o -> '\'' + o + '\'').collect(Collectors.joining(","))
+ ' ';

if (!relNode.getInputs().isEmpty())
relNode = new NoInputsRelNodeWrap(relNode);

log.debug(String.format("Skipped hint '%s' %sfor relation operator '%s'. %s", hint.hintName,
hintOptions, RelOptUtil.toString(relNode, SqlExplainLevel.EXPPLAN_ATTRIBUTES).trim(), reason));
}
}

/** */
private static final class NoInputsRelNodeWrap extends AbstractRelNode {
/** Original rel. */
private final RelNode rel;

/** Ctor. */
private NoInputsRelNodeWrap(RelNode relNode) {
super(relNode.getCluster(), relNode.getTraitSet());

this.rel = relNode;
}

/** {@inheritDoc} */
@Override public List<RelNode> getInputs() {
return Collections.emptyList();
}

/** {@inheritDoc} */
@Override protected RelDataType deriveRowType() {
return rel.getRowType();
}

/** {@inheritDoc} */
@Override public void explain(RelWriter pw) {
rel.explain(pw);
}
}
}
Loading

0 comments on commit 9f70c2a

Please sign in to comment.