Repository: calcite Updated Branches: refs/heads/master 37a6f9f4f -> d9486e6c3
[CALCITE-2504] RexProgrammFuzzyTest: fuzzer for RexNodes Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/d9486e6c Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/d9486e6c Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/d9486e6c Branch: refs/heads/master Commit: d9486e6c31d8c86a5fb3f703351958d633bc55fd Parents: 37a6f9f Author: Vladimir Sitnikov <[email protected]> Authored: Tue Aug 7 11:03:49 2018 +0300 Committer: Vladimir Sitnikov <[email protected]> Committed: Mon Sep 17 23:10:20 2018 +0300 ---------------------------------------------------------------------- .../org/apache/calcite/test/CalciteSuite.java | 2 + .../apache/calcite/test/fuzzer/RexFuzzer.java | 216 ++++++++++++ .../test/fuzzer/RexProgramFuzzyTest.java | 342 +++++++++++++++++++ .../test/fuzzer/RexToTestCodeShuttle.java | 148 ++++++++ .../calcite/test/fuzzer/SimplifyTask.java | 46 +++ 5 files changed, 754 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/CalciteSuite.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java index 4896411..2c16620 100644 --- a/core/src/test/java/org/apache/calcite/test/CalciteSuite.java +++ b/core/src/test/java/org/apache/calcite/test/CalciteSuite.java @@ -50,6 +50,7 @@ import org.apache.calcite.sql.type.SqlTypeUtilTest; import org.apache.calcite.sql.validate.LexCaseSensitiveTest; import org.apache.calcite.sql.validate.SqlValidatorUtilTest; import org.apache.calcite.test.enumerable.EnumerableCorrelateTest; +import org.apache.calcite.test.fuzzer.RexProgramFuzzyTest; import org.apache.calcite.tools.FrameworksTest; import org.apache.calcite.tools.PlannerTest; import org.apache.calcite.util.BitSetsTest; @@ -163,6 +164,7 @@ import org.junit.runners.Suite; LinqFrontJdbcBackTest.class, JdbcFrontJdbcBackLinqMiddleTest.class, CalciteSqlOperatorTest.class, + RexProgramFuzzyTest.class, ProfilerTest.class, LatticeTest.class, ReflectiveSchemaTest.class, http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java b/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java new file mode 100644 index 0000000..7851db2 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/fuzzer/RexFuzzer.java @@ -0,0 +1,216 @@ +/* + * 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.test.fuzzer; + +import org.apache.calcite.adapter.java.JavaTypeFactory; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexBuilder; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.test.RexProgramBuilderBase; + +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.function.Function; + +/** + * Generates random {@link RexNode} instances for tests. + */ +public class RexFuzzer extends RexProgramBuilderBase { + private static final int MAX_VARS = 2; + + private static final SqlOperator[] BOOL_TO_BOOL = { + SqlStdOperatorTable.NOT, + SqlStdOperatorTable.IS_TRUE, + SqlStdOperatorTable.IS_FALSE, + SqlStdOperatorTable.IS_NOT_TRUE, + SqlStdOperatorTable.IS_NOT_FALSE, + }; + + private static final SqlOperator[] ANY_TO_BOOL = { + SqlStdOperatorTable.IS_NULL, + SqlStdOperatorTable.IS_NOT_NULL, + SqlStdOperatorTable.IS_UNKNOWN, + SqlStdOperatorTable.IS_NOT_UNKNOWN, + }; + + private static final SqlOperator[] COMPARABLE_TO_BOOL = { + SqlStdOperatorTable.EQUALS, + SqlStdOperatorTable.NOT_EQUALS, + SqlStdOperatorTable.GREATER_THAN, + SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, + SqlStdOperatorTable.LESS_THAN, + SqlStdOperatorTable.LESS_THAN_OR_EQUAL, + SqlStdOperatorTable.IS_DISTINCT_FROM, + SqlStdOperatorTable.IS_NOT_DISTINCT_FROM, + }; + + private static final SqlOperator[] BOOL_TO_BOOL_MULTI_ARG = { + SqlStdOperatorTable.OR, + SqlStdOperatorTable.AND, + SqlStdOperatorTable.COALESCE, + }; + + private static final SqlOperator[] ANY_SAME_TYPE_MULTI_ARG = { + SqlStdOperatorTable.COALESCE, + }; + + private static final SqlOperator[] NUMERIC_TO_NUMERIC = { + SqlStdOperatorTable.PLUS, + SqlStdOperatorTable.MINUS, + SqlStdOperatorTable.MULTIPLY, + // Divide by zero is not allowed, so we do not generate divide +// SqlStdOperatorTable.DIVIDE, +// SqlStdOperatorTable.DIVIDE_INTEGER, + }; + + private static final SqlOperator[] UNARY_NUMERIC = { + SqlStdOperatorTable.UNARY_MINUS, + SqlStdOperatorTable.UNARY_PLUS, + }; + + + private static final int[] INT_VALUES = {-1, 0, 1, 100500}; + + private final RelDataType intType; + private final RelDataType nullableIntType; + + /** + * Generates randomized {@link RexNode}. + * + * @param rexBuilder builder to be used to create nodes + * @param typeFactory type factory + */ + public RexFuzzer(RexBuilder rexBuilder, JavaTypeFactory typeFactory) { + setUp(); + this.rexBuilder = rexBuilder; + this.typeFactory = typeFactory; + + intType = typeFactory.createSqlType(SqlTypeName.INTEGER); + nullableIntType = typeFactory.createTypeWithNullability(intType, true); + } + + private int getInputRefId(int base, boolean nullable) { + return base + (nullable ? 100 : 0); + } + + public RexNode getExpression(Random r, int depth) { + return getComparableExpression(r, depth); + } + + private RexNode fuzzOperator(Random r, SqlOperator[] operators, RexNode... args) { + return rexBuilder.makeCall(operators[r.nextInt(operators.length)], args); + } + + private RexNode fuzzOperator(Random r, SqlOperator[] operators, int length, + Function<Random, RexNode> factory) { + List<RexNode> args = new ArrayList<>(length); + for (int i = 0; i < length; i++) { + args.add(factory.apply(r)); + } + return rexBuilder.makeCall(operators[r.nextInt(operators.length)], args); + } + + public RexNode getComparableExpression(Random r, int depth) { + int v = r.nextInt(2); + switch (v) { + case 0: + return getBoolExpression(r, depth); + case 1: + return getIntExpression(r, depth); + } + throw new AssertionError("should not reach here"); + } + + public RexNode getSimpleBool(Random r) { + int v = r.nextInt(2); + switch (v) { + case 0: + boolean nullable = r.nextBoolean(); + int field = r.nextInt(MAX_VARS); + return nullable ? vBool(field) : vBoolNotNull(field); + case 1: + return r.nextBoolean() ? trueLiteral : falseLiteral; + case 2: + return nullBool; + } + throw new AssertionError("should not reach here"); + } + + public RexNode getBoolExpression(Random r, int depth) { + int v = depth <= 0 ? 0 : r.nextInt(6); + switch (v) { + case 0: + return getSimpleBool(r); + case 1: + return fuzzOperator(r, ANY_TO_BOOL, getExpression(r, depth - 1)); + case 2: + return fuzzOperator(r, BOOL_TO_BOOL, getBoolExpression(r, depth - 1)); + case 3: + return fuzzOperator(r, COMPARABLE_TO_BOOL, getBoolExpression(r, depth - 1), + getBoolExpression(r, depth - 1)); + case 4: + return fuzzOperator(r, COMPARABLE_TO_BOOL, getIntExpression(r, depth - 1), + getIntExpression(r, depth - 1)); + case 5: + return fuzzOperator(r, BOOL_TO_BOOL_MULTI_ARG, r.nextInt(3) + 2, + x -> getBoolExpression(x, depth - 1)); + } + throw new AssertionError("should not reach here"); + } + + public RexNode getSimpleInt(Random r) { + int v = r.nextInt(3); + switch (v) { + case 0: + boolean nullable = r.nextBoolean(); + int field = r.nextInt(MAX_VARS); + return nullable ? vInt(field) : vIntNotNull(field); + case 1: { + int i = r.nextInt(INT_VALUES.length + 1); + int val = i < INT_VALUES.length ? INT_VALUES[i] : r.nextInt(); + return rexBuilder.makeLiteral(val, r.nextBoolean() ? intType : nullableIntType, false); + } + case 2: + return nullInt; + } + throw new AssertionError("should not reach here"); + } + + public RexNode getIntExpression(Random r, int depth) { + int v = depth <= 0 ? 0 : r.nextInt(4); + switch (v) { + case 0: + return getSimpleInt(r); + case 1: + return fuzzOperator(r, UNARY_NUMERIC, getIntExpression(r, depth - 1)); + case 2: + return fuzzOperator(r, NUMERIC_TO_NUMERIC, getIntExpression(r, depth - 1), + getIntExpression(r, depth - 1)); + case 3: + return fuzzOperator(r, ANY_SAME_TYPE_MULTI_ARG, r.nextInt(3) + 2, + x -> getIntExpression(x, depth - 1)); + } + throw new AssertionError("should not reach here"); + } + +} + +// End RexFuzzer.java http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java b/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java new file mode 100644 index 0000000..159e88d --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/fuzzer/RexProgramFuzzyTest.java @@ -0,0 +1,342 @@ +/* + * 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.test.fuzzer; + +import org.apache.calcite.plan.Strong; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexUtil; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.test.RexProgramBuilderBase; +import org.apache.calcite.util.ImmutableBitSet; + +import org.junit.Before; +import org.junit.Ignore; +import org.junit.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.PriorityQueue; +import java.util.Random; +import java.util.Set; +import java.util.concurrent.TimeUnit; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +/** + * Validates that {@link org.apache.calcite.rex.RexSimplify} is able to deal with + * randomized {@link RexNode}. + * Note: the default fuzzing time is 5 seconds to keep overall test duration reasonable. + * The test starts from a random point every time, so the longer it runs the more errors it detects. + * + * <p>Note: The test is not included to {@link org.apache.calcite.test.CalciteSuite} since it would + * fail every build (there are lots of issues with {@link org.apache.calcite.rex.RexSimplify}) + */ +public class RexProgramFuzzyTest extends RexProgramBuilderBase { + protected static final Logger LOGGER = + LoggerFactory.getLogger(RexProgramFuzzyTest.class); + + private static final int TEST_DURATION = Integer.getInteger("rex.fuzzing.duration", 5); + private static final long TEST_ITERATIONS = Long.getLong("rex.fuzzing.iterations", 5); + // Stop fuzzing after detecting MAX_FAILURES errors + private static final int MAX_FAILURES = + Integer.getInteger("rex.fuzzing.max.failures", 1); + // Number of slowest to simplify expressions to show + private static final int TOPN_SLOWEST = + Integer.getInteger("rex.fuzzing.max.slowest", 0); + // 0 means use random seed + // 42 is used to make sure tests pass in CI + private static final long SEED = + Long.getLong("rex.fuzzing.seed", 43); + + private PriorityQueue<SimplifyTask> slowestTasks; + + private long currentSeed = 0; + + private static final Strong STRONG = Strong.of(ImmutableBitSet.of()); + + /** + * A bounded variation of {@link PriorityQueue} + * + * @param <E> the type of elements held in this collection + */ + private static class TopN<E extends Comparable<E>> extends PriorityQueue<E> { + private final int n; + + private TopN(int n) { + this.n = n; + } + + @Override public boolean offer(E o) { + if (size() == n) { + E peek = peek(); + if (peek != null && peek.compareTo(o) > 0) { + // If the smallest element in the queue exceeds the added one + // then just ignore the offer + return false; + } + // otherwise extract the smallest element, and offer a new one + poll(); + } + return super.offer(o); + } + + @Override public Iterator<E> iterator() { + throw new UnsupportedOperationException("Order of elements is not defined, please use .peek"); + } + } + + @Before public void setUp() { + super.setUp(); + } + + /** + * Verifies {@code IS TRUE(IS NULL(null))} kind of expressions up to 4 level deep. + */ + @Ignore("[CALCITE-2556] RexSimplify: not(trueLiteral) could be simplified to false") + @Test public void testNestedCalls() { + nestedCalls(trueLiteral); + nestedCalls(falseLiteral); + nestedCalls(nullBool); + nestedCalls(vBool()); + nestedCalls(vBoolNotNull()); + } + + private void nestedCalls(RexNode arg) { + SqlOperator[] operators = { + SqlStdOperatorTable.NOT, + SqlStdOperatorTable.IS_FALSE, + SqlStdOperatorTable.IS_NOT_FALSE, + SqlStdOperatorTable.IS_TRUE, + SqlStdOperatorTable.IS_NOT_TRUE, + SqlStdOperatorTable.IS_NULL, + SqlStdOperatorTable.IS_NOT_NULL, + SqlStdOperatorTable.IS_UNKNOWN, + SqlStdOperatorTable.IS_NOT_UNKNOWN + }; + for (SqlOperator op1 : operators) { + RexNode n1 = rexBuilder.makeCall(op1, arg); + checkTrueFalse(n1); + for (SqlOperator op2 : operators) { + RexNode n2 = rexBuilder.makeCall(op2, n1); + checkTrueFalse(n2); + for (SqlOperator op3 : operators) { + RexNode n3 = rexBuilder.makeCall(op3, n2); + checkTrueFalse(n3); + for (SqlOperator op4 : operators) { + RexNode n4 = rexBuilder.makeCall(op4, n3); + checkTrueFalse(n4); + } + } + } + } + } + + + private void checkTrueFalse(RexNode node) { + checkTrueFalse(node, true); + checkTrueFalse(node, false); + } + + private void checkTrueFalse(RexNode node, boolean unknownAsFalse) { + RexNode opt; + String uaf = unknownAsFalse ? "unknownAsFalse " : ""; + try { + long start = System.nanoTime(); + opt = this.simplify.withUnknownAsFalse(unknownAsFalse).simplify(node); + long end = System.nanoTime(); + if (end - start > 1000 && slowestTasks != null) { + slowestTasks.add(new SimplifyTask(node, currentSeed, opt, end - start)); + } + } catch (AssertionError a) { + String message = a.getMessage(); + if (message != null && message.startsWith("result mismatch")) { + throw a; + } + throw new IllegalStateException("Unable to simplify " + uaf + nodeToString(node), a); + } catch (Throwable t) { + throw new IllegalStateException("Unable to simplify " + uaf + nodeToString(node), t); + } + if (trueLiteral.equals(opt) && node.isAlwaysFalse()) { + String msg = nodeToString(node); + fail(msg + " optimizes to TRUE, isAlwaysFalse MUST not be true " + uaf); +// This is a missing optimization, not a bug +// assertFalse(msg + " optimizes to TRUE, isAlwaysTrue MUST be true", +// !node.isAlwaysTrue()); + } + if (falseLiteral.equals(opt) && node.isAlwaysTrue()) { + String msg = nodeToString(node); + fail(msg + " optimizes to FALSE, isAlwaysTrue MUST not be true " + uaf); +// This is a missing optimization, not a bug +// assertFalse(msg + " optimizes to FALSE, isAlwaysFalse MUST be true", +// !node.isAlwaysFalse()); + } + if (STRONG.isNull(opt)) { + if (node.isAlwaysTrue()) { + fail(nodeToString(node) + " optimizes to NULL: " + nodeToString(opt) + + ", isAlwaysTrue MUST be FALSE " + uaf); + } + if (node.isAlwaysFalse()) { + fail(nodeToString(node) + " optimizes to NULL: " + nodeToString(opt) + + ", isAlwaysFalse MUST be FALSE " + uaf); + } + } + if (node.isAlwaysTrue()) { + if (!trueLiteral.equals(opt)) { + assertEquals(nodeToString(node) + " isAlwaysTrue, so it should simplify to TRUE " + + uaf, + trueLiteral, opt); + } + } + if (node.isAlwaysFalse()) { + if (!falseLiteral.equals(opt)) { + assertEquals(nodeToString(node) + " isAlwaysFalse, so it should simplify to FALSE " + + uaf, + falseLiteral, opt); + } + } + if (STRONG.isNull(node)) { + if (unknownAsFalse) { + if (!falseLiteral.equals(opt)) { + assertEquals(nodeToString(node) + + " is always null, so it should simplify to FALSE " + uaf, + falseLiteral, opt); + } + } else { + if (!RexUtil.isNull(opt)) { + assertEquals(nodeToString(node) + + " is always null, so it should simplify to NULL " + uaf, + nullBool, opt); + } + } + } + } + + private static String nodeToString(RexNode node) { + return node + "\n" + + node.accept(new RexToTestCodeShuttle()); + } + + @Test public void testFuzzy() { + if (TEST_DURATION == 0) { + return; + } + slowestTasks = new TopN<>(TOPN_SLOWEST); + Random r = new Random(); + if (SEED != 0) { + LOGGER.info("Using seed {} for rex fuzzing", SEED); + r.setSeed(SEED); + } + long start = System.currentTimeMillis(); + long deadline = start + TimeUnit.SECONDS.toMillis(TEST_DURATION); + List<Throwable> exceptions = new ArrayList<>(); + Set<String> duplicates = new HashSet<>(); + long total = 0; + int dup = 0; + int fail = 0; + RexFuzzer fuzzer = new RexFuzzer(rexBuilder, typeFactory); + while (System.currentTimeMillis() < deadline && exceptions.size() < MAX_FAILURES + && (TEST_ITERATIONS == 0 || total < TEST_ITERATIONS)) { + long seed = r.nextLong(); + this.currentSeed = seed; + r.setSeed(seed); + try { + total++; + generateRexAndCheckTrueFalse(fuzzer, r); + } catch (Throwable e) { + if (!duplicates.add(e.getMessage())) { + dup++; + // known exception, nothing to see here + continue; + } + fail++; + StackTraceElement[] stackTrace = e.getStackTrace(); + for (int j = 0; j < stackTrace.length; j++) { + if (stackTrace[j].getClassName().endsWith("RexProgramTest")) { + e.setStackTrace(Arrays.copyOf(stackTrace, j + 1)); + break; + } + } + e.addSuppressed(new Throwable("seed " + seed) { + @Override public synchronized Throwable fillInStackTrace() { + return this; + } + }); + exceptions.add(e); + } + } + long rate = total * 1000 / (System.currentTimeMillis() - start); + LOGGER.info( + "Rex fuzzing results: number of cases tested={}, failed cases={}, duplicate failures={}, fuzz rate={} per second", + total, fail, dup, rate); + + if (TOPN_SLOWEST > 0) { + LOGGER.info("The 5 slowest to simplify nodes were"); + SimplifyTask task; + RexToTestCodeShuttle v = new RexToTestCodeShuttle(); + while ((task = slowestTasks.poll()) != null) { + LOGGER.info(task.duration / 1000 + " us (" + task.seed + ")"); + LOGGER.info(" " + task.node.toString()); + LOGGER.info(" " + task.node.accept(v)); + LOGGER.info(" =>" + task.result.toString()); + } + } + + if (exceptions.isEmpty()) { + return; + } + + // Print the shortest fails first + exceptions.sort( + Comparator. + <Throwable>comparingInt(t -> t.getMessage() == null ? -1 : t.getMessage().length()) + .thenComparing(Throwable::getMessage)); + + for (int i = 0; i < exceptions.size() && i < 100; i++) { + Throwable exception = exceptions.get(i); + exception.printStackTrace(); + } + + Throwable ex = exceptions.get(0); + if (ex instanceof Error) { + throw (Error) ex; + } + throw new RuntimeException("Exception in testFuzzy", ex); + } + + private void generateRexAndCheckTrueFalse(RexFuzzer fuzzer, Random r) { + RexNode expression = fuzzer.getExpression(r, r.nextInt(10)); + checkTrueFalse(expression); + } + + @Ignore("This is just a scaffold for quick investigation of a single fuzz test") + @Test public void singleFuzzyTest() { + Random r = new Random(); + r.setSeed(-179916965778405462L); + RexFuzzer fuzzer = new RexFuzzer(rexBuilder, typeFactory); + generateRexAndCheckTrueFalse(fuzzer, r); + } +} + +// End RexProgramFuzzyTest.java http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java b/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java new file mode 100644 index 0000000..ad60eae --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/fuzzer/RexToTestCodeShuttle.java @@ -0,0 +1,148 @@ +/* + * 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.test.fuzzer; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rex.RexCall; +import org.apache.calcite.rex.RexFieldAccess; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexVisitorImpl; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.fun.SqlStdOperatorTable; +import org.apache.calcite.sql.type.SqlTypeName; + +import com.google.common.collect.ImmutableMap; + +import java.util.List; +import java.util.Map; + +/** + * Converts {@link RexNode} into a string form usable for inclusion into + * {@link RexProgramFuzzyTest}. + * For instance, it converts {@code AND(=(?0.bool0, true), =(?0.bool1, true))} to + * {@code isTrue(and(eq(vBool(0), trueLiteral), eq(vBool(1), trueLiteral)))}. + */ +public class RexToTestCodeShuttle extends RexVisitorImpl<String> { + private static final Map<SqlOperator, String> OP_METHODS = + ImmutableMap.<SqlOperator, String>builder() + .put(SqlStdOperatorTable.AND, "and") + .put(SqlStdOperatorTable.OR, "or") + .put(SqlStdOperatorTable.COALESCE, "coalesce") + .put(SqlStdOperatorTable.IS_NULL, "isNull") + .put(SqlStdOperatorTable.IS_NOT_NULL, "isNotNull") + .put(SqlStdOperatorTable.IS_UNKNOWN, "isUnknown") + .put(SqlStdOperatorTable.IS_TRUE, "isTrue") + .put(SqlStdOperatorTable.IS_NOT_TRUE, "isNotTrue") + .put(SqlStdOperatorTable.IS_FALSE, "isFalse") + .put(SqlStdOperatorTable.IS_NOT_FALSE, "isNotFalse") + .put(SqlStdOperatorTable.IS_DISTINCT_FROM, "isDistinctFrom") + .put(SqlStdOperatorTable.IS_NOT_DISTINCT_FROM, "isNotDistinctFrom") + .put(SqlStdOperatorTable.NULLIF, "nullIf") + .put(SqlStdOperatorTable.NOT, "not") + .put(SqlStdOperatorTable.GREATER_THAN, "gt") + .put(SqlStdOperatorTable.GREATER_THAN_OR_EQUAL, "ge") + .put(SqlStdOperatorTable.LESS_THAN, "lt") + .put(SqlStdOperatorTable.LESS_THAN_OR_EQUAL, "le") + .put(SqlStdOperatorTable.EQUALS, "eq") + .put(SqlStdOperatorTable.NOT_EQUALS, "ne") + .put(SqlStdOperatorTable.PLUS, "plus") + .put(SqlStdOperatorTable.UNARY_PLUS, "unaryPlus") + .put(SqlStdOperatorTable.MINUS, "sub") + .put(SqlStdOperatorTable.UNARY_MINUS, "unaryMinus") + .build(); + + + protected RexToTestCodeShuttle() { + super(true); + } + + @Override public String visitCall(RexCall call) { + SqlOperator operator = call.getOperator(); + String method = OP_METHODS.get(operator); + + StringBuilder sb = new StringBuilder(); + if (method != null) { + sb.append(method); + sb.append('('); + } else { + sb.append("rexBuilder.makeCall("); + sb.append("SqlStdOperatorTable."); + sb.append(operator.getName().replace(' ', '_')); + sb.append(", "); + } + List<RexNode> operands = call.getOperands(); + for (int i = 0; i < operands.size(); i++) { + RexNode operand = operands.get(i); + if (i > 0) { + sb.append(", "); + } + sb.append(operand.accept(this)); + } + sb.append(')'); + return sb.toString(); + } + + @Override public String visitLiteral(RexLiteral literal) { + RelDataType type = literal.getType(); + + if (type.getSqlTypeName() == SqlTypeName.BOOLEAN) { + if (literal.isNull()) { + return "nullBool"; + } + return literal.toString() + "Literal"; + } + if (type.getSqlTypeName() == SqlTypeName.INTEGER) { + if (literal.isNull()) { + return "nullInt"; + } + return "literal(" + literal.getValue() + ")"; + } + if (type.getSqlTypeName() == SqlTypeName.VARCHAR) { + if (literal.isNull()) { + return "nullVarchar"; + } + } + return "/*" + literal.getTypeName().getName() + "*/" + literal.toString(); + } + + @Override public String visitFieldAccess(RexFieldAccess fieldAccess) { + StringBuilder sb = new StringBuilder(); + sb.append("v"); + RelDataType type = fieldAccess.getType(); + switch (type.getSqlTypeName()) { + case BOOLEAN: + sb.append("Bool"); + break; + case INTEGER: + sb.append("Int"); + break; + case VARCHAR: + sb.append("Varchar"); + break; + } + if (!type.isNullable()) { + sb.append("NotNull"); + } + sb.append("("); + sb.append(fieldAccess.getField().getIndex() % 10); + sb.append(")"); + return sb.toString(); + } +} + +// End RexToTestCodeShuttle.java http://git-wip-us.apache.org/repos/asf/calcite/blob/d9486e6c/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java b/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java new file mode 100644 index 0000000..ed3ae89 --- /dev/null +++ b/core/src/test/java/org/apache/calcite/test/fuzzer/SimplifyTask.java @@ -0,0 +1,46 @@ +/* + * 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.test.fuzzer; + +import org.apache.calcite.rex.RexNode; + +/** + * Tracks rex nodes used in {@link RexProgramFuzzyTest} to identify the ones + * which take most time to simplify. + */ +class SimplifyTask implements Comparable<SimplifyTask> { + public final RexNode node; + public final long seed; + public final RexNode result; + public final long duration; + + SimplifyTask(RexNode node, long seed, RexNode result, long duration) { + this.node = node; + this.seed = seed; + this.result = result; + this.duration = duration; + } + + @Override public int compareTo(SimplifyTask o) { + if (duration != o.duration) { + return Long.compare(duration, o.duration); + } + return Integer.compare(node.toString().length(), o.node.toString().length()); + } +} + +// End SimplifyTask.java
