| /* |
| * 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.calcite.rel.hint; |
| |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| |
| import org.checkerframework.checker.nullness.qual.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| |
| import static com.google.common.base.Preconditions.checkState; |
| |
| /** |
| * Hint attached to a relation expression. |
| * |
| * <p>A hint can be used to: |
| * |
| * <ul> |
| * <li>Enforce planner: there's no perfect planner, so it makes sense to implement hints to |
| * allow user better control the execution. For instance, "never merge this subquery with others", |
| * "treat those tables as leading ones" in the join ordering, etc.</li> |
| * <li>Append meta data/statistics: Some statistics like “table index for scan” and |
| * “skew info of some shuffle keys” are somewhat dynamic for the query, it would be very |
| * convenient to config them with hints because our planning metadata from the planner is very |
| * often not that accurate.</li> |
| * <li>Operator resource constraints: For many cases, we would give a default resource |
| * configuration for the execution operators, i.e. min parallelism or |
| * managed memory (resource consuming UDF) or special resource requirement (GPU or SSD disk) |
| * and so on, it would be very flexible to profile the resource with hints per query |
| * (instead of the Job).</li> |
| * </ul> |
| * |
| * <p>In order to support hint override, each hint has a {@code inheritPath} (integers list) to |
| * record its propagate path from the root node, number `0` represents the hint was propagated |
| * along the first(left) child, number `1` represents the hint was propagated along the |
| * second(right) child. Given a relational expression tree with initial attached hints: |
| * |
| * <blockquote><pre> |
| * Filter (Hint1) |
| * | |
| * Join |
| * / \ |
| * Scan Project (Hint2) |
| * | |
| * Scan2 |
| * </pre></blockquote> |
| * |
| * <p>The plan would have hints path as follows (assumes each hint can be propagated to all |
| * child nodes): |
| * |
| * <blockquote><ul> |
| * <li>Filter → {Hint1[]}</li> |
| * <li>Join → {Hint1[0]}</li> |
| * <li>Scan → {Hint1[0, 0]}</li> |
| * <li>Project → {Hint1[0,1], Hint2[]}</li> |
| * <li>Scan2 → {[Hint1[0, 1, 0], Hint2[0]}</li> |
| * </ul></blockquote> |
| * |
| * <p>{@code listOptions} and {@code kvOptions} are supposed to contain the same information, |
| * they are mutually exclusive, that means, they can not both be non-empty. |
| * |
| * <p>RelHint is immutable. |
| */ |
| public class RelHint { |
| //~ Instance fields -------------------------------------------------------- |
| |
| public final ImmutableList<Integer> inheritPath; |
| public final String hintName; |
| public final List<String> listOptions; |
| public final Map<String, String> kvOptions; |
| |
| //~ Constructors ----------------------------------------------------------- |
| |
| /** |
| * Creates a {@code RelHint}. |
| * |
| * @param inheritPath Hint inherit path |
| * @param hintName Hint name |
| * @param listOption Hint options as string list |
| * @param kvOptions Hint options as string key value pair |
| */ |
| private RelHint( |
| Iterable<Integer> inheritPath, |
| String hintName, |
| @Nullable List<String> listOption, |
| @Nullable Map<String, String> kvOptions) { |
| Objects.requireNonNull(inheritPath, "inheritPath"); |
| Objects.requireNonNull(hintName, "hintName"); |
| this.inheritPath = ImmutableList.copyOf(inheritPath); |
| this.hintName = hintName; |
| this.listOptions = listOption == null ? ImmutableList.of() : ImmutableList.copyOf(listOption); |
| this.kvOptions = kvOptions == null ? ImmutableMap.of() : ImmutableMap.copyOf(kvOptions); |
| } |
| |
| //~ Methods ---------------------------------------------------------------- |
| |
| /** Creates a hint builder with specified hint name. */ |
| public static Builder builder(String hintName) { |
| return new Builder(hintName); |
| } |
| |
| /** |
| * Returns a copy of this hint with specified inherit path. |
| * |
| * @param inheritPath Hint path |
| * @return the new {@code RelHint} |
| */ |
| public RelHint copy(List<Integer> inheritPath) { |
| Objects.requireNonNull(inheritPath, "inheritPath"); |
| return new RelHint(inheritPath, hintName, listOptions, kvOptions); |
| } |
| |
| @Override public boolean equals(@Nullable Object o) { |
| if (this == o) { |
| return true; |
| } |
| if (o == null || getClass() != o.getClass()) { |
| return false; |
| } |
| RelHint hint = (RelHint) o; |
| return inheritPath.equals(hint.inheritPath) |
| && hintName.equals(hint.hintName) |
| && Objects.equals(listOptions, hint.listOptions) |
| && Objects.equals(kvOptions, hint.kvOptions); |
| } |
| |
| @Override public int hashCode() { |
| return Objects.hash(this.hintName, this.inheritPath, |
| this.listOptions, this.kvOptions); |
| } |
| |
| @Override public String toString() { |
| StringBuilder builder = new StringBuilder(); |
| builder.append("[") |
| .append(this.hintName) |
| .append(" inheritPath:") |
| .append(this.inheritPath); |
| if (this.listOptions.size() > 0 || this.kvOptions.size() > 0) { |
| builder.append(" options:"); |
| if (this.listOptions.size() > 0) { |
| builder.append(this.listOptions.toString()); |
| } else { |
| builder.append(this.kvOptions.toString()); |
| } |
| } |
| builder.append("]"); |
| return builder.toString(); |
| } |
| |
| //~ Inner Class ------------------------------------------------------------ |
| |
| /** Builder for {@link RelHint}. */ |
| public static class Builder { |
| private String hintName; |
| private List<Integer> inheritPath; |
| |
| private List<String> listOptions; |
| private Map<String, String> kvOptions; |
| |
| private Builder(String hintName) { |
| this.listOptions = new ArrayList<>(); |
| this.kvOptions = new LinkedHashMap<>(); |
| this.hintName = hintName; |
| this.inheritPath = ImmutableList.of(); |
| } |
| |
| /** Sets up the inherit path with given integer list. */ |
| public Builder inheritPath(Iterable<Integer> inheritPath) { |
| this.inheritPath = ImmutableList.copyOf(Objects.requireNonNull(inheritPath, "inheritPath")); |
| return this; |
| } |
| |
| /** Sets up the inherit path with given integer array. */ |
| public Builder inheritPath(Integer... inheritPath) { |
| this.inheritPath = Arrays.asList(inheritPath); |
| return this; |
| } |
| |
| /** Add a hint option as string. */ |
| public Builder hintOption(String hintOption) { |
| Objects.requireNonNull(hintOption, "hintOption"); |
| checkState(this.kvOptions.isEmpty(), |
| "List options and key value options can not be mixed in"); |
| this.listOptions.add(hintOption); |
| return this; |
| } |
| |
| /** Add multiple string hint options. */ |
| public Builder hintOptions(Iterable<String> hintOptions) { |
| Objects.requireNonNull(hintOptions, "hintOptions"); |
| checkState(this.kvOptions.isEmpty(), |
| "List options and key value options can not be mixed in"); |
| this.listOptions = ImmutableList.copyOf(hintOptions); |
| return this; |
| } |
| |
| /** Add a hint option as string key-value pair. */ |
| public Builder hintOption(String optionKey, String optionValue) { |
| Objects.requireNonNull(optionKey, "optionKey"); |
| Objects.requireNonNull(optionValue, "optionValue"); |
| checkState(this.listOptions.isEmpty(), |
| "List options and key value options can not be mixed in"); |
| this.kvOptions.put(optionKey, optionValue); |
| return this; |
| } |
| |
| /** Add multiple string key-value pair hint options. */ |
| public Builder hintOptions(Map<String, String> kvOptions) { |
| Objects.requireNonNull(kvOptions, "kvOptions"); |
| checkState(this.listOptions.isEmpty(), |
| "List options and key value options can not be mixed in"); |
| this.kvOptions = kvOptions; |
| return this; |
| } |
| |
| public RelHint build() { |
| return new RelHint(this.inheritPath, this.hintName, this.listOptions, this.kvOptions); |
| } |
| } |
| } |