com.hazelcast.org.apache.calcite.rel.hint.RelHint Maven / Gradle / Ivy
/*
* 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 com.hazelcast.org.apache.calcite.rel.hint;
import com.hazelcast.com.google.common.base.Preconditions;
import com.hazelcast.com.google.common.collect.ImmutableList;
import com.hazelcast.com.google.common.collect.ImmutableMap;
import com.hazelcast.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;
/**
* Hint attached to a relation expression.
*
* A hint can be used to:
*
*
* - 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.
* - 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.
* - 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).
*
*
* 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:
*
*
* Filter (Hint1)
* |
* Join
* / \
* Scan Project (Hint2)
* |
* Scan2
*
*
* The plan would have hints path as follows (assumes each hint can be propagated to all
* child nodes):
*
*
* - Filter → {Hint1[]}
* - Join → {Hint1[0]}
* - Scan → {Hint1[0, 0]}
* - Project → {Hint1[0,1], Hint2[]}
* - Scan2 → {[Hint1[0, 1, 0], Hint2[0]}
*
*
* {@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.
*
*
RelHint is immutable.
*/
public class RelHint {
//~ Instance fields --------------------------------------------------------
public final ImmutableList inheritPath;
public final String hintName;
public final List listOptions;
public final Map 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 inheritPath,
String hintName,
@Nullable List listOption,
@Nullable Map 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 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 inheritPath;
private List listOptions;
private Map 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 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");
Preconditions.checkState(this.kvOptions.size() == 0,
"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 hintOptions) {
Objects.requireNonNull(hintOptions, "hintOptions");
Preconditions.checkState(this.kvOptions.size() == 0,
"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");
Preconditions.checkState(this.listOptions.size() == 0,
"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 kvOptions) {
Objects.requireNonNull(kvOptions, "kvOptions");
Preconditions.checkState(this.listOptions.size() == 0,
"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);
}
}
}