All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.calcite.sql.test.SqlOperatorFixture Maven / Gradle / Ivy

The newest version!
/*
 * 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.sql.test;

import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.config.CalciteConnectionProperty;
import org.apache.calcite.config.Lex;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.fun.SqlLibrary;
import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.parser.StringAndPos;
import org.apache.calcite.sql.test.SqlTester.ResultChecker;
import org.apache.calcite.sql.test.SqlTester.TypeChecker;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.test.CalciteAssert;
import org.apache.calcite.test.ConnectionFactories;
import org.apache.calcite.test.ConnectionFactory;
import org.apache.calcite.test.Matchers;
import org.apache.calcite.util.Bug;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.UnaryOperator;

import static org.apache.calcite.rel.type.RelDataTypeImpl.NON_NULLABLE_SUFFIX;
import static org.apache.calcite.sql.test.ResultCheckers.isSingle;

/**
 * A fixture for testing the SQL operators.
 *
 * 

It provides a fluent API so that you can write tests by chaining method * calls. * *

It is immutable. If you have two test cases that require a similar set up * (for example, the same SQL expression and parser configuration), it is safe * to use the same fixture object as a starting point for both tests. * *

The idea is that when you define an operator (or another piece of SQL * functionality), you can define the logical behavior of that operator once, as * part of that operator. Later you can define one or more physical * implementations of that operator, and test them all using the same set of * tests. * *

Depending on the implementation of {@link SqlTester} used * (see {@link #withTester(UnaryOperator)}), the fixture may or may not * evaluate expressions and check their results. */ public interface SqlOperatorFixture extends AutoCloseable { //~ Enums ------------------------------------------------------------------ // TODO: Change message String INVALID_CHAR_MESSAGE = "(?s).*"; String OUT_OF_RANGE_MESSAGE = ".* out of range.*"; String WRONG_FORMAT_MESSAGE = "Number has wrong format.*"; // TODO: Change message String DIVISION_BY_ZERO_MESSAGE = "(?s).*"; // TODO: Change message String STRING_TRUNC_MESSAGE = "(?s).*"; // TODO: Change message String BAD_DATETIME_MESSAGE = "(?s).*"; String LITERAL_OUT_OF_RANGE_MESSAGE = "(?s).*Numeric literal.*out of range.*"; String INVALID_ARGUMENTS_NUMBER = "Invalid number of arguments to function .* Was expecting .* arguments"; String INVALID_ARGUMENTS_TYPE_VALIDATION_ERROR = "Cannot apply '.*' to arguments of type .*"; //~ Enums ------------------------------------------------------------------ /** * Name of a virtual machine that can potentially implement an operator. */ enum VmName { JAVA, EXPAND } //~ Methods ---------------------------------------------------------------- /** Returns the test factory. */ SqlTestFactory getFactory(); /** Creates a copy of this fixture with a new test factory. */ SqlOperatorFixture withFactory(UnaryOperator transform); /** Returns the tester. */ SqlTester getTester(); /** Creates a copy of this fixture with a new tester. */ SqlOperatorFixture withTester(UnaryOperator transform); /** Creates a copy of this fixture with a new parser configuration. */ default SqlOperatorFixture withParserConfig( UnaryOperator transform) { return withFactory(f -> f.withParserConfig(transform)); } /** Returns a fixture that tests a given SQL quoting style. */ default SqlOperatorFixture withQuoting(Quoting quoting) { return withParserConfig(c -> c.withQuoting(quoting)); } /** Returns a fixture that applies a given casing policy to quoted * identifiers. */ default SqlOperatorFixture withQuotedCasing(Casing casing) { return withParserConfig(c -> c.withQuotedCasing(casing)); } /** Returns a fixture that applies a given casing policy to unquoted * identifiers. */ default SqlOperatorFixture withUnquotedCasing(Casing casing) { return withParserConfig(c -> c.withUnquotedCasing(casing)); } /** Returns a fixture that matches identifiers by case-sensitive or * case-insensitive. */ default SqlOperatorFixture withCaseSensitive(boolean sensitive) { return withParserConfig(c -> c.withCaseSensitive(sensitive)); } /** Returns a fixture that follows a given lexical policy. */ default SqlOperatorFixture withLex(Lex lex) { return withParserConfig(c -> c.withLex(lex)); } /** Returns a fixture that tests conformance to a particular SQL language * version. */ default SqlOperatorFixture withConformance(SqlConformance conformance) { return withParserConfig(c -> c.withConformance(conformance)) .withValidatorConfig(c -> c.withConformance(conformance)) .withConnectionFactory(cf -> cf.with("conformance", conformance)); } /** Returns the conformance. */ default SqlConformance conformance() { return getFactory().parserConfig().conformance(); } /** Returns a fixture with a given validator configuration. */ default SqlOperatorFixture withValidatorConfig( UnaryOperator transform) { return withFactory(f -> f.withValidatorConfig(transform)); } /** Returns a fixture that tests with implicit type coercion on/off. */ default SqlOperatorFixture enableTypeCoercion(boolean enabled) { return withValidatorConfig(c -> c.withTypeCoercionEnabled(enabled)); } /** Returns a fixture that does not fail validation if it encounters an * unknown function. */ default SqlOperatorFixture withLenientOperatorLookup(boolean lenient) { return withValidatorConfig(c -> c.withLenientOperatorLookup(lenient)); } /** Returns a fixture that gets connections from a given factory. */ default SqlOperatorFixture withConnectionFactory( UnaryOperator transform) { return withFactory(f -> f.withConnectionFactory(transform)); } /** Returns a fixture that uses a given operator table. */ default SqlOperatorFixture withOperatorTable( SqlOperatorTable operatorTable) { return withFactory(f -> f.withOperatorTable(o -> operatorTable)); } /** Returns whether to run tests that are considered 'broken'. * Returns false by default, but it is useful to temporarily enable the * 'broken' tests to see whether they are still broken. */ boolean brokenTestsEnabled(); /** Sets {@link #brokenTestsEnabled()}. */ SqlOperatorFixture withBrokenTestsEnabled(boolean enableBrokenTests); void checkScalar(String expression, TypeChecker typeChecker, ResultChecker resultChecker); /** * Tests that a scalar SQL expression returns the expected result and the * expected type. For example, * *

*
checkScalar("1.1 + 2.9", "4.0", "DECIMAL(2, 1) NOT NULL");
*
* * @param expression Scalar expression * @param result Expected result * @param resultType Expected result type */ default void checkScalar( String expression, Object result, String resultType) { checkType(expression, resultType); checkScalar(expression, SqlTests.ANY_TYPE_CHECKER, ResultCheckers.createChecker(result)); } /** * Tests that a scalar SQL expression returns the expected exact numeric * result as an integer. For example, * *
*
checkScalarExact("1 + 2", 3);
*
* * @param expression Scalar expression * @param result Expected result */ default void checkScalarExact(String expression, int result) { checkScalar(expression, SqlTests.INTEGER_TYPE_CHECKER, isSingle(result)); } /** * Tests that a scalar SQL expression returns the expected exact numeric * result. For example, * *
*
checkScalarExact("1 + 2", "3");
*
* * @param expression Scalar expression * @param expectedType Type we expect the result to have, including * nullability, precision and scale, for example * DECIMAL(2, 1) NOT NULL. * @param result Expected result */ default void checkScalarExact( String expression, String expectedType, String result) { checkScalarExact(expression, expectedType, isSingle(result)); } void checkScalarExact( String expression, String expectedType, ResultChecker resultChecker); /** * Tests that a scalar SQL expression returns expected approximate numeric * result. For example, * *
*
checkScalarApprox("1.0 + 2.1", "3.1");
*
* * @param expression Scalar expression * @param expectedType Type we expect the result to have, including * nullability, precision and scale, for example * DECIMAL(2, 1) NOT NULL. * @param result Expected result, or a matcher * * @see Matchers#within(Number, double) */ void checkScalarApprox( String expression, String expectedType, Object result); /** * Tests that a scalar SQL expression returns the expected boolean result. * For example, * *
*
checkScalarExact("TRUE AND FALSE", Boolean.TRUE);
*
* *

The expected result can be null: * *

*
checkScalarExact("NOT UNKNOWN", null);
*
* * @param expression Scalar expression * @param result Expected result (null signifies NULL). */ void checkBoolean( String expression, @Nullable Boolean result); /** * Tests that a scalar SQL expression returns the expected string result. * For example, * *
*
checkScalarExact("'ab' || 'c'", "abc");
*
* * @param expression Scalar expression * @param result Expected result * @param resultType Expected result type */ void checkString( String expression, String result, String resultType); /** * Tests that a SQL expression returns the SQL NULL value. For example, * *
*
checkNull("CHAR_LENGTH(CAST(NULL AS VARCHAR(3))");
*
* * @param expression Scalar expression */ void checkNull(String expression); /** * Tests that a SQL expression has a given type. For example, * *
* checkType("SUBSTR('hello' FROM 1 FOR 3)", * "VARCHAR(3) NOT NULL"); *
* *

This method checks length/precision, scale, and whether the type allows * NULL values, so is more precise than the type-checking done by methods * such as {@link #checkScalarExact}. * * @param expression Scalar expression * @param type Type string */ void checkType( String expression, String type); /** Very similar to {@link #checkType}, but generates inside a SELECT * with a non-empty GROUP BY. Aggregate functions may be nullable if executed * in a SELECT with an empty GROUP BY. * *

Viz: {@code SELECT sum(1) FROM emp} has type "INTEGER", * {@code SELECT sum(1) FROM emp GROUP BY deptno} has type "INTEGER NOT NULL", */ default SqlOperatorFixture checkAggType(String expr, String type) { checkColumnType(AbstractSqlTester.buildQueryAgg(expr), type); return this; } /** * Checks that a query returns one column of an expected type. For example, * checkType("VALUES (1 + 2)", "INTEGER NOT NULL"). * * @param sql Query expression * @param type Type string */ void checkColumnType( String sql, String type); /** * Tests that a SQL query returns a single column with the given type. For * example, * *

*
check("VALUES (1 + 2)", "3", SqlTypeName.Integer);
*
* *

If result is null, the expression must yield the SQL NULL * value. If result is a {@link java.util.regex.Pattern}, the * result must match that pattern. * * @param query SQL query * @param typeChecker Checks whether the result is the expected type; must * not be null * @param result Expected result, or matcher */ default void check(String query, TypeChecker typeChecker, Object result) { check(query, typeChecker, SqlTests.ANY_PARAMETER_CHECKER, ResultCheckers.createChecker(result)); } default void check(String query, String expectedType, Object result) { check(query, new SqlTests.StringTypeChecker(expectedType), result); } /** * Tests that a SQL query returns a result of expected type and value. * Checking of type and value are abstracted using {@link TypeChecker} * and {@link ResultChecker} functors. * * @param query SQL query * @param typeChecker Checks whether the result is the expected type * @param parameterChecker Checks whether the parameters are of expected * types * @param resultChecker Checks whether the result has the expected value */ default void check(String query, SqlTester.TypeChecker typeChecker, SqlTester.ParameterChecker parameterChecker, ResultChecker resultChecker) { getTester() .check(getFactory(), query, typeChecker, parameterChecker, resultChecker); } /** * Declares that this test is for a given operator. So we can check that all * operators are tested. * * @param operator Operator * @param unimplementedVmNames Names of virtual machines for which this */ SqlOperatorFixture setFor( SqlOperator operator, VmName... unimplementedVmNames); /** * Checks that an aggregate expression returns the expected result. * *

For example, checkAgg("AVG(DISTINCT x)", new String[] {"2", "3", * null, "3" }, new Double(2.5), 0); * * @param expr Aggregate expression, e.g. SUM(DISTINCT x) * @param inputValues Array of input values, e.g. ["1", null, * "2"]. * @param checker Result checker */ void checkAgg( String expr, String[] inputValues, ResultChecker checker); /** * Checks that an aggregate expression returns the expected result. * *

For example, checkAgg("AVG(DISTINCT x)", new String[] {"2", "3", * null, "3" }, "INTEGER", isSingle([2, 3])); * * @param expr Aggregate expression, e.g. SUM(DISTINCT x) * @param inputValues Array of input values, e.g. ["1", null, * "2"]. * @param type Expected result type * @param checker Result checker */ void checkAgg( String expr, String[] inputValues, String type, ResultChecker checker); /** * Checks that an aggregate expression with multiple args returns the expected * result. * * @param expr Aggregate expression, e.g. AGG_FUNC(x, x2, x3) * @param inputValues Nested array of input values, e.g. [ * ["1", null, "2"] * ["3", "4", null] * ] * @param resultChecker Checks whether the result has the expected value */ void checkAggWithMultipleArgs( String expr, String[][] inputValues, ResultChecker resultChecker); /** * Checks that a windowed aggregate expression returns the expected result. * *

For example, checkWinAgg("FIRST_VALUE(x)", new String[] {"2", * "3", null, "3" }, "INTEGER NOT NULL", 2, 0d); * * @param expr Aggregate expression, e.g. {@code SUM(DISTINCT x)} * @param inputValues Array of input values, e.g. {@code ["1", null, "2"]} * @param type Expected result type * @param resultChecker Checks whether the result has the expected value */ void checkWinAgg( String expr, String[] inputValues, String windowSpec, String type, ResultChecker resultChecker); /** * Tests that an aggregate expression fails at run time. * * @param expr An aggregate expression * @param inputValues Array of input values * @param expectedError Pattern for expected error * @param runtime If true, must fail at runtime; if false, must fail at * validate time */ void checkAggFails( String expr, String[] inputValues, String expectedError, boolean runtime); /** * Tests that a scalar SQL expression fails at run time. * * @param expression SQL scalar expression * @param expectedError Pattern for expected error. If !runtime, must * include an error location. * @param runtime If true, must fail at runtime; if false, must fail at * validate time */ void checkFails( StringAndPos expression, String expectedError, boolean runtime); /** As {@link #checkFails(StringAndPos, String, boolean)}, but with a string * that contains carets. */ default void checkFails( String expression, String expectedError, boolean runtime) { checkFails(StringAndPos.of(expression), expectedError, runtime); } /** * Tests that a SQL query fails at prepare time. * * @param sap SQL query and error position * @param expectedError Pattern for expected error. Must * include an error location. */ void checkQueryFails(StringAndPos sap, String expectedError); /** * Tests that a SQL query succeeds at prepare time. * * @param sql SQL query */ void checkQuery(String sql); default SqlOperatorFixture withLibrary(SqlLibrary library) { return withOperatorTable( SqlLibraryOperatorTableFactory.INSTANCE .getOperatorTable(SqlLibrary.STANDARD, library)) .withConnectionFactory(cf -> cf.with(ConnectionFactories.add(CalciteAssert.SchemaSpec.HR)) .with(CalciteConnectionProperty.FUN, library.fun)); } default SqlOperatorFixture withLibraries(SqlLibrary... libraries) { List names = new ArrayList<>(); for (SqlLibrary lib : libraries) { names.add(lib.fun); } return withOperatorTable( SqlLibraryOperatorTableFactory.INSTANCE .getOperatorTable(libraries)) .withConnectionFactory(cf -> cf.with(ConnectionFactories.add(CalciteAssert.SchemaSpec.HR)) .with(CalciteConnectionProperty.FUN, String.join(",", names))); } /** Applies this fixture to some code for each of the given libraries. */ default void forEachLibrary(Iterable libraries, Consumer consumer) { SqlLibrary.expand(libraries).forEach(library -> { try { consumer.accept(this.withLibrary(library)); } catch (Exception e) { throw new RuntimeException("for library " + library, e); } }); } /** Applies this fixture to some code for each of the given conformances. */ default void forEachConformance(Iterable conformances, Consumer consumer) { conformances.forEach(conformance -> { try { consumer.accept(this.withConformance(conformance)); } catch (Exception e) { throw new RuntimeException("for conformance " + conformance, e); } }); } default SqlOperatorFixture forOracle(SqlConformance conformance) { return withConformance(conformance) .withOperatorTable( SqlLibraryOperatorTableFactory.INSTANCE .getOperatorTable(SqlLibrary.STANDARD, SqlLibrary.ORACLE)) .withConnectionFactory(cf -> cf.with(ConnectionFactories.add(CalciteAssert.SchemaSpec.HR)) .with("fun", "oracle")); } /** * Types for cast. */ enum CastType { CAST("cast"), SAFE_CAST("safe_cast"), TRY_CAST("try_cast"); CastType(String name) { this.name = name; } final String name; } default String getCastString( String value, String targetType, boolean errorLoc, CastType castType) { if (errorLoc) { value = "^" + value + "^"; } String function = castType.name; return function + "(" + value + " as " + targetType + ")"; } default void checkCastToApproxOkay(String value, String targetType, Object expected, CastType castType) { checkScalarApprox(getCastString(value, targetType, false, castType), getTargetType(targetType, castType), expected); } default void checkCastToStringOkay(String value, String targetType, String expected, CastType castType) { final String castString = getCastString(value, targetType, false, castType); checkString(castString, expected, getTargetType(targetType, castType)); } default void checkCastToScalarOkay(String value, String targetType, String expected, CastType castType) { final String castString = getCastString(value, targetType, false, castType); checkScalarExact(castString, getTargetType(targetType, castType), expected); } default String getTargetType(String targetType, CastType castType) { return castType == CastType.CAST ? targetType + NON_NULLABLE_SUFFIX : targetType; } default void checkCastToScalarOkay(String value, String targetType, CastType castType) { checkCastToScalarOkay(value, targetType, value, castType); } default void checkCastFails(String value, String targetType, String expectedError, boolean runtime, CastType castType) { // Safe casts should never fail boolean shouldFail = castType == CastType.CAST; final String castString = getCastString(value, targetType, shouldFail && !runtime, castType); if (shouldFail) { checkFails(castString, expectedError, runtime); } else { checkNull(castString); } } default void checkCastToString(String value, @Nullable String type, @Nullable String expected, CastType castType) { String spaces = " "; if (expected == null) { expected = value.trim(); } int len = expected.length(); if (type != null) { value = getCastString(value, type, false, castType); } // currently no exception thrown for truncation if (Bug.DT239_FIXED) { checkCastFails(value, "VARCHAR(" + (len - 1) + ")", STRING_TRUNC_MESSAGE, true, castType); } checkCastToStringOkay(value, "VARCHAR(" + len + ")", expected, castType); checkCastToStringOkay(value, "VARCHAR(" + (len + 5) + ")", expected, castType); // currently no exception thrown for truncation if (Bug.DT239_FIXED) { checkCastFails(value, "CHAR(" + (len - 1) + ")", STRING_TRUNC_MESSAGE, true, castType); } checkCastToStringOkay(value, "CHAR(" + len + ")", expected, castType); checkCastToStringOkay(value, "CHAR(" + (len + 5) + ")", expected + spaces, castType); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy