JaninoRelMetadataProvider.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.rel.metadata;
import org.apache.calcite.config.CalciteSystemProperty;
import org.apache.calcite.interpreter.JaninoRexCompiler;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.metadata.janino.RelMetadataHandlerGeneratorUtil;
import org.apache.calcite.util.Util;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.UncheckedExecutionException;
import org.apiguardian.api.API;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.commons.compiler.CompilerFactoryFactory;
import org.codehaus.commons.compiler.ICompilerFactory;
import org.codehaus.commons.compiler.ISimpleCompiler;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;
import static java.util.Objects.requireNonNull;
/**
* Implementation of the {@link RelMetadataProvider} interface that generates
* a class that dispatches to the underlying providers.
*/
public class JaninoRelMetadataProvider implements RelMetadataProvider, MetadataHandlerProvider {
private final RelMetadataProvider provider;
// Constants and static fields
public static final JaninoRelMetadataProvider DEFAULT =
JaninoRelMetadataProvider.of(DefaultRelMetadataProvider.INSTANCE);
/** Cache of pre-generated handlers by provider and kind of metadata.
* For the cache to be effective, providers should implement identity
* correctly. */
private static final LoadingCache<Key, MetadataHandler<?>> HANDLERS =
maxSize(CacheBuilder.newBuilder(),
CalciteSystemProperty.METADATA_HANDLER_CACHE_MAXIMUM_SIZE.value())
.build(
CacheLoader.from(key ->
generateCompileAndInstantiate(key.handlerClass,
key.provider.handlers(key.handlerClass))));
/** Private constructor; use {@link #of}. */
private JaninoRelMetadataProvider(RelMetadataProvider provider) {
this.provider = provider;
}
/** Creates a JaninoRelMetadataProvider.
*
* @param provider Underlying provider
*/
public static JaninoRelMetadataProvider of(RelMetadataProvider provider) {
if (provider instanceof JaninoRelMetadataProvider) {
return (JaninoRelMetadataProvider) provider;
}
return new JaninoRelMetadataProvider(provider);
}
// helper for initialization
private static <K, V> CacheBuilder<K, V> maxSize(CacheBuilder<K, V> builder,
int size) {
if (size >= 0) {
builder.maximumSize(size);
}
return builder;
}
@Override public boolean equals(@Nullable Object obj) {
return obj == this
|| obj instanceof JaninoRelMetadataProvider
&& ((JaninoRelMetadataProvider) obj).provider.equals(provider);
}
@Override public int hashCode() {
return 109 + provider.hashCode();
}
@Deprecated // to be removed before 2.0
@Override public <@Nullable M extends @Nullable Metadata> UnboundMetadata<M> apply(
Class<? extends RelNode> relClass, Class<? extends M> metadataClass) {
throw new UnsupportedOperationException();
}
@Deprecated // to be removed before 2.0
@Override public <M extends Metadata> Multimap<Method, MetadataHandler<M>>
handlers(MetadataDef<M> def) {
return provider.handlers(def);
}
@Override public List<MetadataHandler<?>> handlers(
Class<? extends MetadataHandler<?>> handlerClass) {
return provider.handlers(handlerClass);
}
private static <MH extends MetadataHandler<?>> MH generateCompileAndInstantiate(
Class<MH> handlerClass,
List<? extends MetadataHandler<? extends Metadata>> handlers) {
final List<? extends MetadataHandler<? extends Metadata>> uniqueHandlers = handlers.stream()
.distinct()
.collect(Collectors.toList());
RelMetadataHandlerGeneratorUtil.HandlerNameAndGeneratedCode handlerNameAndGeneratedCode =
RelMetadataHandlerGeneratorUtil.generateHandler(handlerClass, uniqueHandlers);
try {
return compile(handlerNameAndGeneratedCode.getHandlerName(),
handlerNameAndGeneratedCode.getGeneratedCode(), handlerClass, uniqueHandlers);
} catch (CompileException e) {
throw new RuntimeException("Error compiling:\n"
+ handlerNameAndGeneratedCode.getGeneratedCode(), e);
}
}
static <MH extends MetadataHandler<?>> MH compile(String className,
String generatedCode, Class<MH> handlerClass,
List<? extends Object> argList) throws CompileException {
final ICompilerFactory compilerFactory;
ClassLoader classLoader =
requireNonNull(JaninoRelMetadataProvider.class.getClassLoader(),
"classLoader");
try {
compilerFactory = CompilerFactoryFactory.getDefaultCompilerFactory(classLoader);
} catch (Exception e) {
throw new IllegalStateException(
"Unable to instantiate java compiler", e);
}
final ISimpleCompiler compiler = compilerFactory.newSimpleCompiler();
compiler.setParentClassLoader(JaninoRexCompiler.class.getClassLoader());
if (CalciteSystemProperty.DEBUG.value()) {
// Add line numbers to the generated janino class
compiler.setDebuggingInformation(true, true, true);
System.out.println(generatedCode);
}
compiler.cook(generatedCode);
final Constructor constructor;
final Object o;
try {
constructor = compiler.getClassLoader().loadClass(className)
.getDeclaredConstructors()[0];
o = constructor.newInstance(argList.toArray());
} catch (InstantiationException
| IllegalAccessException
| InvocationTargetException
| ClassNotFoundException e) {
throw new RuntimeException(e);
}
return handlerClass.cast(o);
}
@Override public synchronized <H extends MetadataHandler<?>> H revise(Class<H> handlerClass) {
try {
final Key key = new Key(handlerClass, provider);
//noinspection unchecked
return handlerClass.cast(HANDLERS.get(key));
} catch (UncheckedExecutionException | ExecutionException e) {
throw Util.throwAsRuntime(Util.causeOrSelf(e));
}
}
/** Registers some classes. Does not flush the providers, but next time we
* need to generate a provider, it will handle all of these classes. So,
* calling this method reduces the number of times we need to re-generate. */
@Deprecated
public void register(Iterable<Class<? extends RelNode>> classes) {
}
/** Exception that indicates there there should be a handler for
* this class but there is not. The action is probably to
* re-generate the handler class. Use {@link MetadataHandlerProvider.NoHandler} instead.
* */
@Deprecated
public static class NoHandler extends MetadataHandlerProvider.NoHandler {
public NoHandler(Class<? extends RelNode> relClass) {
super(relClass);
}
}
/** Key for the cache. */
private static class Key {
final Class<? extends MetadataHandler<? extends Metadata>> handlerClass;
final RelMetadataProvider provider;
private Key(Class<? extends MetadataHandler<?>> handlerClass,
RelMetadataProvider provider) {
this.handlerClass = handlerClass;
this.provider = provider;
}
@Override public int hashCode() {
return (handlerClass.hashCode() * 37
+ provider.hashCode()) * 37;
}
@Override public boolean equals(@Nullable Object obj) {
return this == obj
|| obj instanceof Key
&& ((Key) obj).handlerClass.equals(handlerClass)
&& ((Key) obj).provider.equals(provider);
}
}
@SuppressWarnings("deprecation")
@Override public <MH extends MetadataHandler<?>> MH handler(final Class<MH> handlerClass) {
return handlerClass.cast(
Proxy.newProxyInstance(RelMetadataQuery.class.getClassLoader(),
new Class[] {handlerClass}, (proxy, method, args) -> {
final RelNode r = requireNonNull((RelNode) args[0], "(RelNode) args[0]");
throw new NoHandler(r.getClass());
}));
}
@API(status = API.Status.INTERNAL)
@VisibleForTesting
public static void clearStaticCache() {
HANDLERS.invalidateAll();
}
}