/*
* Licensed 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.facebook.presto.sql.gen;
import com.facebook.presto.bytecode.ClassDefinition;
import com.facebook.presto.bytecode.CompilationException;
import com.facebook.presto.metadata.Metadata;
import com.facebook.presto.operator.CursorProcessor;
import com.facebook.presto.operator.project.PageFilter;
import com.facebook.presto.operator.project.PageProcessor;
import com.facebook.presto.operator.project.PageProjection;
import com.facebook.presto.spi.PrestoException;
import com.facebook.presto.sql.relational.RowExpression;
import com.google.common.base.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.ImmutableList;
import org.weakref.jmx.Managed;
import org.weakref.jmx.Nested;
import javax.inject.Inject;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import static com.facebook.presto.bytecode.Access.FINAL;
import static com.facebook.presto.bytecode.Access.PUBLIC;
import static com.facebook.presto.bytecode.Access.a;
import static com.facebook.presto.bytecode.CompilerUtils.defineClass;
import static com.facebook.presto.bytecode.CompilerUtils.makeClassName;
import static com.facebook.presto.bytecode.ParameterizedType.type;
import static com.facebook.presto.spi.StandardErrorCode.COMPILER_ERROR;
import static com.facebook.presto.spi.type.BooleanType.BOOLEAN;
import static com.facebook.presto.sql.gen.BytecodeUtils.invoke;
import static com.facebook.presto.sql.relational.Expressions.constant;
import static com.google.common.base.MoreObjects.toStringHelper;
import static com.google.common.collect.ImmutableList.toImmutableList;
public class ExpressionCompiler
{
private final Metadata metadata;
private final LoadingCache<CacheKey, Class<? extends CursorProcessor>> cursorProcessors = CacheBuilder.newBuilder().recordStats().maximumSize(1000).build(
new CacheLoader<CacheKey, Class<? extends CursorProcessor>>()
{
@Override
public Class<? extends CursorProcessor> load(CacheKey key)
throws Exception
{
return compile(key.getFilter(), key.getProjections(), new CursorProcessorCompiler(metadata), CursorProcessor.class);
}
});
@Inject
public ExpressionCompiler(Metadata metadata)
{
this.metadata = metadata;
}
@Managed
public long getCacheSize()
{
return cursorProcessors.size();
}
@Managed
@Nested
public CacheStatsMBean getCursorCacheStats()
{
return new CacheStatsMBean(cursorProcessors);
}
public Supplier<CursorProcessor> compileCursorProcessor(Optional<RowExpression> filter, List<? extends RowExpression> projections, Object uniqueKey)
{
Class<? extends CursorProcessor> cursorProcessor = cursorProcessors.getUnchecked(new CacheKey(filter, projections, uniqueKey));
return () -> {
try {
return cursorProcessor.newInstance();
}
catch (ReflectiveOperationException e) {
throw Throwables.propagate(e);
}
};
}
public Supplier<PageProcessor> compilePageProcessor(Optional<RowExpression> filter, List<? extends RowExpression> projections)
{
PageFunctionCompiler pageFunctionCompiler = new PageFunctionCompiler(metadata);
Optional<Supplier<PageFilter>> filterFunctionSupplier = filter.map(pageFunctionCompiler::compileFilter);
List<Supplier<PageProjection>> pageProjectionSuppliers = projections.stream()
.map(pageFunctionCompiler::compileProjection)
.collect(toImmutableList());
return () -> {
Optional<PageFilter> filterFunction = filterFunctionSupplier.map(Supplier::get);
List<PageProjection> pageProjections = pageProjectionSuppliers.stream()
.map(Supplier::get)
.collect(toImmutableList());
return new PageProcessor(filterFunction, pageProjections);
};
}
private <T> Class<? extends T> compile(Optional<RowExpression> filter, List<RowExpression> projections, BodyCompiler<T> bodyCompiler, Class<? extends T> superType)
{
// create filter and project page iterator class
try {
return compileProcessor(filter.orElse(constant(true, BOOLEAN)), projections, bodyCompiler, superType);
}
catch (CompilationException e) {
throw new PrestoException(COMPILER_ERROR, e.getCause());
}
}
private <T> Class<? extends T> compileProcessor(
RowExpression filter,
List<RowExpression> projections,
BodyCompiler<T> bodyCompiler,
Class<? extends T> superType)
{
ClassDefinition classDefinition = new ClassDefinition(
a(PUBLIC, FINAL),
makeClassName(superType.getSimpleName()),
type(Object.class),
type(superType));
CallSiteBinder callSiteBinder = new CallSiteBinder();
bodyCompiler.generateMethods(classDefinition, callSiteBinder, filter, projections);
//
// toString method
//
generateToString(
classDefinition,
callSiteBinder,
toStringHelper(classDefinition.getType().getJavaClassName())
.add("filter", filter)
.add("projections", projections)
.toString());
return defineClass(classDefinition, superType, callSiteBinder.getBindings(), getClass().getClassLoader());
}
private static void generateToString(ClassDefinition classDefinition, CallSiteBinder callSiteBinder, String string)
{
// bind constant via invokedynamic to avoid constant pool issues due to large strings
classDefinition.declareMethod(a(PUBLIC), "toString", type(String.class))
.getBody()
.append(invoke(callSiteBinder.bind(string, String.class), "toString"))
.retObject();
}
private static final class CacheKey
{
private final Optional<RowExpression> filter;
private final List<RowExpression> projections;
private final Object uniqueKey;
private CacheKey(Optional<RowExpression> filter, List<? extends RowExpression> projections, Object uniqueKey)
{
this.filter = filter;
this.uniqueKey = uniqueKey;
this.projections = ImmutableList.copyOf(projections);
}
private Optional<RowExpression> getFilter()
{
return filter;
}
private List<RowExpression> getProjections()
{
return projections;
}
@Override
public int hashCode()
{
return Objects.hash(filter, projections, uniqueKey);
}
@Override
public boolean equals(Object obj)
{
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CacheKey other = (CacheKey) obj;
return Objects.equals(this.filter, other.filter) &&
Objects.equals(this.projections, other.projections) &&
Objects.equals(this.uniqueKey, other.uniqueKey);
}
@Override
public String toString()
{
return toStringHelper(this)
.add("filter", filter)
.add("projections", projections)
.add("uniqueKey", uniqueKey)
.toString();
}
}
}