blob: 4e20d2ccc51de6f14411d0fc9b34b44e5e83fb02 [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.rex;
import org.apache.calcite.DataContext;
import org.apache.calcite.adapter.java.JavaTypeFactory;
import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.linq4j.QueryProvider;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptSchema;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.schema.SchemaPlus;
import org.apache.calcite.schema.Schemas;
import org.apache.calcite.server.CalciteServerStatement;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.fun.SqlMonotonicBinaryOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import org.junit.Assert;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.instanceOf;
import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Unit test for {@link org.apache.calcite.rex.RexExecutorImpl}.
*/
public class RexExecutorTest {
public RexExecutorTest() {
}
protected void check(final Action action) throws Exception {
Frameworks.withPrepare(
new Frameworks.PrepareAction<Void>() {
public Void apply(RelOptCluster cluster, RelOptSchema relOptSchema,
SchemaPlus rootSchema, CalciteServerStatement statement) {
final RexBuilder rexBuilder = cluster.getRexBuilder();
DataContext dataContext =
Schemas.createDataContext(statement.getConnection(), rootSchema);
final RexExecutorImpl executor = new RexExecutorImpl(dataContext);
action.check(rexBuilder, executor);
return null;
}
});
}
/** Tests an executor that uses variables stored in a {@link DataContext}.
* Can change the value of the variable and execute again. */
@Test public void testVariableExecution() throws Exception {
check((rexBuilder, executor) -> {
Object[] values = new Object[1];
final DataContext testContext = new TestDataContext(values);
final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
final RelDataType varchar =
typeFactory.createSqlType(SqlTypeName.VARCHAR);
final RelDataType integer =
typeFactory.createSqlType(SqlTypeName.INTEGER);
// Calcite is internally creating the input ref via a RexRangeRef
// which eventually leads to a RexInputRef. So we are good.
final RexInputRef input = rexBuilder.makeInputRef(varchar, 0);
final RexNode lengthArg = rexBuilder.makeLiteral(3, integer, true);
final RexNode substr =
rexBuilder.makeCall(SqlStdOperatorTable.SUBSTRING, input,
lengthArg);
ImmutableList<RexNode> constExps = ImmutableList.of(substr);
final RelDataType rowType = typeFactory.builder()
.add("someStr", varchar)
.build();
final RexExecutable exec = executor.getExecutable(rexBuilder,
constExps, rowType);
exec.setDataContext(testContext);
values[0] = "Hello World";
Object[] result = exec.execute();
assertTrue(result[0] instanceof String);
assertThat((String) result[0], equalTo("llo World"));
values[0] = "Calcite";
result = exec.execute();
assertTrue(result[0] instanceof String);
assertThat((String) result[0], equalTo("lcite"));
});
}
@Test public void testConstant() throws Exception {
check((rexBuilder, executor) -> {
final List<RexNode> reducedValues = new ArrayList<>();
final RexLiteral ten = rexBuilder.makeExactLiteral(BigDecimal.TEN);
executor.reduce(rexBuilder, ImmutableList.of(ten),
reducedValues);
assertThat(reducedValues.size(), equalTo(1));
assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(0)).getValue2(),
equalTo((Object) 10L));
});
}
/** Reduces several expressions to constants. */
@Test public void testConstant2() throws Exception {
// Same as testConstant; 10 -> 10
checkConstant(10L,
rexBuilder -> rexBuilder.makeExactLiteral(BigDecimal.TEN));
// 10 + 1 -> 11
checkConstant(11L,
rexBuilder -> rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
rexBuilder.makeExactLiteral(BigDecimal.TEN),
rexBuilder.makeExactLiteral(BigDecimal.ONE)));
// date 'today' <= date 'today' -> true
checkConstant(true, rexBuilder -> {
final DateString d =
DateString.fromCalendarFields(Util.calendar());
return rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN_OR_EQUAL,
rexBuilder.makeDateLiteral(d),
rexBuilder.makeDateLiteral(d));
});
// date 'today' < date 'today' -> false
checkConstant(false, rexBuilder -> {
final DateString d =
DateString.fromCalendarFields(Util.calendar());
return rexBuilder.makeCall(SqlStdOperatorTable.LESS_THAN,
rexBuilder.makeDateLiteral(d),
rexBuilder.makeDateLiteral(d));
});
}
private void checkConstant(final Object operand,
final Function<RexBuilder, RexNode> function) throws Exception {
check((rexBuilder, executor) -> {
final List<RexNode> reducedValues = new ArrayList<>();
final RexNode expression = function.apply(rexBuilder);
assert expression != null;
executor.reduce(rexBuilder, ImmutableList.of(expression),
reducedValues);
assertThat(reducedValues.size(), equalTo(1));
assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(0)).getValue2(),
equalTo(operand));
});
}
@Test public void testSubstring() throws Exception {
check((rexBuilder, executor) -> {
final List<RexNode> reducedValues = new ArrayList<>();
final RexLiteral hello =
rexBuilder.makeCharLiteral(
new NlsString("Hello world!", null, null));
final RexNode plus =
rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
rexBuilder.makeExactLiteral(BigDecimal.ONE),
rexBuilder.makeExactLiteral(BigDecimal.ONE));
RexLiteral four = rexBuilder.makeExactLiteral(BigDecimal.valueOf(4));
final RexNode substring =
rexBuilder.makeCall(SqlStdOperatorTable.SUBSTRING,
hello, plus, four);
executor.reduce(rexBuilder, ImmutableList.of(substring, plus),
reducedValues);
assertThat(reducedValues.size(), equalTo(2));
assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(0)).getValue2(),
equalTo((Object) "ello")); // substring('Hello world!, 2, 4)
assertThat(reducedValues.get(1), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(1)).getValue2(),
equalTo((Object) 2L));
});
}
@Test public void testBinarySubstring() throws Exception {
check((rexBuilder, executor) -> {
final List<RexNode> reducedValues = new ArrayList<>();
// hello world! -> 48656c6c6f20776f726c6421
final RexLiteral binaryHello =
rexBuilder.makeBinaryLiteral(
new ByteString("Hello world!".getBytes(UTF_8)));
final RexNode plus =
rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
rexBuilder.makeExactLiteral(BigDecimal.ONE),
rexBuilder.makeExactLiteral(BigDecimal.ONE));
RexLiteral four = rexBuilder.makeExactLiteral(BigDecimal.valueOf(4));
final RexNode substring =
rexBuilder.makeCall(SqlStdOperatorTable.SUBSTRING,
binaryHello, plus, four);
executor.reduce(rexBuilder, ImmutableList.of(substring, plus),
reducedValues);
assertThat(reducedValues.size(), equalTo(2));
assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(0)).getValue2().toString(),
equalTo((Object) "656c6c6f")); // substring('Hello world!, 2, 4)
assertThat(reducedValues.get(1), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(1)).getValue2(),
equalTo((Object) 2L));
});
}
@Test public void testDeterministic1() throws Exception {
check((rexBuilder, executor) -> {
final RexNode plus =
rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
rexBuilder.makeExactLiteral(BigDecimal.ONE),
rexBuilder.makeExactLiteral(BigDecimal.ONE));
assertThat(RexUtil.isDeterministic(plus), equalTo(true));
});
}
@Test public void testDeterministic2() throws Exception {
check((rexBuilder, executor) -> {
final RexNode plus =
rexBuilder.makeCall(PLUS_RANDOM,
rexBuilder.makeExactLiteral(BigDecimal.ONE),
rexBuilder.makeExactLiteral(BigDecimal.ONE));
assertThat(RexUtil.isDeterministic(plus), equalTo(false));
});
}
@Test public void testDeterministic3() throws Exception {
check((rexBuilder, executor) -> {
final RexNode plus =
rexBuilder.makeCall(SqlStdOperatorTable.PLUS,
rexBuilder.makeCall(PLUS_RANDOM,
rexBuilder.makeExactLiteral(BigDecimal.ONE),
rexBuilder.makeExactLiteral(BigDecimal.ONE)),
rexBuilder.makeExactLiteral(BigDecimal.ONE));
assertThat(RexUtil.isDeterministic(plus), equalTo(false));
});
}
private static final SqlBinaryOperator PLUS_RANDOM =
new SqlMonotonicBinaryOperator(
"+",
SqlKind.PLUS,
40,
true,
ReturnTypes.NULLABLE_SUM,
InferTypes.FIRST_KNOWN,
OperandTypes.PLUS_OPERATOR) {
@Override public boolean isDeterministic() {
return false;
}
};
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1009">[CALCITE-1009]
* SelfPopulatingList is not thread-safe</a>. */
@Test public void testSelfPopulatingList() {
final List<Thread> threads = new ArrayList<>();
//noinspection MismatchedQueryAndUpdateOfCollection
final List<String> list = new RexSlot.SelfPopulatingList("$", 1);
final Random random = new Random();
for (int i = 0; i < 10; i++) {
threads.add(
new Thread() {
public void run() {
for (int j = 0; j < 1000; j++) {
// Random numbers between 0 and ~1m, smaller values more common
final int index = random.nextInt(1234567)
>> random.nextInt(16) >> random.nextInt(16);
list.get(index);
}
}
});
}
for (Thread runnable : threads) {
runnable.start();
}
for (Thread runnable : threads) {
try {
runnable.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
final int size = list.size();
for (int i = 0; i < size; i++) {
assertThat(list.get(i), is("$" + i));
}
}
@Test public void testSelfPopulatingList30() {
//noinspection MismatchedQueryAndUpdateOfCollection
final List<String> list = new RexSlot.SelfPopulatingList("$", 30);
final String s = list.get(30);
assertThat(s, is("$30"));
}
/** Callback for {@link #check}. Test code will typically use {@code builder}
* to create some expressions, call
* {@link org.apache.calcite.rex.RexExecutorImpl#reduce} to evaluate them into
* a list, then check that the results are as expected. */
interface Action {
void check(RexBuilder rexBuilder, RexExecutorImpl executor);
}
/**
* ArrayList-based DataContext to check Rex execution.
*/
public static class TestDataContext implements DataContext {
private final Object[] values;
public TestDataContext(Object[] values) {
this.values = values;
}
public SchemaPlus getRootSchema() {
throw new RuntimeException("Unsupported");
}
public JavaTypeFactory getTypeFactory() {
throw new RuntimeException("Unsupported");
}
public QueryProvider getQueryProvider() {
throw new RuntimeException("Unsupported");
}
public Object get(String name) {
if (name.equals("inputRecord")) {
return values;
} else {
Assert.fail("Wrong DataContext access");
return null;
}
}
}
}
// End RexExecutorTest.java