/* * 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(); } } }