Hook.java
/*
* 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.runtime;
import org.apache.calcite.rel.RelRoot;
import org.apache.calcite.util.Holder;
import org.apache.calcite.util.TryThreadLocal;
import org.apache.calcite.util.Util;
import org.apiguardian.api.API;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Collection of hooks that can be set by observers and are executed at various
* parts of the query preparation process.
*
* <p>For testing and debugging rather than for end-users.
*/
public enum Hook {
/** Called to get the current time. Use this to return a predictable time
* in tests. */
CURRENT_TIME,
/** Called to get stdin, stdout, stderr.
* Use this to re-assign streams in tests. */
STANDARD_STREAMS,
/** Returns a boolean value, whether RelBuilder should simplify expressions.
* Default true. */
REL_BUILDER_SIMPLIFY,
/** Returns a boolean value, whether the return convention should be
* {@link org.apache.calcite.interpreter.BindableConvention}.
* Default false. */
ENABLE_BINDABLE,
/** Called with the SQL string and parse tree, in an array. */
PARSE_TREE,
/** Converts a SQL string to a
* {@link org.apache.calcite.jdbc.CalcitePrepare.Query} object. This hook is
* an opportunity to execute a {@link org.apache.calcite.rel.RelNode} query
* plan in the JDBC driver rather than the usual SQL string. */
STRING_TO_QUERY,
/** Called with the generated Java plan, just before it is compiled by
* Janino. */
JAVA_PLAN,
/** Called before SqlToRelConverter is built. */
SQL2REL_CONVERTER_CONFIG_BUILDER,
/** Called with the output of sql-to-rel-converter. */
CONVERTED,
/** Called with the created planner. */
PLANNER,
/** Called after de-correlation and field trimming, but before
* optimization. */
TRIMMED,
/** Called by the planner after substituting a materialization. */
SUB,
/** Called when a constant expression is being reduced. */
EXPRESSION_REDUCER,
/** Called to create a Program to optimize the statement. */
PROGRAM,
/** Called when materialization is created. */
CREATE_MATERIALIZATION,
/** Called with a query that has been generated to send to a back-end system.
* The query might be a SQL string (for the JDBC adapter), a list of Mongo
* pipeline expressions (for the MongoDB adapter), et cetera. */
QUERY_PLAN,
/**
* Called when a plan is about to be implemented (e.g. implemented via Enumerable, Bindable,
* and so on).
* The hook supplies {@link RelRoot} as an argument.
*/
@API(since = "1.22", status = API.Status.EXPERIMENTAL)
PLAN_BEFORE_IMPLEMENTATION;
@SuppressWarnings("ImmutableEnumChecker")
private final List<Consumer<Object>> handlers =
new CopyOnWriteArrayList<>();
@SuppressWarnings("ImmutableEnumChecker")
private final TryThreadLocal<List<Consumer<Object>>> threadHandlers =
TryThreadLocal.withInitial(ArrayList::new);
/** Adds a handler for this Hook.
*
* <p>Returns a {@link Hook.Closeable} so that you can use the following
* try-finally pattern to prevent leaks:
*
* <blockquote><pre>
* final Hook.Closeable closeable = Hook.FOO.add(HANDLER);
* try {
* ...
* } finally {
* closeable.close();
* }</pre>
* </blockquote>
*
* @deprecated this installs a global hook (cross-thread), so it might have greater impact
* than expected. Use with caution. Prefer thread-local hooks.
* @see #addThread(Consumer)
*/
@API(status = API.Status.MAINTAINED)
@Deprecated
public <T> Closeable add(final Consumer<T> handler) {
//noinspection unchecked
handlers.add((Consumer<Object>) handler);
return () -> remove(handler);
}
// CHECKSTYLE: IGNORE 1
/** @deprecated Use {@link #add(Consumer)}. */
@SuppressWarnings({"Guava", "ReturnValueIgnored"})
@Deprecated // to be removed before 2.0
public <T, R> Closeable add(final Function<T, R> handler) {
return add((Consumer<T>) handler::apply);
}
/** Removes a handler from this Hook. */
private boolean remove(Consumer handler) {
return handlers.remove(handler);
}
/** Adds a handler for this thread. */
public <T> Closeable addThread(final Consumer<T> handler) {
//noinspection unchecked
threadHandlers.get().add((Consumer<Object>) handler);
return () -> removeThread(handler);
}
// CHECKSTYLE: IGNORE 1
/** @deprecated Use {@link #addThread(Consumer)}. */
@SuppressWarnings("Guava")
@Deprecated // to be removed before 2.0
public <T, R> Closeable addThread(
final com.google.common.base.Function<T, R> handler) {
return addThread(functionConsumer(handler));
}
/** Converts a Guava function into a JDK consumer. */
@SuppressWarnings("Guava")
private static <T, R> Consumer<T> functionConsumer(
com.google.common.base.Function<T, R> handler) {
return t -> {
// Squash ErrorProne warnings that the return of the function is not
// used.
R r = handler.apply(t);
Util.discard(r);
};
}
/** Removes a thread handler from this Hook. */
@SuppressWarnings({"rawtypes", "UnusedReturnValue"})
private boolean removeThread(Consumer handler) {
return threadHandlers.get().remove(handler);
}
// CHECKSTYLE: IGNORE 1
/** @deprecated Use {@link #propertyJ}. */
@SuppressWarnings("Guava")
@Deprecated // return type will change in 2.0
public static <V> com.google.common.base.Function<Holder<V>, Void> property(final V v) {
return holder -> {
holder.set(v);
return null;
};
}
/** Returns a function that, when a hook is called, will "return" a given
* value. (Because of the way hooks work, it "returns" the value by writing
* into a {@link Holder}. */
public static <V> Consumer<Holder<V>> propertyJ(final V v) {
return holder -> {
holder.set(v);
};
}
/** Runs all handlers registered for this Hook, with the given argument. */
public void run(Object arg) {
for (Consumer<Object> handler : handlers) {
handler.accept(arg);
}
for (Consumer<Object> handler : threadHandlers.get()) {
handler.accept(arg);
}
}
/** Returns the value of a property hook.
* (Property hooks take a {@link Holder} as an argument.) */
public <V> V get(V defaultValue) {
final Holder<V> holder = Holder.of(defaultValue);
run(holder);
return holder.get();
}
/** Removes a Hook after use. */
public interface Closeable extends AutoCloseable {
/** Closeable that does nothing. */
Closeable EMPTY = () -> { };
// override, removing "throws"
@Override void close();
}
}