blob: b4631eee0bbd91af4ecfed40d67df23a15c15759 [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.DataContexts;
import org.apache.calcite.avatica.util.ByteString;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
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.test.Matchers;
import org.apache.calcite.tools.Frameworks;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.NlsString;
import org.apache.calcite.util.TestUtil;
import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import org.hamcrest.Matcher;
import org.junit.jupiter.api.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.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Unit test for {@link org.apache.calcite.rex.RexExecutorImpl}.
*/
class RexExecutorTest {
protected void check(final Action action) {
Frameworks.withPrepare((cluster, relOptSchema, rootSchema, statement) -> {
final RexBuilder rexBuilder = cluster.getRexBuilder();
DataContext dataContext =
DataContexts.of(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 void testVariableExecution() {
check((rexBuilder, executor) -> {
Object[] values = new Object[1];
final DataContext testContext =
DataContexts.of(name ->
name.equals("inputRecord") ? values : fail("unknown: " + name));
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 void testConstant() {
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, hasSize(1));
assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(0)).getValue2(),
equalTo((Object) 10L));
});
}
/** Reduces several expressions to constants. */
@Test void testConstant2() {
// 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) {
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, hasSize(1));
final RexNode reducedValue = reducedValues.get(0);
assertThat(reducedValue, instanceOf(RexLiteral.class));
final Matcher<Object> matcher;
if (((RexLiteral) reducedValue).getTypeName() == SqlTypeName.TIMESTAMP) {
final long current = System.currentTimeMillis();
//noinspection unchecked
matcher = (Matcher) Matchers.between((long) operand, current);
} else {
matcher = equalTo(operand);
}
assertThat(((RexLiteral) reducedValue).getValue2(), matcher);
});
}
@Test void testUserFromContext() {
testContextLiteral(SqlStdOperatorTable.USER,
DataContext.Variable.USER, "happyCalciteUser");
}
@Test void testSystemUserFromContext() {
testContextLiteral(SqlStdOperatorTable.SYSTEM_USER,
DataContext.Variable.SYSTEM_USER, "");
}
@Test void testTimestampFromContext() {
// CURRENT_TIMESTAMP actually rounds the value to nearest second
// and that's why we do currentTimeInMillis / 1000 * 1000
long val = System.currentTimeMillis() / 1000 * 1000;
testContextLiteral(SqlStdOperatorTable.CURRENT_TIMESTAMP,
DataContext.Variable.CURRENT_TIMESTAMP, val);
}
/**
* Ensures that for a given context operator,
* the correct value is retrieved from the {@link DataContext}.
*
* @param operator The Operator to check
* @param variable The DataContext variable this operator should be bound to
* @param value The expected value to retrieve.
*/
private void testContextLiteral(
final SqlOperator operator,
final DataContext.Variable variable,
final Object value) {
Frameworks.withPrepare((cluster, relOptSchema, rootSchema, statement) -> {
final RexBuilder rexBuilder = cluster.getRexBuilder();
final RexExecutorImpl executor =
new RexExecutorImpl(
DataContexts.of(name ->
name.equals(variable.camelName) ? value
: fail("unknown: " + name)));
try {
checkConstant(value, builder -> {
final List<RexNode> output = new ArrayList<>();
executor.reduce(rexBuilder,
ImmutableList.of(rexBuilder.makeCall(operator)), output);
return output.get(0);
});
} catch (Exception e) {
throw TestUtil.rethrow(e);
}
return null;
});
}
@Test void testSubstring() {
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, hasSize(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 void testBinarySubstring() {
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, hasSize(2));
assertThat(reducedValues.get(0), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(0)).getValue2(),
hasToString("656c6c6f")); // substring('Hello world!, 2, 4)
assertThat(reducedValues.get(1), instanceOf(RexLiteral.class));
assertThat(((RexLiteral) reducedValues.get(1)).getValue2(),
equalTo(2L));
});
}
@Test void testDeterministic1() {
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 void testDeterministic2() {
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 void testDeterministic3() {
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 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 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);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5949">[CALCITE-5949]
* RexExecutable should return unchanged original expressions when it fails</a>.
*/
@Test void testInvalidExpressionInList() {
check((rexBuilder, executor) -> {
final List<RexNode> reducedValues = new ArrayList<>();
final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
final RelDataType integer =
typeFactory.createSqlType(SqlTypeName.INTEGER);
final RexCall first =
(RexCall) rexBuilder.makeCall(SqlStdOperatorTable.LN,
rexBuilder.makeLiteral(3, integer, true));
final RexCall second =
(RexCall) rexBuilder.makeCall(SqlStdOperatorTable.LN,
rexBuilder.makeLiteral(-2, integer, true));
executor.reduce(rexBuilder, ImmutableList.of(first, second),
reducedValues);
assertThat(reducedValues, hasSize(2));
assertThat(reducedValues.get(0), instanceOf(RexCall.class));
assertThat(reducedValues.get(1), instanceOf(RexCall.class));
});
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-6168">[CALCITE-6168]
* RexExecutor can throw during compilation</a>. */
@Test void testCompileTimeException() {
check((rexBuilder, executor) -> {
final List<RexNode> reducedValues = new ArrayList<>();
final RelDataTypeFactory typeFactory = rexBuilder.getTypeFactory();
// CAST(200 as TINYINT)
final RelDataType tinyint =
typeFactory.createSqlType(SqlTypeName.TINYINT);
final RelDataType integer =
typeFactory.createSqlType(SqlTypeName.INTEGER);
final RexNode cast =
rexBuilder.makeCast(tinyint,
rexBuilder.makeLiteral(200, integer, true));
executor.reduce(rexBuilder, ImmutableList.of(cast),
reducedValues);
assertThat(reducedValues, hasSize(1));
assertThat(reducedValues.get(0), instanceOf(RexCall.class));
});
}
}