Skip to content

Commit

Permalink
[CALCITE-5968] Provide an interface class for RexExecutable to decoup…
Browse files Browse the repository at this point in the history
…le janino runtime binding
  • Loading branch information
zhuwenzhuang committed Sep 18, 2023
1 parent 2bf1e7b commit d508405
Show file tree
Hide file tree
Showing 6 changed files with 300 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
import org.apache.calcite.rex.RexCall;
import org.apache.calcite.rex.RexExecutable;
import org.apache.calcite.rex.RexExecutor;
import org.apache.calcite.rex.RexExecutorImpl;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexUtil;
Expand Down Expand Up @@ -256,7 +255,7 @@ private boolean isSatisfiable(RexNode second, @Nullable DataContext dataValues)
}

ImmutableList<RexNode> constExps = ImmutableList.of(second);
final RexExecutable exec = RexExecutorImpl.getExecutable(builder, constExps, rowType);
final RexExecutable exec = executor.getExecutable(builder, constExps, rowType);

@Nullable Object[] result;
exec.setDataContext(dataValues);
Expand Down
114 changes: 26 additions & 88 deletions core/src/main/java/org/apache/calcite/rex/RexExecutable.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,100 +17,38 @@
package org.apache.calcite.rex;

import org.apache.calcite.DataContext;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.runtime.Utilities;
import org.apache.calcite.util.Pair;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.janino.ClassBodyEvaluator;
import org.codehaus.janino.Scanner;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;

import static java.util.Objects.requireNonNull;

/**
* Result of compiling code generated from a {@link RexNode} expression.
* Executable interface for a {@link RexNode} expression.
*/
public class RexExecutable {
private static final String GENERATED_CLASS_NAME = "Reducer";

private final Function1<DataContext, @Nullable Object @Nullable []> compiledFunction;
private final String code;
private @Nullable DataContext dataContext;

public RexExecutable(String code, Object reason) {
this.code = code;
this.compiledFunction = compile(code, reason);
}

private static Function1<DataContext, @Nullable Object @Nullable []> compile(String code,
Object reason) {
try {
final ClassBodyEvaluator cbe = new ClassBodyEvaluator();
cbe.setClassName(GENERATED_CLASS_NAME);
cbe.setExtendedClass(Utilities.class);
cbe.setImplementedInterfaces(new Class[] {Function1.class, Serializable.class});
cbe.setParentClassLoader(RexExecutable.class.getClassLoader());
cbe.cook(new Scanner(null, new StringReader(code)));
Class c = cbe.getClazz();
//noinspection unchecked
final Constructor<Function1<DataContext, @Nullable Object @Nullable []>> constructor =
c.getConstructor();
return constructor.newInstance();
} catch (CompileException | IOException | InstantiationException
| IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException("While compiling " + reason, e);
}
}

public void setDataContext(DataContext dataContext) {
this.dataContext = dataContext;
}

public void reduce(RexBuilder rexBuilder, List<RexNode> constExps,
List<RexNode> reducedValues) {
@Nullable Object[] values;
try {
values = execute();
if (values == null) {
reducedValues.addAll(constExps);
values = new Object[constExps.size()];
} else {
assert values.length == constExps.size();
final List<@Nullable Object> valueList = Arrays.asList(values);
for (Pair<RexNode, @Nullable Object> value : Pair.zip(constExps, valueList)) {
reducedValues.add(
rexBuilder.makeLiteral(value.right, value.left.getType(), true));
}
}
} catch (RuntimeException e) {
// One or more of the expressions failed.
// Don't reduce any of the expressions.
reducedValues.addAll(constExps);
values = new Object[constExps.size()];
}
Hook.EXPRESSION_REDUCER.run(Pair.of(code, values));
}

public Function1<DataContext, @Nullable Object @Nullable []> getFunction() {
return compiledFunction;
}

public @Nullable Object @Nullable [] execute() {
return compiledFunction.apply(requireNonNull(dataContext, "dataContext"));
}
public interface RexExecutable {

/** Reduces expressions, and writes their results into {@code reducedValues}.
*
* <p>If an expression cannot be reduced, writes the original expression.
* For example, {@code CAST('abc' AS INTEGER)} gives an error when executed, so the executor
* ignores the error and writes the original expression.
*
* @param rexBuilder Rex builder
* @param constExps Expressions to be reduced
* @param reducedValues List to which reduced expressions are appended
*/
void reduce(RexBuilder rexBuilder, List<RexNode> constExps, List<RexNode> reducedValues);

/** Set input data for the {@link RexNode} expression.
*
* @param dataContext input data
*/
void setDataContext(DataContext dataContext);

/** Execute the {@link RexNode} expression.
*
* @return execute result
*/
@Nullable Object @Nullable [] execute();

public String getSource() {
return code;
}
}
117 changes: 117 additions & 0 deletions core/src/main/java/org/apache/calcite/rex/RexExecutableImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* 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.rex;

import org.apache.calcite.DataContext;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.runtime.Hook;
import org.apache.calcite.runtime.Utilities;
import org.apache.calcite.util.Pair;

import org.checkerframework.checker.nullness.qual.Nullable;
import org.codehaus.commons.compiler.CompileException;
import org.codehaus.janino.ClassBodyEvaluator;
import org.codehaus.janino.Scanner;

import java.io.IOException;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;

import static java.util.Objects.requireNonNull;

/**
* Result of compiling code generated from a {@link RexNode} expression.
*/
public class RexExecutableImpl implements RexExecutable {
private static final String GENERATED_CLASS_NAME = "Reducer";

private final Function1<DataContext, @Nullable Object @Nullable []> compiledFunction;
private final String code;
private @Nullable DataContext dataContext;

public RexExecutableImpl(String code, Object reason) {
this.code = code;
this.compiledFunction = compile(code, reason);
}

private static Function1<DataContext, @Nullable Object @Nullable []> compile(String code,
Object reason) {
try {
final ClassBodyEvaluator cbe = new ClassBodyEvaluator();
cbe.setClassName(GENERATED_CLASS_NAME);
cbe.setExtendedClass(Utilities.class);
cbe.setImplementedInterfaces(new Class[] {Function1.class, Serializable.class});
cbe.setParentClassLoader(RexExecutableImpl.class.getClassLoader());
cbe.cook(new Scanner(null, new StringReader(code)));
Class c = cbe.getClazz();
//noinspection unchecked
final Constructor<Function1<DataContext, @Nullable Object @Nullable []>> constructor =
c.getConstructor();
return constructor.newInstance();
} catch (CompileException | IOException | InstantiationException
| IllegalAccessException | InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException("While compiling " + reason, e);
}
}

@Override public void setDataContext(DataContext dataContext) {
this.dataContext = dataContext;
}

@Override public void reduce(RexBuilder rexBuilder, List<RexNode> constExps,
List<RexNode> reducedValues) {
@Nullable Object[] values;
try {
values = execute();
if (values == null) {
reducedValues.addAll(constExps);
values = new Object[constExps.size()];
} else {
assert values.length == constExps.size();
final List<@Nullable Object> valueList = Arrays.asList(values);
for (Pair<RexNode, @Nullable Object> value : Pair.zip(constExps, valueList)) {
reducedValues.add(
rexBuilder.makeLiteral(value.right, value.left.getType(), true));
}
}
} catch (RuntimeException e) {
// One or more of the expressions failed.
// Don't reduce any of the expressions.
reducedValues.clear();
reducedValues.addAll(constExps);
values = new Object[constExps.size()];
}
Hook.EXPRESSION_REDUCER.run(Pair.of(code, values));
}

public Function1<DataContext, @Nullable Object @Nullable []> getFunction() {
return compiledFunction;
}

@Override public @Nullable Object @Nullable [] execute() {
return compiledFunction.apply(requireNonNull(dataContext, "dataContext"));
}

public String getSource() {
return code;
}
}
11 changes: 11 additions & 0 deletions core/src/main/java/org/apache/calcite/rex/RexExecutor.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.apache.calcite.rex;

import org.apache.calcite.rel.type.RelDataType;

import java.util.List;

/** Can reduce expressions, writing a literal for each into a list. */
Expand All @@ -32,4 +34,13 @@ public interface RexExecutor {
* @param reducedValues List to which reduced expressions are appended
*/
void reduce(RexBuilder rexBuilder, List<RexNode> constExps, List<RexNode> reducedValues);


/** Creates an {@link RexExecutable} that allows to execute rex expressions.
*
* @param rexBuilder Rex builder
* @param exps Expressions
* @param rowType describes the structure of the input row.
*/
RexExecutable getExecutable(RexBuilder rexBuilder, List<RexNode> exps, RelDataType rowType);
}
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,13 @@ private static String compile(RexBuilder rexBuilder, List<RexNode> constExps,
* @param exps Expressions
* @param rowType describes the structure of the input row.
*/
public static RexExecutable getExecutable(RexBuilder rexBuilder, List<RexNode> exps,
@Override public RexExecutable getExecutable(RexBuilder rexBuilder, List<RexNode> exps,
RelDataType rowType) {
final JavaTypeFactoryImpl typeFactory =
new JavaTypeFactoryImpl(rexBuilder.getTypeFactory().getTypeSystem());
final InputGetter getter = new DataContextInputGetter(rowType, typeFactory);
final String code = compile(rexBuilder, exps, getter, rowType);
return new RexExecutable(code, "generated Rex code");
return new RexExecutableImpl(code, "generated Rex code");
}

/**
Expand All @@ -134,7 +134,7 @@ public static RexExecutable getExecutable(RexBuilder rexBuilder, List<RexNode> e
throw new UnsupportedOperationException();
});

final RexExecutable executable = new RexExecutable(code, constExps);
final RexExecutable executable = new RexExecutableImpl(code, constExps);
executable.setDataContext(dataContext);
executable.reduce(rexBuilder, constExps, reducedValues);
}
Expand Down
Loading

0 comments on commit d508405

Please sign in to comment.