blob: 3d5dd51de302b36b9ae8d5b07859e90e11814c40 [file] [log] [blame]
/*
* 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.linq4j.test;
import org.apache.calcite.linq4j.function.Deterministic;
import org.apache.calcite.linq4j.function.NonDeterministic;
import org.apache.calcite.linq4j.tree.Blocks;
import org.apache.calcite.linq4j.tree.ClassDeclarationFinder;
import org.apache.calcite.linq4j.tree.DeterministicCodeOptimizer;
import org.apache.calcite.linq4j.tree.Expression;
import org.apache.calcite.linq4j.tree.Expressions;
import org.apache.calcite.linq4j.tree.Types;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.Collections;
import java.util.concurrent.Callable;
import static org.apache.calcite.linq4j.test.BlockBuilderBase.FOUR;
import static org.apache.calcite.linq4j.test.BlockBuilderBase.ONE;
import static org.apache.calcite.linq4j.test.BlockBuilderBase.THREE;
import static org.apache.calcite.linq4j.test.BlockBuilderBase.TWO;
import static org.apache.calcite.linq4j.test.BlockBuilderBase.optimize;
import static org.apache.calcite.linq4j.test.BlockBuilderBase.optimizeExpression;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Tests factoring out deterministic expressions.
*/
class DeterministicTest {
/** Class to test {@code @Deterministic} annotation. */
public static class TestClass {
@Deterministic
public static int deterministic(int a) {
return a + 1;
}
public static int nonDeterministic(int a) {
return a + 2;
}
}
/** Class to test {@code @NonDeterministic} annotation. */
@Deterministic
public static class TestDeterministicClass {
public static int deterministic(int a) {
return a + 1;
}
@NonDeterministic
public static int nonDeterministic(int a) {
return a + 2;
}
}
private boolean isAtomic(Expression e) {
/** Subclass to make a protected method public. */
class MyDeterministicCodeOptimizer extends DeterministicCodeOptimizer {
MyDeterministicCodeOptimizer() {
super(ClassDeclarationFinder.create());
}
@Override public boolean isConstant(Expression expression) {
return super.isConstant(expression);
}
}
return new MyDeterministicCodeOptimizer().isConstant(e);
}
private static Method getMethod(Class<?> thisClass, String methodName,
Class<?>... paramClasses) {
try {
return thisClass.getMethod(methodName, paramClasses);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
private boolean isConstant(Expression e) {
Expression e2 =
e.accept(
new DeterministicCodeOptimizer(ClassDeclarationFinder.create()));
return !e.equals(e2);
}
@Test void testConstantIsConstant() {
// Small expressions are atomic.
assertThat(isAtomic(Expressions.constant(0)), is(true));
assertThat(isAtomic(Expressions.constant("xxx")), is(true));
assertThat(isAtomic(Expressions.constant(null)), is(true));
Expression e =
Expressions.call(getMethod(Integer.class, "valueOf", int.class),
Expressions.constant(-100));
assertThat(isAtomic(e), is(false));
assertThat(isConstant(e), is(true));
e = Expressions.call(
Integer.class, "valueOf", Expressions.constant(0));
assertThat(isAtomic(e), is(false));
assertThat(isConstant(e), is(true));
e = Expressions.call(Expressions.constant("xxx"), "length");
assertThat(isAtomic(e), is(false));
assertThat(isConstant(e), is(true));
}
@Test void testFactorOutBinaryAdd() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(Expressions.add(ONE, TWO))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " };\n"
+ "}\n"));
}
@Test void testFactorOutBinaryAddSurvivesMultipleOptimizations() {
assertThat(
optimize(
optimizeExpression(
Expressions.new_(Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(Expressions.add(ONE, TWO)))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " };\n"
+ "}\n"));
}
@Test void testFactorOutBinaryAddNameCollision() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.multiply(Expressions.add(ONE, TWO),
Expressions.subtract(ONE, TWO)))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2_1_20;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " static final int $L4J$C$1_20 = 1 - 2;\n"
+ " static final int $L4J$C$1_2_1_20 = $L4J$C$1_2 * $L4J$C$1_20;\n"
+ " };\n"
+ "}\n"));
}
@Test void testFactorOutBinaryAddMul() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.multiply(Expressions.add(ONE, TWO),
THREE))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_2_3;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " static final int $L4J$C$1_2_3 = $L4J$C$1_2 * 3;\n"
+ " };\n"
+ "}\n"));
}
@Test void testFactorOutNestedClasses() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.add(
Expressions.add(ONE, FOUR),
Expressions.call(
Expressions.new_(
Callable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
Object.class,
"call",
Collections
.emptyList(),
Blocks.toFunctionBlock(
Expressions.multiply(
Expressions.add(ONE, TWO),
THREE)))),
"call",
Collections.emptyList())))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_4 + new java.util.concurrent.Callable(){\n"
+ " Object call() {\n"
+ " return $L4J$C$1_2_3;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_2 = 1 + 2;\n"
+ " static final int $L4J$C$1_2_3 = $L4J$C$1_2 * 3;\n"
+ " }.call();\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$1_4 = 1 + 4;\n"
+ " };\n"
+ "}\n"));
}
@Test void testNewBigInteger() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0, int.class, "test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.new_(BigInteger.class,
Expressions.constant("42")))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$new_java_math_BigInteger_42_;\n"
+ " }\n"
+ "\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$new_java_math_BigInteger_42_ = new java.math.BigInteger(\n"
+ " \"42\");\n"
+ " };\n"
+ "}\n"));
}
@Test void testInstanceofTest() {
// Single instanceof is not optimized
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0, int.class, "test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.typeIs(ONE, Boolean.class))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return 1 instanceof Boolean;\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n"));
}
@Test void testInstanceofComplexTest() {
// instanceof is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.orElse(
Expressions.typeIs(ONE, Boolean.class),
Expressions.typeIs(TWO, Integer.class)))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$1_instanceof_Boolean_2_instanceof_Integer;\n"
+ " }\n"
+ "\n"
+ " static final boolean "
+ "$L4J$C$1_instanceof_Boolean_2_instanceof_Integer = 1 instanceof "
+ "Boolean || 2 instanceof Integer;\n"
+ " };\n"
+ "}\n"));
}
@Test void testIntegerValueOfZeroComplexTest() {
// Integer.valueOf(0) is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
getMethod(Integer.class, "valueOf", int.class),
Expressions.constant(0)))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$Integer_valueOf_0_;\n"
+ " }\n"
+ "\n"
+ " static final Integer $L4J$C$Integer_valueOf_0_ = Integer.valueOf(0);\n"
+ " };\n"
+ "}\n"));
}
@Test void testStaticField() {
// instanceof is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
Expressions.field(null, BigInteger.class, "ONE"),
"add",
Expressions.call(null,
Types.lookupMethod(BigInteger.class, "valueOf",
long.class),
Expressions.constant(42L))))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return "
+ "$L4J$C$java_math_BigInteger_ONE_add_java_math_BigInteger_valueOf_42L_;\n"
+ " }\n"
+ "\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_ = java.math.BigInteger"
+ ".valueOf(42L);\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_ONE_add_java_math_BigInteger_valueOf_42L_ = java.math.BigInteger.ONE.add($L4J$C$java_math_BigInteger_valueOf_42L_);\n"
+ " };\n"
+ "}\n"));
}
@Test void testBigIntegerValueOf() {
// instanceof is optimized in complex expressions
assertThat(
optimize(
Expressions.new_(Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(0, int.class, "test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(
Expressions.call(null,
Types.lookupMethod(BigInteger.class, "valueOf",
long.class),
Expressions.constant(42L)),
"add",
Expressions.call(null,
Types.lookupMethod(BigInteger.class, "valueOf",
long.class),
Expressions.constant(42L))))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_add_java_math_BigInteger_valued8d57d69;\n"
+ " }\n"
+ "\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_ = java.math.BigInteger"
+ ".valueOf(42L);\n"
+ " static final java.math.BigInteger "
+ "$L4J$C$java_math_BigInteger_valueOf_42L_add_java_math_BigInteger_valued8d57d69 = $L4J$C$java_math_BigInteger_valueOf_42L_.add($L4J$C$java_math_BigInteger_valueOf_42L_);\n"
+ " };\n"
+ "}\n"));
}
@Test void testDeterministicMethodCall() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(null,
Types.lookupMethod(TestClass.class,
"deterministic", int.class),
ONE))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$org_apache_calcite_linq4j_test_DeterministicTest_TestClass_dete33e8af1c;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$org_apache_calcite_linq4j_test_DeterministicTest_TestClass_dete33e8af1c = org.apache.calcite.linq4j.test.DeterministicTest.TestClass.deterministic(1);\n"
+ " };\n"
+ "}\n"));
}
@Test void testNonDeterministicMethodCall() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(null,
Types.lookupMethod(TestClass.class,
"nonDeterministic", int.class),
ONE))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return org.apache.calcite.linq4j.test.DeterministicTest.TestClass.nonDeterministic(1);\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n"));
}
@Test void testDeterministicClassDefaultMethod() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(null,
Types.lookupMethod(TestDeterministicClass.class,
"deterministic", int.class),
ONE))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return $L4J$C$org_apache_calcite_linq4j_test_DeterministicTest_TestDeterminis9de610da;\n"
+ " }\n"
+ "\n"
+ " static final int $L4J$C$org_apache_calcite_linq4j_test_DeterministicTest_TestDeterminis9de610da = org.apache.calcite.linq4j.test.DeterministicTest.TestDeterministicClass.deterministic(1);\n"
+ " };\n"
+ "}\n"));
}
@Test void testDeterministicClassNonDeterministicMethod() {
assertThat(
optimize(
Expressions.new_(
Runnable.class,
Collections.emptyList(),
Expressions.methodDecl(
0,
int.class,
"test",
Collections.emptyList(),
Blocks.toFunctionBlock(
Expressions.call(null,
Types.lookupMethod(TestDeterministicClass.class,
"nonDeterministic", int.class),
ONE))))),
equalTo("{\n"
+ " return new Runnable(){\n"
+ " int test() {\n"
+ " return org.apache.calcite.linq4j.test.DeterministicTest.TestDeterministicClass.nonDeterministic(1);\n"
+ " }\n"
+ "\n"
+ " };\n"
+ "}\n"));
}
}