blob: 94e3413e2f878a6286ea7409e12e84915711a461 [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.impala.analysis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.fail;
import org.apache.impala.authorization.Privilege;
import org.apache.impala.common.AnalysisException;
import org.apache.impala.common.FrontendTestBase;
import org.apache.impala.service.BackendConfig;
import org.apache.impala.testutil.TestUtils;
import org.junit.Test;
import com.google.common.base.Preconditions;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
// TODO: Expand this test, in particular, because view creation relies
// on producing correct SQL.
public class ToSqlTest extends FrontendTestBase {
// Helpers for templated join tests.
private static final String[] joinConditions_ =
new String[] {"USING (id)", "ON (a.id = b.id)"};
// All left semi join types.
private static final String[] leftSemiJoinTypes_ =
new String[] {"LEFT SEMI JOIN", "LEFT ANTI JOIN"};
// All right semi join types.
private static final String[] rightSemiJoinTypes_ =
new String[] {"RIGHT SEMI JOIN", "RIGHT ANTI JOIN"};
// All join types that take an ON or USING clause, i.e., all joins except CROSS JOIN.
private static final String[] joinTypes_;
// Same as joinTypes_, but excluding semi joins.
private static final String[] nonSemiJoinTypes_;
static {
// Exclude the NULL AWARE LEFT ANTI JOIN operator because it cannot
// be directly expressed via SQL.
joinTypes_ = new String[JoinOperator.values().length - 2];
int numNonSemiJoinTypes = JoinOperator.values().length - 2 -
leftSemiJoinTypes_.length - rightSemiJoinTypes_.length;
nonSemiJoinTypes_ = new String[numNonSemiJoinTypes];
int i = 0;
int j = 0;
for (JoinOperator op: JoinOperator.values()) {
if (op.isCrossJoin() || op.isNullAwareLeftAntiJoin()) continue;
joinTypes_[i++] = op.toString();
if (op.isSemiJoin()) continue;
nonSemiJoinTypes_[j++] = op.toString();
}
}
/**
* Helper for the common case when the string should be identical after a roundtrip
* through the parser.
*/
private void testToSql(String query) {
testToSql(query, query);
}
private void testToSql(AnalysisContext ctx, String query) {
testToSql(ctx, query, query, false);
}
private void testToSql(String query, String expected) {
testToSql(query, System.getProperty("user.name"), expected);
}
private void testToSql(String query, String expected, ToSqlOptions options) {
String defaultDb = System.getProperty("user.name");
testToSql(createAnalysisCtx(defaultDb), query, defaultDb, expected, false, options);
}
private void testToSql(AnalysisContext ctx, String query, String expected,
boolean ignoreWhiteSpace) {
testToSql(ctx, query, System.getProperty("user.name"), expected, ignoreWhiteSpace);
}
private void testToSql(String query, String defaultDb, String expected) {
testToSql(query, defaultDb, expected, false);
}
private void testToSql(AnalysisContext ctx, String query, String defaultDb,
String expected, boolean ignoreWhiteSpace) {
testToSql(ctx, query, defaultDb, expected, ignoreWhiteSpace, ToSqlOptions.DEFAULT);
}
private void testToSql(String query, String defaultDb, String expected,
boolean ignoreWhitespace) {
testToSql(createAnalysisCtx(defaultDb), query, defaultDb, expected, ignoreWhitespace,
ToSqlOptions.DEFAULT);
}
private void testToSql(AnalysisContext ctx, String query, String defaultDb,
String expected, boolean ignoreWhitespace, ToSqlOptions options) {
String actual = null;
try {
ParseNode node = AnalyzesOk(query, ctx);
if (node instanceof QueryStmt && !options.showRewritten()) {
actual = ((QueryStmt)node).getOrigSqlString();
} else {
actual = node.toSql(options);
}
if (ignoreWhitespace) {
// Transform whitespace to single space.
actual = actual.replace('\n', ' ').replaceAll(" +", " ").trim();
}
assertEquals(expected, actual);
} catch (Exception e) {
e.printStackTrace();
fail("Failed to analyze query: " + query + "\n" + e.getMessage());
}
// Parse and analyze the resulting SQL to ensure its validity.
AnalyzesOk(actual, ctx);
}
private void runTestTemplate(String sql, String expectedSql, String[]... testDims) {
Object[] testVector = new Object[testDims.length];
runTestTemplate(sql, expectedSql, 0, testVector, testDims);
}
private void runTestTemplate(String sql, String expectedSql, int dim,
Object[] testVector, String[]... testDims) {
if (dim >= testDims.length) {
testToSql(String.format(sql, testVector), String.format(expectedSql, testVector));
return;
}
for (String s: testDims[dim]) {
testVector[dim] = s;
runTestTemplate(sql, expectedSql, dim + 1, testVector, testDims);
}
}
/**
* Generates and runs testToSql() on two variants of the given query by replacing all
* occurrences of "$TBL" in the query string with the unqualified and fully-qualified
* version of the given table name. The unqualified variant is analyzed using an
* analyzer that has tbl's db set as the default database.
* The SQL is expected to the same for both variants because toSql() should fully
* qualify unqualified table names.
* Example:
* query = "select id from $TBL, $TBL"
* tbl = "functional.alltypes"
* Variants generated and analyzed:
* select id from alltypes, alltypes (default db is "functional")
* select id from functional.alltypes, functional.alltypes (default db is "default")
*/
private void TblsTestToSql(String query, TableName tbl, String expectedSql) {
Preconditions.checkState(tbl.isFullyQualified());
Preconditions.checkState(query.contains("$TBL"));
String uqQuery = query.replace("$TBL", tbl.getTbl());
testToSql(uqQuery, tbl.getDb(), expectedSql);
AnalyzesOk(uqQuery, createAnalysisCtx(tbl.getDb()));
String fqQuery = query.replace("$TBL", tbl.toString());
testToSql(fqQuery, expectedSql);
}
@Test
public void selectListTest() {
testToSql("select 1234, 1234.0, 1234.0 + 1, 1234.0 + 1.0, 1 + 1, \"abc\" " +
"from functional.alltypes",
"SELECT 1234, 1234.0, 1234.0 + 1, 1234.0 + 1.0, 1 + 1, 'abc' " +
"FROM functional.alltypes");
// Test aliases.
testToSql("select 1234 i, 1234.0 as j, (1234.0 + 1) k, (1234.0 + 1.0) as l " +
"from functional.alltypes",
"SELECT 1234 i, 1234.0 j, (1234.0 + 1) k, (1234.0 + 1.0) l " +
"FROM functional.alltypes");
// Test select without from.
testToSql("select 1234 i, 1234.0 as j, (1234.0 + 1) k, (1234.0 + 1.0) as l",
"SELECT 1234 i, 1234.0 j, (1234.0 + 1) k, (1234.0 + 1.0) l");
// Test select without from.
testToSql("select null, 1234 < 5678, 1234.0 < 5678.0, 1234 < null " +
"from functional.alltypes",
"SELECT NULL, 1234 < 5678, 1234.0 < 5678.0, 1234 < NULL " +
"FROM functional.alltypes");
}
private boolean isCollectionTableRef(String tableName) {
return tableName.split("\\.").length > 0;
}
/**
* Test all table/column combinations in the select list of a query
* using implicit and explicit table aliases.
*/
private void testAllTableAliases(String[] tables, String[] columns)
throws AnalysisException {
for (String tbl: tables) {
TableName tblName = new TableName("functional", tbl);
String uqAlias = tbl.substring(tbl.lastIndexOf(".") + 1);
String fqAlias = "functional." + tbl;
boolean isCollectionTblRef = isCollectionTableRef(tbl);
for (String col: columns) {
String quotedCol = ToSqlUtils.identSql(col);
// Test implicit table aliases with unqualified and fully qualified
// table/view names. Unqualified table/view names should be fully
// qualified in the generated SQL (IMPALA-962).
TblsTestToSql(String.format("select %s from $TBL", col), tblName,
String.format("SELECT %s FROM %s", quotedCol, fqAlias));
TblsTestToSql(String.format("select %s.%s from $TBL", uqAlias, col), tblName,
String.format("SELECT %s.%s FROM %s", uqAlias, quotedCol,
fqAlias));
// Only references to base tables/views have a fully-qualified implicit alias.
if (!isCollectionTblRef) {
TblsTestToSql(String.format("select %s.%s from $TBL", fqAlias, col), tblName,
String.format("SELECT %s.%s FROM %s", fqAlias, col, fqAlias));
}
// Explicit table alias.
TblsTestToSql(String.format("select %s from $TBL a", col), tblName,
String.format("SELECT %s FROM %s a", quotedCol, fqAlias));
TblsTestToSql(String.format("select a.%s from $TBL a", col), tblName,
String.format("SELECT a.%s FROM %s a", quotedCol, fqAlias));
}
}
// Multiple implicit fully-qualified aliases work.
for (String t1: tables) {
for (String t2: tables) {
if (t1 == t2) continue;
// Collection tables do not have a fully-qualified implicit alias.
if (isCollectionTableRef(t1) && isCollectionTableRef(t2)) continue;
for (String col: columns) {
testToSql(String.format(
"select functional.%s.%s, functional.%s.%s " +
"from functional.%s, functional.%s", t1, col, t2, col, t1, t2),
String.format("SELECT functional.%s.%s, functional.%s.%s " +
"FROM functional.%s, functional.%s", t1, col, t2, col, t1, t2));
}
}
}
}
/**
* Tests the toSql() of the given child table and column assumed to be in
* functional.allcomplextypes, including different combinations of
* implicit/explicit aliases of the parent and child table.
*/
private void testChildTableRefs(String childTable, String childColumn) {
TableName tbl = new TableName("functional", "allcomplextypes");
// Child table uses unqualified implicit alias of parent table.
childColumn = ToSqlUtils.identSql(childColumn);
TblsTestToSql(
String.format("select %s from $TBL, allcomplextypes.%s",
childColumn, childTable), tbl,
String.format("SELECT %s FROM %s, functional.allcomplextypes.%s",
childColumn, tbl.toSql(), childTable));
// Child table uses fully qualified implicit alias of parent table.
TblsTestToSql(
String.format("select %s from $TBL, functional.allcomplextypes.%s",
childColumn, childTable), tbl,
String.format("SELECT %s FROM %s, functional.allcomplextypes.%s",
childColumn, tbl.toSql(), childTable));
// Child table uses explicit alias of parent table.
TblsTestToSql(
String.format("select %s from $TBL a, a.%s",
childColumn, childTable), tbl,
String.format("SELECT %s FROM %s a, a.%s",
childColumn, tbl.toSql(), childTable));
// Parent/child/child join.
TblsTestToSql(
String.format("select b.%s from $TBL a, a.%s b, a.int_map_col c",
childColumn, childTable), tbl,
String.format("SELECT b.%s FROM %s a, a.%s b, a.int_map_col c",
childColumn, tbl.toSql(), childTable));
TblsTestToSql(
String.format("select c.%s from $TBL a, a.int_array_col b, a.%s c",
childColumn, childTable), tbl,
String.format("SELECT c.%s FROM %s a, a.int_array_col b, a.%s c",
childColumn, tbl.toSql(), childTable));
// Test join types. Parent/child joins do not require an ON or USING clause.
for (String joinType: joinTypes_) {
TblsTestToSql(String.format("select 1 from $TBL %s allcomplextypes.%s",
joinType, childTable), tbl,
String.format("SELECT 1 FROM %s %s functional.allcomplextypes.%s",
tbl.toSql(), joinType, childTable));
TblsTestToSql(String.format("select 1 from $TBL a %s a.%s",
joinType, childTable), tbl,
String.format("SELECT 1 FROM %s a %s a.%s",
tbl.toSql(), joinType, childTable));
}
// Legal, but not a parent/child join.
TblsTestToSql(
String.format("select %s from $TBL a, functional.allcomplextypes.%s",
childColumn, childTable), tbl,
String.format("SELECT %s FROM %s a, functional.allcomplextypes.%s",
childColumn, tbl.toSql(), childTable));
TblsTestToSql(
String.format("select %s from $TBL.%s, functional.allcomplextypes",
childColumn, childTable), tbl,
String.format("SELECT %s FROM %s.%s, functional.allcomplextypes",
childColumn, tbl.toSql(), childTable));
}
@Test
public void TestCreateTable() throws AnalysisException {
// Table with SORT BY clause.
testToSql("create table p (a int) partitioned by (day string) sort by (a) " +
"comment 'This is a test'",
"default",
"CREATE TABLE default.p ( a INT ) PARTITIONED BY ( day STRING ) " +
"SORT BY LEXICAL ( a ) COMMENT 'This is a test' STORED AS TEXTFILE" , true);
testToSql("create table p (a int, b int) partitioned by (day string) sort by (a ,b) ",
"default",
"CREATE TABLE default.p ( a INT, b INT ) PARTITIONED BY ( day STRING ) " +
"SORT BY LEXICAL ( a, b ) STORED AS TEXTFILE" , true);
// Table with SORT BY LEXICAL clause.
testToSql("create table p (a int) partitioned by (day string) sort by lexical (a) " +
"comment 'This is a test'",
"default",
"CREATE TABLE default.p ( a INT ) PARTITIONED BY ( day STRING ) " +
"SORT BY LEXICAL ( a ) COMMENT 'This is a test' STORED AS TEXTFILE" , true);
testToSql("create table p (a int, b int) partitioned by (day string) sort by " +
"lexical (a, b)",
"default",
"CREATE TABLE default.p ( a INT, b INT ) PARTITIONED BY ( day STRING ) " +
"SORT BY LEXICAL ( a, b ) STORED AS TEXTFILE" , true);
// Table with SORT BY ZORDER clause.
BackendConfig.INSTANCE.setZOrderSortUnlocked(true);
testToSql("create table p (a int, b int) partitioned by (day string) sort by zorder" +
"(a ,b) ", "default",
"CREATE TABLE default.p ( a INT, b INT ) PARTITIONED BY ( day STRING ) " +
"SORT BY ZORDER ( a, b ) STORED AS TEXTFILE" , true);
BackendConfig.INSTANCE.setZOrderSortUnlocked(false);
// Kudu table with a TIMESTAMP column default value
String kuduMasters = catalog_.getDefaultKuduMasterHosts();
testToSql(String.format("create table p (a bigint primary key, " +
"b timestamp default '1987-05-19') partition by hash(a) partitions 3 " +
"stored as kudu tblproperties ('kudu.master_addresses'='%s')", kuduMasters),
"default",
String.format("CREATE TABLE default.p ( a BIGINT PRIMARY KEY, b TIMESTAMP " +
"DEFAULT '1987-05-19' ) PARTITION BY HASH (a) PARTITIONS 3 " +
"STORED AS KUDU TBLPROPERTIES ('kudu.master_addresses'='%s', " +
"'storage_handler'='org.apache.hadoop.hive.kudu.KuduStorageHandler')",
kuduMasters),
true);
// Test primary key and foreign key toSqls.
// TODO: Add support for displaying constraint information (DISABLE, NOVALIDATE, RELY)
testToSql("create table pk(id int, year string, primary key (id, year))", "default",
"CREATE TABLE default.pk ( id INT, year STRING, PRIMARY KEY (id, year) ) "
+ "STORED AS TEXTFILE", true);
// Foreign Key test requires a valid primary key table.
addTestDb("test_pk_fk", "Test DB for PK/FK tests");
addTestTable("create table test_pk_fk.pk (id int, year string, primary key (id, "
+ "year))");
AnalysisContext ctx = createAnalysisCtx("test_pk_fk");
testToSql(ctx, "create table fk(id int, year string, FOREIGN KEY (id) "
+ "REFERENCES pk(id), FOREIGN KEY (year) REFERENCES pk"
+ "(year))", "CREATE TABLE test_pk_fk.fk ( id INT, year STRING, "
+ "FOREIGN KEY(id) REFERENCES test_pk_fk.pk(id), FOREIGN KEY(year) REFERENCES "
+ "test_pk_fk.pk(year) ) STORED AS TEXTFILE", true);
}
@Test
public void TestCreateTableAsSelect() throws AnalysisException {
// Partitioned table.
testToSql("create table p partitioned by (int_col) as " +
"select double_col, int_col from functional.alltypes", "default",
"CREATE TABLE default.p PARTITIONED BY ( int_col ) STORED AS " +
"TEXTFILE AS SELECT double_col, int_col FROM functional.alltypes",
true);
// Table with a comment.
testToSql("create table p partitioned by (int_col) comment 'This is a test' as " +
"select double_col, int_col from functional.alltypes", "default",
"CREATE TABLE default.p PARTITIONED BY ( int_col ) COMMENT 'This is a test' " +
"STORED AS TEXTFILE AS SELECT double_col, int_col FROM functional.alltypes",
true);
// Table with SORT BY clause.
testToSql("create table p partitioned by (int_col) sort by (string_col) as " +
"select double_col, string_col, int_col from functional.alltypes", "default",
"CREATE TABLE default.p PARTITIONED BY ( int_col ) SORT BY LEXICAL " +
"( string_col ) STORED AS TEXTFILE AS SELECT double_col, string_col, int_col " +
"FROM functional.alltypes", true);
// Table with SORT BY LEXICAL clause.
testToSql("create table p partitioned by (int_col) sort by lexical (string_col) as " +
"select double_col, string_col, int_col from functional.alltypes", "default",
"CREATE TABLE default.p PARTITIONED BY ( int_col ) SORT BY LEXICAL " +
"( string_col ) STORED AS TEXTFILE AS SELECT double_col, string_col, int_col " +
"FROM functional.alltypes", true);
// Table with SORT BY ZORDER clause.
BackendConfig.INSTANCE.setZOrderSortUnlocked(true);
testToSql("create table p partitioned by (string_col) sort by zorder (int_col, " +
"bool_col) as select int_col, bool_col, string_col from functional.alltypes",
"default",
"CREATE TABLE default.p PARTITIONED BY ( string_col ) SORT BY ZORDER " +
"( int_col, bool_col ) STORED AS TEXTFILE AS SELECT " +
"int_col, bool_col, string_col FROM functional.alltypes", true);
BackendConfig.INSTANCE.setZOrderSortUnlocked(false);
// Kudu table with multiple partition params
String kuduMasters = catalog_.getDefaultKuduMasterHosts();
testToSql(String.format("create table p primary key (a,b) " +
"partition by hash(a) partitions 3, range (b) (partition value = 1) " +
"stored as kudu tblproperties ('kudu.master_addresses'='%s') as select " +
"int_col a, bigint_col b from functional.alltypes", kuduMasters),
"default",
String.format("CREATE TABLE default.p PRIMARY KEY (a, b) " +
"PARTITION BY HASH (a) PARTITIONS 3, RANGE (b) (PARTITION VALUE = 1) " +
"STORED AS KUDU TBLPROPERTIES ('kudu.master_addresses'='%s', " +
"'storage_handler'='org.apache.hadoop.hive.kudu.KuduStorageHandler') AS SELECT " +
"int_col a, bigint_col b FROM functional.alltypes", kuduMasters), true);
}
@Test
public void TestCreateTableLike() throws AnalysisException {
testToSql("create table p like functional.alltypes", "default",
"CREATE TABLE p LIKE functional.alltypes");
// Table with sort columns.
testToSql("create table p sort by (id) like functional.alltypes", "default",
"CREATE TABLE p SORT BY LEXICAL (id) LIKE functional.alltypes");
// Table with LEXICAL sort columns.
testToSql("create table p sort by LEXICAL (id) like functional.alltypes", "default",
"CREATE TABLE p SORT BY LEXICAL (id) LIKE functional.alltypes");
// Table with ZORDER sort columns.
BackendConfig.INSTANCE.setZOrderSortUnlocked(true);
testToSql("create table p sort by zorder (bool_col, int_col) like " +
"functional.alltypes", "default",
"CREATE TABLE p SORT BY ZORDER (bool_col,int_col) LIKE functional.alltypes");
BackendConfig.INSTANCE.setZOrderSortUnlocked(false);
}
@Test
public void TestCreateTableLikeFile() throws AnalysisException {
testToSql("create table if not exists p like parquet " +
"'/test-warehouse/schemas/alltypestiny.parquet'", "default",
"CREATE TABLE IF NOT EXISTS default.p LIKE PARQUET " +
"'hdfs://localhost:20500/test-warehouse/schemas/alltypestiny.parquet' " +
"STORED AS TEXTFILE", true);
// Table with sort columns.
testToSql("create table if not exists p like parquet " +
"'/test-warehouse/schemas/alltypestiny.parquet' sort by (int_col, id)", "default",
"CREATE TABLE IF NOT EXISTS default.p LIKE PARQUET " +
"'hdfs://localhost:20500/test-warehouse/schemas/alltypestiny.parquet' " +
"SORT BY LEXICAL ( int_col, id ) STORED AS TEXTFILE", true);
// Table with sort LEXICAL columns.
testToSql("create table if not exists p like parquet " +
"'/test-warehouse/schemas/alltypestiny.parquet' sort by lexical (int_col, id)",
"default",
"CREATE TABLE IF NOT EXISTS default.p LIKE PARQUET " +
"'hdfs://localhost:20500/test-warehouse/schemas/alltypestiny.parquet' " +
"SORT BY LEXICAL ( int_col, id ) STORED AS TEXTFILE", true);
// Table with ZORDER sort columns.
BackendConfig.INSTANCE.setZOrderSortUnlocked(true);
testToSql("create table if not exists p like parquet " +
"'/test-warehouse/schemas/alltypestiny.parquet' sort by zorder (int_col, id)",
"default", "CREATE TABLE IF NOT EXISTS default.p LIKE PARQUET " +
"'hdfs://localhost:20500/test-warehouse/schemas/alltypestiny.parquet' " +
"SORT BY ZORDER ( int_col, id ) STORED AS TEXTFILE", true);
BackendConfig.INSTANCE.setZOrderSortUnlocked(false);
}
@Test
public void TestCreateView() throws AnalysisException {
testToSql(
"create view foo_new as select int_col, string_col from functional.alltypes",
"default",
"CREATE VIEW foo_new AS SELECT int_col, string_col FROM functional.alltypes");
testToSql("create view if not exists foo as select * from functional.alltypes",
"default", "CREATE VIEW IF NOT EXISTS foo AS SELECT * FROM functional.alltypes");
testToSql("create view functional.foo (a, b) as select int_col x, double_col y " +
"from functional.alltypes", "default",
"CREATE VIEW functional.foo(a, b) AS SELECT int_col x, double_col y " +
"FROM functional.alltypes");
testToSql("create view foo (aaa, bbb) as select * from functional.complex_view",
"default", "CREATE VIEW foo(aaa, bbb) AS SELECT * FROM functional.complex_view");
testToSql("create view foo as select trim('abc'), 17 * 7", "default",
"CREATE VIEW foo AS SELECT trim('abc'), 17 * 7");
testToSql("create view foo (cnt) as " +
"select count(distinct x.int_col) from functional.alltypessmall x " +
"inner join functional.alltypessmall y on (x.id = y.id) group by x.bigint_col",
"default", "CREATE VIEW foo(cnt) AS "+
"SELECT count(DISTINCT x.int_col) FROM functional.alltypessmall x " +
"INNER JOIN functional.alltypessmall y ON (x.id = y.id) GROUP BY x.bigint_col");
testToSql("create view foo (a, b) as values(1, 'a'), (2, 'b')", "default",
"CREATE VIEW foo(a, b) AS VALUES((1, 'a'), (2, 'b'))");
testToSql("create view foo (a, b) as select 1, 'a' union all select 2, 'b'",
"default", "CREATE VIEW foo(a, b) AS SELECT 1, 'a' UNION ALL SELECT 2, 'b'");
testToSql("create view test_view_with_subquery as " +
"select * from functional.alltypestiny t where exists " +
"(select * from functional.alltypessmall s where s.id = t.id)", "default",
"CREATE VIEW test_view_with_subquery AS " +
"SELECT * FROM functional.alltypestiny t WHERE EXISTS " +
"(SELECT * FROM functional.alltypessmall s WHERE s.id = t.id)");
}
@Test
public void TestAlterView() throws AnalysisException{
testToSql("alter view functional.alltypes_view as " +
"select * from functional.alltypesagg", "default",
"ALTER VIEW functional.alltypes_view AS SELECT * FROM functional.alltypesagg");
testToSql("alter view functional.alltypes_view (a, b) as " +
"select int_col, string_col from functional.alltypes", "default",
"ALTER VIEW functional.alltypes_view(a, b) AS " +
"SELECT int_col, string_col FROM functional.alltypes");
testToSql("alter view functional.alltypes_view (a, b) as " +
"select int_col x, string_col y from functional.alltypes", "default",
"ALTER VIEW functional.alltypes_view(a, b) AS " +
"SELECT int_col x, string_col y FROM functional.alltypes");
testToSql("alter view functional.alltypes_view as select trim('abc'), 17 * 7",
"default", "ALTER VIEW functional.alltypes_view AS SELECT trim('abc'), 17 * 7");
testToSql("alter view functional.alltypes_view (aaa, bbb) as " +
"select * from functional.complex_view", "default",
"ALTER VIEW functional.alltypes_view(aaa, bbb) AS " +
"SELECT * FROM functional.complex_view");
testToSql("alter view functional.complex_view (abc, xyz) as " +
"select year, month from functional.alltypes_view", "default",
"ALTER VIEW functional.complex_view(abc, xyz) AS " +
"SELECT `year`, `month` FROM functional.alltypes_view");
testToSql("alter view functional.alltypes_view (cnt) as " +
"select count(distinct x.int_col) from functional.alltypessmall x " +
"inner join functional.alltypessmall y on (x.id = y.id) group by x.bigint_col",
"default", "ALTER VIEW functional.alltypes_view(cnt) AS "+
"SELECT count(DISTINCT x.int_col) FROM functional.alltypessmall x " +
"INNER JOIN functional.alltypessmall y ON (x.id = y.id) GROUP BY x.bigint_col");
}
@Test
public void TestTableAliases() throws AnalysisException {
String[] tables = new String[] { "alltypes", "alltypes_view" };
String[] columns = new String[] { "int_col", "*" };
testAllTableAliases(tables, columns);
// Unqualified '*' is not ambiguous.
testToSql("select * from functional.alltypes " +
"cross join functional_parquet.alltypes",
"SELECT * FROM functional.alltypes CROSS JOIN functional_parquet.alltypes");
}
@Test
public void TestStructFields() throws AnalysisException {
String[] tables = new String[] { "allcomplextypes", };
String[] columns = new String[] { "id", "int_struct_col.f1",
"nested_struct_col.f2.f12.f21" };
testAllTableAliases(tables, columns);
}
@Test
public void TestCollectionTableRefs() throws AnalysisException {
// Test ARRAY type referenced as a table.
testAllTableAliases(new String[] {
"allcomplextypes.int_array_col"},
new String[] {Path.ARRAY_ITEM_FIELD_NAME, "*"});
testAllTableAliases(new String[]{
"allcomplextypes.struct_array_col"},
new String[]{"f1", "f2", "*"});
// Test MAP type referenced as a table.
testAllTableAliases(new String[] {
"allcomplextypes.int_map_col"},
new String[] {
Path.MAP_KEY_FIELD_NAME,
Path.MAP_VALUE_FIELD_NAME,
"*"});
testAllTableAliases(new String[]{
"allcomplextypes.struct_map_col"},
new String[]{Path.MAP_KEY_FIELD_NAME, "f1", "f2", "*"});
// Test complex table ref path with structs and multiple collections.
testAllTableAliases(new String[]{
"allcomplextypes.complex_nested_struct_col.f2.f12"},
new String[]{Path.MAP_KEY_FIELD_NAME, "f21", "*"});
// Test toSql() of child table refs.
testChildTableRefs("int_array_col", Path.ARRAY_ITEM_FIELD_NAME);
testChildTableRefs("int_map_col", Path.MAP_KEY_FIELD_NAME);
testChildTableRefs("complex_nested_struct_col.f2.f12", "f21");
}
/**
* Tests quoting of identifiers for view compatibility with Hive,
* and for proper quoting of Impala keywords in view-definition stmts.
*/
@Test
public void TestIdentifierQuoting() {
// The quotes of quoted identifiers will be removed if they are unnecessary.
testToSql("select 1 as `abc`, 2.0 as 'xyz'", "SELECT 1 abc, 2.0 xyz");
// These identifiers are lexable by Impala but not Hive. For view compatibility
// we enclose the idents in quotes.
testToSql("select 1 as _c0, 2.0 as $abc", "SELECT 1 `_c0`, 2.0 `$abc`");
// Quoted identifiers that require quoting in both Impala and Hive.
testToSql("select 1 as `???`, 2.0 as '^^^'", "SELECT 1 `???`, 2.0 `^^^`");
// Test quoting of identifiers that are Impala keywords.
testToSql("select `end`.`alter`, `end`.`table` from " +
"(select 1 as `alter`, 2 as `table`) `end`",
"SELECT `end`.`alter`, `end`.`table` FROM " +
"(SELECT 1 `alter`, 2 `table`) `end`");
// Test quoting of inline view aliases.
testToSql("select a from (select 1 as a) as _t",
"SELECT a FROM (SELECT 1 a) `_t`");
// Test quoting of WITH-clause views.
testToSql("with _t as (select 1 as a) select * from _t",
"WITH `_t` AS (SELECT 1 a) SELECT * FROM `_t`");
// Test quoting of non-SlotRef exprs in inline views.
testToSql("select `1 + 10`, `trim('abc')` from (select 1 + 10, trim('abc')) as t",
"SELECT `1 + 10`, `trim('abc')` FROM (SELECT 1 + 10, trim('abc')) t");
}
@Test
public void normalizeStringLiteralTest() {
testToSql("select \"'\"", "SELECT '\\''");
testToSql("select \"\\'\"", "SELECT '\\''");
testToSql("select \"\\\\'\"", "SELECT '\\\\\\''");
testToSql("select '\"'", "SELECT '\"'");
testToSql("select '\\\"'", "SELECT '\"'");
testToSql("select '\\''", "SELECT '\\''");
testToSql("select '\\\\\\''", "SELECT '\\\\\\''");
testToSql("select regexp_replace(string_col, \"\\\\'\", \"'\") from " +
"functional.alltypes", "SELECT regexp_replace(string_col, '\\\\\\'', '\\'') " +
"FROM functional.alltypes");
testToSql("select * from functional.alltypes where '123' = \"123\"",
"SELECT * FROM functional.alltypes WHERE '123' = '123'");
}
// Test the toSql() output of the where clause.
@Test
public void whereTest() {
testToSql("select id from functional.alltypes " +
"where tinyint_col < 40 OR int_col = 4 AND float_col > 1.4",
"SELECT id FROM functional.alltypes " +
"WHERE tinyint_col < 40 OR int_col = 4 AND float_col > 1.4");
testToSql("select id from functional.alltypes where string_col = \"abc\"",
"SELECT id FROM functional.alltypes WHERE string_col = 'abc'");
testToSql("select id from functional.alltypes where string_col = 'abc'",
"SELECT id FROM functional.alltypes WHERE string_col = 'abc'");
testToSql("select id from functional.alltypes " +
"where 5 between smallint_col and int_col",
"SELECT id FROM functional.alltypes WHERE 5 BETWEEN smallint_col AND int_col");
testToSql("select id from functional.alltypes " +
"where 5 not between smallint_col and int_col",
"SELECT id FROM functional.alltypes " +
"WHERE 5 NOT BETWEEN smallint_col AND int_col");
testToSql("select id from functional.alltypes where 5 in (smallint_col, int_col)",
"SELECT id FROM functional.alltypes WHERE 5 IN (smallint_col, int_col)");
testToSql("select id from functional.alltypes " +
"where 5 not in (smallint_col, int_col)",
"SELECT id FROM functional.alltypes WHERE 5 NOT IN (smallint_col, int_col)");
}
// Test the toSql() output of joins in a standalone select block.
@Test
public void joinTest() {
testToSql("select * from functional.alltypes a, functional.alltypes b " +
"where a.id = b.id",
"SELECT * FROM functional.alltypes a, functional.alltypes b WHERE a.id = b.id");
testToSql("select * from functional.alltypes a cross join functional.alltypes b",
"SELECT * FROM functional.alltypes a CROSS JOIN functional.alltypes b");
runTestTemplate("select * from functional.alltypes a %s functional.alltypes b %s",
"SELECT * FROM functional.alltypes a %s functional.alltypes b %s",
joinTypes_, joinConditions_);
}
private void planHintsTestForInsertAndUpsert(String prefix, String suffix) {
for (InsertStmt.HintLocation loc: InsertStmt.HintLocation.values()) {
// Insert hint.
testToSql(InjectInsertHint(
"insert%s into functional.alltypes(int_col, bool_col) " +
"partition(year, month)%s" +
"select int_col, bool_col, year, month from functional.alltypes",
String.format(" %snoshuffle%s", prefix, suffix), loc),
InjectInsertHint("INSERT%s INTO TABLE functional.alltypes(int_col, " +
"bool_col) PARTITION (`year`, `month`)%s " +
"SELECT int_col, bool_col, `year`, `month` FROM functional.alltypes",
" \n-- +noshuffle\n", loc));
testToSql(InjectInsertHint(
"insert%s into functional.alltypes(int_col, bool_col) " +
"partition(year, month)%s" +
"select int_col, bool_col, year, month from functional.alltypes",
String.format(" %sshuffle,clustered%s", prefix, suffix), loc),
InjectInsertHint("INSERT%s INTO TABLE functional.alltypes(int_col, " +
"bool_col) PARTITION (`year`, `month`)%s " +
"SELECT int_col, bool_col, `year`, `month` FROM functional.alltypes",
" \n-- +shuffle,clustered\n", loc));
// Upsert hint.
testToSql(InjectInsertHint(
"upsert%s into functional_kudu.alltypes(id, int_col)%s" +
"select id, int_col from functional_kudu.alltypes",
String.format(" %snoshuffle%s", prefix, suffix), loc),
InjectInsertHint("UPSERT%s INTO TABLE functional_kudu.alltypes(id, int_col)" +
"%s SELECT id, int_col FROM functional_kudu.alltypes",
" \n-- +noshuffle\n", loc));
testToSql(InjectInsertHint(
"upsert%s into functional_kudu.alltypes(id, int_col)%s" +
"select id, int_col from functional_kudu.alltypes",
String.format(" %sshuffle,clustered%s", prefix, suffix), loc),
InjectInsertHint("UPSERT%s INTO TABLE functional_kudu.alltypes(id, int_col)" +
"%s SELECT id, int_col FROM functional_kudu.alltypes",
" \n-- +shuffle,clustered\n", loc));
}
}
/**
* Tests that the toSql() of plan hints use the end-of-line commented hint style
* (for view compatibility with Hive) regardless of what style was used in the
* original query.
*/
@Test
public void planHintsTest() {
for (String[] hintStyle: hintStyles_) {
String prefix = hintStyle[0];
String suffix = hintStyle[1];
// Hint in Insert/Upsert.
planHintsTestForInsertAndUpsert(prefix, suffix);
// Join hint.
testToSql(String.format(
"select * from functional.alltypes a join %sbroadcast%s " +
"functional.alltypes b on a.id = b.id", prefix, suffix),
"SELECT * FROM functional.alltypes a INNER JOIN \n-- +broadcast\n " +
"functional.alltypes b ON a.id = b.id");
// Table hint
testToSql(String.format(
"select * from functional.alltypes atp %sschedule_random_replica%s", prefix,
suffix),
"SELECT * FROM functional.alltypes atp\n-- +schedule_random_replica\n");
testToSql(String.format(
"select * from functional.alltypes %sschedule_random_replica%s", prefix,
suffix),
"SELECT * FROM functional.alltypes\n-- +schedule_random_replica\n");
testToSql(String.format(
"select * from functional.alltypes %sschedule_random_replica," +
"schedule_disk_local%s", prefix, suffix),
"SELECT * FROM functional.alltypes\n-- +schedule_random_replica," +
"schedule_disk_local\n");
testToSql(String.format(
"select c1 from (select atp.tinyint_col as c1 from functional.alltypes atp " +
"%sschedule_random_replica%s) s1", prefix, suffix),
"SELECT c1 FROM (SELECT atp.tinyint_col c1 FROM functional.alltypes atp\n-- +" +
"schedule_random_replica\n) s1");
// Select-list hint. The legacy-style hint has no prefix and suffix.
if (prefix.contains("[")) {
prefix = "";
suffix = "";
}
// Comment-style select-list plan hint.
testToSql(String.format(
"select %sstraight_join%s * from functional.alltypes", prefix, suffix),
"SELECT \n-- +straight_join\n * FROM functional.alltypes");
testToSql(
String.format("select distinct %sstraight_join%s * from functional.alltypes",
prefix, suffix),
"SELECT DISTINCT \n-- +straight_join\n * FROM functional.alltypes");
// Tests for analyzed/rewritten sql.
// First test the test by passing ToSqlOptions.DEFAULT which should result in the
// hints appearing with '--' style comments.
testToSql(
String.format("select distinct %sstraight_join%s * from functional.alltypes",
prefix, suffix),
"SELECT DISTINCT \n-- +straight_join\n * FROM functional.alltypes",
ToSqlOptions.DEFAULT);
// Test that analyzed queries use the '/*' style comments.
testToSql(
String.format("select distinct %sstraight_join%s * from functional.alltypes "
+ "where bool_col = false and id <= 5 and id >= 2",
prefix, suffix),
"SELECT DISTINCT /* +straight_join */ * FROM functional.alltypes "
+ "WHERE bool_col = FALSE AND id <= 5 AND id >= 2",
ToSqlOptions.REWRITTEN);
}
}
// Test the toSql() output of aggregate and group by expressions.
@Test
public void aggregationTest() {
testToSql("select COUNT(*), count(id), COUNT(id), SUM(id), AVG(id) " +
"from functional.alltypes group by tinyint_col",
"SELECT count(*), count(id), count(id), sum(id), avg(id) " +
"FROM functional.alltypes GROUP BY tinyint_col");
testToSql("select avg(float_col / id) from functional.alltypes group by tinyint_col",
"SELECT avg(float_col / id) " +
"FROM functional.alltypes GROUP BY tinyint_col");
testToSql("select avg(double_col) from functional.alltypes " +
"group by int_col, tinyint_col, bigint_col",
"SELECT avg(double_col) FROM functional.alltypes " +
"GROUP BY int_col, tinyint_col, bigint_col");
// Group by with having clause
testToSql("select avg(id) from functional.alltypes " +
"group by tinyint_col having count(tinyint_col) > 10",
"SELECT avg(id) FROM functional.alltypes " +
"GROUP BY tinyint_col HAVING count(tinyint_col) > 10");
testToSql("select sum(id) from functional.alltypes group by tinyint_col " +
"having avg(tinyint_col) > 10 AND count(tinyint_col) > 5",
"SELECT sum(id) FROM functional.alltypes GROUP BY tinyint_col " +
"HAVING avg(tinyint_col) > 10 AND count(tinyint_col) > 5");
}
// Test the toSql() output of the order by clause.
@Test
public void orderByTest() {
testToSql("select id, string_col from functional.alltypes " +
"order by string_col ASC, float_col DESC, int_col ASC",
"SELECT id, string_col FROM functional.alltypes " +
"ORDER BY string_col ASC, float_col DESC, int_col ASC");
testToSql("select id, string_col from functional.alltypes " +
"order by string_col DESC, float_col ASC, int_col DESC",
"SELECT id, string_col FROM functional.alltypes " +
"ORDER BY string_col DESC, float_col ASC, int_col DESC");
testToSql("select id, string_col from functional.alltypes " +
"order by string_col ASC NULLS FIRST, float_col DESC NULLS LAST, " +
"int_col DESC",
"SELECT id, string_col FROM functional.alltypes " +
"ORDER BY string_col ASC NULLS FIRST, float_col DESC NULLS LAST, " +
"int_col DESC");
// Test limit/offset
testToSql("select id, string_col from functional.alltypes " +
"order by string_col ASC NULLS FIRST, float_col DESC NULLS LAST, " +
"int_col DESC LIMIT 10 OFFSET 5",
"SELECT id, string_col FROM functional.alltypes " +
"ORDER BY string_col ASC NULLS FIRST, float_col DESC NULLS LAST, " +
"int_col DESC LIMIT 10 OFFSET 5");
// Offset shouldn't be printed if it's not necessary
testToSql("select id, string_col from functional.alltypes " +
"order by string_col ASC NULLS FIRST, float_col DESC NULLS LAST, " +
"int_col DESC LIMIT 10 OFFSET 0",
"SELECT id, string_col FROM functional.alltypes " +
"ORDER BY string_col ASC NULLS FIRST, float_col DESC NULLS LAST, " +
"int_col DESC LIMIT 10");
// Check we do not print NULLS FIRST/LAST unless necessary
testToSql("select id, string_col from functional.alltypes " +
"order by string_col DESC NULLS FIRST, float_col ASC NULLS LAST, " +
"int_col DESC",
"SELECT id, string_col FROM functional.alltypes " +
"ORDER BY string_col DESC, float_col ASC, " +
"int_col DESC");
}
// Test the toSql() output of queries with all clauses.
@Test
public void allTest() {
testToSql("select bigint_col, avg(double_col), sum(tinyint_col) " +
"from functional.alltypes " +
"where double_col > 2.5 AND string_col != \"abc\"" +
"group by bigint_col, int_col " +
"having count(int_col) > 10 OR sum(bigint_col) > 20 " +
"order by 2 DESC NULLS LAST, 3 ASC",
"SELECT bigint_col, avg(double_col), sum(tinyint_col) " +
"FROM functional.alltypes " +
"WHERE double_col > 2.5 AND string_col != 'abc' " +
"GROUP BY bigint_col, int_col " +
"HAVING count(int_col) > 10 OR sum(bigint_col) > 20 " +
"ORDER BY 2 DESC NULLS LAST, 3 ASC");
}
@Test
public void unionTest() {
testToSql("select bool_col, rank() over(order by id) from functional.alltypes " +
"union select bool_col, int_col from functional.alltypessmall " +
"union select bool_col, bigint_col from functional.alltypes",
"SELECT bool_col, rank() OVER (ORDER BY id ASC) FROM functional.alltypes " +
"UNION SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION SELECT bool_col, bigint_col FROM functional.alltypes");
testToSql("select bool_col, int_col from functional.alltypes " +
"union all select bool_col, int_col from functional.alltypessmall " +
"union all select bool_col, int_col from functional.alltypessmall " +
"union all select bool_col, int_col from functional.alltypessmall " +
"union all select bool_col, bigint_col from functional.alltypes",
"SELECT bool_col, int_col FROM functional.alltypes " +
"UNION ALL SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION ALL SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION ALL SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION ALL SELECT bool_col, bigint_col FROM functional.alltypes");
// With 'order by' and 'limit' on union, and also on last select.
testToSql("(select bool_col, int_col from functional.alltypes) " +
"union all (select bool_col, int_col from functional.alltypessmall) " +
"union all (select bool_col, bigint_col " +
"from functional.alltypes order by 1 nulls first limit 1) " +
"order by int_col nulls first, bool_col limit 5 + 5",
"SELECT bool_col, int_col FROM functional.alltypes " +
"UNION ALL SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION ALL SELECT bool_col, bigint_col " +
"FROM functional.alltypes ORDER BY 1 ASC NULLS FIRST LIMIT 1 " +
"ORDER BY int_col ASC NULLS FIRST, bool_col ASC LIMIT 5 + 5");
// With 'order by' and 'limit' on union but not on last select.
testToSql("select bool_col, int_col from functional.alltypes " +
"union all select bool_col, int_col from functional.alltypessmall " +
"union all (select bool_col, bigint_col from functional.alltypes) " +
"order by int_col nulls first, bool_col limit 10",
"SELECT bool_col, int_col FROM functional.alltypes " +
"UNION ALL SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION ALL (SELECT bool_col, bigint_col FROM functional.alltypes) " +
"ORDER BY int_col ASC NULLS FIRST, bool_col ASC LIMIT 10");
// Nested unions require parenthesis.
testToSql("select bool_col, int_col from functional.alltypes " +
"union all (select bool_col, int_col from functional.alltypessmall " +
"union distinct (select bool_col, bigint_col from functional.alltypes)) " +
"order by int_col, bool_col limit 10",
"SELECT bool_col, int_col FROM functional.alltypes UNION ALL " +
"(SELECT bool_col, int_col FROM functional.alltypessmall " +
"UNION SELECT bool_col, bigint_col FROM functional.alltypes) " +
"ORDER BY int_col ASC, bool_col ASC LIMIT 10");
}
@Test
public void valuesTest() {
testToSql("values(1, 'a', 1.0)", "VALUES(1, 'a', 1.0)");
testToSql("values(1 as x, 'a' y, 1.0 as z)", "VALUES(1 x, 'a' y, 1.0 z)");
testToSql("values(1, 'a'), (2, 'b'), (3, 'c')",
"VALUES((1, 'a'), (2, 'b'), (3, 'c'))");
testToSql("values(1 x, 'a' as y), (2 as y, 'b'), (3, 'c' x)",
"VALUES((1 x, 'a' y), (2 y, 'b'), (3, 'c' x))");
testToSql("select * from (values(1, 'a'), (2, 'b')) as t",
"SELECT * FROM (VALUES((1, 'a'), (2, 'b'))) t");
testToSql("values(1, 'a'), (2, 'b') union all values(3, 'c')",
"VALUES((1, 'a'), (2, 'b')) UNION ALL (VALUES(3, 'c'))");
testToSql("insert into table functional.alltypessmall " +
"partition (`year`=2009, `month`=4) " +
"values(1, true, 1, 1, 10, 10, 10.0, 10.0, 'a', 'a', cast (0 as timestamp))",
"INSERT INTO TABLE functional.alltypessmall PARTITION (`year`=2009, `month`=4) " +
"VALUES(1, TRUE, 1, 1, 10, 10, 10.0, 10.0, 'a', 'a', CAST(0 AS TIMESTAMP))");
testToSql("insert into table functional.date_tbl " +
"partition (date_part='9999-12-31') " +
"values(112, DATE '1970-01-01')",
"INSERT INTO TABLE functional.date_tbl " +
"PARTITION (date_part='9999-12-31') " +
"VALUES(112, DATE '1970-01-01')");
testToSql("upsert into table functional_kudu.testtbl values(1, 'a', 1)",
"UPSERT INTO TABLE functional_kudu.testtbl VALUES(1, 'a', 1)");
}
/**
* Tests that toSql() properly handles inline views and their expression substitutions.
*/
@Test
public void inlineViewTest() {
// Test joins in an inline view.
testToSql("select t.* from " +
"(select a.* from functional.alltypes a, functional.alltypes b " +
"where a.id = b.id) t",
"SELECT t.* FROM " +
"(SELECT a.* FROM functional.alltypes a, functional.alltypes b " +
"WHERE a.id = b.id) t");
testToSql("select t.* from (select a.* from functional.alltypes a " +
"cross join functional.alltypes b) t",
"SELECT t.* FROM (SELECT a.* FROM functional.alltypes a " +
"CROSS JOIN functional.alltypes b) t");
runTestTemplate("select t.* from (select a.* from functional.alltypes a %s " +
"functional.alltypes b %s) t",
"SELECT t.* FROM (SELECT a.* FROM functional.alltypes a %s " +
"functional.alltypes b %s) t", nonSemiJoinTypes_, joinConditions_);
runTestTemplate("select t.* from (select a.* from functional.alltypes a %s " +
"functional.alltypes b %s) t",
"SELECT t.* FROM (SELECT a.* FROM functional.alltypes a %s " +
"functional.alltypes b %s) t", leftSemiJoinTypes_, joinConditions_);
runTestTemplate("select t.* from (select b.* from functional.alltypes a %s " +
"functional.alltypes b %s) t",
"SELECT t.* FROM (SELECT b.* FROM functional.alltypes a %s " +
"functional.alltypes b %s) t", rightSemiJoinTypes_, joinConditions_);
// Test undoing expr substitution in select-list exprs and on clause.
testToSql("select t1.int_col, t2.int_col from " +
"(select int_col, rank() over (order by int_col) from functional.alltypes) " +
"t1 inner join " +
"(select int_col from functional.alltypes) t2 on (t1.int_col = t2.int_col)",
"SELECT t1.int_col, t2.int_col FROM " +
"(SELECT int_col, rank() OVER (ORDER BY int_col ASC) " +
"FROM functional.alltypes) t1 INNER JOIN " +
"(SELECT int_col FROM functional.alltypes) t2 ON (t1.int_col = t2.int_col)");
// Test undoing expr substitution in aggregates and group by and having clause.
testToSql("select count(t1.string_col), sum(t2.float_col) from " +
"(select id, string_col from functional.alltypes) t1 inner join " +
"(select id, float_col from functional.alltypes) t2 on (t1.id = t2.id) " +
"group by t1.id, t2.id having count(t2.float_col) > 2",
"SELECT count(t1.string_col), sum(t2.float_col) FROM " +
"(SELECT id, string_col FROM functional.alltypes) t1 INNER JOIN " +
"(SELECT id, float_col FROM functional.alltypes) t2 ON (t1.id = t2.id) " +
"GROUP BY t1.id, t2.id HAVING count(t2.float_col) > 2");
// Test undoing expr substitution in order by clause.
testToSql("select t1.id, t2.id from " +
"(select id, string_col from functional.alltypes) t1 inner join " +
"(select id, float_col from functional.alltypes) t2 on (t1.id = t2.id) " +
"order by t1.id, t2.id nulls first",
"SELECT t1.id, t2.id FROM " +
"(SELECT id, string_col FROM functional.alltypes) t1 INNER JOIN " +
"(SELECT id, float_col FROM functional.alltypes) t2 ON (t1.id = t2.id) " +
"ORDER BY t1.id ASC, t2.id ASC NULLS FIRST");
// Test undoing expr substitution in where-clause conjuncts.
testToSql("select t1.id, t2.id from " +
"(select id, string_col from functional.alltypes) t1, " +
"(select id, float_col from functional.alltypes) t2 " +
"where t1.id = t2.id and t1.string_col = 'abc' and t2.float_col < 10",
"SELECT t1.id, t2.id FROM " +
"(SELECT id, string_col FROM functional.alltypes) t1, " +
"(SELECT id, float_col FROM functional.alltypes) t2 " +
"WHERE t1.id = t2.id AND t1.string_col = 'abc' AND t2.float_col < 10");
// Test inline views with correlated table refs. Implicit alias only.
testToSql(
"select cnt from functional.allcomplextypes t, " +
"(select count(*) cnt from t.int_array_col) v",
"SELECT cnt FROM functional.allcomplextypes t, " +
"(SELECT count(*) cnt FROM t.int_array_col) v");
// Multiple correlated table refs. Explicit aliases.
testToSql(
"select avg from functional.allcomplextypes t, " +
"(select avg(a1.item) avg from t.int_array_col a1, t.int_array_col a2) v",
"SELECT avg FROM functional.allcomplextypes t, " +
"(SELECT avg(a1.item) avg FROM t.int_array_col a1, t.int_array_col a2) v");
// Correlated table ref has child ref itself. Mix of explicit and implicit aliases.
testToSql(
"select key, item from functional.allcomplextypes t, " +
"(select a1.key, value.item from t.array_map_col a1, a1.value) v",
"SELECT `key`, item FROM functional.allcomplextypes t, " +
"(SELECT a1.`key`, value.item FROM t.array_map_col a1, a1.value) v");
// Correlated table refs in a union.
testToSql(
"select item from functional.allcomplextypes t, " +
"(select * from t.int_array_col union all select * from t.int_array_col) v",
"SELECT item FROM functional.allcomplextypes t, " +
"(SELECT * FROM t.int_array_col UNION ALL SELECT * FROM t.int_array_col) v");
// Correlated inline view in WITH-clause.
testToSql(
"with w as (select c from functional.allcomplextypes t, " +
"(select count(a1.key) c from t.array_map_col a1) v1) " +
"select * from w",
"WITH w AS (SELECT c FROM functional.allcomplextypes t, " +
"(SELECT count(a1.`key`) c FROM t.array_map_col a1) v1) " +
"SELECT * FROM w");
}
@Test
public void TestUpdate() {
TestUtils.assumeKuduIsSupported();
testToSql("update functional_kudu.dimtbl set name = '10' where name < '11'",
"UPDATE functional_kudu.dimtbl SET name = '10' FROM functional_kudu.dimtbl " +
"WHERE name < '11'");
testToSql(
"update functional_kudu.dimtbl set name = '10', zip=cast(99 as int) where name " +
"< '11'",
"UPDATE functional_kudu.dimtbl SET name = '10', zip = CAST(99 AS INT) FROM " +
"functional_kudu.dimtbl WHERE name < '11'");
testToSql("update a set name = '10' FROM functional_kudu.dimtbl a",
"UPDATE a SET name = '10' FROM functional_kudu.dimtbl a");
testToSql(
"update a set a.name = 'oskar' from functional_kudu.dimtbl a join functional" +
".alltypes b on a.id = b.id where zip > 94549",
"UPDATE a SET a.name = 'oskar' FROM functional_kudu.dimtbl a INNER JOIN " +
"functional.alltypes b ON a.id = b.id WHERE zip > 94549");
}
@Test
public void TestDelete() {
TestUtils.assumeKuduIsSupported();
testToSql("delete functional_kudu.testtbl where zip = 10",
"DELETE FROM functional_kudu.testtbl WHERE zip = 10");
testToSql("delete from functional_kudu.testtbl where zip = 10",
"DELETE FROM functional_kudu.testtbl WHERE zip = 10");
testToSql("delete a from functional_kudu.testtbl a where zip = 10",
"DELETE a FROM functional_kudu.testtbl a WHERE zip = 10");
}
/**
* Tests that toSql() properly handles subqueries in the where clause.
*/
@Test
public void subqueryTest() {
// Nested predicates
testToSql("select * from functional.alltypes where id in " +
"(select id from functional.alltypestiny)",
"SELECT * FROM functional.alltypes WHERE id IN " +
"(SELECT id FROM functional.alltypestiny)");
testToSql("select * from functional.alltypes where id not in " +
"(select id from functional.alltypestiny)",
"SELECT * FROM functional.alltypes WHERE id NOT IN " +
"(SELECT id FROM functional.alltypestiny)");
testToSql("select * from functional.alltypes where bigint_col = " +
"(select count(*) from functional.alltypestiny)",
"SELECT * FROM functional.alltypes WHERE bigint_col = " +
"(SELECT count(*) FROM functional.alltypestiny)");
testToSql("select * from functional.alltypes where exists " +
"(select * from functional.alltypestiny)",
"SELECT * FROM functional.alltypes WHERE EXISTS " +
"(SELECT * FROM functional.alltypestiny)");
testToSql("select * from functional.alltypes where not exists " +
"(select * from functional.alltypestiny)",
"SELECT * FROM functional.alltypes WHERE NOT EXISTS " +
"(SELECT * FROM functional.alltypestiny)");
// Multiple nesting levels
testToSql("select * from functional.alltypes where id in " +
"(select id from functional.alltypestiny where int_col = " +
"(select avg(int_col) from functional.alltypesagg))",
"SELECT * FROM functional.alltypes WHERE id IN " +
"(SELECT id FROM functional.alltypestiny WHERE int_col = " +
"(SELECT avg(int_col) FROM functional.alltypesagg))");
// Inline view with a subquery
testToSql("select * from (select id from functional.alltypes where " +
"int_col in (select int_col from functional.alltypestiny)) t where " +
"t.id < 10",
"SELECT * FROM (SELECT id FROM functional.alltypes WHERE " +
"int_col IN (SELECT int_col FROM functional.alltypestiny)) t WHERE " +
"t.id < 10");
// Subquery in a WITH clause
testToSql("with t as (select * from functional.alltypes where id in " +
"(select id from functional.alltypestiny)) select * from t",
"WITH t AS (SELECT * FROM functional.alltypes WHERE id IN " +
"(SELECT id FROM functional.alltypestiny)) SELECT * FROM t");
testToSql("with t as (select * from functional.alltypes s where id in " +
"(select id from functional.alltypestiny t where s.id = t.id)) " +
"select * from t t1, t t2 where t1.id = t2.id",
"WITH t AS (SELECT * FROM functional.alltypes s WHERE id IN " +
"(SELECT id FROM functional.alltypestiny t WHERE s.id = t.id)) " +
"SELECT * FROM t t1, t t2 WHERE t1.id = t2.id");
}
@Test
public void withClauseTest() {
// WITH clause in select stmt.
testToSql("with t as (select * from functional.alltypes) select * from t",
"WITH t AS (SELECT * FROM functional.alltypes) SELECT * FROM t");
testToSql("with t(c1) as (select * from functional.alltypes) select * from t",
"WITH t(c1) AS (SELECT * FROM functional.alltypes) SELECT * FROM t");
testToSql("with t(`table`, col, `create`) as (select * from functional.alltypes) " +
"select * from t",
"WITH t(`table`, col, `create`) AS (SELECT * FROM functional.alltypes) " +
"SELECT * FROM t");
testToSql("with t(c1, c2) as (select * from functional.alltypes) select * from t",
"WITH t(c1, c2) AS (SELECT * FROM functional.alltypes) SELECT * FROM t");
testToSql("with t as (select sum(int_col) over(partition by tinyint_col, " +
"bool_col order by float_col rows between unbounded preceding and " +
"current row) as x from functional.alltypes) " +
"select t1.x, t2.x from t t1 join t t2 on (t1.x = t2.x)",
"WITH t AS (SELECT sum(int_col) OVER (PARTITION BY tinyint_col, bool_col " +
"ORDER BY float_col ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) " +
"x FROM functional.alltypes) SELECT t1.x, t2.x FROM t t1 INNER JOIN t t2 ON " +
"(t1.x = t2.x)");
// WITH clause in select stmt with a join and an ON clause.
testToSql("with t as (select * from functional.alltypes) " +
"select * from t a inner join t b on (a.int_col = b.int_col)",
"WITH t AS (SELECT * FROM functional.alltypes) " +
"SELECT * FROM t a INNER JOIN t b ON (a.int_col = b.int_col)");
testToSql("with t(c1, c2) as (select * from functional.alltypes) " +
"select a.c1, a.c2 from t a inner join t b on (a.c1 = b.c2)",
"WITH t(c1, c2) AS (SELECT * FROM functional.alltypes) " +
"SELECT a.c1, a.c2 FROM t a INNER JOIN t b ON (a.c1 = b.c2)");
// WITH clause in select stmt with a join and a USING clause.
testToSql("with t as (select * from functional.alltypes) " +
"select * from t a inner join t b using(int_col)",
"WITH t AS (SELECT * FROM functional.alltypes) " +
"SELECT * FROM t a INNER JOIN t b USING (int_col)");
// WITH clause in a union stmt.
testToSql("with t1 as (select * from functional.alltypes)" +
"select * from t1 union all select * from t1",
"WITH t1 AS (SELECT * FROM functional.alltypes) " +
"SELECT * FROM t1 UNION ALL SELECT * FROM t1");
// WITH clause in values stmt.
testToSql("with t1 as (select * from functional.alltypes) values(1, 2), (3, 4)",
"WITH t1 AS (SELECT * FROM functional.alltypes) VALUES((1, 2), (3, 4))");
// WITH clause in insert stmt.
testToSql("with t1 as (select * from functional.alltypes) " +
"insert into functional.alltypes partition(year, month) select * from t1",
"WITH t1 AS (SELECT * FROM functional.alltypes) " +
"INSERT INTO TABLE functional.alltypes PARTITION (`year`, `month`) " +
"SELECT * FROM t1");
// WITH clause in upsert stmt.
testToSql("with t1 as (select * from functional.alltypes) upsert into " +
"functional_kudu.testtbl select bigint_col, string_col, int_col from t1",
"WITH t1 AS (SELECT * FROM functional.alltypes) UPSERT INTO TABLE " +
"functional_kudu.testtbl SELECT bigint_col, string_col, int_col FROM t1");
// Test joins in WITH-clause view.
testToSql("with t as (select a.* from functional.alltypes a, " +
"functional.alltypes b where a.id = b.id) select * from t",
"WITH t AS (SELECT a.* FROM functional.alltypes a, " +
"functional.alltypes b WHERE a.id = b.id) SELECT * FROM t");
testToSql("with t as (select a.* from functional.alltypes a " +
"cross join functional.alltypes b) select * from t",
"WITH t AS (SELECT a.* FROM functional.alltypes a " +
"CROSS JOIN functional.alltypes b) SELECT * FROM t");
runTestTemplate("with t as (select a.* from functional.alltypes a %s " +
"functional.alltypes b %s) select * from t",
"WITH t AS (SELECT a.* FROM functional.alltypes a %s " +
"functional.alltypes b %s) SELECT * FROM t", nonSemiJoinTypes_, joinConditions_);
runTestTemplate("with t as (select a.* from functional.alltypes a %s " +
"functional.alltypes b %s) select * from t",
"WITH t AS (SELECT a.* FROM functional.alltypes a %s " +
"functional.alltypes b %s) SELECT * FROM t",
leftSemiJoinTypes_, joinConditions_);
runTestTemplate("with t as (select b.* from functional.alltypes a %s " +
"functional.alltypes b %s) select * from t",
"WITH t AS (SELECT b.* FROM functional.alltypes a %s " +
"functional.alltypes b %s) SELECT * FROM t",
rightSemiJoinTypes_, joinConditions_);
// WITH clause in complex query with joins and and order by + limit.
testToSql("with t as (select int_col x, bigint_col y from functional.alltypestiny " +
"order by id nulls first limit 2) " +
"select * from t t1 left outer join t t2 on t1.y = t2.x " +
"full outer join t t3 on t2.y = t3.x order by t1.x nulls first limit 5 * 2",
"WITH t AS (SELECT int_col x, bigint_col y FROM functional.alltypestiny " +
"ORDER BY id ASC NULLS FIRST LIMIT 2) " +
"SELECT * FROM t t1 LEFT OUTER JOIN t t2 ON t1.y = t2.x " +
"FULL OUTER JOIN t t3 ON t2.y = t3.x ORDER BY t1.x ASC NULLS FIRST LIMIT 5 * 2");
}
// Test the toSql() output of insert queries.
@Test
public void insertTest() {
// Insert into unpartitioned table without partition clause.
testToSql("insert into table functional.alltypesnopart " +
"select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, " +
"float_col, double_col, date_string_col, string_col, timestamp_col " +
"from functional.alltypes",
"INSERT INTO TABLE functional.alltypesnopart " +
"SELECT id, bool_col, tinyint_col, " +
"smallint_col, int_col, bigint_col, float_col, double_col, date_string_col, " +
"string_col, timestamp_col FROM functional.alltypes");
// Insert into overwrite unpartitioned table without partition clause.
testToSql("insert overwrite table functional.alltypesnopart " +
"select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, " +
"float_col, double_col, date_string_col, string_col, timestamp_col " +
"from functional.alltypes",
"INSERT OVERWRITE TABLE functional.alltypesnopart " +
"SELECT id, bool_col, tinyint_col, " +
"smallint_col, int_col, bigint_col, float_col, double_col, date_string_col, " +
"string_col, timestamp_col FROM functional.alltypes");
// Static partition.
testToSql("insert into table functional.alltypessmall " +
"partition (year=2009, month=4)" +
"select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, " +
"float_col, double_col, date_string_col, string_col, timestamp_col " +
"from functional.alltypes",
"INSERT INTO TABLE functional.alltypessmall " +
"PARTITION (`year`=2009, `month`=4) SELECT id, " +
"bool_col, tinyint_col, smallint_col, int_col, bigint_col, float_col, " +
"double_col, date_string_col, string_col, timestamp_col " +
"FROM functional.alltypes");
testToSql("insert into table functional.date_tbl " +
"partition (date_part='2009-10-30') " +
"select id, cast(timestamp_col as date) from functional.alltypes",
"INSERT INTO TABLE functional.date_tbl " +
"PARTITION (date_part='2009-10-30') " +
"SELECT id, CAST(timestamp_col AS DATE) FROM functional.alltypes");
// Fully dynamic partitions.
testToSql("insert into table functional.alltypessmall " +
"partition (year, month)" +
"select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, " +
"float_col, double_col, date_string_col, string_col, timestamp_col, year, " +
"month from functional.alltypes",
"INSERT INTO TABLE functional.alltypessmall " +
"PARTITION (`year`, `month`) SELECT id, bool_col, " +
"tinyint_col, smallint_col, int_col, bigint_col, float_col, double_col, " +
"date_string_col, string_col, timestamp_col, `year`, `month` " +
"FROM functional.alltypes");
testToSql("insert into table functional.date_tbl " +
"partition (date_part) " +
"select id, cast(timestamp_col as date) date_col, " +
"cast(timestamp_col as date) date_part " +
"from functional.alltypes",
"INSERT INTO TABLE functional.date_tbl " +
"PARTITION (date_part) " +
"SELECT id, CAST(timestamp_col AS DATE) date_col, " +
"CAST(timestamp_col AS DATE) date_part " +
"FROM functional.alltypes");
// Partially dynamic partitions.
testToSql("insert into table functional.alltypessmall " +
"partition (year=2009, month)" +
"select id, bool_col, tinyint_col, smallint_col, int_col, bigint_col, " +
"float_col, double_col, date_string_col, string_col, timestamp_col, month " +
"from functional.alltypes",
"INSERT INTO TABLE functional.alltypessmall " +
"PARTITION (`year`=2009, `month`) SELECT id, " +
"bool_col, tinyint_col, smallint_col, int_col, bigint_col, float_col, " +
"double_col, date_string_col, string_col, timestamp_col, `month` " +
"FROM functional.alltypes");
// Permutations
testToSql("insert into table functional.alltypesnopart(id, bool_col, tinyint_col) " +
" values(1, true, 0)",
"INSERT INTO TABLE functional.alltypesnopart(id, bool_col, tinyint_col) " +
"VALUES(1, TRUE, 0)");
// Permutations that mention partition column
testToSql("insert into table functional.alltypes(id, year, month) " +
" values(1, 1990, 12)",
"INSERT INTO TABLE functional.alltypes(id, `year`, `month`) " +
"VALUES(1, 1990, 12)");
// Empty permutation with no select statement
testToSql("insert into table functional.alltypesnopart()",
"INSERT INTO TABLE functional.alltypesnopart()");
// Permutation and explicit partition clause
testToSql("insert into table functional.alltypes(id) " +
" partition (year=2009, month) values(1, 12)",
"INSERT INTO TABLE functional.alltypes(id) " +
"PARTITION (`year`=2009, `month`) VALUES(1, 12)");
}
@Test
public void upsertTest() {
// VALUES clause
testToSql("upsert into functional_kudu.testtbl values (1, 'a', 1)",
"UPSERT INTO TABLE functional_kudu.testtbl VALUES(1, 'a', 1)");
// SELECT clause
testToSql("upsert into functional_kudu.testtbl select bigint_col, string_col, " +
"int_col from functional.alltypes", "UPSERT INTO TABLE functional_kudu.testtbl " +
"SELECT bigint_col, string_col, int_col FROM functional.alltypes");
// WITH clause
testToSql("with x as (select bigint_col, string_col, int_col from " +
"functional.alltypes) upsert into table functional_kudu.testtbl select * from x",
"WITH x AS (SELECT bigint_col, string_col, int_col FROM functional.alltypes) " +
"UPSERT INTO TABLE functional_kudu.testtbl SELECT * FROM x");
// Permutation
testToSql("upsert into table functional_kudu.testtbl (zip, id, name) values " +
"(1, 1, 'a')", "UPSERT INTO TABLE functional_kudu.testtbl(zip, id, name) " +
"VALUES(1, 1, 'a')");
}
@Test
public void alterTableAddPartitionTest() {
// Add partition
testToSql(
"alter table functional.alltypes add partition (year=2050, month=1)",
"ALTER TABLE functional.alltypes ADD PARTITION (year=2050, month=1)");
// Add multiple partitions
testToSql(
"alter table functional.alltypes add partition (year=2050, month=1) " +
"partition (year=2050, month=2)",
"ALTER TABLE functional.alltypes ADD PARTITION (year=2050, month=1) " +
"PARTITION (year=2050, month=2)");
// with IF NOT EXISTS
testToSql(
"alter table functional.alltypes add if not exists " +
"partition (year=2050, month=1) " +
"partition (year=2050, month=2)",
"ALTER TABLE functional.alltypes ADD IF NOT EXISTS " +
"PARTITION (year=2050, month=1) " +
"PARTITION (year=2050, month=2)");
// with location
testToSql(
"alter table functional.alltypes add if not exists " +
"partition (year=2050, month=1) location 'hdfs://localhost:20500/y2050m1' " +
"partition (year=2050, month=2) location '/y2050m2'",
"ALTER TABLE functional.alltypes ADD IF NOT EXISTS "+
"PARTITION (year=2050, month=1) LOCATION 'hdfs://localhost:20500/y2050m1' " +
"PARTITION (year=2050, month=2) LOCATION 'hdfs://localhost:20500/y2050m2'");
// and caching
testToSql(
"alter table functional.alltypes add if not exists " +
"partition (year=2050, month=1) location 'hdfs://localhost:20500/y2050m1' " +
"cached in 'testPool' with replication=3 " +
"partition (year=2050, month=2) location '/y2050m2' " +
"uncached",
"ALTER TABLE functional.alltypes ADD IF NOT EXISTS "+
"PARTITION (year=2050, month=1) LOCATION 'hdfs://localhost:20500/y2050m1' " +
"CACHED IN 'testPool' WITH REPLICATION = 3 " +
"PARTITION (year=2050, month=2) LOCATION 'hdfs://localhost:20500/y2050m2' " +
"UNCACHED");
}
@Test
public void testAnalyticExprs() {
testToSql(
"select sum(int_col) over (partition by id order by tinyint_col "
+ "rows between unbounded preceding and current row) from functional.alltypes",
"SELECT sum(int_col) OVER (PARTITION BY id ORDER BY tinyint_col ASC ROWS "
+ "BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) FROM functional.alltypes");
testToSql(
"select last_value(tinyint_col ignore nulls) over (order by tinyint_col) "
+ "from functional.alltypesagg",
"SELECT last_value(tinyint_col IGNORE NULLS) OVER (ORDER BY tinyint_col ASC) "
+ "FROM functional.alltypesagg");
}
/**
* Tests all expressions including whether their toSql() is properly
* enclosed in parentheses.
*/
@Test
public void testExprs() {
// AggregateExpr.
testToSql("select count(*), (count(*)), avg(int_col), (avg(int_col)), " +
"sum(int_col), (sum(int_col)), min(int_col), (min(int_col)), " +
"max(int_col), (max(int_col)) from functional.alltypes",
"SELECT count(*), (count(*)), avg(int_col), (avg(int_col)), " +
"sum(int_col), (sum(int_col)), min(int_col), (min(int_col)), " +
"max(int_col), (max(int_col)) FROM functional.alltypes");
// ArithmeticExpr.
testToSql("select 1 * 1, (1 * 1), 2 / 2, (2 / 2), 3 % 3, (3 % 3), " +
"4 DIV 4, (4 DIV 4), 5 + 5, (5 + 5), 6 - 6, (6 - 6), 7 & 7, (7 & 7), " +
"8 | 8, (8 | 8), 9 ^ 9, (9 ^ 9), ~10, (~10)",
"SELECT 1 * 1, (1 * 1), 2 / 2, (2 / 2), 3 % 3, (3 % 3), " +
"4 DIV 4, (4 DIV 4), 5 + 5, (5 + 5), 6 - 6, (6 - 6), 7 & 7, (7 & 7), " +
"8 | 8, (8 | 8), 9 ^ 9, (9 ^ 9), ~10, (~10)");
testToSql("select (((1 + 2) * (3 - 4) + 6) / 7)",
"SELECT (((1 + 2) * (3 - 4) + 6) / 7)");
// CaseExpr.
// Single case without else clause. No case expr.
testToSql("select case when true then 1 end, " +
"(case when true then 1 end)",
"SELECT CASE WHEN TRUE THEN 1 END, " +
"(CASE WHEN TRUE THEN 1 END)");
// Multiple cases with else clause. No case expr.
testToSql("select case when true then 1 when false then 2 else 3 end, " +
"(case when true then 1 when false then 2 else 3 end)",
"SELECT CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END, " +
"(CASE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END)");
// Multiple cases with else clause with case expr.
testToSql("select case true when true then 1 when false then 2 else 3 end, " +
"(case true when true then 1 when false then 2 else 3 end)",
"SELECT CASE TRUE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END, " +
"(CASE TRUE WHEN TRUE THEN 1 WHEN FALSE THEN 2 ELSE 3 END)");
// DECODE version of CaseExpr.
testToSql("select decode(1, 2, 3), (decode(4, 5, 6))",
"SELECT decode(1, 2, 3), (decode(4, 5, 6))");
testToSql("select decode(1, 2, 3, 4, 5, 6), (decode(1, 2, 3, 4, 5, 6))",
"SELECT decode(1, 2, 3, 4, 5, 6), (decode(1, 2, 3, 4, 5, 6))");
// CastExpr.
testToSql("select cast(NULL as INT), (cast(NULL as INT))",
"SELECT CAST(NULL AS INT), (CAST(NULL AS INT))");
// FunctionCallExpr.
testToSql("select pi(), (pi()), trim('a'), (trim('a'))",
"SELECT pi(), (pi()), trim('a'), (trim('a'))");
// LiteralExpr.
testToSql("select 10, (10), 20.0, (20.0), NULL, (NULL), 'abc', ('abc')",
"SELECT 10, (10), 20.0, (20.0), NULL, (NULL), 'abc', ('abc')");
// BetweenPredicate.
testToSql("select 5 between 10 and 20, (5 between 10 and 20)",
"SELECT 5 BETWEEN 10 AND 20, (5 BETWEEN 10 AND 20)");
testToSql("select 5 not between 10 and 20, (5 not between 10 and 20)",
"SELECT 5 NOT BETWEEN 10 AND 20, (5 NOT BETWEEN 10 AND 20)");
// BinaryPredicate.
testToSql("select 'a' = 'b', ('a' = 'b'), 'a' != 'b', ('a' != 'b'), " +
"1 < 2, (1 < 2), 1 <= 2, (1 <= 2), 1 > 2, (1 > 2), 1 >= 2, (1 >= 2)",
"SELECT 'a' = 'b', ('a' = 'b'), 'a' != 'b', ('a' != 'b'), " +
"1 < 2, (1 < 2), 1 <= 2, (1 <= 2), 1 > 2, (1 > 2), 1 >= 2, (1 >= 2)");
// CompoundPredicate.
testToSql("select true and false, (true and false), " +
"false or true, (false or true), " +
"!true, (!true), not false, (not false)",
"SELECT TRUE AND FALSE, (TRUE AND FALSE), " +
"FALSE OR TRUE, (FALSE OR TRUE), " +
"NOT TRUE, (NOT TRUE), NOT FALSE, (NOT FALSE)");
testToSql("select ((true and (false or false) or true) and (false or true))",
"SELECT ((TRUE AND (FALSE OR FALSE) OR TRUE) AND (FALSE OR TRUE))");
// InPredicate.
testToSql("select 5 in (4, 6, 7, 5), (5 in (4, 6, 7, 5))," +
"5 not in (4, 6, 7, 5), (5 not In (4, 6, 7, 5))",
"SELECT 5 IN (4, 6, 7, 5), (5 IN (4, 6, 7, 5)), " +
"5 NOT IN (4, 6, 7, 5), (5 NOT IN (4, 6, 7, 5))");
// IsNullPredicate.
testToSql("select 5 is null, (5 is null), 10 is not null, (10 is not null)",
"SELECT 5 IS NULL, (5 IS NULL), 10 IS NOT NULL, (10 IS NOT NULL)");
// Boolean test expression (expanded to istrue/false).
testToSql("select (true is true)", "SELECT (istrue(TRUE))");
testToSql("select (true is not true)", "SELECT (isnottrue(TRUE))");
testToSql("select (true is false)", "SELECT (isfalse(TRUE))");
testToSql("select (true is unknown)", "SELECT (TRUE IS NULL)");
testToSql("select (true is not unknown)", "SELECT (TRUE IS NOT NULL)");
testToSql("select not(true is true)", "SELECT NOT (istrue(TRUE))");
testToSql("select (false is false)", "SELECT (isfalse(FALSE))");
testToSql("select (null is unknown)", "SELECT (NULL IS NULL)");
testToSql("select (1 > 1 is true is unknown)", "SELECT (istrue(1 > 1) IS NULL)");
// LikePredicate.
testToSql("select 'a' LIKE '%b.', ('a' LIKE '%b.'), " +
"'a' ILIKE '%b.', ('a' ILIKE '%b.'), " +
"'b' RLIKE '.c%', ('b' RLIKE '.c%')," +
"'d' IREGEXP '.e%', ('d' IREGEXP '.e%')," +
"'d' REGEXP '.e%', ('d' REGEXP '.e%')",
"SELECT 'a' LIKE '%b.', ('a' LIKE '%b.'), " +
"'a' ILIKE '%b.', ('a' ILIKE '%b.'), " +
"'b' RLIKE '.c%', ('b' RLIKE '.c%'), " +
"'d' IREGEXP '.e%', ('d' IREGEXP '.e%'), " +
"'d' REGEXP '.e%', ('d' REGEXP '.e%')" );
// SlotRef.
testToSql("select bool_col, (bool_col), int_col, (int_col) " +
"string_col, (string_col), timestamp_col, (timestamp_col) " +
"from functional.alltypes",
"SELECT bool_col, (bool_col), int_col, (int_col) " +
"string_col, (string_col), timestamp_col, (timestamp_col) " +
"FROM functional.alltypes");
// TimestampArithmeticExpr.
// Non-function-call like version.
testToSql("select timestamp_col + interval 10 years, " +
"(timestamp_col + interval 10 years) from functional.alltypes",
"SELECT timestamp_col + INTERVAL 10 years, " +
"(timestamp_col + INTERVAL 10 years) FROM functional.alltypes");
testToSql("select timestamp_col - interval 20 months, " +
"(timestamp_col - interval 20 months) from functional.alltypes",
"SELECT timestamp_col - INTERVAL 20 months, " +
"(timestamp_col - INTERVAL 20 months) FROM functional.alltypes");
// Reversed interval and timestamp using addition.
testToSql("select interval 30 weeks + timestamp_col, " +
"(interval 30 weeks + timestamp_col) from functional.alltypes",
"SELECT INTERVAL 30 weeks + timestamp_col, " +
"(INTERVAL 30 weeks + timestamp_col) FROM functional.alltypes");
// Function-call like version.
testToSql("select date_add(timestamp_col, interval 40 days), " +
"(date_add(timestamp_col, interval 40 days)) from functional.alltypes",
"SELECT DATE_ADD(timestamp_col, INTERVAL 40 days), " +
"(DATE_ADD(timestamp_col, INTERVAL 40 days)) FROM functional.alltypes");
testToSql("select date_sub(timestamp_col, interval 40 hours), " +
"(date_sub(timestamp_col, interval 40 hours)) from functional.alltypes",
"SELECT DATE_SUB(timestamp_col, INTERVAL 40 hours), " +
"(DATE_SUB(timestamp_col, INTERVAL 40 hours)) FROM functional.alltypes");
}
/**
* Tests decimals are output correctly.
*/
@Test
public void testDecimal() {
testToSql("select cast(1 as decimal)", "SELECT CAST(1 AS DECIMAL(9,0))");
}
/**
* Tests set query option statements are output correctly.
*/
@Test
public void testSet() {
testToSql("set a = 1", "SET a='1'");
testToSql("set `a b` = \"x y\"", "SET `a b`='x y'");
testToSql("set", "SET");
}
@Test
public void testTableSample() {
testToSql("select * from functional.alltypes tablesample system(10)",
"SELECT * FROM functional.alltypes TABLESAMPLE SYSTEM(10)");
testToSql(
"select * from functional.alltypes tablesample system(10) repeatable(20)",
"SELECT * FROM functional.alltypes TABLESAMPLE SYSTEM(10) REPEATABLE(20)");
testToSql(
"select * from functional.alltypes a " +
"tablesample system(10) /* +schedule_random */",
"SELECT * FROM functional.alltypes a " +
"TABLESAMPLE SYSTEM(10)\n-- +schedule_random\n");
testToSql(
"with t as (select * from functional.alltypes tablesample system(5)) " +
"select * from t",
"WITH t AS (SELECT * FROM functional.alltypes TABLESAMPLE SYSTEM(5)) " +
"SELECT * FROM t");
}
@Test
public void testCreateDropRole() {
String testRole = "test_role";
AnalysisContext ctx = createAnalysisCtx(createAuthorizationFactory());
testToSql(ctx, String.format("CREATE ROLE %s", testRole));
try {
catalog_.addRole(testRole);
testToSql(ctx, String.format("DROP ROLE %s", testRole));
} finally {
catalog_.removeRole(testRole);
}
}
@Test
public void testGrantRevokePrivStmt() {
AnalysisContext ctx = createAnalysisCtx(createAuthorizationFactory());
List<String> principalTypes = Arrays.asList("USER", "ROLE", "GROUP");
String testRole = System.getProperty("user.name");
String testUri = "hdfs://localhost:20500/test-warehouse";
for (String pt : principalTypes) {
try {
catalog_.addRole(testRole);
List<Privilege> privileges = Arrays.stream(Privilege.values())
.filter(p -> p != Privilege.OWNER &&
p != Privilege.VIEW_METADATA &&
p != Privilege.ANY)
.collect(Collectors.toList());
for (Privilege p : privileges) {
// Server
testToSql(ctx, String.format("GRANT %s ON SERVER server1 TO %s %s", p,
pt, testRole));
testToSql(ctx, String.format("GRANT %s ON SERVER TO %s", p, testRole),
String.format("GRANT %s ON SERVER server1 TO ROLE %s", p, testRole), false);
testToSql(ctx, String.format(
"GRANT %s ON SERVER server1 TO %s %s WITH GRANT OPTION", p, pt,
testRole));
testToSql(ctx, String.format("REVOKE %s ON SERVER server1 FROM %s %s", p,
pt, testRole));
testToSql(ctx, String.format("REVOKE %s ON SERVER FROM %s %s", p, pt,
testRole),
String.format("REVOKE %s ON SERVER server1 FROM %s %s", p, pt,
testRole), false);
testToSql(ctx, String.format(
"REVOKE GRANT OPTION FOR %s ON SERVER server1 FROM %s %s", p, pt,
testRole));
// Database
testToSql(ctx, String.format("GRANT %s ON DATABASE functional TO %s %s",
p, pt, testRole));
testToSql(ctx, String.format(
"GRANT %s ON DATABASE functional TO %s %s WITH GRANT OPTION", p, pt,
testRole));
testToSql(ctx, String.format("REVOKE %s ON DATABASE functional FROM %s %s",
p, pt, testRole));
testToSql(ctx, String.format(
"REVOKE GRANT OPTION FOR %s ON DATABASE functional FROM %s %s", p, pt,
testRole));
}
privileges = Arrays.stream(Privilege.values())
.filter(p -> p != Privilege.OWNER &&
p != Privilege.CREATE &&
p != Privilege.VIEW_METADATA &&
p != Privilege.ANY)
.collect(Collectors.toList());
for (Privilege p : privileges) {
// Table
testToSql(ctx, String.format("GRANT %s ON TABLE functional.alltypes TO %s %s",
p, pt, testRole));
testToSql(ctx, String.format(
"GRANT %s ON TABLE functional.alltypes TO %s %s WITH GRANT OPTION", p,
pt, testRole));
testToSql(ctx, String.format(
"REVOKE %s ON TABLE functional.alltypes FROM %s %s", p, pt,
testRole));
testToSql(ctx, String.format(
"REVOKE GRANT OPTION FOR %s ON TABLE functional.alltypes FROM %s %s", p,
pt, testRole));
}
// Uri (Only ALL is supported)
testToSql(ctx, String.format("GRANT ALL ON URI '%s' TO %s %s", testUri, pt,
testRole));
testToSql(ctx, String.format("GRANT ALL ON URI '%s' TO %s %s WITH GRANT OPTION",
testUri, pt, testRole));
testToSql(ctx, String.format("REVOKE ALL ON URI '%s' FROM %s %s", testUri,
pt, testRole));
testToSql(ctx, String.format("REVOKE GRANT OPTION FOR ALL ON URI '%s' FROM %s %s",
testUri, pt, testRole));
// Column (Only SELECT is supported)
testToSql(ctx, String.format(
"GRANT SELECT (id) ON TABLE functional.alltypes TO %s %s", pt,
testRole));
testToSql(ctx, String.format(
"GRANT SELECT (id) ON TABLE functional.alltypes TO %s %s WITH GRANT OPTION",
pt, testRole));
testToSql(ctx, String.format(
"REVOKE SELECT (id) ON TABLE functional.alltypes FROM %s %s", pt,
testRole));
testToSql(ctx, String.format(
"REVOKE GRANT OPTION FOR SELECT (id) ON TABLE functional.alltypes FROM %s %s",
pt, testRole));
} finally {
catalog_.removeRole(testRole);
}
}
}
@Test
public void testGrantRevokeRoleStmt() {
AnalysisContext ctx = createAnalysisCtx(createAuthorizationFactory());
String testRole = "test_role";
String testGroup = "test_group";
try {
catalog_.addRole(testRole);
testToSql(ctx, String.format("GRANT ROLE %s TO GROUP %s", testRole, testGroup));
testToSql(ctx, String.format("REVOKE ROLE %s FROM GROUP %s", testRole, testGroup));
} finally {
catalog_.removeRole(testRole);
}
}
@Test
public void testShowGrantPrincipalStmt() {
AnalysisContext ctx = createAnalysisCtx(createAuthorizationFactory());
String testRole = "test_role";
String testUser = System.getProperty("user.name");
String testUri = "hdfs://localhost:20500/test-warehouse";
try {
catalog_.addRole(testRole);
testToSql(ctx, String.format("SHOW GRANT ROLE %s", testRole));
testToSql(ctx, String.format("SHOW GRANT USER %s", testUser));
testToSql(ctx, String.format("SHOW GRANT ROLE %s ON SERVER", testRole));
testToSql(ctx, String.format("SHOW GRANT ROLE %s ON DATABASE functional",
testRole));
testToSql(ctx, String.format("SHOW GRANT ROLE %s ON TABLE functional.alltypes",
testRole));
testToSql(ctx, String.format("SHOW GRANT ROLE %s ON URI '%s'",
testRole, testUri));
} finally {
catalog_.removeRole(testRole);
}
}
@Test
public void testShowRolesStmt() {
AnalysisContext ctx = createAnalysisCtx(createAuthorizationFactory());
String testGroup = "test_group";
testToSql(ctx, "SHOW CURRENT ROLES");
testToSql(ctx, "SHOW ROLES");
testToSql(ctx, String.format("SHOW ROLE GRANT GROUP %s", testGroup));
}
/**
* Tests invalidate statements are output correctly.
*/
@Test
public void testInvalidate() {
testToSql("INVALIDATE METADATA");
testToSql("INVALIDATE METADATA functional.alltypes");
}
/**
* Tests refresh statements are output correctly.
*/
@Test
public void testRefresh() {
testToSql("REFRESH functional.alltypes");
testToSql("REFRESH functional.alltypes PARTITION (year=2009, month=1)");
testToSql("REFRESH FUNCTIONS functional");
testToSql(createAnalysisCtx(createAuthorizationFactory()), "REFRESH AUTHORIZATION");
}
/**
* Test admin functions are output correctly.
*/
@Test
public void testAdminFn() {
testToSql(":shutdown()");
testToSql(":shutdown('hostname')");
testToSql(":shutdown('hostname', 1000)");
testToSql(":shutdown(1000)");
}
}