blob: e68d94c869df0b100784e8b36e16226c387a8edf [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.test;
import org.apache.calcite.avatica.util.Casing;
import org.apache.calcite.avatica.util.Quoting;
import org.apache.calcite.avatica.util.TimeUnit;
import org.apache.calcite.config.Lex;
import org.apache.calcite.rel.type.DelegatingTypeSystem;
import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.rel.type.RelDataTypeSystem;
import org.apache.calcite.rel.type.TimeFrameSet;
import org.apache.calcite.runtime.CalciteContextException;
import org.apache.calcite.sql.SqlCollation;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.fun.SqlLibrary;
import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParseException;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.ArraySqlType;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.type.SqlTypeUtil;
import org.apache.calcite.sql.util.SqlShuttle;
import org.apache.calcite.sql.validate.SqlAbstractConformance;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.sql.validate.SqlDelegatingConformance;
import org.apache.calcite.sql.validate.SqlMonotonicity;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorCatalogReader;
import org.apache.calcite.sql.validate.SqlValidatorImpl;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.test.catalog.CountingFactory;
import org.apache.calcite.test.catalog.MustFilterMockCatalogReader;
import org.apache.calcite.testlib.annotations.LocaleEnUs;
import org.apache.calcite.tools.ValidationException;
import org.apache.calcite.util.Bug;
import org.apache.calcite.util.ImmutableBitSet;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Ordering;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.TreeSet;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import static org.apache.calcite.test.Matchers.isCharset;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasToString;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.junit.jupiter.api.Assumptions.assumeTrue;
import static java.util.Arrays.asList;
/**
* Concrete child class of {@link SqlValidatorTestCase}, containing lots of unit
* tests.
*
* <p>If you want to run these same tests in a different environment, create a
* derived class whose {@link #fixture()} returns a different implementation of
* {@link SqlValidatorFixture}.
*/
@LocaleEnUs
public class SqlValidatorTest extends SqlValidatorTestCase {
//~ Static fields/initializers ---------------------------------------------
// CHECKSTYLE: IGNORE 1
/** @deprecated Deprecated so that usages of this constant will show up in
* yellow in Intellij and maybe someone will fix them. */
protected static final boolean TODO = false;
private static final String ANY = "(?s).*";
protected static final Logger LOGGER =
LoggerFactory.getLogger(SqlValidatorTest.class);
private static final String ERR_IN_VALUES_INCOMPATIBLE =
"Values in expression list must have compatible types";
private static final String ERR_IN_OPERANDS_INCOMPATIBLE =
"Values passed to IN operator must have compatible types";
private static final String ERR_AGG_IN_GROUP_BY =
"Aggregate expression is illegal in GROUP BY clause";
private static final String ERR_AGG_IN_ORDER_BY =
"Aggregate expression is illegal in ORDER BY clause of non-aggregating SELECT";
private static final String ERR_NESTED_AGG =
"Aggregate expressions cannot be nested";
private static final String EMP_RECORD_TYPE =
"RecordType(INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " INTEGER NOT NULL DEPTNO,"
+ " BOOLEAN NOT NULL SLACKER) NOT NULL";
private static final String STR_AGG_REQUIRES_MONO = "Streaming aggregation "
+ "requires at least one monotonic expression in GROUP BY clause";
private static final String STR_ORDER_REQUIRES_MONO =
"Streaming ORDER BY must start with monotonic expression";
private static final String STR_SET_OP_INCONSISTENT =
"Set operator cannot combine streaming and non-streaming inputs";
private static final String ROW_RANGE_NOT_ALLOWED_WITH_RANK =
"ROW/RANGE not allowed with RANK, DENSE_RANK, ROW_NUMBER or PERCENTILE_CONT/DISC functions";
private static final String RANK_REQUIRES_ORDER_BY = "RANK or DENSE_RANK "
+ "functions require ORDER BY clause in window specification";
private static String cannotConvertToStream(String name) {
return "Cannot convert table '" + name + "' to stream";
}
private static String cannotConvertToRelation(String table) {
return "Cannot convert stream '" + table + "' to relation";
}
private static String cannotStreamResultsForNonStreamingInputs(String inputs) {
return "Cannot stream results of a query with no streaming inputs: '"
+ inputs
+ "'. At least one input should be convertible to a stream";
}
static SqlOperatorTable operatorTableFor(SqlLibrary library) {
return SqlLibraryOperatorTableFactory.INSTANCE.getOperatorTable(
SqlLibrary.STANDARD, library);
}
@Test void testMultipleSameAsPass() {
sql("select 1 as again,2 as \"again\", 3 as AGAiN from (values (true))")
.ok();
}
@Test void testMultipleDifferentAs() {
sql("select 1 as c1,2 as c2 from (values(true))").ok();
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-6190">
* Incorrect precision derivation for negative numeric types</a>. */
@Test void testTypeOfDecimal() {
sql("select DECIMAL '100.01' as c1 from (values (true))")
.columnType("DECIMAL(5, 2) NOT NULL");
sql("select DECIMAL '-100.01' as c1 from (values (true))")
.columnType("DECIMAL(5, 2) NOT NULL");
sql("select DECIMAL ' 100.01 ' as c1 from (values (true))")
.columnType("DECIMAL(5, 2) NOT NULL");
sql("select DECIMAL ' -100.01 ' as c1 from (values (true))")
.columnType("DECIMAL(5, 2) NOT NULL");
}
@Test void testTypeOfAs() {
sql("select 1 as c1 from (values (true))")
.columnType("INTEGER NOT NULL");
sql("select 'hej' as c1 from (values (true))")
.columnType("CHAR(3) NOT NULL");
sql("select x'deadbeef' as c1 from (values (true))")
.columnType("BINARY(4) NOT NULL");
sql("select cast(null as boolean) as c1 from (values (true))")
.columnType("BOOLEAN");
}
@Test void testTypesLiterals() {
expr("'abc'")
.columnType("CHAR(3) NOT NULL");
expr("n'abc'")
.columnType("CHAR(3) NOT NULL");
expr("_UTF16'abc'")
.columnType("CHAR(3) NOT NULL");
expr("'ab '\n"
+ "' cd'")
.columnType("CHAR(6) NOT NULL");
expr("'ab'\n"
+ "'cd'\n"
+ "'ef'\n"
+ "'gh'\n"
+ "'ij'\n"
+ "'kl'")
.columnType("CHAR(12) NOT NULL");
expr("n'ab '\n"
+ "' cd'")
.columnType("CHAR(6) NOT NULL");
expr("_UTF16'ab '\n"
+ "' cd'")
.columnType("CHAR(6) NOT NULL");
expr("^x'abc'^")
.fails("Binary literal string must contain an even number of hexits");
expr("x'abcd'")
.columnType("BINARY(2) NOT NULL");
expr("x'abcd'\n"
+ "'ff001122aabb'")
.columnType("BINARY(8) NOT NULL");
expr("x'aaaa'\n"
+ "'bbbb'\n"
+ "'0000'\n"
+ "'1111'")
.columnType("BINARY(8) NOT NULL");
expr("1234567890")
.columnType("INTEGER NOT NULL");
expr("123456.7890")
.columnType("DECIMAL(10, 4) NOT NULL");
expr("123456.7890e3")
.columnType("DOUBLE NOT NULL");
expr("true")
.columnType("BOOLEAN NOT NULL");
expr("false")
.columnType("BOOLEAN NOT NULL");
expr("unknown")
.columnType("BOOLEAN");
expr("DECIMAL '123456.7890'")
.columnType("DECIMAL(10, 4) NOT NULL");
}
/** Tests that date-time literals with invalid strings are considered invalid.
* Originally the parser did that checking, but now the parser creates a
* {@link org.apache.calcite.sql.SqlUnknownLiteral} and the checking is
* deferred to the validator. */
@Test void testLiteral() {
expr("^DATE '12/21/99'^")
.fails("(?s).*Illegal DATE literal.*");
expr("^TIME '1230:33'^")
.fails("(?s).*Illegal TIME literal.*");
expr("^TIME '12:00:00 PM'^")
.fails("(?s).*Illegal TIME literal.*");
expr("^TIMESTAMP '12-21-99, 12:30:00'^")
.fails("(?s).*Illegal TIMESTAMP literal.*");
expr("^TIMESTAMP WITH LOCAL TIME ZONE '12-21-99, 12:30:00'^")
.fails("(?s).*Illegal TIMESTAMP WITH LOCAL TIME ZONE literal.*");
expr("^TIMESTAMP WITH TIME ZONE '12-21-99, 12:30:00'^")
.fails("(?s).*Illegal TIMESTAMP literal.*");
}
/** PostgreSQL and Redshift allow TIMESTAMP literals that contain only a
* date part. */
@Test void testShortTimestampLiteral() {
sql("select timestamp '1969-07-20'")
.ok();
// PostgreSQL allows the following. We should too.
sql("select ^timestamp '1969-07-20 1:2'^")
.fails("Illegal TIMESTAMP literal '1969-07-20 1:2': not in format "
+ "'yyyy-MM-dd HH:mm:ss'"); // PostgreSQL gives 1969-07-20 01:02:00
sql("select ^timestamp '1969-07-20:23:'^")
.fails("Illegal TIMESTAMP literal '1969-07-20:23:': not in format "
+ "'yyyy-MM-dd HH:mm:ss'"); // PostgreSQL gives 1969-07-20 23:00:00
}
@Test void testBooleans() {
sql("select TRUE OR unknowN from (values(true))").ok();
sql("select false AND unknown from (values(true))").ok();
sql("select not UNKNOWn from (values(true))").ok();
sql("select not true from (values(true))").ok();
sql("select not false from (values(true))").ok();
}
@Test void testAndOrIllegalTypesFails() {
// TODO need col+line number
wholeExpr("'abc' AND FaLsE")
.fails("(?s).*'<CHAR.3.> AND <BOOLEAN>'.*");
wholeExpr("TRUE OR 1")
.fails(ANY);
wholeExpr("unknown OR 1.0")
.fails(ANY);
wholeExpr("true OR 1.0e4")
.fails(ANY);
if (TODO) {
wholeExpr("TRUE OR (TIME '12:00' AT LOCAL)")
.fails(ANY);
}
}
@Test void testNotIllegalTypeFails() {
sql("select ^NOT 3.141^ from (values(true))")
.fails("(?s).*Cannot apply 'NOT' to arguments of type "
+ "'NOT<DECIMAL.4, 3.>'.*");
sql("select ^NOT 'abc'^ from (values(true))")
.fails(ANY);
sql("select ^NOT 1^ from (values(true))")
.fails(ANY);
}
@Test void testIs() {
sql("select TRUE IS FALSE FROM (values(true))").ok();
sql("select false IS NULL FROM (values(true))").ok();
sql("select UNKNOWN IS NULL FROM (values(true))").ok();
sql("select FALSE IS UNKNOWN FROM (values(true))").ok();
sql("select TRUE IS NOT FALSE FROM (values(true))").ok();
sql("select TRUE IS NOT NULL FROM (values(true))").ok();
sql("select false IS NOT NULL FROM (values(true))").ok();
sql("select UNKNOWN IS NOT NULL FROM (values(true))").ok();
sql("select FALSE IS NOT UNKNOWN FROM (values(true))").ok();
sql("select 1 IS NULL FROM (values(true))").ok();
sql("select 1.2 IS NULL FROM (values(true))").ok();
expr("^'abc' IS NOT UNKNOWN^")
.fails("(?s).*Cannot apply.*");
}
@Test void testIsFails() {
sql("select ^1 IS TRUE^ FROM (values(true))")
.fails("(?s).*'<INTEGER> IS TRUE'.*");
sql("select ^1.1 IS NOT FALSE^ FROM (values(true))")
.fails(ANY);
sql("select ^1.1e1 IS NOT FALSE^ FROM (values(true))")
.fails("(?s).*Cannot apply 'IS NOT FALSE' to arguments of type "
+ "'<DOUBLE> IS NOT FALSE'.*");
sql("select ^'abc' IS NOT TRUE^ FROM (values(true))")
.fails(ANY);
}
@Test void testScalars() {
sql("select 1 + 1 from (values(true))").ok();
sql("select 1 + 2.3 from (values(true))").ok();
sql("select 1.2+3 from (values(true))").ok();
sql("select 1.2+3.4 from (values(true))").ok();
sql("select 1 - 1 from (values(true))").ok();
sql("select 1 - 2.3 from (values(true))").ok();
sql("select 1.2-3 from (values(true))").ok();
sql("select 1.2-3.4 from (values(true))").ok();
sql("select 1 * 2 from (values(true))").ok();
sql("select 1.2* 3 from (values(true))").ok();
sql("select 1 * 2.3 from (values(true))").ok();
sql("select 1.2* 3.4 from (values(true))").ok();
sql("select 1 / 2 from (values(true))").ok();
sql("select 1 / 2.3 from (values(true))").ok();
sql("select 1.2/ 3 from (values(true))").ok();
sql("select 1.2/3.4 from (values(true))").ok();
}
@Test void testScalarsFails() {
sql("select ^1+TRUE^ from (values(true))")
.fails("(?s).*Cannot apply '\\+' to arguments of type "
+ "'<INTEGER> \\+ <BOOLEAN>'\\. Supported form\\(s\\):.*");
}
@Test void testNumbers() {
sql("select 1+-2.*-3.e-1/-4>+5 AND true from (values(true))").ok();
}
@Test void testPrefix() {
expr("+interval '1' second")
.columnType("INTERVAL SECOND NOT NULL");
expr("-interval '1' month")
.columnType("INTERVAL MONTH NOT NULL");
sql("SELECT ^-'abc'^ from (values(true))")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply '-' to arguments of type '-<CHAR.3.>'.*");
sql("SELECT -'abc' from (values(true))").ok();
sql("SELECT ^+'abc'^ from (values(true))")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply '\\+' to arguments of type '\\+<CHAR.3.>'.*");
sql("SELECT +'abc' from (values(true))").ok();
}
@Test void testNiladicForBigQuery() {
sql("select current_time, current_time(), current_date, "
+ "current_date(), current_timestamp, current_timestamp()")
.withConformance(SqlConformanceEnum.BIG_QUERY).ok();
}
@Test void testEqualNotEqual() {
expr("''=''").ok();
expr("'abc'=n''").ok();
expr("''=_latin1''").ok();
expr("n''=''").ok();
expr("n'abc'=n''").ok();
expr("n''=_latin1''").ok();
expr("_latin1''=''").ok();
expr("_latin1''=n''").ok();
expr("_latin1''=_latin1''").ok();
expr("''<>''").ok();
expr("'abc'<>n''").ok();
expr("''<>_latin1''").ok();
expr("n''<>''").ok();
expr("n'abc'<>n''").ok();
expr("n''<>_latin1''").ok();
expr("_latin1''<>''").ok();
expr("_latin1'abc'<>n''").ok();
expr("_latin1''<>_latin1''").ok();
expr("true=false").ok();
expr("unknown<>true").ok();
expr("1=1").ok();
expr("1=.1").ok();
expr("1=1e-1").ok();
expr("0.1=1").ok();
expr("0.1=0.1").ok();
expr("0.1=1e1").ok();
expr("1.1e1=1").ok();
expr("1.1e1=1.1").ok();
expr("1.1e-1=1e1").ok();
expr("''<>''").ok();
expr("1<>1").ok();
expr("1<>.1").ok();
expr("1<>1e-1").ok();
expr("0.1<>1").ok();
expr("0.1<>0.1").ok();
expr("0.1<>1e1").ok();
expr("1.1e1<>1").ok();
expr("1.1e1<>1.1").ok();
expr("1.1e-1<>1e1").ok();
}
@Test void testEqualNotEqualFails() {
// compare CHAR, INTEGER ok; implicitly convert CHAR
expr("''<>1").ok();
expr("'1'>=1").ok();
// compare INTEGER, NCHAR ok
expr("1<>n'abc'").ok();
// compare CHAR, DECIMAL ok
expr("''=.1").ok();
expr("true<>1e-1").ok();
expr("^true<>1e-1^")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply '<>' to arguments of type '<BOOLEAN> <> <DOUBLE>'.*");
// compare BOOLEAN, CHAR ok
expr("false=''").ok();
expr("^x'a4'=0.01^")
.fails("(?s).*Cannot apply '=' to arguments of type "
+ "'<BINARY.1.> = <DECIMAL.3, 2.>'.*");
expr("^x'a4'=1^")
.fails("(?s).*Cannot apply '=' to arguments of type '<BINARY.1.> = <INTEGER>'.*");
expr("^x'13'<>0.01^")
.fails("(?s).*Cannot apply '<>' to arguments of type "
+ "'<BINARY.1.> <> <DECIMAL.3, 2.>'.*");
expr("^x'abcd'<>1^")
.fails("(?s).*Cannot apply '<>' to arguments of type "
+ "'<BINARY.2.> <> <INTEGER>'.*");
}
@Test void testBinaryString() {
sql("select x'face'=X'' from (values(true))").ok();
sql("select x'ff'=X'' from (values(true))").ok();
}
@Test void testBinaryStringFails() {
expr("select x'ffee'='abc' from (values(true))")
.columnType("BOOLEAN");
sql("select ^x'ffee'='abc'^ from (values(true))")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply '=' to arguments of type "
+ "'<BINARY.2.> = <CHAR.3.>'.*");
sql("select ^x'ff'=88^ from (values(true))")
.fails("(?s).*Cannot apply '=' to arguments of type "
+ "'<BINARY.1.> = <INTEGER>'.*");
sql("select ^x''<>1.1e-1^ from (values(true))")
.fails("(?s).*Cannot apply '<>' to arguments of type "
+ "'<BINARY.0.> <> <DOUBLE>'.*");
sql("select ^x''<>1.1^ from (values(true))")
.fails("(?s).*Cannot apply '<>' to arguments of type "
+ "'<BINARY.0.> <> <DECIMAL.2, 1.>'.*");
}
@Test void testStringLiteral() {
sql("select n''=_iso-8859-1'abc' from (values(true))").ok();
sql("select N'f'<>'''' from (values(true))").ok();
}
@Test void testStringLiteralBroken() {
sql("select 'foo'\n"
+ "'bar' from (values(true))").ok();
sql("select 'foo'\r'bar' from (values(true))").ok();
sql("select 'foo'\n\r'bar' from (values(true))").ok();
sql("select 'foo'\r\n'bar' from (values(true))").ok();
sql("select 'foo'\n'bar' from (values(true))").ok();
sql("select 'foo' /* comment */ ^'bar'^ from (values(true))")
.fails("String literal continued on same line");
sql("select 'foo' -- comment\r from (values(true))").ok();
sql("select 'foo' ^'bar'^ from (values(true))")
.fails("String literal continued on same line");
}
@Test void testArithmeticOperators() {
expr("power(2,3)").ok();
expr("aBs(-2.3e-2)").ok();
expr("MOD(5 ,\t\f\r\n2)").ok();
expr("ln(5.43 )").ok();
expr("log10(- -.2 )").ok();
expr("mod(5.1, 3)").ok();
expr("mod(2,5.1)").ok();
expr("5.1 % 3")
.withConformance(SqlConformanceEnum.LENIENT)
.columnType("DECIMAL(2, 1) NOT NULL");
expr("2 % 5.1")
.withConformance(SqlConformanceEnum.LENIENT)
.columnType("DECIMAL(2, 1) NOT NULL");
expr("exp(3.67)").ok();
}
@Test void testArithmeticOperatorsFails() {
expr("^power(2,'abc')^")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'POWER' to arguments of type "
+ "'POWER.<INTEGER>, <CHAR.3.>.*");
expr("power(2,'abc')")
.columnType("DOUBLE NOT NULL");
expr("^power(true,1)^")
.fails("(?s).*Cannot apply 'POWER' to arguments of type "
+ "'POWER.<BOOLEAN>, <INTEGER>.*");
expr("^mod(x'1100',1)^")
.fails("(?s).*Cannot apply 'MOD' to arguments of type "
+ "'MOD.<BINARY.2.>, <INTEGER>.*");
expr("^mod(1, x'1100')^")
.fails("(?s).*Cannot apply 'MOD' to arguments of type "
+ "'MOD.<INTEGER>, <BINARY.2.>.*");
expr("^abs(x'')^")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'ABS' to arguments of type 'ABS.<BINARY.0.>.*");
expr("^ln(x'face12')^")
.fails("(?s).*Cannot apply 'LN' to arguments of type 'LN.<BINARY.3.>.*");
expr("^log10(x'fa')^")
.fails("(?s).*Cannot apply 'LOG10' to arguments of type 'LOG10.<BINARY.1.>.*");
expr("^exp('abc')^")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'EXP' to arguments of type 'EXP.<CHAR.3.>.*");
expr("exp('abc')")
.columnType("DOUBLE NOT NULL");
}
@Test void testCaseExpression() {
expr("case 1 when 1 then 'one' end").ok();
expr("case 1 when 1 then 'one' else null end").ok();
expr("case 1 when 1 then 'one' else 'more' end").ok();
expr("case 1 when 1 then 'one' when 2 then null else 'more' end").ok();
expr("case when TRUE then 'true' else 'false' end").ok();
sql("values case when TRUE then 'true' else 'false' end").ok();
expr("CASE 1 WHEN 1 THEN cast(null as integer) WHEN 2 THEN null END").ok();
expr("CASE 1 WHEN 1 THEN cast(null as integer) WHEN 2 THEN cast(null as "
+ "integer) END").ok();
expr("CASE 1 WHEN 1 THEN null WHEN 2 THEN cast(null as integer) END").ok();
expr("CASE 1 WHEN 1 THEN cast(null as integer) WHEN 2 THEN cast(cast(null"
+ " as tinyint) as integer) END").ok();
}
@Test void testCaseExpressionTypes() {
expr("case 1 when 1 then 'one' else 'not one' end")
.columnType("CHAR(7) NOT NULL");
expr("case when 2<1 then 'impossible' end")
.columnType("CHAR(10)");
expr("case 'one' when 'two' then 2.00 when 'one' then 1.3 else 3.2 end")
.columnType("DECIMAL(3, 2) NOT NULL");
expr("case 'one' when 'two' then 2 when 'one' then 1.00 else 3 end")
.columnType("DECIMAL(12, 2) NOT NULL");
expr("case 1 when 1 then 'one' when 2 then null else 'more' end")
.columnType("CHAR(4)");
expr("case when TRUE then 'true' else 'false' end")
.columnType("CHAR(5) NOT NULL");
expr("CASE 1 WHEN 1 THEN cast(null as integer) END")
.columnType("INTEGER");
expr("CASE 1\n"
+ "WHEN 1 THEN NULL\n"
+ "WHEN 2 THEN cast(cast(null as tinyint) as integer) END")
.columnType("INTEGER");
expr("CASE 1\n"
+ "WHEN 1 THEN cast(null as integer)\n"
+ "WHEN 2 THEN cast(null as integer) END")
.columnType("INTEGER");
expr("CASE 1\n"
+ "WHEN 1 THEN cast(null as integer)\n"
+ "WHEN 2 THEN cast(cast(null as tinyint) as integer)\n"
+ "END")
.columnType("INTEGER");
expr("CASE 1\n"
+ "WHEN 1 THEN INTERVAL '12 3:4:5.6' DAY TO SECOND(6)\n"
+ "WHEN 2 THEN INTERVAL '12 3:4:5.6' DAY TO SECOND(9)\n"
+ "END")
.columnType("INTERVAL DAY TO SECOND(9)");
sql("select\n"
+ "CASE WHEN job is not null THEN mgr\n"
+ "ELSE 5 end as mgr\n"
+ "from EMP")
.columnType("INTEGER");
}
@Test void testCaseExpressionFails() {
// varchar not comparable with bit string
expr("case 'string' when x'01' then 'zero one' else 'something' end")
.columnType("CHAR(9) NOT NULL");
wholeExpr("case 'string' when x'01' then 'zero one' else 'something' end")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply '=' to arguments of type '<CHAR.6.> = <BINARY.1.>'.*");
// all thens and else return null
wholeExpr("case 1 when 1 then null else null end")
.withTypeCoercion(false)
.fails("(?s).*ELSE clause or at least one THEN clause must be non-NULL.*");
expr("case 1 when 1 then null else null end")
.columnType("NULL");
// all thens and else return null
wholeExpr("case 1 when 1 then null end")
.withTypeCoercion(false)
.fails("(?s).*ELSE clause or at least one THEN clause must be non-NULL.*");
expr("case 1 when 1 then null end")
.columnType("NULL");
wholeExpr("case when true and true then 1 "
+ "when false then 2 "
+ "when false then true " + "else "
+ "case when true then 3 end end")
.fails("Illegal mixing of types in CASE or COALESCE statement");
}
@Test void testNullIf() {
expr("nullif(1,2)").ok();
expr("nullif(1,2)")
.columnType("INTEGER");
expr("nullif('a','b')")
.columnType("CHAR(1)");
expr("nullif(345.21, 2)")
.columnType("DECIMAL(5, 2)");
expr("nullif(345.21, 2e0)")
.columnType("DECIMAL(5, 2)");
wholeExpr("nullif(1,2,3)")
.fails("Invalid number of arguments to function 'NULLIF'. Was "
+ "expecting 2 arguments");
}
@Test void testCoalesce() {
expr("coalesce('a','b')").ok();
expr("coalesce('a','b','c')")
.columnType("CHAR(1) NOT NULL");
sql("select COALESCE(mgr, 12) as m from EMP")
.columnType("INTEGER NOT NULL");
}
@Test void testCoalesceFails() {
wholeExpr("coalesce('a',1)")
.withTypeCoercion(false)
.fails("Illegal mixing of types in CASE or COALESCE statement");
expr("coalesce('a',1)")
.columnType("VARCHAR NOT NULL");
wholeExpr("coalesce('a','b',1)")
.withTypeCoercion(false)
.fails("Illegal mixing of types in CASE or COALESCE statement");
expr("coalesce('a','b',1)")
.columnType("VARCHAR NOT NULL");
}
@Test void testStringCompare() {
expr("'a' = 'b'").ok();
expr("'a' <> 'b'").ok();
expr("'a' > 'b'").ok();
expr("'a' < 'b'").ok();
expr("'a' >= 'b'").ok();
expr("'a' <= 'b'").ok();
expr("cast('' as varchar(1))>cast('' as char(1))").ok();
expr("cast('' as varchar(1))<cast('' as char(1))").ok();
expr("cast('' as varchar(1))>=cast('' as char(1))").ok();
expr("cast('' as varchar(1))<=cast('' as char(1))").ok();
expr("cast('' as varchar(1))=cast('' as char(1))").ok();
expr("cast('' as varchar(1))<>cast('' as char(1))").ok();
}
@Test void testStringCompareType() {
expr("'a' = 'b'")
.columnType("BOOLEAN NOT NULL");
expr("'a' <> 'b'")
.columnType("BOOLEAN NOT NULL");
expr("'a' > 'b'")
.columnType("BOOLEAN NOT NULL");
expr("'a' < 'b'")
.columnType("BOOLEAN NOT NULL");
expr("'a' >= 'b'")
.columnType("BOOLEAN NOT NULL");
expr("'a' <= 'b'")
.columnType("BOOLEAN NOT NULL");
expr("CAST(NULL AS VARCHAR(33)) > 'foo'")
.columnType("BOOLEAN");
}
@Test void testConcat() {
expr("'a'||'b'").ok();
expr("x'12'||x'34'").ok();
expr("'a'||'b'")
.columnType("CHAR(2) NOT NULL");
expr("cast('a' as char(1))||cast('b' as char(2))")
.columnType("CHAR(3) NOT NULL");
expr("cast(null as char(1))||cast('b' as char(2))")
.columnType("CHAR(3)");
expr("'a'||'b'||'c'")
.columnType("CHAR(3) NOT NULL");
expr("'a'||'b'||'cde'||'f'")
.columnType("CHAR(6) NOT NULL");
expr("'a'||'b'||cast('cde' as VARCHAR(3))|| 'f'")
.columnType("VARCHAR(6) NOT NULL");
expr("_UTF16'a'||_UTF16'b'||_UTF16'c'").ok();
}
@Test void testConcatWithCharset() {
sql("_UTF16'a'||_UTF16'b'||_UTF16'c'")
.assertCharset(isCharset("UTF-16LE"));
}
@Test void testConcatFails() {
wholeExpr("'a'||x'ff'")
.fails("(?s).*Cannot apply '\\|\\|' to arguments of type "
+ "'<CHAR.1.> \\|\\| <BINARY.1.>'.*Supported form.s.: "
+ "'<STRING> \\|\\| <STRING>.*'");
}
/** Tests the CONCAT function, which unlike the concat operator ('||') is not
* standard but enabled in the ORACLE, MySQL, BigQuery, POSTGRESQL and MSSQL libraries. */
@Test void testConcatFunction() {
// CONCAT is not in the library operator table
final SqlValidatorFixture s = fixture()
.withOperatorTable(operatorTableFor(SqlLibrary.POSTGRESQL));
s.withExpr("concat('a', 'b')").ok();
s.withExpr("concat(x'12', x'34')").ok();
s.withExpr("concat(_UTF16'a', _UTF16'b', _UTF16'c')").ok();
s.withExpr("concat('aabbcc', 'ab', '+-')")
.columnType("VARCHAR(10) NOT NULL");
s.withExpr("concat('aabbcc', CAST(NULL AS VARCHAR(20)), '+-')")
.columnType("VARCHAR(28) NOT NULL");
s.withExpr("concat('aabbcc', 2)")
.withWhole(true)
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'CONCAT' to arguments of type "
+ "'CONCAT\\(<CHAR\\(6\\)>, <INTEGER>\\)'\\. .*");
s.withExpr("concat('aabbcc', 2)").ok();
s.withExpr("concat('abc', 'ab', 123)")
.withWhole(true)
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'CONCAT' to arguments of type "
+ "'CONCAT\\(<CHAR\\(3\\)>, <CHAR\\(2\\)>, <INTEGER>\\)'\\. .*");
s.withExpr("concat('abc', 'ab', 123)").ok();
s.withExpr("concat(true, false)")
.withWhole(true)
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'CONCAT' to arguments of type "
+ "'CONCAT\\(<BOOLEAN>, <BOOLEAN>\\)'\\. .*");
s.withExpr("concat(true, false)").ok();
s.withExpr("concat(DATE '2020-04-17', TIMESTAMP '2020-04-17 14:17:51')")
.withWhole(true)
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'CONCAT' to arguments of type "
+ "'CONCAT\\(<DATE>, <TIMESTAMP\\(0\\)>\\)'\\. .*");
s.withExpr("concat(DATE '2020-04-17', TIMESTAMP '2020-04-17 14:17:51')").ok();
}
@Test void testBetween() {
expr("1 between 2 and 3").ok();
expr("'a' between 'b' and 'c'").ok();
// can implicitly convert CHAR to INTEGER
expr("'' between 2 and 3").ok();
wholeExpr("date '2012-02-03' between 2 and 3")
.fails("(?s).*Cannot apply 'BETWEEN ASYMMETRIC' to arguments of type.*");
}
@Test void testCharsetMismatch() {
wholeExpr("''=_UTF16''")
.fails("Cannot apply .* to the two different charsets ISO-8859-1 and "
+ "UTF-16LE");
wholeExpr("''<>_UTF16''")
.fails("(?s).*Cannot apply .* to the two different charsets.*");
wholeExpr("''>_UTF16''")
.fails("(?s).*Cannot apply .* to the two different charsets.*");
wholeExpr("''<_UTF16''")
.fails("(?s).*Cannot apply .* to the two different charsets.*");
wholeExpr("''<=_UTF16''")
.fails("(?s).*Cannot apply .* to the two different charsets.*");
wholeExpr("''>=_UTF16''")
.fails("(?s).*Cannot apply .* to the two different charsets.*");
wholeExpr("''||_UTF16''")
.fails(ANY);
wholeExpr("'a'||'b'||_UTF16'c'")
.fails(ANY);
}
// FIXME jvs 2-Feb-2005:
@Disabled("all collation-related tests are disabled due to dtbug 280")
void testSimpleCollate() {
expr("'s' collate latin1$en$1").ok();
expr("'s' collate latin1$en$1")
.columnType("CHAR(1)");
sql("'s'")
.assertCollation(is("ISO-8859-1$en_US$primary"),
is(SqlCollation.Coercibility.COERCIBLE));
sql("'s' collate latin1$sv$3")
.assertCollation(is("ISO-8859-1$sv$3"),
is(SqlCollation.Coercibility.EXPLICIT));
}
@Disabled("all collation-related tests are disabled due to dtbug 280")
void testCharsetAndCollateMismatch() {
// todo
expr("_UTF16's' collate latin1$en$1")
.fails("?");
}
@Disabled("all collation-related tests are disabled due to dtbug 280")
void testDyadicCollateCompare() {
expr("'s' collate latin1$en$1 < 't'").ok();
expr("'t' > 's' collate latin1$en$1").ok();
expr("'s' collate latin1$en$1 <> 't' collate latin1$en$1").ok();
}
@Disabled("all collation-related tests are disabled due to dtbug 280")
void testDyadicCompareCollateFails() {
// two different explicit collations. difference in strength
expr("'s' collate latin1$en$1 <= 't' collate latin1$en$2")
.fails("(?s).*Two explicit different collations.*are illegal.*");
// two different explicit collations. difference in language
expr("'s' collate latin1$sv$1 >= 't' collate latin1$en$1")
.fails("(?s).*Two explicit different collations.*are illegal.*");
}
@Disabled("all collation-related tests are disabled due to dtbug 280")
void testDyadicCollateOperator() {
sql("'a' || 'b'")
.assertCollation(is("ISO-8859-1$en_US$primary"),
is(SqlCollation.Coercibility.COERCIBLE));
sql("'a' collate latin1$sv$3 || 'b'")
.assertCollation(is("ISO-8859-1$sv$3"),
is(SqlCollation.Coercibility.EXPLICIT));
sql("'a' collate latin1$sv$3 || 'b' collate latin1$sv$3")
.assertCollation(is("ISO-8859-1$sv$3"),
is(SqlCollation.Coercibility.EXPLICIT));
}
@Test void testCharLength() {
expr("char_length('string')").ok();
expr("char_length(_UTF16'string')").ok();
expr("character_length('string')").ok();
expr("char_length('string')")
.columnType("INTEGER NOT NULL");
expr("character_length('string')")
.columnType("INTEGER NOT NULL");
}
@Test void testUpperLower() {
expr("upper(_UTF16'sadf')").ok();
expr("lower(n'sadf')").ok();
expr("lower('sadf')")
.columnType("CHAR(4) NOT NULL");
wholeExpr("upper(123)")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'UPPER' to arguments of type 'UPPER.<INTEGER>.'.*");
expr("upper(123)")
.columnType("VARCHAR NOT NULL");
}
@Test void testPosition() {
expr("position('mouse' in 'house')").ok();
expr("position(x'11' in x'100110')").ok();
expr("position(x'11' in x'100110' FROM 10)").ok();
expr("position(x'abcd' in x'')").ok();
expr("position('mouse' in 'house')")
.columnType("INTEGER NOT NULL");
wholeExpr("position(x'1234' in '110')")
.fails("Parameters must be of the same type");
wholeExpr("position(x'1234' in '110' from 3)")
.fails("Parameters must be of the same type");
}
@Test void testTrim() {
expr("trim('mustache' FROM 'beard')").ok();
expr("trim(both 'mustache' FROM 'beard')").ok();
expr("trim(leading 'mustache' FROM 'beard')").ok();
expr("trim(trailing 'mustache' FROM 'beard')").ok();
expr("trim('mustache' FROM 'beard')")
.columnType("VARCHAR(5) NOT NULL");
expr("trim('beard ')")
.columnType("VARCHAR(7) NOT NULL");
expr("trim('mustache' FROM cast(null as varchar(4)))")
.columnType("VARCHAR(4)");
if (TODO) {
final SqlCollation.Coercibility expectedCoercibility = null;
sql("trim('mustache' FROM 'beard')")
.assertCollation(is("CHAR(5)"), is(expectedCoercibility));
}
}
@Test void testTrimFails() {
wholeExpr("trim(123 FROM 'beard')")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'TRIM' to arguments of type.*");
expr("trim(123 FROM 'beard')")
.columnType("VARCHAR(5) NOT NULL");
wholeExpr("trim('a' FROM 123)")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'TRIM' to arguments of type.*");
expr("trim('a' FROM 123)")
.columnType("VARCHAR NOT NULL");
wholeExpr("trim('a' FROM _UTF16'b')")
.fails("(?s).*not comparable to each other.*");
}
@Test void testConvertAndTranslate() {
sql("select convert('abc' using utf16) from emp").ok();
sql("select convert(cast(deptno as varchar) using utf8) from emp");
sql("select convert(null using utf16) from emp").ok();
sql("select ^convert(deptno using latin1)^ from emp")
.fails("Invalid type 'INTEGER NOT NULL' in 'TRANSLATE' function\\. "
+ "Only 'CHARACTER' type is supported");
sql("select convert(ename using utf9) from emp").fails("UTF9");
sql("select translate('abc' using utf8) from emp").ok();
sql("select translate(cast(deptno as varchar) using utf8) from emp");
sql("select translate(null using utf16) from emp").ok();
sql("select ^translate(deptno using latin1)^ from emp")
.fails("Invalid type 'INTEGER NOT NULL' in 'TRANSLATE' function\\. "
+ "Only 'CHARACTER' type is supported");
sql("select translate(ename using utf9) from emp").fails("UTF9");
}
@Test void testTranslate3() {
// TRANSLATE3 is not in the standard operator table
wholeExpr("translate('aabbcc', 'ab', '+-')")
.fails("No match found for function signature "
+ "TRANSLATE3\\(<CHARACTER>, <CHARACTER>, <CHARACTER>\\)");
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.ORACLE);
expr("translate('aabbcc', 'ab', '+-')")
.withOperatorTable(opTable)
.columnType("VARCHAR(6) NOT NULL");
wholeExpr("translate('abc', 'ab')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'TRANSLATE3'. "
+ "Was expecting 3 arguments");
wholeExpr("translate('abc', 'ab', 123)")
.withOperatorTable(opTable)
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'TRANSLATE3' to arguments of type "
+ "'TRANSLATE3\\(<CHAR\\(3\\)>, <CHAR\\(2\\)>, <INTEGER>\\)'\\. .*");
expr("translate('abc', 'ab', 123)")
.withOperatorTable(opTable)
.columnType("VARCHAR(3) NOT NULL");
wholeExpr("translate('abc', 'ab', '+-', 'four')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'TRANSLATE3'. "
+ "Was expecting 3 arguments");
}
@Test void testOverlay() {
expr("overlay('ABCdef' placing 'abc' from 1)").ok();
expr("overlay('ABCdef' placing 'abc' from 1 for 3)").ok();
wholeExpr("overlay('ABCdef' placing 'abc' from '1' for 3)")
.withTypeCoercion(false)
.fails("(?s).*OVERLAY\\(<STRING> PLACING <STRING> FROM <INTEGER>\\).*");
expr("overlay('ABCdef' placing 'abc' from '1' for 3)")
.columnType("VARCHAR(9) NOT NULL");
expr("overlay('ABCdef' placing 'abc' from 1 for 3)")
.columnType("VARCHAR(9) NOT NULL");
expr("overlay('ABCdef' placing 'abc' from 6 for 3)")
.columnType("VARCHAR(9) NOT NULL");
expr("overlay('ABCdef' placing cast(null as char(5)) from 1)")
.columnType("VARCHAR(11)");
if (TODO) {
sql("overlay('ABCdef' placing 'abc' collate latin1$sv from 1 for 3)")
.assertCollation(is("ISO-8859-1$sv"),
is(SqlCollation.Coercibility.EXPLICIT));
}
}
@Test void testSubstring() {
expr("substring('a' FROM 1)").ok();
expr("substring('a' FROM 1 FOR 3)").ok();
expr("substring('a' FROM 'reg' FOR '\\')").ok();
// binary string
expr("substring(x'ff' FROM 1 FOR 2)").ok();
expr("substring('10' FROM 1 FOR 2)")
.columnType("VARCHAR(2) NOT NULL");
expr("substring('1000' FROM 2)")
.columnType("VARCHAR(4) NOT NULL");
expr("substring('1000' FROM '1' FOR 'w')")
.columnType("VARCHAR(4) NOT NULL");
expr("substring(cast(' 100 ' as CHAR(99)) FROM '1' FOR 'w')")
.columnType("VARCHAR(99) NOT NULL");
expr("substring(x'10456b' FROM 1 FOR 2)")
.columnType("VARBINARY(3) NOT NULL");
sql("substring('10' FROM 1 FOR 2)")
.assertCharset(isCharset("ISO-8859-1")); // aka "latin1"
sql("substring(_UTF16'10' FROM 1 FOR 2)")
.assertCharset(isCharset("UTF-16LE"));
expr("substring('a', 1)").ok();
expr("substring('a', 1, 3)").ok();
// Implicit type coercion.
expr("substring(12345, '1')")
.columnType("VARCHAR NOT NULL");
expr("substring('a', '1')")
.columnType("VARCHAR(1) NOT NULL");
expr("substring('a', 1, '3')")
.columnType("VARCHAR(1) NOT NULL");
// Correctly processed null string and params.
expr("SUBSTRING(NULL FROM 1 FOR 2)").ok();
expr("SUBSTRING('text' FROM 1 FOR NULL)").ok();
expr("SUBSTRING('text' FROM NULL FOR 2)").ok();
expr("SUBSTRING('text' FROM NULL)").ok();
}
@Test void testSubstringFails() {
String error = "(?s).*Cannot apply 'SUBSTRING' to arguments of type.*";
wholeExpr("substring('a' from 1 for 'b')")
.withTypeCoercion(false)
.fails(error);
wholeExpr("substring(_UTF16'10' FROM '0' FOR '\\')")
.withTypeCoercion(false)
.fails(error);
wholeExpr("substring('10' FROM _UTF16'0' FOR '\\')")
.withTypeCoercion(false)
.fails(error);
wholeExpr("substring('10' FROM '0' FOR _UTF16'\\')")
.withTypeCoercion(false)
.fails(error);
}
@Test void testLikeAndSimilar() {
expr("'a' like 'b'").ok();
expr("'a' like 'b'").ok();
expr("'a' similar to 'b'").ok();
expr("'a' similar to 'b' escape 'c'").ok();
}
@Test void testIlike() {
final SqlValidatorFixture s = fixture()
.withOperatorTable(operatorTableFor(SqlLibrary.POSTGRESQL));
s.withExpr("'a' ilike 'b'").columnType("BOOLEAN NOT NULL");
s.withExpr("'a' ilike cast(null as varchar(99))").columnType("BOOLEAN");
s.withExpr("cast(null as varchar(99)) not ilike 'b'").columnType("BOOLEAN");
s.withExpr("'a' not ilike 'b' || 'c'").columnType("BOOLEAN NOT NULL");
// ILIKE is only available in the PostgreSQL function library
expr("^'a' ilike 'b'^")
.fails("No match found for function signature ILIKE");
}
@Test void testRlike() {
// RLIKE is supported for SPARK
final SqlValidatorFixture s = fixture()
.withOperatorTable(operatorTableFor(SqlLibrary.SPARK));
s.withExpr("'first_name' rlike '%Ted%'").columnType("BOOLEAN NOT NULL");
s.withExpr("'first_name' rlike '^M+'").columnType("BOOLEAN NOT NULL");
// RLIKE is only supported for Spark and Hive
String noMatch = "(?s).*No match found for function signature RLIKE";
expr("^'b' rlike '.+@.+\\\\..+'^")
.fails(noMatch)
.withOperatorTable(operatorTableFor(SqlLibrary.POSTGRESQL))
.fails(noMatch)
.withOperatorTable(operatorTableFor(SqlLibrary.SPARK))
.columnType("BOOLEAN NOT NULL")
.withOperatorTable(operatorTableFor(SqlLibrary.HIVE))
.columnType("BOOLEAN NOT NULL");
}
@Disabled
void testLikeAndSimilarFails() {
expr("'a' like _UTF16'b' escape 'c'")
.fails("(?s).*Operands _ISO-8859-1.a. COLLATE ISO-8859-1.en_US.primary,"
+ " _SHIFT_JIS.b..*");
expr("'a' similar to _UTF16'b' escape 'c'")
.fails("(?s).*Operands _ISO-8859-1.a. COLLATE ISO-8859-1.en_US.primary,"
+ " _SHIFT_JIS.b..*");
expr("'a' similar to 'b' collate UTF16$jp escape 'c'")
.fails("(?s).*Operands _ISO-8859-1.a. COLLATE ISO-8859-1.en_US.primary,"
+ " _ISO-8859-1.b. COLLATE SHIFT_JIS.jp.primary.*");
}
@Test void testNull() {
expr("nullif(null, 1)").ok();
expr("values 1.0 + ^NULL^").ok();
expr("1.0 + ^NULL^").ok();
expr("case when 1 > 0 then null else 0 end").ok();
expr("1 > 0 and null").ok();
expr("position(null in 'abc' from 1)").ok();
expr("substring(null from 1)").ok();
expr("trim(null from 'ab')").ok();
expr("trim(null from null)").ok();
expr("null || 'a'").ok();
expr("not(null)").ok();
expr("+null").ok();
expr("-null").ok();
expr("upper(null)").ok();
expr("lower(null)").ok();
expr("initcap(null)").ok();
expr("mod(null, 2) + 1").ok();
expr("abs(null)").ok();
expr("round(null,1)").ok();
expr("sign(null) + 1").ok();
expr("truncate(null,1) + 1").ok();
sql("select null as a from emp").ok();
sql("select avg(null) from emp").ok();
sql("select bit_and(null) from emp").ok();
sql("select bit_or(null) from emp").ok();
expr("substring(null from 1) + 1").ok();
expr("substring(^NULL^ from 1)")
.withTypeCoercion(false)
.fails("(?s).*Illegal use of .NULL.*");
expr("values 1.0 + ^NULL^")
.withTypeCoercion(false)
.fails("(?s).*Illegal use of .NULL.*");
expr("values 1.0 + NULL")
.columnType("DECIMAL(2, 1)");
expr("1.0 + ^NULL^")
.withTypeCoercion(false)
.fails("(?s).*Illegal use of .NULL.*");
expr("1.0 + NULL")
.columnType("DECIMAL(2, 1)");
// FIXME: SQL:2003 does not allow raw NULL in IN clause
expr("1 in (1, null, 2)").ok();
expr("1 in (null, 1, null, 2)").ok();
expr("1 in (cast(null as integer), null)").ok();
expr("1 in (null, null)").ok();
}
@Test void testNullCast() {
expr("cast(null as tinyint)")
.columnType("TINYINT");
expr("cast(null as smallint)")
.columnType("SMALLINT");
expr("cast(null as integer)")
.columnType("INTEGER");
expr("cast(null as bigint)")
.columnType("BIGINT");
expr("cast(null as float)")
.columnType("FLOAT");
expr("cast(null as real)")
.columnType("REAL");
expr("cast(null as double)")
.columnType("DOUBLE");
expr("cast(null as boolean)")
.columnType("BOOLEAN");
expr("cast(null as varchar(1))")
.columnType("VARCHAR(1)");
expr("cast(null as char(1))")
.columnType("CHAR(1)");
expr("cast(null as binary(1))")
.columnType("BINARY(1)");
expr("cast(null as date)")
.columnType("DATE");
expr("cast(null as time)")
.columnType("TIME(0)");
expr("cast(null as timestamp)")
.columnType("TIMESTAMP(0)");
expr("cast(null as decimal)")
.columnType("DECIMAL(19, 0)");
expr("cast(null as varbinary(1))")
.columnType("VARBINARY(1)");
expr("cast(null as integer), cast(null as char(1))").ok();
}
@Test void testCastTypeToType() {
expr("cast(123 as char)")
.columnType("CHAR(1) NOT NULL");
expr("cast(123 as varchar)")
.columnType("VARCHAR NOT NULL");
expr("cast(x'1234' as binary)")
.columnType("BINARY(1) NOT NULL");
expr("cast(x'1234' as varbinary)")
.columnType("VARBINARY NOT NULL");
expr("cast(123 as varchar(3))")
.columnType("VARCHAR(3) NOT NULL");
expr("cast(123 as char(3))")
.columnType("CHAR(3) NOT NULL");
expr("cast('123' as integer)")
.columnType("INTEGER NOT NULL");
expr("cast('123' as double)")
.columnType("DOUBLE NOT NULL");
expr("cast('1.0' as real)")
.columnType("REAL NOT NULL");
expr("cast(1.0 as tinyint)")
.columnType("TINYINT NOT NULL");
expr("cast(1 as tinyint)")
.columnType("TINYINT NOT NULL");
expr("cast(1.0 as smallint)")
.columnType("SMALLINT NOT NULL");
expr("cast(1 as integer)")
.columnType("INTEGER NOT NULL");
expr("cast(1.0 as integer)")
.columnType("INTEGER NOT NULL");
expr("cast(1.0 as bigint)")
.columnType("BIGINT NOT NULL");
expr("cast(1 as bigint)")
.columnType("BIGINT NOT NULL");
expr("cast(1.0 as float)")
.columnType("FLOAT NOT NULL");
expr("cast(1 as float)")
.columnType("FLOAT NOT NULL");
expr("cast(1.0 as real)")
.columnType("REAL NOT NULL");
expr("cast(1 as real)")
.columnType("REAL NOT NULL");
expr("cast(1.0 as double)")
.columnType("DOUBLE NOT NULL");
expr("cast(1 as double)")
.columnType("DOUBLE NOT NULL");
expr("cast(123 as decimal(6,4))")
.columnType("DECIMAL(6, 4) NOT NULL");
expr("cast(123 as decimal(6))")
.columnType("DECIMAL(6, 0) NOT NULL");
expr("cast(123 as decimal)")
.columnType("DECIMAL(19, 0) NOT NULL");
expr("cast(1.234 as decimal(2,5))")
.columnType("DECIMAL(2, 5) NOT NULL");
expr("cast('4.5' as decimal(3,1))")
.columnType("DECIMAL(3, 1) NOT NULL");
expr("cast(null as boolean)")
.columnType("BOOLEAN");
expr("cast('abc' as varchar(1))")
.columnType("VARCHAR(1) NOT NULL");
expr("cast('abc' as char(1))")
.columnType("CHAR(1) NOT NULL");
expr("cast(x'ff' as binary(1))")
.columnType("BINARY(1) NOT NULL");
expr("cast(multiset[1] as double multiset)")
.columnType("DOUBLE NOT NULL MULTISET NOT NULL");
expr("cast(multiset['abc'] as integer multiset)")
.columnType("INTEGER NOT NULL MULTISET NOT NULL");
expr("cast(1 as boolean)")
.columnType("BOOLEAN NOT NULL");
expr("cast(1.0e1 as boolean)")
.columnType("BOOLEAN NOT NULL");
expr("^cast(true as numeric)^")
.fails("Cast function cannot convert value of type BOOLEAN "
+ "to type DECIMAL\\(19, 0\\)");
// It's a runtime error that 'TRUE' cannot fit into CHAR(3), but at
// validate time this expression is OK.
expr("cast(true as char(3))")
.columnType("CHAR(3) NOT NULL");
// test cast to time type.
expr("cast('abc' as time)")
.columnType("TIME(0) NOT NULL");
expr("cast('abc' as time without time zone)")
.columnType("TIME(0) NOT NULL");
expr("cast('abc' as time with local time zone)")
.columnType("TIME_WITH_LOCAL_TIME_ZONE(0) NOT NULL");
expr("cast('abc' as time with time zone)")
.columnType("TIME_TZ(0) NOT NULL");
expr("cast('abc' as time(3))")
.columnType("TIME(3) NOT NULL");
expr("cast('abc' as time(3) without time zone)")
.columnType("TIME(3) NOT NULL");
expr("cast('abc' as time(3) with local time zone)")
.columnType("TIME_WITH_LOCAL_TIME_ZONE(3) NOT NULL");
expr("cast('abc' as time(3) with time zone)")
.columnType("TIME_TZ(3) NOT NULL");
// test cast to timestamp type.
expr("cast('abc' as timestamp)")
.columnType("TIMESTAMP(0) NOT NULL");
expr("cast('abc' as timestamp without time zone)")
.columnType("TIMESTAMP(0) NOT NULL");
expr("cast('abc' as timestamp with local time zone)")
.columnType("TIMESTAMP_WITH_LOCAL_TIME_ZONE(0) NOT NULL");
expr("cast('abc' as timestamp with time zone)")
.columnType("TIMESTAMP_TZ(0) NOT NULL");
expr("cast('abc' as timestamp(3))")
.columnType("TIMESTAMP(3) NOT NULL");
expr("cast('abc' as timestamp(3) without time zone)")
.columnType("TIMESTAMP(3) NOT NULL");
expr("cast('abc' as timestamp(3) with local time zone)")
.columnType("TIMESTAMP_WITH_LOCAL_TIME_ZONE(3) NOT NULL");
expr("cast('abc' as timestamp(3) with time zone)")
.columnType("TIMESTAMP_TZ(3) NOT NULL");
}
@Test void testCastRegisteredType() {
expr("cast(123 as ^customBigInt^)")
.fails("Unknown identifier 'CUSTOMBIGINT'");
expr("cast(123 as sales.customBigInt)")
.columnType("BIGINT NOT NULL");
expr("cast(123 as catalog.sales.customBigInt)")
.columnType("BIGINT NOT NULL");
}
@Test void testCastFails() {
expr("cast('foo' as ^bar^)")
.fails("Unknown identifier 'BAR'");
wholeExpr("cast(multiset[1] as integer)")
.fails("(?s).*Cast function cannot convert value of type "
+ "INTEGER MULTISET to type INTEGER");
wholeExpr("cast(x'ff' as decimal(5,2))")
.fails("(?s).*Cast function cannot convert value of type "
+ "BINARY\\(1\\) to type DECIMAL\\(5, 2\\)");
wholeExpr("cast(DATE '1243-12-01' as TIME)")
.fails("(?s).*Cast function cannot convert value of type "
+ "DATE to type TIME.*");
wholeExpr("cast(TIME '12:34:01' as DATE)")
.fails("(?s).*Cast function cannot convert value of type "
+ "TIME\\(0\\) to type DATE.*");
}
@Test void testCastBinaryLiteral() {
expr("cast(^x'0dd'^ as binary(5))")
.fails("Binary literal string must contain an even number of hexits");
}
/**
* Tests whether the GEOMETRY data type is allowed.
*
* @see SqlConformance#allowGeometry()
*/
@Test void testGeometry() {
final String err =
"Geo-spatial extensions and the GEOMETRY data type are not enabled";
sql("select cast(null as ^geometry^) as g from emp")
.withConformance(SqlConformanceEnum.STRICT_2003).fails(err)
.withConformance(SqlConformanceEnum.LENIENT).ok();
}
@Test void testDateTime() {
// LOCAL_TIME
expr("LOCALTIME(3)").ok();
expr("LOCALTIME").ok(); // fix sqlcontext later.
wholeExpr("LOCALTIME(1+2)")
.fails("Argument to function 'LOCALTIME' must be a literal");
wholeExpr("LOCALTIME(NULL)")
.withTypeCoercion(false)
.fails("Argument to function 'LOCALTIME' must not be NULL");
wholeExpr("LOCALTIME(NULL)")
.fails("Argument to function 'LOCALTIME' must not be NULL");
wholeExpr("LOCALTIME(CAST(NULL AS INTEGER))")
.fails("Argument to function 'LOCALTIME' must not be NULL");
wholeExpr("LOCALTIME()")
.fails("No match found for function signature LOCALTIME..");
// with TZ?
expr("LOCALTIME")
.columnType("TIME(0) NOT NULL");
wholeExpr("LOCALTIME(-1)")
.fails("Argument to function 'LOCALTIME' must be a positive integer literal");
expr("^LOCALTIME(100000000000000)^")
.fails("(?s).*Numeric literal '100000000000000' out of range.*");
wholeExpr("LOCALTIME(4)")
.fails("Argument to function 'LOCALTIME' must be a valid precision "
+ "between '0' and '3'");
wholeExpr("LOCALTIME('foo')")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*");
wholeExpr("LOCALTIME('foo')")
.fails("Argument to function 'LOCALTIME' must be a literal");
// LOCALTIMESTAMP
expr("LOCALTIMESTAMP(3)").ok();
// fix sqlcontext later.
expr("LOCALTIMESTAMP").ok();
wholeExpr("LOCALTIMESTAMP(1+2)")
.fails("Argument to function 'LOCALTIMESTAMP' must be a literal");
wholeExpr("LOCALTIMESTAMP()")
.fails("No match found for function signature LOCALTIMESTAMP..");
// with TZ?
expr("LOCALTIMESTAMP")
.columnType("TIMESTAMP(0) NOT NULL");
wholeExpr("LOCALTIMESTAMP(-1)")
.fails("Argument to function 'LOCALTIMESTAMP' must be a positive "
+ "integer literal");
expr("^LOCALTIMESTAMP(100000000000000)^")
.fails("(?s).*Numeric literal '100000000000000' out of range.*");
wholeExpr("LOCALTIMESTAMP(4)")
.fails("Argument to function 'LOCALTIMESTAMP' must be a valid "
+ "precision between '0' and '3'");
wholeExpr("LOCALTIMESTAMP('foo')")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*");
wholeExpr("LOCALTIMESTAMP('foo')")
.fails("Argument to function 'LOCALTIMESTAMP' must be a literal");
// CURRENT_DATE
wholeExpr("CURRENT_DATE(3)")
.fails("No match found for function signature CURRENT_DATE..NUMERIC..");
// fix sqlcontext later.
expr("CURRENT_DATE").ok();
wholeExpr("CURRENT_DATE(1+2)")
.fails("No match found for function signature CURRENT_DATE..NUMERIC..");
wholeExpr("CURRENT_DATE()")
.fails("No match found for function signature CURRENT_DATE..");
// with TZ?
expr("CURRENT_DATE")
.columnType("DATE NOT NULL");
// I guess -s1 is an expression?
wholeExpr("CURRENT_DATE(-1)")
.fails("No match found for function signature CURRENT_DATE..NUMERIC..");
wholeExpr("CURRENT_DATE('foo')")
.fails(ANY);
// current_time
expr("current_time(3)").ok();
// fix sqlcontext later.
expr("current_time").ok();
wholeExpr("current_time(1+2)")
.fails("Argument to function 'CURRENT_TIME' must be a literal");
wholeExpr("current_time()")
.fails("No match found for function signature CURRENT_TIME..");
// with TZ?
expr("current_time")
.columnType("TIME(0) NOT NULL");
wholeExpr("current_time(-1)")
.fails("Argument to function 'CURRENT_TIME' must be a positive integer literal");
expr("^CURRENT_TIME(100000000000000)^")
.fails("(?s).*Numeric literal '100000000000000' out of range.*");
wholeExpr("CURRENT_TIME(4)")
.fails("Argument to function 'CURRENT_TIME' must be a valid precision "
+ "between '0' and '3'");
wholeExpr("current_time('foo')")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*");
wholeExpr("current_time('foo')")
.fails("Argument to function 'CURRENT_TIME' must be a literal");
// current_timestamp
expr("CURRENT_TIMESTAMP(3)").ok();
// fix sqlcontext later.
expr("CURRENT_TIMESTAMP").ok();
sql("SELECT CURRENT_TIMESTAMP AS X FROM (VALUES (1))").ok();
wholeExpr("CURRENT_TIMESTAMP(1+2)")
.fails("Argument to function 'CURRENT_TIMESTAMP' must be a literal");
wholeExpr("CURRENT_TIMESTAMP()")
.fails("No match found for function signature CURRENT_TIMESTAMP..");
// should type be 'TIMESTAMP with TZ'?
expr("CURRENT_TIMESTAMP")
.columnType("TIMESTAMP(0) NOT NULL");
// should type be 'TIMESTAMP with TZ'?
expr("CURRENT_TIMESTAMP(2)")
.columnType("TIMESTAMP(2) NOT NULL");
wholeExpr("CURRENT_TIMESTAMP(-1)")
.fails("Argument to function 'CURRENT_TIMESTAMP' must be a positive "
+ "integer literal");
expr("^CURRENT_TIMESTAMP(100000000000000)^")
.fails("(?s).*Numeric literal '100000000000000' out of range.*");
wholeExpr("CURRENT_TIMESTAMP(4)")
.fails("Argument to function 'CURRENT_TIMESTAMP' must be a valid "
+ "precision between '0' and '3'");
wholeExpr("CURRENT_TIMESTAMP('foo')")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*");
wholeExpr("CURRENT_TIMESTAMP('foo')")
.fails("Argument to function 'CURRENT_TIMESTAMP' must be a literal");
// Date literals
expr("DATE '2004-12-01'").ok();
expr("TIME '12:01:01'").ok();
expr("TIME '11:59:59.99'").ok();
expr("TIME '12:01:01.001'").ok();
expr("TIMESTAMP '2004-12-01 12:01:01'").ok();
expr("TIMESTAMP '2004-12-01 12:01:01.001'").ok();
// REVIEW: Can't think of any date/time/ts literals that will parse,
// but not validate.
}
/**
* Tests casting to/from date/time types.
*/
@Test void testDateTimeCast() {
wholeExpr("CAST(1 as DATE)")
.fails("Cast function cannot convert value of type INTEGER to type DATE");
expr("CAST(DATE '2001-12-21' AS VARCHAR(10))").ok();
expr("CAST( '2001-12-21' AS DATE)").ok();
expr("CAST( TIMESTAMP '2001-12-21 10:12:21' AS VARCHAR(20))").ok();
expr("CAST( TIME '10:12:21' AS VARCHAR(20))").ok();
expr("CAST( '10:12:21' AS TIME)").ok();
expr("CAST( '2004-12-21 10:12:21' AS TIMESTAMP)").ok();
}
@Test void testConvertTimezoneFunction() {
wholeExpr("CONVERT_TIMEZONE('UTC', 'America/Los_Angeles',"
+ " CAST('2000-01-01' AS TIMESTAMP))")
.fails("No match found for function signature "
+ "CONVERT_TIMEZONE\\(<CHARACTER>, <CHARACTER>, <TIMESTAMP>\\)");
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.POSTGRESQL);
expr("CONVERT_TIMEZONE('UTC', 'America/Los_Angeles',\n"
+ " CAST('2000-01-01' AS TIMESTAMP))")
.withOperatorTable(opTable)
.columnType("DATE NOT NULL");
wholeExpr("CONVERT_TIMEZONE('UTC', 'America/Los_Angeles')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'CONVERT_TIMEZONE'. "
+ "Was expecting 3 arguments");
wholeExpr("CONVERT_TIMEZONE('UTC', 'America/Los_Angeles', '2000-01-01')")
.withOperatorTable(opTable)
.fails("Cannot apply 'CONVERT_TIMEZONE' to arguments of type "
+ "'CONVERT_TIMEZONE\\(<CHAR\\(3\\)>, <CHAR\\(19\\)>, "
+ "<CHAR\\(10\\)>\\)'\\. Supported form\\(s\\): "
+ "'CONVERT_TIMEZONE\\(<CHARACTER>, <CHARACTER>, <DATETIME>\\)'");
wholeExpr("CONVERT_TIMEZONE('UTC', 'America/Los_Angeles', "
+ "'UTC', CAST('2000-01-01' AS TIMESTAMP))")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'CONVERT_TIMEZONE'. "
+ "Was expecting 3 arguments");
}
@Test void testToCharFunction() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.POSTGRESQL);
expr("TO_CHAR(CURRENT_TIMESTAMP, 'YYYY-MM-DD HH24:MI:SS.MS TZ')")
.withOperatorTable(opTable)
.ok();
expr("^TO_CHAR(1680080352, 'YYYY-MM-DD HH24:MI:SS.MS TZ')^")
.withOperatorTable(opTable)
.fails("Cannot apply 'TO_CHAR' to arguments of type "
+ "'TO_CHAR\\(<INTEGER>, <CHAR\\(27\\)>\\)'\\. Supported form\\(s\\): "
+ "'TO_CHAR\\(<TIMESTAMP>, <STRING>\\)'");
}
@Test void testToDateFunction() {
wholeExpr("TO_DATE('2000-01-01', 'YYYY-MM-DD')")
.fails("No match found for function signature "
+ "TO_DATE\\(<CHARACTER>, <CHARACTER>\\)");
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.POSTGRESQL);
expr("TO_DATE('2000-01-01', 'YYYY-MM-DD')")
.withOperatorTable(opTable)
.columnType("DATE NOT NULL");
wholeExpr("TO_DATE('2000-01-01')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'TO_DATE'. "
+ "Was expecting 2 arguments");
expr("TO_DATE(2000, 'YYYY')")
.withOperatorTable(opTable)
.columnType("DATE NOT NULL");
wholeExpr("TO_DATE(2000, 'YYYY')")
.withOperatorTable(opTable)
.withTypeCoercion(false)
.fails("Cannot apply 'TO_DATE' to arguments of type "
+ "'TO_DATE\\(<INTEGER>, <CHAR\\(4\\)>\\)'\\. "
+ "Supported form\\(s\\): 'TO_DATE\\(<STRING>, <STRING>\\)'");
wholeExpr("TO_DATE('2000-01-01', 'YYYY-MM-DD', 'YYYY-MM-DD')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'TO_DATE'. "
+ "Was expecting 2 arguments");
}
@Test void testToTimestampFunction() {
wholeExpr("TO_TIMESTAMP('2000-01-01 01:00:00', 'YYYY-MM-DD HH:MM:SS')")
.fails("No match found for function signature "
+ "TO_TIMESTAMP\\(<CHARACTER>, <CHARACTER>\\)");
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.POSTGRESQL);
expr("TO_TIMESTAMP('2000-01-01 01:00:00', 'YYYY-MM-DD HH:MM:SS')")
.withOperatorTable(opTable)
.columnType("TIMESTAMP(0) NOT NULL");
wholeExpr("TO_TIMESTAMP('2000-01-01 01:00:00')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'TO_TIMESTAMP'. "
+ "Was expecting 2 arguments");
expr("TO_TIMESTAMP(2000, 'YYYY')")
.withOperatorTable(opTable)
.columnType("TIMESTAMP(0) NOT NULL");
wholeExpr("TO_TIMESTAMP(2000, 'YYYY')")
.withOperatorTable(opTable)
.withTypeCoercion(false)
.fails("Cannot apply 'TO_TIMESTAMP' to arguments of type "
+ "'TO_TIMESTAMP\\(<INTEGER>, <CHAR\\(4\\)>\\)'\\. "
+ "Supported form\\(s\\): 'TO_TIMESTAMP\\(<STRING>, <STRING>\\)'");
wholeExpr("TO_TIMESTAMP('2000-01-01 01:00:00', 'YYYY-MM-DD HH:MM:SS',"
+ " 'YYYY-MM-DD')")
.withOperatorTable(opTable)
.fails("Invalid number of arguments to function 'TO_TIMESTAMP'. "
+ "Was expecting 2 arguments");
}
@Test void testCurrentDatetime() throws SqlParseException, ValidationException {
final String currentDateTimeExpr = "select ^current_datetime^";
SqlValidatorFixture shouldFail = sql(currentDateTimeExpr)
.withConformance(SqlConformanceEnum.BIG_QUERY);
final String expectedError = "query [select CURRENT_DATETIME]; exception "
+ "[Column 'CURRENT_DATETIME' not found in any table]; class "
+ "[class org.apache.calcite.sql.validate.SqlValidatorException]; pos [line 1 col 8 thru line 1 col 8]";
shouldFail.fails("Column 'CURRENT_DATETIME' not found in any table");
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
sql("select current_datetime")
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable).ok();
sql("select current_datetime()")
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable).ok();
sql("select CURRENT_DATETIME('America/Los_Angeles')")
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable).ok();
sql("select CURRENT_DATETIME(CAST(NULL AS VARCHAR(20)))")
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable).ok();
}
@Test void testInvalidFunction() {
wholeExpr("foo()")
.fails("No match found for function signature FOO..");
wholeExpr("mod(123)")
.fails("Invalid number of arguments to function 'MOD'. "
+ "Was expecting 2 arguments");
assumeTrue(false,
"test case for [CALCITE-3326], disabled til it is fixed");
sql("select foo()")
.withTypeCoercion(false)
.fails("No match found for function signature FOO..");
}
@Test void testInvalidTableFunction() {
// A table function at most have one input table with row semantics
sql("select * from table(^invalid(table orders, table emp)^)")
.fails("A table function at most has one input table with row semantics."
+ " Table function 'INVALID' has multiple input tables with row semantics");
// Only tables with set semantics may be partitioned
sql("select * from table(^score(table orders partition by productid)^)")
.fails("Only tables with set semantics may be partitioned."
+ " Invalid PARTITION BY clause in the 0-th operand of table function 'SCORE'");
// Only tables with set semantics may be ordered
sql("select * from table(^score(table orders order by orderId)^)")
.fails("Only tables with set semantics may be ordered."
+ " Invalid ORDER BY clause in the 0-th operand of table function 'SCORE'");
}
@Test void testTableFunctionWithTableParam() {
// test input table with row semantic
sql("select * from table(score(table orders))").ok();
// test no partition by clause and order by clause for input table with set semantic
sql("select * from table(topn(table orders, 3))").ok();
// test one partition key for input table with set semantic
sql("select * from table(topn(table orders partition by productid, 3))")
.ok();
// test multiple partition keys for input table with set semantic
sql("select * from table(topn(table orders partition by (orderId, productid), 3))")
.ok();
// test one order key for input table with set semantic
sql("select * from table(topn(table orders order by orderId, 3))")
.ok();
// test multiple order keys for input table with set semantic
sql("select * from table(topn(table orders order by (orderId, productid), 3))")
.ok();
// test complex order-by clause for input table with set semantic
sql("select * from table(topn(table orders order by (orderId desc, productid asc), 3))")
.ok();
// test partition by clause and order by clause for input table with set semantic
sql("select * from table(topn(table orders partition by productid order by orderId, 3))")
.ok();
// test partition by clause and order by clause for subquery
sql("select * from table(topn(select * from Orders partition by productid\n "
+ "order by orderId, 3))")
.ok();
// test multiple input tables
sql("select * from table(\n"
+ "similarlity(\n"
+ " table emp partition by deptno order by empno,\n"
+ " table emp_b partition by deptno order by empno))")
.ok();
}
@Test void testUnknownFunctionHandling() {
final SqlValidatorFixture s = fixture().withLenientOperatorLookup(true);
s.withExpr("concat('a', 2)").ok();
s.withExpr("foo('2001-12-21')").ok();
s.withExpr("\"foo\"('b')").ok();
s.withExpr("foo()").ok();
s.withExpr("'a' || foo(bar('2001-12-21'))").ok();
s.withExpr("cast(foo(5, 2) as DECIMAL)").ok();
s.withExpr("select ascii('xyz')").ok();
s.withExpr("select get_bit(CAST('FFFF' as BINARY), 1)").ok();
s.withExpr("select now()").ok();
s.withExpr("^TIMESTAMP_CMP_TIMESTAMPTZ^").fails("(?s).*");
s.withExpr("atan(0)").ok();
s.withExpr("select row_number() over () from emp").ok();
s.withExpr("select coalesce(1, 2, 3)").ok();
s.withSql("select count() from emp").ok(); // too few args
s.withSql("select sum(1, 2) from emp").ok(); // too many args
}
@Test void testJdbcFunctionCall() {
expr("{fn log10(1)}").ok();
expr("{fn locate('','')}").ok();
expr("{fn insert('',1,2,'')}").ok();
// 'lower' is a valid SQL function but not valid JDBC fn; the JDBC
// equivalent is 'lcase'
wholeExpr("{fn lower('Foo' || 'Bar')}")
.fails("Function '\\{fn LOWER\\}' is not defined");
expr("{fn lcase('Foo' || 'Bar')}").ok();
expr("{fn power(2, 3)}").ok();
wholeExpr("{fn insert('','',1,2)}")
.withTypeCoercion(false)
.fails("(?s).*.*");
expr("{fn insert('','',1,2)}").ok();
wholeExpr("{fn insert('','',1)}")
.fails("(?s).*4.*");
expr("{fn locate('','',1)}").ok();
wholeExpr("{fn log10('1')}")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*fn LOG10..<CHAR.1.>.*");
expr("{fn log10('1')}").ok();
final String expected = "Cannot apply '\\{fn LOG10\\}' to arguments of"
+ " type '\\{fn LOG10\\}\\(<INTEGER>, <INTEGER>\\)'\\. "
+ "Supported form\\(s\\): '\\{fn LOG10\\}\\(<NUMERIC>\\)'";
wholeExpr("{fn log10(1,1)}")
.fails(expected);
wholeExpr("{fn fn(1)}")
.fails("(?s).*Function '.fn FN.' is not defined.*");
wholeExpr("{fn hahaha(1)}")
.fails("(?s).*Function '.fn HAHAHA.' is not defined.*");
}
@Test void testQuotedFunction() {
if (false) {
// REVIEW jvs 2-Feb-2005: I am disabling this test because I
// removed the corresponding support from the parser. Where in the
// standard does it state that you're supposed to be able to quote
// keywords for builtin functions?
expr("\"CAST\"(1 as double)").ok();
expr("\"POSITION\"('b' in 'alphabet')").ok();
// convert and translate not yet implemented
// checkExp("\"CONVERT\"('b' using conversion)");
// checkExp("\"TRANSLATE\"('b' using translation)");
expr("\"OVERLAY\"('a' PLAcing 'b' from 1)").ok();
expr("\"SUBSTRING\"('a' from 1)").ok();
expr("\"TRIM\"('b')").ok();
} else {
expr("\"TRIM\"('b' ^FROM^ 'a')")
.fails("(?s).*Encountered \"FROM\" at .*");
// Without the "FROM" noise word, TRIM is parsed as a regular
// function without quoting and built-in function with quoting.
expr("\"TRIM\"('b', 'FROM', 'a')")
.columnType("VARCHAR(1) NOT NULL");
expr("TRIM('b')")
.columnType("VARCHAR(1) NOT NULL");
}
}
/**
* Not able to parse member function yet.
*/
@Test void testInvalidMemberFunction() {
expr("myCol.^func()^")
.fails("(?s).*No match found for function signature FUNC().*");
expr("customer.mySubschema.^memberFunc()^")
.fails("(?s).*No match found for function signature MEMBERFUNC().*");
}
@Test void testRowtype() {
sql("values (1),(2),(1)").ok();
sql("values (1),(2),(1)")
.type("RecordType(INTEGER NOT NULL EXPR$0) NOT NULL");
sql("values (1,'1'),(2,'2')").ok();
sql("values (1,'1'),(2,'2')")
.type("RecordType(INTEGER NOT NULL EXPR$0, CHAR(1) NOT NULL EXPR$1) NOT NULL");
sql("values true")
.type("RecordType(BOOLEAN NOT NULL EXPR$0) NOT NULL");
sql("^values ('1'),(2)^")
.fails("Values passed to VALUES operator must have compatible types");
if (TODO) {
sql("values (1),(2.0),(3)")
.columnType("ROWTYPE(DOUBLE)");
}
}
@Test void testRow() {
// double-nested rows can confuse validator namespace resolution
sql("select t.r.\"EXPR$1\".\"EXPR$2\"\n"
+ "from (select ((1,2),(3,4,5)) r from dept) t")
.columnType("INTEGER NOT NULL");
sql("select row(emp.empno, emp.ename) from emp")
.columnType("RecordType(INTEGER NOT NULL EXPR$0, VARCHAR(20) NOT NULL EXPR$1) NOT NULL");
sql("select row(emp.empno + 1, emp.ename) from emp")
.columnType("RecordType(INTEGER NOT NULL EXPR$0, VARCHAR(20) NOT NULL EXPR$1) NOT NULL");
sql("select row((select deptno from dept where dept.deptno = emp.deptno), emp.ename)\n"
+ "from emp")
.columnType("RecordType(INTEGER EXPR$0, VARCHAR(20) NOT NULL EXPR$1) NOT NULL");
}
@Test void testRowWithValidDot() {
sql("select ((1,2),(3,4,5)).\"EXPR$1\".\"EXPR$2\"\n from dept")
.columnType("INTEGER NOT NULL");
sql("select row(1,2).\"EXPR$1\" from dept")
.columnType("INTEGER NOT NULL");
sql("select t.a.\"EXPR$1\" from (select row(1,2) as a from (values (1))) as t")
.columnType("INTEGER NOT NULL");
}
@Test void testRowWithInvalidDotOperation() {
final String sql = "select t.^s.\"EXPR$1\"^ from (\n"
+ " select 1 AS s from (values (1))) as t";
expr(sql)
.fails("(?s).*Column 'S\\.EXPR\\$1' not found in table 'T'.*");
expr("select ^array[1, 2, 3]^.\"EXPR$1\" from dept")
.fails("(?s).*Incompatible types.*");
expr("select ^'mystr'^.\"EXPR$1\" from dept")
.fails("(?s).*Incompatible types.*");
}
@Test void testDotAfterParenthesizedIdentifier() {
sql("select (home_address).city from emp_address")
.columnType("VARCHAR(20) NOT NULL");
}
@Test void testMultiset() {
expr("multiset[1]")
.columnType("INTEGER NOT NULL MULTISET NOT NULL");
expr("multiset[1, CAST(null AS DOUBLE)]")
.columnType("DOUBLE MULTISET NOT NULL");
expr("multiset[1.3,2.3]")
.columnType("DECIMAL(2, 1) NOT NULL MULTISET NOT NULL");
expr("multiset[1,2.3, cast(4 as bigint)]")
.columnType("DECIMAL(19, 0) NOT NULL MULTISET NOT NULL");
expr("multiset['1','22', '333','22']")
.columnType("CHAR(3) NOT NULL MULTISET NOT NULL");
expr("^multiset[1, '2']^")
.fails("Parameters must be of the same type");
expr("multiset[ROW(1,2)]")
.columnType("RecordType(INTEGER NOT NULL EXPR$0,"
+ " INTEGER NOT NULL EXPR$1) NOT NULL MULTISET NOT NULL");
expr("multiset[ROW(1,2),ROW(2,5)]")
.columnType("RecordType(INTEGER NOT NULL EXPR$0,"
+ " INTEGER NOT NULL EXPR$1) NOT NULL MULTISET NOT NULL");
expr("multiset[ROW(1,2),ROW(3.4,5.4)]")
.columnType("RecordType(DECIMAL(11, 1) NOT NULL EXPR$0,"
+ " DECIMAL(11, 1) NOT NULL EXPR$1) NOT NULL MULTISET NOT NULL");
expr("multiset(select*from emp)")
.columnType("RecordType(INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " INTEGER NOT NULL DEPTNO,"
+ " BOOLEAN NOT NULL SLACKER) NOT NULL MULTISET NOT NULL");
}
@Test void testMultisetSetOperators() {
expr("multiset[1] multiset union multiset[1,2.3]").ok();
expr("multiset[324.2] multiset union multiset[23.2,2.32]")
.columnType("DECIMAL(5, 2) NOT NULL MULTISET NOT NULL");
expr("multiset[1] multiset union multiset[1,2.3]")
.columnType("DECIMAL(11, 1) NOT NULL MULTISET NOT NULL");
expr("multiset[1] multiset union all multiset[1,2.3]").ok();
expr("multiset[1] multiset except multiset[1,2.3]").ok();
expr("multiset[1] multiset except all multiset[1,2.3]").ok();
expr("multiset[1] multiset intersect multiset[1,2.3]").ok();
expr("multiset[1] multiset intersect all multiset[1,2.3]").ok();
expr("^multiset[1, '2']^ multiset union multiset[1]")
.fails("Parameters must be of the same type");
expr("multiset[ROW(1,2)] multiset intersect multiset[row(3,4)]").ok();
if (TODO) {
wholeExpr("multiset[ROW(1,'2')] multiset union multiset[ROW(1,2)]")
.fails("Parameters must be of the same type");
}
}
@Test void testSubMultisetOf() {
expr("multiset[1] submultiset of multiset[1,2.3]")
.columnType("BOOLEAN NOT NULL");
expr("multiset[1] submultiset of multiset[1]")
.columnType("BOOLEAN NOT NULL");
expr("^multiset[1, '2']^ submultiset of multiset[1]")
.fails("Parameters must be of the same type");
expr("multiset[ROW(1,2)] submultiset of multiset[row(3,4)]").ok();
}
@Test void testElement() {
expr("element(multiset[1])")
.columnType("INTEGER");
expr("1.0+element(multiset[1])")
.columnType("DECIMAL(12, 1)");
expr("element(multiset['1'])")
.columnType("CHAR(1)");
expr("element(multiset[1e-2])")
.columnType("DOUBLE");
expr("element(multiset[multiset[cast(null as tinyint)]])")
.columnType("TINYINT MULTISET");
// Test case for https://issues.apache.org/jira/projects/CALCITE/issues/CALCITE-6227
// ELEMENT(NULL) causes an assertion failure.
expr("element(null)")
.columnType("NULL");
}
@Test void testMemberOf() {
expr("1 member of multiset[1]")
.columnType("BOOLEAN NOT NULL");
wholeExpr("1 member of multiset['1']")
.fails("Cannot compare values of types 'INTEGER', 'CHAR\\(1\\)'");
}
@Test void testIsASet() {
expr("multiset[1] is a set").ok();
expr("multiset['1'] is a set").ok();
wholeExpr("'a' is a set")
.fails(".*Cannot apply 'IS A SET' to.*");
}
@Test void testCardinality() {
expr("cardinality(multiset[1])")
.columnType("INTEGER NOT NULL");
expr("cardinality(multiset['1'])")
.columnType("INTEGER NOT NULL");
wholeExpr("cardinality('a')")
.fails("Cannot apply 'CARDINALITY' to arguments of type "
+ "'CARDINALITY\\(<CHAR\\(1\\)>\\)'\\. Supported form\\(s\\): "
+ "'CARDINALITY\\(<MULTISET>\\)'\n"
+ "'CARDINALITY\\(<ARRAY>\\)'\n"
+ "'CARDINALITY\\(<MAP>\\)'");
}
@Test void testPivot() {
final String sql = "SELECT * FROM emp\n"
+ "PIVOT (sum(sal) AS ss FOR job in ('CLERK' AS c, 'MANAGER' AS m))";
sql(sql).type("RecordType(INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME, INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE, INTEGER NOT NULL COMM,"
+ " INTEGER NOT NULL DEPTNO, BOOLEAN NOT NULL SLACKER,"
+ " INTEGER C_SS, INTEGER M_SS) NOT NULL");
}
@Test void testPivot2() {
final String sql = "SELECT *\n"
+ "FROM (SELECT deptno, job, sal\n"
+ " FROM emp)\n"
+ "PIVOT (SUM(sal) AS sum_sal, COUNT(*) AS \"COUNT\"\n"
+ " FOR (job) IN ('CLERK', 'MANAGER' mgr, 'ANALYST' AS \"a\"))\n"
+ "ORDER BY deptno";
final String type = "RecordType(INTEGER NOT NULL DEPTNO, "
+ "INTEGER 'CLERK'_SUM_SAL, BIGINT NOT NULL 'CLERK'_COUNT, "
+ "INTEGER MGR_SUM_SAL, BIGINT NOT NULL MGR_COUNT, INTEGER a_SUM_SAL, "
+ "BIGINT NOT NULL a_COUNT) NOT NULL";
sql(sql).type(type);
}
@Test void testPivotAliases() {
final String sql = "SELECT *\n"
+ "FROM (\n"
+ " SELECT deptno, job, sal FROM emp)\n"
+ "PIVOT (SUM(sal) AS ss\n"
+ " FOR (job, deptno)\n"
+ " IN (('A B'/*C*/||' D', 10),\n"
+ " ('MANAGER', null) mgr,\n"
+ " ('ANALYST', 30) AS \"a\"))";
// Oracle uses parse tree without spaces around '||',
// 'A B'||' D'_10_SUM_SAL
// but close enough.
final String type = "RecordType(INTEGER 'A B' || ' D'_10_SS, "
+ "INTEGER MGR_SS, INTEGER a_SS) NOT NULL";
sql(sql).type(type);
}
@Test void testPivotAggAliases() {
final String sql = "SELECT *\n"
+ "FROM (SELECT deptno, job, sal FROM emp)\n"
+ "PIVOT (SUM(sal) AS ss, MIN(job)\n"
+ " FOR deptno IN (10 AS ten, 20))";
final String type = "RecordType(INTEGER TEN_SS, VARCHAR(10) TEN, "
+ "INTEGER 20_SS, VARCHAR(10) 20) NOT NULL";
sql(sql).type(type);
}
@Test void testPivotNoValues() {
final String sql = "SELECT *\n"
+ "FROM (SELECT deptno, sal, job FROM emp)\n"
+ "PIVOT (sum(sal) AS sum_sal FOR job in ())";
sql(sql).type("RecordType(INTEGER NOT NULL DEPTNO) NOT NULL");
}
/** Output only includes columns not referenced in an aggregate or axis. */
@Test void testPivotRemoveColumns() {
final String sql = "SELECT * FROM emp\n"
+ "PIVOT (sum(sal) AS sum_sal, count(comm) AS count_comm,\n"
+ " min(hiredate) AS min_hiredate, max(hiredate) AS max_hiredate\n"
+ " FOR (job, deptno, slacker, mgr, ename)\n"
+ " IN (('CLERK', 10, false, null, ename) AS c10))";
sql(sql).type("RecordType(INTEGER NOT NULL EMPNO,"
+ " INTEGER C10_SUM_SAL, BIGINT NOT NULL C10_COUNT_COMM,"
+ " TIMESTAMP(0) C10_MIN_HIREDATE,"
+ " TIMESTAMP(0) C10_MAX_HIREDATE) NOT NULL");
}
@Test void testPivotInvalidCol() {
final String sql = "SELECT * FROM emp\n"
+ "PIVOT (sum(^invalid^) AS sal FOR job in ('CLERK' AS c, 'MANAGER'))";
sql(sql).fails("Column 'INVALID' not found in any table");
}
@Test void testPivotInvalidCol2() {
final String sql = "SELECT * FROM emp\n"
+ "PIVOT (sum(sal) AS sal FOR (job, ^invalid^) in (('CLERK', 'x') AS c))";
sql(sql).fails("Column 'INVALID' not found in any table");
}
@Test void testPivotMeasureMustBeAgg() {
final String sql = "SELECT * FROM emp\n"
+ "PIVOT (sal ^+^ 1 AS sal1 FOR job in ('CLERK' AS c, 'MANAGER'))";
sql(sql).fails("(?s).*Encountered \"\\+\" at .*");
final String sql2 = "SELECT * FROM emp\n"
+ "PIVOT (^log10(sal)^ AS logSal FOR job in ('CLERK' AS c, 'MANAGER'))";
sql(sql2).fails("Measure expression in PIVOT must use aggregate function");
final String sql3 = "SELECT * FROM emp\n"
+ "PIVOT (^123^ AS logSal FOR job in ('CLERK' AS c, 'MANAGER'))";
sql(sql3).fails("(?s).*Encountered \"123\" at .*");
}
/** Tests an expression as argument to an aggregate function in a PIVOT.
* Both of the columns referenced ({@code sum} and {@code deptno}) are removed
* from the implicit GROUP BY. */
@Test void testPivotAggExpression() {
final String sql = "SELECT * FROM (SELECT sal, deptno, job, mgr FROM Emp)\n"
+ "PIVOT (sum(sal + deptno + 1)\n"
+ " FOR job in ('CLERK' AS c, 'ANALYST' AS a))";
sql(sql).type("RecordType(INTEGER MGR, INTEGER C, INTEGER A) NOT NULL");
}
@Test void testPivotValueMismatch() {
final String sql = "SELECT * FROM Emp\n"
+ "PIVOT (SUM(sal) FOR job IN (^('A', 'B')^, ('C', 'D')))";
sql(sql).fails("Value count in PIVOT \\(2\\) must match number of "
+ "FOR columns \\(1\\)");
final String sql2 = "SELECT * FROM Emp\n"
+ "PIVOT (SUM(sal) FOR job IN (^('A', 'B')^ AS x, ('C', 'D')))";
sql(sql2).fails("Value count in PIVOT \\(2\\) must match number of "
+ "FOR columns \\(1\\)");
final String sql3 = "SELECT * FROM Emp\n"
+ "PIVOT (SUM(sal) FOR (job) IN (^('A', 'B')^))";
sql(sql3).fails("Value count in PIVOT \\(2\\) must match number of "
+ "FOR columns \\(1\\)");
final String sql4 = "SELECT * FROM Emp\n"
+ "PIVOT (SUM(sal) FOR (job, deptno) IN (^'CLERK'^, 10))";
sql(sql4).fails("Value count in PIVOT \\(1\\) must match number of "
+ "FOR columns \\(2\\)");
}
@Test void testUnpivot() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT (remuneration\n"
+ " FOR remuneration_type IN (comm AS 'commission',\n"
+ " sal as 'salary'))";
sql(sql).type("RecordType(INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME, VARCHAR(10) NOT NULL JOB, INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE, INTEGER NOT NULL DEPTNO,"
+ " BOOLEAN NOT NULL SLACKER, CHAR(10) NOT NULL REMUNERATION_TYPE,"
+ " INTEGER NOT NULL REMUNERATION) NOT NULL");
}
@Test void testUnpivotInvalidColumn() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT (remuneration\n"
+ " FOR remuneration_type IN (comm AS 'commission',\n"
+ " ^unknownCol^ as 'salary'))";
sql(sql).fails("Column 'UNKNOWNCOL' not found in any table");
}
@Test void testUnpivotCannotDeriveMeasureType() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT (remuneration\n"
+ " FOR remuneration_type IN (^comm^ AS 'commission',\n"
+ " ename as 'salary'))";
sql(sql).fails("In UNPIVOT, cannot derive type for measure 'REMUNERATION'"
+ " because source columns have different data types");
}
@Test void testUnpivotValueMismatch() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT (remuneration\n"
+ " FOR remuneration_type IN (comm AS 'commission',\n"
+ " sal AS ^('salary', 1)^))";
String expected = "Value count in UNPIVOT \\(2\\) must match "
+ "number of FOR columns \\(1\\)";
sql(sql).fails(expected);
}
@Test void testUnpivotDuplicateName() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT ((remuneration, ^remuneration^)\n"
+ " FOR remuneration_type\n"
+ " IN ((comm, comm) AS 'commission',\n"
+ " (sal, sal) AS 'salary'))";
sql(sql).fails("Duplicate column name 'REMUNERATION' in UNPIVOT");
}
@Test void testUnpivotDuplicateName2() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT (remuneration\n"
+ " FOR ^remuneration^ IN (comm AS 'commission',\n"
+ " sal AS 'salary'))";
sql(sql).fails("Duplicate column name 'REMUNERATION' in UNPIVOT");
}
@Test void testUnpivotDuplicateName3() {
final String sql = "SELECT * FROM emp\n"
+ "UNPIVOT (remuneration\n"
+ " FOR ^deptno^ IN (comm AS 'commission',\n"
+ " sal AS 'salary'))";
sql(sql).fails("Duplicate column name 'DEPTNO' in UNPIVOT");
}
@Test void testUnpivotMissingAs() {
final String sql = "SELECT *\n"
+ "FROM (\n"
+ " SELECT *\n"
+ " FROM (VALUES (0, 1, 2, 3, 4),\n"
+ " (10, 11, 12, 13, 14))\n"
+ " AS t (c0, c1, c2, c3, c4))\n"
+ "UNPIVOT ((m0, m1, m2)\n"
+ " FOR (a0, a1)\n"
+ " IN ((c1, c2, c3) AS ('col1','col2'),\n"
+ " (c2, c3, c4)))";
sql(sql).type("RecordType(INTEGER NOT NULL C0, VARCHAR(8) NOT NULL A0,"
+ " VARCHAR(8) NOT NULL A1, INTEGER M0, INTEGER M1,"
+ " INTEGER M2) NOT NULL");
}
@Test void testUnpivotMissingAs2() {
final String sql = "SELECT *\n"
+ "FROM (\n"
+ " SELECT *\n"
+ " FROM (VALUES (0, 1, 2, 3, 4),\n"
+ " (10, 11, 12, 13, 14))\n"
+ " AS t (c0, c1, c2, c3, c4))\n"
+ "UNPIVOT ((m0, m1, m2)\n"
+ " FOR (^a0^, a1)\n"
+ " IN ((c1, c2, c3) AS (6, true),\n"
+ " (c2, c3, c4)))";
sql(sql).fails("In UNPIVOT, cannot derive type for axis 'A0'");
}
@Test void testMatchRecognizeWithDistinctAggregation() {
final String sql = "SELECT *\n"
+ "FROM emp\n"
+ "MATCH_RECOGNIZE (\n"
+ " ORDER BY ename\n"
+ " MEASURES\n"
+ " ^COUNT(DISTINCT A.deptno)^ AS deptno\n"
+ " PATTERN (A B)\n"
+ " DEFINE\n"
+ " A AS A.empno = 123\n"
+ ") AS T";
sql(sql).fails("DISTINCT/ALL not allowed with "
+ "COUNT\\(DISTINCT `A`\\.`DEPTNO`\\) function");
}
@Test void testIntervalTimeUnitEnumeration() {
// Since there is validation code relaying on the fact that the
// enumerated time unit ordinals in SqlIntervalQualifier starts with 0
// and ends with 5, this test is here to make sure that if someone
// changes how the time untis are setup, an early feedback will be
// generated by this test.
assertEquals(
0,
TimeUnit.YEAR.ordinal());
assertEquals(
1,
TimeUnit.MONTH.ordinal());
assertEquals(
2,
TimeUnit.DAY.ordinal());
assertEquals(
3,
TimeUnit.HOUR.ordinal());
assertEquals(
4,
TimeUnit.MINUTE.ordinal());
assertEquals(
5,
TimeUnit.SECOND.ordinal());
boolean b =
(TimeUnit.YEAR.ordinal()
< TimeUnit.MONTH.ordinal())
&& (TimeUnit.MONTH.ordinal()
< TimeUnit.DAY.ordinal())
&& (TimeUnit.DAY.ordinal()
< TimeUnit.HOUR.ordinal())
&& (TimeUnit.HOUR.ordinal()
< TimeUnit.MINUTE.ordinal())
&& (TimeUnit.MINUTE.ordinal()
< TimeUnit.SECOND.ordinal());
assertTrue(b);
}
@Test void testIntervalMonthsConversion() {
expr("INTERVAL '1' YEAR").assertInterval(is(12L));
expr("INTERVAL '5' MONTH").assertInterval(is(5L));
expr("INTERVAL '3-2' YEAR TO MONTH").assertInterval(is(38L));
expr("INTERVAL '-5-4' YEAR TO MONTH").assertInterval(is(-64L));
}
@Test void testIntervalMillisConversion() {
expr("INTERVAL '1' DAY").assertInterval(is(86_400_000L));
expr("INTERVAL '1' HOUR").assertInterval(is(3_600_000L));
expr("INTERVAL '1' MINUTE").assertInterval(is(60_000L));
expr("INTERVAL '1' SECOND").assertInterval(is(1_000L));
expr("INTERVAL '1:05' HOUR TO MINUTE").assertInterval(is(3_900_000L));
expr("INTERVAL '1:05' MINUTE TO SECOND").assertInterval(is(65_000L));
expr("INTERVAL '1 1' DAY TO HOUR").assertInterval(is(90_000_000L));
expr("INTERVAL '1 1:05' DAY TO MINUTE").assertInterval(is(90_300_000L));
expr("INTERVAL '1 1:05:03' DAY TO SECOND").assertInterval(is(90_303_000L));
expr("INTERVAL '1 1:05:03.12345' DAY TO SECOND")
.assertInterval(is(90_303_123L));
expr("INTERVAL '1.12345' SECOND").assertInterval(is(1_123L));
expr("INTERVAL '1:05.12345' MINUTE TO SECOND").assertInterval(is(65_123L));
expr("INTERVAL '1:05:03' HOUR TO SECOND").assertInterval(is(3903000L));
expr("INTERVAL '1:05:03.12345' HOUR TO SECOND")
.assertInterval(is(3_903_123L));
}
@Test void testDatetimePlusNullInterval() {
expr("TIME '8:8:8' + cast(NULL AS interval hour)").columnType("TIME(0)");
expr("TIME '8:8:8' + cast(NULL AS interval YEAR)").columnType("TIME(0)");
expr("TIMESTAMP '1990-12-12 12:12:12' + cast(NULL AS interval hour)")
.columnType("TIMESTAMP(0)");
expr("TIMESTAMP '1990-12-12 12:12:12' + cast(NULL AS interval YEAR)")
.columnType("TIMESTAMP(0)");
expr("cast(NULL AS interval hour) + TIME '8:8:8'").columnType("TIME(0)");
expr("cast(NULL AS interval YEAR) + TIME '8:8:8'").columnType("TIME(0)");
expr("cast(NULL AS interval hour) + TIMESTAMP '1990-12-12 12:12:12'")
.columnType("TIMESTAMP(0)");
expr("cast(NULL AS interval YEAR) + TIMESTAMP '1990-12-12 12:12:12'")
.columnType("TIMESTAMP(0)");
}
@Test void testIntervalLiterals() {
// First check that min, max, and defaults are what we expect
// (values used in subtests depend on these being true to
// accurately test bounds)
final RelDataTypeSystem typeSystem =
fixture().factory.getTypeFactory().getTypeSystem();
final RelDataTypeSystem defTypeSystem = RelDataTypeSystem.DEFAULT;
for (SqlTypeName typeName : SqlTypeName.INTERVAL_TYPES) {
assertThat(typeName.getMinPrecision(), is(1));
assertThat(typeSystem.getMaxPrecision(typeName), is(10));
assertThat(typeSystem.getDefaultPrecision(typeName), is(2));
assertThat(typeName.getMinScale(), is(0));
assertThat(typeSystem.getMaxScale(typeName), is(9));
assertThat(typeName.getDefaultScale(), is(6));
}
final SqlValidatorFixture f = fixture();
final IntervalTest.Fixture intervalFixture = new IntervalTest.Fixture() {
@Override public IntervalTest.Fixture2 expr(String s) {
return getFixture2(f.withExpr(s));
}
@Override public IntervalTest.Fixture2 wholeExpr(String s) {
return getFixture2(f.withExpr(s).withWhole(true));
}
private IntervalTest.Fixture2 getFixture2(SqlValidatorFixture f2) {
return new IntervalTest.Fixture2() {
@Override public void fails(String message) {
f2.fails(message);
}
@Override public void columnType(String expectedType) {
f2.columnType(expectedType);
}
@Override public IntervalTest.Fixture2 assertParse(String expected) {
return this;
}
};
}
};
new IntervalTest(intervalFixture).testAll();
}
@Test void testIntervalExpression() {
expr("interval 1 hour").columnType("INTERVAL HOUR NOT NULL");
expr("interval (2 + 3) month").columnType("INTERVAL MONTH NOT NULL");
expr("interval (cast(null as integer)) year").columnType("INTERVAL YEAR");
expr("interval (cast(null as integer)) year(2)")
.columnType("INTERVAL YEAR(2)");
expr("interval (date '1970-01-01') hour").withWhole(true)
.fails("Cannot apply 'INTERVAL' to arguments of type "
+ "'INTERVAL <DATE> <INTERVAL HOUR>'\\. Supported form\\(s\\): "
+ "'INTERVAL <NUMERIC> <DATETIME_INTERVAL>'");
expr("interval (nullif(true, true)) hour").withWhole(true)
.fails("Cannot apply 'INTERVAL' to arguments of type "
+ "'INTERVAL <BOOLEAN> <INTERVAL HOUR>'\\. Supported form\\(s\\): "
+ "'INTERVAL <NUMERIC> <DATETIME_INTERVAL>'");
expr("interval (interval '1' day) hour").withWhole(true)
.fails("Cannot apply 'INTERVAL' to arguments of type "
+ "'INTERVAL <INTERVAL DAY> <INTERVAL HOUR>'\\. "
+ "Supported form\\(s\\): "
+ "'INTERVAL <NUMERIC> <DATETIME_INTERVAL>'");
sql("select interval empno hour as h from emp")
.columnType("INTERVAL HOUR NOT NULL");
sql("select interval emp.mgr hour as h from emp")
.columnType("INTERVAL HOUR");
expr("interval '1' second(1, 0)")
.columnType("INTERVAL SECOND(1, 0) NOT NULL");
}
@Test void testIntervalOperators() {
expr("interval '1' hour + TIME '8:8:8'")
.columnType("TIME(0) NOT NULL");
expr("TIME '8:8:8' - interval '1' hour")
.columnType("TIME(0) NOT NULL");
expr("TIME '8:8:8' + interval '1' hour")
.columnType("TIME(0) NOT NULL");
expr("interval '1' day + interval '1' DAY(4)")
.columnType("INTERVAL DAY(4) NOT NULL");
expr("interval '1' day(5) + interval '1' DAY")
.columnType("INTERVAL DAY(5) NOT NULL");
expr("interval '1' day + interval '1' HOUR(10)")
.columnType("INTERVAL DAY TO HOUR NOT NULL");
expr("interval '1' day + interval '1' MINUTE")
.columnType("INTERVAL DAY TO MINUTE NOT NULL");
expr("interval '1' day + interval '1' second")
.columnType("INTERVAL DAY TO SECOND NOT NULL");
expr("interval '1:2' hour to minute + interval '1' second")
.columnType("INTERVAL HOUR TO SECOND NOT NULL");
expr("interval '1:3' hour to minute + interval '1 1:2:3.4' day to second")
.columnType("INTERVAL DAY TO SECOND NOT NULL");
expr("interval '1:2' hour to minute + interval '1 1' day to hour")
.columnType("INTERVAL DAY TO MINUTE NOT NULL");
expr("interval '1:2' hour to minute + interval '1 1' day to hour")
.columnType("INTERVAL DAY TO MINUTE NOT NULL");
expr("interval '1 2' day to hour + interval '1:1' minute to second")
.columnType("INTERVAL DAY TO SECOND NOT NULL");
expr("interval '1' year + interval '1' month")
.columnType("INTERVAL YEAR TO MONTH NOT NULL");
expr("interval '1' day - interval '1' hour")
.columnType("INTERVAL DAY TO HOUR NOT NULL");
expr("interval '1' year - interval '1' month")
.columnType("INTERVAL YEAR TO MONTH NOT NULL");
expr("interval '1' month - interval '1' year")
.columnType("INTERVAL YEAR TO MONTH NOT NULL");
wholeExpr("interval '1' year + interval '1' day")
.fails("(?s).*Cannot apply '\\+' to arguments of type "
+ "'<INTERVAL YEAR> \\+ <INTERVAL DAY>'.*");
wholeExpr("interval '1' month + interval '1' second")
.fails("(?s).*Cannot apply '\\+' to arguments of type "
+ "'<INTERVAL MONTH> \\+ <INTERVAL SECOND>'.*");
wholeExpr("interval '1' year - interval '1' day")
.fails("(?s).*Cannot apply '-' to arguments of type "
+ "'<INTERVAL YEAR> - <INTERVAL DAY>'.*");
wholeExpr("interval '1' month - interval '1' second")
.fails("(?s).*Cannot apply '-' to arguments of type "
+ "'<INTERVAL MONTH> - <INTERVAL SECOND>'.*");
// mixing between datetime and interval todo checkExpType("date
// '1234-12-12' + INTERVAL '1' month + interval '1' day","DATE"); todo
// checkExpFails("date '1234-12-12' + (INTERVAL '1' month +
// interval '1' day)","?");
// multiply operator
expr("interval '1' year * 2")
.columnType("INTERVAL YEAR NOT NULL");
expr("1.234*interval '1 1:2:3' day to second ")
.columnType("INTERVAL DAY TO SECOND NOT NULL");
// division operator
expr("interval '1' month / 0.1")
.columnType("INTERVAL MONTH NOT NULL");
expr("interval '1-2' year TO month / 0.1e-9")
.columnType("INTERVAL YEAR TO MONTH NOT NULL");
wholeExpr("1.234/interval '1 1:2:3' day to second")
.fails("(?s).*Cannot apply '/' to arguments of type "
+ "'<DECIMAL.4, 3.> / <INTERVAL DAY TO SECOND>'.*");
}
@Test void testTimestampAddAndDiff() {
List<String> tsi = ImmutableList.<String>builder()
.add("FRAC_SECOND")
.add("MICROSECOND")
.add("MINUTE")
.add("HOUR")
.add("DAY")
.add("WEEK")
.add("MONTH")
.add("QUARTER")
.add("YEAR")
.add("SQL_TSI_FRAC_SECOND")
.add("SQL_TSI_MICROSECOND")
.add("SQL_TSI_MINUTE")
.add("SQL_TSI_HOUR")
.add("SQL_TSI_DAY")
.add("SQL_TSI_WEEK")
.add("SQL_TSI_MONTH")
.add("SQL_TSI_QUARTER")
.add("SQL_TSI_YEAR")
.build();
List<String> functions = ImmutableList.<String>builder()
.add("timestampadd(%s, 12, current_timestamp)")
.add("timestampdiff(%s, current_timestamp, current_timestamp)")
.build();
for (String interval : tsi) {
for (String function : functions) {
expr(String.format(Locale.ROOT, function, interval)).ok();
}
}
expr("timestampadd(SQL_TSI_WEEK, 2, current_timestamp)")
.columnType("TIMESTAMP(0) NOT NULL");
expr("timestampadd(SQL_TSI_WEEK, 2, cast(null as timestamp))")
.columnType("TIMESTAMP(0)");
expr("timestampdiff(SQL_TSI_WEEK, current_timestamp, current_timestamp)")
.columnType("INTEGER NOT NULL");
expr("timestampdiff(SQL_TSI_WEEK, cast(null as timestamp), current_timestamp)")
.columnType("INTEGER");
expr("timestampadd(^incorrect^, 1, current_timestamp)")
.fails("'INCORRECT' is not a valid time frame");
expr("timestampdiff(^incorrect^, current_timestamp, current_timestamp)")
.fails("'INCORRECT' is not a valid time frame");
}
@Test void testTimestampAddNullInterval() {
expr("timestampadd(SQL_TSI_SECOND, cast(NULL AS INTEGER),"
+ " current_timestamp)")
.columnType("TIMESTAMP(0)");
expr("timestampadd(SQL_TSI_DAY, cast(NULL AS INTEGER),"
+ " current_timestamp)")
.columnType("TIMESTAMP(0)");
}
@Test void testNumericOperators() {
// unary operator
expr("- cast(1 as TINYINT)")
.columnType("TINYINT NOT NULL");
expr("+ cast(1 as INT)")
.columnType("INTEGER NOT NULL");
expr("- cast(1 as FLOAT)")
.columnType("FLOAT NOT NULL");
expr("+ cast(1 as DOUBLE)")
.columnType("DOUBLE NOT NULL");
expr("-1.643")
.columnType("DECIMAL(4, 3) NOT NULL");
expr("+1.643")
.columnType("DECIMAL(4, 3) NOT NULL");
// addition operator
expr("cast(1 as TINYINT) + cast(5 as INTEGER)")
.columnType("INTEGER NOT NULL");
expr("cast(null as SMALLINT) + cast(5 as BIGINT)")
.columnType("BIGINT");
expr("cast(1 as REAL) + cast(5 as INTEGER)")
.columnType("REAL NOT NULL");
expr("cast(null as REAL) + cast(5 as DOUBLE)")
.columnType("DOUBLE");
expr("cast(null as REAL) + cast(5 as REAL)")
.columnType("REAL");
expr("cast(1 as DECIMAL(5, 2)) + cast(1 as REAL)")
.columnType("DOUBLE NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) + cast(1 as DOUBLE)")
.columnType("DOUBLE NOT NULL");
expr("cast(null as DECIMAL(5, 2)) + cast(1 as DOUBLE)")
.columnType("DOUBLE");
expr("1.543 + 2.34")
.columnType("DECIMAL(5, 3) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) + cast(1 as BIGINT)")
.columnType("DECIMAL(19, 2) NOT NULL");
expr("cast(1 as NUMERIC(5, 2)) + cast(1 as INTEGER)")
.columnType("DECIMAL(13, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) + cast(null as SMALLINT)")
.columnType("DECIMAL(8, 2)");
expr("cast(1 as DECIMAL(5, 2)) + cast(1 as TINYINT)")
.columnType("DECIMAL(6, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) + cast(1 as DECIMAL(5, 2))")
.columnType("DECIMAL(6, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) + cast(1 as DECIMAL(6, 2))")
.columnType("DECIMAL(7, 2) NOT NULL");
expr("cast(1 as DECIMAL(4, 2)) + cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(7, 4) NOT NULL");
expr("cast(null as DECIMAL(4, 2)) + cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(7, 4)");
expr("cast(1 as DECIMAL(19, 2)) + cast(1 as DECIMAL(19, 2))")
.columnType("DECIMAL(19, 2) NOT NULL");
// subtraction operator
expr("cast(1 as TINYINT) - cast(5 as BIGINT)")
.columnType("BIGINT NOT NULL");
expr("cast(null as INTEGER) - cast(5 as SMALLINT)")
.columnType("INTEGER");
expr("cast(1 as INTEGER) - cast(5 as REAL)")
.columnType("REAL NOT NULL");
expr("cast(null as REAL) - cast(5 as DOUBLE)")
.columnType("DOUBLE");
expr("cast(null as REAL) - cast(5 as REAL)")
.columnType("REAL");
expr("cast(1 as DECIMAL(5, 2)) - cast(1 as DOUBLE)")
.columnType("DOUBLE NOT NULL");
expr("cast(null as DOUBLE) - cast(1 as DECIMAL)")
.columnType("DOUBLE");
expr("1.543 - 24")
.columnType("DECIMAL(14, 3) NOT NULL");
expr("cast(1 as DECIMAL(5)) - cast(1 as BIGINT)")
.columnType("DECIMAL(19, 0) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) - cast(1 as INTEGER)")
.columnType("DECIMAL(13, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) - cast(null as SMALLINT)")
.columnType("DECIMAL(8, 2)");
expr("cast(1 as DECIMAL(5, 2)) - cast(1 as TINYINT)")
.columnType("DECIMAL(6, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) - cast(1 as DECIMAL(7))")
.columnType("DECIMAL(10, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) - cast(1 as DECIMAL(6, 2))")
.columnType("DECIMAL(7, 2) NOT NULL");
expr("cast(1 as DECIMAL(4, 2)) - cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(7, 4) NOT NULL");
expr("cast(null as DECIMAL) - cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(19, 4)");
expr("cast(1 as DECIMAL(19, 2)) - cast(1 as DECIMAL(19, 2))")
.columnType("DECIMAL(19, 2) NOT NULL");
// multiply operator
expr("cast(1 as TINYINT) * cast(5 as INTEGER)")
.columnType("INTEGER NOT NULL");
expr("cast(null as SMALLINT) * cast(5 as BIGINT)")
.columnType("BIGINT");
expr("cast(1 as REAL) * cast(5 as INTEGER)")
.columnType("REAL NOT NULL");
expr("cast(null as REAL) * cast(5 as DOUBLE)")
.columnType("DOUBLE");
expr("cast(1 as DECIMAL(7, 3)) * 1.654")
.columnType("DECIMAL(11, 6) NOT NULL");
expr("cast(null as DECIMAL(7, 3)) * cast (1.654 as DOUBLE)")
.columnType("DOUBLE");
expr("cast(null as DECIMAL(5, 2)) * cast(1 as BIGINT)")
.columnType("DECIMAL(19, 2)");
expr("cast(1 as DECIMAL(5, 2)) * cast(1 as INTEGER)")
.columnType("DECIMAL(15, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) * cast(1 as SMALLINT)")
.columnType("DECIMAL(10, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) * cast(1 as TINYINT)")
.columnType("DECIMAL(8, 2) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) * cast(1 as DECIMAL(5, 2))")
.columnType("DECIMAL(10, 4) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) * cast(1 as DECIMAL(6, 2))")
.columnType("DECIMAL(11, 4) NOT NULL");
expr("cast(1 as DECIMAL(4, 2)) * cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(10, 6) NOT NULL");
expr("cast(null as DECIMAL(4, 2)) * cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(10, 6)");
expr("cast(1 as DECIMAL(4, 10)) * cast(null as DECIMAL(6, 10))")
.columnType("DECIMAL(10, 19)");
expr("cast(1 as DECIMAL(19, 2)) * cast(1 as DECIMAL(19, 2))")
.columnType("DECIMAL(19, 4) NOT NULL");
// divide operator
expr("cast(1 as TINYINT) / cast(5 as INTEGER)")
.columnType("INTEGER NOT NULL");
expr("cast(null as SMALLINT) / cast(5 as BIGINT)")
.columnType("BIGINT");
expr("cast(1 as REAL) / cast(5 as INTEGER)")
.columnType("REAL NOT NULL");
expr("cast(null as REAL) / cast(5 as DOUBLE)")
.columnType("DOUBLE");
expr("cast(1 as DECIMAL(7, 3)) / 1.654")
.columnType("DECIMAL(15, 8) NOT NULL");
expr("cast(null as DECIMAL(7, 3)) / cast (1.654 as DOUBLE)")
.columnType("DOUBLE");
expr("cast(null as DECIMAL(5, 2)) / cast(1 as BIGINT)")
.columnType("DECIMAL(19, 16)");
expr("cast(1 as DECIMAL(5, 2)) / cast(1 as INTEGER)")
.columnType("DECIMAL(16, 13) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) / cast(1 as SMALLINT)")
.columnType("DECIMAL(11, 8) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) / cast(1 as TINYINT)")
.columnType("DECIMAL(9, 6) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) / cast(1 as DECIMAL(5, 2))")
.columnType("DECIMAL(13, 8) NOT NULL");
expr("cast(1 as DECIMAL(5, 2)) / cast(1 as DECIMAL(6, 2))")
.columnType("DECIMAL(14, 9) NOT NULL");
expr("cast(1 as DECIMAL(4, 2)) / cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(15, 9) NOT NULL");
expr("cast(null as DECIMAL(4, 2)) / cast(1 as DECIMAL(6, 4))")
.columnType("DECIMAL(15, 9)");
expr("cast(1 as DECIMAL(4, 10)) / cast(null as DECIMAL(6, 19))")
.columnType("DECIMAL(19, 6)");
expr("cast(1 as DECIMAL(19, 2)) / cast(1 as DECIMAL(19, 2))")
.columnType("DECIMAL(19, 0) NOT NULL");
expr("4/3")
.columnType("INTEGER NOT NULL");
expr("-4.0/3")
.columnType("DECIMAL(13, 12) NOT NULL");
expr("4/3.0")
.columnType("DECIMAL(17, 6) NOT NULL");
expr("cast(2.3 as float)/3")
.columnType("FLOAT NOT NULL");
// null
expr("cast(2.3 as float)/null")
.columnType("FLOAT");
}
@Test void testFloorCeil() {
expr("floor(cast(null as tinyint))")
.columnType("TINYINT");
expr("floor(1.2)")
.columnType("DECIMAL(2, 0) NOT NULL");
expr("floor(1)")
.columnType("INTEGER NOT NULL");
expr("floor(1.2e-2)")
.columnType("DOUBLE NOT NULL");
expr("floor(interval '2' day)")
.columnType("INTERVAL DAY NOT NULL");
expr("ceil(cast(null as bigint))")
.columnType("BIGINT");
expr("ceil(1.2)")
.columnType("DECIMAL(2, 0) NOT NULL");
expr("ceil(1)")
.columnType("INTEGER NOT NULL");
expr("ceil(1.2e-2)")
.columnType("DOUBLE NOT NULL");
expr("ceil(interval '2' second)")
.columnType("INTERVAL SECOND NOT NULL");
}
/** Tests that EXTRACT, FLOOR, CEIL functions accept abbreviations for
* time units (such as "Y" for "YEAR").
*
* <p>This used to be accomplished via the now deprecated
* {@code timeUnitCodes} method in {@link SqlParser.Config}, and is now
* accomplished via
* {@link RelDataTypeSystem#deriveTimeFrameSet(TimeFrameSet)}. */
@Test void testTimeUnitCodes() {
final Map<String, TimeUnit> simpleCodes =
ImmutableMap.<String, TimeUnit>builder()
.put("Y", TimeUnit.YEAR)
.put("M", TimeUnit.MONTH)
.put("D", TimeUnit.DAY)
.put("H", TimeUnit.HOUR)
.put("N", TimeUnit.MINUTE)
.put("S", TimeUnit.SECOND)
.build();
// Time unit abbreviations for Microsoft SQL Server
final Map<String, TimeUnit> mssqlCodes =
ImmutableMap.<String, TimeUnit>builder()
.put("Y", TimeUnit.YEAR)
.put("YY", TimeUnit.YEAR)
.put("YYYY", TimeUnit.YEAR)
.put("Q", TimeUnit.QUARTER)
.put("QQ", TimeUnit.QUARTER)
.put("M", TimeUnit.MONTH)
.put("MM", TimeUnit.MONTH)
.put("W", TimeUnit.WEEK)
.put("WK", TimeUnit.WEEK)
.put("WW", TimeUnit.WEEK)
.put("DY", TimeUnit.DOY)
.put("DW", TimeUnit.DOW)
.put("D", TimeUnit.DAY)
.put("DD", TimeUnit.DAY)
.put("H", TimeUnit.HOUR)
.put("HH", TimeUnit.HOUR)
.put("N", TimeUnit.MINUTE)
.put("MI", TimeUnit.MINUTE)
.put("S", TimeUnit.SECOND)
.put("SS", TimeUnit.SECOND)
.put("MS", TimeUnit.MILLISECOND)
.build();
checkTimeUnitCodes(ImmutableMap.of());
checkTimeUnitCodes(simpleCodes);
checkTimeUnitCodes(mssqlCodes);
}
/** Checks parsing of built-in functions that accept time unit
* abbreviations.
*
* <p>For example, {@code EXTRACT(Y FROM orderDate)} is using
* "Y" as an abbreviation for "YEAR".
*
* <p>Override if your parser supports more such functions. */
protected void checkTimeUnitCodes(Map<String, TimeUnit> timeUnitCodes) {
SqlValidatorFixture f = fixture()
.withFactory(tf ->
tf.withTypeSystem(typeSystem ->
new DelegatingTypeSystem(typeSystem) {
@Override public TimeFrameSet deriveTimeFrameSet(
TimeFrameSet frameSet) {
TimeFrameSet.Builder b = TimeFrameSet.builder();
b.addAll(frameSet);
timeUnitCodes.forEach((name, unit) ->
b.addAlias(name, unit.name()));
return b.build();
}
}));
final String ts = "TIMESTAMP '2020-08-27 18:16:43'";
BiConsumer<String, TimeUnit> validConsumer = (abbrev, timeUnit) -> {
f.withSql("select extract(" + abbrev + " from " + ts + ")").ok();
f.withSql("select floor(" + ts + " to " + abbrev + ")").ok();
f.withSql("select ceil(" + ts + " to " + abbrev + ")").ok();
};
BiConsumer<String, TimeUnit> invalidConsumer = (abbrev, timeUnit) -> {
final String upAbbrev = abbrev.toUpperCase(Locale.ROOT);
String message = "'" + upAbbrev + "' is not a valid time frame";
f.withSql("select extract(^" + abbrev + "^ from " + ts + ")")
.fails(message);
f.withSql("SELECT FLOOR(" + ts + " to ^" + abbrev + "^)")
.fails(message);
f.withSql("SELECT CEIL(" + ts + " to ^" + abbrev + "^)")
.fails(message);
};
// Check that each valid code passes each query that it should.
timeUnitCodes.forEach(validConsumer);
// If "M" is a valid code then "m" should be also.
timeUnitCodes.forEach((abbrev, timeUnit) ->
validConsumer.accept(abbrev.toLowerCase(Locale.ROOT), timeUnit));
// Check that invalid codes generate the right error messages.
final Map<String, TimeUnit> invalidCodes =
ImmutableMap.of("A", TimeUnit.YEAR,
"a", TimeUnit.YEAR);
invalidCodes.forEach(invalidConsumer);
}
/** Tests parsing of built-in functions that accept time unit
* "WEEK(WEEKDAY)". */
@Test void testWeekdayCustomTimeFrames() {
SqlValidatorFixture f = fixture()
.withOperatorTable(operatorTableFor(SqlLibrary.BIG_QUERY));
// Check that each valid code passes each query that it should.
final String ds = "DATE '2022-12-25'";
Consumer<String> validConsumer = weekday -> {
f.withSql("select date_trunc(" + ds + ", " + weekday + ")").ok();
};
validConsumer.accept("WEEK");
validConsumer.accept("WEEK(SUNDAY)");
validConsumer.accept("WEEK(MONDAY)");
validConsumer.accept("WEEK(TUESDAY)");
validConsumer.accept("WEEK(WEDNESDAY)");
validConsumer.accept("WEEK(THURSDAY)");
validConsumer.accept("WEEK(FRIDAY)");
validConsumer.accept("WEEK(SUNDAY)");
// Check that each invalid code fails each query that it should.
Consumer<String> invalidConsumer = weekday -> {
String errorMessage = "Column '" + weekday + "' not found in any table";
f.withSql("select date_trunc(" + ds + ", ^" + weekday + "^)")
.fails(errorMessage);
};
invalidConsumer.accept("A");
}
public void checkWinFuncExpWithWinClause(
String sql,
String expectedMsgPattern) {
winExp(sql).fails(expectedMsgPattern);
}
// test window partition clause. See SQL 2003 specification for detail
@Disabled
void testWinPartClause() {
win("window w as (w2 order by deptno), w2 as (^rang^e 100 preceding)")
.fails("Referenced window cannot have framing declarations");
// Test specified collation, window clause syntax rule 4,5.
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-820">[CALCITE-820]
* Validate that window functions have OVER clause</a>, and
* <a href="https://issues.apache.org/jira/browse/CALCITE-1340">[CALCITE-1340]
* Window aggregates give invalid errors</a>. */
@Test void testWindowFunctionsWithoutOver() {
winSql("select sum(empno)\n"
+ "from emp\n"
+ "group by deptno\n"
+ "order by ^row_number()^")
.fails("OVER clause is necessary for window functions");
winSql("select ^rank()^\n"
+ "from emp")
.fails("OVER clause is necessary for window functions");
// With [CALCITE-1340], the validator would see RANK without OVER,
// mistakenly think this is an aggregate query, and wrongly complain
// about the PARTITION BY: "Expression 'DEPTNO' is not being grouped"
winSql("select cume_dist() over w , ^rank()^\n"
+ "from emp\n"
+ "window w as (partition by deptno order by deptno)")
.fails("OVER clause is necessary for window functions");
winSql("select ^nth_value(sal, 2)^\n"
+ "from emp")
.fails("OVER clause is necessary for window functions");
}
@Test void testOverInPartitionBy() {
winSql("select sum(deptno) over ^(partition by sum(deptno)\n"
+ "over(order by deptno))^ from emp")
.fails("PARTITION BY expression should not contain OVER clause");
winSql("select sum(deptno) over w\n"
+ "from emp\n"
+ "window w as ^(partition by sum(deptno) over(order by deptno))^")
.fails("PARTITION BY expression should not contain OVER clause");
}
@Test void testOverInOrderBy() {
winSql("select sum(deptno) over ^(order by sum(deptno)\n"
+ "over(order by deptno))^ from emp")
.fails("ORDER BY expression should not contain OVER clause");
winSql("select sum(deptno) over w\n"
+ "from emp\n"
+ "window w as ^(order by sum(deptno) over(order by deptno))^")
.fails("ORDER BY expression should not contain OVER clause");
}
@Test void testAggregateFunctionInOver() {
final String sql = "select sum(deptno) over (order by count(empno))\n"
+ "from emp\n"
+ "group by deptno";
winSql(sql).ok();
final String sql2 = "select sum(^empno^) over (order by count(empno))\n"
+ "from emp\n"
+ "group by deptno";
winSql(sql2).fails("Expression 'EMPNO' is not being grouped");
}
@Test void testAggregateInsideOverClause() {
final String sql = "select ^empno^,\n"
+ " sum(empno) over (partition by min(sal)) empno_sum\n"
+ "from emp";
sql(sql).fails("Expression 'EMPNO' is not being grouped");
final String sql2 = "select ^empno^,\n"
+ " sum(empno) over (partition by min(sal)) empno_sum\n"
+ "from emp\n"
+ "group by empno";
sql(sql2).ok();
}
@Test void testAggregateInsideOverClause2() {
final String sql = "select ^empno^,\n"
+ " sum(empno) over ()\n"
+ " + sum(empno) over (partition by min(sal)) empno_sum\n"
+ "from emp";
sql(sql).fails("Expression 'EMPNO' is not being grouped");
}
@Test void testWindowFunctions() {
// SQL 03 Section 6.10
// Window functions may only appear in the <select list> of a
// <query specification> or <select statement: single row>,
// or the <order by clause> of a simple table query.
// See 4.15.3 for detail
winSql("select *\n"
+ " from emp\n"
+ " where ^sum(sal) over (partition by deptno\n"
+ " order by empno\n"
+ " rows 3 preceding)^ > 10")
.fails("Windowed aggregate expression is illegal in WHERE clause");
winSql("select *\n"
+ " from emp\n"
+ " group by ename, ^sum(sal) over (partition by deptno\n"
+ " order by empno\n"
+ " rows 3 preceding)^ + 10\n"
+ "order by deptno")
.fails("Windowed aggregate expression is illegal in GROUP BY clause");
winSql("select *\n"
+ " from emp\n"
+ " join dept on emp.deptno = dept.deptno\n"
+ " and ^sum(sal) over (partition by emp.deptno\n"
+ " order by empno\n"
+ " rows 3 preceding)^ = dept.deptno + 40\n"
+ "order by deptno")
.fails("Windowed aggregate expression is illegal in ON clause");
// rule 3, a)
winSql("select sal from emp\n"
+ "order by sum(sal) over (partition by deptno order by deptno)")
.ok();
// scope reference
// rule 4,
// valid window functions
winExp("sum(sal)").ok();
}
@Test void testWindowFunctions2() {
List<String> defined =
asList("CUME_DIST", "DENSE_RANK", "PERCENT_RANK", "RANK",
"ROW_NUMBER");
if (Bug.TODO_FIXED) {
sql("select rank() over (order by deptno) from emp")
.columnType("INTEGER NOT NULL");
}
winSql("select rank() over w from emp\n"
+ "window w as ^(partition by sal)^, w2 as (w order by deptno)")
.fails(RANK_REQUIRES_ORDER_BY);
winSql("select rank() over w2 from emp\n"
+ "window w as (partition by sal), w2 as (w order by deptno)").ok();
// row_number function
winExp("row_number() over (order by deptno)").ok();
winExp("row_number() over (partition by deptno)").ok();
winExp("row_number() over ()").ok();
winExp("row_number() over (order by deptno ^rows^ 2 preceding)")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
winExp("row_number() over (order by deptno ^range^ 2 preceding)")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
// rank function type
if (defined.contains("DENSE_RANK")) {
winExp("^dense_rank()^")
.fails("OVER clause is necessary for window functions");
} else {
checkWinFuncExpWithWinClause("^dense_rank()^",
"Function 'DENSE_RANK\\(\\)' is not defined");
}
winExp("rank() over (order by empno)").ok();
winExp("percent_rank() over (order by empno)").ok();
winExp("cume_dist() over (order by empno)").ok();
winExp("nth_value(sal, 2) over (order by empno)").ok();
// rule 6a
// ORDER BY required with RANK & DENSE_RANK
winSql("select rank() over ^(partition by deptno)^ from emp")
.fails(RANK_REQUIRES_ORDER_BY);
winSql("select dense_rank() over ^(partition by deptno)^ from emp ")
.fails(RANK_REQUIRES_ORDER_BY);
winSql("select rank() over w from emp window w as ^(partition by deptno)^")
.fails(RANK_REQUIRES_ORDER_BY);
winSql("select dense_rank() over w from emp\n"
+ "window w as ^(partition by deptno)^")
.fails(RANK_REQUIRES_ORDER_BY);
// rule 6b
// Framing not allowed with RANK & DENSE_RANK functions
// window framing defined in window clause
winSql("select rank() over w from emp\n"
+ "window w as (order by empno ^rows^ 2 preceding )")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
winSql("select dense_rank() over w from emp\n"
+ "window w as (order by empno ^rows^ 2 preceding)")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
if (defined.contains("PERCENT_RANK")) {
winSql("select percent_rank() over w from emp\n"
+ "window w as (order by empno)")
.ok();
winSql("select percent_rank() over w from emp\n"
+ "window w as (order by empno ^rows^ 2 preceding)")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
winSql("select percent_rank() over w from emp\n"
+ "window w as ^(partition by empno)^")
.fails(RANK_REQUIRES_ORDER_BY);
} else {
checkWinFuncExpWithWinClause("^percent_rank()^",
"Function 'PERCENT_RANK\\(\\)' is not defined");
}
if (defined.contains("CUME_DIST")) {
winSql("select cume_dist() over w from emp\n"
+ "window w as ^(rows 2 preceding)^")
.fails(RANK_REQUIRES_ORDER_BY);
winSql("select cume_dist() over w from emp\n"
+ "window w as (order by empno ^rows^ 2 preceding)")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
winSql("select cume_dist() over w from emp window w as (order by empno)")
.ok();
winSql("select cume_dist() over (order by empno) from emp ").ok();
} else {
checkWinFuncExpWithWinClause("^cume_dist()^",
"Function 'CUME_DIST\\(\\)' is not defined");
}
// window framing defined in in-line window
winSql("select rank() over (order by empno ^range^ 2 preceding ) from emp ")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
winSql("select dense_rank() over (order by empno ^rows^ 2 preceding ) from emp ")
.fails(ROW_RANGE_NOT_ALLOWED_WITH_RANK);
if (defined.contains("PERCENT_RANK")) {
winSql("select percent_rank() over (order by empno) from emp").ok();
}
// invalid column reference
checkWinFuncExpWithWinClause("sum(^invalidColumn^)",
"Column 'INVALIDCOLUMN' not found in any table");
// invalid window functions
checkWinFuncExpWithWinClause("^invalidFun(sal)^",
"No match found for function signature INVALIDFUN\\(<NUMERIC>\\)");
// 6.10 rule 10. no distinct allowed aggregate function
// Fails in parser.
// checkWinFuncExpWithWinClause(" sum(distinct sal) over w ", null);
// 7.11 rule 10c
winSql("select sum(sal) over (w partition by ^deptno^)\n"
+ " from emp window w as (order by empno rows 2 preceding )")
.fails("PARTITION BY not allowed with existing window reference");
// 7.11 rule 10d
winSql("select sum(sal) over (w order by ^empno^)\n"
+ " from emp window w as (order by empno rows 2 preceding )")
.fails("ORDER BY not allowed in both base and referenced windows");
// 7.11 rule 10e
winSql("select sum(sal) over (w)\n"
+ " from emp window w as (order by empno ^rows^ 2 preceding )")
.fails("Referenced window cannot have framing declarations");
// Empty window is OK for functions that don't require ordering.
winSql("select sum(sal) over () from emp").ok();
winSql("select sum(sal) over w from emp window w as ()").ok();
winSql("select count(*) over () from emp").ok();
winSql("select count(*) over w from emp window w as ()").ok();
winSql("select rank() over ^()^ from emp")
.fails(RANK_REQUIRES_ORDER_BY);
winSql("select rank() over w from emp window w as ^()^")
.fails(RANK_REQUIRES_ORDER_BY);
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-883">[CALCITE-883]
* Give error if the aggregate function don't support null treatment</a>. */
@Test void testWindowFunctionsIgnoreNulls() {
winSql("select lead(sal, 4) over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select lead(sal, 4) IGNORE NULLS over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select lag(sal, 4) over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select lag(sal, 4) IGNORE NULLS over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select first_value(sal) over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select first_value(sal) IGNORE NULLS over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select last_value(sal) over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select last_value(sal) IGNORE NULLS over (w)\n"
+ " from emp window w as (order by empno)").ok();
winSql("select ^sum(sal)^ IGNORE NULLS over (w)\n"
+ " from emp window w as (order by empno)")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'SUM'");
winSql("select ^count(sal)^ IGNORE NULLS over (w)\n"
+ " from emp window w as (order by empno)")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'COUNT'");
winSql("select ^avg(sal)^ IGNORE NULLS\n"
+ " from emp")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'AVG'");
winSql("select ^abs(sal)^ IGNORE NULLS\n"
+ " from emp")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'ABS'");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-883">[CALCITE-883]
* Give error if the aggregate function don't support null treatment</a>. */
@Test void testWindowFunctionsRespectNulls() {
winSql("select lead(sal, 4) over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select lead(sal, 4) RESPECT NULLS over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select lag(sal, 4) over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select lag(sal, 4) RESPECT NULLS over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select first_value(sal) over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select first_value(sal) RESPECT NULLS over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select last_value(sal) over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select last_value(sal) RESPECT NULLS over (w)\n"
+ "from emp window w as (order by empno)").ok();
winSql("select ^sum(sal)^ RESPECT NULLS over (w)\n"
+ "from emp window w as (order by empno)")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'SUM'");
winSql("select ^count(sal)^ RESPECT NULLS over (w)\n"
+ "from emp window w as (order by empno)")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'COUNT'");
winSql("select ^avg(sal)^ RESPECT NULLS\n"
+ "from emp")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'AVG'");
winSql("select ^abs(sal)^ RESPECT NULLS\n"
+ "from emp")
.fails("Cannot specify IGNORE NULLS or RESPECT NULLS following 'ABS'");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1954">[CALCITE-1954]
* Column from outer join should be null, whether or not it is aliased</a>. */
@Test void testLeftOuterJoinWithAlias() {
final String query = "select *\n"
+ "from (select row_number() over (order by sal) from emp) as emp1(r1)\n"
+ "left outer join\n"
+ "(select dense_rank() over(order by sal) from emp) as emp2(r2)\n"
+ "on (emp1.r1 = emp2.r2)";
// In this case, R2 is nullable in the join since we have a left outer join.
final String type = "RecordType(BIGINT NOT NULL R1, BIGINT R2) NOT NULL";
sql(query).type(type);
// Similar query, without "AS t(c)"
final String query2 = "select *\n"
+ "from (select row_number() over (order by sal) as r1 from emp) as emp1\n"
+ "left outer join\n"
+ "(select dense_rank() over(order by sal) as r2 from emp) as emp2\n"
+ "on (emp1.r1 = emp2.r2)";
sql(query2).type(type);
// Similar query, without "AS t"
final String query3 = "select *\n"
+ "from (select row_number() over (order by sal) as r1 from emp)\n"
+ "left outer join\n"
+ "(select dense_rank() over(order by sal) as r2 from emp)\n"
+ "on r1 = r2";
sql(query3).type(type);
}
@Test void testInvalidWindowFunctionWithGroupBy() {
sql("select max(^empno^) over () from emp\n"
+ "group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select max(deptno) over (partition by ^empno^) from emp\n"
+ "group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select rank() over (order by ^empno^) from emp\n"
+ "group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
}
@Test void testInlineWinDef() {
// the <window specification> used by windowed agg functions is
// fully defined in SQL 03 Std. section 7.1 <window clause>
sql("select sum(sal) over (partition by deptno order by empno)\n"
+ "from emp order by empno").ok();
winExp2("sum(sal) OVER ("
+ "partition by deptno "
+ "order by empno "
+ "rows 2 preceding )").ok();
winExp2("sum(sal) OVER ("
+ "order by 1 "
+ "rows 2 preceding )").ok();
winExp2("sum(sal) OVER ("
+ "order by 'b' "
+ "rows 2 preceding )").ok();
winExp2("sum(sal) over ("
+ "partition by deptno "
+ "order by 1+1 rows 26 preceding)").ok();
winExp2("sum(sal) over (order by deptno rows unbounded preceding)").ok();
winExp2("sum(sal) over (order by deptno rows current row)").ok();
winExp2("sum(sal) over ^("
+ "order by deptno "
+ "rows between unbounded preceding and unbounded following)^").ok();
winExp2("sum(sal) over ^("
+ "order by deptno "
+ "rows between CURRENT ROW and unbounded following)^").ok();
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between unbounded preceding and CURRENT ROW)").ok();
// logical current row/current row
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between CURRENT ROW and CURRENT ROW)").ok();
// physical current row/current row
winExp2("sum(sal) over ("
+ "order by deptno "
+ "range between CURRENT ROW and CURRENT ROW)").ok();
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between 2 preceding and CURRENT ROW)").ok();
winExp("sum(sal) OVER (w "
+ "rows 2 preceding )").ok();
winExp2("sum(sal) over (order by deptno range 2.0 preceding)").ok();
// compound order by with literal bounds
winExp2("sum(sal) over "
+ "(order by sal,deptno "
+ "range between current row and unbounded following)").ok();
winExp2("sum(sal) over "
+ "(order by sal,deptno "
+ "range between current row and current row)").ok();
winExp2("sum(sal) over "
+ "(order by sal,deptno "
+ "range between unbounded preceding and current row)").ok();
winExp2("sum(sal) over "
+ "(order by sal,deptno "
+ "range between unbounded preceding and unbounded following)").ok();
// Range without order by with only literal bounds
winExp2("sum(sal) over "
+ "(partition by sal,deptno "
+ "range between unbounded preceding and unbounded following)").ok();
// RANGE with non-difference type order by and literal bounds
winExp2("sum(sal) over "
+ "(order by ename "
+ "range between unbounded preceding and current row)").ok();
// Range without order by with only preceding / following should fail
winExp2("sum(sal) over "
+ "^(partition by sal "
+ "range between 3 preceding and unbounded following)^")
.fails("Window specification must contain an ORDER BY clause");
winExp2("sum(sal) over "
+ "^(partition by sal "
+ "range between current row and 3 following)^")
.fails("Window specification must contain an ORDER BY clause");
// Failure mode tests
winExp2("sum(sal) over (order by deptno "
+ "rows between ^UNBOUNDED FOLLOWING^ and unbounded preceding)")
.fails("UNBOUNDED FOLLOWING cannot be specified for the lower frame boundary");
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between 2 preceding and ^UNBOUNDED PRECEDING^)")
.fails("UNBOUNDED PRECEDING cannot be specified for the upper frame boundary");
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between CURRENT ROW and ^2 preceding^)")
.fails("Upper frame boundary cannot be PRECEDING when lower boundary is CURRENT ROW");
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between 2 following and ^CURRENT ROW^)")
.fails("Upper frame boundary cannot be CURRENT ROW when lower boundary is FOLLOWING");
winExp2("sum(sal) over ("
+ "order by deptno "
+ "rows between 2 following and ^2 preceding^)")
.fails("Upper frame boundary cannot be PRECEDING when lower boundary is FOLLOWING");
winExp2("sum(sal) over ("
+ "order by deptno "
+ "RANGE BETWEEN ^INTERVAL '1' SECOND^ PRECEDING AND INTERVAL '1' SECOND FOLLOWING)")
.fails("Data Type mismatch between ORDER BY and RANGE clause");
winExp2("sum(sal) over ("
+ "order by empno "
+ "RANGE BETWEEN ^INTERVAL '1' SECOND^ PRECEDING AND INTERVAL '1' SECOND FOLLOWING)")
.fails("Data Type mismatch between ORDER BY and RANGE clause");
winExp2("sum(sal) over (order by deptno, empno ^range^ 2 preceding)")
.fails("RANGE clause cannot be used with compound ORDER BY clause");
winExp2("sum(sal) over ^(partition by deptno range 5 preceding)^")
.fails("Window specification must contain an ORDER BY clause");
winExp2("sum(sal) over ^w1^")
.fails("Window 'W1' not found");
winExp2("sum(sal) OVER (^w1^ "
+ "partition by deptno "
+ "order by empno "
+ "rows 2 preceding )")
.fails("Window 'W1' not found");
}
@Test void testPartitionByExpr() {
winExp2("sum(sal) over (partition by empno + deptno order by empno range 5 preceding)")
.ok();
winSql("select "
+ "sum(sal) over (partition by ^empno + ename^ order by empno range 5 preceding)"
+ " from emp")
.withTypeCoercion(false)
.fails("(?s)Cannot apply '\\+' to arguments of type '<INTEGER> \\+ <VARCHAR\\(20\\)>'.*");
winExp2("sum(sal) over (partition by empno + ename order by empno range 5 preceding)");
}
@Test void testWindowClause() {
// -----------------------------------
// -- positive testings --
// -----------------------------------
// correct syntax:
winExp("sum(sal) as sumsal").ok();
win("window w as (partition by sal order by deptno rows 2 preceding)")
.ok();
// define window on an existing window
win("window w as (order by sal), w1 as (w)").ok();
// -----------------------------------
// -- negative testings --
// -----------------------------------
// Test fails in parser
// checkWinClauseExp("window foo.w as (range 100 preceding) "+
// "Window name must be a simple identifier\");
// rule 11
// a)
// missing window order clause.
win("window w as ^(range 100 preceding)^")
.fails("Window specification must contain an ORDER BY clause");
// order by number
win("window w as (order by sal range 100 preceding)").ok();
// order by date
win("window w as (order by hiredate range ^100^ preceding)")
.fails("Data Type mismatch between ORDER BY and RANGE clause");
// order by string, should fail
win("window w as (order by ename range ^100^ preceding)")
.fails("Data type of ORDER BY prohibits use of RANGE clause");
// todo: interval test ???
// b)
// valid
win("window w as (rows 2 preceding)").ok();
// invalid tests exact numeric for the unsigned value specification The
// following two test fail as they should but in the parser: JR not
// anymore now the validator kicks out
win("window w as (rows ^-2.5^ preceding)")
.fails("ROWS value must be a non-negative integral constant");
win("window w as (rows ^-2^ preceding)")
.fails("ROWS value must be a non-negative integral constant");
// This test should fail as per 03 Std. but we pass it and plan
// to apply the FLOOR function before window processing
win("window w as (rows ^2.5^ preceding)")
.fails("ROWS value must be a non-negative integral constant");
// CALCITE-5931 - Allow integers like 1.00 in window frame
win("window w as (rows 2.00 preceding)").ok();
// -----------------------------------
// -- negative testings --
// -----------------------------------
// reference undefined xyz column
win("window w as (partition by ^xyz^)")
.fails("Column 'XYZ' not found in any table");
// window definition is empty when applied to unsorted table
win("window w as ^( /* boo! */ )^").ok();
// duplicate window name
win("window w as (order by empno), ^w^ as (order by empno)")
.fails("Duplicate window names not allowed");
win("window win1 as (order by empno), ^win1^ as (order by empno)")
.fails("Duplicate window names not allowed");
// syntax rule 6
sql("select min(sal) over (order by deptno) from emp group by deptno,sal")
.ok();
sql("select min(sal) over (order by ^deptno^) from emp group by sal")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select min(sal) over\n"
+ "(partition by comm order by deptno) from emp group by deptno,sal,comm")
.ok();
sql("select min(sal) over\n"
+ "(partition by ^comm^ order by deptno) from emp group by deptno,sal")
.fails("Expression 'COMM' is not being grouped");
// syntax rule 7
win("window w as ^(order by rank() over (order by sal))^")
.fails("ORDER BY expression should not contain OVER clause");
// ------------------------------------
// ---- window frame between tests ----
// ------------------------------------
// bound 1 shall not specify UNBOUNDED FOLLOWING
win("window w as (rows between ^unbounded following^ and 5 following)")
.fails("UNBOUNDED FOLLOWING cannot be specified for the lower frame boundary");
// bound 2 shall not specify UNBOUNDED PRECEDING
win("window w as ("
+ "order by deptno "
+ "rows between 2 preceding and ^UNBOUNDED PRECEDING^)")
.fails("UNBOUNDED PRECEDING cannot be specified for the upper frame boundary");
win("window w as ("
+ "order by deptno "
+ "rows between 2 following and ^2 preceding^)")
.fails("Upper frame boundary cannot be PRECEDING when lower boundary is FOLLOWING");
win("window w as ("
+ "order by deptno "
+ "rows between CURRENT ROW and ^2 preceding^)")
.fails("Upper frame boundary cannot be PRECEDING when lower boundary is CURRENT ROW");
win("window w as ("
+ "order by deptno "
+ "rows between 2 following and ^CURRENT ROW^)")
.fails("Upper frame boundary cannot be CURRENT ROW when lower boundary is FOLLOWING");
// Sql '03 rule 10 c) assertExceptionIsThrown("select deptno as d, sal
// as s from emp window w as (partition by deptno order by sal), w2 as
// (w partition by deptno)", null); checkWinClauseExp("window w as
// (partition by sal order by deptno), w2 as (w partition by sal)",
// null); d) valid because existing window does not have an ORDER BY
// clause
win("window w as (w2 range 2 preceding), w2 as (order by sal)").ok();
win("window w as ^(partition by sal)^, w2 as (w order by deptno)").ok();
win("window w as (w2 partition by ^sal^), w2 as (order by deptno)")
.fails("PARTITION BY not allowed with existing window reference");
win("window w as (partition by sal order by deptno), w2 as (w order by ^deptno^)")
.fails("ORDER BY not allowed in both base and referenced windows");
// e)
win("window w as (w2 order by deptno), w2 as (^range^ 100 preceding)")
.fails("Referenced window cannot have framing declarations");
// rule 12, todo: test scope of window assertExceptionIsThrown("select
// deptno as d from emp window d as (partition by deptno)", null);
// rule 13
win("window w as (order by sal)").ok();
win("window w as (order by ^non_exist_col^)")
.fails("Column 'NON_EXIST_COL' not found in any table");
win("window w as (partition by ^non_exist_col^ order by sal)")
.fails("Column 'NON_EXIST_COL' not found in any table");
}
@Test void testWindowClause2() {
// 7.10 syntax rule 2 <new window name> NWN1 shall not be contained in
// the scope of another <new window name> NWN2 such that NWN1 and NWN2
// are equivalent.
win("window\n"
+ "w as (partition by deptno order by empno rows 2 preceding),\n"
+ "w2 as ^(partition by deptno order by empno rows 2 preceding)^\n")
.fails("Duplicate window specification not allowed in the same window clause");
}
@Test void testWindowClauseWithSubQuery() {
sql("select * from\n"
+ "( select sum(empno) over w, sum(deptno) over w from emp\n"
+ "window w as (order by hiredate range interval '1' minute preceding))").ok();
sql("select * from\n"
+ "( select sum(empno) over w, sum(deptno) over w, hiredate from emp)\n"
+ "window w as (order by hiredate range interval '1' minute preceding)").ok();
sql("select * from\n"
+ "( select sum(empno) over w, sum(deptno) over w from emp)\n"
+ "window w as (order by ^hiredate^ range interval '1' minute preceding)")
.fails("Column 'HIREDATE' not found in any table");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-754">[CALCITE-754]
* Validator error when resolving OVER clause of JOIN query</a>. */
@Test void testPartitionByColumnInJoinAlias() {
sql("select sum(1) over(partition by t1.ename)\n"
+ "from emp t1, emp t2")
.ok();
sql("select sum(1) over(partition by emp.ename)\n"
+ "from emp, dept")
.ok();
sql("select sum(1) over(partition by ^deptno^)\n"
+ "from emp, dept")
.fails("Column 'DEPTNO' is ambiguous");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1535">[CALCITE-1535]
* Give error if column referenced in ORDER BY is ambiguous</a>. */
@Test void testOrderByColumn() {
sql("select emp.deptno from emp, dept order by emp.deptno")
.ok();
// Not ambiguous. There are two columns which could be referenced as
// "deptno", but the one in the SELECT clause takes priority.
sql("select emp.deptno from emp, dept order by deptno")
.ok();
sql("select emp.deptno as deptno from emp, dept order by deptno")
.ok();
sql("select emp.empno as deptno from emp, dept order by deptno")
.ok();
sql("select emp.deptno as n, dept.deptno as n from emp, dept order by ^n^")
.fails("Column 'N' is ambiguous");
sql("select emp.empno as deptno, dept.deptno from emp, dept\n"
+ "order by ^deptno^")
.fails("Column 'DEPTNO' is ambiguous");
sql("select emp.empno as deptno, dept.deptno from emp, dept\n"
+ "order by emp.deptno")
.ok();
sql("select emp.empno as deptno, dept.deptno from emp, dept order by 1, 2")
.ok();
sql("select empno as \"deptno\", deptno from emp order by deptno")
.ok();
sql("select empno as \"deptno\", deptno from emp order by \"deptno\"")
.ok();
}
@Test void testWindowNegative() {
// Do not fail when window has negative size. Allow
final String negSize = null;
checkNegWindow("rows between 2 preceding and 4 preceding", negSize);
checkNegWindow("rows between 2 preceding and 3 preceding", negSize);
checkNegWindow("rows between 2 preceding and 2 preceding", null);
checkNegWindow("rows between unbounded preceding and current row",
null);
// Unbounded following IS supported
final String unboundedFollowing =
null;
checkNegWindow("rows between unbounded preceding and unbounded following",
unboundedFollowing);
checkNegWindow("rows between current row and unbounded following",
unboundedFollowing);
checkNegWindow("rows between current row and 2 following", null);
checkNegWindow("range between 2 preceding and 2 following", null);
checkNegWindow("range between 2 preceding and -2 preceding", null);
checkNegWindow("range between 4 following and 3 following", negSize);
checkNegWindow("range between 4 following and 5 following", null);
checkNegWindow("rows between 1 following and 0 following", negSize);
checkNegWindow("rows between 0 following and 0 following", null);
}
private void checkNegWindow(String s, String msg) {
String sql =
"select sum(deptno) over ^(order by empno "
+ s
+ ")^ from emp";
sql(sql).failsIf(msg != null, msg);
}
@Test void testWindowPartial() {
sql("select sum(deptno) over (\n"
+ "order by deptno, empno rows 2 preceding disallow partial)\n"
+ "from emp").ok();
// cannot do partial over logical window
sql("select sum(deptno) over (\n"
+ " partition by deptno\n"
+ " order by empno\n"
+ " range between 2 preceding and 3 following\n"
+ " ^disallow partial^)\n"
+ "from emp")
.fails("Cannot use DISALLOW PARTIAL with window based on RANGE");
}
@Test void testOneWinFunc() {
win("window w as (partition by sal order by deptno rows 2 preceding)")
.ok();
}
@Test void testNameResolutionInValuesClause() {
final String emps =
"(select 1 as empno, 'x' as name, 10 as deptno, 'M' as gender, 'San Francisco' as city, 30 as empid, 25 as age from (values (1)))";
final String depts =
"(select 10 as deptno, 'Sales' as name from (values (1)))";
sql("select * from " + emps + " join " + depts + "\n"
+ " on ^emps^.deptno = deptno")
.fails("Table 'EMPS' not found");
// this is ok
sql("select * from " + emps + " as e\n"
+ " join " + depts + " as d\n"
+ " on e.deptno = d.deptno").ok();
// fail: ambiguous column in WHERE
sql("select * from " + emps + " as emps,\n"
+ " " + depts + "\n"
+ "where ^deptno^ > 5")
.fails("Column 'DEPTNO' is ambiguous");
// fail: ambiguous column reference in ON clause
sql("select * from " + emps + " as e\n"
+ " join " + depts + " as d\n"
+ " on e.deptno = ^deptno^")
.fails("Column 'DEPTNO' is ambiguous");
// ok: column 'age' is unambiguous
sql("select * from " + emps + " as e\n"
+ " join " + depts + " as d\n"
+ " on e.deptno = age").ok();
// ok: reference to derived column
sql("select * from " + depts + "\n"
+ " join (select mod(age, 30) as agemod from " + emps + ")\n"
+ "on deptno = agemod").ok();
// fail: deptno is ambiguous
sql("select name from " + depts + "\n"
+ "join (select mod(age, 30) as agemod, deptno from " + emps + ")\n"
+ "on ^deptno^ = agemod")
.fails("Column 'DEPTNO' is ambiguous");
// fail: lateral reference
sql("select * from " + emps + " as e,\n"
+ " (select 1, ^e^.deptno from (values(true))) as d")
.fails("Table 'E' not found");
// fail: deptno is ambiguous
sql("select ^deptno^, name from " + depts + "as d\n"
+ "join " + emps + "as e\n"
+ "on d.deptno = e.deptno")
.fails("Column 'DEPTNO' is ambiguous");
}
@Test void testNestedFrom() {
sql("values (true)")
.columnType("BOOLEAN NOT NULL");
sql("select * from (values(true))")
.columnType("BOOLEAN NOT NULL");
sql("select * from (select * from (values(true)))")
.columnType("BOOLEAN NOT NULL");
sql("select * from (select * from (select * from (values(true))))")
.columnType("BOOLEAN NOT NULL");
sql("select * from ("
+ " select * from ("
+ " select * from (values(true))"
+ " union"
+ " select * from (values (false)))"
+ " except"
+ " select * from (values(true)))")
.columnType("BOOLEAN NOT NULL");
}
@Test void testAmbiguousColumn() {
sql("select * from emp join dept\n"
+ " on emp.deptno = ^deptno^")
.fails("Column 'DEPTNO' is ambiguous");
// this is ok
sql("select * from emp as e\n"
+ " join dept as d\n"
+ " on e.deptno = d.deptno").ok();
// fail: ambiguous column in WHERE
sql("select * from emp as emps, dept\n"
+ "where ^deptno^ > 5")
.fails("Column 'DEPTNO' is ambiguous");
// fail: alias 'd' obscures original table name 'dept'
sql("select * from emp as emps, dept as d\n"
+ "where ^dept^.deptno > 5")
.fails("Table 'DEPT' not found");
// fail: ambiguous column reference in ON clause
sql("select * from emp as e\n"
+ " join dept as d\n"
+ " on e.deptno = ^deptno^")
.fails("Column 'DEPTNO' is ambiguous");
// ok: column 'comm' is unambiguous
sql("select * from emp as e\n"
+ " join dept as d\n"
+ " on e.deptno = comm").ok();
// ok: reference to derived column
sql("select * from dept\n"
+ " join (select mod(comm, 30) as commmod from emp)\n"
+ "on deptno = commmod").ok();
// fail: deptno is ambiguous
sql("select name from dept\n"
+ "join (select mod(comm, 30) as commmod, deptno from emp)\n"
+ "on ^deptno^ = commmod")
.fails("Column 'DEPTNO' is ambiguous");
// fail: lateral reference
sql("select * from emp as e,\n"
+ " (select 1, ^e^.deptno from (values(true))) as d")
.fails("Table 'E' not found");
}
@Test void testExpandStar() {
// dtbug 282 -- "select r.* from sales.depts" gives NPE.
// dtbug 318 -- error location should be ^r^ not ^r.*^.
sql("select ^r^.* from dept")
.fails("Unknown identifier 'R'");
sql("select e.* from emp as e").ok();
sql("select emp.* from emp").ok();
// Error message could be better (EMPNO does exist, but it's a column).
sql("select ^empno^ . * from emp")
.fails("Not a record type. The '\\*' operator requires a record");
sql("select ^emp.empno^ . * from emp")
.fails("Not a record type. The '\\*' operator requires a record");
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-546">[CALCITE-546]
* Allow table, column and field called '*'</a>.
*/
@Test void testStarIdentifier() {
sql("SELECT * FROM (VALUES (0, 0)) AS T(A, \"*\")")
.type("RecordType(INTEGER NOT NULL A, INTEGER NOT NULL *) NOT NULL");
}
@Test void testStarAliasFails() {
sql("select emp.^*^ AS x from emp")
.fails("Unknown field '\\*'");
}
@Test void testNonLocalStar() {
// MySQL allows this, and now so do we
sql("select * from emp e where exists (\n"
+ " select e.* from dept where dept.deptno = e.deptno)")
.type(EMP_RECORD_TYPE);
}
/**
* Parser does not allow "*" in FROM clause.
* Parser allows "*" in column expressions, but throws if they are outside
* of the SELECT clause.
*
* @see #testStarIdentifier()
*/
@Test void testStarInFromFails() {
sql("select emp.empno AS x from sales^.^*")
.fails("(?s)Encountered \"\\. \\*\" at .*");
sql("select * from emp^.^*")
.fails("(?s)Encountered \"\\. \\*\" at .*");
sql("select emp.empno AS x from emp^.^*")
.fails("(?s)Encountered \"\\. \\*\" at .*");
sql("select emp.empno from emp where emp.^*^ is not null")
.fails("Unknown field '\\*'");
}
@Test void testStarDotIdFails() {
// Fails in parser
sql("select emp.^*^.\"EXPR$1\" from emp")
.fails("(?s).*Unknown field '\\*'");
sql("select emp.^*^.foo from emp")
.fails("(?s).*Unknown field '\\*'");
// Parser does not allow star dot identifier.
sql("select *^.^foo from emp")
.fails("(?s).*Encountered \".\" at .*");
}
@Test void testStarWithoutFromFails() {
final String selectStarRequiresAFromClause =
"SELECT \\* requires a FROM clause";
sql("select ^*^")
.fails(selectStarRequiresAFromClause);
sql("select * from (select 2 as two)")
.type("RecordType(INTEGER NOT NULL TWO) NOT NULL");
sql("select ^e^.*")
.fails("Unknown identifier 'E'");
sql("select ^*^, 2 as two")
.fails(selectStarRequiresAFromClause);
sql("select 2 as two, ^*^")
.fails(selectStarRequiresAFromClause);
sql("select 3 as three union select ^*^ union select 4 as four")
.fails(selectStarRequiresAFromClause);
sql("select sum(1) as someone, ^*^")
.fails(selectStarRequiresAFromClause);
sql("select c from (select ^*^) as t(c)")
.fails(selectStarRequiresAFromClause);
sql("select 2 as two\n"
+ "from emp as e\n"
+ "where exists (select e.*)")
.type("RecordType(INTEGER NOT NULL TWO) NOT NULL");
}
@Test void testAsColumnList() {
sql("select d.a, b from dept as d(a, b)").ok();
sql("select d.^deptno^ from dept as d(a, b)")
.fails("(?s).*Column 'DEPTNO' not found in table 'D'.*");
sql("select 1 from dept as d(^a, b, c^)")
.fails("(?s).*List of column aliases must have same degree as table; "
+ "table has 2 columns \\('DEPTNO', 'NAME'\\), "
+ "whereas alias list has 3 columns.*");
sql("select * from dept as d(a, b)")
.type("RecordType(INTEGER NOT NULL A, VARCHAR(10) NOT NULL B) NOT NULL");
sql("select * from (values ('a', 1), ('bc', 2)) t (a, b)")
.type("RecordType(CHAR(2) NOT NULL A, INTEGER NOT NULL B) NOT NULL");
}
@Test void testMeasureRef() {
// A measure can be used in the SELECT clause of a GROUP BY query even
// though it is not a GROUP BY key.
SqlValidatorFixture f =
fixture().withExtendedCatalog()
.withOperatorTable(operatorTableFor(SqlLibrary.CALCITE));
SqlValidatorFixture fNakedInsideOnly =
f.withValidatorConfig(c ->
c.withNakedMeasuresInAggregateQuery(true)
.withNakedMeasuresInNonAggregateQuery(false));
SqlValidatorFixture fNakedOutsideOnly =
f.withValidatorConfig(c ->
c.withNakedMeasuresInNonAggregateQuery(true)
.withNakedMeasuresInAggregateQuery(false));
SqlValidatorFixture fNoNakedMeasures =
f.withValidatorConfig(c ->
c.withNakedMeasuresInNonAggregateQuery(false)
.withNakedMeasuresInAggregateQuery(false));
final String measureIllegal =
"Measure expressions can only occur within AGGREGATE function";
final String measureIllegal2 =
"Measure expressions can only occur within a GROUP BY query";
final String sql0 = "select deptno, ^count_plus_100^\n"
+ "from empm\n"
+ "group by deptno";
f.withSql(sql0)
.isAggregate(is(true))
.ok();
// Same SQL is invalid if naked measures are not enabled
fNoNakedMeasures.withSql(sql0).fails(measureIllegal);
fNakedOutsideOnly.withSql(sql0).fails(measureIllegal);
fNakedInsideOnly.withSql(sql0).isAggregate(is(true)).ok();
// Similarly, with alias
final String sql1b = "select deptno, ^count_plus_100^ as x\n"
+ "from empm\n"
+ "group by deptno";
f.withSql(sql1b).isAggregate(is(true)).ok();
fNoNakedMeasures.withSql(sql1b).fails(measureIllegal);
fNakedOutsideOnly.withSql(sql1b).fails(measureIllegal);
fNakedInsideOnly.withSql(sql1b).isAggregate(is(true)).ok();
// Similarly, in an expression
final String sql1c = "select deptno, deptno + ^count_plus_100^ * 2 as x\n"
+ "from empm\n"
+ "group by deptno";
f.withSql(sql1c).isAggregate(is(true)).ok();
fNoNakedMeasures.withSql(sql1c).fails(measureIllegal);
fNakedOutsideOnly.withSql(sql1c).fails(measureIllegal);
fNakedInsideOnly.withSql(sql1c).isAggregate(is(true)).ok();
// Similarly, for a query that is an aggregate query because of another
// aggregate function.
final String sql1 = "select count(*), ^count_plus_100^\n"
+ "from empm";
f.withSql(sql1).isAggregate(is(true)).ok();
fNoNakedMeasures.withSql(sql1).fails(measureIllegal);
fNakedInsideOnly.withSql(sql1).isAggregate(is(true)).ok();
fNakedOutsideOnly.withSql(sql1).fails(measureIllegal);
// A measure in a non-aggregate query.
// Using a measure should not make it an aggregate query.
// The type of the measure should be the result type of the COUNT aggregate
// function (BIGINT), not type of the un-aggregated argument type (VARCHAR).
final String sql2 = "select deptno, ^count_plus_100^, ename\n"
+ "from empm";
f.withSql(sql2)
.type("RecordType(INTEGER NOT NULL DEPTNO, "
+ "MEASURE<INTEGER NOT NULL> NOT NULL COUNT_PLUS_100, "
+ "VARCHAR(20) NOT NULL ENAME) NOT NULL")
.isAggregate(is(false));
fNoNakedMeasures.withSql(sql2).fails(measureIllegal2);
fNakedInsideOnly.withSql(sql2).fails(measureIllegal2);
fNakedOutsideOnly.withSql(sql2).isAggregate(is(false)).ok();
// as above, wrapping the measure in AGGREGATE
final String sql3 = "select deptno, aggregate(count_plus_100) as x, ename\n"
+ "from empm\n"
+ "group by deptno, ename";
f.withSql(sql3)
.type("RecordType(INTEGER NOT NULL DEPTNO, "
+ "INTEGER NOT NULL X, "
+ "VARCHAR(20) NOT NULL ENAME) NOT NULL");
// you can apply the AGGREGATE function only to measures
f.withSql("select deptno, aggregate(count_plus_100), ^aggregate(ename)^\n"
+ "from empm\n"
+ "group by deptno, ename")
.fails("Argument to function 'AGGREGATE' must be a measure");
f.withSql("select deptno, ^aggregate(count_plus_100 + 1)^\n"
+ "from empm\n"
+ "group by deptno, ename")
.fails("Argument to function 'AGGREGATE' must be a measure");
// A query with AGGREGATE is an aggregate query, even without GROUP BY,
// and even if it is inside an expression.
f.withSql("select aggregate(count_plus_100) + 1\n"
+ "from empm")
.isAggregate(is(true));
// Including a measure in a query does not make it an aggregate query
f.withSql("select count_plus_100\n"
+ "from empm")
.isAggregate(is(false));
}
@Test void testLeastRestrictiveUsesMeasureElement() {
SqlValidatorFixture f =
fixture().withExtendedCatalog()
.withOperatorTable(operatorTableFor(SqlLibrary.BIG_QUERY));
f.withSql("select ifnull(count_times_100, 0) from empm")
.type("RecordType(DECIMAL(19, 0) NOT NULL EXPR$0) NOT NULL");
}
@Test void testAmbiguousColumnInIn() {
// ok: cyclic reference
sql("select * from emp as e\n"
+ "where e.deptno in (\n"
+ " select 1 from (values(true)) where e.empno > 10)").ok();
// ok: cyclic reference
sql("select * from emp as e\n"
+ "where e.deptno in (\n"
+ " select e.deptno from (values(true)))").ok();
}
@Test void testInList() {
sql("select * from emp where empno in (10,20)").ok();
// "select * from emp where empno in ()" is invalid -- see parser test
sql("select * from emp\n"
+ "where empno in (10 + deptno, cast(null as integer))").ok();
sql("select * from emp where empno in (10, '20')").ok();
sql("select * from emp where empno in ^(10, '20')^")
.withTypeCoercion(false)
.fails(ERR_IN_VALUES_INCOMPATIBLE);
expr("1 in (2, 3, 4)")
.columnType("BOOLEAN NOT NULL");
expr("cast(null as integer) in (2, 3, 4)")
.columnType("BOOLEAN");
expr("1 in (2, cast(null as integer) , 4)")
.columnType("BOOLEAN");
expr("1 in (2.5, 3.14)")
.columnType("BOOLEAN NOT NULL");
expr("true in (false, unknown)")
.columnType("BOOLEAN");
expr("true in (false, false or unknown)")
.columnType("BOOLEAN");
expr("true in (false, true)")
.columnType("BOOLEAN NOT NULL");
expr("(1,2) in ((1,2), (3,4))")
.columnType("BOOLEAN NOT NULL");
expr("'medium' in (cast(null as varchar(10)), 'bc')")
.columnType("BOOLEAN");
// nullability depends on nullability of both sides
sql("select empno in (1, 2) from emp")
.columnType("BOOLEAN NOT NULL");
sql("select nullif(empno,empno) in (1, 2) from emp")
.columnType("BOOLEAN");
sql("select empno in (1, nullif(empno,empno), 2) from emp")
.columnType("BOOLEAN");
expr("1 in (2, 'c')").ok();
expr("1 in ^(2, 'c')^")
.withTypeCoercion(false)
.fails(ERR_IN_VALUES_INCOMPATIBLE);
expr("1 in ^((2), (3,4))^")
.fails(ERR_IN_VALUES_INCOMPATIBLE);
expr("false and ^1 in ('b', 'c')^").ok();
expr("false and ^1 in (date '2012-01-02', date '2012-01-04')^")
.fails(ERR_IN_OPERANDS_INCOMPATIBLE);
expr("1 > 5 or ^(1, 2) in (3, 4)^")
.fails(ERR_IN_OPERANDS_INCOMPATIBLE);
}
@Test void testInSubQuery() {
sql("select * from emp where deptno in (select deptno from dept)").ok();
sql("select * from emp where (empno,deptno)"
+ " in (select deptno,deptno from dept)").ok();
// NOTE: jhyde: The closing caret should be one character to the right
// ("dept)^"), but it's difficult to achieve, because parentheses are
// discarded during the parsing process.
sql("select * from emp where ^deptno in "
+ "(select deptno,deptno from dept^)")
.fails("Values passed to IN operator must have compatible types");
}
@Test void testAnyList() {
sql("select * from emp where empno = any (10,20)").ok();
sql("select * from emp\n"
+ "where empno < any (10 + deptno, cast(null as integer))").ok();
sql("select * from emp where empno < any (10, '20')").ok();
sql("select * from emp where empno < any ^(10, '20')^")
.withTypeCoercion(false)
.fails(ERR_IN_VALUES_INCOMPATIBLE);
expr("1 < all (2, 3, 4)")
.columnType("BOOLEAN NOT NULL");
expr("cast(null as integer) < all (2, 3, 4)")
.columnType("BOOLEAN");
expr("1 > some (2, cast(null as integer) , 4)")
.columnType("BOOLEAN");
expr("1 > any (2.5, 3.14)")
.columnType("BOOLEAN NOT NULL");
expr("true = any (false, unknown)")
.columnType("BOOLEAN");
expr("true = any (false, false or unknown)")
.columnType("BOOLEAN");
expr("true <> any (false, true)")
.columnType("BOOLEAN NOT NULL");
expr("(1,2) = any ((1,2), (3,4))")
.columnType("BOOLEAN NOT NULL");
expr("(1,2) < any ((1,2), (3,4))")
.columnType("BOOLEAN NOT NULL");
expr("'abc' < any (cast(null as varchar(10)), 'bc')")
.columnType("BOOLEAN");
// nullability depends on nullability of both sides
sql("select empno < any (1, 2) from emp")
.columnType("BOOLEAN NOT NULL");
sql("select nullif(empno,empno) > all (1, 2) from emp")
.columnType("BOOLEAN");
sql("select empno in (1, nullif(empno,empno), 2) from emp")
.columnType("BOOLEAN");
expr("1 = any (2, 'c')").ok();
expr("1 = any ^(2, 'c')^")
.withTypeCoercion(false)
.fails(ERR_IN_VALUES_INCOMPATIBLE);
expr("1 > all ^((2), (3,4))^")
.fails(ERR_IN_VALUES_INCOMPATIBLE);
expr("false and 1 = any ('b', 'c')").ok();
expr("false and ^1 = any (date '2012-01-02', date '2012-01-04')^")
.fails(ERR_IN_OPERANDS_INCOMPATIBLE);
expr("1 > 5 or ^(1, 2) < any (3, 4)^")
.fails(ERR_IN_OPERANDS_INCOMPATIBLE);
}
@Test void testDoubleNoAlias() {
sql("select * from emp join dept on true").ok();
sql("select * from emp, dept").ok();
sql("select * from emp cross join dept").ok();
}
@Test void testDuplicateColumnAliasIsOK() {
// duplicate column aliases are daft, but SQL:2003 allows them
sql("select 1 as a, 2 as b, 3 as a from emp").ok();
}
@Test void testDuplicateTableAliasFails() {
// implicit alias clashes with implicit alias
sql("select 1 from emp, ^emp^")
.fails("Duplicate relation name 'EMP' in FROM clause");
// implicit alias clashes with implicit alias, using join syntax
sql("select 1 from emp join ^emp^ on emp.empno = emp.mgrno")
.fails("Duplicate relation name 'EMP' in FROM clause");
// explicit alias clashes with implicit alias
sql("select 1 from emp join ^dept as emp^ on emp.empno = emp.deptno")
.fails("Duplicate relation name 'EMP' in FROM clause");
// implicit alias does not clash with overridden alias
sql("select 1 from emp as e join emp on emp.empno = e.deptno").ok();
// explicit alias does not clash with overridden alias
sql("select 1 from emp as e join dept as emp on e.empno = emp.deptno").ok();
// more than 2 in from clause
sql("select 1 from emp, dept, emp as e, ^dept as emp^, emp")
.fails("Duplicate relation name 'EMP' in FROM clause");
// alias applied to sub-query
sql("select 1 from emp, (^select 1 as x from (values (true))) as emp^")
.fails("Duplicate relation name 'EMP' in FROM clause");
sql("select 1 from emp, (^values (true,false)) as emp (b, c)^, dept as emp")
.fails("Duplicate relation name 'EMP' in FROM clause");
// alias applied to table function. doesn't matter that table fn
// doesn't exist - should find the alias problem first
sql("select 1 from emp, ^table(foo()) as emp^")
.fails("Duplicate relation name 'EMP' in FROM clause");
// explicit table
sql("select 1 from emp, ^(table foo.bar.emp) as emp^")
.fails("Duplicate relation name 'EMP' in FROM clause");
// alias does not clash with alias inherited from enclosing context
sql("select 1 from emp, dept where exists (\n"
+ " select 1 from emp where emp.empno = emp.deptno)").ok();
}
@Test void testSchemaTableStar() {
sql("select ^sales.e^.* from sales.emp as e")
.fails("Unknown identifier 'SALES\\.E'");
sql("select sales.dept.* from sales.dept")
.type("RecordType(INTEGER NOT NULL DEPTNO,"
+ " VARCHAR(10) NOT NULL NAME) NOT NULL");
sql("select sales.emp.* from emp").ok();
sql("select sales.emp.* from emp as emp").ok();
// MySQL gives: "Unknown table 'emp'"
// (consistent with MySQL)
sql("select ^sales.emp^.* from emp as e")
.fails("Unknown identifier 'SALES.EMP'");
}
@Test void testSchemaTableColumn() {
sql("select emp.empno from sales.emp").ok();
sql("select sales.emp.empno from sales.emp").ok();
sql("select sales.emp.empno from sales.emp\n"
+ "where sales.emp.deptno > 0").ok();
sql("select 1 from sales.emp where sales.emp.^bad^ < 0")
.fails("Column 'BAD' not found in table 'SALES.EMP'");
sql("select ^sales.bad^.empno from sales.emp\n"
+ "where sales.emp.deptno > 0")
.fails("Table 'SALES\\.BAD' not found");
sql("select sales.emp.deptno from sales.emp").ok();
sql("select 1 from sales.emp where sales.emp.deptno = 10").ok();
sql("select 1 from sales.emp order by sales.emp.deptno").ok();
// alias does not hide the fully-qualified name if same
// (consistent with MySQL)
sql("select sales.emp.deptno from sales.emp as emp").ok();
// alias hides the fully-qualified name
// (consistent with MySQL)
sql("select ^sales.emp^.deptno from sales.emp as e")
.fails("Table 'SALES\\.EMP' not found");
sql("select sales.emp.deptno from sales.emp, ^sales.emp^")
.fails("Duplicate relation name 'EMP' in FROM clause");
// Table exists but not used in FROM clause
sql("select ^sales.emp^.deptno from sales.dept as d1, sales.dept")
.fails("Table 'SALES.EMP' not found");
// Table does not exist
sql("select ^sales.bad^.deptno from sales.dept as d1, sales.dept")
.fails("Table 'SALES.BAD' not found");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-881">[CALCITE-881]
* Allow schema.table.column references in GROUP BY</a>. */
@Test void testSchemaTableColumnInGroupBy() {
sql("select 1 from sales.emp group by sales.emp.deptno").ok();
sql("select deptno from sales.emp group by sales.emp.deptno").ok();
sql("select deptno + 1 from sales.emp group by sales.emp.deptno").ok();
}
@Test void testInvalidGroupBy() {
sql("select ^empno^, deptno from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
}
@Test void testInvalidGroupBy2() {
sql("select count(*) from emp group by ^deptno + 'a'^")
.withTypeCoercion(false)
.fails("(?s)Cannot apply '\\+' to arguments of type.*");
sql("select count(*) from emp group by deptno + 'a'").ok();
}
@Test void testInvalidGroupBy3() {
sql("select deptno / 2 + 1, count(*) as c\n"
+ "from emp\n"
+ "group by rollup(deptno / 2, sal), rollup(empno, ^deptno + 'a'^)")
.withTypeCoercion(false)
.fails("(?s)Cannot apply '\\+' to arguments of type.*");
sql("select deptno / 2 + 1, count(*) as c\n"
+ "from emp\n"
+ "group by rollup(deptno / 2, sal), rollup(empno, ^deptno + 'a'^)").ok();
}
/**
* Test case for <a href="https://issues.apache.org/jira/browse/CALCITE-3003">[CALCITE-3003]
* AssertionError when GROUP BY nested field</a>.
*
* <p>Make sure table name of GROUP BY item with nested field could be
* properly validated.
*/
@Test void testInvalidGroupByWithInvalidTableName() {
final String sql =
"select\n"
+ " coord.x,\n"
+ " avg(coord.y)\n"
+ "from\n"
+ " customer.contact_peek\n"
+ "group by\n"
+ " ^unknown_table_alias.coord^.x";
sql(sql)
.fails("Table 'UNKNOWN_TABLE_ALIAS.COORD' not found");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1781">[CALCITE-1781]
* Allow expression in CUBE and ROLLUP</a>. */
@Test void testCubeExpression() {
final String sql = "select deptno + 1\n"
+ "from emp\n"
+ "group by cube(deptno + 1)";
sql(sql).ok();
final String sql2 = "select deptno + 2 - 2\n"
+ "from emp\n"
+ "group by cube(deptno + 2, empno)";
sql(sql2).ok();
final String sql3 = "select ^deptno^\n"
+ "from emp\n"
+ "group by cube(deptno + 1)";
sql(sql3).fails("Expression 'DEPTNO' is not being grouped");
final String sql4 = "select ^deptno^ + 10\n"
+ "from emp\n"
+ "group by rollup(empno, deptno + 10 - 10)";
sql(sql4).fails("Expression 'DEPTNO' is not being grouped");
final String sql5 = "select deptno + 10\n"
+ "from emp\n"
+ "group by rollup(deptno + 10 - 10, deptno)";
sql(sql5).ok();
}
/** Unit test for
* {@link org.apache.calcite.sql.validate.SqlValidatorUtil#rollup}. */
@Test void testRollupBitSets() {
assertThat(rollup(ImmutableBitSet.of(1), ImmutableBitSet.of(3)),
hasToString("[{1, 3}, {1}, {}]"));
assertThat(rollup(ImmutableBitSet.of(1), ImmutableBitSet.of(3, 4)),
hasToString("[{1, 3, 4}, {1}, {}]"));
assertThat(rollup(ImmutableBitSet.of(1, 3), ImmutableBitSet.of(4)),
hasToString("[{1, 3, 4}, {1, 3}, {}]"));
assertThat(rollup(ImmutableBitSet.of(1, 4), ImmutableBitSet.of(3)),
hasToString("[{1, 3, 4}, {1, 4}, {}]"));
// non-disjoint bit sets
assertThat(rollup(ImmutableBitSet.of(1, 4), ImmutableBitSet.of(3, 4)),
hasToString("[{1, 3, 4}, {1, 4}, {}]"));
// some bit sets are empty
assertThat(
rollup(ImmutableBitSet.of(1, 4), ImmutableBitSet.of(),
ImmutableBitSet.of(3, 4), ImmutableBitSet.of()),
hasToString("[{1, 3, 4}, {1, 4}, {}]"));
assertThat(rollup(ImmutableBitSet.of(1)),
hasToString("[{1}, {}]"));
// one empty bit set
assertThat(rollup(ImmutableBitSet.of()),
hasToString("[{}]"));
// no bit sets
assertThat(rollup(),
hasToString("[{}]"));
}
private ImmutableList<ImmutableBitSet> rollup(ImmutableBitSet... sets) {
return SqlValidatorUtil.rollup(ImmutableList.copyOf(sets));
}
/** Unit test for
* {@link org.apache.calcite.sql.validate.SqlValidatorUtil#cube}. */
@Test void testCubeBitSets() {
assertThat(cube(ImmutableBitSet.of(1), ImmutableBitSet.of(3)),
hasToString("[{1, 3}, {1}, {3}, {}]"));
assertThat(cube(ImmutableBitSet.of(1), ImmutableBitSet.of(3, 4)),
hasToString("[{1, 3, 4}, {1}, {3, 4}, {}]"));
assertThat(cube(ImmutableBitSet.of(1, 3), ImmutableBitSet.of(4)),
hasToString("[{1, 3, 4}, {1, 3}, {4}, {}]"));
assertThat(cube(ImmutableBitSet.of(1, 4), ImmutableBitSet.of(3)),
hasToString("[{1, 3, 4}, {1, 4}, {3}, {}]"));
// non-disjoint bit sets
assertThat(
cube(ImmutableBitSet.of(1, 4), ImmutableBitSet.of(3, 4)),
hasToString("[{1, 3, 4}, {1, 4}, {3, 4}, {}]"));
// some bit sets are empty, and there are duplicates
assertThat(
cube(ImmutableBitSet.of(1, 4),
ImmutableBitSet.of(),
ImmutableBitSet.of(1, 4),
ImmutableBitSet.of(3, 4),
ImmutableBitSet.of()),
hasToString("[{1, 3, 4}, {1, 4}, {3, 4}, {}]"));
assertThat(cube(ImmutableBitSet.of(1)),
hasToString("[{1}, {}]"));
assertThat(cube(ImmutableBitSet.of()),
hasToString("[{}]"));
assertThat(cube(),
hasToString("[{}]"));
}
private ImmutableList<ImmutableBitSet> cube(ImmutableBitSet... sets) {
return SqlValidatorUtil.cube(ImmutableList.copyOf(sets));
}
@Test void testGrouping() {
sql("select deptno, grouping(deptno) from emp group by deptno").ok();
sql("select deptno, grouping(deptno, deptno) from emp group by deptno")
.ok();
sql("select deptno / 2, grouping(deptno / 2),\n"
+ " ^grouping(deptno / 2, empno)^\n"
+ "from emp group by deptno / 2, empno")
.ok();
sql("select deptno, grouping(^empno^) from emp group by deptno")
.fails("Argument to GROUPING operator must be a grouped expression");
sql("select deptno, grouping(deptno, ^empno^) from emp group by deptno")
.fails("Argument to GROUPING operator must be a grouped expression");
sql("select deptno, grouping(^empno^, deptno) from emp group by deptno")
.fails("Argument to GROUPING operator must be a grouped expression");
sql("select deptno, grouping(^deptno + 1^) from emp group by deptno")
.fails("Argument to GROUPING operator must be a grouped expression");
sql("select deptno, grouping(emp.^xxx^) from emp")
.fails("Column 'XXX' not found in table 'EMP'");
sql("select deptno, ^grouping(deptno)^ from emp")
.fails("GROUPING operator may only occur in an aggregate query");
sql("select deptno, sum(^grouping(deptno)^) over () from emp")
.fails("GROUPING operator may only occur in an aggregate query");
sql("select deptno from emp group by deptno having grouping(deptno) < 5")
.ok();
sql("select deptno from emp group by deptno order by grouping(deptno)")
.ok();
sql("select deptno as xx from emp group by deptno order by grouping(xx)")
.ok();
sql("select deptno as empno from emp\n"
+ "group by deptno order by grouping(empno)")
.ok();
sql("select 1 as deptno from emp\n"
+ "group by deptno order by grouping(^deptno^)")
.fails("Argument to GROUPING operator must be a grouped expression");
sql("select deptno from emp group by deptno order by grouping(emp.deptno)")
.ok();
sql("select ^deptno^ from emp group by empno order by grouping(deptno)")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select deptno from emp order by ^grouping(deptno)^")
.fails("GROUPING operator may only occur in an aggregate query");
sql("select deptno from emp where ^grouping(deptno)^ = 1")
.fails("GROUPING operator may only occur in an aggregate query");
sql("select deptno from emp where ^grouping(deptno)^ = 1 group by deptno")
.fails("GROUPING operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp group by deptno, ^grouping(deptno)^")
.fails("GROUPING operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp\n"
+ "group by grouping sets(deptno, ^grouping(deptno)^)")
.fails("GROUPING operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp\n"
+ "group by cube(empno, ^grouping(deptno)^)")
.fails("GROUPING operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp\n"
+ "group by rollup(empno, ^grouping(deptno)^)")
.fails("GROUPING operator may only occur in SELECT, HAVING or ORDER BY clause");
}
@Test void testGroupingId() {
sql("select deptno, grouping_id(deptno) from emp group by deptno").ok();
sql("select deptno, grouping_id(deptno, deptno) from emp group by deptno")
.ok();
sql("select deptno / 2, grouping_id(deptno / 2),\n"
+ " ^grouping_id(deptno / 2, empno)^\n"
+ "from emp group by deptno / 2, empno")
.ok();
sql("select deptno / 2, ^grouping_id()^\n"
+ "from emp group by deptno / 2, empno")
.fails("Invalid number of arguments to function 'GROUPING_ID'. Was expecting 1 arguments");
sql("select deptno, grouping_id(^empno^) from emp group by deptno")
.fails("Argument to GROUPING_ID operator must be a grouped expression");
sql("select deptno, grouping_id(^deptno + 1^) from emp group by deptno")
.fails("Argument to GROUPING_ID operator must be a grouped expression");
sql("select deptno, grouping_id(emp.^xxx^) from emp")
.fails("Column 'XXX' not found in table 'EMP'");
sql("select deptno, ^grouping_id(deptno)^ from emp")
.fails("GROUPING_ID operator may only occur in an aggregate query");
sql("select deptno, sum(^grouping_id(deptno)^) over () from emp")
.fails("GROUPING_ID operator may only occur in an aggregate query");
sql("select deptno from emp group by deptno having grouping_id(deptno) < 5")
.ok();
sql("select deptno from emp group by deptno order by grouping_id(deptno)")
.ok();
sql("select deptno as xx from emp group by deptno order by grouping_id(xx)")
.ok();
sql("select deptno as empno from emp\n"
+ "group by deptno order by grouping_id(empno)")
.ok();
sql("select 1 as deptno from emp\n"
+ "group by deptno order by grouping_id(^deptno^)")
.fails("Argument to GROUPING_ID operator must be a grouped expression");
sql("select deptno from emp group by deptno\n"
+ "order by grouping_id(emp.deptno)")
.ok();
sql("select ^deptno^ from emp group by empno order by grouping_id(deptno)")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select deptno from emp order by ^grouping_id(deptno)^")
.fails("GROUPING_ID operator may only occur in an aggregate query");
sql("select deptno from emp where ^grouping_id(deptno)^ = 1")
.fails("GROUPING_ID operator may only occur in an aggregate query");
sql("select deptno from emp where ^grouping_id(deptno)^ = 1\n"
+ "group by deptno")
.fails("GROUPING_ID operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp group by deptno, ^grouping_id(deptno)^")
.fails("GROUPING_ID operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp\n"
+ "group by grouping sets(deptno, ^grouping_id(deptno)^)")
.fails("GROUPING_ID operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp\n"
+ "group by cube(empno, ^grouping_id(deptno)^)")
.fails("GROUPING_ID operator may only occur in SELECT, HAVING or ORDER BY clause");
sql("select deptno from emp\n"
+ "group by rollup(empno, ^grouping_id(deptno)^)")
.fails("GROUPING_ID operator may only occur in SELECT, HAVING or ORDER BY clause");
}
@Test void testGroupId() {
final String groupIdOnlyInAggregate =
"GROUP_ID operator may only occur in an aggregate query";
final String groupIdWrongClause =
"GROUP_ID operator may only occur in SELECT, HAVING or ORDER BY clause";
sql("select deptno, group_id() from emp group by deptno").ok();
sql("select deptno, ^group_id^ as x from emp group by deptno")
.fails("Column 'GROUP_ID' not found in any table");
sql("select deptno, ^group_id(deptno)^ from emp group by deptno")
.fails("Invalid number of arguments to function 'GROUP_ID'\\. "
+ "Was expecting 0 arguments");
// Oracle throws "GROUPING function only supported with GROUP BY CUBE or
// ROLLUP"
sql("select ^group_id()^ from emp")
.fails(groupIdOnlyInAggregate);
sql("select deptno from emp order by ^group_id(deptno)^")
.fails(groupIdOnlyInAggregate);
// Oracle throws "GROUPING function only supported with GROUP BY CUBE or
// ROLLUP"
sql("select 1 from emp order by ^group_id()^")
.fails(groupIdOnlyInAggregate);
sql("select 1 from emp order by ^grouping(deptno)^")
.fails("GROUPING operator may only occur in an aggregate query");
// Oracle throws "group function is not allowed here"
sql("select deptno from emp where ^group_id()^ = 1")
.fails(groupIdOnlyInAggregate);
// Oracle throws "group function is not allowed here"
sql("select deptno from emp group by ^group_id()^")
.fails(groupIdWrongClause);
sql("select deptno from emp where ^group_id()^ = 1 group by deptno")
.fails(groupIdWrongClause);
sql("select deptno from emp group by deptno, ^group_id()^")
.fails(groupIdWrongClause);
sql("select deptno from emp\n"
+ "group by grouping sets(deptno, ^group_id()^)")
.fails(groupIdWrongClause);
sql("select deptno from emp\n"
+ "group by cube(empno, ^group_id()^)")
.fails(groupIdWrongClause);
sql("select deptno from emp\n"
+ "group by rollup(empno, ^group_id()^)")
.fails(groupIdWrongClause);
sql("select grouping(^group_id()^) from emp")
.fails(groupIdOnlyInAggregate);
// Oracle throws "not a GROUP BY expression"
sql("select grouping(^group_id()^) from emp group by deptno")
.fails(groupIdWrongClause);
sql("select ^grouping(sum(empno))^ from emp group by deptno")
.fails("Aggregate expressions cannot be nested");
}
@Test void testCubeGrouping() {
sql("select deptno, grouping(deptno) from emp group by cube(deptno)").ok();
sql("select deptno, grouping(^deptno + 1^) from emp\n"
+ "group by cube(deptno, empno)")
.fails("Argument to GROUPING operator must be a grouped expression");
}
@Test void testSumInvalidArgs() {
sql("select ^sum(ename)^, deptno from emp group by deptno")
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'SUM' to arguments of type 'SUM\\(<VARCHAR\\(20\\)>\\)'\\. .*");
sql("select sum(ename), deptno from emp group by deptno")
.type("RecordType(DECIMAL(19, 9) NOT NULL EXPR$0, INTEGER NOT NULL DEPTNO) NOT NULL");
}
@Test void testSumTooManyArgs() {
sql("select ^sum(empno, deptno)^, deptno from emp group by deptno")
.fails("Invalid number of arguments to function 'SUM'. Was expecting 1 arguments");
}
@Test void testSumTooFewArgs() {
sql("select ^sum()^, deptno from emp group by deptno")
.fails("Invalid number of arguments to function 'SUM'. Was expecting 1 arguments");
}
@Test void testSingleNoAlias() {
sql("select * from emp").ok();
}
@Test void testObscuredAliasFails() {
// It is an error to refer to a table which has been given another
// alias.
sql("select * from emp as e where exists (\n"
+ " select 1 from dept where dept.deptno = ^emp^.deptno)")
.fails("Table 'EMP' not found");
}
@Test void testFromReferenceFails() {
// You cannot refer to a table ('e2') in the parent scope of a query in
// the from clause.
sql("select * from emp as e1 where exists (\n"
+ " select * from emp as e2,\n"
+ " (select * from dept where dept.deptno = ^e2^.deptno))")
.fails("Table 'E2' not found");
}
@Test void testWhereReference() {
// You can refer to a table ('e1') in the parent scope of a query in
// the from clause.
//
// Note: Oracle10g does not allow this query.
sql("select * from emp as e1 where exists (\n"
+ " select * from emp as e2,\n"
+ " (select * from dept where dept.deptno = e1.deptno))").ok();
}
@Test void testUnionNameResolution() {
sql("select * from emp as e1 where exists (\n"
+ " select * from emp as e2,\n"
+ " (select deptno from dept as d\n"
+ " union\n"
+ " select deptno from emp as e3 where deptno = ^e2^.deptno))")
.fails("Table 'E2' not found");
sql("select * from emp\n"
+ "union\n"
+ "select * from dept where ^empno^ < 10")
.fails("Column 'EMPNO' not found in any table");
}
@Test void testUnionCountMismatchFails() {
sql("select 1,2 from emp\n"
+ "union\n"
+ "select ^3^ from dept")
.fails("Column count mismatch in UNION");
}
@Test void testUnionCountMismatcWithValuesFails() {
sql("select * from ( values (1))\n"
+ "union\n"
+ "select ^*^ from ( values (1,2))")
.fails("Column count mismatch in UNION");
sql("select * from ( values (1))\n"
+ "union\n"
+ "select ^*^ from emp")
.fails("Column count mismatch in UNION");
sql("select * from emp\n"
+ "union\n"
+ "select ^*^ from ( values (1))")
.fails("Column count mismatch in UNION");
}
@Test void testUnionTypeMismatchFails() {
sql("select 1, ^2^ from emp union select deptno, name from dept")
.withTypeCoercion(false)
.fails("Type mismatch in column 2 of UNION");
sql("select 1, 2 from emp union select deptno, ^name^ from dept").ok();
sql("select ^slacker^ from emp union select name from dept")
.withTypeCoercion(false)
.fails("Type mismatch in column 1 of UNION");
sql("select ^slacker^ from emp union select name from dept").ok();
}
@Test void testUnionTypeMismatchWithStarFails() {
sql("select ^*^ from dept union select 1, 2 from emp")
.withTypeCoercion(false)
.fails("Type mismatch in column 2 of UNION");
sql("select * from dept union select 1, 2 from emp").ok();
sql("select ^dept.*^ from dept union select 1, 2 from emp")
.withTypeCoercion(false)
.fails("Type mismatch in column 2 of UNION");
sql("select dept.* from dept union select 1, 2 from emp").ok();
}
@Test void testUnionTypeMismatchWithValuesFails() {
sql("values (1, ^2^, 3), (3, 4, 5), (6, 7, 8) union\n"
+ "select deptno, name, deptno from dept")
.withTypeCoercion(false)
.fails("Type mismatch in column 2 of UNION");
sql("values (1, 2, 3), (3, 4, 5), (6, 7, 8) union\n"
+ "select deptno, ^name^, deptno from dept").ok();
sql("select 1 from (values (^'x'^)) union\n"
+ "select 'a' from (values ('y'))")
.withTypeCoercion(false)
.fails("Type mismatch in column 1 of UNION");
sql("select 1 from (values ('x')) union\n"
+ "select 'a' from (values ('y'))").ok();
sql("select 1 from (values (^'x'^)) union\n"
+ "(values ('a'))")
.withTypeCoercion(false)
.fails("Type mismatch in column 1 of UNION");
sql("select 1 from (values ('x')) union\n"
+ "(values ('a'))").ok();
sql("select 1, ^2^, 3 union\n "
+ "select deptno, name, deptno from dept")
.withTypeCoercion(false)
.fails("Type mismatch in column 2 of UNION");
sql("select 1, 2, 3 union\n "
+ "select deptno, name, deptno from dept").ok();
}
@Test void testValuesTypeMismatchFails() {
sql("^values (1), ('a')^")
.fails("Values passed to VALUES operator must have compatible types");
}
@Test void testNaturalCrossJoinFails() {
sql("select * from emp natural cross ^join^ dept")
.fails("Cannot specify condition \\(NATURAL keyword, or ON or USING "
+ "clause\\) following CROSS JOIN");
}
@Test void testCrossJoinUsingFails() {
sql("select * from emp cross join dept ^using^ (deptno)")
.fails("Cannot specify condition \\(NATURAL keyword, or ON or USING "
+ "clause\\) following CROSS JOIN");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5547">[CALCITE-5547]
* Join using returns incorrect column names</a>. */
@Test void testExtraColJoinUsing() {
final String expectedType = "RecordType(INTEGER NOT NULL TWO, "
+ "INTEGER NOT NULL DEPTNO, "
+ "INTEGER NOT NULL EMPNO, "
+ "VARCHAR(20) NOT NULL ENAME, "
+ "VARCHAR(10) NOT NULL JOB, "
+ "INTEGER MGR, "
+ "TIMESTAMP(0) NOT NULL HIREDATE, "
+ "INTEGER NOT NULL SAL, "
+ "INTEGER NOT NULL COMM, "
+ "BOOLEAN NOT NULL SLACKER, "
+ "VARCHAR(10) NOT NULL NAME) NOT NULL";
sql("select 2 as two, * from emp inner join dept using(deptno)")
.type(expectedType);
sql("select 2 as two, * from emp natural join dept")
.type(expectedType);
sql("select *, 2 as two from emp natural join dept")
.type("RecordType(INTEGER NOT NULL DEPTNO, "
+ "INTEGER NOT NULL EMPNO, "
+ "VARCHAR(20) NOT NULL ENAME, "
+ "VARCHAR(10) NOT NULL JOB, "
+ "INTEGER MGR, "
+ "TIMESTAMP(0) NOT NULL HIREDATE, "
+ "INTEGER NOT NULL SAL, "
+ "INTEGER NOT NULL COMM, "
+ "BOOLEAN NOT NULL SLACKER, "
+ "VARCHAR(10) NOT NULL NAME, "
+ "INTEGER NOT NULL TWO) NOT NULL");
}
@Test void testJoinUsing() {
final String empDeptType = "RecordType(INTEGER NOT NULL DEPTNO,"
+ " INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " BOOLEAN NOT NULL SLACKER,"
+ " VARCHAR(10) NOT NULL NAME) NOT NULL";
sql("select * from emp join dept using (deptno)").ok()
.type(empDeptType);
// NATURAL has same effect as USING
sql("select * from emp natural join dept").ok()
.type(empDeptType);
// fail: comm exists on one side not the other
// todo: The error message could be improved.
sql("select * from emp join dept using (deptno, ^comm^)")
.fails("Column 'COMM' not found in any table");
sql("select * from emp join dept using (^empno^)")
.fails("Column 'EMPNO' not found in any table");
sql("select * from dept join emp using (^empno^)")
.fails("Column 'EMPNO' not found in any table");
// not on either side
sql("select * from dept join emp using (^abc^)")
.fails("Column 'ABC' not found in any table");
// column exists, but wrong case
sql("select * from dept join emp using (^\"deptno\"^)")
.fails("Column 'deptno' not found in any table");
// ok to repeat (ok in Oracle10g too)
final String sql = "select * from emp join dept using (deptno, deptno)";
sql(sql).ok()
.type(empDeptType);
// inherited column, not found in either side of the join, in the
// USING clause
sql("select * from dept where exists (\n"
+ "select 1 from emp join bonus using (^dname^))")
.fails("Column 'DNAME' not found in any table");
// inherited column, found in only one side of the join, in the
// USING clause
sql("select * from dept where exists (\n"
+ "select 1 from emp join bonus using (^deptno^))")
.fails("Column 'DEPTNO' not found in any table");
// cannot qualify common column in Oracle.
sql("select ^emp.deptno^ from emp join dept using (deptno)")
.withConformance(SqlConformanceEnum.ORACLE_10)
.fails("Cannot qualify common column 'EMP.DEPTNO'")
.withConformance(SqlConformanceEnum.ORACLE_12)
.fails("Cannot qualify common column 'EMP.DEPTNO'");
sql("select ^emp.deptno^ from emp natural join dept")
.withConformance(SqlConformanceEnum.ORACLE_10)
.fails("Cannot qualify common column 'EMP.DEPTNO'")
.withConformance(SqlConformanceEnum.ORACLE_12)
.fails("Cannot qualify common column 'EMP.DEPTNO'");
sql("select count(^emp.deptno^) from emp join dept using (deptno)")
.withConformance(SqlConformanceEnum.ORACLE_10)
.fails("Cannot qualify common column 'EMP.DEPTNO'")
.withConformance(SqlConformanceEnum.ORACLE_12)
.fails("Cannot qualify common column 'EMP.DEPTNO'");
sql("select emp.deptno from emp join dept using (deptno)")
.withConformance(SqlConformanceEnum.DEFAULT)
.ok()
.withConformance(SqlConformanceEnum.MYSQL_5)
.ok();
sql("select emp.empno from emp natural join dept")
.withConformance(SqlConformanceEnum.DEFAULT)
.ok()
.withConformance(SqlConformanceEnum.ORACLE_10)
.ok()
.withConformance(SqlConformanceEnum.ORACLE_12)
.ok();
sql("select emp.empno from emp join dept using (deptno)")
.withConformance(SqlConformanceEnum.DEFAULT)
.ok()
.withConformance(SqlConformanceEnum.ORACLE_10)
.ok()
.withConformance(SqlConformanceEnum.ORACLE_12)
.ok();
}
@Test void testCrossJoinOnFails() {
sql("select * from emp cross join dept\n"
+ " ^on^ emp.deptno = dept.deptno")
.fails("Cannot specify condition \\(NATURAL keyword, or ON or "
+ "USING clause\\) following CROSS JOIN");
}
@Test void testInnerJoinWithoutUsingOrOnFails() {
sql("select * from emp inner ^join^ dept\n"
+ "where emp.deptno = dept.deptno")
.fails("INNER, LEFT, RIGHT or FULL join requires a condition "
+ "\\(NATURAL keyword or ON or USING clause\\)");
}
@Test void testNaturalJoinWithOnFails() {
sql("select * from emp natural join dept on ^emp.deptno = dept.deptno^")
.fails("Cannot specify NATURAL keyword with ON or USING clause");
}
@Test void testNaturalJoinWithUsing() {
sql("select * from emp natural join dept ^using (deptno)^")
.fails("Cannot specify NATURAL keyword with ON or USING clause");
}
@Test void testNaturalJoinCaseSensitive() {
// With case-insensitive match, more columns are recognized as join columns
// and therefore "*" expands to fewer columns.
final String sql = "select *\n"
+ "from (select empno, deptno from emp)\n"
+ "natural join (select deptno as \"deptno\", name from dept)";
final String type0 = "RecordType(INTEGER NOT NULL EMPNO,"
+ " INTEGER NOT NULL DEPTNO,"
+ " INTEGER NOT NULL deptno,"
+ " VARCHAR(10) NOT NULL NAME) NOT NULL";
final String type1 = "RecordType(INTEGER NOT NULL DEPTNO,"
+ " INTEGER NOT NULL EMPNO,"
+ " VARCHAR(10) NOT NULL NAME) NOT NULL";
sql(sql)
.type(type0)
.withCaseSensitive(false)
.type(type1);
}
@Test void testNaturalJoinIncompatibleDatatype() {
sql("select *\n"
+ "from (select ename as name, hiredate as deptno from emp)\n"
+ "^natural^ join\n"
+ "(select deptno, name as sal from dept)")
.fails("Column 'DEPTNO' matched using NATURAL keyword or USING clause "
+ "has incompatible types: "
+ "cannot compare 'TIMESTAMP\\(0\\)' to 'INTEGER'");
// INTEGER and VARCHAR are comparable: VARCHAR implicit converts to INTEGER
sql("select * from emp natural ^join^\n"
+ "(select deptno, name as sal from dept)").ok();
// make sal occur more than once on rhs, it is ignored and therefore
// there is no error about incompatible types
sql("select * from emp natural join\n"
+ " (select deptno, name as sal, 'foo' as sal2 from dept)").ok();
}
@Test void testJoinUsingIncompatibleDatatype() {
sql("select *\n"
+ "from (select ename as name, hiredate as deptno from emp)\n"
+ "join (select deptno, name as sal from dept) using (^deptno^, sal)")
.fails("Column 'DEPTNO' matched using NATURAL keyword or USING clause "
+ "has incompatible types: "
+ "cannot compare 'TIMESTAMP\\(0\\)' to 'INTEGER'");
// INTEGER and VARCHAR are comparable: VARCHAR implicit converts to INTEGER
final String sql = "select * from emp\n"
+ "join (select deptno, name as sal from dept) using (deptno, sal)";
sql(sql).ok();
}
@Test void testJoinUsingInvalidColsFails() {
// todo: Improve error msg
sql("select * from emp left join dept using (^gender^)")
.fails("Column 'GENDER' not found in any table");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5171">[CALCITE-5171]
* NATURAL join and USING should fail if join columns are not unique</a>. */
@Test void testJoinDuplicateColumns() {
// NATURAL join and USING should fail if join columns are not unique
final String message = "Column name 'DEPTNO' in NATURAL join or "
+ "USING clause is not unique on one side of join";
sql("select e.ename, d.name\n"
+ "from dept as d\n"
+ "^natural^ join (select ename, sal as deptno, deptno from emp) as e")
.fails(message);
// A similar query with USING fails with the same error
sql("select e.ename, d.name\n"
+ "from dept as d\n"
+ "join (select ename, sal as deptno, deptno from emp) as e\n"
+ " using (^deptno^)")
.fails(message);
// Reversed query gives reversed error message
sql("select e.ename, d.name\n"
+ "from (select ename, sal as deptno, deptno from emp) as e\n"
+ "join dept as d\n"
+ " using (^deptno^)")
.fails(message);
// Also with "*". (Proves that FROM is validated before SELECT.)
sql("select *\n"
+ "from emp\n"
+ "left join (select deptno, name as deptno from dept)\n"
+ " using (^deptno^)")
.fails(message);
}
@Test @DisplayName("Natural join require input column uniqueness")
void testNaturalJoinRequireInputColumnUniqueness() {
final String message = "Column name 'DEPTNO' in NATURAL join or "
+ "USING clause is not unique on one side of join";
// Invalid. NATURAL JOIN eliminates duplicate columns from its output but
// requires input columns to be unique.
sql("select *\n"
+ "from (emp as e cross join dept as d)\n"
+ "^natural^ join\n"
+ "(emp as e2 cross join dept as d2)")
.fails(message);
}
@Test @DisplayName("Should produce two DEPTNO columns")
void testReturnsCorrectRowTypeOnCombinedJoin() {
sql("select *\n"
+ "from emp as e\n"
+ "natural join dept as d\n"
+ "join (select deptno as x, deptno from dept) as d2"
+ " on d2.deptno = e.deptno")
.type("RecordType("
+ "INTEGER NOT NULL DEPTNO, "
+ "INTEGER NOT NULL EMPNO, "
+ "VARCHAR(20) NOT NULL ENAME, "
+ "VARCHAR(10) NOT NULL JOB, "
+ "INTEGER MGR, "
+ "TIMESTAMP(0) NOT NULL HIREDATE, "
+ "INTEGER NOT NULL SAL, "
+ "INTEGER NOT NULL COMM, "
+ "BOOLEAN NOT NULL SLACKER, "
+ "VARCHAR(10) NOT NULL NAME, "
+ "INTEGER NOT NULL X, "
+ "INTEGER NOT NULL DEPTNO1) NOT NULL");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5171">[CALCITE-5171]
* NATURAL join and USING should fail if join columns are not unique</a>. */
@Test void testCorrectJoinDuplicateColumns() {
// The error only occurs if the duplicate column is referenced. The
// following query has a duplicate hiredate column.
sql("select e.ename, d.name\n"
+ "from dept as d\n"
+ "join (select ename, sal as hiredate, deptno from emp) as e\n"
+ " using (deptno)")
.ok();
// Previous join chain does not affect validation.
sql("select * from EMP natural join EMPNULLABLES natural join DEPT")
.ok();
}
@Test void testNaturalEmptyKey() {
// If there are no columns in common, natural join is empty, and that's OK.
sql("select *\n"
+ "from (select ename from emp) as e\n"
+ "natural join dept as d")
.type("RecordType("
+ "VARCHAR(20) NOT NULL ENAME, "
+ "INTEGER NOT NULL DEPTNO, "
+ "VARCHAR(10) NOT NULL NAME) NOT NULL");
// If there are duplicates on one side, that's OK, because the empty natural
// join prevents us from checking.
sql("select d.*\n"
+ "from (select ename, sal as ename from emp) as e\n"
+ "natural join dept as d")
.type("RecordType("
+ "INTEGER NOT NULL DEPTNO, "
+ "VARCHAR(10) NOT NULL NAME) NOT NULL");
// Cannot expand star if it contains duplicate columns.
// (Postgres thinks this query is OK.)
sql("select ^e.*^\n"
+ "from (select ename, sal as ename from emp) as e\n"
+ "natural join dept as d")
.fails("Column 'ENAME' is ambiguous");
}
@Test void testJoinRowType() {
sql("select * from emp left join dept on emp.deptno = dept.deptno")
.type("RecordType(INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " INTEGER NOT NULL DEPTNO,"
+ " BOOLEAN NOT NULL SLACKER,"
+ " INTEGER DEPTNO0,"
+ " VARCHAR(10) NAME) NOT NULL");
sql("select * from emp right join dept on emp.deptno = dept.deptno")
.type("RecordType(INTEGER EMPNO,"
+ " VARCHAR(20) ENAME,"
+ " VARCHAR(10) JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) HIREDATE,"
+ " INTEGER SAL,"
+ " INTEGER COMM,"
+ " INTEGER DEPTNO,"
+ " BOOLEAN SLACKER,"
+ " INTEGER NOT NULL DEPTNO0,"
+ " VARCHAR(10) NOT NULL NAME) NOT NULL");
sql("select * from emp full join dept on emp.deptno = dept.deptno")
.type("RecordType(INTEGER EMPNO,"
+ " VARCHAR(20) ENAME,"
+ " VARCHAR(10) JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) HIREDATE,"
+ " INTEGER SAL,"
+ " INTEGER COMM,"
+ " INTEGER DEPTNO,"
+ " BOOLEAN SLACKER,"
+ " INTEGER DEPTNO0,"
+ " VARCHAR(10) NAME) NOT NULL");
}
@Test void testJoinUsingWithParentheses() {
sql("select * from (emp join bonus using (job))\n"
+ "join dept using (deptno)").ok();
// Cannot alias a JOIN (until
// [CALCITE-5168] Allow AS after parenthesized JOIN
// is fixed).
sql("select * from (emp ^join^ bonus using (job)) as x\n"
+ "join dept using (deptno)")
.fails("Join expression encountered in illegal context");
sql("select * from (emp join bonus using (job))\n"
+ "join dept using (^dname^)")
.fails("Column 'DNAME' not found in any table");
// Needs real Error Message and error marks in query
sql("select * from (emp join bonus using (job))\n"
+ "join (select 1 as job from ^(^true)) using (job)")
.fails("(?s).*Encountered \"\\( true\" at .*");
}
@Disabled("bug: should fail if sub-query does not have alias")
@Test void testJoinSubQuery() {
// Sub-queries require alias
sql("select * from (select 1 as uno from emp)\n"
+ "join (values (1), (2)) on true")
.fails("require alias");
}
@Test void testJoinOnIn() {
final String sql = "select * from emp join dept\n"
+ "on dept.deptno in (select deptno from emp)";
sql(sql).ok();
}
@Test void testJoinOnInCorrelated() {
final String sql = "select * from emp as e join dept\n"
+ "on dept.deptno in (select deptno from emp where deptno < e.deptno)";
sql(sql).ok();
}
@Test void testJoinOnInCorrelatedFails() {
final String sql = "select * from emp as e join dept as d\n"
+ "on d.deptno in (select deptno from emp where deptno < d.^empno^)";
sql(sql).fails("Column 'EMPNO' not found in table 'D'");
}
@Test void testJoinOnExistsCorrelated() {
final String sql = "select * from emp as e join dept\n"
+ "on exists (select 1, 2 from emp where deptno < e.deptno)";
sql(sql).ok();
}
@Test void testJoinOnScalarCorrelated() {
final String sql = "select * from emp as e join dept d\n"
+ "on d.deptno = (select 1 from emp where deptno < e.deptno)";
sql(sql).ok();
}
@Test void testJoinOnScalarFails() {
final String sql = "select * from emp as e join dept d\n"
+ "on d.deptno = (^select 1, 2 from emp where deptno < e.deptno^)";
final String expected = "(?s)Cannot apply '\\$SCALAR_QUERY' to arguments "
+ "of type '\\$SCALAR_QUERY\\(<RECORDTYPE\\(INTEGER EXPR\\$0, INTEGER "
+ "EXPR\\$1\\)>\\)'\\. Supported form\\(s\\).*";
sql(sql).fails(expected);
}
@Test void testJoinUsingThreeWay() {
final String sql0 = "select *\n"
+ "from emp as e\n"
+ "join dept as d using (deptno)\n"
+ "join emp as e2 using (empno)";
sql(sql0).ok();
final String sql1 = "select *\n"
+ "from emp as e\n"
+ "join dept as d using (deptno)\n"
+ "join dept as d2 using (^deptno^)";
sql(sql1).ok();
final String sql2 = "select *\n"
+ "from emp as e\n"
+ "join dept as d using (deptno)";
final String type2 = "RecordType(INTEGER NOT NULL DEPTNO,"
+ " INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " BOOLEAN NOT NULL SLACKER,"
+ " VARCHAR(10) NOT NULL NAME) NOT NULL";
sql(sql2).type(type2);
final String sql3 = "select *\n"
+ "from emp as e\n"
+ "join dept as d using (deptno)\n"
+ "join (values (10, 1000)) as b (empno, bonus) using (empno)";
final String type3 = "RecordType(INTEGER NOT NULL EMPNO,"
+ " INTEGER NOT NULL DEPTNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " BOOLEAN NOT NULL SLACKER,"
+ " VARCHAR(10) NOT NULL NAME,"
+ " INTEGER NOT NULL BONUS) NOT NULL";
sql(sql3).type(type3);
}
@Test void testWhere() {
sql("select * from emp where ^sal^")
.fails("WHERE clause must be a condition");
}
@Test void testOn() {
sql("select * from emp e1 left outer join emp e2 on ^e1.sal^")
.fails("ON clause must be a condition");
}
@Test void testHaving() {
sql("select * from emp having ^sum(sal)^")
.fails("HAVING clause must be a condition");
sql("select ^*^ from emp having sum(sal) > 10")
.fails("Expression 'EMP\\.EMPNO' is not being grouped");
// agg in select and having, no group by
sql("select sum(sal + sal) from emp having sum(sal) > 10").ok();
sql("SELECT deptno FROM emp GROUP BY deptno HAVING ^sal^ > 10")
.fails("Expression 'SAL' is not being grouped");
}
@Test void testHavingBetween() {
// FRG-115: having clause with between not working
sql("select deptno from emp group by deptno\n"
+ "having deptno between 10 and 12").ok();
// this worked even before FRG-115 was fixed
sql("select deptno from emp group by deptno having deptno + 5 > 10").ok();
}
/** Tests the {@code QUALIFY} clause. */
@Test void testQualifyPositive() {
final SqlValidatorFixture f =
fixture().withConformance(SqlConformanceEnum.LENIENT);
final String qualifyWithoutAlias = "SELECT\n"
+ "empno, ename\n"
+ "FROM emp\n"
+ "QUALIFY ROW_NUMBER() over (partition by ename order by deptno) = 1";
f.withSql(qualifyWithoutAlias).ok();
final String qualifyWithAlias = "SELECT empno, ename, deptno,\n"
+ " ROW_NUMBER() over (partition by ename order by deptno) as row_num\n"
+ "FROM emp\n"
+ "QUALIFY row_num = 1";
f.withSql(qualifyWithAlias).ok();
final String qualifyWithWindowClause = "SELECT empno, ename,\n"
+ " SUM(deptno) OVER myWindow as sumDeptNo\n"
+ "FROM emp\n"
+ "WINDOW myWindow AS (PARTITION BY ename ORDER BY empno)\n"
+ "QUALIFY sumDeptNo = 1";
f.withSql(qualifyWithWindowClause).ok();
final String qualifyWithEverything = "SELECT DISTINCT empno, ename, deptno,\n"
+ " RANK() OVER (PARTITION BY ename ORDER BY deptno DESC) as rank_val\n"
+ "FROM emp\n"
+ "WHERE sal > 1000\n"
+ "QUALIFY rank_val = (SELECT COUNT(*) FROM emp)\n"
+ "ORDER BY deptno\n"
+ "LIMIT 5";
f.withSql(qualifyWithEverything).ok();
final String qualifyReferencingCommonColumnInJoin = "SELECT * \n"
+ "FROM emp\n"
+ "NATURAL JOIN dept\n"
+ "QUALIFY ROW_NUMBER() over (partition by ename order by emp.deptno) = 1";
f.withSql(qualifyReferencingCommonColumnInJoin).ok();
final String qualifyOnMultipleWindowFunctions = "SELECT"
+ " AVG(deptno) OVER (PARTITION BY ename) avgDeptNo,"
+ " RANK() OVER (PARTITION BY deptno ORDER BY ename) as myRank\n"
+ "FROM emp\n"
+ "QUALIFY avgDeptNo = 1 AND myRank = 1";
f.withSql(qualifyOnMultipleWindowFunctions).ok();
}
/** Negative tests for the {@code QUALIFY} clause. */
@Test void testQualifyNegative() {
final SqlValidatorFixture f =
fixture().withConformance(SqlConformanceEnum.LENIENT);
// The predicate in the QUALIFY clause expression must be a boolean, since
// we use it as a filter.
final String qualifyWithNonBooleanExpression = "SELECT\n"
+ "empno, ename\n"
+ "FROM emp\n"
+ "QUALIFY ^1 + 1^";
f.withSql(qualifyWithNonBooleanExpression)
.fails("QUALIFY clause must be a condition");
// We don't allow for using ordinal column references in QUALIFY.
final String qualifyWithOrdinal = "SELECT\n"
+ "empno, ename, ROW_NUMBER() over (partition by ename order by deptno) = 1\n"
+ "FROM emp\n"
+ "QUALIFY ^3^";
f.withSql(qualifyWithOrdinal)
.fails("QUALIFY clause must be a condition");
// 'deptno' is a common column in both the emp and dept table.
// We need to use emp.deptno or dept.deptno to make it unambiguous
final String qualifyReferencingAmbiguousCommonColumnInJoin = "SELECT *\n"
+ "FROM emp\n"
+ "NATURAL JOIN dept\n"
+ "QUALIFY ROW_NUMBER() over (partition by ename order by ^deptno^) = 1";
f.withSql(qualifyReferencingAmbiguousCommonColumnInJoin)
.fails("Column 'DEPTNO' is ambiguous");
// Qualify must contain a window function. This matches the behavior where
// HAVING needs to have an aggregate or reference a group column.
final String qualifyOnNonWindowFunction = "SELECT * \n"
+ "FROM emp\n"
+ "QUALIFY ^SUM(deptno) = 1^";
f.withSql(qualifyOnNonWindowFunction)
.fails("QUALIFY expression 'SUM\\(`EMP`\\.`DEPTNO`\\) = 1' "
+ "must contain a window function");
final String qualifyOnAliasedNonWindowFunction = ""
+ "SELECT ^SUM(deptno) as sumDeptNo\n"
+ "FROM emp\n"
+ "QUALIFY sumDeptNo = 1^";
f.withSql(qualifyOnAliasedNonWindowFunction)
.fails("QUALIFY expression 'SUM\\(`EMP`\\.`DEPTNO`\\) = 1' "
+ "must contain a window function");
// This query fails, since it's a mix of regular aggregates and window
// functions. This query needs to fail, since we assume that qualify filters
// on the result of a window function in SqlToRelConverter and that the
// input Rel is a LogicalProject and not a LogicalAggregate.
final String mixedNonAggregateAndWindowAggregate = "SELECT\n"
+ " SUM(deptno) as sumDeptNo, "
+ " RANK() OVER (PARTITION BY ^ename^ ORDER BY deptno) myRank\n"
+ "FROM emp\n";
f.withSql(mixedNonAggregateAndWindowAggregate)
.fails("Expression 'ENAME' is not being grouped");
}
/** Tests the {@code WITH} clause, also called common table expressions. */
@Test void testWith() {
// simplest possible
sql("with emp2 as (select * from emp)\n"
+ "select * from emp2")
.type(EMP_RECORD_TYPE);
// simplest with recursive fails
sql("with RECURSIVE emp2 as (select * from ^emp2^)\n"
+ "select * from emp2")
.fails("Object 'EMP2' not found");
sql("with RECURSIVE emp2 as ("
+ "select * from emp "
+ " union select * from ^emp2^"
+ " union select * from emp2"
+ ")\n"
+ "select * from emp2")
.fails("Object 'EMP2' not found");
// mutually recursive queries are not supported.
sql("WITH RECURSIVE\n"
+ "x (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM ^y^ WHERE id < 5),\n"
+ "y (id) AS (SELECT 1 UNION ALL SELECT id+1 FROM x WHERE id < 5)\n"
+ "SELECT * FROM x")
.fails("Object 'Y' not found");
sql("WITH RECURSIVE t_out(n) AS\n"
+ " (WITH RECURSIVE t_in(n) AS\n"
+ " (\n"
+ " VALUES (1)\n"
+ " UNION ALL SELECT n+1\n"
+ " FROM ^t_out^\n"
+ " WHERE n < 9 ) SELECT n\n"
+ " FROM t_in\n"
+ " UNION ALL SELECT n*10\n"
+ " FROM t_out\n"
+ " WHERE n < 100 )\n"
+ "SELECT n\n"
+ "FROM t_out")
.fails("Object 'T_OUT' not found");
sql("WITH RECURSIVE cte (n) AS\n"
+ "(\n"
+ " SELECT 1, 2\n"
+ " UNION ALL\n"
+ " SELECT ^n + 1^ FROM cte WHERE n < 5\n"
+ ")\n"
+ "SELECT * FROM cte")
.fails("Column count mismatch in UNION ALL");
// simplest with RECURSIVE working case.
sql("with RECURSIVE emp2 as (select * from emp union select * from emp2)\n"
+ "select * from emp2")
.type(EMP_RECORD_TYPE);
// union all with recursive working case.
sql("with RECURSIVE emp2 as (select * from emp union all select * from emp2)\n"
+ "select * from emp2")
.type(EMP_RECORD_TYPE);
// recursive usage of the with clause table name on the left child should throw an error.
sql("with RECURSIVE emp2 as (select * from ^emp2^ union all select * from emp2)\n"
+ "select * from emp2")
.fails("Object 'EMP2' not found");
sql("with RECURSIVE emp2 as (select * from emp intersect select * from ^emp2^)\n"
+ "select * from emp2")
.fails("Object 'EMP2' not found");
sql("with recursive emp2 as (select * from emp),\n"
+ "emp3 as (select * from emp2 union all select * from emp3)\n"
+ "select * from emp2")
.type(EMP_RECORD_TYPE);
// degree of emp2 column list does not match its query
sql("with emp2 ^(x, y)^ as (select * from emp)\n"
+ "select * from emp2")
.fails("Number of columns must match number of query columns");
// duplicate names in column list
sql("with emp2 (x, y, ^y^, x) as (select sal, deptno, ename, empno from emp)\n"
+ "select * from emp2")
.fails("Duplicate name 'Y' in column list");
// column list required if aliases are not unique
sql("with emp2 as (^select empno as e, sal, deptno as e from emp^)\n"
+ "select * from emp2")
.fails("Column has duplicate column name 'E' and no column list specified");
// forward reference
sql("with emp3 as (select * from ^emp2^),\n"
+ " emp2 as (select * from emp)\n"
+ "select * from emp3")
.fails("Object 'EMP2' not found");
// forward reference in with-item not used; should still fail
sql("with emp3 as (select * from ^emp2^),\n"
+ " emp2 as (select * from emp)\n"
+ "select * from emp2")
.fails("Object 'EMP2' not found");
// table not used is ok
sql("with emp2 as (select * from emp),\n"
+ " emp3 as (select * from emp2)\n"
+ "select * from emp2")
.type(EMP_RECORD_TYPE);
// self-reference is not ok, even in table not used
sql("with emp2 as (select * from emp),\n"
+ " emp3 as (select * from ^emp3^)\n"
+ "values (1)")
.fails("Object 'EMP3' not found");
// self-reference not ok
sql("with emp2 as (select * from ^emp2^)\n"
+ "select * from emp2 where false")
.fails("Object 'EMP2' not found");
// refer to 2 previous tables, not just immediately preceding
sql("with emp2 as (select * from emp),\n"
+ " dept2 as (select * from dept),\n"
+ " empDept as (select emp2.empno, dept2.deptno from dept2 join emp2 using (deptno))\n"
+ "select 1 as uno from empDept")
.type("RecordType(INTEGER NOT NULL UNO) NOT NULL");
}
/** Tests the {@code WITH} clause with UNION. */
@Test void testWithUnion() {
// nested WITH (parentheses required - and even with parentheses SQL
// standard doesn't allow sub-query to have WITH)
sql("with emp2 as (select * from emp)\n"
+ "select * from emp2 union all select * from emp")
.type(EMP_RECORD_TYPE);
}
/** Tests the {@code WITH} clause and column aliases. */
@Test void testWithColumnAlias() {
sql("with w(x, y) as (select * from dept)\n"
+ "select * from w")
.type("RecordType(INTEGER NOT NULL X, VARCHAR(10) NOT NULL Y) NOT NULL");
sql("with w(x, y) as (select * from dept)\n"
+ "select * from w, w as w2")
.type("RecordType(INTEGER NOT NULL X, VARCHAR(10) NOT NULL Y, "
+ "INTEGER NOT NULL X0, VARCHAR(10) NOT NULL Y0) NOT NULL");
sql("with w(x, y) as (select * from dept)\n"
+ "select ^deptno^ from w")
.fails("Column 'DEPTNO' not found in any table");
sql("with w(x, ^x^) as (select * from dept)\n"
+ "select * from w")
.fails("Duplicate name 'X' in column list");
}
/** Tests the {@code WITH} clause in sub-queries. */
@Test void testWithSubQuery() {
// nested WITH (parentheses required - and even with parentheses SQL
// standard doesn't allow sub-query to have WITH)
sql("with emp2 as (select * from emp)\n"
+ "(\n"
+ " with dept2 as (select * from dept)\n"
+ " (\n"
+ " with empDept as (select emp2.empno, dept2.deptno from dept2 join emp2 using (deptno))\n"
+ " select 1 as uno from empDept))")
.type("RecordType(INTEGER NOT NULL UNO) NOT NULL");
// WITH inside WHERE can see enclosing tables
sql("select * from emp\n"
+ "where exists (\n"
+ " with dept2 as (select * from dept where dept.deptno >= emp.deptno)\n"
+ " select 1 from dept2 where deptno <= emp.deptno)")
.type(EMP_RECORD_TYPE);
// WITH inside FROM cannot see enclosing tables
sql("select * from emp\n"
+ "join (\n"
+ " with dept2 as (select * from dept where dept.deptno >= ^emp^.deptno)\n"
+ " select * from dept2) as d on true")
.fails("Table 'EMP' not found");
// as above, using USING
sql("select * from emp\n"
+ "join (\n"
+ " with dept2 as (select * from dept where dept.deptno >= ^emp^.deptno)\n"
+ " select * from dept2) as d using (deptno)")
.fails("Table 'EMP' not found");
// WITH inside FROM
sql("select e.empno, d.* from emp as e\n"
+ "join (\n"
+ " with dept2 as (select * from dept where dept.deptno > 10)\n"
+ " select deptno, 1 as uno from dept2) as d using (deptno)")
.type("RecordType(INTEGER NOT NULL EMPNO,"
+ " INTEGER NOT NULL DEPTNO,"
+ " INTEGER NOT NULL UNO) NOT NULL");
sql("select ^e^.empno, d.* from emp\n"
+ "join (\n"
+ " with dept2 as (select * from dept where dept.deptno > 10)\n"
+ " select deptno, 1 as uno from dept2) as d using (deptno)")
.fails("Table 'E' not found");
}
@Test void testWithOrderAgg() {
sql("select count(*) from emp order by count(*)").ok();
sql("with q as (select * from emp)\n"
+ "select count(*) from q group by deptno order by count(*)").ok();
sql("with q as (select * from emp)\n"
+ "select count(*) from q order by count(*)").ok();
// ORDER BY on UNION would produce a similar parse tree,
// SqlOrderBy(SqlUnion(SqlSelect ...)), but is not valid SQL.
sql("select count(*) from emp\n"
+ "union all\n"
+ "select count(*) from emp\n"
+ "order by ^count(*)^")
.fails("Aggregate expression is illegal in ORDER BY clause of non-aggregating SELECT");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4092">[CALCITE-4092]
* NPE using WITH clause without a corresponding SELECT FROM</a>. */
@Test void testWithNotSelected() {
final String sql = "with emp2 as (select max(empno) as empno from emp)\n"
+ "select * from emp where empno < ^emp2^.empno";
sql(sql).fails("Table 'EMP2' not found");
}
/**
* Tests a large scalar expression, which will expose any O(n^2) algorithms
* lurking in the validation process.
*/
@Test void testLarge() {
checkLarge(700, sql -> sql(sql).ok());
}
static void checkLarge(int x, Consumer<String> f) {
if (System.getProperty("os.name").startsWith("Windows")) {
// NOTE jvs 1-Nov-2006: Default thread stack size
// on Windows is too small, so avoid stack overflow
x /= 3;
}
// E.g. large = "deptno * 1 + deptno * 2 + deptno * 3".
String large = list(" + ", "deptno * ", x);
f.accept("select " + large + "from emp");
f.accept("select distinct " + large + "from emp");
f.accept("select " + large + " from emp " + "group by deptno");
f.accept("select * from emp where " + large + " > 5");
f.accept("select * from emp order by " + large + " desc");
f.accept("select " + large + " from emp order by 1");
f.accept("select distinct " + large + " from emp order by " + large);
// E.g. "in (0, 1, 2, ...)"
f.accept("select * from emp where deptno in (" + list(", ", "", x) + ")");
// E.g. "where x = 1 or x = 2 or x = 3 ..."
f.accept("select * from emp where " + list(" or ", "deptno = ", x));
// E.g. "select x1, x2 ... from (
// select 'a' as x1, 'a' as x2, ... from emp union
// select 'bb' as x1, 'bb' as x2, ... from dept)"
f.accept("select " + list(", ", "x", x)
+ " from (select " + list(", ", "'a' as x", x) + " from emp "
+ "union all select " + list(", ", "'bb' as x", x) + " from dept)");
}
private static String list(String sep, String before, int count) {
StringBuilder buf = new StringBuilder();
for (int i = 0; i < count; i++) {
if (i > 0) {
buf.append(sep);
}
buf.append(before).append(i);
}
return buf.toString();
}
@Test void testAbstractConformance()
throws InvocationTargetException, IllegalAccessException {
final SqlAbstractConformance c0 = new SqlAbstractConformance() {
};
final SqlConformance c1 = SqlConformanceEnum.DEFAULT;
for (Method method : SqlConformance.class.getMethods()) {
final Object o0 = method.invoke(c0);
final Object o1 = method.invoke(c1);
assertThat(method.toString(), Objects.equals(o0, o1), is(true));
}
}
@Test void testUserDefinedConformance() {
final SqlConformance custom =
new SqlDelegatingConformance(SqlConformanceEnum.DEFAULT) {
public boolean isBangEqualAllowed() {
return true;
}
};
// Our conformance behaves differently from ORACLE_10 for FROM-less query.
sql("^select 2+2^")
.withConformance(custom)
.ok()
.withConformance(SqlConformanceEnum.DEFAULT)
.ok()
.withConformance(SqlConformanceEnum.ORACLE_10)
.fails("SELECT must have a FROM clause");
// Our conformance behaves like ORACLE_10 for "!=" operator.
sql("select * from (values 1) where 1 ^!=^ 2")
.withConformance(custom)
.ok()
.withConformance(SqlConformanceEnum.DEFAULT)
.fails("Bang equal '!=' is not allowed under the current SQL conformance level")
.withConformance(SqlConformanceEnum.ORACLE_10)
.ok();
sql("select * from (values 1) where 1 ^!=^ any (2, 3)")
.withConformance(custom)
.ok()
.withConformance(SqlConformanceEnum.DEFAULT)
.fails("Bang equal '!=' is not allowed under the current SQL conformance level")
.withConformance(SqlConformanceEnum.ORACLE_10)
.ok();
}
@Test void testOrder() {
final SqlConformance conformance = fixture().conformance();
sql("select empno as x from emp order by empno").ok();
// invalid use of 'asc'
sql("select empno, sal from emp order by ^asc^")
.fails("Column 'ASC' not found in any table");
// In sql92, empno is obscured by the alias.
// Otherwise valid.
// Checked Oracle10G -- is it valid.
sql("select empno as x from emp order by empno")
.failsIf(conformance.isSortByAliasObscures(),
"unknown column empno");
// valid in oracle and pre-99 sql,
// invalid in sql:2003
sql("select empno as x from emp order by ^x^")
.failsIf(!conformance.isSortByAlias(),
"Column 'X' not found in any table");
// invalid in oracle and pre-99;
// valid from sql:99 onwards (but sorting by constant achieves
// nothing!)
sql("select empno as x from emp order by ^10^")
.failsIf(conformance.isSortByOrdinal(),
"Ordinal out of range");
// Has different meanings in different dialects (which makes it very
// confusing!) but is always valid.
sql("select empno + 1 as empno from emp order by empno").ok();
// Always fails
sql("select empno as x from emp, dept order by ^deptno^")
.fails("Column 'DEPTNO' is ambiguous");
sql("select empno + 1 from emp order by deptno asc, empno + 1 desc").ok();
// Alias 'deptno' is closer in scope than 'emp.deptno'
// and 'dept.deptno', and is therefore not ambiguous.
// Checked Oracle10G -- it is valid.
// Ambiguous in SQL:2003
sql("select empno as deptno from emp, dept order by deptno")
.failsIf(!conformance.isSortByAlias(),
"col ambig");
sql("select deptno from dept\n"
+ "union\n"
+ "select empno from emp\n"
+ "order by deptno").ok();
sql("select deptno from dept\n"
+ "union\n"
+ "select empno from emp\n"
+ "order by ^empno^")
.fails("Column 'EMPNO' not found in any table");
// invalid in oracle and pre-99
sql("select deptno from dept\n"
+ "union\n"
+ "select empno from emp\n"
+ "order by ^10^")
.failsIf(conformance.isSortByOrdinal(),
"Ordinal out of range");
// Sort by scalar sub-query
sql("select * from emp\n"
+ "order by (select name from dept where deptno = emp.deptno)").ok();
sql("select * from emp\n"
+ "order by (select name from dept where deptno = emp.^foo^)")
.fails("Column 'FOO' not found in table 'EMP'");
// REVIEW jvs 10-Apr-2008: I disabled this because I don't
// understand what it means; see
// testAggregateInOrderByFails for the discrimination I added
// (SELECT should be aggregating for this to make sense).
/*
// Sort by aggregate. Oracle allows this.
check("select 1 from emp order by sum(sal)");
*/
// ORDER BY and SELECT *
sql("select * from emp order by empno").ok();
sql("select * from emp order by ^nonExistent^, deptno")
.fails("Column 'NONEXISTENT' not found in any table");
// Overriding expression has different type.
sql("select 'foo' as empno from emp order by ^empno + 5^")
.withTypeCoercion(false)
.fails("(?s)Cannot apply '\\+' to arguments of type '<CHAR\\(3\\)> \\+ <INTEGER>'\\..*");
sql("select 'foo' as empno from emp order by empno + 5").ok();
}
/** Tests that you can reference a column alias in the ORDER BY clause if
* {@link SqlConformance#isSortByAlias()}. */
@Test void testOrderByAlias() {
sql("select count(*) as total from emp order by ^total^")
.ok()
.withConformance(SqlConformanceEnum.BIG_QUERY)
.ok()
.withConformance(SqlConformanceEnum.STRICT_2003)
.fails("Column 'TOTAL' not found in any table");
}
@Test void testOrderJoin() {
sql("select * from emp as e, dept as d order by e.empno").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-633">[CALCITE-633]
* WITH ... ORDER BY cannot find table</a>. */
@Test void testWithOrder() {
sql("with e as (select * from emp)\n"
+ "select * from e as e1 order by e1.empno").ok();
sql("with e as (select * from emp)\n"
+ "select * from e as e1, e as e2 order by e1.empno").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-662">[CALCITE-662]
* Query validation fails when an ORDER BY clause is used with WITH
* CLAUSE</a>. */
@Test void testWithOrderInParentheses() {
sql("with e as (select * from emp)\n"
+ "(select e.empno from e order by e.empno)").ok();
sql("with e as (select * from emp)\n"
+ "(select e.empno from e order by 1)").ok();
sql("with e as (select * from emp)\n"
+ "(select ee.empno from e as ee order by ee.deptno)").ok();
// worked even before CALCITE-662 fixed
sql("with e as (select * from emp)\n"
+ "(select e.empno from e)").ok();
}
@Test void testOrderUnion() {
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by empno").ok();
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by ^asc^")
.fails("Column 'ASC' not found in any table");
// name belongs to emp but is not projected so cannot sort on it
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by ^ename^ desc")
.fails("Column 'ENAME' not found in any table");
// empno is not an alias in the first select in the union
sql("select deptno, deptno as no2 from dept "
+ "union all "
+ "select empno, sal from emp "
+ "order by deptno asc, ^empno^")
.fails("Column 'EMPNO' not found in any table");
// ordinals ok
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by 2").ok();
// ordinal out of range -- if 'order by <ordinal>' means something in
// this dialect
final SqlConformance conformance = fixture().conformance();
if (conformance.isSortByOrdinal()) {
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by ^3^")
.fails("Ordinal out of range");
}
// Expressions made up of aliases are OK.
// (This is illegal in Oracle 10G.)
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by empno * sal + 2").ok();
sql("select empno, sal from emp "
+ "union all "
+ "select deptno, deptno from dept "
+ "order by 'foobar'").ok();
}
/**
* Tests validation of the ORDER BY clause when GROUP BY is present.
*/
@Test void testOrderGroup() {
// Group by
sql("select 1 from emp group by deptno order by ^empno^")
.fails("Expression 'EMPNO' is not being grouped");
// order by can contain aggregate expressions
sql("select empno from emp "
+ "group by empno, deptno "
+ "order by deptno * sum(sal + 2)").ok();
// Having
sql("select sum(sal) from emp having count(*) > 3 order by ^empno^")
.fails("Expression 'EMPNO' is not being grouped");
sql("select sum(sal) from emp having count(*) > 3 order by sum(deptno)").ok();
// Select distinct
sql("select distinct deptno from emp group by deptno order by ^empno^")
.fails("Expression 'EMPNO' is not in the select clause");
sql("select distinct deptno from emp group by deptno order by deptno, ^empno^")
.fails("Expression 'EMPNO' is not in the select clause");
sql("select distinct deptno from emp group by deptno order by deptno").ok();
// UNION of SELECT DISTINCT and GROUP BY behaves just like a UNION.
sql("select distinct deptno from dept "
+ "union all "
+ "select empno from emp group by deptno, empno "
+ "order by deptno").ok();
// order by can contain a mixture of aliases and aggregate expressions
sql("select empno as x "
+ "from emp "
+ "group by empno, deptno "
+ "order by x * sum(sal + 2)").ok();
final SqlConformance conformance = fixture().conformance();
sql("select empno as x "
+ "from emp "
+ "group by empno, deptno "
+ "order by empno * sum(sal + 2)")
.failsIf(conformance.isSortByAliasObscures(), "xxxx");
}
/**
* Tests validation of the aliases in GROUP BY.
*
* <p>Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1306">[CALCITE-1306]
* Allow GROUP BY and HAVING to reference SELECT expressions by ordinal and
* alias</a>.
*
* @see SqlConformance#isGroupByAlias()
*/
@Test void testAliasInGroupBy() {
final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
final SqlConformanceEnum strict = SqlConformanceEnum.STRICT_2003;
// Group by
sql("select empno as e from emp group by ^e^")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select empno as e from emp group by ^e^")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select emp.empno as e from emp group by ^e^")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select e.empno from emp as e group by e.empno")
.withConformance(strict).ok()
.withConformance(lenient).ok();
sql("select e.empno as eno from emp as e group by ^eno^")
.withConformance(strict).fails("Column 'ENO' not found in any table")
.withConformance(lenient).ok();
sql("select deptno as dno from emp group by cube(^dno^)")
.withConformance(strict).fails("Column 'DNO' not found in any table")
.withConformance(lenient).ok();
sql("select deptno as dno, ename name, sum(sal) from emp\n"
+ "group by grouping sets ((^dno^), (name, deptno))")
.withConformance(strict).fails("Column 'DNO' not found in any table")
.withConformance(lenient).ok();
sql("select ename as deptno from emp as e join dept as d on "
+ "e.deptno = d.deptno group by ^deptno^")
.withConformance(lenient).ok();
sql("select t.e, count(*) from (select empno as e from emp) t group by e")
.withConformance(strict).ok()
.withConformance(lenient).ok();
// The following 2 tests have the same SQL but fail for different reasons.
sql("select t.e, count(*) as c from "
+ " (select empno as e from emp) t group by e,^c^")
.withConformance(strict).fails("Column 'C' not found in any table");
sql("select t.e, ^count(*)^ as c from "
+ " (select empno as e from emp) t group by e,c")
.withConformance(lenient).fails(ERR_AGG_IN_GROUP_BY);
sql("select t.e, e + ^count(*)^ as c from "
+ " (select empno as e from emp) t group by e,c")
.withConformance(lenient).fails(ERR_AGG_IN_GROUP_BY);
sql("select t.e, e + ^count(*)^ as c from "
+ " (select empno as e from emp) t group by e,2")
.withConformance(lenient).fails(ERR_AGG_IN_GROUP_BY)
.withConformance(strict).ok();
sql("select deptno,(select empno + 1 from emp) eno\n"
+ "from dept group by deptno,^eno^")
.withConformance(strict).fails("Column 'ENO' not found in any table")
.withConformance(lenient).ok();
sql("select empno as e, deptno as e\n"
+ "from emp group by ^e^")
.withConformance(lenient).fails("Column 'E' is ambiguous");
sql("select empno, ^count(*)^ c from emp group by empno, c")
.withConformance(lenient).fails(ERR_AGG_IN_GROUP_BY);
sql("select deptno + empno as d, deptno + empno + mgr from emp"
+ " group by d,mgr")
.withConformance(lenient).ok();
// When alias is equal to one or more columns in the query then giving
// priority to alias. But Postgres may throw ambiguous column error or give
// priority to column name.
sql("select count(*) from (\n"
+ " select ename AS deptno FROM emp GROUP BY deptno) t")
.withConformance(lenient).ok();
sql("select count(*) from "
+ "(select ename AS deptno FROM emp, dept GROUP BY deptno) t")
.withConformance(lenient).ok();
sql("select empno + deptno AS \"z\" FROM emp GROUP BY \"Z\"")
.withConformance(lenient).withCaseSensitive(false).ok();
sql("select empno + deptno as c, ^c^ + mgr as d from emp group by c, d")
.withConformance(lenient).fails("Column 'C' not found in any table");
// Group by alias with strict conformance should fail.
sql("select empno as e from emp group by ^e^")
.withConformance(strict).fails("Column 'E' not found in any table");
}
/**
* Tests validation of ordinals in GROUP BY.
*
* @see SqlConformance#isGroupByOrdinal()
*/
@Test void testOrdinalInGroupBy() {
final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
final SqlConformanceEnum strict = SqlConformanceEnum.STRICT_2003;
sql("select ^empno^,deptno from emp group by 1, deptno")
.withConformance(strict).fails("Expression 'EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select ^emp.empno^ as e from emp group by 1")
.withConformance(strict).fails("Expression 'EMP.EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select 2 + ^emp.empno^ + 3 as e from emp group by 1")
.withConformance(strict).fails("Expression 'EMP.EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select ^e.empno^ from emp as e group by 1")
.withConformance(strict).fails("Expression 'E.EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select e.empno from emp as e group by 1, empno")
.withConformance(strict).ok()
.withConformance(lenient).ok();
sql("select ^e.empno^ as eno from emp as e group by 1")
.withConformance(strict).fails("Expression 'E.EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select ^deptno^ as dno from emp group by cube(1)")
.withConformance(strict).fails("Expression 'DEPTNO' is not being grouped")
.withConformance(lenient).ok();
sql("select 1 as dno from emp group by cube(1)")
.withConformance(strict).ok()
.withConformance(lenient).ok();
sql("select deptno as dno, ename name, sum(sal) from emp\n"
+ "group by grouping sets ((1), (^name^, deptno))")
.withConformance(strict).fails("Column 'NAME' not found in any table")
.withConformance(lenient).ok();
sql("select ^e.deptno^ from emp as e\n"
+ "join dept as d on e.deptno = d.deptno group by 1")
.withConformance(strict).fails("Expression 'E.DEPTNO' is not being grouped")
.withConformance(lenient).ok();
sql("select ^deptno^,(select empno from emp) eno from dept"
+ " group by 1,2")
.withConformance(strict).fails("Expression 'DEPTNO' is not being grouped")
.withConformance(lenient).ok();
sql("select ^empno^, count(*) from emp group by 1 order by 1")
.withConformance(strict).fails("Expression 'EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select ^empno^ eno, count(*) from emp group by 1 order by 1")
.withConformance(strict).fails("Expression 'EMPNO' is not being grouped")
.withConformance(lenient).ok();
sql("select count(*) from (select 1 from emp"
+ " group by substring(ename from 2 for 3))")
.withConformance(strict).ok()
.withConformance(lenient).ok();
sql("select deptno from emp group by deptno, ^100^")
.withConformance(lenient).fails("Ordinal out of range")
.withConformance(strict).ok();
// Calcite considers integers in GROUP BY to be constants, so test passes.
// Postgres considers them ordinals and throws out of range position error.
sql("select deptno from emp group by ^100^, deptno")
.withConformance(lenient).fails("Ordinal out of range")
.withConformance(strict).ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5507">[CALCITE-5507]
* HAVING alias failed when aggregate function in condition</a>. */
@Test void testAggregateFunAndAliasInHaving() {
final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
final SqlConformanceEnum strict = SqlConformanceEnum.STRICT_2003;
sql("select count(empno) as e from emp having ^e^ > 10 and count(empno) > 10 ")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select count(empno) as e from emp having count(empno) > 10 and count(^e^) > 10")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).fails("Column 'E' not found in any table");
}
/**
* Tests validation of the aliases in HAVING.
*
* @see SqlConformance#isHavingAlias()
*/
@Test void testAliasInHaving() {
final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
final SqlConformanceEnum strict = SqlConformanceEnum.STRICT_2003;
sql("select count(empno) as e from emp having ^e^ > 10")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select emp.empno as e from emp group by ^e^ having e > 10")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select emp.empno as e from emp group by empno having ^e^ > 10")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
sql("select e.empno from emp as e group by 1 having ^e.empno^ > 10")
.withConformance(strict).fails("Expression 'E.EMPNO' is not being grouped")
.withConformance(lenient).ok();
// When alias is equal to one or more columns in the query then giving
// priority to alias, but PostgreSQL throws ambiguous column error or gives
// priority to column name.
sql("select count(empno) as deptno from emp having ^deptno^ > 10")
.withConformance(strict).fails("Expression 'DEPTNO' is not being grouped")
.withConformance(lenient).ok();
// Alias in aggregate is not allowed.
sql("select empno as e from emp having max(^e^) > 10")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).fails("Column 'E' not found in any table");
sql("select count(empno) as e from emp having ^e^ > 10")
.withConformance(strict).fails("Column 'E' not found in any table")
.withConformance(lenient).ok();
}
/**
* Tests validation of the ORDER BY clause when DISTINCT is present.
*/
@Test void testOrderDistinct() {
// Distinct on expressions with attempts to order on a column in
// the underlying table
sql("select distinct cast(empno as bigint) "
+ "from emp order by ^empno^")
.fails("Expression 'EMPNO' is not in the select clause");
sql("select distinct cast(empno as bigint) "
+ "from emp order by ^emp.empno^")
.fails("Expression 'EMP\\.EMPNO' is not in the select clause");
sql("select distinct cast(empno as bigint) as empno "
+ "from emp order by ^emp.empno^")
.fails("Expression 'EMP\\.EMPNO' is not in the select clause");
sql("select distinct cast(empno as bigint) as empno "
+ "from emp as e order by ^e.empno^")
.fails("Expression 'E\\.EMPNO' is not in the select clause");
// These tests are primarily intended to test cases where sorting by
// an alias is allowed. But for instances that don't support sorting
// by alias, the tests also verify that a proper exception is thrown.
final SqlConformance conformance = fixture().conformance();
sql("select distinct cast(empno as bigint) as empno "
+ "from emp order by ^empno^")
.failsIf(!conformance.isSortByAlias(),
"Expression 'EMPNO' is not in the select clause");
sql("select distinct cast(empno as bigint) as eno "
+ "from emp order by ^eno^")
.failsIf(!conformance.isSortByAlias(),
"Column 'ENO' not found in any table");
sql("select distinct cast(empno as bigint) as empno "
+ "from emp e order by ^empno^")
.failsIf(!conformance.isSortByAlias(),
"Expression 'EMPNO' is not in the select clause");
// Distinct on expressions, sorting using ordinals.
if (conformance.isSortByOrdinal()) {
sql("select distinct cast(empno as bigint) from emp order by 1").ok();
sql("select distinct cast(empno as bigint) as empno "
+ "from emp order by 1").ok();
sql("select distinct cast(empno as bigint) as empno "
+ "from emp as e order by 1").ok();
}
// Distinct on expressions with ordering on expressions as well
sql("select distinct cast(empno as varchar(10)) from emp "
+ "order by cast(empno as varchar(10))").ok();
sql("select distinct cast(empno as varchar(10)) as eno from emp "
+ " order by upper(^eno^)")
.failsIf(!conformance.isSortByAlias(),
"Column 'ENO' not found in any table");
// Test case for [CALCITE-5653], order by on aggregate will fail when there is an implicit cast
// and IdentifierExpansion is off
sql("select distinct sum(deptno + '1') from dept order by 1")
.withValidatorIdentifierExpansion(false)
.ok();
}
/**
* Tests validation of the ORDER BY clause when DISTINCT and GROUP BY are
* present.
*
* <p>Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-634">[CALCITE-634]
* Allow ORDER BY aggregate function in SELECT DISTINCT, provided that it
* occurs in SELECT clause</a>.
*/
@Test void testOrderGroupDistinct() {
// Order by an aggregate function,
// which exists in select-clause with distinct being present
sql("select distinct count(empno) AS countEMPNO from emp\n"
+ "group by empno\n"
+ "order by 1").ok();
sql("select distinct count(empno) from emp\n"
+ "group by empno\n"
+ "order by 1").ok();
sql("select distinct count(empno) AS countEMPNO from emp\n"
+ "group by empno\n"
+ "order by count(empno)").ok();
sql("select distinct count(empno) from emp\n"
+ "group by empno\n"
+ "order by count(empno)").ok();
sql("select distinct count(empno) from emp\n"
+ "group by empno\n"
+ "order by count(empno) desc").ok();
sql("SELECT DISTINCT deptno from emp\n"
+ "ORDER BY deptno, ^sum(empno)^")
.fails("Aggregate expression is illegal in ORDER BY clause of non-aggregating SELECT");
sql("SELECT DISTINCT deptno from emp\n"
+ "GROUP BY deptno ORDER BY deptno, ^sum(empno)^")
.fails("Expression 'SUM\\(`EMPNO`\\)' is not in the select clause");
sql("SELECT DISTINCT deptno, min(empno) from emp\n"
+ "GROUP BY deptno ORDER BY deptno, ^sum(empno)^")
.fails("Expression 'SUM\\(`EMPNO`\\)' is not in the select clause");
sql("SELECT DISTINCT deptno, sum(empno) from emp\n"
+ "GROUP BY deptno ORDER BY deptno, sum(empno)").ok();
}
@Test void testGroup() {
sql("select empno from emp where ^sum(sal)^ > 50")
.fails("Aggregate expression is illegal in WHERE clause");
sql("select ^empno^ from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select ^*^ from emp group by deptno")
.fails("Expression 'EMP\\.EMPNO' is not being grouped");
// If we're grouping on ALL columns, 'select *' is ok.
// Checked on Oracle10G.
sql("select * from (select empno,deptno from emp) group by deptno,empno").ok();
// This query tries to reference an agg expression from within a
// sub-query as a correlating expression, but the SQL syntax rules say
// that the agg function SUM always applies to the current scope.
// As it happens, the query is valid.
sql("select deptno\n"
+ "from emp\n"
+ "group by deptno\n"
+ "having exists (select sum(emp.sal) > 10 from (values(true)))").ok();
// if you reference a column from a sub-query, it must be a group col
sql("select deptno "
+ "from emp "
+ "group by deptno "
+ "having exists (select 1 from (values(true)) where emp.deptno = 10)").ok();
// Needs proper error message text and error markers in query
if (TODO) {
sql("select deptno "
+ "from emp "
+ "group by deptno "
+ "having exists (select 1 from (values(true)) where emp.empno = 10)")
.fails("xx");
}
// constant expressions
sql("select cast(1 as integer) + 2 from emp group by deptno").ok();
sql("select localtime, deptno + 3 from emp group by deptno").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-886">[CALCITE-886]
* System functions in GROUP BY clause</a>. */
@Test void testGroupBySystemFunction() {
sql("select CURRENT_USER from emp group by CURRENT_USER").ok();
sql("select CURRENT_USER from emp group by rollup(CURRENT_USER)").ok();
sql("select CURRENT_USER from emp group by rollup(CURRENT_USER, ^x^)")
.fails("Column 'X' not found in any table");
sql("select CURRENT_USER from emp group by deptno").ok();
}
@Test void testGroupingSets() {
sql("select count(1), ^empno^ from emp group by grouping sets (deptno)")
.fails("Expression 'EMPNO' is not being grouped");
sql("select deptno, ename, sum(sal) from emp\n"
+ "group by grouping sets ((deptno), (ename, deptno))\n"
+ "order by 2").ok();
// duplicate (and heavily nested) GROUPING SETS
sql("select sum(sal) from emp\n"
+ "group by deptno,\n"
+ " grouping sets (deptno,\n"
+ " grouping sets (deptno, ename),\n"
+ " (ename)),\n"
+ " ()").ok();
}
@Test void testRollup() {
// DEPTNO is not null in database, but rollup introduces nulls
sql("select deptno, count(*) as c, sum(sal) as s\n"
+ "from emp\n"
+ "group by rollup(deptno)")
.ok()
.type("RecordType(INTEGER DEPTNO, BIGINT NOT NULL C, INTEGER NOT NULL S) NOT NULL");
// EMPNO stays NOT NULL because it is not rolled up
sql("select deptno, empno\n"
+ "from emp\n"
+ "group by empno, rollup(deptno)")
.ok()
.type("RecordType(INTEGER DEPTNO, INTEGER NOT NULL EMPNO) NOT NULL");
// DEPTNO stays NOT NULL because it is not rolled up
sql("select deptno, empno\n"
+ "from emp\n"
+ "group by rollup(empno), deptno")
.ok()
.type("RecordType(INTEGER NOT NULL DEPTNO, INTEGER EMPNO) NOT NULL");
// empno becomes NULL because it is rolled up, and so does empno + 1.
sql("select empno, empno + 1 as e1\n"
+ "from emp\n"
+ "group by rollup(empno)")
.ok()
.type("RecordType(INTEGER EMPNO, INTEGER E1) NOT NULL");
}
@Test void testGroupByCorrelatedColumn() {
// This is not sql 2003 standard; see sql2003 part2, 7.9
// But the extension seems harmless.
final String sql = "select count(*)\n"
+ "from emp\n"
+ "where exists (select count(*) from dept group by emp.empno)";
sql(sql).ok();
}
@Test void testGroupExpressionEquivalence() {
// operator equivalence
sql("select empno + 1 from emp group by empno + 1").ok();
sql("select 1 + ^empno^ from emp group by empno + 1")
.fails("Expression 'EMPNO' is not being grouped");
// datatype equivalence
sql("select cast(empno as VARCHAR(10)) from emp\n"
+ "group by cast(empno as VARCHAR(10))").ok();
sql("select cast(^empno^ as VARCHAR(11)) from emp group by cast(empno as VARCHAR(10))")
.fails("Expression 'EMPNO' is not being grouped");
}
@Test void testGroupExpressionEquivalenceId() {
// identifier equivalence
sql("select case empno when 10 then deptno else null end from emp "
+ "group by case empno when 10 then deptno else null end").ok();
// matches even when one column is qualified (checked on Oracle10.1)
sql("select case empno when 10 then deptno else null end from emp "
+ "group by case empno when 10 then emp.deptno else null end").ok();
sql("select case empno when 10 then deptno else null end from emp "
+ "group by case emp.empno when 10 then emp.deptno else null end").ok();
sql("select case emp.empno when 10 then deptno else null end from emp "
+ "group by case empno when 10 then emp.deptno else null end").ok();
// emp.deptno is different to dept.deptno (even though there is an '='
// between them)
sql("select case ^emp.empno^ when 10 then emp.deptno else null end "
+ "from emp join dept on emp.deptno = dept.deptno "
+ "group by case emp.empno when 10 then dept.deptno else null end")
.fails("Expression 'EMP\\.EMPNO' is not being grouped");
}
@Disabled("todo: enable when correlating variables work")
void testGroupExpressionEquivalenceCorrelated() {
// dname comes from dept, so it is constant within the sub-query, and
// is so is a valid expr in a group-by query
sql("select * from dept where exists ("
+ "select dname from emp group by empno)").ok();
sql("select * from dept where exists ("
+ "select dname + empno + 1 from emp group by empno, dept.deptno)").ok();
}
@Disabled("todo: enable when params are implemented")
void testGroupExpressionEquivalenceParams() {
sql("select cast(? as integer) from emp group by cast(? as integer)").ok();
}
@Test void testGroupExpressionEquivalenceLiteral() {
// The purpose of this test is to see whether the validator
// regards a pair of constants as equivalent. If we just used the raw
// constants the validator wouldn't care ('SELECT 1 FROM emp GROUP BY
// 2' is legal), so we combine a column and a constant into the same
// CASE expression.
// literal equivalence
sql("select case empno when 10 then date '1969-04-29' else null end\n"
+ "from emp\n"
+ "group by case empno when 10 then date '1969-04-29' else null end").ok();
// this query succeeds in oracle 10.1 because 1 and 1.0 have the same
// type
sql("select case ^empno^ when 10 then 1 else null end from emp "
+ "group by case empno when 10 then 1.0 else null end")
.fails("Expression 'EMPNO' is not being grouped");
// 3.1415 and 3.14150 are different literals (I don't care either way)
sql("select case ^empno^ when 10 then 3.1415 else null end from emp "
+ "group by case empno when 10 then 3.14150 else null end")
.fails("Expression 'EMPNO' is not being grouped");
// 3 and 03 are the same literal (I don't care either way)
sql("select case empno when 10 then 03 else null end from emp "
+ "group by case empno when 10 then 3 else null end").ok();
sql("select case ^empno^ when 10 then 1 else null end from emp "
+ "group by case empno when 10 then 2 else null end")
.fails("Expression 'EMPNO' is not being grouped");
sql("select case empno when 10 then timestamp '1969-04-29 12:34:56.0'\n"
+ " else null end from emp\n"
+ "group by case empno when 10 then timestamp '1969-04-29 12:34:56'\n"
+ " else null end")
.ok();
}
@Test void testGroupExpressionEquivalenceStringLiteral() {
sql("select case empno when 10 then 'foo bar' else null end from emp "
+ "group by case empno when 10 then 'foo bar' else null end").ok();
if (Bug.FRG78_FIXED) {
final String sql = "select case empno when 10\n"
+ " then _iso-8859-1'foo bar' collate latin1$en$1\n"
+ " else null end\n"
+ "from emp\n"
+ "group by case empno when 10\n"
+ " then _iso-8859-1'foo bar' collate latin1$en$1\n"
+ " else null end";
sql(sql).ok();
}
sql("select case ^empno^ when 10 then _iso-8859-1'foo bar' else null end from emp "
+ "group by case empno when 10 then _UTF16'foo bar' else null end")
.fails("Expression 'EMPNO' is not being grouped");
if (Bug.FRG78_FIXED) {
sql("select case ^empno^ when 10 then 'foo bar' collate latin1$en$1 else null end from emp "
+ "group by case empno when 10 then 'foo bar' collate latin1$fr$1 else null end")
.fails("Expression 'EMPNO' is not being grouped");
}
}
@Test void testGroupByAliasedColumn() {
// alias in GROUP BY query has been known to cause problems
sql("select deptno as d, count(*) as c from emp group by deptno").ok();
}
@Test void testNestedAggFails() {
// simple case
sql("select ^sum(max(empno))^ from emp")
.fails(ERR_NESTED_AGG);
// should still fail with intermediate expression
sql("select ^sum(2*max(empno))^ from emp")
.fails(ERR_NESTED_AGG);
// make sure it fails with GROUP BY too
sql("select ^sum(max(empno))^ from emp group by deptno")
.fails(ERR_NESTED_AGG);
// make sure it fails in HAVING too
sql("select count(*) from emp group by deptno "
+ "having ^sum(max(empno))^=3")
.fails(ERR_NESTED_AGG);
// double-nesting should fail too; bottom-up validation currently
// causes us to flag the intermediate level
sql("select sum(^max(min(empno))^) from emp")
.fails(ERR_NESTED_AGG);
}
@Test void testNestedAggOver() {
sql("select sum(max(empno))\n"
+ " OVER (order by ^deptno^ ROWS 2 PRECEDING)\n"
+ " from emp")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select sum(max(empno)) OVER w\n"
+ " from emp\n"
+ " window w as (order by ^deptno^ ROWS 2 PRECEDING)")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select sum(max(empno)) OVER w2, sum(deptno) OVER w1\n"
+ " from emp group by deptno\n"
+ " window w1 as (partition by ^empno^ ROWS 2 PRECEDING),\n"
+ " w2 as (order by deptno ROWS 2 PRECEDING)")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(count(empno)) over w\n"
+ "from emp group by deptno\n"
+ "window w as (partition by deptno order by ^empno^)")
.fails("Expression 'EMPNO' is not being grouped");
// in OVER clause with more than one level of nesting
sql("select ^avg(sum(min(sal)))^ OVER (partition by deptno)\n"
+ "from emp group by deptno")
.fails(ERR_NESTED_AGG);
// OVER clause columns not appearing in GROUP BY clause NOT OK
sql("select avg(^sal^) OVER (), avg(count(empno)) OVER (partition by 1)\n"
+ " from emp")
.fails("Expression 'SAL' is not being grouped");
sql("select avg(^sal^) OVER (), avg(count(empno)) OVER (partition by deptno)\n"
+ " from emp group by deptno")
.fails("Expression 'SAL' is not being grouped");
sql("select avg(deptno) OVER (partition by ^empno^),\n"
+ " avg(count(empno)) OVER (partition by deptno)\n"
+ " from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(deptno) OVER (order by ^empno^),\n"
+ " avg(count(empno)) OVER (partition by deptno)\n"
+ " from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by ^empno^)\n"
+ " from emp")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by ^empno^)\n"
+ "from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by ^empno^)\n"
+ " from emp")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by ^empno^)\n"
+ "from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by deptno order by ^empno^)\n"
+ "from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(^sal^) OVER (),\n"
+ " avg(count(empno)) OVER (partition by min(deptno))\n"
+ " from emp")
.fails("Expression 'SAL' is not being grouped");
sql("select avg(deptno) OVER (),\n"
+ " avg(count(empno)) OVER (partition by deptno)"
+ " from emp group by deptno").ok();
// COUNT(*), PARTITION BY(CONST) with GROUP BY is OK
sql("select avg(count(*)) OVER ()\n"
+ " from emp group by deptno").ok();
sql("select count(*) OVER ()\n"
+ " from emp group by deptno").ok();
sql("select count(deptno) OVER ()\n"
+ " from emp group by deptno").ok();
sql("select count(deptno, deptno + 1) OVER ()\n"
+ " from emp group by deptno").ok();
sql("select count(deptno, deptno + 1, ^empno^) OVER ()\n"
+ " from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select count(emp.^*^)\n"
+ "from emp group by deptno")
.fails("Unknown field '\\*'");
sql("select count(emp.^*^) OVER ()\n"
+ "from emp group by deptno")
.fails("Unknown field '\\*'");
sql("select avg(count(*)) OVER (partition by 1)\n"
+ " from emp group by deptno").ok();
sql("select avg(sum(sal)) OVER (partition by 1)\n"
+ " from emp").ok();
sql("select avg(sal), avg(count(empno)) OVER (partition by 1)\n"
+ " from emp").ok();
// expression is OK
sql("select avg(sum(sal)) OVER (partition by 10 - deptno\n"
+ " order by deptno / 2 desc)\n"
+ "from emp group by deptno").ok();
sql("select avg(sal),\n"
+ " avg(count(empno)) OVER (partition by min(deptno))\n"
+ " from emp").ok();
sql("select avg(min(sal)) OVER (),\n"
+ " avg(count(empno)) OVER (partition by min(deptno))\n"
+ " from emp").ok();
// expression involving non-GROUP column is not OK
sql("select avg(2+^sal^) OVER (partition by deptno)\n"
+ "from emp group by deptno")
.fails("Expression 'SAL' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by deptno + ^empno^)\n"
+ "from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by empno + deptno)\n"
+ "from emp group by empno + deptno").ok();
sql("select avg(sum(sal)) OVER (partition by empno + deptno + 1)\n"
+ "from emp group by empno + deptno").ok();
sql("select avg(sum(sal)) OVER (partition by ^deptno^ + 1)\n"
+ "from emp group by empno + deptno")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select avg(empno + deptno) OVER (partition by empno + deptno + 1),\n"
+ " count(empno + deptno) OVER (partition by empno + deptno + 1)\n"
+ " from emp group by empno + deptno").ok();
// expression is NOT OK (AS clause)
sql("select sum(max(empno))\n"
+ " OVER (order by ^deptno^ ROWS 2 PRECEDING) AS sumEmpNo\n"
+ " from emp")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select sum(max(empno)) OVER w AS sumEmpNo\n"
+ " from emp\n"
+ " window w AS (order by ^deptno^ ROWS 2 PRECEDING)")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select avg(^sal^) OVER () AS avgSal,"
+ " avg(count(empno)) OVER (partition by 1) AS avgEmpNo\n"
+ " from emp")
.fails("Expression 'SAL' is not being grouped");
sql("select count(deptno, deptno + 1, ^empno^) OVER () AS cntDeptEmp\n"
+ " from emp group by deptno")
.fails("Expression 'EMPNO' is not being grouped");
sql("select avg(sum(sal)) OVER (partition by ^deptno^ + 1) AS avgSal\n"
+ "from emp group by empno + deptno")
.fails("Expression 'DEPTNO' is not being grouped");
// OVER in clause
sql("select ^sum(max(empno) OVER (order by deptno ROWS 2 PRECEDING))^ from emp")
.fails(ERR_NESTED_AGG);
}
@Test void testAggregateInGroupByFails() {
sql("select count(*) from emp group by ^sum(empno)^")
.fails(ERR_AGG_IN_GROUP_BY);
}
@Test void testAggregateInNonGroupBy() {
sql("select count(1), ^empno^ from emp")
.fails("Expression 'EMPNO' is not being grouped");
sql("select count(*) from emp")
.columnType("BIGINT NOT NULL");
sql("select count(deptno) from emp")
.columnType("BIGINT NOT NULL");
// Even though deptno is not null, its sum may be, because emp may be empty.
sql("select sum(deptno) from emp")
.columnType("INTEGER");
sql("select sum(deptno) from emp group by ()")
.columnType("INTEGER");
sql("select sum(deptno) from emp group by empno")
.columnType("INTEGER NOT NULL");
}
@Test void testAggregateInOrderByFails() {
sql("select empno from emp order by ^sum(empno)^")
.fails(ERR_AGG_IN_ORDER_BY);
// but this should be OK
sql("select sum(empno) from emp group by deptno order by sum(empno)").ok();
// this should also be OK
sql("select sum(empno) from emp order by sum(empno)").ok();
}
@Test void testAggregateFilter() {
sql("select sum(empno) filter (where deptno < 10) as s from emp")
.type("RecordType(INTEGER S) NOT NULL");
}
@Test void testAggregateFilterNotBoolean() {
sql("select sum(empno) filter (where ^deptno + 10^) from emp")
.fails("FILTER clause must be a condition");
}
@Test void testAggregateFilterInHaving() {
sql("select sum(empno) as s from emp\n"
+ "group by deptno\n"
+ "having sum(empno) filter (where deptno < 20) > 10")
.ok();
}
@Test void testAggregateFilterContainsAggregate() {
sql("select sum(empno) filter (where ^count(*) < 10^) from emp")
.fails("FILTER must not contain aggregate expression");
}
@Test void testWithinGroup() {
sql("select deptno,\n"
+ " collect(empno) within group(order by 1)\n"
+ "from emp\n"
+ "group by deptno").ok();
sql("select collect(empno) within group(order by 1)\n"
+ "from emp\n"
+ "group by ()").ok();
sql("select deptno,\n"
+ " collect(empno) within group(order by deptno)\n"
+ "from emp\n"
+ "group by deptno").ok();
sql("select deptno,\n"
+ " collect(empno) within group(order by deptno, hiredate desc)\n"
+ "from emp\n"
+ "group by deptno").ok();
sql("select deptno,\n"
+ " collect(empno) within group(\n"
+ " order by cast(deptno as varchar), hiredate desc)\n"
+ "from emp\n"
+ "group by deptno").ok();
sql("select collect(empno) within group(order by 1)\n"
+ "from emp\n"
+ "group by deptno").ok();
sql("select collect(empno) within group(order by 1)\n"
+ "from emp").ok();
sql("select ^power(deptno, 1) within group(order by 1)^ from emp")
.fails("(?s).*WITHIN GROUP not allowed with POWER function.*");
sql("select ^collect(empno)^ within group(order by count(*))\n"
+ "from emp\n"
+ "group by deptno")
.fails("WITHIN GROUP must not contain aggregate expression");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4644">[CALCITE-4644]
* Add PERCENTILE_CONT and PERCENTILE_DISC aggregate functions</a>. */
@Test void testPercentile() {
final String sql = "select\n"
+ " percentile_cont(0.25) within group (order by sal) as c,\n"
+ " percentile_disc(0.5) within group (order by sal desc) as d\n"
+ "from emp\n"
+ "group by deptno";
sql(sql)
.type("RecordType(INTEGER NOT NULL C, INTEGER NOT NULL D) NOT NULL");
}
/** Tests that {@code PERCENTILE_CONT} only allows numeric fields. */
@Test void testPercentileContMustOrderByNumeric() {
final String sql = "select\n"
+ " percentile_cont(0.25) within group (^order by ename^)\n"
+ "from emp";
sql(sql)
.fails("Invalid type 'VARCHAR' in ORDER BY clause of "
+ "'PERCENTILE_CONT' function. Only NUMERIC types are supported");
}
/** Tests that {@code PERCENTILE_CONT} only allows one sort key. */
@Test void testPercentileContMultipleOrderByFields() {
final String sql = "select\n"
+ " percentile_cont(0.25) within group (^order by deptno, empno^)\n"
+ "from emp";
sql(sql)
.fails("'PERCENTILE_CONT' requires precisely one ORDER BY key");
}
@Test void testPercentileContFractionMustBeLiteral() {
final String sql = "select\n"
+ " ^percentile_cont(deptno)^ within group (order by empno)\n"
+ "from emp\n"
+ "group by deptno";
sql(sql)
.fails("Argument to function 'PERCENTILE_CONT' must be a literal");
}
@Test void testPercentileContFractionOutOfRange() {
final String sql = "select\n"
+ " ^percentile_cont(1.5)^ within group (order by deptno)\n"
+ "from emp";
sql(sql)
.fails("Argument to function 'PERCENTILE_CONT' must be a numeric "
+ "literal between 0 and 1");
}
/** Tests that {@code PERCENTILE_DISC} only allows numeric fields. */
@Test void testPercentileDiscMustOrderByNumeric() {
final String sql = "select\n"
+ " percentile_disc(0.25) within group (^order by ename^)\n"
+ "from emp";
sql(sql)
.fails("Invalid type 'VARCHAR' in ORDER BY clause of "
+ "'PERCENTILE_DISC' function. Only NUMERIC types are supported");
}
/** Tests that {@code PERCENTILE_DISC} only allows one sort key. */
@Test void testPercentileDiscMultipleOrderByFields() {
final String sql = "select\n"
+ " percentile_disc(0.25) within group (^order by deptno, empno^)\n"
+ "from emp";
sql(sql)
.fails("'PERCENTILE_DISC' requires precisely one ORDER BY key");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3679">[CALCITE-3679]
* Allow lambda expressions in SQL queries</a>. */
@Test void testHigherOrderFunction() {
final SqlValidatorFixture s = fixture()
.withOperatorTable(MockSqlOperatorTable.standard().extend());
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> x + 1)").ok();
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> y)").ok();
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> char_length(x) + 1)").ok();
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> cast(null as integer))").ok();
s.withSql("select HIGHER_ORDER_FUNCTION2(1, () -> 0.1)").ok();
s.withSql("select emp.deptno, HIGHER_ORDER_FUNCTION(1, (x, deptno) -> deptno) from emp").ok();
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> char_length(x) + 1)")
.type("RecordType(INTEGER NOT NULL EXPR$0) NOT NULL");
s.withSql("select HIGHER_ORDER_FUNCTION2(1, () -> 0.1)")
.type("RecordType(INTEGER NOT NULL EXPR$0) NOT NULL");
// test for type check
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> ^x + 1^)")
.withTypeCoercion(false)
.fails("(?s)Cannot apply '\\+' to arguments of type '<VARCHAR> \\+ <INTEGER>'\\..*");
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> ^null^)")
.withTypeCoercion(false)
.fails("(?s)Illegal use of 'NULL'.*");
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> ^array[1] = x^)")
.fails("Cannot apply '=' to arguments of type '<INTEGER ARRAY> = <VARCHAR>'.*");
s.withSql("select ^HIGHER_ORDER_FUNCTION(1, null)^")
.fails("Cannot apply '(?s).*HIGHER_ORDER_FUNCTION' to arguments of type "
+ "'HIGHER_ORDER_FUNCTION\\(<INTEGER>, <NULL>\\)'.*");
s.withSql("select ^HIGHER_ORDER_FUNCTION(1, (x, y, z) -> x + 1)^")
.fails("Cannot apply '(?s).*HIGHER_ORDER_FUNCTION' to arguments of type "
+ "'HIGHER_ORDER_FUNCTION\\(<INTEGER>, <FUNCTION\\(ANY, ANY, ANY\\) -> ANY>\\)'.*");
// test for illegal parameters
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> x + 1 + ^emp.deptno^) from emp")
.fails("Param 'EMP\\.DEPTNO' not found in lambda expression "
+ "'\\(`X`, `Y`\\) -> `X` \\+ 1 \\+ `EMP`\\.`DEPTNO`'");
s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> x + 1 + ^deptno^) from emp")
.fails("Param 'DEPTNO' not found in lambda expression "
+ "'\\(`X`, `Y`\\) -> `X` \\+ 1 \\+ `DEPTNO`'");
}
@Test void testPercentileFunctionsBigQuery() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
final String sql = "select\n"
+ " percentile_cont(sal, 0.25) over() as c,\n"
+ " percentile_disc(sal, 0.5) over() as d\n"
+ "from emp";
sql(sql)
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable)
.type("RecordType(DOUBLE NOT NULL C, INTEGER NOT NULL D) NOT NULL");
}
@Test void testPercentileContBigQueryFraction() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
final String sql = "select\n"
+ "^percentile_cont(sal, 1.5)^ over() as c\n"
+ "from emp as x";
sql(sql)
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable)
.fails("Argument to function 'PERCENTILE_CONT' must be a numeric "
+ "literal between 0 and 1");
}
@Test void testPercentileContBigQueryAllowsNullTreatment() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
final String sql = "select\n"
+ "percentile_cont(sal, 1 RESPECT NULLS) over() as c\n"
+ "from emp";
sql(sql)
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable)
.type("RecordType(DOUBLE NOT NULL C) NOT NULL");
}
@Test void testPercentileDiscBigQueryFraction() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
final String sql = "select\n"
+ "^percentile_disc(sal, 1.5)^ over() as c\n"
+ "from emp";
sql(sql)
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable)
.fails("Argument to function 'PERCENTILE_DISC' must be a numeric "
+ "literal between 0 and 1");
}
@Test void testPercentileDiscBigQueryAllowsNullTreatment() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
final String sql = "select\n"
+ "percentile_disc(sal, 1 RESPECT NULLS) over() as c\n"
+ "from emp";
sql(sql)
.withConformance(SqlConformanceEnum.BIG_QUERY)
.withOperatorTable(opTable)
.type("RecordType(INTEGER NOT NULL C) NOT NULL");
}
@Test void testCorrelatingVariables() {
// reference to unqualified correlating column
sql("select * from emp where exists (\n"
+ "select * from dept where deptno = sal)").ok();
// reference to qualified correlating column
sql("select * from emp where exists (\n"
+ "select * from dept where deptno = emp.sal)").ok();
}
@Test void testIntervalCompare() {
expr("interval '1' hour = interval '1' day")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' hour <> interval '1' hour")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' hour < interval '1' second")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' hour <= interval '1' minute")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' minute > interval '1' second")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' second >= interval '1' day")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' year >= interval '1' year")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' month = interval '1' year")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' month <> interval '1' month")
.columnType("BOOLEAN NOT NULL");
expr("interval '1' year >= interval '1' month")
.columnType("BOOLEAN NOT NULL");
wholeExpr("interval '1' second >= interval '1' year")
.fails("(?s).*Cannot apply '>=' to arguments of type "
+ "'<INTERVAL SECOND> >= <INTERVAL YEAR>'.*");
wholeExpr("interval '1' month = interval '1' day")
.fails("(?s).*Cannot apply '=' to arguments of type "
+ "'<INTERVAL MONTH> = <INTERVAL DAY>'.*");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-613">[CALCITE-613]
* Implicitly convert strings in comparisons</a>. */
@Test void testDateCompare() {
// can convert character value to date, time, timestamp, interval
// provided it is on one side of a comparison operator (=, <, >, BETWEEN)
expr("date '2015-03-17' < '2015-03-18'")
.columnType("BOOLEAN NOT NULL");
expr("date '2015-03-17' > '2015-03-18'")
.columnType("BOOLEAN NOT NULL");
expr("date '2015-03-17' = '2015-03-18'")
.columnType("BOOLEAN NOT NULL");
expr("'2015-03-17' < date '2015-03-18'")
.columnType("BOOLEAN NOT NULL");
expr("date '2015-03-17' between '2015-03-16' and '2015-03-19'")
.columnType("BOOLEAN NOT NULL");
expr("date '2015-03-17' between '2015-03-16' and '2015-03'||'-19'")
.columnType("BOOLEAN NOT NULL");
expr("'2015-03-17' between date '2015-03-16' and date '2015-03-19'")
.columnType("BOOLEAN NOT NULL");
expr("date '2015-03-17' between date '2015-03-16' and '2015-03-19'")
.columnType("BOOLEAN NOT NULL");
expr("date '2015-03-17' between '2015-03-16' and date '2015-03-19'")
.columnType("BOOLEAN NOT NULL");
expr("time '12:34:56' < '12:34:57'")
.columnType("BOOLEAN NOT NULL");
expr("timestamp '2015-03-17 12:34:56' < '2015-03-17 12:34:57'")
.columnType("BOOLEAN NOT NULL");
expr("interval '2' hour < '2:30'")
.columnType("BOOLEAN NOT NULL");
// can convert to exact and approximate numeric
expr("123 > '72'")
.columnType("BOOLEAN NOT NULL");
expr("12.3 > '7.2'")
.columnType("BOOLEAN NOT NULL");
// can convert to boolean
expr("true = 'true'")
.columnType("BOOLEAN NOT NULL");
expr("^true and 'true'^")
.fails("Cannot apply 'AND' to arguments of type '<BOOLEAN> AND <CHAR\\(4\\)>'\\..*");
}
@Test void testOverlaps() {
expr("(date '1-2-3', date '1-2-3')\n"
+ " overlaps (date '1-2-3', date '1-2-3')")
.columnType("BOOLEAN NOT NULL");
expr("period (date '1-2-3', date '1-2-3')\n"
+ " overlaps period (date '1-2-3', date '1-2-3')")
.columnType("BOOLEAN NOT NULL");
expr("(date '1-2-3', date '1-2-3') overlaps (date '1-2-3', interval '1' "
+ "year)").ok();
expr("(time '1:2:3', interval '1' second) overlaps (time '23:59:59', time"
+ " '1:2:3')").ok();
expr("(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' ) overlaps "
+ "(timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to second)").ok();
wholeExpr("(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' )\n"
+ " overlaps (time '4:5:6', interval '1 2:3:4.5' day to second)")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
wholeExpr("(time '4:5:6', timestamp '1-2-3 4:5:6' )\n"
+ " overlaps (time '4:5:6', interval '1 2:3:4.5' day to second)")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
wholeExpr("(time '4:5:6', time '4:5:6' )\n"
+ " overlaps (time '4:5:6', date '1-2-3')")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
wholeExpr("1 overlaps 2")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type "
+ "'<INTEGER> OVERLAPS <INTEGER>'\\. Supported form.*");
expr("true\n"
+ "or (date '1-2-3', date '1-2-3')\n"
+ " overlaps (date '1-2-3', date '1-2-3')\n"
+ "or false")
.columnType("BOOLEAN NOT NULL");
// row with 3 arguments as left argument to overlaps
expr("true\n"
+ "or ^(date '1-2-3', date '1-2-3', date '1-2-3')\n"
+ " overlaps (date '1-2-3', date '1-2-3')^\n"
+ "or false")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
// row with 3 arguments as right argument to overlaps
expr("true\n"
+ "or ^(date '1-2-3', date '1-2-3')\n"
+ " overlaps (date '1-2-3', date '1-2-3', date '1-2-3')^\n"
+ "or false")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
expr("^period (date '1-2-3', date '1-2-3')\n"
+ " overlaps (date '1-2-3', date '1-2-3', date '1-2-3')^")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
expr("true\n"
+ "or ^(1, 2) overlaps (2, 3)^\n"
+ "or false")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
// Other operators with similar syntax
String[] ops = {
"overlaps", "contains", "equals", "precedes", "succeeds",
"immediately precedes", "immediately succeeds"
};
for (String op : ops) {
expr("period (date '1-2-3', date '1-2-3')\n"
+ " " + op + " period (date '1-2-3', date '1-2-3')")
.columnType("BOOLEAN NOT NULL");
expr("(date '1-2-3', date '1-2-3')\n"
+ " " + op + " (date '1-2-3', date '1-2-3')")
.columnType("BOOLEAN NOT NULL");
}
}
@Test void testContains() {
final String cannotApply =
"(?s).*Cannot apply 'CONTAINS' to arguments of type .*";
expr("(date '1-2-3', date '1-2-3')\n"
+ " contains (date '1-2-3', date '1-2-3')")
.columnType("BOOLEAN NOT NULL");
expr("period (date '1-2-3', date '1-2-3')\n"
+ " contains period (date '1-2-3', date '1-2-3')")
.columnType("BOOLEAN NOT NULL");
expr("(date '1-2-3', date '1-2-3')\n"
+ " contains (date '1-2-3', interval '1' year)").ok();
expr("(time '1:2:3', interval '1' second)\n"
+ " contains (time '23:59:59', time '1:2:3')").ok();
expr("(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6')\n"
+ " contains (timestamp '1-2-3 4:5:6', interval '1 2:3:4.5' day to "
+ "second)").ok();
// period contains point
expr("(date '1-2-3', date '1-2-3')\n"
+ " contains date '1-2-3'").ok();
// same, with "period" keyword
expr("period (date '1-2-3', date '1-2-3')\n"
+ " contains date '1-2-3'").ok();
// point contains period
wholeExpr("date '1-2-3'\n"
+ " contains (date '1-2-3', date '1-2-3')")
.fails(cannotApply);
// same, with "period" keyword
wholeExpr("date '1-2-3'\n"
+ " contains period (date '1-2-3', date '1-2-3')")
.fails(cannotApply);
// point contains point
wholeExpr("date '1-2-3' contains date '1-2-3'")
.fails(cannotApply);
wholeExpr("(timestamp '1-2-3 4:5:6', timestamp '1-2-3 4:5:6' )\n"
+ " contains (time '4:5:6', interval '1 2:3:4.5' day to second)")
.fails(cannotApply);
wholeExpr("(time '4:5:6', timestamp '1-2-3 4:5:6' )\n"
+ " contains (time '4:5:6', interval '1 2:3:4.5' day to second)")
.fails(cannotApply);
wholeExpr("(time '4:5:6', time '4:5:6' )\n"
+ " contains (time '4:5:6', date '1-2-3')")
.fails(cannotApply);
wholeExpr("1 contains 2")
.fails(cannotApply);
// row with 3 arguments
expr("true\n"
+ "or ^(date '1-2-3', date '1-2-3', date '1-2-3')\n"
+ " contains (date '1-2-3', date '1-2-3')^\n"
+ "or false")
.fails(cannotApply);
expr("true\n"
+ "or (date '1-2-3', date '1-2-3')\n"
+ " contains (date '1-2-3', date '1-2-3')\n"
+ "or false")
.columnType("BOOLEAN NOT NULL");
// second argument is a point
expr("true\n"
+ "or (date '1-2-3', date '1-2-3')\n"
+ " contains date '1-2-3'\n"
+ "or false")
.columnType("BOOLEAN NOT NULL");
// first argument may be null, so result may be null
expr("true\n"
+ "or (date '1-2-3',\n"
+ " case 1 when 2 then date '1-2-3' else null end)\n"
+ " contains date '1-2-3'\n"
+ "or false")
.columnType("BOOLEAN");
// second argument may be null, so result may be null
expr("true\n"
+ "or (date '1-2-3', date '1-2-3')\n"
+ " contains case 1 when 1 then date '1-2-3' else null end\n"
+ "or false")
.columnType("BOOLEAN");
expr("true\n"
+ "or ^period (date '1-2-3', date '1-2-3')\n"
+ " contains period (date '1-2-3', time '4:5:6')^\n"
+ "or false")
.fails(cannotApply);
expr("true\n"
+ "or ^(1, 2) contains (2, 3)^\n"
+ "or false")
.fails(cannotApply);
}
@Test void testExtract() {
// TODO: Need to have extract return decimal type for seconds
// so we can have seconds fractions
expr("extract(year from interval '1-2' year to month)")
.columnType("BIGINT NOT NULL");
expr("extract(minute from interval '1.1' second)").ok();
expr("extract(year from DATE '2008-2-2')").ok();
expr("extract(minute from interval '11' month)").ok();
wholeExpr("extract(year from interval '11' second)")
.fails("(?s).*Cannot apply.*");
}
@Test void testCastToInterval() {
expr("cast(interval '1' hour as varchar(20))")
.columnType("VARCHAR(20) NOT NULL");
expr("cast(interval '1' hour as bigint)")
.columnType("BIGINT NOT NULL");
expr("cast(1000 as interval hour)")
.columnType("INTERVAL HOUR NOT NULL");
expr("cast(interval '1' month as interval year)")
.columnType("INTERVAL YEAR NOT NULL");
expr("cast(interval '1-1' year to month as interval month)")
.columnType("INTERVAL MONTH NOT NULL");
expr("cast(interval '1:1' hour to minute as interval day)")
.columnType("INTERVAL DAY NOT NULL");
expr("cast(interval '1:1' hour to minute as interval minute to second)")
.columnType("INTERVAL MINUTE TO SECOND NOT NULL");
wholeExpr("cast(interval '1:1' hour to minute as interval month)")
.fails("Cast function cannot convert value of type "
+ "INTERVAL HOUR TO MINUTE to type INTERVAL MONTH");
wholeExpr("cast(interval '1-1' year to month as interval second)")
.fails("Cast function cannot convert value of type "
+ "INTERVAL YEAR TO MONTH to type INTERVAL SECOND");
}
@Test void testMinusDateOperator() {
expr("(CURRENT_DATE - CURRENT_DATE) HOUR")
.columnType("INTERVAL HOUR NOT NULL");
expr("(CURRENT_DATE - CURRENT_DATE) YEAR TO MONTH")
.columnType("INTERVAL YEAR TO MONTH NOT NULL");
wholeExpr("(CURRENT_DATE - LOCALTIME) YEAR TO MONTH")
.fails("(?s).*Parameters must be of the same type.*");
}
@Test void testBind() {
sql("select * from emp where deptno = ?").ok();
sql("select * from emp where deptno = ? and sal < 100000").ok();
sql("select case when deptno = ? then 1 else 2 end from emp").ok();
// It is not possible to infer type of ?, because SUBSTRING is overloaded
sql("select deptno from emp group by substring(ename from ^?^ for ?)")
.fails("Illegal use of dynamic parameter");
// In principle we could infer that ? should be a VARCHAR
sql("select count(*) from emp group by position(^?^ in ename)")
.fails("Illegal use of dynamic parameter");
sql("select ^deptno^ from emp\n"
+ "group by case when deptno = ? then 1 else 2 end")
.fails("Expression 'DEPTNO' is not being grouped");
sql("select deptno from emp\n"
+ "group by deptno, case when deptno = ? then 1 else 2 end").ok();
sql("select 1 from emp having sum(sal) < ?").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1310">[CALCITE-1310]
* Infer type of arguments to BETWEEN operator</a>. */
@Test void testBindBetween() {
sql("select * from emp where ename between ? and ?").ok();
sql("select * from emp where deptno between ? and ?").ok();
sql("select * from emp where ? between deptno and ?").ok();
sql("select * from emp where ? between ? and deptno").ok();
sql("select * from emp where ^?^ between ? and ?")
.fails("Illegal use of dynamic parameter");
}
@Test void testUnnest() {
sql("select*from unnest(multiset[1])")
.columnType("INTEGER NOT NULL");
sql("select*from unnest(multiset[1, 2])")
.columnType("INTEGER NOT NULL");
sql("select*from unnest(multiset[321.3, 2.33])")
.columnType("DECIMAL(5, 2) NOT NULL");
sql("select*from unnest(multiset[321.3, 4.23e0])")
.columnType("DOUBLE NOT NULL");
sql("select*from unnest(multiset[43.2e1, cast(null as decimal(4,2))])")
.columnType("DOUBLE");
sql("select*from unnest(multiset[1, 2.3, 1])")
.columnType("DECIMAL(11, 1) NOT NULL");
sql("select*from unnest(multiset['1','22','333'])")
.columnType("CHAR(3) NOT NULL");
sql("select*from unnest(multiset['1','22','333','22'])")
.columnType("CHAR(3) NOT NULL");
sql("select*from ^unnest(1)^")
.fails("(?s).*Cannot apply 'UNNEST' to arguments of type 'UNNEST.<INTEGER>.'.*");
sql("select*from unnest(multiset(select*from dept))").ok();
sql("select c from unnest(multiset(select deptno from dept)) as t(c)").ok();
sql("select c from unnest(multiset(select * from dept)) as t(^c^)")
.fails("List of column aliases must have same degree as table; "
+ "table has 2 columns \\('DEPTNO', 'NAME'\\), "
+ "whereas alias list has 1 columns");
sql("select ^c1^ from unnest(multiset(select name from dept)) as t(c)")
.fails("Column 'C1' not found in any table");
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-3789">[CALCITE-3789]
* Support validation of UNNEST multiple array columns like Presto</a>.
*/
@Test void testAliasUnnestMultipleArrays() {
// for accessing a field in STRUCT type unnested from array
sql("select e.ENAME\n"
+ "from dept_nested_expanded as d CROSS JOIN\n"
+ " UNNEST(d.employees) as t(e)")
.withConformance(SqlConformanceEnum.PRESTO).columnType("VARCHAR(10) NOT NULL");
// for unnesting multiple arrays at the same time
sql("select d.deptno, e, k.empno, l.\"unit\", l.\"X\" * l.\"Y\"\n"
+ "from dept_nested_expanded as d CROSS JOIN\n"
+ " UNNEST(d.admins, d.employees, d.offices) as t(e, k, l)")
.withConformance(SqlConformanceEnum.PRESTO).ok();
// Make sure validation fails properly given illegal select items
sql("select d.deptno, ^e^.some_column, k.empno\n"
+ "from dept_nested_expanded as d CROSS JOIN\n"
+ " UNNEST(d.admins, d.employees) as t(e, k)")
.withConformance(SqlConformanceEnum.PRESTO)
.fails("Table 'E' not found");
sql("select d.deptno, e.detail, ^unknown^.detail\n"
+ "from dept_nested_expanded as d CROSS JOIN\n"
+ " UNNEST(d.employees) as t(e)")
.withConformance(SqlConformanceEnum.PRESTO).fails("Incompatible types");
// Make sure validation fails without PRESTO conformance
sql("select e.ENAME\n"
+ "from dept_nested_expanded as d CROSS JOIN\n"
+ " UNNEST(d.employees) as t(^e^)")
.fails("List of column aliases must have same degree as table; table has 3 columns "
+ "\\('EMPNO', 'ENAME', 'DETAIL'\\), whereas alias list has 1 columns");
}
@Test void testUnnestArray() {
sql("select*from unnest(array[1])")
.columnType("INTEGER NOT NULL");
sql("select*from unnest(array[1, 2])")
.columnType("INTEGER NOT NULL");
sql("select*from unnest(array[321.3, 2.33])")
.columnType("DECIMAL(5, 2) NOT NULL");
sql("select*from unnest(array[321.3, 4.23e0])")
.columnType("DOUBLE NOT NULL");
sql("select*from unnest(array[43.2e1, cast(null as decimal(4,2))])")
.columnType("DOUBLE");
sql("select*from unnest(array[1, 2.3, 1])")
.columnType("DECIMAL(11, 1) NOT NULL");
sql("select*from unnest(array['1','22','333'])")
.columnType("CHAR(3) NOT NULL");
sql("select*from unnest(array['1','22','333','22'])")
.columnType("CHAR(3) NOT NULL");
sql("select*from unnest(array['1','22',null,'22'])")
.columnType("CHAR(2)");
sql("select*from ^unnest(1)^")
.fails("(?s).*Cannot apply 'UNNEST' to arguments of type 'UNNEST.<INTEGER>.'.*");
sql("select*from unnest(array(select*from dept))").ok();
sql("select*from unnest(array[1,null])").ok();
sql("select c from unnest(array(select deptno from dept)) as t(c)").ok();
sql("select c from unnest(array(select * from dept)) as t(^c^)")
.fails("List of column aliases must have same degree as table; "
+ "table has 2 columns \\('DEPTNO', 'NAME'\\), "
+ "whereas alias list has 1 columns");
sql("select ^c1^ from unnest(array(select name from dept)) as t(c)")
.fails("Column 'C1' not found in any table");
}
@Test void testArrayConstructor() {
sql("select array[1,2] as a from (values (1))")
.columnType("INTEGER NOT NULL ARRAY NOT NULL");
sql("select array[1,cast(null as integer), 2] as a\n"
+ "from (values (1))")
.columnType("INTEGER ARRAY NOT NULL");
sql("select array[1,null,2] as a from (values (1))")
.columnType("INTEGER ARRAY NOT NULL");
sql("select array['1',null,'234',''] as a from (values (1))")
.columnType("CHAR(3) ARRAY NOT NULL");
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4999">[CALCITE-4999]
* ARRAY, MULTISET functions should return a collection of scalars
* if a sub-query returns 1 column</a>.
*/
@Test void testArrayQueryConstructor() {
sql("select array(select 1)")
.columnType("INTEGER NOT NULL ARRAY NOT NULL");
sql("select array(select ROW(1,2))")
.columnType(
"RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) NOT NULL ARRAY NOT NULL");
}
@Test void testCastAsCollectionType() {
sql("select cast(array[1,null,2] as int array) from (values (1))")
.columnType("INTEGER ARRAY NOT NULL");
sql("select cast(array['1',null,'2'] as varchar(5) array) from (values (1))")
.columnType("VARCHAR(5) ARRAY NOT NULL");
sql("select cast(multiset[1,null,2] as int multiset) from (values (1))")
.columnType("INTEGER MULTISET NOT NULL");
sql("select cast(array[1,null,2] as int multiset) from (values (1))")
.columnType("INTEGER MULTISET NOT NULL");
// test array type.
sql("select cast(\"intArrayType\" as int array) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("INTEGER NOT NULL ARRAY NOT NULL");
sql("select cast(\"varchar5ArrayType\" as varchar(5) array) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("VARCHAR(5) NOT NULL ARRAY NOT NULL");
sql("select cast(\"intArrayArrayType\" as int array array) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("INTEGER NOT NULL ARRAY NOT NULL ARRAY NOT NULL");
sql("select cast(\"varchar5ArrayArrayType\" as varchar(5) array array) "
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("VARCHAR(5) NOT NULL ARRAY NOT NULL ARRAY NOT NULL");
// test multiset type.
sql("select cast(\"intMultisetType\" as int multiset) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("INTEGER NOT NULL MULTISET NOT NULL");
sql("select cast(\"varchar5MultisetType\" as varchar(5) multiset) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("VARCHAR(5) NOT NULL MULTISET NOT NULL");
sql("select cast(\"intMultisetArrayType\" as int multiset array) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("INTEGER NOT NULL MULTISET NOT NULL ARRAY NOT NULL");
sql("select cast(\"varchar5MultisetArrayType\" as varchar(5) multiset array) "
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("VARCHAR(5) NOT NULL MULTISET NOT NULL ARRAY NOT NULL");
// test row type nested in collection type.
sql("select cast(\"rowArrayMultisetType\" as row(f0 int array multiset, "
+ "f1 varchar(5) array) array multiset) "
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("RecordType(INTEGER NOT NULL ARRAY NOT NULL MULTISET NOT NULL F0, "
+ "VARCHAR(5) NOT NULL ARRAY NOT NULL F1) NOT NULL "
+ "ARRAY NOT NULL MULTISET NOT NULL");
// test UDT collection type.
sql("select cast(a as ^MyUDT^ array multiset) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.fails("Unknown identifier 'MYUDT'");
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5570">[CALCITE-5570]
* Support nested map type for SqlDataTypeSpec</a>.
*/
@Test void testCastMapType() {
sql("select cast(\"int2IntMapType\" as map<int,int>) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("(INTEGER NOT NULL, INTEGER NOT NULL) MAP NOT NULL");
sql("select cast(\"int2varcharArrayMapType\" as map<int,varchar array>) "
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("(INTEGER NOT NULL, VARCHAR NOT NULL ARRAY NOT NULL) MAP NOT NULL");
sql("select cast(\"varcharMultiset2IntIntMapType\" as map<varchar(5) multiset, map<int, int>>)"
+ " from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("(VARCHAR(5) NOT NULL MULTISET NOT NULL, "
+ "(INTEGER NOT NULL, INTEGER NOT NULL) MAP NOT NULL) MAP NOT NULL");
}
@Test void testCastAsRowType() {
sql("select cast(a as row(f0 int, f1 varchar)) from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("RecordType(INTEGER NOT NULL F0, VARCHAR NOT NULL F1) NOT NULL");
sql("select cast(b as row(f0 int not null, f1 varchar null))\n"
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("RecordType(INTEGER NOT NULL F0, VARCHAR F1) NOT NULL");
// test nested row type.
sql("select "
+ "cast(c as row("
+ "f0 row(ff0 int not null, ff1 varchar null) null, "
+ "f1 timestamp not null))"
+ " from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("RecordType("
+ "RecordType(INTEGER FF0, VARCHAR FF1) F0, "
+ "TIMESTAMP(0) NOT NULL F1) NOT NULL");
// test row type in collection data types.
sql("select cast(d as row(f0 bigint not null, f1 decimal null) array)\n"
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("RecordType(BIGINT NOT NULL F0, DECIMAL(19, 0) F1) NOT NULL "
+ "ARRAY NOT NULL");
sql("select cast(e as row(f0 varchar not null, f1 timestamp null) multiset)\n"
+ "from COMPLEXTYPES.CTC_T1")
.withExtendedCatalog()
.columnType("RecordType(VARCHAR NOT NULL F0, TIMESTAMP(0) F1) NOT NULL "
+ "MULTISET NOT NULL");
}
@Test void testSafeCastAsCollectionType() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.BIG_QUERY);
sql("select safe_cast(array[1,null,2] as int array) from (values (1))")
.withOperatorTable(opTable)
.columnType("INTEGER ARRAY");
sql("select safe_cast(multiset[1,null,2] as int multiset) from (values (1))")
.withOperatorTable(opTable)
.columnType("INTEGER MULTISET");
// test array type.
sql("select safe_cast(\"varchar5ArrayArrayType\" as varchar(5) array array) "
+ "from COMPLEXTYPES.CTC_T1")
.withOperatorTable(opTable)
.withExtendedCatalog()
.columnType("VARCHAR(5) ARRAY ARRAY");
// test multiset type.
sql("select safe_cast(\"varchar5MultisetArrayType\" as varchar(5) multiset array) "
+ "from COMPLEXTYPES.CTC_T1")
.withOperatorTable(opTable)
.withExtendedCatalog()
.columnType("VARCHAR(5) MULTISET ARRAY");
}
@Test void testTryCastAsRowType() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.MSSQL);
sql("select try_cast(a as row(f0 int, f1 varchar)) from COMPLEXTYPES.CTC_T1")
.withOperatorTable(opTable)
.withExtendedCatalog()
.columnType("RecordType(INTEGER F0, VARCHAR F1)");
// test nested row type.
sql("select "
+ "try_cast(c as row("
+ "f0 row(ff0 int, ff1 varchar), "
+ "f1 timestamp))"
+ " from COMPLEXTYPES.CTC_T1")
.withOperatorTable(opTable)
.withExtendedCatalog()
.columnType("RecordType("
+ "RecordType(INTEGER FF0, VARCHAR FF1) F0, "
+ "TIMESTAMP(0) F1)");
// test row type in collection data types.
sql("select try_cast(d as row(f0 bigint, f1 decimal) array)\n"
+ "from COMPLEXTYPES.CTC_T1")
.withOperatorTable(opTable)
.withExtendedCatalog()
.columnType("RecordType(BIGINT F0, DECIMAL(19, 0) F1) "
+ "ARRAY");
}
@Test void testMultisetConstructor() {
sql("select multiset[1,null,2] as a from (values (1))")
.columnType("INTEGER MULTISET NOT NULL");
}
/**
* Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4999">[CALCITE-4999]
* ARRAY, MULTISET functions should return an collection of scalars
* if a sub-query returns 1 column</a>.
*/
@Test void testMultisetQueryConstructor() {
sql("select multiset(select 1)")
.columnType("INTEGER NOT NULL MULTISET NOT NULL");
sql("select multiset(select ROW(1,2))")
.columnType(
"RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) NOT NULL MULTISET NOT NULL");
}
@Test void testUnnestArrayColumn() {
final String sql1 = "select d.deptno, e.*\n"
+ "from dept_nested as d,\n"
+ " UNNEST(d.employees) as e";
final String type = "RecordType(INTEGER NOT NULL DEPTNO,"
+ " INTEGER NOT NULL EMPNO,"
+ " VARCHAR(10) NOT NULL ENAME,"
+ " RecordType(RecordType(VARCHAR(10) NOT NULL TYPE, VARCHAR(20) NOT NULL DESC,"
+ " RecordType(VARCHAR(10) NOT NULL A, VARCHAR(10) NOT NULL B) NOT NULL OTHERS)"
+ " NOT NULL ARRAY NOT NULL SKILLS) NOT NULL DETAIL) NOT NULL";
sql(sql1).type(type);
// equivalent query without table alias
final String sql1b = "select d.deptno, e.*\n"
+ "from dept_nested as d,\n"
+ " UNNEST(employees) as e";
sql(sql1b).type(type);
// equivalent query using CROSS JOIN
final String sql2 = "select d.deptno, e.*\n"
+ "from dept_nested as d CROSS JOIN\n"
+ " UNNEST(d.employees) as e";
sql(sql2).type(type);
// equivalent query using CROSS JOIN, without table alias
final String sql2b = "select d.deptno, e.*\n"
+ "from dept_nested as d CROSS JOIN\n"
+ " UNNEST(employees) as e";
sql(sql2b).type(type);
// LATERAL works left-to-right
final String sql3 = "select d.deptno, e.*\n"
+ "from UNNEST(^d^.employees) as e, dept_nested as d";
sql(sql3).fails("Table 'D' not found");
}
@Test void testUnnestWithOrdinality() {
sql("select*from unnest(array[1, 2]) with ordinality")
.type("RecordType(INTEGER NOT NULL EXPR$0, INTEGER NOT NULL ORDINALITY) NOT NULL");
sql("select*from unnest(array[43.2e1, cast(null as decimal(4,2))]) with ordinality")
.type("RecordType(DOUBLE EXPR$0, INTEGER NOT NULL ORDINALITY) NOT NULL");
sql("select * from unnest(array(select deptno from dept)) with ordinality as t")
.type("RecordType(INTEGER NOT NULL T, INTEGER NOT NULL ORDINALITY) NOT NULL");
sql("select*from ^unnest(1) with ordinality^")
.fails("(?s).*Cannot apply 'UNNEST' to arguments of type 'UNNEST.<INTEGER>.'.*");
sql("select deptno\n"
+ "from unnest(array(select*from dept)) with ordinality\n"
+ "where ordinality < 5").ok();
sql("select c from unnest(\n"
+ " array(select deptno from dept)) with ordinality as t(^c^)")
.fails("List of column aliases must have same degree as table; table has 2 "
+ "columns \\('EXPR\\$0', 'ORDINALITY'\\), "
+ "whereas alias list has 1 columns");
sql("select c from unnest(\n"
+ " array(select deptno from dept)) with ordinality as t(c, d)").ok();
sql("select c from unnest(\n"
+ " array(select deptno from dept)) with ordinality as t(^c, d, e^)")
.fails("List of column aliases must have same degree as table; table has 2 "
+ "columns \\('EXPR\\$0', 'ORDINALITY'\\), "
+ "whereas alias list has 3 columns");
sql("select c\n"
+ "from unnest(array(select * from dept)) with ordinality as t(^c, d, e, f^)")
.fails("List of column aliases must have same degree as table; table has 3 "
+ "columns \\('DEPTNO', 'NAME', 'ORDINALITY'\\), "
+ "whereas alias list has 4 columns");
sql("select ^name^ from unnest(array(select name from dept)) with ordinality as t(c, o)")
.fails("Column 'NAME' not found in any table");
sql("select ^ordinality^ from unnest(array(select name from dept)) with ordinality as t(c, o)")
.fails("Column 'ORDINALITY' not found in any table");
}
@Test void unnestMapMustNameColumnsKeyAndValueWhenNotAliased() {
sql("select * from unnest(map[1, 12, 2, 22])")
.type("RecordType(INTEGER NOT NULL KEY, INTEGER NOT NULL VALUE) NOT NULL");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-4305">[CALCITE-4305]
* Implicit column alias for single-column UNNEST</a>. */
@Test void testUnnestAlias() {
final String expectedType = "RecordType(CHAR(6) NOT NULL FRUIT) NOT NULL";
// When UNNEST produces a single column, and you use an alias for the
// relation, that alias becomes the name of the column.
sql("select fruit.* from UNNEST(array ['apple', 'banana']) as fruit")
.type(expectedType);
sql("select fruit.* from UNNEST(array(select 'banana')) as fruit")
.type(expectedType);
sql("SELECT array(SELECT y + 1 FROM UNNEST(s.x) y) FROM (SELECT ARRAY[1,2,3] as x) s")
.ok();
// The magic doesn't happen if the query is not an UNNEST.
// In this case, the query is a SELECT.
sql("SELECT fruit.*\n"
+ "FROM (\n"
+ " SELECT * FROM UNNEST(array ['apple', 'banana']) as x) as fruit")
.type("RecordType(CHAR(6) NOT NULL X) NOT NULL");
// The magic doesn't happen if the UNNEST yields more than one column.
sql("select * from UNNEST(array [('apple', 1), ('banana', 2)]) as fruit")
.type("RecordType(CHAR(6) NOT NULL EXPR$0, INTEGER NOT NULL EXPR$1) "
+ "NOT NULL");
// VALUES gets the same treatment as ARRAY. (Unlike PostgreSQL.)
sql("select * from (values ('apple'), ('banana')) as fruit")
.type("RecordType(CHAR(6) NOT NULL FRUIT) NOT NULL");
// UNNEST MULTISET gets the same treatment as UNNEST ARRAY.
sql("select * from unnest(multiset [1, 2, 1]) as f")
.type("RecordType(INTEGER NOT NULL F) NOT NULL");
// The magic doesn't happen if the UNNEST is used without AS operator.
sql("select * from (SELECT ARRAY['banana'] as fruits) as t, UNNEST(t.fruits)")
.type("RecordType(CHAR(6) NOT NULL ARRAY NOT NULL FRUITS, "
+ "CHAR(6) NOT NULL EXPR$0) NOT NULL").ok();
}
@Test void testCorrelationJoin() {
sql("select *,"
+ " multiset(select * from emp where deptno=dept.deptno) "
+ " as empset"
+ " from dept").ok();
sql("select*from unnest(select multiset[8] from dept)").ok();
sql("select*from unnest(select multiset[deptno] from dept)").ok();
}
@Test void testStructuredTypes() {
sql("values new address()")
.columnType("ObjectSqlType(ADDRESS) NOT NULL");
sql("select home_address from emp_address")
.columnType("ObjectSqlType(ADDRESS) NOT NULL");
sql("select ea.home_address.zip from emp_address ea")
.columnType("INTEGER NOT NULL");
sql("select ea.mailing_address.city from emp_address ea")
.columnType("VARCHAR(20) NOT NULL");
}
@Test void testLateral() {
sql("select * from emp, (select * from dept where ^emp^.deptno=dept.deptno)")
.fails("Table 'EMP' not found");
sql("select * from emp,\n"
+ " LATERAL (select * from dept where emp.deptno=dept.deptno)").ok();
sql("select * from emp,\n"
+ " LATERAL (select * from dept where emp.deptno=dept.deptno) as ldt").ok();
sql("select * from emp,\n"
+ " LATERAL (select * from dept where emp.deptno=dept.deptno) ldt").ok();
}
@Test void testCollect() {
sql("select collect(deptno) from emp").ok();
sql("select collect(multiset[3]) from emp").ok();
sql("select collect(multiset[3]), ^deptno^ from emp")
.fails("Expression 'DEPTNO' is not being grouped");
}
@Test void testFusion() {
sql("select ^fusion(deptno)^ from emp")
.fails("(?s).*Cannot apply 'FUSION' to arguments of type 'FUSION.<INTEGER>.'.*");
sql("select fusion(multiset[3]) from emp").ok();
// todo. FUSION is an aggregate function. test that validator can only
// take set operators in its select list once aggregation support is
// complete
}
@Test void testCountFunction() {
sql("select count(*) from emp").ok();
sql("select count(ename) from emp").ok();
sql("select count(sal) from emp").ok();
sql("select count(1) from emp").ok();
sql("select ^count()^ from emp")
.fails("Invalid number of arguments to function 'COUNT'. Was expecting 1 arguments");
}
@Test void testCountCompositeFunction() {
sql("select count(ename, deptno) from emp").ok();
sql("select count(ename, deptno, ^gender^) from emp")
.fails("Column 'GENDER' not found in any table");
sql("select count(ename, 1, deptno) from emp").ok();
sql("select count(distinct ename, 1, deptno) from emp").ok();
sql("select count(deptno, ^*^) from emp")
.fails("(?s).*Encountered \"\\*\" at .*");
sql("select count(*^,^ deptno) from emp")
.fails("(?s).*Encountered \",\" at .*");
}
@Test void testLastFunction() {
sql("select LAST_VALUE(sal) over (order by empno) from emp").ok();
sql("select LAST_VALUE(ename) over (order by empno) from emp").ok();
sql("select FIRST_VALUE(sal) over (order by empno) from emp").ok();
sql("select FIRST_VALUE(ename) over (order by empno) from emp").ok();
sql("select NTH_VALUE(sal, 2) over (order by empno) from emp").ok();
sql("select NTH_VALUE(ename, 2) over (order by empno) from emp").ok();
}
@Test void testMinMaxFunctions() {
sql("SELECT MIN(true) from emp").ok();
sql("SELECT MAX(false) from emp").ok();
sql("SELECT MIN(sal+deptno) FROM emp").ok();
sql("SELECT MAX(ename) FROM emp").ok();
sql("SELECT MIN(5.5) FROM emp").ok();
sql("SELECT MAX(5) FROM emp").ok();
}
@Test void testArgMinMaxFunctions() {
sql("SELECT ARG_MIN(1, true) from emp").ok();
sql("SELECT ARG_MAX(2, false) from emp").ok();
sql("SELECT ARG_MIN(sal, deptno) FROM emp").ok();
sql("SELECT ARG_MAX(deptno, sal) FROM emp").ok();
sql("SELECT ARG_MIN('a', 5.5) FROM emp").ok();
sql("SELECT ARG_MAX('b', 5) FROM emp").ok();
}
@Test void testModeFunction() {
sql("select MODE(sal) from emp").ok();
sql("select MODE(sal) over (order by empno) from emp").ok();
sql("select MODE(ename) from emp where sal=3000");
sql("select MODE(sal) from emp group by deptno").ok();
sql("select MODE(sal) from emp group by deptno order by deptno").ok();
sql("select deptno,\n"
+ "^mode(empno)^ within group(order by 1)\n"
+ "from emp\n"
+ "group by deptno")
.fails("Aggregate expression 'MODE' must not contain a WITHIN GROUP clause");
}
@Test void testSomeEveryAndIntersectionFunctions() {
sql("select some(sal = 100), every(sal > 0), intersection(multiset[1,2]) from emp").ok();
sql("select some(sal = 100), ^empno^ from emp")
.fails("Expression 'EMPNO' is not being grouped");
sql("select every(sal > 0), ^empno^ from emp")
.fails("Expression 'EMPNO' is not being grouped");
sql("select intersection(multiset[1]), ^empno^ from emp")
.fails("Expression 'EMPNO' is not being grouped");
}
@Test void testAnyValueFunction() {
sql("SELECT any_value(ename) from emp").ok();
}
@Test void testBoolAndBoolOrFunction() {
final SqlValidatorFixture s = fixture()
.withOperatorTable(operatorTableFor(SqlLibrary.POSTGRESQL));
s.withSql("SELECT bool_and(true) from emp").ok();
s.withSql("SELECT bool_or(true) from emp").ok();
s.withSql("select bool_and(col)\n"
+ "from (values(true), (false), (true)) as tbl(col)").ok();
s.withSql("select bool_or(col)\n"
+ "from (values(true), (false), (true)) as tbl(col)").ok();
s.withSql("select bool_and(col)\n"
+ "from (values(true), (false), (null)) as tbl(col)").ok();
s.withSql("select bool_or(col)\n"
+ "from (values(true), (false), (null)) as tbl(col)").ok();
s.withSql("SELECT ^bool_and(ename)^ from emp")
.fails("(?s).*Cannot apply 'BOOL_AND' to arguments of type "
+ "'BOOL_AND\\(<VARCHAR\\(20\\)>\\)'.*");
s.withSql("SELECT ^bool_or(ename)^ from emp")
.fails("(?s).*Cannot apply 'BOOL_OR' to arguments of type "
+ "'BOOL_OR\\(<VARCHAR\\(20\\)>\\)'.*");
}
@Test void testConvertFunction() {
sql("select convert(ename, utf16, utf8) from emp").ok();
sql("select convert(cast(deptno as varchar), utf16, utf8) from emp");
sql("select convert(null, gbk, utf8) from emp");
sql("select ^convert(deptno, utf8, latin1)^ from emp")
.fails("Invalid type 'INTEGER NOT NULL' in 'CONVERT' function\\. "
+ "Only 'CHARACTER' type is supported");
sql("select convert(ename, utf8, utf9) from emp").fails("UTF9");
}
@Test void testFunctionalDistinct() {
sql("select count(distinct sal) from emp").ok();
sql("select COALESCE(^distinct^ sal) from emp")
.fails("DISTINCT/ALL not allowed with COALESCE function");
}
@Test void testColumnNotFound() {
sql("select ^b0^ from sales.emp")
.fails("Column 'B0' not found in any table");
}
@Test void testColumnNotFound2() {
sql("select ^b0^ from sales.emp, sales.dept")
.fails("Column 'B0' not found in any table");
}
@Test void testColumnNotFound3() {
sql("select e.^b0^ from sales.emp as e")
.fails("Column 'B0' not found in table 'E'");
}
@Test void testSelectDistinct() {
sql("SELECT DISTINCT deptno FROM emp").ok();
sql("SELECT DISTINCT deptno, sal FROM emp").ok();
sql("SELECT DISTINCT deptno FROM emp GROUP BY deptno").ok();
sql("SELECT DISTINCT ^deptno^ FROM emp GROUP BY sal")
.fails("Expression 'DEPTNO' is not being grouped");
sql("SELECT DISTINCT avg(sal) from emp").ok();
sql("SELECT DISTINCT ^deptno^, avg(sal) from emp")
.fails("Expression 'DEPTNO' is not being grouped");
sql("SELECT DISTINCT deptno, sal from emp GROUP BY sal, deptno").ok();
sql("SELECT deptno FROM emp GROUP BY deptno HAVING deptno > 55").ok();
sql("SELECT DISTINCT deptno, 33 FROM emp\n"
+ "GROUP BY deptno HAVING deptno > 55").ok();
sql("SELECT DISTINCT deptno, 33 FROM emp HAVING ^deptno^ > 55")
.fails("Expression 'DEPTNO' is not being grouped");
// same query under a different conformance finds a different error first
sql("SELECT DISTINCT ^deptno^, 33 FROM emp HAVING deptno > 55")
.withConformance(SqlConformanceEnum.LENIENT)
.fails("Expression 'DEPTNO' is not being grouped");
sql("SELECT DISTINCT 33 FROM emp HAVING ^deptno^ > 55")
.fails("Expression 'DEPTNO' is not being grouped")
.withConformance(SqlConformanceEnum.LENIENT)
.fails("Expression 'DEPTNO' is not being grouped");
sql("SELECT DISTINCT * from emp").ok();
sql("SELECT DISTINCT ^*^ from emp GROUP BY deptno")
.fails("Expression 'EMP\\.EMPNO' is not being grouped");
// similar validation for SELECT DISTINCT and GROUP BY
sql("SELECT deptno FROM emp GROUP BY deptno ORDER BY deptno, ^empno^")
.fails("Expression 'EMPNO' is not being grouped");
sql("SELECT DISTINCT deptno from emp ORDER BY deptno, ^empno^")
.fails("Expression 'EMPNO' is not in the select clause");
sql("SELECT DISTINCT deptno from emp ORDER BY deptno + 2").ok();
// The ORDER BY clause works on what is projected by DISTINCT - even if
// GROUP BY is present.
sql("SELECT DISTINCT deptno FROM emp GROUP BY deptno, empno ORDER BY deptno, ^empno^")
.fails("Expression 'EMPNO' is not in the select clause");
// redundant distinct; same query is in unitsql/optimizer/distinct.sql
sql("select distinct * from (\n"
+ " select distinct deptno from emp) order by 1").ok();
sql("SELECT DISTINCT 5, 10+5, 'string' from emp").ok();
}
@Test void testSelectWithoutFrom() {
sql("^select 2+2^")
.withConformance(SqlConformanceEnum.DEFAULT)
.ok();
sql("^select 2+2^")
.withConformance(SqlConformanceEnum.ORACLE_10)
.fails("SELECT must have a FROM clause");
sql("^select 2+2^")
.withConformance(SqlConformanceEnum.STRICT_2003)
.fails("SELECT must have a FROM clause");
}
@Test void testSelectAmbiguousField() {
sql("select ^t0^ from (select 1 as t0, 2 as T0 from dept)")
.withCaseSensitive(false)
.withUnquotedCasing(Casing.UNCHANGED)
.fails("Column 't0' is ambiguous");
sql("select ^t0^ from (select 1 as t0, 2 as t0,3 as t1,4 as t1, 5 as t2 from dept)")
.withCaseSensitive(false)
.withUnquotedCasing(Casing.UNCHANGED)
.fails("Column 't0' is ambiguous");
// t0 is not referenced,so this case is allowed
sql("select 1 as t0, 2 as t0 from dept")
.withCaseSensitive(false)
.withUnquotedCasing(Casing.UNCHANGED)
.ok();
sql("select t0 from (select 1 as t0, 2 as T0 from DEPT)")
.withCaseSensitive(true)
.withUnquotedCasing(Casing.UNCHANGED)
.ok();
}
@Test void testTableExtend() {
sql("select * from dept extend (x int not null)")
.type("RecordType(INTEGER NOT NULL DEPTNO, VARCHAR(10) NOT NULL NAME, "
+ "INTEGER NOT NULL X) NOT NULL");
sql("select deptno + x as z\n"
+ "from dept extend (x int not null) as x\n"
+ "where x > 10")
.type("RecordType(INTEGER NOT NULL Z) NOT NULL");
}
@Test void testExplicitTable() {
final String empRecordType =
"RecordType(INTEGER NOT NULL EMPNO,"
+ " VARCHAR(20) NOT NULL ENAME,"
+ " VARCHAR(10) NOT NULL JOB,"
+ " INTEGER MGR,"
+ " TIMESTAMP(0) NOT NULL HIREDATE,"
+ " INTEGER NOT NULL SAL,"
+ " INTEGER NOT NULL COMM,"
+ " INTEGER NOT NULL DEPTNO,"
+ " BOOLEAN NOT NULL SLACKER) NOT NULL";
sql("select * from (table emp)")
.type(empRecordType);
sql("table emp")
.type(empRecordType);
sql("table ^nonexistent^")
.fails("Object 'NONEXISTENT' not found");
sql("table ^sales.nonexistent^")
.fails("Object 'NONEXISTENT' not found within 'SALES'");
sql("table ^nonexistent.foo^")
.fails("Object 'NONEXISTENT' not found");
}
@Test void testCollectionTable() {
sql("select * from table(ramp(3))")
.type("RecordType(INTEGER NOT NULL I) NOT NULL");
sql("select * from table(^ramp('3')^)")
.withTypeCoercion(false)
.fails("Cannot apply 'RAMP' to arguments of type "
+ "'RAMP\\(<CHAR\\(1\\)>\\)'\\. "
+ "Supported form\\(s\\): 'RAMP\\(<NUMERIC>\\)'");
sql("select * from table(ramp('3'))")
.type("RecordType(INTEGER NOT NULL I) NOT NULL");
sql("select * from table(^abs^(-1))")
.fails("(?s)Encountered \"abs\" at .*");
sql("select * from table(^1^ + 2)")
.fails("(?s)Encountered \"1\" at .*");
sql("select * from table(^nonExistentRamp('3')^)")
.fails("No match found for function signature NONEXISTENTRAMP\\(<CHARACTER>\\)");
sql("select * from table(^bad_ramp(3)^)")
.fails("Argument must be a table function: BAD_RAMP");
sql("select * from table(^bad_table_function(3)^)")
.fails("Table function should have CURSOR type, not"
+ " RecordType\\(INTEGER I\\)");
}
/** Tests that Calcite gives an error if a table function is used anywhere
* that a scalar expression is expected. */
@Test void testTableFunctionAsExpression() {
sql("select ^ramp(3)^ from (values (1))")
.fails("Cannot call table function here: 'RAMP'");
sql("select * from (values (1)) where ^ramp(3)^")
.fails("WHERE clause must be a condition");
sql("select * from (values (1)) where ^ramp(3) and 1 = 1^")
.fails("Cannot apply 'AND' to arguments of type '<CURSOR> AND "
+ "<BOOLEAN>'\\. Supported form\\(s\\): '<BOOLEAN> AND <BOOLEAN>'");
sql("select * from (values (1)) where ^ramp(3) is not null^")
.fails("Cannot apply 'IS NOT NULL' to arguments of type '<CURSOR> IS"
+ " NOT NULL'\\. Supported form\\(s\\): '<ANY> IS NOT NULL'");
sql("select ^sum(ramp(3))^ from (values (1))")
.fails("Cannot apply 'SUM' to arguments of type 'SUM\\(<CURSOR>\\)'\\. "
+ "Supported form\\(s\\): 'SUM\\(<NUMERIC>\\)'");
sql("select 0 from (values (1)) group by ^ramp(3)^")
.fails("Cannot call table function here: 'RAMP'");
sql("select count(*) from (values (1)) having ^ramp(3)^")
.fails("HAVING clause must be a condition");
sql("select * from (values (1)) order by ^ramp(3)^ asc, 1 desc")
.fails("Cannot call table function here: 'RAMP'");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-5779">[CALCITE-5779]
* Implicit column alias for single-column table function should work</a>. */
@Test void testTableFunctionSingleColumnAlias() {
final SqlValidatorFixture s = fixture()
.withOperatorTable(MockSqlOperatorTable.standard().extend());
s.withSql("select rmp from table(ramp(3)) as rmp").ok();
s.withSql("select rmp.i from table(ramp(3)) as rmp").ok();
s.withSql("select rmp.i, rmp from table(ramp(3)) as rmp").ok();
s.withSql("select l from table(ramp(3)) as rmp(l)").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1309">[CALCITE-1309]
* Support LATERAL TABLE</a>. */
@Test void testCollectionTableWithLateral() {
final String expectedType = "RecordType(INTEGER NOT NULL DEPTNO, "
+ "VARCHAR(10) NOT NULL NAME, "
+ "INTEGER NOT NULL I) NOT NULL";
sql("select * from dept, lateral table(ramp(dept.deptno))")
.type(expectedType);
sql("select * from dept cross join lateral table(ramp(dept.deptno))")
.type(expectedType);
sql("select * from dept join lateral table(ramp(dept.deptno)) on true")
.type(expectedType);
final String expectedType2 = "RecordType(INTEGER NOT NULL DEPTNO, "
+ "VARCHAR(10) NOT NULL NAME, "
+ "INTEGER I) NOT NULL";
sql("select * from dept left join lateral table(ramp(dept.deptno)) on true")
.type(expectedType2);
sql("select * from dept, lateral table(^ramp(dept.name)^)")
.withTypeCoercion(false)
.fails("(?s)Cannot apply 'RAMP' to arguments of type 'RAMP\\(<VARCHAR\\(10\\)>\\)'.*");
sql("select * from dept, lateral table(ramp(dept.name))")
.type("RecordType(INTEGER NOT NULL DEPTNO, "
+ "VARCHAR(10) NOT NULL NAME, "
+ "INTEGER NOT NULL I) NOT NULL");
sql("select * from lateral table(ramp(^dept^.deptno)), dept")
.fails("Table 'DEPT' not found");
final String expectedType3 = "RecordType(INTEGER NOT NULL I, "
+ "INTEGER NOT NULL DEPTNO, "
+ "VARCHAR(10) NOT NULL NAME) NOT NULL";
sql("select * from lateral table(ramp(1234)), dept")
.type(expectedType3);
}
@Test void testCollectionTableWithLateral2() {
// The expression inside the LATERAL can only see tables before it in the
// FROM clause. And it can't see itself.
sql("select * from emp, lateral table(ramp(emp.deptno)), dept")
.ok();
sql("select * from emp, lateral table(ramp(^z^.i)) as z, dept")
.fails("Table 'Z' not found");
sql("select * from emp, lateral table(ramp(^dept^.deptno)), dept")
.fails("Table 'DEPT' not found");
}
@Test void testCollectionTableWithCursorParam() {
sql("select * from table(dedup(cursor(select * from emp),'ename'))")
.type("RecordType(VARCHAR(1024) NOT NULL NAME) NOT NULL");
sql("select * from table(dedup(cursor(select * from ^bloop^),'ename'))")
.fails("Object 'BLOOP' not found");
}
@Test void testTemporalTable() {
sql("select stream * from orders, ^products^ for system_time as of"
+ " TIMESTAMP '2011-01-02 00:00:00'")
.fails("Table 'PRODUCTS' is not a temporal table, "
+ "can not be queried in system time period specification");
sql("select stream * from orders, ^products^ for system_time as of"
+ " TIMESTAMP WITH LOCAL TIME ZONE '2011-01-02 00:00:00'")
.fails("Table 'PRODUCTS' is not a temporal table, "
+ "can not be queried in system time period specification");
sql("select stream * from orders, products_temporal "
+ "for system_time as of ^'2011-01-02 00:00:00'^")
.fails("The system time period specification expects Timestamp type but is 'CHAR'");
// verify inner join with a specific timestamp
sql("select stream * from orders join products_temporal "
+ "for system_time as of timestamp '2011-01-02 00:00:00' "
+ "on orders.productid = products_temporal.productid").ok();
// verify left join with a timestamp field
sql("select stream * from orders left join products_temporal "
+ "for system_time as of orders.rowtime "
+ "on orders.productid = products_temporal.productid").ok();
// verify left join with a timestamp expression
sql("select stream * from orders left join products_temporal\n"
+ "for system_time as of orders.rowtime - INTERVAL '3' DAY\n"
+ "on orders.productid = products_temporal.productid").ok();
// verify left join with a datetime value function
sql("select stream * from orders left join products_temporal\n"
+ "for system_time as of CURRENT_TIMESTAMP\n"
+ "on orders.productid = products_temporal.productid").ok();
}
@Test void testScalarSubQuery() {
sql("SELECT ename,(select name from dept where deptno=1) FROM emp").ok();
sql("SELECT ename,(^select losal, hisal from salgrade where grade=1^) FROM emp")
.fails("Cannot apply '\\$SCALAR_QUERY' to arguments of type "
+ "'\\$SCALAR_QUERY\\(<RECORDTYPE\\(INTEGER LOSAL, "
+ "INTEGER HISAL\\)>\\)'\\. Supported form\\(s\\): "
+ "'\\$SCALAR_QUERY\\(<RECORDTYPE\\(SINGLE FIELD\\)>\\)'");
// Note that X is a field (not a record) and is nullable even though
// EMP.NAME is NOT NULL.
sql("SELECT ename,(select name from dept where deptno=1) FROM emp")
.type("RecordType(VARCHAR(20) NOT NULL ENAME, VARCHAR(10) EXPR$1) NOT NULL");
// scalar subqery inside AS operator
sql("SELECT ename,(select name from dept where deptno=1) as X FROM emp")
.type("RecordType(VARCHAR(20) NOT NULL ENAME, VARCHAR(10) X) NOT NULL");
// scalar subqery inside + operator
sql("SELECT ename, 1 + (select deptno from dept where deptno=1) as X FROM emp")
.type("RecordType(VARCHAR(20) NOT NULL ENAME, INTEGER X) NOT NULL");
// scalar sub-query inside WHERE
sql("select * from emp where (select true from dept)").ok();
}
@Disabled("not supported")
@Test void testSubQueryInOnClause() {
// Currently not supported. Should give validator error, but gives
// internal error.
sql("select * from emp as emps left outer join dept as depts\n"
+ "on emps.deptno = depts.deptno and emps.deptno = (\n"
+ "select min(deptno) from dept as depts2)").ok();
}
@Test void dynamicParameterType() {
expr("CAST(? AS INTEGER)")
.columnType("INTEGER");
}
@Test void testRecordType() {
// Have to qualify columns with table name.
sql("SELECT ^coord^.x, coord.y FROM customer.contact")
.fails("Table 'COORD' not found");
sql("SELECT contact.coord.x, contact.coord.y FROM customer.contact")
.type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
// Qualifying with schema is OK.
sql("SELECT customer.contact.coord.x, customer.contact.email,\n"
+ " contact.coord.y\n"
+ "FROM customer.contact")
.type("RecordType(INTEGER NOT NULL X, VARCHAR(20) NOT NULL EMAIL, "
+ "INTEGER NOT NULL Y) NOT NULL");
}
@Test void testArrayOfRecordType() {
sql("SELECT name, dept_nested.employees[1].^ne^ as ne from dept_nested")
.fails("Unknown field 'NE'");
sql("SELECT name, dept_nested.employees[1].ename as ename from dept_nested")
.type("RecordType(VARCHAR(10) NOT NULL NAME, VARCHAR(10) ENAME) NOT NULL");
sql("SELECT dept_nested.employees[1].detail.skills[1].desc as DESCRIPTION\n"
+ "from dept_nested")
.type("RecordType(VARCHAR(20) DESCRIPTION) NOT NULL");
sql("SELECT dept_nested.employees[1].detail.skills[1].others.a as oa\n"
+ "from dept_nested")
.type("RecordType(VARCHAR(10) OA) NOT NULL");
}
@Test void testItemOperatorException() {
sql("select ^name[0]^ from dept")
.fails("Cannot apply 'ITEM' to arguments of type 'ITEM\\(<VARCHAR\\(10\\)>, "
+ "<INTEGER>\\)'\\. Supported form\\(s\\): <ARRAY>\\[<INTEGER>\\]\n"
+ "<MAP>\\[<ANY>\\]\n"
+ "<ROW>\\[<CHARACTER>\\|<INTEGER>\\].*");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-497">[CALCITE-497]
* Support optional qualifier for column name references</a>. */
@Test void testRecordTypeElided() {
sql("SELECT contact.^x^, contact.coord.y FROM customer.contact")
.fails("Column 'X' not found in table 'CONTACT'");
// Fully qualified works.
sql("SELECT contact.coord.x, contact.coord.y FROM customer.contact")
.type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y) NOT NULL");
// Because the types of CONTACT_PEEK.COORD and CONTACT_PEEK.COORD_NE are
// marked "peek", the validator can see through them.
sql("SELECT c.x, c.coord.y, c.m, c.b FROM customer.contact_peek as c")
.type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y, INTEGER NOT NULL M,"
+ " INTEGER NOT NULL B) NOT NULL");
sql("SELECT c.coord.x, c.coord.y, c.coord_ne.m FROM customer.contact_peek as c")
.type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL Y, INTEGER NOT NULL M) NOT NULL");
sql("SELECT x, a, c.coord.y FROM customer.contact_peek as c")
.type("RecordType(INTEGER NOT NULL X, INTEGER NOT NULL A, INTEGER NOT NULL Y) NOT NULL");
// Because CONTACT_PEEK.COORD_NE is marked "peek no expand",
// "select *" does not flatten it.
sql("SELECT coord_ne.* FROM customer.contact_peek as c")
.type("RecordType(INTEGER NOT NULL M, "
+ "RecordType:peek_no_expand(INTEGER NOT NULL A, INTEGER NOT NULL B) "
+ "NOT NULL SUB) NOT NULL");
sql("SELECT * FROM customer.contact_peek as c")
.type("RecordType(INTEGER NOT NULL CONTACTNO, VARCHAR(10) NOT NULL FNAME, "
+ "VARCHAR(10) NOT NULL LNAME, VARCHAR(20) NOT NULL EMAIL, INTEGER NOT NULL X, "
+ "INTEGER NOT NULL Y, VARCHAR(20) NOT NULL unit, "
+ "RecordType:peek_no_expand(INTEGER NOT NULL M, "
+ "RecordType:peek_no_expand(INTEGER NOT NULL A, INTEGER NOT NULL B) "
+ "NOT NULL SUB) NOT NULL COORD_NE) NOT NULL");
// Qualifying with schema is OK.
final String sql = "SELECT customer.contact_peek.x,\n"
+ " customer.contact_peek.email, contact_peek.coord.y\n"
+ "FROM customer.contact_peek";
sql(sql).type("RecordType(INTEGER NOT NULL X, VARCHAR(20) NOT NULL EMAIL,"
+ " INTEGER NOT NULL Y) NOT NULL");
}
@Test void testSample() {
// applied to table
sql("SELECT * FROM emp TABLESAMPLE SUBSTITUTE('foo')").ok();
sql("SELECT * FROM emp TABLESAMPLE BERNOULLI(50)").ok();
sql("SELECT * FROM emp TABLESAMPLE SYSTEM(50)").ok();
// applied to query
sql("SELECT * FROM ("
+ "SELECT deptno FROM emp "
+ "UNION ALL "
+ "SELECT deptno FROM dept) AS x TABLESAMPLE SUBSTITUTE('foo') "
+ "WHERE x.deptno < 100").ok();
sql("SELECT x.^empno^ FROM ("
+ "SELECT deptno FROM emp TABLESAMPLE SUBSTITUTE('bar') "
+ "UNION ALL "
+ "SELECT deptno FROM dept) AS x TABLESAMPLE SUBSTITUTE('foo') "
+ "ORDER BY 1")
.fails("Column 'EMPNO' not found in table 'X'");
sql("select * from (\n"
+ " select * from emp\n"
+ " join dept on emp.deptno = dept.deptno\n"
+ ") tablesample substitute('SMALL')").ok();
sql("SELECT * FROM ("
+ "SELECT deptno FROM emp "
+ "UNION ALL "
+ "SELECT deptno FROM dept) AS x TABLESAMPLE BERNOULLI(50) "
+ "WHERE x.deptno < 100").ok();
sql("SELECT x.^empno^ FROM ("
+ "SELECT deptno FROM emp TABLESAMPLE BERNOULLI(50) "
+ "UNION ALL "
+ "SELECT deptno FROM dept) AS x TABLESAMPLE BERNOULLI(10) "
+ "ORDER BY 1")
.fails("Column 'EMPNO' not found in table 'X'");
sql("select * from (\n"
+ " select * from emp\n"
+ " join dept on emp.deptno = dept.deptno\n"
+ ") tablesample bernoulli(10)").ok();
sql("SELECT * FROM ("
+ "SELECT deptno FROM emp "
+ "UNION ALL "
+ "SELECT deptno FROM dept) AS x TABLESAMPLE SYSTEM(50) "
+ "WHERE x.deptno < 100").ok();
sql("SELECT x.^empno^ FROM ("
+ "SELECT deptno FROM emp TABLESAMPLE SYSTEM(50) "
+ "UNION ALL "
+ "SELECT deptno FROM dept) AS x TABLESAMPLE SYSTEM(10) "
+ "ORDER BY 1")
.fails("Column 'EMPNO' not found in table 'X'");
sql("select * from (\n"
+ " select * from emp\n"
+ " join dept on emp.deptno = dept.deptno\n"
+ ") tablesample system(10)").ok();
sql("select * from ^emp TABLESAMPLE BERNOULLI(1000)^")
.fails("TABLESAMPLE percentage must be between 0 and 100, inclusive");
sql("select * from ^emp TABLESAMPLE SYSTEM(101)^")
.fails("TABLESAMPLE percentage must be between 0 and 100, inclusive");
}
@Test void testRewriteWithoutIdentifierExpansion() {
sql("select * from dept")
.withValidatorIdentifierExpansion(false)
.rewritesTo("SELECT *\n"
+ "FROM `DEPT`");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-6007">[CALCITE-6007]
* Sub-query that contains WITH and has no alias generates invalid SQL after
* expansion</a>. */
@Test void testSubQueryWithoutAlias() {
// Note the 'AS `EXPR$0`' in the rewritten form of each query.
// Before [CALCITE-6007] was fixed, that alias was missing.
sql("select a from (select 1 as a)")
.withValidatorIdentifierExpansion(true)
.rewritesTo("SELECT `EXPR$0`.`A`\n"
+ "FROM (SELECT 1 AS `A`) AS `EXPR$0`");
sql("select a from (with sub as (select 1 as a) select a from sub)")
.withValidatorIdentifierExpansion(true)
.rewritesTo("SELECT `EXPR$0`.`A`\n"
+ "FROM (WITH `SUB` AS (SELECT 1 AS `A`) "
+ "SELECT `SUB`.`A`\n"
+ "FROM `SUB` AS `SUB`) AS `EXPR$0`");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1238">[CALCITE-1238]
* Unparsing LIMIT without ORDER BY after validation</a>. */
@Test void testRewriteWithLimitWithoutOrderBy() {
final String sql = "select name from dept limit 2";
final String expected = "SELECT `NAME`\n"
+ "FROM `DEPT`\n"
+ "FETCH NEXT 2 ROWS ONLY";
sql(sql)
.withValidatorIdentifierExpansion(false)
.rewritesTo(expected);
}
@Test void testRewriteWithLimitWithDynamicParameters() {
final String sql = "select name from dept offset ? rows fetch next ? rows only";
final String expected = "SELECT `NAME`\n"
+ "FROM `DEPT`\n"
+ "OFFSET ? ROWS\n"
+ "FETCH NEXT ? ROWS ONLY";
sql(sql)
.withValidatorIdentifierExpansion(false)
.rewritesTo(expected);
}
@Test void testRewriteWithOffsetWithoutOrderBy() {
final String sql = "select name from dept offset 2";
final String expected = "SELECT `NAME`\n"
+ "FROM `DEPT`\n"
+ "OFFSET 2 ROWS";
sql(sql)
.withValidatorIdentifierExpansion(false)
.rewritesTo(expected);
}
@Test void testRewriteWithUnionFetchWithoutOrderBy() {
final String sql =
"select name from dept union all select name from dept limit 2";
final String expected = "SELECT *\n"
+ "FROM (SELECT `NAME`\n"
+ "FROM `DEPT`\n"
+ "UNION ALL\n"
+ "SELECT `NAME`\n"
+ "FROM `DEPT`)\n"
+ "FETCH NEXT 2 ROWS ONLY";
sql(sql)
.withValidatorIdentifierExpansion(false)
.rewritesTo(expected);
}
@Test void testRewriteWithIdentifierExpansion() {
sql("select * from dept")
.withValidatorIdentifierExpansion(true)
.rewritesTo("SELECT `DEPT`.`DEPTNO`, `DEPT`.`NAME`\n"
+ "FROM `CATALOG`.`SALES`.`DEPT` AS `DEPT`");
}
@Test void testRewriteWithColumnReferenceExpansion() {
// The names in the ORDER BY clause are not qualified.
// This is because ORDER BY references columns in the SELECT clause
// in preference to columns in tables in the FROM clause.
sql("select name from dept where name = 'Moonracer' group by name"
+ " having sum(deptno) > 3 order by name")
.withValidatorIdentifierExpansion(true)
.withValidatorColumnReferenceExpansion(true)
.rewritesTo("SELECT `DEPT`.`NAME`\n"
+ "FROM `CATALOG`.`SALES`.`DEPT` AS `DEPT`\n"
+ "WHERE `DEPT`.`NAME` = 'Moonracer'\n"
+ "GROUP BY `DEPT`.`NAME`\n"
+ "HAVING SUM(`DEPT`.`DEPTNO`) > 3\n"
+ "ORDER BY `NAME`");
}
@Test void testRewriteWithColumnReferenceExpansionAndFromAlias() {
// In the ORDER BY clause, 'ename' is not qualified but 'deptno' and 'sal'
// are. This is because 'ename' appears as an alias in the SELECT clause.
// 'sal' is qualified in the ORDER BY clause, so remains qualified.
final String sql = "select ename, sal from (select * from emp) as e"
+ " where ename = 'Moonracer' group by ename, deptno, sal"
+ " having sum(deptno) > 3 order by ename, deptno, e.sal";
final String expected = "SELECT `E`.`ENAME`, `E`.`SAL`\n"
+ "FROM (SELECT `EMP`.`EMPNO`, `EMP`.`ENAME`, `EMP`.`JOB`,"
+ " `EMP`.`MGR`, `EMP`.`HIREDATE`, `EMP`.`SAL`, `EMP`.`COMM`,"
+ " `EMP`.`DEPTNO`, `EMP`.`SLACKER`\n"
+ "FROM `CATALOG`.`SALES`.`EMP` AS `EMP`) AS `E`\n"
+ "WHERE `E`.`ENAME` = 'Moonracer'\n"
+ "GROUP BY `E`.`ENAME`, `E`.`DEPTNO`, `E`.`SAL`\n"
+ "HAVING SUM(`E`.`DEPTNO`) > 3\n"
+ "ORDER BY `ENAME`, `E`.`DEPTNO`, `E`.`SAL`";
sql(sql)
.withValidatorIdentifierExpansion(true)
.withValidatorColumnReferenceExpansion(true)
.rewritesTo(expected);
}
@Test void testRewriteExpansionOfColumnReferenceBeforeResolution() {
final String sql = "select unexpanded.deptno from dept \n"
+ " where unexpanded.name = 'Moonracer' \n"
+ " group by unexpanded.deptno\n"
+ " having sum(unexpanded.deptno) > 0\n"
+ " order by unexpanded.deptno";
final String expectedSql = "SELECT `DEPT`.`DEPTNO`\n"
+ "FROM `CATALOG`.`SALES`.`DEPT` AS `DEPT`\n"
+ "WHERE `DEPT`.`NAME` = 'Moonracer'\n"
+ "GROUP BY `DEPT`.`DEPTNO`\n"
+ "HAVING SUM(`DEPT`.`DEPTNO`) > 0\n"
+ "ORDER BY `DEPT`.`DEPTNO`";
SqlValidatorTestCase.FIXTURE
.withFactory(t -> t.withValidator(UnexpandedToDeptValidator::new))
.withSql(sql)
.withValidatorIdentifierExpansion(true)
.withValidatorColumnReferenceExpansion(true)
.withConformance(SqlConformanceEnum.LENIENT)
.rewritesTo(expectedSql);
}
@Test void testCoalesceWithoutRewrite() {
final String sql = "select coalesce(deptno, empno) from emp";
final String expected1 = "SELECT COALESCE(`EMP`.`DEPTNO`, `EMP`.`EMPNO`)\n"
+ "FROM `CATALOG`.`SALES`.`EMP` AS `EMP`";
final String expected2 = "SELECT COALESCE(`DEPTNO`, `EMPNO`)\n"
+ "FROM `EMP`";
sql(sql)
.withValidatorCallRewrite(false)
.withValidatorIdentifierExpansion(true)
.rewritesTo(expected1)
.withValidatorIdentifierExpansion(false)
.rewritesTo(expected2);
}
@Test void testCoalesceWithRewrite() {
final String sql = "select coalesce(deptno, empno) from emp";
final String expected1 = "SELECT CASE WHEN `EMP`.`DEPTNO` IS NOT NULL"
+ " THEN `EMP`.`DEPTNO` ELSE `EMP`.`EMPNO` END\n"
+ "FROM `CATALOG`.`SALES`.`EMP` AS `EMP`";
final String expected2 = "SELECT CASE WHEN `DEPTNO` IS NOT NULL"
+ " THEN `DEPTNO` ELSE `EMPNO` END\n"
+ "FROM `EMP`";
sql(sql)
.withValidatorCallRewrite(true)
.withValidatorIdentifierExpansion(true)
.rewritesTo(expected1)
.withValidatorIdentifierExpansion(false)
.rewritesTo(expected2);
}
@Test void testDatePartWithRewrite() {
final String sql = "select week(date '2022-04-27'), year(date '2022-04-27')";
final String expected = "SELECT EXTRACT(WEEK FROM DATE '2022-04-27'),"
+ " EXTRACT(YEAR FROM DATE '2022-04-27')";
sql(sql)
.withValidatorCallRewrite(true)
.rewritesTo(expected);
final String noParamSql = "select ^week()^";
sql(noParamSql)
.withValidatorCallRewrite(true)
.fails("Invalid number of arguments to function 'WEEK'. Was expecting 1 arguments");
final String multiParamsSql = "select ^week(date '2022-04-27', 1)^";
sql(multiParamsSql)
.withValidatorCallRewrite(true)
.fails("Invalid number of arguments to function 'WEEK'. Was expecting 1 arguments");
}
@Test void testDatePartWithoutRewrite() {
final String sql = "select week(date '2022-04-27'), year(date '2022-04-27')";
final String expected = "SELECT WEEK(DATE '2022-04-27'), YEAR(DATE '2022-04-27')";
sql(sql)
.withValidatorCallRewrite(false)
.rewritesTo(expected);
final String noParamSql = "select ^week()^";
sql(noParamSql)
.withValidatorCallRewrite(false)
.fails("Invalid number of arguments to function 'WEEK'. Was expecting 1 arguments");
final String multiParamsSql = "select ^week(date '2022-04-27', 1)^";
sql(multiParamsSql)
.withValidatorCallRewrite(false)
.fails("Invalid number of arguments to function 'WEEK'. Was expecting 1 arguments");
}
@Disabled
@Test void testValuesWithAggFuncs() {
sql("values(^count(1)^)")
.fails("Call to xxx is invalid\\. Direct calls to aggregate "
+ "functions not allowed in ROW definitions\\.");
}
@Test void testFieldOrigin() {
sql("select * from emp join dept on true")
.assertFieldOrigin(
is("{CATALOG.SALES.EMP.EMPNO,"
+ " CATALOG.SALES.EMP.ENAME,"
+ " CATALOG.SALES.EMP.JOB,"
+ " CATALOG.SALES.EMP.MGR,"
+ " CATALOG.SALES.EMP.HIREDATE,"
+ " CATALOG.SALES.EMP.SAL,"
+ " CATALOG.SALES.EMP.COMM,"
+ " CATALOG.SALES.EMP.DEPTNO,"
+ " CATALOG.SALES.EMP.SLACKER,"
+ " CATALOG.SALES.DEPT.DEPTNO,"
+ " CATALOG.SALES.DEPT.NAME}"));
sql("select distinct emp.empno, hiredate, 1 as uno,\n"
+ " emp.empno * 2 as twiceEmpno\n"
+ "from emp join dept on true")
.assertFieldOrigin(
is("{CATALOG.SALES.EMP.EMPNO,"
+ " CATALOG.SALES.EMP.HIREDATE,"
+ " null,"
+ " null}"));
sql("select e.empno from dept_nested, unnest(employees) as e")
.assertFieldOrigin(
is("{CATALOG.SALES.DEPT_NESTED.EMPLOYEES.EMPNO}"));
sql("select * from UNNEST(ARRAY['a', 'b'])")
.assertFieldOrigin(is("{null}"));
sql("select * from UNNEST(ARRAY['a', 'b'], ARRAY['d', 'e'])")
.assertFieldOrigin(is("{null, null}"));
sql("select dpt.skill.desc from dept_nested as dpt")
.assertFieldOrigin(is("{CATALOG.SALES.DEPT_NESTED.SKILL.DESC}"));
}
@Test void testBrackets() {
final SqlValidatorFixture s = fixture().withQuoting(Quoting.BRACKET);
s.withSql("select [e].EMPNO from [EMP] as [e]")
.type("RecordType(INTEGER NOT NULL EMPNO) NOT NULL");
s.withSql("select ^e^.EMPNO from [EMP] as [e]")
.fails("Table 'E' not found; did you mean 'e'\\?");
s.withSql("select ^x^ from (\n"
+ " select [e].EMPNO as [x] from [EMP] as [e])")
.fails("Column 'X' not found in any table; did you mean 'x'\\?");
s.withSql("select ^x^ from (\n"
+ " select [e].EMPNO as [x ] from [EMP] as [e])")
.fails("Column 'X' not found in any table");
s.withSql("select EMP^.^\"x\" from EMP")
.fails("(?s).*Encountered \"\\. \\\\\"\" at line .*");
s.withSql("select [x[y]] z ] from (\n"
+ " select [e].EMPNO as [x[y]] z ] from [EMP] as [e])").type(
"RecordType(INTEGER NOT NULL x[y] z ) NOT NULL");
}
@Test void testLexJava() {
final SqlValidatorFixture s = fixture().withLex(Lex.JAVA);
s.withSql("select e.EMPNO from EMP as e")
.type("RecordType(INTEGER NOT NULL EMPNO) NOT NULL");
s.withSql("select ^e^.EMPNO from EMP as E")
.fails("Table 'e' not found; did you mean 'E'\\?");
s.withSql("select ^E^.EMPNO from EMP as e")
.fails("Table 'E' not found; did you mean 'e'\\?");
s.withSql("select ^x^ from (\n"
+ " select e.EMPNO as X from EMP as e)")
.fails("Column 'x' not found in any table; did you mean 'X'\\?");
s.withSql("select ^x^ from (\n"
+ " select e.EMPNO as Xx from EMP as e)")
.fails("Column 'x' not found in any table");
// double-quotes are not valid in this lexical convention
s.withSql("select EMP^.^\"x\" from EMP")
.fails("(?s).*Encountered \"\\. \\\\\"\" at line .*");
// in Java mode, creating identifiers with spaces is not encouraged, but you
// can use back-ticks if you really have to
s.withSql("select `x[y] z ` from (\n"
+ " select e.EMPNO as `x[y] z ` from EMP as e)")
.type("RecordType(INTEGER NOT NULL x[y] z ) NOT NULL");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-145">[CALCITE-145]
* Unexpected upper-casing of keywords when using java lexer</a>. */
@Test void testLexJavaKeyword() {
final SqlValidatorFixture s = fixture().withLex(Lex.JAVA);
s.withSql("select path, x from (select 1 as path, 2 as x from (values (true)))")
.type("RecordType(INTEGER NOT NULL path, INTEGER NOT NULL x) NOT NULL");
s.withSql("select path, x from (select 1 as `path`, 2 as x from (values (true)))")
.type("RecordType(INTEGER NOT NULL path, INTEGER NOT NULL x) NOT NULL");
s.withSql("select `path`, x from (select 1 as path, 2 as x from (values (true)))")
.type("RecordType(INTEGER NOT NULL path, INTEGER NOT NULL x) NOT NULL");
s.withSql("select ^PATH^ from (select 1 as path from (values (true)))")
.fails("Column 'PATH' not found in any table; did you mean 'path'\\?");
s.withSql("select t.^PATH^ from (select 1 as path from (values (true))) as t")
.fails("Column 'PATH' not found in table 't'; did you mean 'path'\\?");
s.withSql("select t.x, t.^PATH^ from (values (true, 1)) as t(path, x)")
.fails("Column 'PATH' not found in table 't'; did you mean 'path'\\?");
// Built-in functions can be written in any case, even those with no args,
// and regardless of spaces between function name and open parenthesis.
s.withSql("values (current_timestamp, floor(2.5), ceil (3.5))").ok();
s.withSql("values (CURRENT_TIMESTAMP, FLOOR(2.5), CEIL (3.5))").ok();
s.withSql("values (CURRENT_TIMESTAMP, CEIL (3.5))")
.type("RecordType(TIMESTAMP(0) NOT NULL CURRENT_TIMESTAMP, "
+ "DECIMAL(2, 0) NOT NULL EXPR$1) NOT NULL");
}
@Test void testLexAndQuoting() {
// in Java mode, creating identifiers with spaces is not encouraged, but you
// can use double-quote if you really have to
fixture()
.withLex(Lex.JAVA)
.withQuoting(Quoting.DOUBLE_QUOTE)
.withSql("select \"x[y] z \" from (\n"
+ " select e.EMPNO as \"x[y] z \" from EMP as e)")
.type("RecordType(INTEGER NOT NULL x[y] z ) NOT NULL");
}
/** Tests using case-insensitive matching of identifiers. */
@Test void testCaseInsensitive() {
final SqlValidatorFixture s = fixture()
.withCaseSensitive(false)
.withQuoting(Quoting.BRACKET);
final SqlValidatorFixture sensitive = fixture()
.withQuoting(Quoting.BRACKET);
s.withSql("select EMPNO from EMP").ok();
s.withSql("select empno from emp").ok();
s.withSql("select [empno] from [emp]").ok();
s.withSql("select [E].[empno] from [emp] as e").ok();
s.withSql("select t.[x] from (\n"
+ " select [E].[empno] as x from [emp] as e) as [t]").ok();
// correlating variable
s.withSql("select * from emp as [e] where exists (\n"
+ "select 1 from dept where dept.deptno = [E].deptno)").ok();
sensitive.withSql("select * from emp as [e] where exists (\n"
+ "select 1 from dept where dept.deptno = ^[E]^.deptno)")
.fails("(?s).*Table 'E' not found; did you mean 'e'\\?");
sql("select count(1), ^empno^ from emp")
.fails("Expression 'EMPNO' is not being grouped");
}
/** Tests using case-insensitive matching of user-defined functions. */
@Test void testCaseInsensitiveUdfs() {
final SqlOperatorTable operatorTable =
MockSqlOperatorTable.standard().extend();
final SqlValidatorFixture insensitive = fixture()
.withCaseSensitive(false)
.withQuoting(Quoting.BRACKET)
.withOperatorTable(operatorTable);
final SqlValidatorFixture sensitive = fixture()
.withQuoting(Quoting.BRACKET)
.withOperatorTable(operatorTable);
// test table function lookup case-insensitively.
insensitive.withSql("select * from dept, lateral table(ramp(dept.deptno))").ok();
insensitive.withSql("select * from dept, lateral table(RAMP(dept.deptno))").ok();
insensitive.withSql("select * from dept, lateral table([RAMP](dept.deptno))").ok();
insensitive.withSql("select * from dept, lateral table([Ramp](dept.deptno))").ok();
// test scalar function lookup case-insensitively.
insensitive.withSql("select myfun(EMPNO) from EMP").ok();
insensitive.withSql("select MYFUN(empno) from emp").ok();
insensitive.withSql("select [MYFUN]([empno]) from [emp]").ok();
insensitive.withSql("select [Myfun]([E].[empno]) from [emp] as e").ok();
insensitive.withSql("select t.[x] from (\n"
+ " select [Myfun]([E].[empno]) as x from [emp] as e) as [t]").ok();
// correlating variable
insensitive.withSql("select * from emp as [e] where exists (\n"
+ "select 1 from dept where dept.deptno = myfun([E].deptno))").ok();
sensitive.withSql("select * from emp as [e] where exists (\n"
+ "select 1 from dept where dept.deptno = ^[myfun]([e].deptno)^)")
.fails("No match found for function signature myfun\\(<NUMERIC>\\).*");
}
/** Tests using case-sensitive matching of builtin functions. */
@Test void testCaseSensitiveBuiltinFunction() {
final SqlValidatorFixture sensitive = fixture()
.withCaseSensitive(true)
.withUnquotedCasing(Casing.UNCHANGED)
.withQuoting(Quoting.BRACKET)
.withOperatorTable(SqlStdOperatorTable.instance());
sensitive.withSql("select sum(EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select [sum](EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select [SUM](EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select SUM(EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select Sum(EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select count(EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select [count](EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select [COUNT](EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select COUNT(EMPNO) from EMP group by ENAME, EMPNO").ok();
sensitive.withSql("select Count(EMPNO) from EMP group by ENAME, EMPNO").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-319">[CALCITE-319]
* Table aliases should follow case-sensitivity policy</a>. */
@Test void testCaseInsensitiveTableAlias() {
final SqlValidatorFixture s = fixture()
.withCaseSensitive(false)
.withQuoting(Quoting.BRACKET);
final SqlValidatorFixture sensitive = fixture()
.withQuoting(Quoting.BRACKET);
// Table aliases should follow case-sensitivity preference.
//
// In MySQL, table aliases are case-insensitive:
// mysql> select `D`.day from DAYS as `d`, DAYS as `D`;
// ERROR 1066 (42000): Not unique table/alias: 'D'
s.withSql("select count(*) from dept as [D], ^dept as [d]^")
.fails("Duplicate relation name 'd' in FROM clause");
sensitive.withSql("select count(*) from dept as [D], dept as [d]").ok();
sensitive.withSql("select count(*) from dept as [D], ^dept as [D]^")
.fails("Duplicate relation name 'D' in FROM clause");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1305">[CALCITE-1305]
* Case-insensitive table aliases and GROUP BY</a>. */
@Test void testCaseInsensitiveTableAliasInGroupBy() {
final SqlValidatorFixture s = fixture()
.withCaseSensitive(false)
.withUnquotedCasing(Casing.UNCHANGED);
s.withSql("select deptno, count(*) from EMP AS emp\n"
+ "group by eMp.deptno").ok();
s.withSql("select deptno, count(*) from EMP AS EMP\n"
+ "group by eMp.deptno").ok();
s.withSql("select deptno, count(*) from EMP\n"
+ "group by eMp.deptno").ok();
s.withSql("select * from EMP where exists (\n"
+ " select 1 from dept\n"
+ " group by eMp.deptno)").ok();
s.withSql("select deptno, count(*) from EMP group by DEPTNO").ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1549">[CALCITE-1549]
* Improve error message when table or column not found</a>. */
@Test void testTableNotFoundDidYouMean() {
// No table in default schema
sql("select * from ^unknownTable^")
.fails("Object 'UNKNOWNTABLE' not found");
// Similar table exists in default schema
sql("select * from ^\"Emp\"^")
.fails("Object 'Emp' not found within 'SALES'; did you mean 'EMP'\\?");
// Schema correct, but no table in specified schema
sql("select * from ^sales.unknownTable^")
.fails("Object 'UNKNOWNTABLE' not found within 'SALES'");
// Similar table exists in specified schema
sql("select * from ^sales.\"Emp\"^")
.fails("Object 'Emp' not found within 'SALES'; did you mean 'EMP'\\?");
// No schema found
sql("select * from ^unknownSchema.unknownTable^")
.fails("Object 'UNKNOWNSCHEMA' not found");
// Similar schema found
sql("select * from ^\"sales\".emp^")
.fails("Object 'sales' not found; did you mean 'SALES'\\?");
sql("select * from ^\"saLes\".\"eMp\"^")
.fails("Object 'saLes' not found; did you mean 'SALES'\\?");
// Spurious after table
sql("select * from ^emp.foo^")
.fails("Object 'FOO' not found within 'SALES\\.EMP'");
sql("select * from ^sales.emp.foo^")
.fails("Object 'FOO' not found within 'SALES\\.EMP'");
// Alias not found
sql("select ^aliAs^.\"name\"\n"
+ "from sales.emp as \"Alias\"")
.fails("Table 'ALIAS' not found; did you mean 'Alias'\\?");
// Alias not found, fully-qualified
sql("select ^sales.\"emp\"^.\"name\" from sales.emp")
.fails("Table 'SALES\\.emp' not found; did you mean 'EMP'\\?");
}
@Test void testColumnNotFoundDidYouMean() {
// Column not found
sql("select ^\"unknownColumn\"^ from emp")
.fails("Column 'unknownColumn' not found in any table");
// Similar column in table, unqualified table name
sql("select ^\"empNo\"^ from emp")
.fails("Column 'empNo' not found in any table; did you mean 'EMPNO'\\?");
// Similar column in table, table name qualified with schema
sql("select ^\"empNo\"^ from sales.emp")
.fails("Column 'empNo' not found in any table; did you mean 'EMPNO'\\?");
// Similar column in table, table name qualified with catalog and schema
sql("select ^\"empNo\"^ from catalog.sales.emp")
.fails("Column 'empNo' not found in any table; did you mean 'EMPNO'\\?");
// With table alias
sql("select e.^\"empNo\"^ from catalog.sales.emp as e")
.fails("Column 'empNo' not found in table 'E'; did you mean 'EMPNO'\\?");
// With fully-qualified table alias
sql("select catalog.sales.emp.^\"empNo\"^\n"
+ "from catalog.sales.emp")
.fails("Column 'empNo' not found in table 'CATALOG\\.SALES\\.EMP'; "
+ "did you mean 'EMPNO'\\?");
// Similar column in table; multiple tables
sql("select ^\"name\"^ from emp, dept")
.fails("Column 'name' not found in any table; did you mean 'NAME'\\?");
// Similar column in table; table and a query
sql("select ^\"name\"^ from emp,\n"
+ " (select * from dept) as d")
.fails("Column 'name' not found in any table; did you mean 'NAME'\\?");
// Similar column in table; table and an un-aliased query
sql("select ^\"name\"^ from emp, (select * from dept)")
.fails("Column 'name' not found in any table; did you mean 'NAME'\\?");
// Similar column in table, multiple tables
sql("select ^\"deptno\"^ from emp,\n"
+ " (select deptno as \"deptNo\" from dept)")
.fails("Column 'deptno' not found in any table; "
+ "did you mean 'DEPTNO', 'deptNo'\\?");
sql("select ^\"deptno\"^ from emp,\n"
+ " (select * from dept) as t(\"deptNo\", name)")
.fails("Column 'deptno' not found in any table; "
+ "did you mean 'DEPTNO', 'deptNo'\\?");
}
/** Tests matching of built-in operator names. */
@Test void testUnquotedBuiltInFunctionNames() {
final SqlValidatorFixture mysql = fixture()
.withUnquotedCasing(Casing.UNCHANGED)
.withQuoting(Quoting.BACK_TICK)
.withCaseSensitive(false);
final SqlValidatorFixture oracle = fixture()
.withUnquotedCasing(Casing.TO_UPPER)
.withCaseSensitive(true);
// Built-in functions are always case-insensitive.
oracle.withSql("select count(*), sum(deptno), floor(2.5) from dept").ok();
oracle.withSql("select COUNT(*), FLOOR(2.5) from dept").ok();
oracle.withSql("select cOuNt(*), FlOOr(2.5) from dept").ok();
oracle.withSql("select cOuNt (*), FlOOr (2.5) from dept").ok();
oracle.withSql("select current_time from dept").ok();
oracle.withSql("select Current_Time from dept").ok();
oracle.withSql("select CURRENT_TIME from dept").ok();
mysql.withSql("select sum(deptno), floor(2.5) from dept").ok();
mysql.withSql("select count(*), sum(deptno), floor(2.5) from dept").ok();
mysql.withSql("select COUNT(*), FLOOR(2.5) from dept").ok();
mysql.withSql("select cOuNt(*), FlOOr(2.5) from dept").ok();
mysql.withSql("select cOuNt (*), FlOOr (2.5) from dept").ok();
mysql.withSql("select current_time from dept").ok();
mysql.withSql("select Current_Time from dept").ok();
mysql.withSql("select CURRENT_TIME from dept").ok();
// MySQL assumes that a quoted function name is not a built-in.
//
// mysql> select `sum`(`day`) from days;
// ERROR 1630 (42000): FUNCTION foodmart.sum does not exist. Check the
// 'Function Name Parsing and Resolution' section in the Reference Manual
// mysql> select `SUM`(`day`) from days;
// ERROR 1630 (42000): FUNCTION foodmart.SUM does not exist. Check the
// 'Function Name Parsing and Resolution' section in the Reference Manual
// mysql> select SUM(`day`) from days;
// +------------+
// | SUM(`day`) |
// +------------+
// | 28 |
// +------------+
// 1 row in set (0.00 sec)
//
// We do not follow MySQL in this regard. `count` is preserved in
// lower-case, and is matched case-insensitively because it is a built-in.
// So, the query succeeds.
oracle.withSql("select \"count\"(*) from dept").ok();
mysql.withSql("select `count`(*) from dept").ok();
}
/** Sanity check: All built-ins are upper-case. We rely on this. */
@Test void testStandardOperatorNamesAreUpperCase() {
for (SqlOperator op : SqlStdOperatorTable.instance().getOperatorList()) {
final String name = op.getName();
switch (op.getSyntax()) {
case SPECIAL:
case INTERNAL:
break;
default:
assertThat(name.toUpperCase(Locale.ROOT), equalTo(name));
break;
}
}
}
private static int prec(SqlOperator op) {
return Math.max(op.getLeftPrec(), op.getRightPrec());
}
/** Tests that operators, sorted by precedence, are in a sane order. Each
* operator has a {@link SqlOperator#getLeftPrec() left} and
* {@link SqlOperator#getRightPrec()} right} precedence, but we would like
* the order to remain the same even if we tweak particular operators'
* precedences. If you need to update the expected output, you might also
* need to change
* <a href="http://calcite.apache.org/docs/reference.html#operator-precedence">
* the documentation</a>. */
@Test void testOperatorsSortedByPrecedence() {
final StringBuilder b = new StringBuilder();
final Comparator<SqlOperator> comparator = (o1, o2) -> {
int c = Integer.compare(prec(o1), prec(o2));
if (c != 0) {
return -c;
}
c = o1.getName().compareTo(o2.getName());
if (c != 0) {
return c;
}
return o1.getSyntax().compareTo(o2.getSyntax());
};
final List<SqlOperator> operators =
SqlStdOperatorTable.instance().getOperatorList();
int p = -1;
for (SqlOperator op : Ordering.from(comparator).sortedCopy(operators)) {
final String type;
switch (op.getSyntax()) {
case FUNCTION:
case FUNCTION_ID:
case FUNCTION_STAR:
case INTERNAL:
continue;
case PREFIX:
type = "pre";
break;
case POSTFIX:
type = "post";
break;
case BINARY:
if (op.getLeftPrec() < op.getRightPrec()) {
type = "left";
} else {
type = "right";
}
break;
default:
if (op instanceof SqlSpecialOperator) {
type = "-";
} else {
continue;
}
}
if (prec(op) != p) {
b.append('\n');
p = prec(op);
}
b.append(op.getName())
.append(' ')
.append(type)
.append('\n');
}
final String expected = "\n"
+ "ARRAY -\n"
+ "ARRAY -\n"
+ "COLUMN_LIST -\n"
+ "CURSOR -\n"
+ "LATERAL -\n"
+ "MAP -\n"
+ "MAP -\n"
+ "MULTISET -\n"
+ "MULTISET -\n"
+ "ROW -\n"
+ "TABLE -\n"
+ "UNNEST -\n"
+ "\n"
+ "CURRENT_VALUE -\n"
+ "DEFAULT -\n"
+ "DOT -\n"
+ "ITEM -\n"
+ "NEXT_VALUE -\n"
+ "PATTERN_EXCLUDE -\n"
+ "PATTERN_PERMUTE -\n"
+ "WITHIN DISTINCT left\n"
+ "WITHIN GROUP left\n"
+ "\n"
+ "PATTERN_QUANTIFIER -\n"
+ "\n"
+ " left\n"
+ "$LiteralChain -\n"
+ "+ pre\n"
+ "- pre\n"
+ "FINAL pre\n"
+ "RUNNING pre\n"
+ "\n"
+ "| left\n"
+ "\n"
+ "% left\n"
+ "* left\n"
+ "/ left\n"
+ "/INT left\n"
+ "|| left\n"
+ "\n"
+ "+ left\n"
+ "+ -\n"
+ "- left\n"
+ "- -\n"
+ "EXISTS pre\n"
+ "UNIQUE pre\n"
+ "\n"
+ "< ALL left\n"
+ "< SOME left\n"
+ "<= ALL left\n"
+ "<= SOME left\n"
+ "<> ALL left\n"
+ "<> SOME left\n"
+ "= ALL left\n"
+ "= SOME left\n"
+ "> ALL left\n"
+ "> SOME left\n"
+ ">= ALL left\n"
+ ">= SOME left\n"
+ "BETWEEN ASYMMETRIC -\n"
+ "BETWEEN SYMMETRIC -\n"
+ "IN left\n"
+ "LIKE -\n"
+ "NEGATED POSIX REGEX CASE INSENSITIVE left\n"
+ "NEGATED POSIX REGEX CASE SENSITIVE left\n"
+ "NOT BETWEEN ASYMMETRIC -\n"
+ "NOT BETWEEN SYMMETRIC -\n"
+ "NOT IN left\n"
+ "NOT LIKE -\n"
+ "NOT SIMILAR TO -\n"
+ "POSIX REGEX CASE INSENSITIVE left\n"
+ "POSIX REGEX CASE SENSITIVE left\n"
+ "SIMILAR TO -\n"
+ "\n"
+ "$IS_DIFFERENT_FROM left\n"
+ "< left\n"
+ "<= left\n"
+ "<> left\n"
+ "= left\n"
+ "> left\n"
+ ">= left\n"
+ "CONTAINS left\n"
+ "EQUALS left\n"
+ "IMMEDIATELY PRECEDES left\n"
+ "IMMEDIATELY SUCCEEDS left\n"
+ "IS DISTINCT FROM left\n"
+ "IS NOT DISTINCT FROM left\n"
+ "MEMBER OF left\n"
+ "NOT SUBMULTISET OF left\n"
+ "OVERLAPS left\n"
+ "PRECEDES left\n"
+ "SUBMULTISET OF left\n"
+ "SUCCEEDS left\n"
+ "\n"
+ "FORMAT JSON post\n"
+ "IS A SET post\n"
+ "IS EMPTY post\n"
+ "IS FALSE post\n"
+ "IS JSON ARRAY post\n"
+ "IS JSON OBJECT post\n"
+ "IS JSON SCALAR post\n"
+ "IS JSON VALUE post\n"
+ "IS NOT A SET post\n"
+ "IS NOT EMPTY post\n"
+ "IS NOT FALSE post\n"
+ "IS NOT JSON ARRAY post\n"
+ "IS NOT JSON OBJECT post\n"
+ "IS NOT JSON SCALAR post\n"
+ "IS NOT JSON VALUE post\n"
+ "IS NOT NULL post\n"
+ "IS NOT TRUE post\n"
+ "IS NOT UNKNOWN post\n"
+ "IS NULL post\n"
+ "IS TRUE post\n"
+ "IS UNKNOWN post\n"
+ "\n"
+ "NOT pre\n"
+ "\n"
+ "AND left\n"
+ "\n"
+ "OR left\n"
+ "\n"
+ "=> -\n"
+ "AS -\n"
+ "DESC post\n"
+ "FILTER left\n"
+ "IGNORE NULLS -\n"
+ "OVER left\n"
+ "RESPECT NULLS -\n"
+ "TABLESAMPLE -\n"
+ "\n"
+ "NULLS FIRST post\n"
+ "NULLS LAST post\n"
+ "\n"
+ "INTERSECT left\n"
+ "INTERSECT ALL left\n"
+ "MULTISET INTERSECT ALL left\n"
+ "MULTISET INTERSECT DISTINCT left\n"
+ "\n"
+ "EXCEPT left\n"
+ "EXCEPT ALL left\n"
+ "MULTISET EXCEPT ALL left\n"
+ "MULTISET EXCEPT DISTINCT left\n"
+ "MULTISET UNION ALL left\n"
+ "MULTISET UNION DISTINCT left\n"
+ "UNION left\n"
+ "UNION ALL left\n"
+ "\n"
+ "$throw -\n"
+ "Reinterpret -\n"
+ "TABLE pre\n"
+ "VALUES -\n"
+ "\n"
+ "CALL pre\n"
+ "ESCAPE -\n"
+ "NEW pre\n";
assertThat(b, hasToString(expected));
}
/** Tests that it is an error to insert into the same column twice, even using
* case-insensitive matching. */
@Test void testCaseInsensitiveInsert() {
sql("insert into EMP ([EMPNO], deptno, ^[empno]^)\n"
+ " values (1, 1, 1)")
.withCaseSensitive(false)
.withQuoting(Quoting.BRACKET)
.fails("Target column 'EMPNO' is assigned more than once");
}
/** Tests referencing columns from a sub-query that has duplicate column
* names. (The standard says it should be an error, but we don't right
* now.) */
@Test void testCaseInsensitiveSubQuery() {
final SqlValidatorFixture insensitive = fixture()
.withCaseSensitive(false)
.withQuoting(Quoting.BRACKET);
final SqlValidatorFixture sensitive = fixture()
.withCaseSensitive(true)
.withUnquotedCasing(Casing.UNCHANGED)
.withQuoting(Quoting.BRACKET);
String sql = "select [e] from (\n"
+ "select EMPNO as [e], DEPTNO as d, 1 as [e2] from EMP)";
sensitive.withSql(sql).ok();
insensitive.withSql(sql).ok();
String sql1 = "select e2 from (\n"
+ "select EMPNO as [e2], DEPTNO as d, 1 as [E] from EMP)";
insensitive.withSql(sql1).ok();
sensitive.withSql(sql1).ok();
}
/** Tests using case-insensitive matching of table names. */
@Test void testCaseInsensitiveTables() {
final SqlValidatorFixture mssql = fixture().withLex(Lex.SQL_SERVER);
mssql.withSql("select eMp.* from (select * from emp) as EmP").ok();
mssql.withSql("select ^eMp^.* from (select * from emp as EmP)")
.fails("Unknown identifier 'eMp'");
mssql.withSql("select eMp.* from (select * from emP) as EmP").ok();
mssql.withSql("select eMp.empNo from (select * from emP) as EmP").ok();
mssql.withSql("select empNo from (select Empno from emP) as EmP").ok();
mssql.withSql("select empNo from (select Empno from emP)").ok();
}
@Test void testInsert() {
sql("insert into empnullables (empno, ename)\n"
+ "values (1, 'Ambrosia')").ok();
sql("insert into empnullables (empno, ename)\n"
+ "select 1, 'Ardy' from (values 'a')").ok();
final String sql = "insert into emp\n"
+ "values (1, 'nom', 'job', 0, timestamp '1970-01-01 00:00:00', 1, 1,\n"
+ " 1, false)";
sql(sql).ok();
final String sql2 = "insert into emp (empno, ename, job, mgr, hiredate,\n"
+ " sal, comm, deptno, slacker)\n"
+ "select 1, 'nom', 'job', 0, timestamp '1970-01-01 00:00:00',\n"
+ " 1, 1, 1, false\n"
+ "from (values 'a')";
sql(sql2).ok();
sql("insert into empnullables (ename, empno, deptno)\n"
+ "values ('Pat', 1, null)").ok();
final String sql3 = "insert into empnullables (\n"
+ " empno, ename, job, hiredate)\n"
+ "values (1, 'Jim', 'Baker', timestamp '1970-01-01 00:00:00')";
sql(sql3).ok();
sql("insert into empnullables (empno, ename)\n"
+ "select 1, 'b' from (values 'a')").ok();
sql("insert into empnullables (empno, ename)\n"
+ "values (1, 'Karl')").ok();
}
@Test void testInsertWithNonEqualSourceSinkFieldsNum() {
sql("insert into ^dept^ select sid, ename, deptno "
+ "from "
+ "(select sum(empno) as sid, ename, deptno, sal "
+ "from emp group by ename, deptno, sal)")
.fails("Number of INSERT target columns \\(2\\) "
+ "does not equal number of source items \\(3\\)");
}
@Test void testInsertSubset() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
final String sql1 = "insert into empnullables\n"
+ "values (1, 'nom', 'job', 0, timestamp '1970-01-01 00:00:00')";
s.withSql(sql1).ok();
final String sql2 = "insert into empnullables\n"
+ "values (1, 'nom', null, 0, null)";
s.withSql(sql2).ok();
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1510">[CALCITE-1510]
* INSERT/UPSERT should allow fewer values than columns</a>,
* check for default value only when target field is null. */
@Test void testInsertShouldNotCheckForDefaultValue() {
final int c = CountingFactory.THREAD_CALL_COUNT.get().get();
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
final String sql1 = "insert into emp values(1, 'nom', 'job', 0, "
+ "timestamp '1970-01-01 00:00:00', 1, 1, 1, false)";
s.withSql(sql1).ok();
assertThat("Should not check for default value if column is in INSERT",
CountingFactory.THREAD_CALL_COUNT.get().get(), is(c));
// Now add a list of target columns, keeping the query otherwise the same.
final String sql2 = "insert into emp (empno, ename, job, mgr, hiredate,\n"
+ " sal, comm, deptno, slacker)\n"
+ "values(1, 'nom', 'job', 0,\n"
+ " timestamp '1970-01-01 00:00:00', 1, 1, 1, false)";
s.withSql(sql2).ok();
assertThat("Should not check for default value if column is in INSERT",
CountingFactory.THREAD_CALL_COUNT.get().get(), is(c));
// Now remove SLACKER, which is NOT NULL, from the target list.
final String sql3 = "insert into ^emp^ (empno, ename, job, mgr, hiredate,\n"
+ " sal, comm, deptno)\n"
+ "values(1, 'nom', 'job', 0,\n"
+ " timestamp '1970-01-01 00:00:00', 1, 1, 1)";
s.withSql(sql3)
.fails("Column 'SLACKER' has no default value and does not allow NULLs");
assertThat("Should not check for default value, even if if column is "
+ "missing from INSERT and nullable",
CountingFactory.THREAD_CALL_COUNT.get().get(),
is(c));
// Now remove DEPTNO, which has a default value, from the target list.
// Will generate an extra call to newColumnDefaultValue at sql-to-rel time,
// just not yet.
final String sql4 = "insert into ^emp^ (empno, ename, job, mgr, hiredate,\n"
+ " sal, comm, slacker)\n"
+ "values(1, 'nom', 'job', 0,\n"
+ " timestamp '1970-01-01 00:00:00', 1, 1, false)";
s.withSql(sql4).ok();
assertThat("Missing DEFAULT column generates a call to factory",
CountingFactory.THREAD_CALL_COUNT.get().get(),
is(c));
}
@Test void testInsertView() {
sql("insert into empnullables_20 (ename, empno, comm)\n"
+ "values ('Karl', 1, 1)").ok();
}
@Test void testInsertSubsetView() {
sql("insert into empnullables_20\n"
+ "values (1, 'Karl')")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.ok();
}
@Test void testInsertModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("insert into EMP_MODIFIABLEVIEW (empno, ename, job)\n"
+ "values (1, 'Arthur', 'clown')").ok();
s.withSql("insert into EMP_MODIFIABLEVIEW2 (empno, ename, job, extra)\n"
+ "values (1, 'Arthur', 'clown', true)").ok();
}
@Test void testInsertSubsetModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog()
.withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("insert into EMP_MODIFIABLEVIEW2\n"
+ "values ('Arthur', 1)").ok();
s.withSql("insert into EMP_MODIFIABLEVIEW2\n"
+ "values ('Arthur', 1, 'Knight', 20, false, 99999, true, timestamp '1370-01-01 00:00:00',"
+ " 1, 100)").ok();
}
@Test void testInsertBind() {
// VALUES
final String sql0 = "insert into empnullables (empno, ename, deptno)\n"
+ "values (?, ?, ?)";
final String expectedType0 =
"RecordType(INTEGER ?0, VARCHAR(20) ?1, INTEGER ?2)";
sql(sql0).ok()
.assertBindType(is(expectedType0));
// multiple VALUES
final String sql1 = "insert into empnullables (empno, ename, deptno)\n"
+ "values (?, 'Pat', 1), (2, ?, ?), (3, 'Tod', ?), (4, 'Arthur', null)";
final String expectedType1 =
"RecordType(INTEGER ?0, VARCHAR(20) ?1, INTEGER ?2, INTEGER ?3)";
sql(sql1).ok()
.assertBindType(is(expectedType1));
// VALUES with expression
sql("insert into empnullables (ename, empno) values (?, ? + 1)")
.ok()
.assertBindType(is("RecordType(VARCHAR(20) ?0, INTEGER ?1)"));
// SELECT
sql("insert into empnullables (ename, empno) select ?, ? from (values (1))")
.ok()
.assertBindType(is("RecordType(VARCHAR(20) ?0, INTEGER ?1)"));
// WITH
final String sql3 = "insert into empnullables (ename, empno)\n"
+ "with v as (values ('a'))\n"
+ "select ?, ? from (values (1))";
sql(sql3).ok()
.assertBindType(is("RecordType(VARCHAR(20) ?0, INTEGER ?1)"));
// UNION
final String sql2 = "insert into empnullables (ename, empno)\n"
+ "select ?, ? from (values (1))\n"
+ "union all\n"
+ "select ?, ? from (values (time '1:2:3'))";
final String expected2 = "RecordType(VARCHAR(20) ?0, INTEGER ?1,"
+ " VARCHAR(20) ?2, INTEGER ?3)";
sql(sql2).ok().assertBindType(is(expected2));
}
@Test void testInsertWithExtendedColumns() {
final String sql0 = "insert into empnullables\n"
+ " (empno, ename, \"f.dc\" ^varchar(10)^)\n"
+ "values (?, ?, ?)";
final String expectedType0 =
"RecordType(INTEGER ?0, VARCHAR(20) ?1, VARCHAR(10) ?2)";
sql(sql0).withExtendedCatalog()
.withConformance(SqlConformanceEnum.LENIENT)
.ok()
.assertBindType(is(expectedType0))
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.fails("Extended columns not allowed under "
+ "the current SQL conformance level");
final String sql1 = "insert into empnullables\n"
+ " (empno, ename, dynamic_column ^double^ not null)\n"
+ "values (?, ?, ?)";
final String expectedType1 =
"RecordType(INTEGER ?0, VARCHAR(20) ?1, DOUBLE ?2)";
sql(sql1)
.withExtendedCatalog()
.withConformance(SqlConformanceEnum.LENIENT)
.ok()
.assertBindType(is(expectedType1))
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.fails("Extended columns not allowed under "
+ "the current SQL conformance level");
final String sql2 = "insert into struct.t_extend\n"
+ " (f0.c0, f1.c1, \"F2\".\"C2\" ^varchar(20)^ not null)\n"
+ "values (?, ?, ?)";
final String expectedType2 =
"RecordType(INTEGER ?0, INTEGER ?1, VARCHAR(20) ?2)";
sql(sql2)
.withExtendedCatalog()
.withConformance(SqlConformanceEnum.LENIENT)
.ok()
.assertBindType(is(expectedType2))
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.fails("Extended columns not allowed under "
+ "the current SQL conformance level");
}
@Test void testInsertBindSubset() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
// VALUES
final String sql0 = "insert into empnullables\n"
+ "values (?, ?, ?)";
final String expectedType0 = "RecordType(INTEGER ?0, VARCHAR(20) ?1, VARCHAR(10) ?2)";
s.withSql(sql0).ok()
.assertBindType(is(expectedType0));
// multiple VALUES
final String sql1 = "insert into empnullables\n"
+ "values (?, 'Pat', 'Tailor'), (2, ?, ?),\n"
+ " (3, 'Tod', ?), (4, 'Arthur', null)";
final String expectedType1 = "RecordType(INTEGER ?0, VARCHAR(20) ?1, VARCHAR(10) ?2, "
+ "VARCHAR(10) ?3)";
s.withSql(sql1).ok()
.assertBindType(is(expectedType1));
// VALUES with expression
s.withSql("insert into empnullables values (? + 1, ?)").ok()
.assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)"));
// SELECT
s.withSql("insert into empnullables select ?, ? from (values (1))").ok()
.assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)"));
// WITH
final String sql3 = "insert into empnullables\n"
+ "with v as (values ('a'))\n"
+ "select ?, ? from (values (1))";
s.withSql(sql3).ok()
.assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)"));
// UNION
final String sql2 = "insert into empnullables\n"
+ "select ?, ? from (values (1))\n"
+ "union all\n"
+ "select ?, ? from (values (time '1:2:3'))";
final String expected2 = "RecordType(INTEGER ?0, VARCHAR(20) ?1,"
+ " INTEGER ?2, VARCHAR(20) ?3)";
s.withSql(sql2).ok().assertBindType(is(expected2));
}
@Test void testInsertBindView() {
final String sql = "insert into EMP_MODIFIABLEVIEW (mgr, empno, ename)"
+ " values (?, ?, ?)";
final String expectedType =
"RecordType(INTEGER ?0, INTEGER ?1, VARCHAR(20) ?2)";
sql(sql).withExtendedCatalog().ok()
.assertBindType(is(expectedType));
}
@Test void testInsertModifiableViewPassConstraint() {
sql("insert into EMP_MODIFIABLEVIEW2 (deptno, empno, ename, extra)"
+ " values (20, 100, 'Lex', true)")
.withExtendedCatalog()
.ok();
sql("insert into EMP_MODIFIABLEVIEW2 (empno, ename, extra)"
+ " values (100, 'Lex', true)")
.withExtendedCatalog()
.ok();
sql("insert into EMP_MODIFIABLEVIEW2 values ('Edward', 20)")
.withExtendedCatalog()
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.ok();
}
@Test void testInsertModifiableViewFailConstraint() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW2 (deptno, empno, ename)"
+ " values (^21^, 100, 'Lex')";
final String error0 = "Modifiable view constraint is not satisfied"
+ " for column 'DEPTNO' of base table 'EMP_MODIFIABLEVIEW2'";
s.withSql(sql0).fails(error0);
final String sql1 = "insert into EMP_MODIFIABLEVIEW2 (deptno, empno, ename)"
+ " values (^19+1^, 100, 'Lex')";
final String error1 = "Modifiable view constraint is not satisfied"
+ " for column 'DEPTNO' of base table 'EMP_MODIFIABLEVIEW2'";
s.withSql(sql1).fails(error1);
final String sql2 = "insert into EMP_MODIFIABLEVIEW2\n"
+ "values ('Arthur', 1, 'Knight', ^27^, false, 99999, true,"
+ "timestamp '1370-01-01 00:00:00', 1, 100)";
final String error2 = "Modifiable view constraint is not satisfied"
+ " for column 'DEPTNO' of base table 'EMP_MODIFIABLEVIEW2'";
s.withSql(sql2).fails(error2);
}
@Test void testUpdateModifiableViewPassConstraint() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("update EMP_MODIFIABLEVIEW2"
+ " set deptno = 20, empno = 99"
+ " where ename = 'Lex'").ok();
s.withSql("update EMP_MODIFIABLEVIEW2"
+ " set empno = 99"
+ " where ename = 'Lex'").ok();
}
@Test void testUpdateModifiableViewFailConstraint() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "update EMP_MODIFIABLEVIEW2"
+ " set deptno = ^21^, empno = 99"
+ " where ename = 'Lex'";
final String error = "Modifiable view constraint is not satisfied"
+ " for column 'DEPTNO' of base table 'EMP_MODIFIABLEVIEW2'";
s.withSql(sql0).fails(error);
final String sql1 = "update EMP_MODIFIABLEVIEW2"
+ " set deptno = ^19 + 1^, empno = 99"
+ " where ename = 'Lex'";
s.withSql(sql1).fails(error);
}
@Test void testInsertTargetTableWithVirtualColumns() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("insert into VIRTUALCOLUMNS.VC_T1\n"
+ "select a, b, c from VIRTUALCOLUMNS.VC_T2").ok();
final String sql0 = "insert into ^VIRTUALCOLUMNS.VC_T1^\n"
+ "values(1, 2, 'abc', 3, 4)";
final String error0 = "Cannot INSERT into generated column 'D'";
s.withSql(sql0).fails(error0);
final String sql1 = "insert into ^VIRTUALCOLUMNS.VC_T1^\n"
+ "values(1, 2, 'abc', DEFAULT, DEFAULT)";
s.withSql(sql1).ok();
final String sql2 = "insert into ^VIRTUALCOLUMNS.VC_T1^\n"
+ "values(1, 2, 'abc', DEFAULT)";
final String error2 = "(?s).*Number of INSERT target columns \\(5\\) "
+ "does not equal number of source items \\(4\\).*";
s.withSql(sql2).fails(error2);
final String sql3 = "insert into ^VIRTUALCOLUMNS.VC_T1^\n"
+ "values(1, 2, 'abc', DEFAULT, DEFAULT, DEFAULT)";
final String error3 = "(?s).*Number of INSERT target columns \\(5\\) "
+ "does not equal number of source items \\(6\\).*";
s.withSql(sql3).fails(error3);
final String sql4 = "insert into VIRTUALCOLUMNS.VC_T1\n"
+ "^values(1, '2', 'abc')^";
final String error4 = "(?s).*Cannot assign to target field 'B' of type BIGINT "
+ "from source field 'EXPR\\$1' of type CHAR\\(1\\).*";
s.withSql(sql4).withTypeCoercion(false).fails(error4);
s.withSql(sql4).ok();
}
@Test void testInsertFailNullability() {
sql("insert into ^emp^ (ename) values ('Kevin')")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
sql("insert into ^emp^ (empno) values (10)")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
sql("insert into emp (empno, ename, deptno) ^values (5, null, 5)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertSubsetFailNullability() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("insert into ^emp^ values (1)")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
s.withSql("insert into emp ^values (null, 'Liam')^")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
s.withSql("insert into emp ^values (45, null, 5)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertViewFailNullability() {
sql("insert into ^emp_20^ (ename) values ('Jake')")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
sql("insert into ^emp_20^ (empno) values (9)")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
sql("insert into emp_20 (empno, ename, mgr) ^values (5, null, 5)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertSubsetViewFailNullability() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("insert into ^EMP_20^ values (1)")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
s.withSql("insert into EMP_20 ^values (null, 'Liam')^")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
s.withSql("insert into EMP_20 ^values (45, null)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertBindFailNullability() {
sql("insert into ^emp^ (ename) values (?)")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
sql("insert into ^emp^ (empno) values (?)")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
sql("insert into emp (empno, ename, deptno) ^values (?, null, 5)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertBindSubsetFailNullability() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("insert into ^emp^ values (?)")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
s.withSql("insert into emp ^values (null, ?)^")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
s.withSql("insert into emp ^values (?, null)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertSubsetDisallowed() {
sql("insert into ^emp^ values (1)")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(1\\)");
sql("insert into ^emp^ values (null)")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(1\\)");
sql("insert into ^emp^ values (1, 'Kevin')")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(2\\)");
}
@Test void testInsertSubsetViewDisallowed() {
sql("insert into ^emp_20^ values (1)")
.fails("Number of INSERT target columns \\(8\\) does not equal "
+ "number of source items \\(1\\)");
sql("insert into ^emp_20^ values (null)")
.fails("Number of INSERT target columns \\(8\\) does not equal "
+ "number of source items \\(1\\)");
sql("insert into ^emp_20^ values (?, ?)")
.fails("Number of INSERT target columns \\(8\\) does not equal "
+ "number of source items \\(2\\)");
}
@Test void testInsertBindSubsetDisallowed() {
sql("insert into ^emp^ values (?)")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(1\\)");
sql("insert into ^emp^ values (?, ?)")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(2\\)");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-985">[CALCITE-985]
* Validate MERGE</a>. */
@Test void testMergeInto() {
sql("merge into empnullables e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ename, deptno, sal) "
+ "values(t.empno, t.ename, 10, t.sal * .15)")
.ok();
sql("merge into ^emp^ e "
+ "using (select * from empnullables where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ename, deptno, sal) "
+ "values(t.empno, t.ename, 10, t.sal * .15)")
.fails("Column 'JOB' has no default value and does not allow NULLs");
}
@Test void testMergeFailCaseSensitivity() {
final SqlValidatorFixture s = fixture()
.withExtendedCatalog();
final String sql0 = "merge into EMP_MODIFIABLEVIEW e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, sal = t.sal * .1 "
+ "when not matched then insert (^\"empno\"^, ename, sal) "
+ "values(t.empno, t.ename, t.sal * .15)";
s.withSql(sql0).fails("Unknown target column 'empno'");
}
@Test void testMergeFailExcludedColumn() {
sql("merge into empnullables e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ^name^, deptno, sal) "
+ "values(t.empno, t.ename, 10, t.sal * .15)")
.fails("Unknown target column 'NAME'");
}
@Test void testMergeBindWithCustomInitializerExpressionFactory() {
sql("merge into empdefaults e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (deptno) "
+ "values(?)").ok()
.assertBindType(is("RecordType(INTEGER ?0)"));
sql("merge into empdefaults e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ename) "
+ "values(?, ?)").ok()
.assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)"));
sql("merge into empdefaults e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ename) "
+ "values(^null^, ?)")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
}
@Test void testMergeBindSubsetWithCustomInitializerExpressionFactory() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("merge into empdefaults e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ename, deptno, sal) "
+ "values(t.empno, t.ename, ?, t.sal * .15)")
.assertBindType(is("RecordType(INTEGER ?0)"));
s.withSql("merge into empdefaults e "
+ "using (select * from emp where deptno is null) t "
+ "on e.empno = t.empno "
+ "when matched then update "
+ "set ename = t.ename, deptno = t.deptno, sal = t.sal * .1 "
+ "when not matched then insert (empno, ename, deptno, sal) "
+ "values(^null^, t.ename, ?, t.sal * .15)")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
}
@Test void testSelectExtendedColumnDuplicate() {
sql("select deptno, extra from emp (extra int, \"extra\" boolean)").ok();
sql("select deptno, extra from emp (extra int, \"extra\" int)").ok();
sql("select deptno, extra from emp (extra int, ^extra^ int)")
.fails("Duplicate name 'EXTRA' in column list");
sql("select deptno, extra from emp (extra int, ^extra^ boolean)")
.fails("Duplicate name 'EXTRA' in column list");
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "select deptno, extra\n"
+ "from EMP_MODIFIABLEVIEW (extra int, ^extra^ int)";
s.withSql(sql0).fails("Duplicate name 'EXTRA' in column list");
final String sql1 = "select deptno, extra from EMP_MODIFIABLEVIEW"
+ " (extra int, ^extra^ boolean)";
s.withSql(sql1).fails("Duplicate name 'EXTRA' in column list");
}
@Test void testSelectViewFailExcludedColumn() {
final String sql = "select ^deptno^, empno from EMP_MODIFIABLEVIEW";
final String error = "Column 'DEPTNO' not found in any table";
sql(sql).withExtendedCatalog().fails(error);
}
@Test void testSelectViewExtendedColumnCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR\n"
+ " from EMP_MODIFIABLEVIEW3 extend (SAL int)\n"
+ " where SAL = 20").ok();
s.withSql("select ENAME, EMPNO, JOB, SLACKER, SAL, \"Sal\", HIREDATE, MGR\n"
+ " from EMP_MODIFIABLEVIEW3 extend (\"Sal\" VARCHAR)\n"
+ " where SAL = 20").ok();
}
@Test void testSelectViewExtendedColumnExtendedCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, EXTRA\n"
+ " from EMP_MODIFIABLEVIEW2 extend (EXTRA boolean)\n"
+ " where SAL = 20").ok();
s.withSql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, EXTRA,"
+ " \"EXtra\"\n"
+ " from EMP_MODIFIABLEVIEW2 extend (\"EXtra\" VARCHAR)\n"
+ " where SAL = 20").ok();
}
@Test void testSelectViewExtendedColumnUnderlyingCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, COMM\n"
+ " from EMP_MODIFIABLEVIEW3 extend (COMM int)\n"
+ " where SAL = 20").ok();
s.withSql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, \"comM\"\n"
+ " from EMP_MODIFIABLEVIEW3 extend (\"comM\" BOOLEAN)\n"
+ " where SAL = 20").ok();
}
@Test void testSelectExtendedColumnCollision() {
sql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, COMM\n"
+ " from EMPDEFAULTS extend (COMM int)\n"
+ " where SAL = 20").ok();
sql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, COMM, \"ComM\"\n"
+ " from EMPDEFAULTS extend (\"ComM\" int)\n"
+ " where SAL = 20").ok();
}
@Test void testSelectExtendedColumnFailCollision() {
sql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, COMM\n"
+ " from EMPDEFAULTS extend (^COMM^ boolean)\n"
+ " where SAL = 20")
.fails("Cannot assign to target field 'COMM' of type INTEGER "
+ "from source field 'COMM' of type BOOLEAN");
sql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, COMM\n"
+ " from EMPDEFAULTS extend (^EMPNO^ integer)\n"
+ " where SAL = 20")
.fails("Cannot assign to target field 'EMPNO' of type INTEGER NOT NULL "
+ "from source field 'EMPNO' of type INTEGER");
sql("select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE, MGR, COMM\n"
+ " from EMPDEFAULTS extend (^\"EMPNO\"^ integer)\n"
+ " where SAL = 20")
.fails("Cannot assign to target field 'EMPNO' of type INTEGER NOT NULL "
+ "from source field 'EMPNO' of type INTEGER");
}
@Test void testSelectViewExtendedColumnFailCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE,"
+ " MGR, EXTRA\n"
+ "from EMP_MODIFIABLEVIEW2 extend (^SLACKER^ integer)\n"
+ " where SAL = 20";
final String error0 = "Cannot assign to target field 'SLACKER' of type"
+ " BOOLEAN from source field 'SLACKER' of type INTEGER";
s.withSql(sql0).fails(error0);
final String sql1 = "select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE,"
+ " MGR, COMM\n"
+ "from EMP_MODIFIABLEVIEW2 extend (^EMPNO^ integer)\n"
+ " where SAL = 20";
final String error1 = "Cannot assign to target field 'EMPNO' of type"
+ " INTEGER NOT NULL from source field 'EMPNO' of type INTEGER";
s.withSql(sql1).fails(error1);
}
@Test void testSelectViewExtendedColumnFailExtendedCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE,"
+ " MGR, EXTRA\n"
+ "from EMP_MODIFIABLEVIEW2 extend (^EXTRA^ integer)\n"
+ " where SAL = 20";
final String error = "Cannot assign to target field 'EXTRA' of type"
+ " BOOLEAN from source field 'EXTRA' of type INTEGER";
s.withSql(sql0).fails(error);
final String sql1 = "select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE,"
+ " MGR, EXTRA\n"
+ "from EMP_MODIFIABLEVIEW2 extend (^\"EXTRA\"^ integer)\n"
+ " where SAL = 20";
s.withSql(sql1).fails(error);
}
@Test void testSelectViewExtendedColumnFailUnderlyingCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE,"
+ " MGR, COMM\n"
+ "from EMP_MODIFIABLEVIEW3 extend (^COMM^ boolean)\n"
+ "where SAL = 20";
final String error = "Cannot assign to target field 'COMM' of type INTEGER"
+ " from source field 'COMM' of type BOOLEAN";
s.withSql(sql0).fails(error);
final String sql1 = "select ENAME, EMPNO, JOB, SLACKER, SAL, HIREDATE,"
+ " MGR, COMM\n"
+ "from EMP_MODIFIABLEVIEW3 extend (^\"COMM\"^ boolean)\n"
+ " where SAL = 20";
s.withSql(sql1).fails(error);
}
@Test void testSelectFailCaseSensitivity() {
sql("select ^\"empno\"^, ename, deptno from EMP")
.fails("Column 'empno' not found in any table; did you mean 'EMPNO'\\?");
sql("select ^\"extra\"^, ename, deptno from EMP (extra boolean)")
.fails("Column 'extra' not found in any table; did you mean 'EXTRA'\\?");
sql("select ^extra^, ename, deptno from EMP (\"extra\" boolean)")
.fails("Column 'EXTRA' not found in any table; did you mean 'extra'\\?");
}
@Test void testInsertFailCaseSensitivity() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW"
+ " (^\"empno\"^, ename, deptno)"
+ " values (45, 'Jake', 5)";
s.withSql(sql0).fails("Unknown target column 'empno'");
final String sql1 = "insert into EMP_MODIFIABLEVIEW (\"extra\" int)"
+ " (^extra^, ename, deptno)"
+ " values (45, 'Jake', 5)";
s.withSql(sql1).fails("Unknown target column 'EXTRA'");
final String sql2 = "insert into EMP_MODIFIABLEVIEW (extra int)"
+ " (^\"extra\"^, ename, deptno)"
+ " values (45, 'Jake', 5)";
s.withSql(sql2).fails("Unknown target column 'extra'");
}
@Test void testInsertFailExcludedColumn() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql = ""
+ "insert into EMP_MODIFIABLEVIEW (empno, ename, ^deptno^)"
+ " values (45, 'Jake', 5)";
s.withSql(sql).fails("Unknown target column 'DEPTNO'");
}
@Test void testInsertBindViewFailExcludedColumn() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql = "insert into EMP_MODIFIABLEVIEW (empno, ename, ^deptno^)"
+ " values (?, ?, ?)";
s.withSql(sql).fails("Unknown target column 'DEPTNO'");
}
@Test void testInsertWithCustomInitializerExpressionFactory() {
sql("insert into empdefaults (deptno) values (1)").ok();
sql("insert into empdefaults (ename, empno) values ('Quan', 50)").ok();
sql("insert into empdefaults (ename, deptno) ^values (null, 1)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
sql("insert into ^empdefaults^ values (null, 'Tod')")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(2\\)");
}
@Test void testInsertSubsetWithCustomInitializerExpressionFactory() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("insert into empdefaults values (101)").ok();
s.withSql("insert into empdefaults values (101, 'Coral')").ok();
s.withSql("insert into empdefaults ^values (null, 'Tod')^")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
s.withSql("insert into empdefaults ^values (78, null)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
}
@Test void testInsertBindWithCustomInitializerExpressionFactory() {
sql("insert into empdefaults (deptno) values (?)").ok()
.assertBindType(is("RecordType(INTEGER ?0)"));
sql("insert into empdefaults (ename, empno) values (?, ?)").ok()
.assertBindType(is("RecordType(VARCHAR(20) ?0, INTEGER ?1)"));
sql("insert into empdefaults (ename, deptno) ^values (null, ?)^")
.fails("Column 'ENAME' has no default value and does not allow NULLs");
sql("insert into ^empdefaults^ values (null, ?)")
.fails("Number of INSERT target columns \\(9\\) does not equal "
+ "number of source items \\(2\\)");
}
@Test void testInsertBindSubsetWithCustomInitializerExpressionFactory() {
final SqlValidatorFixture s = fixture().withConformance(SqlConformanceEnum.PRAGMATIC_2003);
s.withSql("insert into empdefaults values (101, ?)").ok()
.assertBindType(is("RecordType(VARCHAR(20) ?0)"));
s.withSql("insert into empdefaults ^values (null, ?)^")
.fails("Column 'EMPNO' has no default value and does not allow NULLs");
}
@Test void testInsertBindWithCustomColumnResolving() {
final SqlConformanceEnum pragmatic = SqlConformanceEnum.PRAGMATIC_2003;
final String sql = "insert into struct.t\n"
+ "values (?, ?, ?, ?, ?, ?, ?, ?, ?)";
final String expected = "RecordType(VARCHAR(20) ?0, VARCHAR(20) ?1,"
+ " INTEGER ?2, BOOLEAN ?3, INTEGER ?4, INTEGER ?5, INTEGER ?6,"
+ " INTEGER ?7, INTEGER ?8)";
sql(sql).ok().assertBindType(is(expected));
final String sql2 =
"insert into struct.t_nullables (c0, c2, c1) values (?, ?, ?)";
final String expected2 =
"RecordType(INTEGER ?0, INTEGER ?1, VARCHAR(20) ?2)";
sql(sql2).withConformance(pragmatic).ok().assertBindType(is(expected2));
final String sql3 =
"insert into struct.t_nullables (f1.c0, f1.c2, f0.c1) values (?, ?, ?)";
final String expected3 =
"RecordType(INTEGER ?0, INTEGER ?1, INTEGER ?2)";
sql(sql3).withConformance(pragmatic).ok().assertBindType(is(expected3));
sql("insert into struct.t_nullables (c0, ^c4^, c1) values (?, ?, ?)")
.withConformance(pragmatic)
.fails("Unknown target column 'C4'");
sql("insert into struct.t_nullables (^a0^, c2, c1) values (?, ?, ?)")
.withConformance(pragmatic)
.fails("Unknown target column 'A0'");
final String sql4 = "insert into struct.t_nullables (\n"
+ " f1.c0, ^f0.a0^, f0.c1) values (?, ?, ?)";
sql(sql4).withConformance(pragmatic)
.fails("Unknown target column 'F0.A0'");
final String sql5 = "insert into struct.t_nullables (\n"
+ " f1.c0, f1.c2, ^f1.c0^) values (?, ?, ?)";
sql(sql5).withConformance(pragmatic)
.fails("Target column '\"F1\".\"C0\"' is assigned more than once");
}
@Test void testUpdateBind() {
final String sql = "update emp\n"
+ "set ename = ?\n"
+ "where deptno = ?";
sql(sql).ok()
.assertBindType(is("RecordType(VARCHAR(20) ?0, INTEGER ?1)"));
}
@Test void testDeleteBind() {
final String sql = "delete from emp\n"
+ "where deptno = ?\n"
+ "or ename = ?";
sql(sql).ok()
.assertBindType(is("RecordType(INTEGER ?0, VARCHAR(20) ?1)"));
}
@Test void testStream() {
sql("select stream * from orders").ok();
sql("select stream * from ^emp^")
.fails(cannotConvertToStream("EMP"));
sql("select * from ^orders^")
.fails(cannotConvertToRelation("ORDERS"));
}
@Test void testStreamWhere() {
sql("select stream * from orders where productId < 10").ok();
sql("select stream * from ^emp^ where deptno = 10")
.fails(cannotConvertToStream("EMP"));
sql("select stream * from ^emp^ as e where deptno = 10")
.fails(cannotConvertToStream("E"));
sql("select stream * from (^select * from emp as e1^) as e\n"
+ "where deptno = 10")
.fails("Cannot convert table 'E' to stream");
sql("select * from ^orders^ where productId > 10")
.fails(cannotConvertToRelation("ORDERS"));
}
@Test void testStreamGroupBy() {
sql("select stream rowtime, productId, count(*) as c\n"
+ "from orders\n"
+ "group by productId, rowtime").ok();
sql("select stream floor(rowtime to hour) as rowtime, productId,\n"
+ " count(*) as c\n"
+ "from orders\n"
+ "group by floor(rowtime to hour), productId").ok();
sql("select stream productId, count(*) as c\n"
+ "from orders\n"
+ "^group by productId^")
.fails(STR_AGG_REQUIRES_MONO);
sql("select stream ^count(*)^ as c\n"
+ "from orders")
.fails(STR_AGG_REQUIRES_MONO);
sql("select stream count(*) as c\n"
+ "from orders ^group by ()^")
.fails(STR_AGG_REQUIRES_MONO);
}
@Test void testStreamHaving() {
sql("select stream rowtime, productId, count(*) as c\n"
+ "from orders\n"
+ "group by productId, rowtime\n"
+ "having count(*) > 5").ok();
sql("select stream floor(rowtime to hour) as rowtime, productId,\n"
+ " count(*) as c\n"
+ "from orders\n"
+ "group by floor(rowtime to hour), productId\n"
+ "having false").ok();
sql("select stream productId, count(*) as c\n"
+ "from orders\n"
+ "^group by productId^\n"
+ "having count(*) > 5")
.fails(STR_AGG_REQUIRES_MONO);
sql("select stream 1\n"
+ "from orders\n"
+ "having ^count(*) > 3^")
.fails(STR_AGG_REQUIRES_MONO);
}
/** Tests that various expressions are monotonic. */
@Test void testMonotonic() {
sql("select stream floor(rowtime to hour) from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream ceil(rowtime to minute) from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream extract(minute from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.NOT_MONOTONIC));
sql("select stream (rowtime - timestamp '1970-01-01 00:00:00') hour from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream\n"
+ "cast((rowtime - timestamp '1970-01-01 00:00:00') hour as integer)\n"
+ "from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream\n"
+ "cast((rowtime - timestamp '1970-01-01 00:00:00') hour as integer) / 15\n"
+ "from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream\n"
+ "mod(cast((rowtime - timestamp '1970-01-01 00:00:00') hour as integer), 15)\n"
+ "from orders")
.assertMonotonicity(is(SqlMonotonicity.NOT_MONOTONIC));
// constant
sql("select stream 1 - 2 from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
sql("select stream 1 + 2 from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
// extract(YEAR) is monotonic, extract(other time unit) is not
sql("select stream extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream extract(month from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.NOT_MONOTONIC));
// <monotonic> - constant
sql("select stream extract(year from rowtime) - 3 from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream extract(year from rowtime) * 5 from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream extract(year from rowtime) * -5 from orders")
.assertMonotonicity(is(SqlMonotonicity.DECREASING));
// <monotonic> / constant
sql("select stream extract(year from rowtime) / -5 from orders")
.assertMonotonicity(is(SqlMonotonicity.DECREASING));
sql("select stream extract(year from rowtime) / 5 from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream extract(year from rowtime) / 0 from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT)); // +inf is constant!
sql("select stream extract(year from rowtime) / null from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
sql("select stream null / extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
sql("select stream extract(year from rowtime) / cast(null as integer) from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
sql("select stream cast(null as integer) / extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
// constant / <monotonic> is not monotonic (we don't know whether sign of
// expression ever changes)
sql("select stream 5 / extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.NOT_MONOTONIC));
// <monotonic> * constant
sql("select stream extract(year from rowtime) * -5 from orders")
.assertMonotonicity(is(SqlMonotonicity.DECREASING));
sql("select stream extract(year from rowtime) * 5 from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream extract(year from rowtime) * 0 from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT)); // 0 is constant!
// constant * <monotonic>
sql("select stream -5 * extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.DECREASING));
sql("select stream 5 * extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
sql("select stream 0 * extract(year from rowtime) from orders")
.assertMonotonicity(is(SqlMonotonicity.CONSTANT));
// <monotonic> - <monotonic>
sql("select stream\n"
+ "extract(year from rowtime) - extract(year from rowtime)\n"
+ "from orders")
.assertMonotonicity(is(SqlMonotonicity.NOT_MONOTONIC));
// <monotonic> + <monotonic>
sql("select stream\n"
+ "extract(year from rowtime) + extract(year from rowtime)\n"
+ "from orders")
.assertMonotonicity(is(SqlMonotonicity.INCREASING));
}
@Test void testStreamUnionAll() {
sql("select orderId\n"
+ "from ^orders^\n"
+ "union all\n"
+ "select orderId\n"
+ "from shipments")
.fails(cannotConvertToRelation("ORDERS"));
sql("select stream orderId\n"
+ "from orders\n"
+ "union all\n"
+ "^select orderId\n"
+ "from shipments^")
.fails(STR_SET_OP_INCONSISTENT);
sql("select empno\n"
+ "from emp\n"
+ "union all\n"
+ "^select stream orderId\n"
+ "from orders^")
.fails(STR_SET_OP_INCONSISTENT);
sql("select stream orderId\n"
+ "from orders\n"
+ "union all\n"
+ "select stream orderId\n"
+ "from shipments").ok();
sql("select stream rowtime, orderId\n"
+ "from orders\n"
+ "union all\n"
+ "select stream rowtime, orderId\n"
+ "from shipments\n"
+ "order by rowtime").ok();
}
@Test void testStreamValues() {
sql("select stream * from (^values 1) as e^")
.fails(cannotConvertToStream("E"));
sql("select stream * from (^values 1) as e (c)^")
.fails(cannotConvertToStream("E"));
sql("select stream orderId from orders\n"
+ "union all\n"
+ "^values 1^")
.fails(STR_SET_OP_INCONSISTENT);
sql("values 1, 2\n"
+ "union all\n"
+ "^select stream orderId from orders^\n")
.fails(STR_SET_OP_INCONSISTENT);
}
@Test void testStreamOrderBy() {
sql("select stream *\n"
+ "from orders\n"
+ "order by rowtime").ok();
sql("select stream *\n"
+ "from orders\n"
+ "order by floor(rowtime to hour)").ok();
sql("select stream floor(rowtime to minute), productId\n"
+ "from orders\n"
+ "order by floor(rowtime to hour)").ok();
sql("select stream floor(rowtime to minute), productId\n"
+ "from orders\n"
+ "order by floor(rowtime to minute), productId desc").ok();
sql("select stream *\n"
+ "from orders\n"
+ "order by ^productId^, rowtime")
.fails(STR_ORDER_REQUIRES_MONO);
sql("select stream *\n"
+ "from orders\n"
+ "order by ^rowtime desc^")
.fails(STR_ORDER_REQUIRES_MONO);
sql("select stream *\n"
+ "from orders\n"
+ "order by floor(rowtime to hour), rowtime desc").ok();
}
@Test void testStreamJoin() {
sql("select stream\n"
+ " orders.rowtime as rowtime, orders.orderId as orderId,\n"
+ " products.supplierId as supplierId\n"
+ "from orders\n"
+ "join products on orders.productId = products.productId").ok();
sql("^select stream *\n"
+ "from products\n"
+ "join suppliers on products.supplierId = suppliers.supplierId^")
.fails(cannotStreamResultsForNonStreamingInputs("PRODUCTS, SUPPLIERS"));
}
@Test void testDummy() {
// (To debug individual statements, paste them into this method.)
expr("true\n"
+ "or ^(date '1-2-3', date '1-2-3', date '1-2-3')\n"
+ " overlaps (date '1-2-3', date '1-2-3')^\n"
+ "or false")
.fails("(?s).*Cannot apply 'OVERLAPS' to arguments of type .*");
}
@Test void testCustomColumnResolving() {
checkCustomColumnResolving("T");
}
@Test void testCustomColumnResolvingWithView() {
checkCustomColumnResolving("T_10");
}
private void checkCustomColumnResolving(String table) {
// Table STRUCT.T is defined as: (
// K0 VARCHAR(20) NOT NULL,
// C1 VARCHAR(20) NOT NULL,
// RecordType:PEEK_FIELDS_DEFAULT(
// C0 INTEGER NOT NULL,
// C1 INTEGER NOT NULL) F0,
// RecordType:PEEK_FIELDS(
// C0 INTEGER,
// C2 INTEGER NOT NULL,
// A0 INTEGER NOT NULL) F1,
// RecordType:PEEK_FIELDS(
// C3 INTEGER NOT NULL,
// A0 BOOLEAN NOT NULL) F2)
//
// The labels 'PEEK_FIELDS_DEFAULT' and 'PEEK_FIELDS' mean that F0, F1 and
// F2 can all be transparent. F0 has default struct priority; F1 and F2 have
// lower priority.
sql("select * from struct." + table).ok();
// Resolve K0 as top-level column K0.
sql("select k0 from struct." + table)
.type("RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
// Resolve C2 as secondary-level column F1.C2.
sql("select c2 from struct." + table)
.type("RecordType(INTEGER NOT NULL C2) NOT NULL");
// Resolve F1.C2 as fully qualified column F1.C2.
sql("select f1.c2 from struct." + table)
.type("RecordType(INTEGER NOT NULL C2) NOT NULL");
// Resolve C1 as top-level column C1 as opposed to F0.C1.
sql("select c1 from struct." + table)
.type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
// Resolve C0 as secondary-level column F0.C0 as opposed to F1.C0, since F0
// has the default priority.
sql("select c0 from struct." + table)
.type("RecordType(INTEGER NOT NULL C0) NOT NULL");
// Resolve F1.C0 as fully qualified column F1.C0 (as evidenced by "INTEGER"
// rather than "INTEGER NOT NULL")
sql("select f1.c0 from struct." + table)
.type("RecordType(INTEGER C0) NOT NULL");
// Fail ambiguous column reference A0, since F1.A0 and F2.A0 both exist with
// the same resolving priority.
sql("select ^a0^ from struct." + table)
.fails("Column 'A0' is ambiguous");
// Resolve F2.A0 as fully qualified column F2.A0.
sql("select f2.a0 from struct." + table)
.type("RecordType(BOOLEAN NOT NULL A0) NOT NULL");
// Resolve T0.K0 as top-level column K0, since T0 is recognized as the table
// alias.
sql("select t0.k0 from struct." + table + " t0")
.type("RecordType(VARCHAR(20) NOT NULL K0) NOT NULL");
// Resolve T0.C2 as secondary-level column F1.C2, since T0 is recognized as
// the table alias here.
sql("select t0.c2 from struct." + table + " t0")
.type("RecordType(INTEGER NOT NULL C2) NOT NULL");
// Resolve F0.C2 as secondary-level column F1.C2, since F0 is recognized as
// the table alias here.
sql("select f0.c2 from struct." + table + " f0")
.type("RecordType(INTEGER NOT NULL C2) NOT NULL");
// Resolve F0.C1 as top-level column C1 as opposed to F0.C1, since F0 is
// recognized as the table alias here.
sql("select f0.c1 from struct." + table + " f0")
.type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
// Resolve C1 as inner INTEGER column not top-level VARCHAR column.
sql("select f0.f0.c1 from struct." + table + " f0")
.type("RecordType(INTEGER NOT NULL C1) NOT NULL");
// Resolve <table>.C1 as top-level column C1 as opposed to F0.C1, since <table> is
// recognized as the table name.
sql("select " + table + ".c1 from struct." + table)
.type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
// Alias "f0" obscures table name "<table>"
sql("select ^" + table + "^.c1 from struct." + table + " f0")
.fails("Table '" + table + "' not found");
// Resolve STRUCT.<table>.C1 as top-level column C1 as opposed to F0.C1, since
// STRUCT.<table> is recognized as the schema and table name.
sql("select struct." + table + ".c1 from struct." + table)
.type("RecordType(VARCHAR(20) NOT NULL C1) NOT NULL");
// Table alias "f0" obscures table name "STRUCT.<table>"
sql("select ^struct." + table + "^.c1 from struct." + table + " f0")
.fails("Table 'STRUCT." + table + "' not found");
// Resolve F0.F0.C1 as secondary-level column F0.C1, since the first F0 is
// recognized as the table alias here.
sql("select f0.f0.c1 from struct." + table + " f0")
.type("RecordType(INTEGER NOT NULL C1) NOT NULL");
// Resolve <table>.F0.C1 as secondary-level column F0.C1, since <table> is
// recognized as the table name.
sql("select " + table + ".f0.c1 from struct." + table)
.type("RecordType(INTEGER NOT NULL C1) NOT NULL");
// Table alias obscures
sql("select ^" + table + ".f0^.c1 from struct." + table + " f0")
.fails("Table '" + table + ".F0' not found");
// Resolve STRUCT.<table>.F0.C1 as secondary-level column F0.C1, since
// STRUCT.<table> is recognized as the schema and table name.
sql("select struct." + table + ".f0.c1 from struct." + table)
.type("RecordType(INTEGER NOT NULL C1) NOT NULL");
// Table alias "f0" obscures table name "STRUCT.<table>"
sql("select ^struct." + table + ".f0^.c1 from struct." + table + " f0")
.fails("Table 'STRUCT." + table + ".F0' not found");
// Resolve struct type F1 with wildcard.
sql("select f1.* from struct." + table)
.type("RecordType(INTEGER NOT NULL A0, INTEGER C0,"
+ " INTEGER NOT NULL C2) NOT NULL");
// Resolve struct type F1 with wildcard.
sql("select " + table + ".f1.* from struct." + table)
.type("RecordType(INTEGER NOT NULL A0, INTEGER C0,"
+ " INTEGER NOT NULL C2) NOT NULL");
// Fail non-existent column B0.
sql("select ^b0^ from struct." + table)
.fails("Column 'B0' not found in any table");
// A column family can only be referenced with a star expansion.
sql("select ^f1^ from struct." + table)
.fails("Column 'F1' not found in any table");
// If we fail to find a column, give an error based on the shortest prefix
// that fails.
sql("select " + table + ".^f0.notFound^.a.b.c.d from struct." + table)
.fails("Column 'F0\\.NOTFOUND' not found in table '" + table + "'");
sql("select " + table + ".^f0.notFound^ from struct." + table)
.fails("Column 'F0\\.NOTFOUND' not found in table '" + table + "'");
sql("select " + table + ".^f0.c1.notFound^ from struct." + table)
.fails("Column 'F0\\.C1\\.NOTFOUND' not found in table '" + table + "'");
}
@Test void testDescriptor() {
sql("select * from table(tumble(table orders, descriptor(rowtime), interval '2' hour))").ok();
sql("select * from table(tumble(table orders, descriptor(^column_not_exist^), "
+ "interval '2' hour))")
.fails("Unknown identifier 'COLUMN_NOT_EXIST'");
}
@Test void testTumbleTableFunction() {
sql("select rowtime, productid, orderid, 'window_start', 'window_end' from table(\n"
+ "tumble(table orders, descriptor(rowtime), interval '2' hour))").ok();
sql("select rowtime, productid, orderid, 'window_start', 'window_end' from table(\n"
+ "tumble(table orders, descriptor(rowtime), interval '2' hour, interval '1' hour))").ok();
// test named params.
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "tumble(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "size => interval '2' hour))").ok();
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "tumble(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "size => interval '2' hour,\n"
+ "\"OFFSET\" => interval '1' hour))").ok();
// negative tests.
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "^tumble(table orders, descriptor(productid), interval '2' hour)^)")
.fails("Cannot apply 'TUMBLE' to arguments of type 'TUMBLE\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, "
+ "<INTERVAL HOUR>\\)'\\. Supported form\\(s\\): TUMBLE\\(TABLE table_name, "
+ "DESCRIPTOR\\(timecol\\), datetime interval\\[, datetime interval\\]\\)");
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "^tumble(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(productid),\n"
+ "size => interval '2' hour)^)")
.fails("Cannot apply 'TUMBLE' to arguments of type 'TUMBLE\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, "
+ "<INTERVAL HOUR>\\)'\\. Supported form\\(s\\): TUMBLE\\(TABLE table_name, "
+ "DESCRIPTOR\\(timecol\\), datetime interval\\[, datetime interval\\]\\)");
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "tumble(\n"
+ "^\"data\"^ => table orders,\n"
+ "TIMECOL => descriptor(rowtime),\n"
+ "SIZE => interval '2' hour))")
.fails("Param 'data' not found in function 'TUMBLE'; did you mean 'DATA'\\?");
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "^tumble(\n"
+ "data => table orders,\n"
+ "SIZE => interval '2' hour)^)")
.fails("Invalid number of arguments to function 'TUMBLE'. Was expecting 3 arguments");
sql("select * from table(\n"
+ "^tumble(table orders, descriptor(rowtime))^)")
.fails("Invalid number of arguments to function 'TUMBLE'. Was expecting 3 arguments");
sql("select * from table(\n"
+ "^tumble(table orders, descriptor(rowtime), 'test')^)")
.fails("Cannot apply 'TUMBLE' to arguments of type 'TUMBLE\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>,"
+ " <CHAR\\(4\\)>\\)'\\. Supported form\\(s\\): TUMBLE\\(TABLE "
+ "table_name, DESCRIPTOR\\(timecol\\), datetime interval"
+ "\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "^tumble(table orders, 'test', interval '2' hour)^)")
.fails("Cannot apply 'TUMBLE' to arguments of type 'TUMBLE\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <CHAR\\"
+ "(4\\)>, <INTERVAL HOUR>\\)'\\. Supported form\\(s\\): TUMBLE\\(TABLE "
+ "table_name, DESCRIPTOR\\(timecol\\), datetime interval"
+ "\\[, datetime interval\\]\\)");
sql("select rowtime, productid, orderid, 'window_start', 'window_end' from table(\n"
+ "^tumble(table orders, descriptor(rowtime), interval '2' hour, 'test')^)")
.fails("Cannot apply 'TUMBLE' to arguments of type 'TUMBLE\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>,"
+ " <INTERVAL HOUR>, <CHAR\\(4\\)>\\)'\\. Supported form\\(s\\): TUMBLE\\(TABLE "
+ "table_name, DESCRIPTOR\\(timecol\\), datetime interval"
+ "\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "tumble(TABLE ^tabler_not_exist^, descriptor(rowtime), interval '2' hour))")
.fails("Object 'TABLER_NOT_EXIST' not found");
}
@Test void testHopTableFunction() {
sql("select * from table(\n"
+ "hop(table orders, descriptor(rowtime), interval '2' hour, interval '1' hour))").ok();
sql("select * from table(\n"
+ "hop(table orders, descriptor(rowtime), interval '2' hour, interval '1' hour, "
+ "interval '20' minute))").ok();
// test named params.
sql("select * from table(\n"
+ "hop(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "slide => interval '2' hour,\n"
+ "size => interval '1' hour))").ok();
sql("select * from table(\n"
+ "hop(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "slide => interval '2' hour,\n"
+ "size => interval '1' hour,\n"
+ "\"OFFSET\" => interval '20' minute))").ok();
// negative tests.
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "^hop(table orders, descriptor(productid), interval '2' hour, interval '1' hour)^)")
.fails("Cannot apply 'HOP' to arguments of type 'HOP\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, "
+ "<INTERVAL HOUR>, <INTERVAL HOUR>\\)'\\. Supported form\\(s\\): "
+ "HOP\\(TABLE table_name, DESCRIPTOR\\(timecol\\), "
+ "datetime interval, datetime interval\\[, datetime interval\\]\\)");
sql("select rowtime, productid, orderid, 'window_start', 'window_end'\n"
+ "from table(\n"
+ "^hop(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(productid),\n"
+ "size => interval '2' hour,\n"
+ "slide => interval '1' hour)^)")
.fails("Cannot apply 'HOP' to arguments of type 'HOP\\(<RECORDTYPE\\"
+ "(TIMESTAMP\\(0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, "
+ "<INTERVAL HOUR>, <INTERVAL HOUR>\\)'\\. Supported form\\(s\\): "
+ "HOP\\(TABLE table_name, DESCRIPTOR\\(timecol\\), "
+ "datetime interval, datetime interval\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "hop(\n"
+ "^\"data\"^ => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "slide => interval '2' hour,\n"
+ "size => interval '1' hour))")
.fails("Param 'data' not found in function 'HOP'; did you mean 'DATA'\\?");
sql("select * from table(\n"
+ "^hop(\n"
+ "data => table orders,\n"
+ "slide => interval '2' hour,\n"
+ "size => interval '1' hour)^)")
.fails("Invalid number of arguments to function 'HOP'. Was expecting 4 arguments");
sql("select * from table(\n"
+ "^hop(table orders, descriptor(rowtime), interval '2' hour)^)")
.fails("Invalid number of arguments to function 'HOP'. Was expecting 4 arguments");
sql("select * from table(\n"
+ "^hop(table orders, descriptor(rowtime), interval '2' hour, 'test')^)")
.fails("Cannot apply 'HOP' to arguments of type 'HOP\\(<RECORDTYPE\\(TIMESTAMP\\(0\\) "
+ "ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, <INTERVAL HOUR>, "
+ "<CHAR\\(4\\)>\\)'. Supported form\\(s\\): HOP\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), datetime interval, datetime interval\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "^hop(table orders, descriptor(rowtime), 'test', interval '2' hour)^)")
.fails("Cannot apply 'HOP' to arguments of type 'HOP\\(<RECORDTYPE\\(TIMESTAMP\\(0\\) "
+ "ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, <CHAR\\(4\\)>, "
+ "<INTERVAL HOUR>\\)'. Supported form\\(s\\): HOP\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), datetime interval, datetime interval\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "^hop(table orders, 'test', interval '2' hour, interval '2' hour)^)")
.fails("Cannot apply 'HOP' to arguments of type 'HOP\\(<RECORDTYPE\\(TIMESTAMP\\(0\\) "
+ "ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <CHAR\\(4\\)>, <INTERVAL HOUR>, "
+ "<INTERVAL HOUR>\\)'. Supported form\\(s\\): HOP\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), datetime interval, datetime interval\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "^hop(table orders, descriptor(rowtime), interval '2' hour, interval '1' hour, 'test')^)")
.fails("Cannot apply 'HOP' to arguments of type 'HOP\\(<RECORDTYPE\\(TIMESTAMP\\(0\\) "
+ "ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, <INTERVAL HOUR>, "
+ "<INTERVAL HOUR>, <CHAR\\(4\\)>\\)'. Supported form\\(s\\): HOP\\(TABLE table_name, "
+ "DESCRIPTOR\\(timecol\\), datetime interval, datetime interval\\[, datetime interval\\]\\)");
sql("select * from table(\n"
+ "hop(TABLE ^tabler_not_exist^, descriptor(rowtime), interval '2' hour, interval '1' hour))")
.fails("Object 'TABLER_NOT_EXIST' not found");
}
@Test void testSessionTableFunction() {
sql("select * from table(\n"
+ "session(table orders, descriptor(rowtime), descriptor(productid), interval '1' hour))")
.ok();
// test without key descriptor
sql("select * from table(\n"
+ "session(table orders, descriptor(rowtime), interval '2' hour))").ok();
// test named params.
sql("select * from table(\n"
+ "session(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "key => descriptor(productid),\n"
+ "size => interval '1' hour))")
.ok();
sql("select * from table(\n"
+ "session(\n"
+ "data => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "size => interval '1' hour))")
.ok();
// negative tests.
sql("select * from table(\n"
+ "^session(\n"
+ "data => table orders,\n"
+ "key => descriptor(productid),\n"
+ "size => interval '1' hour)^)")
.fails("Cannot apply 'SESSION' to arguments of type 'SESSION\\(<RECORDTYPE\\(TIMESTAMP\\("
+ "0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, "
+ "<INTERVAL HOUR>\\)'. Supported form\\(s\\): SESSION\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), DESCRIPTOR\\(key\\) optional, datetime interval\\)");
sql("select * from table(\n"
+ "session(\n"
+ "^\"data\"^ => table orders,\n"
+ "timecol => descriptor(rowtime),\n"
+ "key => descriptor(productid),\n"
+ "size => interval '1' hour))")
.fails("Param 'data' not found in function 'SESSION'; did you mean 'DATA'\\?");
sql("select * from table(\n"
+ "^session(table orders, descriptor(rowtime), descriptor(productid), 'test')^)")
.fails("Cannot apply 'SESSION' to arguments of type 'SESSION\\(<RECORDTYPE\\(TIMESTAMP\\("
+ "0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, <COLUMN_LIST>, "
+ "<CHAR\\(4\\)>\\)'. Supported form\\(s\\): SESSION\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), DESCRIPTOR\\(key\\) optional, datetime interval\\)");
sql("select * from table(\n"
+ "^session(table orders, descriptor(rowtime), 'test', interval '2' hour)^)")
.fails("Cannot apply 'SESSION' to arguments of type 'SESSION\\(<RECORDTYPE\\(TIMESTAMP\\("
+ "0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <COLUMN_LIST>, <CHAR\\(4\\)>, "
+ "<INTERVAL HOUR>\\)'. Supported form\\(s\\): SESSION\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), DESCRIPTOR\\(key\\) optional, datetime interval\\)");
sql("select * from table(\n"
+ "^session(table orders, 'test', descriptor(productid), interval '2' hour)^)")
.fails("Cannot apply 'SESSION' to arguments of type 'SESSION\\(<RECORDTYPE\\(TIMESTAMP\\("
+ "0\\) ROWTIME, INTEGER PRODUCTID, INTEGER ORDERID\\)>, <CHAR\\(4\\)>, <COLUMN_LIST>, "
+ "<INTERVAL HOUR>\\)'. Supported form\\(s\\): SESSION\\(TABLE table_name, DESCRIPTOR\\("
+ "timecol\\), DESCRIPTOR\\(key\\) optional, datetime interval\\)");
sql("select * from table(\n"
+ "session(TABLE ^tabler_not_exist^, descriptor(rowtime), descriptor(productid), interval '1' hour))")
.fails("Object 'TABLER_NOT_EXIST' not found");
}
@Test void testStreamTumble() {
// TUMBLE
sql("select stream tumble_end(rowtime, interval '2' hour) as rowtime\n"
+ "from orders\n"
+ "group by tumble(rowtime, interval '2' hour), productId").ok();
sql("select stream ^tumble(rowtime, interval '2' hour)^ as rowtime\n"
+ "from orders\n"
+ "group by tumble(rowtime, interval '2' hour), productId")
.fails("Group function '\\$TUMBLE' can only appear in GROUP BY clause");
// TUMBLE with align argument
sql("select stream\n"
+ " tumble_end(rowtime, interval '2' hour, time '00:12:00') as rowtime\n"
+ "from orders\n"
+ "group by tumble(rowtime, interval '2' hour, time '00:12:00')").ok();
// TUMBLE_END without corresponding TUMBLE
sql("select stream\n"
+ " ^tumble_end(rowtime, interval '2' hour, time '00:13:00')^ as rowtime\n"
+ "from orders\n"
+ "group by floor(rowtime to hour)")
.fails("Call to auxiliary group function 'TUMBLE_END' must have "
+ "matching call to group function '\\$TUMBLE' in GROUP BY clause");
// Arguments to TUMBLE_END are slightly different to arguments to TUMBLE
sql("select stream\n"
+ " ^tumble_start(rowtime, interval '2' hour, time '00:13:00')^ as rowtime\n"
+ "from orders\n"
+ "group by tumble(rowtime, interval '2' hour, time '00:12:00')")
.fails("Call to auxiliary group function 'TUMBLE_START' must have "
+ "matching call to group function '\\$TUMBLE' in GROUP BY clause");
// Even though align defaults to TIME '00:00:00', we need structural
// equivalence, not semantic equivalence.
sql("select stream\n"
+ " ^tumble_end(rowtime, interval '2' hour, time '00:00:00')^ as rowtime\n"
+ "from orders\n"
+ "group by tumble(rowtime, interval '2' hour)")
.fails("Call to auxiliary group function 'TUMBLE_END' must have "
+ "matching call to group function '\\$TUMBLE' in GROUP BY clause");
// TUMBLE query produces no monotonic column - OK
sql("select stream productId\n"
+ "from orders\n"
+ "group by tumble(rowtime, interval '2' hour), productId").ok();
sql("select stream productId\n"
+ "from orders\n"
+ "^group by productId,\n"
+ " tumble(timestamp '1990-03-04 12:34:56', interval '2' hour)^")
.fails(STR_AGG_REQUIRES_MONO);
}
@Test void testStreamHop() {
// HOP
sql("select stream\n"
+ " hop_start(rowtime, interval '1' hour, interval '3' hour) as rowtime,\n"
+ " count(*) as c\n"
+ "from orders\n"
+ "group by hop(rowtime, interval '1' hour, interval '3' hour)").ok();
sql("select stream\n"
+ " ^hop_start(rowtime, interval '1' hour, interval '2' hour)^,\n"
+ " count(*) as c\n"
+ "from orders\n"
+ "group by hop(rowtime, interval '1' hour, interval '3' hour)")
.fails("Call to auxiliary group function 'HOP_START' must have "
+ "matching call to group function '\\$HOP' in GROUP BY clause");
// HOP with align
sql("select stream\n"
+ " hop_start(rowtime, interval '1' hour, interval '3' hour,\n"
+ " time '12:34:56') as rowtime,\n"
+ " count(*) as c\n"
+ "from orders\n"
+ "group by hop(rowtime, interval '1' hour, interval '3' hour,\n"
+ " time '12:34:56')").ok();
}
@Test void testStreamSession() {
// SESSION
sql("select stream session_start(rowtime, interval '1' hour) as rowtime,\n"
+ " session_end(rowtime, interval '1' hour),\n"
+ " count(*) as c\n"
+ "from orders\n"
+ "group by session(rowtime, interval '1' hour)").ok();
}
@Test void testInsertExtendedColumn() {
sql("insert into empdefaults(extra BOOLEAN, note VARCHAR)"
+ " (deptno, empno, ename, extra, note) values (1, 10, '2', true, 'ok')").ok();
sql("insert into emp(\"rank\" INT, extra BOOLEAN)"
+ " values (1, 'nom', 'job', 0, timestamp '1970-01-01 00:00:00', 1, 1,"
+ " 1, false, 100, false)").ok();
}
@Test void testInsertBindExtendedColumn() {
sql("insert into empdefaults(extra BOOLEAN, note VARCHAR)"
+ " (deptno, empno, ename, extra, note) values (1, 10, '2', ?, 'ok')").ok();
sql("insert into emp(\"rank\" INT, extra BOOLEAN)"
+ " values (1, 'nom', 'job', 0, timestamp '1970-01-01 00:00:00', 1, 1,"
+ " 1, false, ?, ?)").ok();
}
@Test void testInsertExtendedColumnModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW2(extra2 BOOLEAN,"
+ " note VARCHAR) (deptno, empno, ename, extra2, note)\n"
+ "values (20, 10, '2', true, 'ok')";
s.withSql(sql0).ok();
final String sql1 = "insert into EMP_MODIFIABLEVIEW2(\"rank\" INT,"
+ " extra2 BOOLEAN)\n"
+ "values ('nom', 1, 'job', 20, true, 0, false,"
+ " timestamp '1970-01-01 00:00:00', 1, 1, 1, false)";
s.withSql(sql1).ok();
}
@Test void testInsertBindExtendedColumnModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("insert into EMP_MODIFIABLEVIEW2(extra2 BOOLEAN, note VARCHAR)"
+ " (deptno, empno, ename, extra2, note) values (20, 10, '2', true, ?)").ok();
s.withSql("insert into EMP_MODIFIABLEVIEW2(\"rank\" INT, extra2 BOOLEAN)"
+ " values ('nom', 1, 'job', 20, true, 0, false, timestamp '1970-01-01 00:00:00', 1, 1,"
+ " ?, false)").ok();
}
@Test void testInsertExtendedColumnModifiableViewFailConstraint() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW2(extra2 BOOLEAN,"
+ " note VARCHAR) (deptno, empno, ename, extra2, note)\n"
+ "values (^1^, 10, '2', true, 'ok')";
final String error = "Modifiable view constraint is not satisfied"
+ " for column 'DEPTNO' of base table 'EMP_MODIFIABLEVIEW2'";
s.withSql(sql0).fails(error);
final String sql1 = "insert into EMP_MODIFIABLEVIEW2(extra2 BOOLEAN,"
+ " note VARCHAR) (deptno, empno, ename, extra2, note)\n"
+ "values (^?^, 10, '2', true, 'ok')";
s.withSql(sql1).fails(error);
final String sql2 = "insert into EMP_MODIFIABLEVIEW2(\"rank\" INT,"
+ " extra2 BOOLEAN)\n"
+ "values ('nom', 1, 'job', ^0^, true, 0, false,"
+ " timestamp '1970-01-01 00:00:00', 1, 1, 1, false)";
s.withSql(sql2).fails(error);
}
@Test void testInsertExtendedColumnModifiableViewFailColumnCount() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into ^EMP_MODIFIABLEVIEW2(\"rank\" INT, extra2 BOOLEAN)^"
+ " values ('nom', 1, 'job', 0, true, 0, false,"
+ " timestamp '1970-01-01 00:00:00', 1, 1, 1)";
final String error0 = "Number of INSERT target columns \\(12\\) does not"
+ " equal number of source items \\(11\\)";
s.withSql(sql0).fails(error0);
final String sql1 = "insert into ^EMP_MODIFIABLEVIEW2(\"rank\" INT, extra2 BOOLEAN)^"
+ " (deptno, empno, ename, extra2, \"rank\") values (?, 10, '2', true)";
final String error1 = "Number of INSERT target columns \\(5\\) does not"
+ " equal number of source items \\(4\\)";
s.withSql(sql1).fails(error1);
}
@Test void testInsertExtendedColumnFailDuplicate() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW2(extcol INT,"
+ " ^extcol^ BOOLEAN)\n"
+ "values ('nom', 1, 'job', 0, true, 0, false,"
+ " timestamp '1970-01-01 00:00:00', 1, 1, 1)";
final String error = "Duplicate name 'EXTCOL' in column list";
s.withSql(sql0).fails(error);
final String sql1 = "insert into EMP_MODIFIABLEVIEW2(extcol INT,"
+ " ^extcol^ BOOLEAN) (extcol) values (1)";
s.withSql(sql1).fails(error);
final String sql2 = "insert into EMP_MODIFIABLEVIEW2(extcol INT,"
+ " ^extcol^ BOOLEAN) (extcol) values (false)";
s.withSql(sql2).fails(error);
final String sql3 = "insert into EMP(extcol INT, ^extcol^ BOOLEAN)"
+ " (extcol) values (1)";
s.withSql(sql3).fails(error);
final String sql4 = "insert into EMP(extcol INT, ^extcol^ BOOLEAN)"
+ " (extcol) values (false)";
s.withSql(sql4).fails(error);
}
@Test void testUpdateExtendedColumn() {
sql("update empdefaults(extra BOOLEAN, note VARCHAR)"
+ " set deptno = 1, extra = true, empno = 20, ename = 'Bob', note = 'legion'"
+ " where deptno = 10").ok();
sql("update empdefaults(extra BOOLEAN)"
+ " set extra = true, deptno = 1, ename = 'Bob'"
+ " where deptno = 10").ok();
sql("update empdefaults(\"empNo\" VARCHAR)"
+ " set \"empNo\" = '5', deptno = 1, ename = 'Bob'"
+ " where deptno = 10").ok();
}
@Test void testInsertFailDataType() {
sql("insert into empnullables ^values ('5', 'bob')^")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.withTypeCoercion(false)
.fails("Cannot assign to target field 'EMPNO' of type INTEGER"
+ " from source field 'EXPR\\$0' of type CHAR\\(1\\)");
sql("insert into empnullables values ('5', 'bob')")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.ok();
sql("insert into empnullables (^empno^, ename) values ('5', 'bob')")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.withTypeCoercion(false)
.fails("Cannot assign to target field 'EMPNO' of type INTEGER"
+ " from source field 'EXPR\\$0' of type CHAR\\(1\\)");
sql("insert into empnullables (empno, ename) values ('5', 'bob')")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.ok();
sql("insert into empnullables(extra BOOLEAN)"
+ " (empno, ename, ^extra^) values (5, 'bob', 'true')")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.withTypeCoercion(false)
.fails("Cannot assign to target field 'EXTRA' of type BOOLEAN"
+ " from source field 'EXPR\\$2' of type CHAR\\(4\\)");
sql("insert into empnullables(extra BOOLEAN)"
+ " (empno, ename, ^extra^) values (5, 'bob', 'true')")
.withConformance(SqlConformanceEnum.PRAGMATIC_2003)
.ok();
}
@Disabled("CALCITE-1727")
@Test void testUpdateFailDataType() {
sql("update emp"
+ " set ^empNo^ = '5', deptno = 1, ename = 'Bob'"
+ " where deptno = 10")
.fails("Cannot assign to target field 'EMPNO' of type INTEGER"
+ " from source field 'EXPR$0' of type CHAR(1)");
sql("update emp(extra boolean)"
+ " set ^extra^ = '5', deptno = 1, ename = 'Bob'"
+ " where deptno = 10")
.fails("Cannot assign to target field 'EXTRA' of type BOOLEAN"
+ " from source field 'EXPR$0' of type CHAR(1)");
}
@Disabled("CALCITE-1727")
@Test void testUpdateFailCaseSensitivity() {
sql("update empdefaults"
+ " set empNo = '5', deptno = 1, ename = 'Bob'"
+ " where deptno = 10")
.fails("Column 'empno' not found in any table; did you mean 'EMPNO'\\?");
}
@Test void testUpdateExtendedColumnFailCaseSensitivity() {
sql("update empdefaults(\"extra\" BOOLEAN)"
+ " set ^extra^ = true, deptno = 1, ename = 'Bob'"
+ " where deptno = 10")
.fails("Unknown target column 'EXTRA'");
sql("update empdefaults(extra BOOLEAN)"
+ " set ^\"extra\"^ = true, deptno = 1, ename = 'Bob'"
+ " where deptno = 10")
.fails("Unknown target column 'extra'");
}
@Test void testUpdateBindExtendedColumn() {
sql("update empdefaults(extra BOOLEAN, note VARCHAR)"
+ " set deptno = 1, extra = true, empno = 20, ename = 'Bob', note = ?"
+ " where deptno = 10").ok();
sql("update empdefaults(extra BOOLEAN)"
+ " set extra = ?, deptno = 1, ename = 'Bob'"
+ " where deptno = 10").ok();
}
@Test void testUpdateExtendedColumnModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("update EMP_MODIFIABLEVIEW2(extra2 BOOLEAN, note VARCHAR)"
+ " set deptno = 20, extra2 = true, empno = 20, ename = 'Bob', note = 'legion'"
+ " where ename = 'Jane'").ok();
s.withSql("update EMP_MODIFIABLEVIEW2(extra2 BOOLEAN)"
+ " set extra2 = true, ename = 'Bob'"
+ " where ename = 'Jane'").ok();
}
@Test void testUpdateBindExtendedColumnModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("update EMP_MODIFIABLEVIEW2(extra2 BOOLEAN, note VARCHAR)"
+ " set deptno = 20, extra2 = true, empno = 20, ename = 'Bob', note = ?"
+ " where ename = 'Jane'").ok();
s.withSql("update EMP_MODIFIABLEVIEW2(extra2 BOOLEAN)"
+ " set extra2 = ?, ename = 'Bob'"
+ " where ename = 'Jane'").ok();
}
@Test void testUpdateExtendedColumnModifiableViewFailConstraint() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "update EMP_MODIFIABLEVIEW2(extra2 BOOLEAN,"
+ " note VARCHAR)\n"
+ "set deptno = ^1^, extra2 = true, empno = 20, ename = 'Bob',"
+ " note = 'legion'\n"
+ "where ename = 'Jane'";
final String error = "Modifiable view constraint is not satisfied"
+ " for column 'DEPTNO' of base table 'EMP_MODIFIABLEVIEW2'";
s.withSql(sql0).fails(error);
final String sql1 = "update EMP_MODIFIABLEVIEW2(extra2 BOOLEAN)"
+ " set extra2 = true, deptno = ^1^, ename = 'Bob'"
+ " where ename = 'Jane'";
s.withSql(sql1).fails(error);
}
@Test void testUpdateExtendedColumnCollision() {
sql("update empdefaults(empno INTEGER NOT NULL, deptno INTEGER)"
+ " set deptno = 1, empno = 20, ename = 'Bob'"
+ " where deptno = 10").ok();
}
@Test void testUpdateExtendedColumnModifiableViewCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("update EMP_MODIFIABLEVIEW3(empno INTEGER NOT NULL,"
+ " deptno INTEGER)\n"
+ "set deptno = 20, empno = 20, ename = 'Bob'\n"
+ "where empno = 10").ok();
s.withSql("update EMP_MODIFIABLEVIEW3(empno INTEGER NOT NULL,"
+ " \"deptno\" BOOLEAN)\n"
+ "set \"deptno\" = true, empno = 20, ename = 'Bob'\n"
+ "where empno = 10").ok();
}
@Test void testUpdateExtendedColumnFailCollision() {
final String sql = "update empdefaults(^empno^ BOOLEAN, deptno INTEGER)\n"
+ "set deptno = 1, empno = false, ename = 'Bob'\n"
+ "where deptno = 10";
final String expected = "Cannot assign to target field 'EMPNO' of type "
+ "INTEGER NOT NULL from source field 'EMPNO' of type BOOLEAN";
sql(sql).fails(expected);
}
@Disabled("CALCITE-1727")
@Test void testUpdateExtendedColumnFailCollision2() {
final String sql = "update empdefaults(^\"deptno\"^ BOOLEAN)\n"
+ "set \"deptno\" = 1, empno = 1, ename = 'Bob'\n"
+ "where deptno = 10";
final String expected = "Cannot assign to target field 'deptno' of type "
+ "BOOLEAN NOT NULL from source field 'deptno' of type INTEGER";
sql(sql).fails(expected);
}
@Test void testUpdateExtendedColumnModifiableViewFailCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql = "update EMP_MODIFIABLEVIEW3(^empno^ BOOLEAN,"
+ " deptno INTEGER)\n"
+ "set deptno = 1, empno = false, ename = 'Bob'\n"
+ "where deptno = 10";
final String error = "Cannot assign to target field 'EMPNO' of type"
+ " INTEGER NOT NULL from source field 'EMPNO' of type BOOLEAN";
s.withSql(sql).fails(error);
}
@Test void testUpdateExtendedColumnModifiableViewFailExtendedCollision() {
final String error = "Cannot assign to target field 'EXTRA' of type"
+ " BOOLEAN from source field 'EXTRA' of type INTEGER";
final String sql = "update EMP_MODIFIABLEVIEW2(^extra^ INTEGER,"
+ " deptno INTEGER)\n"
+ "set deptno = 20, empno = 20, ename = 'Bob', extra = 5\n"
+ "where empno = 10";
sql(sql).withExtendedCatalog().fails(error);
}
@Test void testUpdateExtendedColumnModifiableViewFailUnderlyingCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql = "update EMP_MODIFIABLEVIEW3(^comm^ BOOLEAN,"
+ " deptno INTEGER)\n"
+ "set deptno = 1, empno = 20, ename = 'Bob', comm = true\n"
+ "where deptno = 10";
final String error = "Cannot assign to target field 'COMM' of type"
+ " INTEGER from source field 'COMM' of type BOOLEAN";
s.withSql(sql).fails(error);
}
@Test void testUpdateExtendedColumnFailDuplicate() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "update emp(comm BOOLEAN, ^comm^ INTEGER)\n"
+ "set deptno = 1, empno = 20, ename = 'Bob', comm = 1\n"
+ "where deptno = 10";
final String error = "Duplicate name 'COMM' in column list";
s.withSql(sql0).fails(error);
final String sql1 = "update EMP_MODIFIABLEVIEW3(comm BOOLEAN,"
+ " ^comm^ INTEGER)\n"
+ "set deptno = 1, empno = 20, ename = 'Bob', comm = true\n"
+ "where deptno = 10";
s.withSql(sql1).fails(error);
}
@Test void testInsertExtendedColumnCollision() {
sql("insert into EMPDEFAULTS(^comm^ INTEGER) (empno, ename, job, comm)\n"
+ "values (1, 'Arthur', 'clown', 5)").ok();
}
@Test void testInsertExtendedColumnModifiableViewCollision() {
final String sql = "insert into EMP_MODIFIABLEVIEW3(^sal^ INTEGER)\n"
+ " (empno, ename, job, sal)\n"
+ "values (1, 'Arthur', 'clown', 5)";
sql(sql).withExtendedCatalog().ok();
}
@Test void testInsertExtendedColumnModifiableViewExtendedCollision() {
final String sql = "insert into EMP_MODIFIABLEVIEW2(^extra^ BOOLEAN)"
+ " (empno, ename, job, extra)\n"
+ "values (1, 'Arthur', 'clown', true)";
sql(sql).withExtendedCatalog().ok();
}
@Test void testInsertExtendedColumnModifiableViewUnderlyingCollision() {
final String sql = "insert into EMP_MODIFIABLEVIEW3(^comm^ INTEGER)\n"
+ " (empno, ename, job, comm)\n"
+ "values (1, 'Arthur', 'clown', 5)";
sql(sql).withExtendedCatalog().ok();
}
@Test void testInsertExtendedColumnFailCollision() {
sql("insert into EMPDEFAULTS(^comm^ BOOLEAN)"
+ " (empno, ename, job, comm)\n"
+ "values (1, 'Arthur', 'clown', true)")
.fails("Cannot assign to target field 'COMM' of type INTEGER"
+ " from source field 'COMM' of type BOOLEAN");
sql("insert into EMPDEFAULTS(\"comm\" BOOLEAN)"
+ " (empno, ename, job, ^comm^)\n"
+ "values (1, 'Arthur', 'clown', true)")
.withTypeCoercion(false)
.fails("Cannot assign to target field 'COMM' of type INTEGER"
+ " from source field 'EXPR\\$3' of type BOOLEAN");
sql("insert into EMPDEFAULTS(\"comm\" BOOLEAN)"
+ " (empno, ename, job, ^comm^)\n"
+ "values (1, 'Arthur', 'clown', true)")
.fails("Cannot assign to target field 'COMM' of type INTEGER"
+ " from source field 'EXPR\\$3' of type BOOLEAN");
sql("insert into EMPDEFAULTS(\"comm\" BOOLEAN)"
+ " (empno, ename, job, comm)\n"
+ "values (1, 'Arthur', 'clown', true)")
.withConformance(SqlConformanceEnum.BIG_QUERY)
.ok();
sql("insert into EMPDEFAULTS(\"comm\" BOOLEAN)"
+ " (empno, ename, job, ^\"comm\"^)\n"
+ "values (1, 'Arthur', 'clown', 1)")
.withTypeCoercion(false)
.fails("Cannot assign to target field 'comm' of type BOOLEAN"
+ " from source field 'EXPR\\$3' of type INTEGER");
sql("insert into EMPDEFAULTS(\"comm\" BOOLEAN)"
+ " (empno, ename, job, \"comm\")\n"
+ "values (1, 'Arthur', 'clown', 1)")
.ok();
}
@Test void testInsertExtendedColumnModifiableViewFailCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW2(^slacker^ INTEGER)"
+ " (empno, ename, job, slacker) values (1, 'Arthur', 'clown', true)";
final String error0 = "Cannot assign to target field 'SLACKER' of type"
+ " BOOLEAN from source field 'SLACKER' of type INTEGER";
s.withSql(sql0).fails(error0);
final String sql1 = "insert into EMP_MODIFIABLEVIEW2(\"slacker\" INTEGER)"
+ " (empno, ename, job, ^slacker^) values (1, 'Arthur', 'clown', 1)";
final String error1 = "Cannot assign to target field 'SLACKER' of type"
+ " BOOLEAN from source field 'EXPR\\$3' of type INTEGER";
s.withSql(sql1).withTypeCoercion(false).fails(error1);
s.withSql(sql1).ok();
final String sql2 = "insert into EMP_MODIFIABLEVIEW2(\"slacker\" INTEGER)"
+ " (empno, ename, job, ^\"slacker\"^)\n"
+ "values (1, 'Arthur', 'clown', true)";
final String error2 = "Cannot assign to target field 'slacker' of type"
+ " INTEGER from source field 'EXPR\\$3' of type BOOLEAN";
s.withSql(sql2).withTypeCoercion(false).fails(error2);
s.withConformance(SqlConformanceEnum.BIG_QUERY).withSql(sql2).ok();
}
@Test void testInsertExtendedColumnModifiableViewFailExtendedCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "insert into EMP_MODIFIABLEVIEW2(^extra^ INTEGER)"
+ " (empno, ename, job, extra) values (1, 'Arthur', 'clown', true)";
final String error0 = "Cannot assign to target field 'EXTRA' of type"
+ " BOOLEAN from source field 'EXTRA' of type INTEGER";
s.withSql(sql0).fails(error0);
final String sql1 = "insert into EMP_MODIFIABLEVIEW2(\"extra\" INTEGER)"
+ " (empno, ename, job, ^extra^) values (1, 'Arthur', 'clown', 1)";
final String error1 = "Cannot assign to target field 'EXTRA' of type"
+ " BOOLEAN from source field 'EXPR\\$3' of type INTEGER";
s.withSql(sql1).withTypeCoercion(false).fails(error1);
final String sql2 = "insert into EMP_MODIFIABLEVIEW2(\"extra\" INTEGER)"
+ " (empno, ename, job, extra) values (1, 'Arthur', 'clown', 1)";
s.withSql(sql2).ok();
final String sql3 = "insert into EMP_MODIFIABLEVIEW2(\"extra\" INTEGER)"
+ " (empno, ename, job, ^\"extra\"^)\n"
+ "values (1, 'Arthur', 'clown', true)";
final String error3 = "Cannot assign to target field 'extra' of type"
+ " INTEGER from source field 'EXPR\\$3' of type BOOLEAN";
s.withSql(sql3).withTypeCoercion(false).fails(error3);
final String sql4 = "insert into EMP_MODIFIABLEVIEW2(\"extra\" INTEGER)"
+ " (empno, ename, job, \"extra\")\n"
+ "values (1, 'Arthur', 'clown', true)";
s.withConformance(SqlConformanceEnum.BIG_QUERY).withTypeCoercion(true)
.withSql(sql4).ok();
}
@Test void testInsertExtendedColumnModifiableViewFailUnderlyingCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String error0 = "Cannot assign to target field 'COMM' of type"
+ " INTEGER from source field 'COMM' of type BOOLEAN";
final String sql0 = "insert into EMP_MODIFIABLEVIEW3(^comm^ BOOLEAN)"
+ " (empno, ename, job, comm) values (1, 'Arthur', 'clown', true)";
s.withSql(sql0).fails(error0);
final String sql1 = "insert into EMP_MODIFIABLEVIEW3(\"comm\" BOOLEAN)"
+ " (empno, ename, job, ^comm^) values (1, 'Arthur', 'clown', 5)";
final String error1 = "Unknown target column 'COMM'";
s.withSql(sql1).fails(error1);
final String sql2 = "insert into EMP_MODIFIABLEVIEW3(\"comm\" BOOLEAN)"
+ " (empno, ename, job, ^\"comm\"^) values (1, 'Arthur', 'clown', 1)";
final String error2 = "Cannot assign to target field 'comm' of type"
+ " BOOLEAN from source field 'EXPR\\$3' of type INTEGER";
s.withSql(sql2).withTypeCoercion(false).fails(error2);
s.withSql(sql2).ok();
}
@Test void testDelete() {
sql("delete from empdefaults where deptno = 10").ok();
}
@Test void testDeleteExtendedColumn() {
sql("delete from empdefaults(extra BOOLEAN) where deptno = 10").ok();
sql("delete from empdefaults(extra BOOLEAN) where extra = false").ok();
}
@Test void testDeleteBindExtendedColumn() {
sql("delete from empdefaults(extra BOOLEAN) where deptno = ?").ok();
sql("delete from empdefaults(extra BOOLEAN) where extra = ?").ok();
}
@Test void testDeleteModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("delete from EMP_MODIFIABLEVIEW2 where deptno = 10").ok();
s.withSql("delete from EMP_MODIFIABLEVIEW2 where deptno = 20").ok();
s.withSql("delete from EMP_MODIFIABLEVIEW2 where empno = 30").ok();
}
@Test void testDeleteExtendedColumnModifiableView() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
s.withSql("delete from EMP_MODIFIABLEVIEW2(extra BOOLEAN) where sal > 10")
.ok();
s.withSql("delete from EMP_MODIFIABLEVIEW2(note BOOLEAN) where note = 'fired'")
.ok();
}
@Test void testDeleteExtendedColumnCollision() {
final String sql =
"delete from emp(empno INTEGER NOT NULL) where sal > 10";
sql(sql).withExtendedCatalog().ok();
}
@Test void testDeleteExtendedColumnModifiableViewCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "delete from EMP_MODIFIABLEVIEW2("
+ "empno INTEGER NOT NULL) where sal > 10";
s.withSql(sql0).ok();
final String sql1 = "delete from EMP_MODIFIABLEVIEW2(\"empno\" INTEGER)\n"
+ "where sal > 10";
s.withSql(sql1).ok();
final String sql2 = "delete from EMP_MODIFIABLEVIEW2(extra BOOLEAN)\n"
+ "where sal > 10";
s.withSql(sql2).ok();
final String sql3 = "delete from EMP_MODIFIABLEVIEW2(\"extra\" VARCHAR)\n"
+ "where sal > 10";
s.withSql(sql3).ok();
final String sql4 = "delete from EMP_MODIFIABLEVIEW3(comm INTEGER)\n"
+ "where sal > 10";
s.withSql(sql4).ok();
final String sql5 = "delete from EMP_MODIFIABLEVIEW3(\"comm\" BIGINT)\n"
+ "where sal > 10";
s.withSql(sql5).ok();
}
@Test void testDeleteExtendedColumnFailCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "delete from EMP_MODIFIABLEVIEW2(^empno^ BOOLEAN)\n"
+ "where sal > 10";
final String error0 = "Cannot assign to target field 'EMPNO' of type"
+ " INTEGER NOT NULL from source field 'EMPNO' of type BOOLEAN";
s.withSql(sql0).fails(error0);
final String sql1 = "delete from EMP_MODIFIABLEVIEW2(^empno^ INTEGER)\n"
+ "where sal > 10";
final String error = "Cannot assign to target field 'EMPNO' of type"
+ " INTEGER NOT NULL from source field 'EMPNO' of type INTEGER";
s.withSql(sql1).fails(error);
final String sql2 = "delete from EMP_MODIFIABLEVIEW2(^\"EMPNO\"^ INTEGER)"
+ " where sal > 10";
s.withSql(sql2).fails(error);
s.withSql(sql1).fails(error);
}
@Test void testDeleteExtendedColumnModifiableViewFailCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String sql0 = "delete from EMP_MODIFIABLEVIEW(^deptno^ BOOLEAN)\n"
+ "where sal > 10";
final String error = "Cannot assign to target field 'DEPTNO' of type"
+ " INTEGER from source field 'DEPTNO' of type BOOLEAN";
s.withSql(sql0).fails(error);
final String sql1 = "delete from EMP_MODIFIABLEVIEW(^\"DEPTNO\"^ BOOLEAN)"
+ " where sal > 10";
s.withSql(sql1).fails(error);
}
@Test void testDeleteExtendedColumnModifiableViewFailExtendedCollision() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
final String error = "Cannot assign to target field 'SLACKER' of type"
+ " BOOLEAN from source field 'SLACKER' of type INTEGER";
final String sql0 = "delete from EMP_MODIFIABLEVIEW(^slacker^ INTEGER)\n"
+ "where sal > 10";
s.withSql(sql0).fails(error);
final String sql1 = "delete from EMP_MODIFIABLEVIEW(^\"SLACKER\"^ INTEGER)"
+ " where sal > 10";
s.withSql(sql1).fails(error);
}
@Test void testDeleteExtendedColumnFailDuplicate() {
final SqlValidatorFixture s = fixture().withExtendedCatalog();
sql("delete from emp (extra VARCHAR, ^extra^ VARCHAR)")
.fails("Duplicate name 'EXTRA' in column list");
s.withSql("delete from EMP_MODIFIABLEVIEW (extra VARCHAR, ^extra^ VARCHAR)"
+ " where extra = 'test'")
.fails("Duplicate name 'EXTRA' in column list");
s.withSql("delete from EMP_MODIFIABLEVIEW (extra VARCHAR, ^\"EXTRA\"^ VARCHAR)"
+ " where extra = 'test'")
.fails("Duplicate name 'EXTRA' in column list");
}
/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-1804">[CALCITE-1804]
* Cannot assign NOT NULL array to nullable array</a>. */
@Test void testArrayAssignment() {
final SqlTypeFactoryImpl typeFactory =
new SqlTypeFactoryImpl(RelDataTypeSystem.DEFAULT);
final RelDataType bigint = typeFactory.createSqlType(SqlTypeName.BIGINT);
final RelDataType bigintNullable =
typeFactory.createTypeWithNullability(bigint, true);
final RelDataType bigintNotNull =
typeFactory.createTypeWithNullability(bigint, false);
final RelDataType date = typeFactory.createSqlType(SqlTypeName.DATE);
final RelDataType dateNotNull =
typeFactory.createTypeWithNullability(date, false);
assertThat(SqlTypeUtil.canAssignFrom(bigintNullable, bigintNotNull),
is(true));
assertThat(SqlTypeUtil.canAssignFrom(bigintNullable, dateNotNull),
is(false));
final RelDataType bigintNullableArray =
typeFactory.createArrayType(bigintNullable, -1);
final RelDataType bigintArrayNullable =
typeFactory.createTypeWithNullability(bigintNullableArray, true);
final RelDataType bigintNotNullArray =
new ArraySqlType(bigintNotNull, false);
assertThat(SqlTypeUtil.canAssignFrom(bigintArrayNullable, bigintNotNullArray),
is(true));
final RelDataType dateNotNullArray =
new ArraySqlType(dateNotNull, false);
assertThat(SqlTypeUtil.canAssignFrom(bigintArrayNullable, dateNotNullArray),
is(false));
}
@Test void testSelectRolledUpColumn() {
final String error = "Rolled up column 'SLACKINGMIN' is not allowed in SELECT";
sql("select ^slackingmin^ from emp_r")
.fails(error);
sql("select a from (select ^slackingmin^ from emp_r)")
.fails(error);
sql("select ^slackingmin^ as b from emp_r")
.fails(error);
sql("select empno, ^slackingmin^ from emp_r")
.fails(error);
sql("select slackingmin from (select empno as slackingmin from emp_r)").ok();
sql("select ^emp_r.slackingmin^ from emp_r")
.fails(error);
sql("select ^sales.emp_r.slackingmin^ from sales.emp_r")
.fails(error);
sql("select ^sales.emp_r.slackingmin^ from emp_r")
.fails(error);
sql("select ^catalog.sales.emp_r.slackingmin^ from emp_r")
.fails(error);
sql("select (select ^slackingmin^ from emp_r), a from (select empno as a from emp_r)")
.fails(error);
sql("select (((^slackingmin^))) from emp_r")
.fails(error);
sql("select ^slackingmin^ from nest.emp_r")
.fails(error);
sql("with emp_r as (select 1 as slackingmin) select slackingmin from emp_r")
.ok();
sql("with emp_r as (select ^slackingmin^ from emp_r) select slackingmin from emp_r")
.fails(error);
sql("with emp_r1 as (select 1 as slackingmin) select emp_r1.slackingmin from emp_r, emp_r1")
.ok();
sql("with emp_r1 as (select 1 as slackingmin) select ^emp_r.slackingmin^ from emp_r, emp_r1")
.fails(error);
}
@Test void testSelectAggregateOnRolledUpColumn() {
final String maxError = "Rolled up column 'SLACKINGMIN' is not allowed in MAX";
final String plusError = "Rolled up column 'SLACKINGMIN' is not allowed in PLUS";
sql("select max(^slackingmin^) from emp_r")
.fails(maxError);
sql("select count(slackingmin) from emp_r").ok();
sql("select count(empno, deptno, slackingmin) from emp_r").ok();
sql("select sum(slackingmin) from emp_r").ok();
sql("select empno, min(slackingmin) from emp_r group by empno").ok();
sql("select count(distinct slackingmin) from emp_r").ok();
sql("select sum(empno + ^slackingmin^) from emp_r")
.fails(plusError);
sql("select max(^slackingmin^) over t as a "
+ "from emp_r window t as (partition by empno order by empno)")
.fails(maxError);
}
@Test void testRolledUpColumnInWhere() {
final String error = "Rolled up column 'SLACKINGMIN' is not allowed in GREATER_THAN";
// Fire these slackers!!
sql("select empno from emp_r where slacker and ^slackingmin^ > 60")
.fails(error);
sql("select sum(slackingmin) filter (where slacker and ^slackingmin^ > 60) from emp_r")
.fails(error);
}
@Test void testRolledUpColumnInHaving() {
final String error = "Rolled up column 'SLACKINGMIN' is not allowed in SUM";
sql("select deptno, sum(slackingmin) from emp_r group "
+ "by deptno having sum(^slackingmin^) > 1000")
.fails(error);
}
@Test void testRollUpInWindow() {
final String partitionError = "Rolled up column 'SLACKINGMIN' is not allowed in PARTITION BY";
final String orderByError = "Rolled up column 'SLACKINGMIN' is not allowed in ORDER BY";
sql("select empno, sum(slackingmin) over (partition by ^slackingmin^) from emp_r")
.fails(partitionError);
sql("select empno, sum(slackingmin) over (partition by empno, ^slackingmin^) from emp_r")
.fails(partitionError);
sql("select empno, sum(slackingmin) over (partition by empno order by ^slackingmin^) "
+ "from emp_r")
.fails(orderByError);
sql("select empno, sum(slackingmin) over slackingmin "
+ "from emp_r window slackingmin as (partition by ^slackingmin^)")
.fails(partitionError);
sql("select sum(slackingmin) over t "
+ "from emp_r window t as (partition by empno order by ^slackingmin^, empno)")
.fails(orderByError);
sql("select sum(slackingmin) over t as a "
+ "from emp_r window t as (partition by empno order by ^slackingmin^, empno)")
.fails(orderByError);
}
@Test void testRollUpInGroupBy() {
final String error = "Rolled up column 'SLACKINGMIN' is not allowed in GROUP BY";
sql("select empno, count(distinct empno) from emp_r group by empno, ^slackingmin^")
.fails(error);
sql("select empno from emp_r group by grouping sets (empno, ^slackingmin^)")
.fails(error);
}
@Test void testRollUpInOrderBy() {
final String error = "Rolled up column 'SLACKINGMIN' is not allowed in ORDER BY";
sql("select empno from emp_r order by ^slackingmin^ asc")
.fails(error);
sql("select slackingmin from (select empno as slackingmin from emp_r)\n"
+ "order by slackingmin")
.ok();
sql("select empno, sum(slackingmin) from emp_r group by empno\n"
+ "order by sum(slackingmin)").ok();
}
@Test void testRollUpInJoin() {
final String onError = "Rolled up column 'SLACKINGMIN' is not allowed in ON";
final String usingError = "Rolled up column 'SLACKINGMIN' is not allowed in USING";
final String selectError = "Rolled up column 'SLACKINGMIN' is not allowed in SELECT";
sql("select * from (select deptno, ^slackingmin^ from emp_r)\n"
+ " join dept using (deptno)")
.fails(selectError);
sql("select * from dept as a\n"
+ "join (select deptno, ^slackingmin^ from emp_r) using (deptno)")
.fails(selectError);
sql("select * from emp_r as a\n"
+ "join dept_r as b using (deptno, ^slackingmin^)")
.fails(usingError);
// Even though the emp_r.slackingmin column will be under the SqlNode for '=',
// The error should say it happened in 'ON' instead
sql("select * from emp_r\n"
+ "join dept_r on (^emp_r.slackingmin^ = dept_r.slackingmin)")
.fails(onError);
}
@Test void testJsonValueExpressionOperator() {
expr("'{}' format json").ok();
expr("'{}' format json encoding utf8").ok();
expr("'{}' format json encoding utf16").ok();
expr("'{}' format json encoding utf32").ok();
expr("'{}' format json")
.columnType("ANY NOT NULL");
expr("'null' format json")
.columnType("ANY NOT NULL");
expr("cast(null as varchar) format json")
.columnType("ANY");
expr("null format json")
.columnType("ANY");
expr("^null^ format json")
.withTypeCoercion(false)
.fails("(?s).*Illegal use of .NULL.*");
}
@Test void testJsonExists() {
expr("json_exists('{}', 'lax $')").ok();
expr("json_exists('{}', 'lax $')")
.columnType("BOOLEAN");
}
@Test void testJsonValue() {
expr("json_value('{\"foo\":\"bar\"}', 'lax $.foo')").ok();
expr("json_value('{\"foo\":\"bar\"}', 123)").ok();
expr("json_value('{\"foo\":\"bar\"}', 'lax $.foo')")
.columnType("VARCHAR(2000)");
expr("json_value('{\"foo\":100}', 'lax $.foo')")
.columnType("VARCHAR(2000)");
expr("json_value('{\"foo\":100}', 'lax $.foo'"
+ "returning integer)")
.columnType("INTEGER");
expr("json_value('{\"foo\":100}', 'lax $.foo'"
+ "returning integer default 0 on empty default 0 on error)")
.columnType("INTEGER");
expr("json_value('{\"foo\":100}', 'lax $.foo'"
+ "returning integer default null on empty default null on error)")
.columnType("INTEGER");
expr("json_value('{\"foo\":true}', 'lax $.foo'"
+ "returning boolean default 100 on empty default 100 on error)")
.columnType("BOOLEAN");
// test type inference of default value
expr("json_value('{\"foo\":100}', 'lax $.foo' default 'empty' on empty)")
.columnType("VARCHAR(2000)");
expr("json_value('{\"foo\":100}', 'lax $.foo' returning boolean"
+ " default 100 on empty)")
.columnType("BOOLEAN");
expr("json_value('{\"foo\":[100, null, 200]}', 'lax $.foo'"
+ "returning integer array)")
.columnType("INTEGER ARRAY");
expr("json_value('{\"foo\":[[100, null, 200]]}', 'lax $.foo'"
+ "returning integer array array)")
.columnType("INTEGER ARRAY ARRAY");
}
@Test void testJsonQuery() {
expr("json_query('{\"foo\":\"bar\"}', 'lax $')").ok();
expr("json_query('{\"foo\":\"bar\"}', 'lax $')")
.columnType("VARCHAR(2000)");
expr("json_query('{\"foo\":\"bar\"}', 'strict $')")
.columnType("VARCHAR(2000)");
expr("json_query('{\"foo\":\"bar\"}', 'strict $' WITH WRAPPER)")
.columnType("VARCHAR(2000)");
expr("json_query('{\"foo\":\"bar\"}', 'strict $' EMPTY OBJECT ON EMPTY)")
.columnType("VARCHAR(2000)");
expr("json_query('{\"foo\":\"bar\"}', 'strict $' EMPTY ARRAY ON ERROR)")
.columnType("VARCHAR(2000)");
expr("json_query('{\"foo\":\"bar\"}', 'strict $' EMPTY OBJECT ON EMPTY "
+ "EMPTY ARRAY ON ERROR EMPTY ARRAY ON EMPTY NULL ON ERROR)")
.columnType("VARCHAR(2000)");
}
@Test void testJsonArray() {
expr("json_array()").ok();
expr("json_array('foo', 'bar')").ok();
expr("json_array('foo', 'bar')")
.columnType("VARCHAR(2000) NOT NULL");
}
@Test void testJsonArrayAgg() {
sql("select json_arrayagg(ename) from emp").ok();
expr("json_arrayagg('foo')")
.columnType("VARCHAR(2000) NOT NULL");
}
@Test void testJsonObject() {
expr("json_object()").ok();
expr("json_object('foo': 'bar')").ok();
expr("json_object('foo': 'bar')")
.columnType("VARCHAR(2000) NOT NULL");
expr("^json_object(100: 'bar')^")
.fails("(?s).*Expected a character type*");
}
@Test void testJsonPretty() {
sql("select json_pretty(ename) from emp").ok();
expr("json_pretty('{\"foo\":\"bar\"}')").ok();
expr("json_pretty('{\"foo\":\"bar\"}')")
.columnType("VARCHAR(2000)");
sql("select json_pretty(^NULL^) from emp")
.withTypeCoercion(false)
.fails("(?s).*Illegal use of .NULL.*");
sql("select json_pretty(NULL) from emp").ok();
if (!Bug.CALCITE_2869_FIXED) {
// the case should throw an error but currently validation
// is done during sql-to-rel process.
//
// see StandardConvertletTable.JsonOperatorValueExprConvertlet
return;
}
sql("select json_pretty(^1^) from emp")
.fails("(.*)JSON_VALUE_EXPRESSION(.*)");
}
@Test void testJsonStorageSize() {
sql("select json_storage_size(ename) from emp").ok();
expr("json_storage_size('{\"foo\":\"bar\"}')").ok();
expr("json_storage_size('{\"foo\":\"bar\"}')")
.columnType("INTEGER");
}
@Test void testJsonType() {
sql("select json_type(ename) from emp").ok();
expr("json_type('{\"foo\":\"bar\"}')").ok();
expr("json_type('{\"foo\":\"bar\"}')")
.columnType("VARCHAR(20)");
if (!Bug.CALCITE_2869_FIXED) {
return;
}
sql("select json_type(^1^) from emp")
.fails("(.*)JSON_VALUE_EXPRESSION(.*)");
}
@Test void testJsonDepth() {
sql("select json_depth(ename) from emp").ok();
expr("json_depth('{\"foo\":\"bar\"}')").ok();
expr("json_depth('{\"foo\":\"bar\"}')")
.columnType("INTEGER");
if (!Bug.CALCITE_2869_FIXED) {
return;
}
sql("select json_depth(^1^) from emp")
.fails("(.*)JSON_VALUE_EXPRESSION(.*)");
}
@Test void testJsonLength() {
expr("json_length('{\"foo\":\"bar\"}')").ok();
expr("json_length('{\"foo\":\"bar\"}', 'lax $')").ok();
expr("json_length('{\"foo\":\"bar\"}')")
.columnType("INTEGER");
expr("json_length('{\"foo\":\"bar\"}', 'lax $')")
.columnType("INTEGER");
expr("json_length('{\"foo\":\"bar\"}', 'strict $')")
.columnType("INTEGER");
}
@Test void testJsonKeys() {
expr("json_keys('{\"foo\":\"bar\"}', 'lax $')").ok();
expr("json_keys('{\"foo\":\"bar\"}', 'lax $')")
.columnType("VARCHAR(2000)");
expr("json_keys('{\"foo\":\"bar\"}', 'strict $')")
.columnType("VARCHAR(2000)");
}
@Test void testJsonRemove() {
expr("json_remove('{\"foo\":\"bar\"}', '$')").ok();
expr("json_remove('{\"foo\":\"bar\"}', '$')")
.columnType("VARCHAR(2000)");
expr("json_remove('{\"foo\":\"bar\"}', 1, '2', 3)")
.columnType("VARCHAR(2000)");
expr("json_remove('{\"foo\":\"bar\"}', 1, 2, 3)")
.columnType("VARCHAR(2000)");
sql("select ^json_remove('{\"foo\":\"bar\"}')^")
.fails("(?s).*Invalid number of arguments.*");
}
@Test void testJsonObjectAgg() {
sql("select json_objectagg(ename: empno) from emp").ok();
sql("select json_objectagg(empno: ename) from emp").ok();
sql("select ^json_objectagg(empno: ename)^ from emp")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*");
expr("json_objectagg('foo': 'bar')")
.columnType("VARCHAR(2000) NOT NULL");
}
@Test void testJsonPredicate() {
expr("'{}' is json")
.columnType("BOOLEAN NOT NULL");
expr("'{}' is json value")
.columnType("BOOLEAN NOT NULL");
expr("'{}' is json object")
.columnType("BOOLEAN NOT NULL");
expr("'[]' is json array")
.columnType("BOOLEAN NOT NULL");
expr("'100' is json scalar")
.columnType("BOOLEAN NOT NULL");
expr("'{}' is not json")
.columnType("BOOLEAN NOT NULL");
expr("'{}' is not json value")
.columnType("BOOLEAN NOT NULL");
expr("'{}' is not json object")
.columnType("BOOLEAN NOT NULL");
expr("'[]' is not json array")
.columnType("BOOLEAN NOT NULL");
expr("'100' is not json scalar")
.columnType("BOOLEAN NOT NULL");
expr("100 is json value")
.columnType("BOOLEAN NOT NULL");
expr("^100 is json value^")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply.*");
}
@Test public void testJsonInsert() {
expr("json_insert('{ \"a\": 1, \"b\": [2]}', '$.a', 10, '$.c', '[true]')").ok();
expr("json_insert('{ \"a\": 1, \"b\": [2]}', '$.a', 10, '$.c', '[true]')")
.columnType("VARCHAR(2000)");
expr("select ^json_insert('{\"foo\":\"bar\"}')^")
.fails("(?s).*Invalid number of arguments.*");
}
@Test public void testJsonReplace() {
expr("json_replace('{ \"a\": 1, \"b\": [2]}', '$.a', 10, '$.c', '[true]')").ok();
expr("json_replace('{ \"a\": 1, \"b\": [2]}', '$.a', 10, '$.c', '[true]')")
.columnType("VARCHAR(2000)");
expr("select ^json_replace('{\"foo\":\"bar\"}')^")
.fails("(?s).*Invalid number of arguments.*");
}
@Test public void testJsonSet() {
expr("json_set('{ \"a\": 1, \"b\": [2]}', '$.a', 10, '$.c', '[true]')").ok();
expr("json_set('{ \"a\": 1, \"b\": [2]}', '$.a', 10, '$.c', '[true]')")
.columnType("VARCHAR(2000)");
expr("select ^json_set('{\"foo\":\"bar\"}')^")
.fails("(?s).*Invalid number of arguments.*");
}
@Test void testRegexpReplace() {
final SqlOperatorTable opTable = operatorTableFor(SqlLibrary.ORACLE);
expr("REGEXP_REPLACE('a b c', 'a', 'X')")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
expr("REGEXP_REPLACE('abc def ghi', '[a-z]+', 'X', 2)")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
expr("REGEXP_REPLACE('abc def ghi', '[a-z]+', 'X', 1, 3)")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
expr("REGEXP_REPLACE('abc def GHI', '[a-z]+', 'X', 1, 3, 'c')")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
// Implicit type coercion.
expr("REGEXP_REPLACE(null, '(-)', '###')")
.withOperatorTable(opTable)
.columnType("VARCHAR");
expr("REGEXP_REPLACE('100-200', null, '###')")
.withOperatorTable(opTable)
.columnType("VARCHAR");
expr("REGEXP_REPLACE('100-200', '(-)', null)")
.withOperatorTable(opTable)
.columnType("VARCHAR");
expr("REGEXP_REPLACE('abc def ghi', '[a-z]+', 'X', '2')")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
expr("REGEXP_REPLACE('abc def ghi', '[a-z]+', 'X', '1', '3')")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
// The last argument to REGEXP_REPLACE should be specific character, but with
// implicit type coercion, the validation still passes.
expr("REGEXP_REPLACE('abc def ghi', '[a-z]+', 'X', '1', '3', '1')")
.withOperatorTable(opTable)
.columnType("VARCHAR NOT NULL");
}
@Test void testInvalidFunctionCall() {
final SqlOperatorTable operatorTable =
MockSqlOperatorTable.standard().extend();
// With implicit type coercion.
expr("^unknown_udf(1, 2)^")
.fails("(?s).*No match found for function signature "
+ "UNKNOWN_UDF\\(<NUMERIC>, <NUMERIC>\\).*");
expr("^power(cast(1 as timestamp), cast(2 as timestamp))^")
.fails("(?s).*Cannot apply 'POWER' to arguments of type "
+ "'POWER\\(<TIMESTAMP\\(0\\)>, <TIMESTAMP\\(0\\)>\\)'.*");
expr("^myFUN(cast('124' as timestamp))^")
.withCaseSensitive(true)
.withOperatorTable(operatorTable)
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'MYFUN' to arguments of type "
+ "'MYFUN\\(<TIMESTAMP\\(0\\)>\\)'.*");
expr("^myFUN(1, 2)^")
.withCaseSensitive(true)
.withOperatorTable(operatorTable)
.withTypeCoercion(false)
.fails("(?s).*No match found for function signature "
+ "MYFUN\\(<NUMERIC>, <NUMERIC>\\).*");
// Without implicit type coercion.
expr("^unknown_udf(1, 2)^")
.withTypeCoercion(false)
.fails("(?s).*No match found for function signature "
+ "UNKNOWN_UDF\\(<NUMERIC>, <NUMERIC>\\).*");
expr("^power(cast(1 as timestamp), cast(2 as timestamp))^")
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'POWER' to arguments of type "
+ "'POWER\\(<TIMESTAMP\\(0\\)>, <TIMESTAMP\\(0\\)>\\)'.*");
expr("^myFUN(cast('124' as timestamp))^")
.withCaseSensitive(true)
.withOperatorTable(operatorTable)
.withTypeCoercion(false)
.fails("(?s).*Cannot apply 'MYFUN' to arguments of type "
+ "'MYFUN\\(<TIMESTAMP\\(0\\)>\\)'.*");
expr("^myFUN(1, 2)^")
.withCaseSensitive(true)
.withOperatorTable(operatorTable)
.withTypeCoercion(false)
.fails("(?s).*No match found for function signature "
+ "MYFUN\\(<NUMERIC>, <NUMERIC>\\).*");
}
@Test void testPositionalAggregateWithExpandedCurrentDateFunction() {
SqlConformance defaultPlusOrdinalGroupBy =
new SqlDelegatingConformance(SqlConformanceEnum.DEFAULT) {
@Override public boolean isGroupByOrdinal() {
return true;
}
};
sql("SELECT HIREDATE >= CURRENT_DATE, COUNT(*) "
+ "FROM EMP GROUP BY 1")
.withConformance(defaultPlusOrdinalGroupBy)
.ok();
}
@Test void testValidatorReportsOriginalQueryUsingReader()
throws Exception {
final String sql = "select a from b";
final SqlParser.Config config = SqlParser.config();
final String messagePassingSqlString;
try {
final SqlParser sqlParserReader = SqlParser.create(sql, config);
final SqlNode node = sqlParserReader.parseQuery();
final SqlValidator validator = fixture().factory.createValidator();
final SqlNode x = validator.validate(node);
fail("expecting an error, got " + x);
return;
} catch (CalciteContextException error) {
// we want the exception to report column and line
assertThat(error.getMessage(),
is("At line 1, column 15: Object 'B' not found"));
messagePassingSqlString = error.getMessage();
// the error does not contain the original query (even using a String)
assertNull(error.getOriginalStatement());
}
// parse SQL text using a java.io.Reader and not a java.lang.String
try {
final SqlParser sqlParserReader =
SqlParser.create(new StringReader(sql), config);
final SqlNode node = sqlParserReader.parseQuery();
final SqlValidator validator = fixture().factory.createValidator();
final SqlNode x = validator.validate(node);
fail("expecting an error, got " + x);
} catch (CalciteContextException error) {
// we want exactly the same error as with java.lang.String input
assertThat(error.getMessage(), is(messagePassingSqlString));
// the error does not contain the original query (using a Reader)
assertThat(error.getOriginalStatement(), nullValue());
}
}
@Test void testValidateParameterizedExpression() throws SqlParseException {
final SqlParser.Config config = SqlParser.config();
final SqlValidator validator = fixture().factory.createValidator();
final RelDataTypeFactory typeFactory = validator.getTypeFactory();
final RelDataType intType = typeFactory.createSqlType(SqlTypeName.INTEGER);
final RelDataType intTypeNull = typeFactory.createTypeWithNullability(intType, true);
final Map<String, RelDataType> nameToTypeMap = new HashMap<>();
nameToTypeMap.put("A", intType);
nameToTypeMap.put("B", intTypeNull);
final String expr = "a + b";
final SqlParser parser = SqlParser.create(expr, config);
final SqlNode sqlNode = parser.parseExpression();
final SqlNode validated = validator.validateParameterizedExpression(sqlNode, nameToTypeMap);
final RelDataType resultType = validator.getValidatedNodeType(validated);
assertThat(resultType, hasToString("INTEGER"));
}
/**
* Tests validation of must-filter columns.
*
* <p>If a table that implements
* {@link org.apache.calcite.sql.validate.SemanticTable} tags fields as
* 'must-filter', and the SQL query does not contain a WHERE or HAVING clause
* on each of the tagged columns, the validator should throw an error.
*/
@Test void testMustFilterColumns() {
final SqlValidatorFixture fixture = fixture()
.withParserConfig(c -> c.withQuoting(Quoting.BACK_TICK))
.withOperatorTable(operatorTableFor(SqlLibrary.BIG_QUERY))
.withCatalogReader(MustFilterMockCatalogReader::create);
// Basic query
fixture.withSql("select empno\n"
+ "from emp\n"
+ "where job = 'doctor'\n"
+ "and empno = 1")
.ok();
fixture.withSql("^select *\n"
+ "from emp\n"
+ "where concat(emp.empno, ' ') = 'abc'^")
.fails(missingFilters("JOB"));
// SUBQUERIES
fixture.withSql("select * from (\n"
+ " select * from emp where empno = 1)\n"
+ "where job = 'doctor'")
.ok();
// Deceitful alias #1. Filter on 'j' is a filter on the underlying 'job'.
fixture.withSql("select * from (\n"
+ " select job as j, ename as job\n"
+ " from emp\n"
+ " where empno = 1)\n"
+ "where j = 'doctor'")
.ok();
// Deceitful alias #2. Filter on 'job' is a filter on the underlying
// 'ename', so the underlying 'job' is missing a filter.
fixture.withSql("^select * from (\n"
+ " select job as j, ename as job\n"
+ " from emp\n"
+ " where empno = 1)\n"
+ "where job = 'doctor'^")
.fails(missingFilters("J"));
fixture.withSql("select * from (\n"
+ " select * from emp where job = 'doctor')\n"
+ "where empno = 1")
.ok();
fixture.withSql("select * from (\n"
+ " select empno from emp where job = 'doctor')\n"
+ "where empno = 1")
.ok();
fixture.withSql("^select * from (\n"
+ " select * from emp where empno = 1)^")
.fails(missingFilters("JOB"));
fixture.withSql("^select * from (select * from `SALES`.`EMP`) as a1^ ")
.fails(missingFilters("EMPNO", "JOB"));
// JOINs
fixture.withSql("^select *\n"
+ "from emp\n"
+ "join dept on emp.deptno = dept.deptno^")
.fails(missingFilters("EMPNO", "JOB", "NAME"));
fixture.withSql("^select *\n"
+ "from emp\n"
+ "join dept on emp.deptno = dept.deptno\n"
+ "where emp.empno = 1^")
.fails(missingFilters("JOB", "NAME"));
fixture.withSql("select *\n"
+ "from emp\n"
+ "join dept on emp.deptno = dept.deptno\n"
+ "where emp.empno = 1\n"
+ "and emp.job = 'doctor'\n"
+ "and dept.name = 'ACCOUNTING'")
.ok();
fixture.withSql("select *\n"
+ "from emp\n"
+ "join dept on emp.deptno = dept.deptno\n"
+ "where empno = 1\n"
+ "and job = 'doctor'\n"
+ "and dept.name = 'ACCOUNTING'")
.ok();
// Self-join
fixture.withSql("^select *\n"
+ "from `SALES`.emp a1\n"
+ "join `SALES`.emp a2 on a1.empno = a2.empno^")
.fails(missingFilters("EMPNO", "EMPNO0", "JOB", "JOB0"));
fixture.withSql("^select *\n"
+ "from emp a1\n"
+ "join emp a2 on a1.empno = a2.empno\n"
+ "where a2.empno = 1\n"
+ "and a1.empno = 1\n"
+ "and a2.job = 'doctor'^")
// There are two JOB columns but only one is filtered
.fails(missingFilters("JOB"));
fixture.withSql("select *\n"
+ "from emp a1\n"
+ "join emp a2 on a1.empno = a2.empno\n"
+ "where a1.empno = 1\n"
+ "and a1.job = 'doctor'\n"
+ "and a2.empno = 2\n"
+ "and a2.job = 'undertaker'\n")
.ok();
fixture.withSql("^select *\n"
+ " from (select * from `SALES`.`EMP`) as a1\n"
+ "join (select * from `SALES`.`EMP`) as a2\n"
+ " on a1.`EMPNO` = a2.`EMPNO`^")
.fails(missingFilters("EMPNO", "EMPNO0", "JOB", "JOB0"));
// USING
fixture.withSql("^select *\n"
+ "from emp\n"
+ "join dept using(deptno)\n"
+ "where emp.empno = 1^")
.fails(missingFilters("JOB", "NAME"));
fixture.withSql("select *\n"
+ "from emp\n"
+ "join dept using(deptno)\n"
+ "where emp.empno = 1\n"
+ "and emp.job = 'doctor'\n"
+ "and dept.name = 'ACCOUNTING'")
.ok();
// GROUP BY (HAVING)
fixture.withSql("select *\n"
+ "from dept\n"
+ "group by deptno, name\n"
+ "having name = 'accounting_dept'")
.ok();
fixture.withSql("^select *\n"
+ "from dept\n"
+ "group by deptno, name^")
.fails(missingFilters("NAME"));
fixture.withSql("select name\n"
+ "from dept\n"
+ "group by name\n"
+ "having name = 'accounting'")
.ok();
fixture.withSql("^select name\n"
+ "from dept\n"
+ "group by name^ ")
.fails(missingFilters("NAME"));
fixture.withSql("select sum(sal)\n"
+ "from emp\n"
+ "where empno > 10\n"
+ "and job = 'doctor'\n"
+ "group by empno\n"
+ "having sum(sal) > 100")
.ok();
fixture.withSql("^select sum(sal)\n"
+ "from emp\n"
+ "where empno > 10\n"
+ "group by empno\n"
+ "having sum(sal) > 100^")
.fails(missingFilters("JOB"));
// CTE
fixture.withSql("^WITH cte AS (\n"
+ " select * from emp order by empno)^\n"
+ "SELECT * from cte")
.fails(missingFilters("EMPNO", "JOB"));
fixture.withSql("^WITH cte AS (\n"
+ " select * from emp where empno = 1)^\n"
+ "SELECT * from cte")
.fails(missingFilters("JOB"));
fixture.withSql("WITH cte AS (\n"
+ " select *\n"
+ " from emp\n"
+ " where empno = 1\n"
+ " and job = 'doctor')\n"
+ "SELECT * from cte")
.ok();
fixture.withSql("^WITH cte AS (\n"
+ " select * from emp)^\n"
+ "SELECT *\n"
+ "from cte\n"
+ "where empno = 1")
.fails(missingFilters("JOB"));
fixture.withSql("WITH cte AS (\n"
+ " select * from emp)\n"
+ "SELECT *\n"
+ "from cte\n"
+ "where empno = 1\n"
+ "and job = 'doctor'")
.ok();
fixture.withSql("WITH cte AS (\n"
+ " select * from emp where empno = 1)\n"
+ "SELECT *\n"
+ "from cte\n"
+ "where job = 'doctor'")
.ok();
fixture.withSql("WITH cte AS (\n"
+ " select empno, job from emp)\n"
+ "SELECT *\n"
+ "from cte\n"
+ "where empno = 1\n"
+ "and job = 'doctor'")
.ok();
// Filters are missing on EMPNO and JOB, but the error message only
// complains about JOB because EMPNO is in the SELECT clause, and could
// theoretically be filtered by an enclosing query.
fixture.withSql("^select empno\n"
+ "from emp^")
.fails(missingFilters("JOB"));
fixture.withSql("^select empno,\n"
+ " sum(sal) over (order by mgr)\n"
+ "from emp^")
.fails(missingFilters("JOB"));
}
/** Returns a message that the particular columns are not filtered. */
private static String missingFilters(String... args) {
return "SQL statement did not contain filters on the following fields: \\["
+ String.join(", ", new TreeSet<>(Arrays.asList(args)))
+ "\\]";
}
@Test void testAccessingNestedFieldsOfNullableRecord() {
sql("select ROW_COLUMN_ARRAY[0].NOT_NULL_FIELD from NULLABLEROWS.NR_T1")
.withExtendedCatalog()
.type("RecordType(BIGINT EXPR$0) NOT NULL");
sql("select ROW_COLUMN_ARRAY[0]['NOT_NULL_FIELD'] from NULLABLEROWS.NR_T1")
.withExtendedCatalog()
.type("RecordType(BIGINT EXPR$0) NOT NULL");
final SqlOperatorTable operatorTable =
MockSqlOperatorTable.standard().extend();
sql("select * FROM TABLE(ROW_FUNC()) AS T(a, b)")
.withOperatorTable(operatorTable)
.type("RecordType(BIGINT NOT NULL A, BIGINT B) NOT NULL");
}
/** Validator that rewrites columnar sql identifiers 'UNEXPANDED'.'Something'
* to 'DEPT'.'Something', where 'Something' is any string. */
private static class UnexpandedToDeptValidator extends SqlValidatorImpl {
UnexpandedToDeptValidator(SqlOperatorTable opTab,
SqlValidatorCatalogReader catalogReader,
RelDataTypeFactory typeFactory, Config config) {
super(opTab, catalogReader, typeFactory, config);
}
@Override public SqlNode validate(SqlNode topNode) {
SqlNode rewrittenNode = rewriteNode(topNode);
return super.validate(rewrittenNode);
}
private static SqlNode rewriteNode(SqlNode sqlNode) {
return sqlNode.accept(new SqlShuttle() {
@Override public SqlNode visit(SqlIdentifier id) {
return rewriteIdentifier(id);
}
});
}
private static SqlIdentifier rewriteIdentifier(SqlIdentifier sqlIdentifier) {
if (sqlIdentifier.names.size() != 2) {
return sqlIdentifier;
}
if (sqlIdentifier.names.get(0).equals("UNEXPANDED")) {
return new SqlIdentifier(asList("DEPT", sqlIdentifier.names.get(1)),
null, sqlIdentifier.getParserPosition(),
asList(sqlIdentifier.getComponentParserPosition(0),
sqlIdentifier.getComponentParserPosition(1)));
} else if (sqlIdentifier.names.get(0).equals("DEPT")) {
// Identifiers are expanded multiple times
return sqlIdentifier;
} else {
return sqlIdentifier;
}
}
}
}