| // 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.assertTrue; |
| import static org.junit.Assert.fail; |
| |
| import java.math.BigInteger; |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.apache.impala.analysis.TimestampArithmeticExpr.TimeUnit; |
| import org.apache.impala.common.AnalysisException; |
| import org.apache.impala.common.FrontendTestBase; |
| import org.apache.impala.compat.MetastoreShim; |
| import org.apache.impala.service.BackendConfig; |
| import org.junit.Test; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.Lists; |
| |
| public class ParserTest extends FrontendTestBase { |
| |
| // Representative operands for testing. |
| private final static String[] operands_ = |
| new String[] {"i", "5", "true", "NULL", "'a'", "(1.5 * 8)" }; |
| |
| /** |
| * Attempts to parse the given select statement, and asserts in case of parser error. |
| * Also asserts that the first select-list expression is of given class. |
| */ |
| public <C extends Expr> Object ParsesOk(String selectStmtSql, Class<C> cl) { |
| Object parseNode = ParsesOk(selectStmtSql); |
| if (!(parseNode instanceof SelectStmt)) { |
| fail(String.format("Statement parsed ok but it is not a select stmt: %s", |
| selectStmtSql)); |
| } |
| SelectStmt selectStmt = (SelectStmt) parseNode; |
| Expr firstExpr = selectStmt.getSelectList().getItems().get(0).getExpr(); |
| // Check the class of the first select-list expression. |
| assertTrue(String.format( |
| "Expression is of class '%s'. Expected class '%s'", |
| firstExpr.getClass().getSimpleName(), cl.getSimpleName()), |
| firstExpr.getClass().equals(cl)); |
| return parseNode; |
| } |
| |
| /** |
| * Asserts if stmt parses fine or the error string doesn't match and it is non-null. |
| */ |
| public void ParserError(String stmt, String expectedErrorString) { |
| @SuppressWarnings("unused") |
| StatementBase result = null; // Save this object to make debugging easier |
| try { |
| result = Parser.parse(stmt); |
| } catch (AnalysisException e) { |
| if (expectedErrorString != null) { |
| String errorString = e.getMessage(); |
| StringBuilder message = new StringBuilder(); |
| message.append("Got: "); |
| message.append(errorString).append("\nExpected: ").append(expectedErrorString); |
| assertTrue(message.toString(), errorString.startsWith(expectedErrorString)); |
| } |
| return; |
| } |
| fail("Stmt didn't result in parsing error: " + stmt); |
| } |
| |
| /** |
| * Asserts if stmt parses fine. |
| * @param stmt |
| */ |
| public void ParserError(String stmt) { |
| ParserError(stmt, null); |
| } |
| |
| @Test |
| public void TestCopyTestCase() { |
| // Only QueryStmts are supported |
| ParsesOk("copy testcase to 'hdfs:///foo' select * from tbl"); |
| ParsesOk("copy testcase to 'hdfs:///foo' with v as (select 1) select * from v"); |
| ParsesOk("copy testcase to 'hdfs:///foo' select * from t1 union select * from t2"); |
| // non QueryStmts aren not supported. |
| ParserError("copy testcase to 'hdfs:///foo' alter table foo add partition (p=1)"); |
| ParserError("copy testcase to 'hdfs:///foo' insert into t values (1)"); |
| // missing output directory. |
| ParserError("copy testcase to select * from tbl"); |
| // missing quotes for the directory path. |
| ParserError("copy testcase to hdfs:///foo select * from tbl"); |
| |
| ParsesOk("copy testcase from 'hdfs:///foo'"); |
| // missing quotes. |
| ParserError("copy testcase from hdfs:///foo"); |
| ParserError("copy testcase"); |
| // testcase is not a reserved word. |
| ParsesOk("select testcase from foo"); |
| } |
| |
| @Test |
| public void TestNoFromClause() { |
| ParsesOk("select 1 + 1, 'two', f(3), a + b"); |
| ParserError("select 1 + 1 'two' f(3) a + b"); |
| ParserError("select a, 2 where a > 2"); |
| ParserError("select a, 2 group by"); |
| ParserError("select a, 2 order by 1"); |
| ParserError("select a, 2 limit 1"); |
| ParserError("select a, 2 order by 1 limit 1"); |
| } |
| |
| @Test |
| public void TestSelect() { |
| ParsesOk("select a from tbl"); |
| ParsesOk("select a, b, c, d from tbl"); |
| ParsesOk("select true, false, NULL from tbl"); |
| ParsesOk("select all a, b, c from tbl"); |
| ParserError("a from tbl"); |
| ParserError("select a b c from tbl"); |
| ParserError("select all from tbl"); |
| } |
| |
| /** |
| * Tests table and column aliases which can specified as |
| * identifiers (quoted or unquoted) or string literals (single or double quoted). |
| */ |
| @Test |
| public void TestAlias() { |
| char[] quotes = {'\'', '"', '`', ' '}; |
| for (int i = 0; i < quotes.length; ++i) { |
| char quote = quotes[i]; |
| // Column aliases. |
| ParsesOk("select a 'b' from tbl".replace('\'', quote)); |
| ParsesOk("select a as 'b' from tbl".replace('\'', quote)); |
| ParsesOk("select a 'x', b as 'y', c 'z' from tbl".replace('\'', quote)); |
| ParsesOk( |
| "select a 'x', b as 'y', sum(x) over () 'z' from tbl".replace('\'', quote)); |
| ParsesOk("select a.b 'x' from tbl".replace('\'', quote)); |
| ParsesOk("select a.b as 'x' from tbl".replace('\'', quote)); |
| ParsesOk("select a.b.c.d 'x' from tbl".replace('\'', quote)); |
| ParsesOk("select a.b.c.d as 'x' from tbl".replace('\'', quote)); |
| // Table aliases. |
| ParsesOk("select a from tbl 'b'".replace('\'', quote)); |
| ParsesOk("select a from tbl as 'b'".replace('\'', quote)); |
| ParsesOk("select a from db.tbl 'b'".replace('\'', quote)); |
| ParsesOk("select a from db.tbl as 'b'".replace('\'', quote)); |
| ParsesOk("select a from db.tbl.col 'b'".replace('\'', quote)); |
| ParsesOk("select a from db.tbl.col as 'b'".replace('\'', quote)); |
| ParsesOk("select a from (select * from tbl) 'b'".replace('\'', quote)); |
| ParsesOk("select a from (select * from tbl) as 'b'".replace('\'', quote)); |
| ParsesOk("select a from (select * from tbl b) as 'b'".replace('\'', quote)); |
| // With-clause view aliases. |
| ParsesOk("with 't' as (select 1) select * from t".replace('\'', quote)); |
| } |
| ParserError("a from tbl"); |
| ParserError("select a as a, b c d from tbl"); |
| } |
| |
| @Test |
| public void TestStar() { |
| ParsesOk("select * from tbl"); |
| ParsesOk("select tbl.* from tbl"); |
| ParsesOk("select db.tbl.* from tbl"); |
| ParsesOk("select db.tbl.struct_col.* from tbl"); |
| ParserError("select * + 5 from tbl"); |
| ParserError("select (*) from tbl"); |
| ParserError("select *.id from tbl"); |
| ParserError("select * from tbl.*"); |
| ParserError("select * from tbl where * = 5"); |
| ParsesOk("select * from tbl where f(*) = 5"); |
| ParserError("select * from tbl where tbl.* = 5"); |
| ParserError("select * from tbl where f(tbl.*) = 5"); |
| } |
| |
| /** |
| * Various test cases for (multiline) C-style comments. |
| */ |
| @Test |
| public void TestMultilineComment() { |
| ParserError("/**/"); |
| ParserError("/*****/"); |
| ParserError("/* select 1 */"); |
| ParserError("/*/ select 1"); |
| ParserError("select 1 /*/"); |
| ParsesOk("/**/select 1"); |
| ParsesOk("select/* */1"); |
| ParsesOk("/** */ select 1"); |
| ParsesOk("select 1/* **/"); |
| ParsesOk("/*/*/select 1"); |
| ParsesOk("/*//*/select 1"); |
| ParsesOk("select 1/***/"); |
| ParsesOk("/*****/select 1"); |
| ParsesOk("/**//**/select 1"); |
| ParserError("/**/**/select 1"); |
| ParsesOk("\nselect 1/**/"); |
| ParsesOk("/*\n*/select 1"); |
| ParsesOk("/*\r*/select 1"); |
| ParsesOk("/*\r\n*/select 1"); |
| ParsesOk("/**\n* Doc style\n*/select 1"); |
| ParsesOk("/************\n*\n* Header style\n*\n***********/select 1"); |
| ParsesOk("/* 1 */ select 1 /* 2 */"); |
| ParsesOk("select\n/**/\n1"); |
| ParserError("/**// select 1"); |
| ParserError("/**/*/ select 1"); |
| ParserError("/ **/ select 1"); |
| ParserError("/** / select 1"); |
| ParserError("/\n**/ select 1"); |
| ParserError("/**\n/ select 1"); |
| ParsesOk("/*--*/ select 1"); |
| ParsesOk("/* --foo */ select 1"); |
| ParsesOk("/*\n--foo */ select 1"); |
| ParsesOk("/*\n--foo\n*/ select 1"); |
| ParserError("select 1 /* --bar"); |
| ParserError("select 1 /*--"); |
| ParsesOk("/* select 1; */ select 1"); |
| ParsesOk("/** select 1; */ select 1"); |
| ParsesOk("/* select */ select 1 /* 1 */"); |
| // Test hint with arguments |
| ParsesOk("select 1 /* hint_with_args(() */"); |
| // Empty argument list in hint_with_args hint is not allowed |
| ParserError("select 1 /*+ hint_with_args() */"); |
| // Mismatching parentheses |
| ParserError("select 1 /*+ hint_with_args(() */"); |
| ParserError("select 1 /*+ hint_with_args(a) \n"); |
| ParserError("select 1 --+ hint_with_args(a) */\n from t"); |
| } |
| |
| /** |
| * Various test cases for one line comment style (starts with --). |
| */ |
| @Test |
| public void TestSinglelineComment() { |
| ParserError("--"); |
| ParserError("--select 1"); |
| ParsesOk("select 1--"); |
| ParsesOk("select 1 --foo"); |
| ParsesOk("select 1 --\ncol_name"); |
| ParsesOk("--foo's \nselect 1 --bar"); |
| ParsesOk("--foo\nselect 1 --bar"); |
| ParsesOk("--foo\r\nselect 1 --bar"); |
| ParsesOk("--/* foo */\n select 1"); |
| ParsesOk("select 1 --/**/"); |
| ParsesOk("-- foo /*\nselect 1"); |
| ParserError("-- baz /*\nselect 1*/"); |
| ParsesOk("select -- blah\n 1"); |
| ParsesOk("select -- select 1\n 1"); |
| // Test hint with arguments |
| ParsesOk("select 1 -- hint_with_args(()"); |
| // Mismatching parentheses |
| ParserError("select 1 -- +hint_with_args(()\n"); |
| } |
| |
| /** |
| * Parses stmt and checks that the all table refs in stmt have the expected join hints. |
| * The expectedHints contains the hints of all table refs from left to right (starting |
| * with the second tableRef because the first one cannot have hints). |
| */ |
| private void TestJoinHints(String stmt, String... expectedHints) { |
| SelectStmt selectStmt = (SelectStmt) ParsesOk(stmt); |
| Preconditions.checkState(selectStmt.getTableRefs().size() > 1); |
| List<String> actualHints = new ArrayList<>(); |
| assertTrue(selectStmt.getTableRefs().get(0).getJoinHints().isEmpty()); |
| for (int i = 1; i < selectStmt.getTableRefs().size(); ++i) { |
| List<PlanHint> hints = selectStmt.getTableRefs().get(i).getJoinHints(); |
| for (PlanHint hint: hints) actualHints.add(hint.toString()); |
| } |
| if (actualHints.isEmpty()) actualHints = Lists.newArrayList((String) null); |
| assertEquals(Lists.newArrayList(expectedHints), actualHints); |
| } |
| |
| private void TestTableHints(String stmt, String... expectedHints) { |
| SelectStmt selectStmt = (SelectStmt) ParsesOk(stmt); |
| Preconditions.checkState(selectStmt.getTableRefs().size() > 0); |
| List<String> actualHints = new ArrayList<>(); |
| for (int i = 0; i < selectStmt.getTableRefs().size(); ++i) { |
| List<PlanHint> hints = selectStmt.getTableRefs().get(i).getTableHints(); |
| for (PlanHint hint: hints) actualHints.add(hint.toString()); |
| } |
| if (actualHints.isEmpty()) actualHints = Lists.newArrayList((String) null); |
| assertEquals(Lists.newArrayList(expectedHints), actualHints); |
| } |
| |
| private void TestTableAndJoinHints(String stmt, String... expectedHints) { |
| SelectStmt selectStmt = (SelectStmt) ParsesOk(stmt); |
| Preconditions.checkState(selectStmt.getTableRefs().size() > 0); |
| List<String> actualHints = new ArrayList<>(); |
| for (int i = 0; i < selectStmt.getTableRefs().size(); ++i) { |
| List<PlanHint> joinHints = selectStmt.getTableRefs().get(i).getJoinHints(); |
| for (PlanHint hint: joinHints) actualHints.add(hint.toString()); |
| List<PlanHint> tableHints = selectStmt.getTableRefs().get(i).getTableHints(); |
| for (PlanHint hint: tableHints) actualHints.add(hint.toString()); |
| } |
| if (actualHints.isEmpty()) actualHints = Lists.newArrayList((String) null); |
| assertEquals(Lists.newArrayList(expectedHints), actualHints); |
| } |
| |
| /** |
| * Parses stmt and checks that the select-list plan hints in stmt are the |
| * expected hints. |
| */ |
| private void TestSelectListHints(String stmt, String... expectedHints) { |
| SelectStmt selectStmt = (SelectStmt) ParsesOk(stmt); |
| List<String> actualHints = new ArrayList<>(); |
| List<PlanHint> hints = selectStmt.getSelectList().getPlanHints(); |
| for (PlanHint hint: hints) actualHints.add(hint.toString()); |
| if (actualHints.isEmpty()) actualHints = Lists.newArrayList((String) null); |
| assertEquals(Lists.newArrayList(expectedHints), actualHints); |
| } |
| |
| /** |
| * Creates an insert into, an insert overwrite, and a CTAS statement with |
| * the given hints and checks that the parsed hints are the same as the expected hints. |
| */ |
| private void TestInsertAndCtasHints(String insertPart, String ctasPart, |
| String[] hintStyle, String hints, String... expectedHints) { |
| String hintsPart = hintStyle[0] + hints + hintStyle[1]; |
| TestInsertStmtHints(String.format("insert %%s into %s %%s select * from t", |
| insertPart), hintsPart, expectedHints); |
| TestInsertStmtHints(String.format("insert %%s overwrite %s %%s select * from t", |
| insertPart), hintsPart, expectedHints); |
| TestCtasHints(String.format("create %s table %s as select * from t", |
| hintsPart, ctasPart), expectedHints); |
| } |
| |
| /** |
| * Injects hints into pattern and checks that the injected hints match the expected |
| * hints. This function covers both insert and upsert statements. |
| */ |
| private void TestInsertStmtHints(String pattern, String hint, String... expectedHints) { |
| for (InsertStmt.HintLocation loc: InsertStmt.HintLocation.values()) { |
| InsertStmt insertStmt = (InsertStmt) ParsesOk(InjectInsertHint(pattern, hint, loc)); |
| assertEquals(expectedHints, HintsToStrings(insertStmt.getPlanHints())); |
| } |
| } |
| |
| /** |
| * Injects hints into pattern and expect parser error on the injected hints. |
| * It covers both insert and upsert statements. |
| */ |
| private void ParserErrorOnInsertStmtHints(String pattern, String hint) { |
| for (InsertStmt.HintLocation loc: InsertStmt.HintLocation.values()) { |
| ParserError(InjectInsertHint(pattern, hint, loc)); |
| } |
| } |
| |
| /** |
| * Parses stmt and checks that the CTAS hints stmt are the expected hints. |
| */ |
| private void TestCtasHints(String stmt, String... expectedHints) { |
| CreateTableAsSelectStmt ctasStmt = (CreateTableAsSelectStmt) ParsesOk(stmt); |
| assertEquals(expectedHints, HintsToStrings(ctasStmt.getInsertStmt().getPlanHints())); |
| } |
| |
| static private String[] HintsToStrings(List<PlanHint> hints) { |
| if (hints.isEmpty()) return new String[] { null }; |
| String[] hintAsStrings = new String[hints.size()]; |
| for (int i = 0; i < hints.size(); ++i) hintAsStrings[i] = hints.get(i).toString(); |
| return hintAsStrings; |
| } |
| |
| @Test |
| public void TestPlanHints() { |
| String[][] commentStyles = new String[][] { |
| new String[] { "/*", "*/" }, // traditional comment |
| new String[] { "--", "\n" } // eol comment |
| }; |
| for (String[] hintStyle: hintStyles_) { |
| String prefix = hintStyle[0]; |
| String suffix = hintStyle[1]; |
| // Test unexpected token |
| ParserError(prefix + suffix,"Syntax error in line 1:\n" + prefix + suffix); |
| // Test join hints. |
| TestJoinHints(String.format( |
| "select * from functional.alltypes a join %sbroadcast%s " + |
| "functional.alltypes b", prefix, suffix), "broadcast"); |
| TestJoinHints(String.format( |
| "select * from functional.alltypes a join %sbroadcast%s " + |
| "functional.alltypes b using(id)", prefix, suffix), "broadcast"); |
| TestJoinHints(String.format( |
| "select * from functional.alltypes a join %sbroadcast%s " + |
| "functional.alltypes b on(a.id = b.id)", prefix, suffix), "broadcast"); |
| TestJoinHints(String.format( |
| "select * from functional.alltypes a cross join %sbroadcast%s " + |
| "functional.alltypes b", prefix, suffix), "broadcast"); |
| // Multiple comma-separated hints. |
| TestJoinHints(String.format( |
| "select * from functional.alltypes a join " + |
| "%sbroadcast,shuffle,foo,bar%s " + |
| "functional.alltypes b using(id)", prefix, suffix), |
| "broadcast", "shuffle", "foo", "bar"); |
| // Test hints in a multi-way join. |
| TestJoinHints(String.format( |
| "select * from functional.alltypes a " + |
| "join %sbroadcast%s functional.alltypes b using(id) " + |
| "join %sshuffle%s functional.alltypes c using(int_col) " + |
| "join %sbroadcast%s functional.alltypes d using(int_col) " + |
| "join %sshuffle%s functional.alltypes e using(string_col)", |
| prefix, suffix, prefix, suffix, prefix, suffix, prefix, suffix), |
| "broadcast", "shuffle", "broadcast", "shuffle"); |
| // Test hints in a multi-way join (flipped prefix/suffix -> bad hint start/ends). |
| ParserError(String.format( |
| "select * from functional.alltypes a " + |
| "join %sbroadcast%s functional.alltypes b using(id) " + |
| "join %sshuffle%s functional.alltypes c using(int_col) " + |
| "join %sbroadcast%s functional.alltypes d using(int_col) " + |
| "join %sshuffle%s functional.alltypes e using(string_col)", |
| prefix, suffix, suffix, prefix, prefix, suffix, suffix, prefix)); |
| // Test hints in a multi-way join (missing prefixes/suffixes). |
| ParserError(String.format( |
| "select * from functional.alltypes a " + |
| "join %sbroadcast%s functional.alltypes b using(id) " + |
| "join %sshuffle%s functional.alltypes c using(int_col) " + |
| "join %sbroadcast%s functional.alltypes d using(int_col) " + |
| "join %sshuffle%s functional.alltypes e using(string_col)", |
| suffix, suffix, suffix, suffix, prefix, "", "", "")); |
| |
| // Test insert/CTAS hints. |
| TestInsertAndCtasHints("t", "t", hintStyle, "noshuffle", "noshuffle"); |
| TestInsertAndCtasHints("t partition(x, y)", "t partitioned by(x, y)", |
| hintStyle, "noshuffle", "noshuffle"); |
| TestInsertAndCtasHints("t(a, b) partition(x, y)", "t partitioned by(x, y)", |
| hintStyle, "shuffle", "shuffle"); |
| TestInsertAndCtasHints("t(a, b) partition(x, y)", "t partitioned by(x, y)", |
| hintStyle, "foo,bar,baz", "foo", "bar", "baz"); |
| |
| // Test upsert hints. |
| TestInsertStmtHints("upsert %s into t %s select * from t", |
| String.format("%sshuffle%s", prefix, suffix), "shuffle"); |
| TestInsertStmtHints("upsert %s into t (x, y) %s select * from t", |
| String.format("%sshuffle%s", prefix, suffix), "shuffle"); |
| |
| // Test TableRef hints. |
| TestTableHints(String.format( |
| "select * from functional.alltypes %sschedule_disk_local%s", prefix, suffix), |
| "schedule_disk_local"); |
| TestTableHints(String.format( |
| "select * from functional.alltypes %sschedule_cache_local," + |
| "schedule_random_replica%s", prefix, suffix), "schedule_cache_local", |
| "schedule_random_replica"); |
| TestTableHints(String.format( |
| "select * from functional.alltypes a %sschedule_cache_local," + |
| "schedule_random_replica%s", prefix, suffix), "schedule_cache_local", |
| "schedule_random_replica"); |
| TestTableHints(String.format( |
| "select * from functional.alltypes a %sschedule_cache_local," + |
| "schedule_random_replica%s" + ", functional.alltypes b %sschedule_remote%s", |
| prefix, suffix, prefix, suffix), "schedule_cache_local", |
| "schedule_random_replica", "schedule_remote"); |
| |
| // Test both TableRef and join hints. |
| TestTableAndJoinHints(String.format( |
| "select * from functional.alltypes a %sschedule_cache_local," + |
| "schedule_random_replica%s join %sbroadcast%s functional.alltypes b " + |
| "%sschedule_remote%s using(id)", prefix, suffix, prefix, suffix, prefix, |
| suffix), "schedule_cache_local", "schedule_random_replica", "broadcast", |
| "schedule_remote"); |
| |
| TestSelectListHints(String.format( |
| "select %sfoo,bar,baz%s * from functional.alltypes a", prefix, suffix), |
| "foo", "bar", "baz"); |
| |
| // Test select-list hints (e.g., straight_join). The legacy-style hint has no |
| // prefix and suffix. |
| { |
| String localPrefix = prefix; |
| String localSuffix = suffix; |
| if (prefix == "[") { |
| localPrefix = ""; |
| localSuffix = ""; |
| } |
| TestSelectListHints(String.format( |
| "select %sstraight_join%s * from functional.alltypes a", localPrefix, |
| localSuffix), "straight_join"); |
| } |
| |
| // Below are tests for hints that are not supported by the legacy syntax. |
| if (prefix == "[") continue; |
| |
| // Test mixing commented hints and comments. |
| for (String[] commentStyle: commentStyles) { |
| String commentPrefix = commentStyle[0]; |
| String commentSuffix = commentStyle[1]; |
| String queryTemplate = |
| "$1comment$2 select $1comment$2 $3straight_join$4 $1comment$2 * " + |
| "from $1comment$2 functional.alltypes a join $1comment$2 $3shuffle$4 " + |
| "$1comment$2 functional.alltypes b $1comment$2 on $1comment$2 " + |
| "(a.id = b.id)"; |
| String query = queryTemplate.replaceAll("\\$1", commentPrefix) |
| .replaceAll("\\$2", commentSuffix).replaceAll("\\$3", prefix) |
| .replaceAll("\\$4", suffix); |
| TestSelectListHints(query, "straight_join"); |
| TestJoinHints(query, "shuffle"); |
| } |
| |
| // Tests for hints with arguments. |
| TestInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%shint_with_args(a)%s", prefix, suffix), "hint_with_args(a)"); |
| TestInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%sclustered,shuffle,hint_with_args(a)%s", prefix, suffix), |
| "clustered", "shuffle", "hint_with_args(a)"); |
| TestInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%shint_with_args(a,b)%s", prefix, suffix), |
| "hint_with_args(a,b)"); |
| TestInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%shint_with_args(a , b)%s", prefix, suffix), |
| "hint_with_args(a,b)"); |
| TestInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%shint_with_args( a , b , c , d, e, f )%s", prefix, |
| suffix), "hint_with_args(a,b,c,d,e,f)"); |
| ParserErrorOnInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%shint_with_args( a , , ,,, b )%s", prefix, suffix)); |
| |
| TestInsertAndCtasHints("t", "t", |
| hintStyle, "hint_with_args(a)", "hint_with_args(a)"); |
| TestInsertAndCtasHints("t", "t", |
| hintStyle, "clustered,shuffle,hint_with_args(a)", |
| "clustered", "shuffle", "hint_with_args(a)"); |
| TestInsertAndCtasHints("t", "t", |
| hintStyle, "hint_with_args(a,b)", "hint_with_args(a,b)"); |
| TestInsertAndCtasHints("t", "t", |
| hintStyle, "hint_with_args(a , b)", "hint_with_args(a,b)"); |
| |
| ParserErrorOnInsertStmtHints("insert %s into t %s select * from t", |
| String.format("%shint_with_args( a , , ,,, b )%s", prefix, suffix)); |
| ParserError(String.format( |
| "create table t %shint_with_args( a , , ,,, b )%s as select * from t", |
| prefix, suffix)); |
| |
| // Negative tests for hints cannot be specified at the both available locations. |
| ParserError(String.format("insert %s into t %s select * from t", |
| String.format("%sshuffle%s", prefix, suffix), |
| String.format("%sclustered%s", prefix, suffix))); |
| ParserError(String.format("upsert %s into t %s select * from t", |
| String.format("%sshuffle%s", prefix, suffix), |
| String.format("%sclustered%s", prefix, suffix))); |
| } |
| // No "+" at the beginning so the comment is not recognized as a hint. |
| TestJoinHints("select * from functional.alltypes a join /* comment */" + |
| "functional.alltypes b using (int_col)", (String) null); |
| TestSelectListHints("select /* comment */ * from functional.alltypes", |
| (String) null); |
| TestInsertStmtHints("insert %s into t(a, b) partition(x, y) %s select 1", |
| "/* comment */", (String) null); |
| TestCtasHints("create /* comment */ table t partitioned by (x, y) as select 1", |
| (String) null); |
| TestSelectListHints("select /* -- +straight_join */ * from functional.alltypes", |
| (String) null); |
| TestSelectListHints("select /* abcdef +straight_join */ * from functional.alltypes", |
| (String) null); |
| TestSelectListHints("select \n-- abcdef +straight_join\n * from functional.alltypes", |
| (String) null); |
| TestSelectListHints("select \n-- /*+straight_join\n * from functional.alltypes", |
| (String) null); |
| |
| // Commented hints cannot span lines (recognized as comments instead). |
| TestSelectListHints("select /*\n +straight_join */ * from functional.alltypes", |
| (String) null); |
| TestSelectListHints("select /* +straight_join \n*/ * from functional.alltypes", |
| (String) null); |
| TestSelectListHints("select /* +straight_\njoin */ * from functional.alltypes", |
| (String) null); |
| ParserError("select -- +straight_join * from functional.alltypes"); |
| ParserError("select \n-- +straight_join * from functional.alltypes"); |
| |
| // Missing "/*" or "/*" |
| ParserError("select * from functional.alltypes a join + */" + |
| "functional.alltypes b using (int_col)"); |
| ParserError("select * from functional.alltypes a join /* + " + |
| "functional.alltypes b using (int_col)"); |
| |
| // Test empty hint tokens. |
| TestSelectListHints("select /* +straight_join, ,, */ * from functional.alltypes", |
| "straight_join"); |
| // Traditional commented hints are not parsed inside a comment. |
| ParserError("select /* /* +straight_join */ */ * from functional.alltypes"); |
| } |
| |
| @Test |
| public void TestFromClause() { |
| String tblRefs[] = new String[] { "tbl", "db.tbl", "db.tbl.col", "db.tbl.col.fld" }; |
| for (String tbl: tblRefs) { |
| ParsesOk( |
| ("select * from $TBL src1 " + |
| "left outer join $TBL src2 on " + |
| " src1.key = src2.key and src1.key < 10 and src2.key > 10 " + |
| "right outer join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "full outer join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "left semi join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "left anti join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "right semi join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "right anti join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 " + |
| "inner join $TBL src3 on " + |
| " src2.key = src3.key and src3.key < 10 ").replace("$TBL", tbl)); |
| ParsesOk( |
| ("select * from $TBL src1 " + |
| "left outer join $TBL src2 using (a, b, c) " + |
| "right outer join $TBL src3 using (d, e, f) " + |
| "full outer join $TBL src4 using (d, e, f) " + |
| "left semi join $TBL src5 using (d, e, f) " + |
| "left anti join $TBL src6 using (d, e, f) " + |
| "right semi join $TBL src7 using (d, e, f) " + |
| "right anti join $TBL src8 using (d, e, f) " + |
| "join $TBL src9 using (d, e, f) " + |
| "inner join $TBL src10 using (d, e, f) ").replace("$TBL", tbl)); |
| |
| // Test cross joins |
| ParsesOk("select * from $TBL cross join $TBL".replace("$TBL", tbl)); |
| } |
| |
| // Test NULLs in on clause. |
| ParsesOk("select * from src src1 " + |
| "left outer join src src2 on NULL " + |
| "right outer join src src3 on (NULL) " + |
| "full outer join src src3 on NULL " + |
| "left semi join src src3 on (NULL) " + |
| "left anti join src src3 on (NULL) " + |
| "right semi join src src3 on (NULL) " + |
| "right anti join src src3 on (NULL) " + |
| "join src src3 on NULL " + |
| "inner join src src3 on (NULL) " + |
| "where src2.bla = src3.bla " + |
| "order by src1.key, src1.value, src2.key, src2.value, src3.key, src3.value"); |
| // Arbitrary exprs in on clause parse ok. |
| ParsesOk("select * from src src1 join src src2 on ('a')"); |
| ParsesOk("select * from src src1 join src src2 on (f(a, b))"); |
| ParserError("select * from src src1 " + |
| "left outer join src src2 on (src1.key = src2.key and)"); |
| |
| // Using clause requires SlotRef. |
| ParserError("select * from src src1 join src src2 using (1)"); |
| ParserError("select * from src src1 join src src2 using (f(id))"); |
| // Using clause required parenthesis. |
| ParserError("select * from src src1 join src src2 using id"); |
| |
| // Cross joins do not accept on/using |
| ParserError("select * from a cross join b on (a.id = b.id)"); |
| ParserError("select * from a cross join b using (id)"); |
| } |
| |
| @Test |
| public void TestTableSampleClause() { |
| String tblRefs[] = new String[] { "tbl", "db.tbl", "db.tbl.col", "db.tbl.col.fld" }; |
| String tblAliases[] = new String[] { "", "t" }; |
| String tblSampleClauses[] = new String[] { |
| "", "tablesample system(10)", "tablesample system(100) repeatable(20)" }; |
| String tblHints[] = new String[] { |
| "", "/* +schedule_remote */", "[schedule_random_replica]" |
| }; |
| for (String tbl: tblRefs) { |
| for (String alias: tblAliases) { |
| for (String smp: tblSampleClauses) { |
| for (String hint: tblHints) { |
| // Single table. |
| ParsesOk(String.format("select * from %s %s %s %s", tbl, alias, smp, hint)); |
| // Multi table. |
| ParsesOk(String.format( |
| "select a.* from %s %s %s %s join %s %s %s %s using (id)", |
| tbl, alias, smp, hint, tbl, alias, smp, hint)); |
| ParsesOk(String.format( |
| "select a.* from %s %s %s %s, %s %s %s %s", |
| tbl, alias, smp, hint, tbl, alias, smp, hint)); |
| // Inline view. |
| ParsesOk(String.format("select * from (select 1 from %s %s) v %s %s", |
| tbl, alias, smp, hint)); |
| |
| } |
| } |
| } |
| } |
| |
| // Table alias most come before TABLESAMPLE. |
| ParserError("select * from t tablesample (10) a"); |
| // Hints must come after TABLESAMPLE. |
| ParserError("select * from t [schedule_remote] tablesample (10)"); |
| ParserError("select * from t /* +schedule_remote */ tablesample (10)"); |
| // Missing SYSTEM. |
| ParserError("select * from t tablesample (10)"); |
| // Missing parenthesis. |
| ParserError("select * from t tablesample system 10"); |
| // Percent must be int literal. |
| ParserError("select * from t tablesample system (10 + 10"); |
| // Missing random seed. |
| ParserError("select * from t tablesample system (10) repeatable"); |
| // Random seed must be an int literal. |
| ParserError("select * from t tablesample system (10) repeatable (10 + 10)"); |
| // Negative percent. |
| ParserError("select * from t tablesample system (-10)"); |
| // Negative random seed. |
| ParserError("select * from t tablesample system (10) repeatable(-10)"); |
| } |
| |
| @Test |
| public void TestWhereClause() { |
| ParsesOk("select a, b from t where a > 15"); |
| ParsesOk("select a, b from t where true"); |
| ParsesOk("select a, b from t where NULL"); |
| // Non-predicate exprs that return boolean. |
| ParsesOk("select a, b from t where case a when b then true else false end"); |
| ParsesOk("select a, b from t where if (a > b, true, false)"); |
| ParsesOk("select a, b from t where bool_col"); |
| // Arbitrary non-predicate exprs parse ok but are semantically incorrect. |
| ParsesOk("select a, b from t where 10.5"); |
| ParsesOk("select a, b from t where trim('abc')"); |
| ParsesOk("select a, b from t where s + 20"); |
| ParserError("select a, b from t where a > 15 from test"); |
| ParserError("select a, b where a > 15"); |
| ParserError("select where a, b from t"); |
| } |
| |
| @Test |
| public void TestGroupBy() { |
| ParsesOk("select a, b, count(c) from test group by 1, 2"); |
| ParsesOk("select a, b, count(c) from test group by a, b"); |
| ParsesOk("select a, b, count(c) from test group by true, false, NULL"); |
| // semantically wrong but parses fine |
| ParsesOk("select a, b, count(c) from test group by 1, b"); |
| ParserError("select a, b, count(c) from test group 1, 2"); |
| ParserError("select a, b, count(c) from test group by order by a"); |
| } |
| |
| @Test |
| public void TestOrderBy() { |
| ParsesOk("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col, 15.7 * float_col, int_col + bigint_col"); |
| ParsesOk("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col asc, 15.7 * float_col desc, int_col + bigint_col asc"); |
| ParsesOk("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col asc, float_col desc, int_col + bigint_col " + |
| "asc nulls first"); |
| ParsesOk("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col asc, float_col desc, int_col + bigint_col " + |
| "desc nulls last"); |
| ParsesOk("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col asc, float_col desc, int_col + bigint_col " + |
| "nulls first"); |
| ParsesOk("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col asc, float_col desc nulls last, int_col + bigint_col " + |
| "nulls first"); |
| ParsesOk("select int_col from alltypes order by true, false, NULL"); |
| ParserError("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by by string_col asc desc"); |
| ParserError("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "nulls first"); |
| ParserError("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col nulls"); |
| ParserError("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col nulls first asc"); |
| ParserError("select int_col, string_col, bigint_col, count(*) from alltypes " + |
| "order by string_col nulls first last"); |
| } |
| |
| @Test |
| public void TestHaving() { |
| ParsesOk("select a, b, count(c) from test group by a, b having count(*) > 5"); |
| ParsesOk("select a, b, count(c) from test group by a, b having NULL"); |
| ParsesOk("select a, b, count(c) from test group by a, b having true"); |
| ParsesOk("select a, b, count(c) from test group by a, b having false"); |
| // Non-predicate exprs that return boolean. |
| ParsesOk("select count(c) from test group by a having if (a > b, true, false)"); |
| ParsesOk("select count(c) from test group by a " + |
| "having case a when b then true else false end"); |
| // Arbitrary non-predicate exprs parse ok but are semantically incorrect. |
| ParsesOk("select a, b, count(c) from test group by a, b having 5"); |
| ParserError("select a, b, count(c) from test group by a, b having order by 5"); |
| ParserError("select a, b, count(c) from test having count(*) > 5 group by a, b"); |
| } |
| |
| @Test |
| public void TestLimit() { |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit 10"); |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit 10 + 10"); |
| // The following will parse because limit takes an expr, though they will fail in |
| // analysis |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit 'a'"); |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit a"); |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit true"); |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit false"); |
| ParsesOk("select a, b, c from test inner join test2 using(a) limit NULL"); |
| // Not an expr, will not parse |
| ParserError("select a, b, c from test inner join test2 using(a) limit 10 " + |
| "where a > 10"); |
| } |
| |
| @Test |
| public void TestOffset() { |
| ParsesOk("select a from test order by a limit 10 offset 5"); |
| ParsesOk("select a from test order by a limit 10 offset 0"); |
| ParsesOk("select a from test order by a limit 10 offset 0 + 5 / 2"); |
| ParsesOk("select a from test order by a asc limit 10 offset 5"); |
| ParsesOk("select a from test order by a offset 5"); |
| ParsesOk("select a from test limit 10 offset 5"); // Parses OK, doesn't analyze |
| ParsesOk("select a from test offset 5"); // Parses OK, doesn't analyze |
| ParsesOk("select a from (select a from test offset 5) A"); // Doesn't analyze |
| ParsesOk("select a from (select a from test order by a offset 5) A"); |
| ParserError("select a from test order by a limit offset"); |
| ParserError("select a from test order by a limit offset 5"); |
| } |
| |
| @Test |
| public void TestUnion() { |
| // Single union test. |
| ParsesOk("select a from test union select a from test"); |
| ParsesOk("select a from test union all select a from test"); |
| ParsesOk("select a from test union distinct select a from test"); |
| // Chained union test. |
| ParsesOk("select a from test union select a from test " + |
| "union select a from test union select a from test"); |
| ParsesOk("select a from test union all select a from test " + |
| "union all select a from test union all select a from test"); |
| ParsesOk("select a from test union distinct select a from test " + |
| "union distinct select a from test union distinct select a from test "); |
| // Mixed union with all and distinct. |
| ParsesOk("select a from test union select a from test " + |
| "union all select a from test union distinct select a from test"); |
| // No from clause. |
| ParsesOk("select sin() union select cos()"); |
| ParsesOk("select sin() union all select cos()"); |
| ParsesOk("select sin() union distinct select cos()"); |
| |
| // All select blocks in parenthesis. |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test)"); |
| // Union with order by, |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test) order by a"); |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test) order by a nulls first"); |
| // Union with limit. |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test) limit 10"); |
| // Union with order by, offset and limit. |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test) order by a limit 10"); |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test) order by a " + |
| "nulls first limit 10"); |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union (select a from test) union (select a from test) order by a " + |
| "nulls first offset 10"); |
| ParserError("select a from test union (select a from test) " + |
| "union (select a from test) union (select a from test) offset 10"); |
| // Union with some select blocks in parenthesis, and others not. |
| ParsesOk("(select a from test) union select a from test " + |
| "union (select a from test) union select a from test"); |
| ParsesOk("select a from test union (select a from test) " + |
| "union select a from test union (select a from test)"); |
| // Union with order by, offset and limit binding to last select. |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union select a from test union select a from test order by a limit 10"); |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union select a from test union select a from test order by a offset 10"); |
| ParsesOk("(select a from test) union (select a from test) " + |
| "union select a from test union select a from test order by a"); |
| // Union with order by and limit. |
| // Last select with order by and limit is in parenthesis. |
| ParsesOk("select a from test union (select a from test) " + |
| "union select a from test union (select a from test order by a limit 10) " + |
| "order by a limit 1"); |
| ParsesOk("select a from test union (select a from test) " + |
| "union select a from test union (select a from test order by a offset 10) " + |
| "order by a limit 1"); |
| ParsesOk("select a from test union (select a from test) " + |
| "union select a from test union (select a from test order by a) " + |
| "order by a limit 1"); |
| // Union with order by, offset in first operand. |
| ParsesOk("select a from test order by a union select a from test"); |
| ParsesOk("select a from test order by a offset 5 union select a from test"); |
| ParsesOk("select a from test offset 5 union select a from test"); |
| // Union with order by and limit. |
| // Last select with order by and limit is not in parenthesis. |
| ParsesOk("select a from test union select a from test " + |
| "union select a from test union select a from test order by a limit 10 " + |
| "order by a limit 1"); |
| |
| // Nested unions with order by and limit. |
| ParsesOk("select a union " + |
| "((select b) union (select c) order by 1 limit 1)"); |
| ParsesOk("select a union " + |
| "((select b) union " + |
| " ((select c) union (select d) " + |
| " order by 1 limit 1) " + |
| " order by 1 limit 1)"); |
| |
| // Union in insert query. |
| ParsesOk("insert into table t select a from test union select a from test"); |
| ParsesOk("insert into table t select a from test union select a from test " + |
| "union select a from test union select a from test"); |
| ParsesOk("insert overwrite table t select a from test union select a from test"); |
| ParsesOk("insert overwrite table t select a from test union select a from test " + |
| "union select a from test union select a from test"); |
| |
| // Union in upsert query. |
| ParsesOk("upsert into table t select a from test union select a from test"); |
| ParsesOk("upsert into table t select a from test union select a from test " + |
| "union select a from test union select a from test"); |
| |
| // No complete select statement on lhs. |
| ParserError("a from test union select a from test"); |
| // No complete select statement on rhs. |
| ParserError("select a from test union a from test"); |
| // Union cannot be a column or table since it's a keyword. |
| ParserError("select union from test"); |
| ParserError("select a from union"); |
| } |
| |
| @Test |
| public void TestValuesStmt() throws AnalysisException { |
| // Values stmt with a single row. |
| ParsesOk("values(1, 'a', abc, 1.0, *)"); |
| ParsesOk("select * from (values(1, 'a', abc, 1.0, *)) as t"); |
| ParsesOk("values(1, 'a', abc, 1.0, *) union all values(1, 'a', abc, 1.0, *)"); |
| ParsesOk("insert into t values(1, 'a', abc, 1.0, *)"); |
| ParsesOk("upsert into t values(1, 'a', abc, 1.0, *)"); |
| // Values stmt with multiple rows. |
| ParsesOk("values(1, abc), ('x', cde), (2), (efg, fgh, ghi)"); |
| ParsesOk("select * from (values(1, abc), ('x', cde), (2), (efg, fgh, ghi)) as t"); |
| ParsesOk("values(1, abc), ('x', cde), (2), (efg, fgh, ghi) " + |
| "union all values(1, abc), ('x', cde), (2), (efg, fgh, ghi)"); |
| ParsesOk("insert into t values(1, abc), ('x', cde), (2), (efg, fgh, ghi)"); |
| ParsesOk("upsert into t values(1, abc), ('x', cde), (2), (efg, fgh, ghi)"); |
| // Test additional parenthesis. |
| ParsesOk("(values(1, abc), ('x', cde), (2), (efg, fgh, ghi))"); |
| ParsesOk("values((1, abc), ('x', cde), (2), (efg, fgh, ghi))"); |
| ParsesOk("(values((1, abc), ('x', cde), (2), (efg, fgh, ghi)))"); |
| // Test alias inside select list to assign column names. |
| ParsesOk("values(1 as x, 2 as y, 3 as z)"); |
| // Test order by and limit. |
| ParsesOk("values(1, 'a') limit 10"); |
| ParsesOk("values(1, 'a') order by 1"); |
| ParsesOk("values(1, 'a') order by 1 limit 10"); |
| ParsesOk("values(1, 'a') order by 1 offset 10"); |
| ParsesOk("values(1, 'a') offset 10"); |
| ParsesOk("values(1, 'a'), (2, 'b') order by 1 limit 10"); |
| ParsesOk("values((1, 'a'), (2, 'b')) order by 1 limit 10"); |
| |
| ParserError("values()"); |
| ParserError("values 1, 'a', abc, 1.0"); |
| ParserError("values(1, 'a') values(1, 'a')"); |
| ParserError("select values(1, 'a')"); |
| ParserError("select * from values(1, 'a', abc, 1.0) as t"); |
| ParserError("values((1, 2, 3), values(1, 2, 3))"); |
| ParserError("values((1, 'a'), (1, 'a') order by 1)"); |
| ParserError("values((1, 'a'), (1, 'a') limit 10)"); |
| } |
| |
| @Test |
| public void TestWithClause() throws AnalysisException { |
| ParsesOk("with t as (select 1 as a) select a from t"); |
| ParsesOk("with t(x) as (select 1 as a) select x from t"); |
| ParsesOk("with t as (select c from tab) select * from t"); |
| ParsesOk("with t(x, y) as (select * from tab) select * from t"); |
| ParsesOk("with t as (values(1, 2, 3), (4, 5, 6)) select * from t"); |
| ParsesOk("with t(x, y, z) as (values(1, 2, 3), (4, 5, 6)) select * from t"); |
| ParsesOk("with t1 as (select 1 as a), t2 as (select 2 as a) select a from t1"); |
| ParsesOk("with t1 as (select c from tab), t2 as (select c from tab)" + |
| "select c from t2"); |
| ParsesOk("with t1(x) as (select c from tab), t2(x) as (select c from tab)" + |
| "select x from t2"); |
| // With clause and union statement. |
| ParsesOk("with t1 as (select 1 as a), t2 as (select 2 as a)" + |
| "select a from t1 union all select a from t2"); |
| // With clause and join. |
| ParsesOk("with t1 as (select 1 as a), t2 as (select 2 as a)" + |
| "select a from t1 inner join t2 on t1.a = t2.a"); |
| // With clause in inline view. |
| ParsesOk("select * from (with t as (select 1 as a) select * from t) as a"); |
| ParsesOk("select * from (with t(x) as (select 1 as a) select * from t) as a"); |
| // With clause in query statement of insert statement. |
| ParsesOk("insert into x with t as (select * from tab) select * from t"); |
| ParsesOk("insert into x with t(x, y) as (select * from tab) select * from t"); |
| ParsesOk("insert into x with t as (values(1, 2, 3)) select * from t"); |
| ParsesOk("insert into x with t(x, y) as (values(1, 2, 3)) select * from t"); |
| // With clause before insert statement. |
| ParsesOk("with t as (select 1) insert into x select * from t"); |
| ParsesOk("with t(x) as (select 1) insert into x select * from t"); |
| // With clause in query statement of upsert statement. |
| ParsesOk("upsert into x with t as (select * from tab) select * from t"); |
| ParsesOk("upsert into x with t(x, y) as (select * from tab) select * from t"); |
| ParsesOk("upsert into x with t as (values(1, 2, 3)) select * from t"); |
| ParsesOk("upsert into x with t(x, y) as (values(1, 2, 3)) select * from t"); |
| // With clause before upsert statement. |
| ParsesOk("with t as (select 1) upsert into x select * from t"); |
| ParsesOk("with t(x) as (select 1) upsert into x select * from t"); |
| |
| // Test quoted identifier or string literal as table alias. |
| ParsesOk("with `t1` as (select 1 a), 't2' as (select 2 a), \"t3\" as (select 3 a)" + |
| "select a from t1 union all select a from t2 union all select a from t3"); |
| // Multiple with clauses. Operands must be in parenthesis to |
| // have their own with clause. |
| ParsesOk("with t as (select 1) " + |
| "(with t as (select 2) select * from t) union all " + |
| "(with t as (select 3) select * from t)"); |
| ParsesOk("with t as (select 1) " + |
| "(with t as (select 2) select * from t) union all " + |
| "(with t as (select 3) select * from t) order by 1 limit 1"); |
| // Multiple with clauses. One before the insert and one inside the query statement. |
| ParsesOk("with t as (select 1) insert into x with t as (select 2) select * from t"); |
| ParsesOk("with t(c1) as (select 1) " + |
| "insert into x with t(c2) as (select 2) select * from t"); |
| // Multiple with clauses. One before the upsert and one inside the query statement. |
| ParsesOk("with t as (select 1) upsert into x with t as (select 2) select * from t"); |
| ParsesOk("with t(c1) as (select 1) " + |
| "upsert into x with t(c2) as (select 2) select * from t"); |
| |
| // Empty with clause. |
| ParserError("with t as () select 1"); |
| ParserError("with t(x) as () select 1"); |
| // No labels inside parenthesis. |
| ParserError("with t() as (select 1 as a) select a from t"); |
| // Missing select, union or insert statement after with clause. |
| ParserError("select * from (with t as (select 1 as a)) as a"); |
| ParserError("with t as (select 1)"); |
| // Missing parenthesis around with query statement. |
| ParserError("with t as select 1 as a select a from t"); |
| ParserError("with t as select 1 as a union all select a from t"); |
| ParserError("with t1 as (select 1 as a), t2 as select 2 as a select a from t"); |
| ParserError("with t as select 1 as a select a from t"); |
| // Missing parenthesis around column labels. |
| ParserError("with t c1 as (select 1 as a) select c1 from t"); |
| // Insert in with clause is not valid. |
| ParserError("with t as (insert into x select * from tab) select * from t"); |
| ParserError("with t(c1) as (insert into x select * from tab) select * from t"); |
| // Upsert in with clause is not valid. |
| ParserError("with t as (upsert into x select * from tab) select * from t"); |
| ParserError("with t(c1) as (upsert into x select * from tab) select * from t"); |
| // Union operands need to be parenthesized to have their own with clause. |
| ParserError("select * from t union all with t as (select 2) select * from t"); |
| } |
| |
| @Test |
| public void TestNumericLiteralMinMaxValues() { |
| // Test integer types. |
| ParsesOk(String.format("select %s", Byte.toString(Byte.MIN_VALUE))); |
| ParsesOk(String.format("select %s", Byte.toString(Byte.MAX_VALUE))); |
| ParsesOk(String.format("select %s", Short.toString(Short.MIN_VALUE))); |
| ParsesOk(String.format("select %s", Short.toString(Short.MAX_VALUE))); |
| ParsesOk(String.format("select %s", Integer.toString(Integer.MIN_VALUE))); |
| ParsesOk(String.format("select %s", Integer.toString(Integer.MAX_VALUE))); |
| ParsesOk(String.format("select %s", Long.toString(Long.MIN_VALUE))); |
| ParsesOk(String.format("select %s", Long.toString(Long.MAX_VALUE))); |
| |
| // Overflowing long integers parse ok. Analysis will handle it. |
| ParsesOk(String.format("select %s1", Long.toString(Long.MIN_VALUE))); |
| ParsesOk(String.format("select %s1", Long.toString(Long.MAX_VALUE))); |
| // Test min int64-1. |
| BigInteger minMinusOne = BigInteger.valueOf(Long.MAX_VALUE); |
| minMinusOne = minMinusOne.add(BigInteger.ONE); |
| ParsesOk(String.format("select %s", minMinusOne.toString())); |
| // Test max int64+1. |
| BigInteger maxPlusOne = BigInteger.valueOf(Long.MAX_VALUE); |
| maxPlusOne = maxPlusOne.add(BigInteger.ONE); |
| ParsesOk(String.format("select %s", maxPlusOne.toString())); |
| |
| // Test floating-point types. |
| ParsesOk(String.format("select %s", Float.toString(Float.MIN_VALUE))); |
| ParsesOk(String.format("select %s", Float.toString(Float.MAX_VALUE))); |
| ParsesOk(String.format("select -%s", Float.toString(Float.MIN_VALUE))); |
| ParsesOk(String.format("select -%s", Float.toString(Float.MAX_VALUE))); |
| ParsesOk(String.format("select %s", Double.toString(Double.MIN_VALUE))); |
| ParsesOk(String.format("select %s", Double.toString(Double.MAX_VALUE))); |
| ParsesOk(String.format("select -%s", Double.toString(Double.MIN_VALUE))); |
| ParsesOk(String.format("select -%s", Double.toString(Double.MAX_VALUE))); |
| |
| // Test overflow and underflow |
| ParserError(String.format("select %s1", Double.toString(Double.MIN_VALUE))); |
| ParserError(String.format("select %s1", Double.toString(Double.MAX_VALUE))); |
| } |
| |
| @Test |
| public void TestIdentQuoting() { |
| ParsesOk("select a from `t`"); |
| ParsesOk("select a from default.`t`"); |
| ParsesOk("select a from default.t"); |
| ParsesOk("select a from default.`t`"); |
| ParsesOk("select 01a from default.`01_t`"); |
| |
| ParsesOk("select `a` from default.t"); |
| ParsesOk("select `tbl`.`a` from default.t"); |
| ParsesOk("select `db`.`tbl`.`a` from default.t"); |
| ParsesOk("select `12db`.`tbl`.`12_a` from default.t"); |
| |
| // Make sure quoted float literals are identifiers. |
| ParsesOk("select `8e6`", SlotRef.class); |
| ParsesOk("select `4.5e2`", SlotRef.class); |
| ParsesOk("select `.7e9`", SlotRef.class); |
| |
| // Mixed quoting |
| ParsesOk("select `db`.tbl.`a` from default.t"); |
| ParsesOk("select `db.table.a` from default.t"); |
| |
| // Identifiers consisting of only whitespace not allowed. |
| ParserError("select a from ` `"); |
| ParserError("select a from ` `"); |
| // Empty quoted identifier doesn't parse. |
| ParserError("select a from ``"); |
| |
| // Whitespace can be interspersed with other characters. |
| // Whitespace is trimmed from the beginning and end of an identifier. |
| ParsesOk("select a from `a a a `"); |
| ParsesOk("select a from ` a a a`"); |
| ParsesOk("select a from ` a a a `"); |
| |
| // Quoted identifiers can contain any characters except "`". |
| ParsesOk("select a from `all types`"); |
| ParsesOk("select a from default.`all types`"); |
| ParsesOk("select a from `~!@#$%^&*()-_=+|;:'\",<.>/?`"); |
| // Quoted identifiers do not unescape escape sequences. |
| ParsesOk("select a from `ab\rabc`"); |
| ParsesOk("select a from `ab\tabc`"); |
| ParsesOk("select a from `ab\fabc`"); |
| ParsesOk("select a from `ab\babc`"); |
| ParsesOk("select a from `ab\nabc`"); |
| // Test non-printable control characters inside quoted identifiers. |
| ParsesOk("select a from `abc\u0000abc`"); |
| ParsesOk("select a from `abc\u0019abc`"); |
| ParsesOk("select a from `abc\u007fabc`"); |
| |
| // Quoted identifiers can contain keywords. |
| ParsesOk("select `select`, `insert`, `upsert` from `table` where `where` = 10"); |
| |
| // Quoted identifiers cannot contain "`" |
| ParserError("select a from `abcde`abcde`"); |
| ParserError("select a from `abc\u0060abc`"); |
| |
| // Wrong quotes |
| ParserError("select a from 'default'.'t'"); |
| |
| // Lots of quoting |
| ParsesOk( |
| "select `db`.`tbl`.`a` from `default`.`t` `alias` where `alias`.`col` = 'string'" |
| + " group by `alias`.`col`"); |
| } |
| |
| @Test |
| public void TestLiteralExprs() { |
| // negative integer literal |
| ParsesOk("select -1 from t where -1", NumericLiteral.class); |
| ParsesOk("select - 1 from t where - 1", NumericLiteral.class); |
| ParsesOk("select a - - 1 from t where a - - 1", ArithmeticExpr.class); |
| ParsesOk("select a - - - 1 from t where a - - - 1", ArithmeticExpr.class); |
| |
| // positive integer literal |
| ParsesOk("select +1 from t where +1", NumericLiteral.class); |
| ParsesOk("select + 1 from t where + 1", NumericLiteral.class); |
| ParsesOk("select a + + 1 from t where a + + 1", ArithmeticExpr.class); |
| ParsesOk("select a + + + 1 from t where a + + + 1", ArithmeticExpr.class); |
| |
| // float literals |
| ParsesOk("select +1.0 from t where +1.0", NumericLiteral.class); |
| ParsesOk("select +-1.0 from t where +-1.0", NumericLiteral.class); |
| ParsesOk("select +1.-0 from t where +1.-0", ArithmeticExpr.class); |
| |
| // test scientific notation |
| ParsesOk("select 8e6 from t where 8e6", NumericLiteral.class); |
| ParsesOk("select +8e6 from t where +8e6", NumericLiteral.class); |
| ParsesOk("select 8e+6 from t where 8e+6", NumericLiteral.class); |
| ParsesOk("select -8e6 from t where -8e6", NumericLiteral.class); |
| ParsesOk("select 8e-6 from t where 8e-6", NumericLiteral.class); |
| ParsesOk("select -8e-6 from t where -8e-6", NumericLiteral.class); |
| // with a decimal point |
| ParsesOk("select 4.5e2 from t where 4.5e2", NumericLiteral.class); |
| ParsesOk("select +4.5e2 from t where +4.5e2", NumericLiteral.class); |
| ParsesOk("select 4.5e+2 from t where 4.5e+2", NumericLiteral.class); |
| ParsesOk("select -4.5e2 from t where -4.5e2", NumericLiteral.class); |
| ParsesOk("select 4.5e-2 from t where 4.5e-2", NumericLiteral.class); |
| ParsesOk("select -4.5e-2 from t where -4.5e-2", NumericLiteral.class); |
| // with a decimal point but without a number before the decimal |
| ParsesOk("select .7e9 from t where .7e9", NumericLiteral.class); |
| ParsesOk("select +.7e9 from t where +.7e9", NumericLiteral.class); |
| ParsesOk("select .7e+9 from t where .7e+9", NumericLiteral.class); |
| ParsesOk("select -.7e9 from t where -.7e9", NumericLiteral.class); |
| ParsesOk("select .7e-9 from t where .7e-9", NumericLiteral.class); |
| ParsesOk("select -.7e-9 from t where -.7e-9", NumericLiteral.class); |
| |
| // mixed signs |
| ParsesOk("select -+-1 from t where -+-1", NumericLiteral.class); |
| ParsesOk("select - +- 1 from t where - +- 1", NumericLiteral.class); |
| ParsesOk("select 1 + -+ 1 from t where 1 + -+ 1", ArithmeticExpr.class); |
| |
| // Boolean literals |
| ParsesOk("select true from t where true", BoolLiteral.class); |
| ParsesOk("select false from t where false", BoolLiteral.class); |
| |
| // Date literals |
| ParsesOk("select date '2011-01-01'", DateLiteral.class); |
| ParsesOk("select date '2011-1-01'", DateLiteral.class); |
| ParsesOk("select date '2011-1-1'", DateLiteral.class); |
| ParserError("select date '2011-001-1'"); |
| ParserError("select date '2011-01-001'"); |
| ParserError("select date '2011-oct-01'"); |
| ParserError("select date '2011-01-0'"); |
| ParserError("select date '2011-01-40'"); |
| ParserError("select date '2011-01-'"); |
| ParserError("select date '2011--01'"); |
| ParserError("select date '2001-02-31'"); |
| |
| // Null literal |
| ParsesOk("select NULL from t where NULL", NullLiteral.class); |
| |
| // -- is parsed as a comment starter |
| ParserError("select --1"); |
| |
| // Postfix operators must be binary |
| ParserError("select 1- from t"); |
| ParserError("select 1 + from t"); |
| |
| // Only - and + can be unary operators |
| ParserError("select /1 from t"); |
| ParserError("select *1 from t"); |
| ParserError("select &1 from t"); |
| ParserError("select =1 from t"); |
| |
| // test string literals with and without quotes in the literal |
| ParsesOk("select 'five', 5, 5.0, i + 5 from t", StringLiteral.class); |
| ParsesOk("select \"\\\"five\\\"\" from t\n", StringLiteral.class); |
| ParsesOk("select \"\'five\'\" from t\n", StringLiteral.class); |
| ParsesOk("select \"\'five\" from t\n", StringLiteral.class); |
| |
| // missing quotes |
| ParserError("select \'5 from t"); |
| ParserError("select \"5 from t"); |
| ParserError("select '5 from t"); |
| ParserError("select `5 from t"); |
| ParserError("select \"\"five\"\" from t\n"); |
| ParserError("select 5.0.5 from t"); |
| |
| // we implement MySQL-style escape sequences just like Hive: |
| // http://dev.mysql.com/doc/refman/5.0/en/string-literals.html |
| // test escape sequences with single and double quoted strings |
| testStringLiteral("\\0"); |
| testStringLiteral("\\\\"); |
| testStringLiteral("\\b"); |
| testStringLiteral("\\n"); |
| testStringLiteral("\\r"); |
| testStringLiteral("\\t"); |
| testStringLiteral("\\Z"); |
| // MySQL deals with escapes and "%" and "_" specially for pattern matching |
| testStringLiteral("\\%"); |
| testStringLiteral("\\\\%"); |
| testStringLiteral("\\_"); |
| testStringLiteral("\\\\_"); |
| // all escape sequences back-to-back |
| testStringLiteral("\\0\\\\\\b\\n\\r\\t\\Z\\%\\\\%\\_\\\\_"); |
| // mixed regular chars and escape sequences |
| testStringLiteral("a\\0b\\\\c\\bd\\ne\\rf\\tg\\Zh\\%i\\\\%j\\_k\\\\_l"); |
| // escaping non-escape chars should scan ok and result in the character itself |
| testStringLiteral("\\a\\b\\c\\d\\1\\2\\3\\$\\&\\*"); |
| // Single backslash is a scanner error. |
| ParserError("select \"\\\" from t", |
| "Syntax error in line 1:\n" + |
| "select \"\\\" from t\n" + |
| " ^\n" + |
| "Encountered: Unexpected character"); |
| // Unsupported character |
| ParserError("@", |
| "Syntax error in line 1:\n" + |
| "@\n" + |
| "^\n" + |
| "Encountered: Unexpected character"); |
| ParsesOk("SELECT '@'"); |
| } |
| |
| // test string literal s with single and double quotes |
| private void testStringLiteral(String s) { |
| String singleQuoteQuery = "select " + "'" + s + "'" + " from t"; |
| String doubleQuoteQuery = "select " + "\"" + s + "\"" + " from t"; |
| ParsesOk(singleQuoteQuery, StringLiteral.class); |
| ParsesOk(doubleQuoteQuery, StringLiteral.class); |
| } |
| |
| @Test |
| public void TestFunctionCallExprs() { |
| ParsesOk("select f1(5), f2('five'), f3(5.0, i + 5) from t"); |
| ParsesOk("select f1(true), f2(true and false), f3(null) from t"); |
| ParsesOk("select f1(*)"); |
| ParsesOk("select f1(distinct col)"); |
| ParsesOk("select f1(distinct col, col2)"); |
| ParsesOk("select decode(col, col2, col3)"); |
| // nullif should rewrite to if |
| assertEquals("SELECT if(col IS DISTINCT FROM col2, col, NULL) FROM t", |
| ParsesOk("select nullif(col, col2) from t").toSql()); |
| assertEquals("SELECT if(col IS DISTINCT FROM col2, col, NULL) FROM t", |
| ParsesOk("select _impala_builtins.nullif(col, col2) from t").toSql()); |
| ParserError("select f( from t"); |
| ParserError("select f(5.0 5.0) from t"); |
| } |
| |
| @Test |
| public void TestArithmeticExprs() { |
| for (String lop: operands_) { |
| for (String rop: operands_) { |
| for (ArithmeticExpr.Operator op : ArithmeticExpr.Operator.values()) { |
| String expr = null; |
| switch (op.getPos()) { |
| case BINARY_INFIX: |
| expr = String.format("%s %s %s", lop, op.toString(), rop); |
| break; |
| case UNARY_POSTFIX: |
| expr = String.format("%s %s", lop, op.toString()); |
| break; |
| case UNARY_PREFIX: |
| expr = String.format("%s %s", op.toString(), lop); |
| break; |
| default: |
| fail("Unknown operator kind: " + op.getPos()); |
| } |
| ParsesOk(String.format("select %s from t where %s", expr, expr)); |
| } |
| } |
| } |
| ParserError("select (i + 5)(1 - i) from t"); |
| ParserError("select %a from t"); |
| ParserError("select *a from t"); |
| ParserError("select /a from t"); |
| ParserError("select &a from t"); |
| ParserError("select |a from t"); |
| ParserError("select ^a from t"); |
| ParserError("select a ~ a from t"); |
| } |
| |
| @Test |
| public void TestFactorialPrecedenceAssociativity() { |
| // Test factorial precedence relative to other arithmetic operators. |
| // Should be parsed as 3 + (3!) |
| // TODO: disabled b/c IMPALA-2149 - low precedence of prefix ! prevents this from |
| // parsing in the expected way |
| SelectStmt stmt = (SelectStmt) ParsesOk("SELECT 3 + 3!"); |
| Expr e = stmt.getSelectList().getItems().get(0).getExpr(); |
| assertTrue(e instanceof ArithmeticExpr); |
| // ArithmeticExpr ae = (ArithmeticExpr) e; |
| // assertEquals("Expected ! to bind more tightly than +", |
| // ArithmeticExpr.Operator.ADD, ae.getOp()); |
| // assertEquals(2, ae.getChildren().size()); |
| // assertTrue(ae.getChild(1) instanceof ArithmeticExpr); |
| // assertEquals(ArithmeticExpr.Operator.FACTORIAL, |
| // ((ArithmeticExpr)ae.getChild(1)).getOp()); |
| |
| // Test factorial associativity. |
| stmt = (SelectStmt) ParsesOk("SELECT 3! = 4"); |
| // Should be parsed as (3!) = (4) |
| e = stmt.getSelectList().getItems().get(0).getExpr(); |
| assertTrue(e instanceof BinaryPredicate); |
| BinaryPredicate bp = (BinaryPredicate) e; |
| assertEquals(BinaryPredicate.Operator.EQ, bp.getOp()); |
| assertEquals(2, bp.getChildren().size()); |
| |
| // Test != not broken |
| stmt = (SelectStmt) ParsesOk("SELECT 3 != 4"); |
| // Should be parsed as (3) != (4) |
| e = stmt.getSelectList().getItems().get(0).getExpr(); |
| assertTrue(e instanceof BinaryPredicate); |
| bp = (BinaryPredicate) e; |
| assertEquals(BinaryPredicate.Operator.NE, bp.getOp()); |
| assertEquals(2, bp.getChildren().size()); |
| } |
| |
| /** |
| * We have three variants of timestamp arithmetic exprs, as in MySQL: |
| * http://dev.mysql.com/doc/refman/5.5/en/date-and-time-functions.html |
| * (section #function_date-add) |
| * 1. Non-function-call like version, e.g., 'a + interval b timeunit' |
| * 2. Beginning with an interval (only for '+'), e.g., 'interval b timeunit + a' |
| * 3. Function-call like version, e.g., date_add(a, interval b timeunit) |
| */ |
| @Test |
| public void TestTimestampArithmeticExprs() { |
| // Tests all valid time units. |
| for (TimeUnit timeUnit : TimeUnit.values()) { |
| // Non-function call like versions. |
| ParsesOk("select a + interval b " + timeUnit.toString()); |
| ParsesOk("select a - interval b " + timeUnit.toString()); |
| ParsesOk("select NULL + interval NULL " + timeUnit.toString()); |
| ParsesOk("select NULL - interval NULL " + timeUnit.toString()); |
| // Reversed interval and timestamp is ok for addition. |
| ParsesOk("select interval b " + timeUnit.toString() + " + a"); |
| ParsesOk("select interval NULL " + timeUnit.toString() + " + NULL"); |
| // Reversed interval and timestamp is an error for subtraction. |
| ParserError("select interval b " + timeUnit.toString() + " - a"); |
| // Function-call like versions. |
| ParsesOk("select date_add(a, interval b " + timeUnit.toString() + ")"); |
| ParsesOk("select date_sub(a, interval b " + timeUnit.toString() + ")"); |
| ParsesOk("select date_add(NULL, interval NULL " + timeUnit.toString() + ")"); |
| ParsesOk("select date_sub(NULL, interval NULL " + timeUnit.toString() + ")"); |
| // Invalid function name for timestamp arithmetic expr should parse ok. |
| ParsesOk("select error(a, interval b " + timeUnit.toString() + ")"); |
| // Invalid time unit parses ok. |
| ParsesOk("select error(a, interval b error)"); |
| // Missing 'interval' keyword. Note that the non-function-call like version will |
| // pass without 'interval' because the time unit is recognized as an alias. |
| ParserError("select date_add(a, b " + timeUnit.toString() + ")"); |
| ParserError("select date_sub(a, b " + timeUnit.toString() + ")"); |
| |
| ParserError("select date_sub(distinct NULL, interval NULL " + |
| timeUnit.toString() + ")"); |
| ParserError("select date_sub(*, interval NULL " + timeUnit.toString() + ")"); |
| } |
| |
| // Test chained timestamp arithmetic exprs. |
| ParsesOk("select a + interval b years + interval c months + interval d days"); |
| ParsesOk("select a - interval b years - interval c months - interval d days"); |
| ParsesOk("select a + interval b years - interval c months + interval d days"); |
| // Starting with interval. |
| ParsesOk("select interval b years + a + interval c months + interval d days"); |
| ParsesOk("select interval b years + a - interval c months - interval d days"); |
| ParsesOk("select interval b years + a - interval c months + interval d days"); |
| |
| // To many arguments. |
| ParserError("select date_sub(a, c, interval b year)"); |
| ParserError("select date_sub(a, interval b year, c)"); |
| } |
| |
| @Test |
| public void TestCaseExprs() { |
| // Test regular exps. |
| ParsesOk("select case a when '5' then x when '6' then y else z end from t"); |
| ParsesOk("select case when 'a' then x when false then y else z end from t"); |
| // Test predicates in case, when, then, and else exprs. |
| ParsesOk("select case when a > 2 then x when false then false else true end from t"); |
| ParsesOk("select case false when a > 2 then x when '6' then false else true end " + |
| "from t"); |
| // Test NULLs; |
| ParsesOk("select case NULL when NULL then NULL when NULL then NULL else NULL end " + |
| "from t"); |
| ParsesOk("select case when NULL then NULL when NULL then NULL else NULL end from t"); |
| // Missing end. |
| ParserError("select case a when true then x when false then y else z from t"); |
| // Missing else after first when. |
| ParserError("select case a when true when false then y else z end from t"); |
| // Incorrectly placed comma. |
| ParserError("select case a when true, false then y else z end from t"); |
| } |
| |
| @Test |
| public void TestCastExprs() { |
| ParsesOk("select cast(a + 5.0 as string) from t"); |
| ParsesOk("select cast(NULL as string) from t"); |
| ParsesOk("select cast('05-01-2017' as timestamp format 'MM-dd-yyyy')"); |
| ParsesOk("select cast('2017.01.02' as date format 'YYYY-MM-DD')"); |
| ParserError("select cast(a + 5.0 as badtype) from t"); |
| ParserError("select cast(a + 5.0, string) from t"); |
| ParserError("select cast('05-01-2017' as timestamp format 12345)"); |
| ParserError("select cast('05-01-2017' as timestamp format )"); |
| ParserError("select cast('05-01-2017' as timestamp format NULL)"); |
| ParserError("select cast('05-01-2017' as date format NULL)"); |
| } |
| |
| @Test |
| public void TestConditionalExprs() { |
| ParsesOk("select if(TRUE, TRUE, FALSE) from t"); |
| ParsesOk("select if(NULL, NULL, NULL) from t"); |
| ParsesOk("select c1, c2, if(TRUE, TRUE, FALSE) from t"); |
| ParsesOk("select if(1 = 2, c1, c2) from t"); |
| ParsesOk("select if(1 = 2, c1, c2)"); |
| ParserError("select if()"); |
| } |
| |
| @Test |
| public void TestAggregateExprs() { |
| ParsesOk("select count(*), count(a), count(distinct a, b) from t"); |
| ParsesOk("select count(NULL), count(TRUE), count(FALSE), " + |
| "count(distinct TRUE, FALSE, NULL) from t"); |
| ParsesOk("select count(all *) from t"); |
| ParsesOk("select count(all 1) from t"); |
| ParsesOk("select min(a), min(distinct a) from t"); |
| ParsesOk("select max(a), max(distinct a) from t"); |
| ParsesOk("select sum(a), sum(distinct a) from t"); |
| ParsesOk("select avg(a), avg(distinct a) from t"); |
| ParsesOk("select distinct a, b, c from t"); |
| ParsesOk("select distinctpc(a), distinctpc(distinct a) from t"); |
| ParsesOk("select distinctpcsa(a), distinctpcsa(distinct a) from t"); |
| ParsesOk("select ndv(a), ndv(distinct a) from t"); |
| ParsesOk("select group_concat(a) from t"); |
| ParsesOk("select group_concat(a, ', ') from t"); |
| ParsesOk("select group_concat(a, ', ', c) from t"); |
| } |
| |
| @Test |
| public void TestAnalyticExprs() { |
| ParsesOk("select sum(v) over (partition by a, 2*b order by 3*c rows between " |
| + "2+2 preceding and 2-2 following) from t"); |
| ParsesOk("select sum(v) over (order by 3*c rows between " |
| + "unbounded preceding and unbounded following) from t"); |
| ParsesOk("select sum(v) over (partition by a, 2*b) from t"); |
| ParsesOk("select sum(v) over (partition by a, 2*b order by 3*c range between " |
| + "unbounded preceding and unbounded following) from t"); |
| ParsesOk("select sum(v) over (order by 3*c range between " |
| + "2 following and 4 following) from t"); |
| ParsesOk("select sum(v) over (partition by a, 2*b) from t"); |
| ParsesOk("select 2 * x, sum(v) over (partition by a, 2*b order by 3*c rows between " |
| + "2+2 preceding and 2-2 following), rank() over (), y from t"); |
| // not a function call |
| ParserError("select v over (partition by a, 2*b order by 3*c rows between 2 " |
| + "preceding and 2 following) from t"); |
| // something missing |
| ParserError("select sum(v) over (partition a, 2*b order by 3*c rows between " |
| + "unbounded preceding and current row) from t"); |
| ParserError("select sum(v) over (partition by a, 2*b order 3*c rows between 2 " |
| + "preceding and 2 following) from t"); |
| ParserError("select sum(v) over (partition by a, 2*b order by 3*c rows 2 " |
| + "preceding and 2 following) from t"); |
| ParsesOk("select sum(v) over (partition by a, 2*b) from t"); |
| // Special case for DECODE, which results in a parse error when used in |
| // an analytic context. Note that "ecode() over ()" would parse fine since |
| // that is handled by the standard function call lookup. |
| ParserError("select decode(1, 2, 3) over () from t"); |
| } |
| |
| @Test |
| public void TestPredicates() { |
| ArrayList<String> operations = new ArrayList<String>(); |
| for (BinaryPredicate.Operator op : BinaryPredicate.Operator.values()) { |
| operations.add(op.toString()); |
| } |
| operations.add("like"); |
| operations.add("ilike"); |
| operations.add("rlike"); |
| operations.add("regexp"); |
| operations.add("iregexp"); |
| |
| ArrayList<String> boolTestVals = new ArrayList<String>(); |
| boolTestVals.add("null"); |
| boolTestVals.add("unknown"); |
| boolTestVals.add("true"); |
| boolTestVals.add("false"); |
| |
| for (String lop : operands_) { |
| for (String rop : operands_) { |
| for (String op : operations) { |
| String expr = String.format("%s %s %s", lop, op.toString(), rop); |
| ParsesOk(String.format("select %s from t where %s", expr, expr)); |
| } |
| } |
| for (String val : boolTestVals) { |
| String isExpr = String.format("%s is %s", lop, val); |
| String isNotExpr = String.format("%s is not %s", lop, val); |
| ParsesOk(String.format("select %s from t where %s", isExpr, isExpr)); |
| ParsesOk(String.format("select %s from t where %s", isNotExpr, isNotExpr)); |
| } |
| ParserError(String.format("select %s is nonsense", lop)); |
| ParserError(String.format("select %s is not nonsense", lop)); |
| } |
| } |
| |
| private void testCompoundPredicates(String andStr, String orStr, String notStr) { |
| // select a, b, c from t where a = 5 and f(b) |
| ParsesOk("select a, b, c from t where a = 5 " + andStr + " f(b)"); |
| // select a, b, c from t where a = 5 or f(b) |
| ParsesOk("select a, b, c from t where a = 5 " + orStr + " f(b)"); |
| // select a, b, c from t where (a = 5 or f(b)) and c = 7 |
| ParsesOk("select a, b, c from t where (a = 5 " + orStr + " f(b)) " + |
| andStr + " c = 7"); |
| // select a, b, c from t where not a = 5 |
| ParsesOk("select a, b, c from t where " + notStr + "a = 5"); |
| // select a, b, c from t where not f(a) |
| ParsesOk("select a, b, c from t where " + notStr + "f(a)"); |
| // select a, b, c from t where (not a = 5 or not f(b)) and not c = 7 |
| ParsesOk("select a, b, c from t where (" + notStr + "a = 5 " + orStr + " " + |
| notStr + "f(b)) " + andStr + " " + notStr + "c = 7"); |
| // select a, b, c from t where (!(!a = 5)) |
| ParsesOk("select a, b, c from t where (" + notStr + "(" + notStr + "a = 5))"); |
| // select a, b, c from t where (!(!f(a))) |
| ParsesOk("select a, b, c from t where (" + notStr + "(" + notStr + "f(a)))"); |
| // semantically incorrect negation, but parses ok |
| ParsesOk("select a, b, c from t where a = " + notStr + "5"); |
| // unbalanced parentheses |
| ParserError("select a, b, c from t where " + |
| "(a = 5 " + orStr + " b = 6) " + andStr + " c = 7)"); |
| ParserError("select a, b, c from t where " + |
| "((a = 5 " + orStr + " b = 6) " + andStr + " c = 7"); |
| // incorrectly positioned negation (!) |
| ParserError("select a, b, c from t where a = 5 " + orStr + " " + notStr); |
| ParserError("select a, b, c from t where " + notStr + "(a = 5) " + orStr + " " + notStr); |
| } |
| |
| private void testLiteralTruthValues(String andStr, String orStr, String notStr) { |
| String[] truthValues = {"true", "false", "null"}; |
| for (String l: truthValues) { |
| for (String r: truthValues) { |
| String andExpr = String.format("%s %s %s", l, andStr, r); |
| String orExpr = String.format("%s %s %s", l, orStr, r); |
| ParsesOk(String.format("select %s from t where %s", andExpr, andExpr)); |
| ParsesOk(String.format("select %s from t where %s", orExpr, orExpr)); |
| } |
| String notExpr = String.format("%s %s", notStr, l); |
| ParsesOk(String.format("select %s from t where %s", notExpr, notExpr)); |
| } |
| } |
| |
| @Test |
| public void TestCompoundPredicates() { |
| String[] andStrs = { "and", "&&" }; |
| String[] orStrs = { "or", "||" }; |
| // Note the trailing space in "not ". We want to test "!" without a space. |
| String[] notStrs = { "!", "not " }; |
| // Test all combinations of representations for 'or', 'and', and 'not'. |
| for (String andStr : andStrs) { |
| for (String orStr : orStrs) { |
| for (String notStr : notStrs) { |
| testCompoundPredicates(andStr, orStr, notStr); |
| testLiteralTruthValues(andStr, orStr, notStr); |
| } |
| } |
| } |
| |
| // Test right associativity of NOT. |
| for (String notStr : notStrs) { |
| SelectStmt stmt = |
| (SelectStmt) ParsesOk(String.format("select %s a != b", notStr)); |
| // The NOT should be applied on the result of a != b, and not on a only. |
| Expr e = stmt.getSelectList().getItems().get(0).getExpr(); |
| assertTrue(e instanceof CompoundPredicate); |
| CompoundPredicate cp = (CompoundPredicate) e; |
| assertEquals(CompoundPredicate.Operator.NOT, cp.getOp()); |
| assertEquals(1, cp.getChildren().size()); |
| assertTrue(cp.getChild(0) instanceof BinaryPredicate); |
| } |
| } |
| |
| @Test |
| public void TestBetweenPredicate() { |
| ParsesOk("select a, b, c from t where i between x and y"); |
| ParsesOk("select a, b, c from t where i not between x and y"); |
| ParsesOk("select a, b, c from t where true not between false and NULL"); |
| ParsesOk("select a, b, c from t where 'abc' between 'a' like 'a' and 'b' like 'b'"); |
| // Additional conditions before and after between predicate. |
| ParsesOk("select a, b, c from t where true and false and i between x and y"); |
| ParsesOk("select a, b, c from t where i between x and y and true and false"); |
| ParsesOk("select a, b, c from t where i between x and (y and true) and false"); |
| ParsesOk("select a, b, c from t where i between x and (y and (true and false))"); |
| // Chaining/nesting of between predicates. |
| ParsesOk("select a, b, c from t " + |
| "where true between false and true and 'b' between 'a' and 'c'"); |
| // true between ('b' between 'a' and 'b') and ('bb' between 'aa' and 'cc) |
| ParsesOk("select a, b, c from t " + |
| "where true between 'b' between 'a' and 'c' and 'bb' between 'aa' and 'cc'"); |
| // Missing condition expr. |
| ParserError("select a, b, c from t where between 5 and 10"); |
| // Missing lower bound. |
| ParserError("select a, b, c from t where i between and 10"); |
| // Missing upper bound. |
| ParserError("select a, b, c from t where i between 5 and"); |
| // Missing exprs after between. |
| ParserError("select a, b, c from t where i between"); |
| // AND has a higher precedence than OR. |
| ParserError("select a, b, c from t where true between 5 or 10 and 20"); |
| } |
| |
| @Test |
| public void TestInPredicate() { |
| ParsesOk("select a, b, c from t where i in (x, y, z)"); |
| ParsesOk("select a, b, c from t where i not in (x, y, z)"); |
| // Test NULL and boolean literals. |
| ParsesOk("select a, b, c from t where NULL in (NULL, NULL, NULL)"); |
| ParsesOk("select a, b, c from t where true in (true, false, true)"); |
| ParsesOk("select a, b, c from t where NULL not in (NULL, NULL, NULL)"); |
| ParsesOk("select a, b, c from t where true not in (true, false, true)"); |
| // Missing condition expr. |
| ParserError("select a, b, c from t where in (x, y, z)"); |
| // Missing parentheses around in list. |
| ParserError("select a, b, c from t where i in x, y, z"); |
| ParserError("select a, b, c from t where i in (x, y, z"); |
| ParserError("select a, b, c from t where i in x, y, z)"); |
| // Missing in list. |
| ParserError("select a, b, c from t where i in"); |
| ParserError("select a, b, c from t where i in ( )"); |
| } |
| |
| @Test |
| public void TestSlotRef() { |
| ParsesOk("select a from t where b > 5"); |
| ParsesOk("select a.b from a where b > 5"); |
| ParsesOk("select a.b.c from a.b where b > 5"); |
| ParsesOk("select a.b.c.d from a.b where b > 5"); |
| } |
| |
| /** |
| * Run positive tests for INSERT INTO/OVERWRITE. |
| */ |
| private void testInsert() { |
| for (String qualifier: new String[] {"overwrite", "into"}) { |
| for (String optTbl: new String[] {"", "table"}) { |
| // Entire unpartitioned table. |
| ParsesOk(String.format("insert %s %s t select a from src where b > 5", |
| qualifier, optTbl)); |
| // Static partition with one partitioning key. |
| ParsesOk(String.format( |
| "insert %s %s t partition (pk1=10) select a from src where b > 5", |
| qualifier, optTbl)); |
| // Dynamic partition with one partitioning key. |
| ParsesOk(String.format( |
| "insert %s %s t partition (pk1) select a from src where b > 5", |
| qualifier, optTbl)); |
| // Static partition with two partitioning keys. |
| ParsesOk(String.format("insert %s %s t partition (pk1=10, pk2=20) " + |
| "select a from src where b > 5", |
| qualifier, optTbl)); |
| // Fully dynamic partition with two partitioning keys. |
| ParsesOk(String.format( |
| "insert %s %s t partition (pk1, pk2) select a from src where b > 5", |
| qualifier, optTbl)); |
| // Partially dynamic partition with two partitioning keys. |
| ParsesOk(String.format( |
| "insert %s %s t partition (pk1=10, pk2) select a from src where b > 5", |
| qualifier, optTbl)); |
| // Partially dynamic partition with two partitioning keys. |
| ParsesOk(String.format( |
| "insert %s %s t partition (pk1, pk2=20) select a from src where b > 5", |
| qualifier, optTbl)); |
| // Static partition with two NULL partitioning keys. |
| ParsesOk(String.format("insert %s %s t partition (pk1=NULL, pk2=NULL) " + |
| "select a from src where b > 5", |
| qualifier, optTbl)); |
| // Static partition with boolean partitioning keys. |
| ParsesOk(String.format("insert %s %s t partition (pk1=false, pk2=true) " + |
| "select a from src where b > 5", |
| qualifier, optTbl)); |
| // Static partition with arbitrary exprs as partitioning keys. |
| ParsesOk(String.format("insert %s %s t partition (pk1=abc, pk2=(5*8+10)) " + |
| "select a from src where b > 5", |
| qualifier, optTbl)); |
| ParsesOk(String.format( |
| "insert %s %s t partition (pk1=f(a), pk2=!true and false) " + |
| "select a from src where b > 5", |
| qualifier, optTbl)); |
| // Permutation |
| ParsesOk(String.format("insert %s %s t(a,b,c) values(1,2,3)", |
| qualifier, optTbl)); |
| // Permutation with mismatched select list (should parse fine) |
| ParsesOk(String.format("insert %s %s t(a,b,c) values(1,2,3,4,5,6)", |
| qualifier, optTbl)); |
| // Permutation and partition |
| ParsesOk(String.format("insert %s %s t(a,b,c) partition(d) values(1,2,3,4)", |
| qualifier, optTbl)); |
| // Empty permutation list |
| ParsesOk(String.format("insert %s %s t() select 1 from a", |
| qualifier, optTbl)); |
| // Permutation with optional query statement |
| ParsesOk(String.format("insert %s %s t() partition(d) ", |
| qualifier, optTbl)); |
| ParsesOk(String.format("insert %s %s t() ", |
| qualifier, optTbl)); |
| // No comma in permutation list |
| ParserError(String.format("insert %s %s t(a b c) select 1 from a", |
| qualifier, optTbl)); |
| // Can't use strings as identifiers in permutation list |
| ParserError(String.format("insert %s %s t('a') select 1 from a", |
| qualifier, optTbl)); |
| // Expressions not allowed in permutation list |
| ParserError(String.format("insert %s %s t(a=1, b) select 1 from a", |
| qualifier, optTbl)); |
| } |
| } |
| } |
| |
| @Test |
| public void TestInsert() { |
| // Positive tests. |
| testInsert(); |
| // Missing query statement |
| ParserError("insert into table t"); |
| // Missing 'overwrite/insert'. |
| ParserError("insert table t select a from src where b > 5"); |
| // Missing target table identifier. |
| ParserError("insert overwrite table select a from src where b > 5"); |
| // Missing target table identifier. |
| ParserError("insert into table select a from src where b > 5"); |
| // Missing select statement. |
| ParserError("insert overwrite table t"); |
| // Missing select statement. |
| ParserError("insert into table t"); |
| // Missing parentheses around 'partition'. |
| ParserError("insert overwrite table t partition pk1=10 " + |
| "select a from src where b > 5"); |
| // Missing parentheses around 'partition'. |
| ParserError("insert into table t partition pk1=10 " + |
| "select a from src where b > 5"); |
| // Missing comma in partition list. |
| ParserError("insert overwrite table t partition (pk1=10 pk2=20) " + |
| "select a from src where b > 5"); |
| // Missing comma in partition list. |
| ParserError("insert into table t partition (pk1=10 pk2=20) " + |
| "select a from src where b > 5"); |
| // Misplaced plan hints. |
| ParserError("insert [shuffle] into table t partition (pk1=10 pk2=20) " + |
| "select a from src where b > 5"); |
| ParserError("insert into [shuffle] table t partition (pk1=10 pk2=20) " + |
| "select a from src where b > 5"); |
| ParserError("insert into table t [shuffle] partition (pk1=10 pk2=20) " + |
| "select a from src where b > 5"); |
| ParserError("insert into table t partition [shuffle] (pk1=10 pk2=20) " + |
| "select a from src where b > 5"); |
| } |
| |
| @Test |
| public void TestUpsert() { |
| for (String optTbl: new String[] {"", "table"}) { |
| // SELECT clause |
| ParsesOk(String.format("upsert into %s t select a from src", optTbl)); |
| // VALUES clause |
| ParsesOk(String.format("upsert into %s t values (1, 2, 3)", optTbl)); |
| // Permutation |
| ParsesOk(String.format("upsert into %s t (a,b,c) values(1,2,3)", optTbl)); |
| // Permutation with mismatched select list (should parse fine) |
| ParsesOk(String.format("upsert into %s t (a,b,c) values(1,2,3,4,5,6)", optTbl)); |
| // Empty permutation list |
| ParsesOk(String.format("upsert into %s t () select 1 from a", optTbl)); |
| // Permutation with optional query statement |
| ParsesOk(String.format("upsert into %s t () ", optTbl)); |
| // WITH clause |
| ParsesOk(String.format("with x as (select a from src where b > 5) upsert into %s " + |
| "t select * from x", optTbl)); |
| |
| // Missing query statement |
| ParserError(String.format("upsert into %s t", optTbl)); |
| // Missing 'into'. |
| ParserError(String.format("upsert %s t select a from src where b > 5", optTbl)); |
| // Missing target table identifier. |
| ParserError(String.format("upsert into %s select a from src where b > 5", optTbl)); |
| // No comma in permutation list |
| ParserError(String.format("upsert %s t(a b c) select 1 from a", optTbl)); |
| // Can't use strings as identifiers in permutation list |
| ParserError(String.format("upsert %s t('a') select 1 from a", optTbl)); |
| // Expressions not allowed in permutation list |
| ParserError(String.format("upsert %s t(a=1, b) select 1 from a", optTbl)); |
| // Upsert doesn't support ignore. |
| ParserError(String.format("upsert ignore into %s t select a from src", optTbl)); |
| // Upsert doesn't support partition clauses. |
| ParserError(String.format( |
| "upsert into %s t partition (pk1=10) select a from src", optTbl)); |
| // Upsert doesn't support overwrite. |
| ParserError(String.format("upsert overwrite %s t select 1 from src", optTbl)); |
| } |
| } |
| |
| @Test |
| public void TestUpdate() { |
| ParsesOk("update t set x = 3 where a < b"); |
| ParsesOk("update t set x = (3 + length(\"hallo\")) where a < 'adasas'"); |
| ParsesOk("update t set x = 3"); |
| ParsesOk("update t set x=3, x=4 from a.b t where b = 10"); |
| ParserError("update t"); |
| ParserError("update t set x < 3"); |
| ParserError("update t set x"); |
| ParserError("update t set 4 = x"); |
| ParserError("update from t set x = 3"); |
| ParserError("update t where x = 4"); |
| ParserError("update t a set a = 10 where x = 4"); |
| ParserError("update t a from t b where set a = 10 x = 4"); |
| } |
| |
| @Test |
| public void TestKuduUpdate() { |
| //TestUtils.assumeKuduIsSupported(); |
| ParserError("update (select * from functional_kudu.testtbl) a set name = '10'"); |
| } |
| |
| @Test |
| public void TestDelete() { |
| ParsesOk("delete from t"); |
| ParsesOk("delete a from t a"); |
| ParsesOk("delete a from t a join b on a.id = b.id where true"); |
| ParsesOk("delete a from t a join b where true"); |
| ParsesOk("delete t from t"); |
| ParsesOk("delete t from t where a < b"); |
| ParsesOk("delete a from t a where a < b"); |
| ParsesOk("delete FROM t where a < b"); |
| ParsesOk("delete t where a < b"); |
| ParsesOk("delete t"); |
| ParserError("delete t join f on t.id = f.id"); |
| } |
| |
| @Test |
| public void TestUse() { |
| ParserError("USE"); |
| ParserError("USE db1 db2"); |
| ParsesOk("USE db1"); |
| } |
| |
| @Test |
| public void TestShow() { |
| // Short form ok |
| ParsesOk("SHOW TABLES"); |
| // Well-formed pattern |
| ParsesOk("SHOW TABLES 'tablename|othername'"); |
| // Empty pattern ok |
| ParsesOk("SHOW TABLES ''"); |
| // Databases |
| ParsesOk("SHOW DATABASES"); |
| ParsesOk("SHOW SCHEMAS"); |
| ParsesOk("SHOW DATABASES LIKE 'pattern'"); |
| ParsesOk("SHOW SCHEMAS LIKE 'p*ttern'"); |
| // Data sources |
| ParsesOk("SHOW DATA SOURCES"); |
| ParsesOk("SHOW DATA SOURCES 'pattern'"); |
| ParsesOk("SHOW DATA SOURCES LIKE 'pattern'"); |
| ParsesOk("SHOW DATA SOURCES LIKE 'p*ttern'"); |
| |
| // Functions |
| for (String fnType: new String[] { "", "AGGREGATE", "ANALYTIC"}) { |
| ParsesOk(String.format("SHOW %s FUNCTIONS", fnType)); |
| ParsesOk(String.format("SHOW %s FUNCTIONS LIKE 'pattern'", fnType)); |
| ParsesOk(String.format("SHOW %s FUNCTIONS LIKE 'p*ttern'", fnType)); |
| ParsesOk(String.format("SHOW %s FUNCTIONS", fnType)); |
| ParsesOk(String.format("SHOW %s FUNCTIONS in DB LIKE 'pattern'", fnType)); |
| ParsesOk(String.format("SHOW %s FUNCTIONS in DB", fnType)); |
| } |
| |
| // Show table/column stats. |
| ParsesOk("SHOW TABLE STATS tbl"); |
| ParsesOk("SHOW TABLE STATS db.tbl"); |
| ParsesOk("SHOW TABLE STATS `db`.`tbl`"); |
| ParsesOk("SHOW COLUMN STATS tbl"); |
| ParsesOk("SHOW COLUMN STATS db.tbl"); |
| ParsesOk("SHOW COLUMN STATS `db`.`tbl`"); |
| |
| // Show partitions |
| ParsesOk("SHOW PARTITIONS tbl"); |
| ParsesOk("SHOW PARTITIONS db.tbl"); |
| ParsesOk("SHOW PARTITIONS `db`.`tbl`"); |
| |
| // Show range partitions |
| ParsesOk("SHOW RANGE PARTITIONS tbl"); |
| ParsesOk("SHOW RANGE PARTITIONS db.tbl"); |
| ParsesOk("SHOW RANGE PARTITIONS `db`.`tbl`"); |
| |
| // Show files of table |
| ParsesOk("SHOW FILES IN tbl"); |
| ParsesOk("SHOW FILES IN db.tbl"); |
| ParsesOk("SHOW FILES IN `db`.`tbl`"); |
| ParsesOk("SHOW FILES IN db.tbl PARTITION(x='a',y='b')"); |
| |
| // Missing arguments |
| ParserError("SHOW"); |
| // Malformed pattern (no quotes) |
| ParserError("SHOW TABLES tablename"); |
| // Invalid SHOW DATA SOURCE statements |
| ParserError("SHOW DATA"); |
| ParserError("SHOW SOURCE"); |
| ParserError("SHOW DATA SOURCE LIKE NotStrLiteral"); |
| ParserError("SHOW UNKNOWN FUNCTIONS"); |
| // Missing table/column qualifier. |
| ParserError("SHOW STATS tbl"); |
| // Missing table. |
| ParserError("SHOW TABLE STATS"); |
| ParserError("SHOW COLUMN STATS"); |
| // String literal not accepted. |
| ParserError("SHOW TABLE STATS 'strlit'"); |
| // Missing table. |
| ParserError("SHOW FILES IN"); |
| |
| ParsesOk("SHOW FILES IN db.tbl PARTITION(p)"); |
| } |
| |
| @Test |
| public void TestShowCreateTable() { |
| ParsesOk("SHOW CREATE TABLE x"); |
| ParsesOk("SHOW CREATE TABLE db.x"); |
| ParserError("SHOW CREATE TABLE"); |
| ParserError("SHOW CREATE TABLE x y z"); |
| } |
| |
| @Test |
| public void TestDescribeDb() { |
| // Missing argument |
| ParserError("DESCRIBE DATABASE"); |
| ParserError("DESCRIBE DATABASE FORMATTED"); |
| ParserError("DESCRIBE DATABASE EXTENDED"); |
| |
| // database ok |
| ParsesOk("DESCRIBE DATABASE databasename"); |
| ParsesOk("DESCRIBE DATABASE FORMATTED databasename"); |
| ParsesOk("DESCRIBE DATABASE EXTENDED databasename"); |
| } |
| |
| @Test |
| public void TestDescribe() { |
| // Missing argument |
| ParserError("DESCRIBE"); |
| ParserError("DESCRIBE FORMATTED"); |
| ParserError("DESCRIBE EXTENDED"); |
| |
| // Unqualified table ok |
| ParsesOk("DESCRIBE tablename"); |
| ParsesOk("DESCRIBE FORMATTED tablename"); |
| ParsesOk("DESCRIBE EXTENDED tablename"); |
| |
| // Fully-qualified table ok |
| ParsesOk("DESCRIBE databasename.tablename"); |
| ParsesOk("DESCRIBE FORMATTED databasename.tablename"); |
| ParsesOk("DESCRIBE EXTENDED databasename.tablename"); |
| |
| // Paths within table ok. |
| ParsesOk("DESCRIBE databasename.tablename.field1"); |
| ParsesOk("DESCRIBE databasename.tablename.field1.field2"); |
| ParsesOk("DESCRIBE FORMATTED databasename.tablename.field1"); |
| ParsesOk("DESCRIBE FORMATTED databasename.tablename.field1.field2"); |
| ParsesOk("DESCRIBE EXTENDED databasename.tablename.field1"); |
| ParsesOk("DESCRIBE EXTENDED databasename.tablename.field1.field2"); |
| } |
| |
| @Test |
| public void TestCreateDatabase() { |
| // Both CREATE DATABASE and CREATE SCHEMA are valid (and equivalent) |
| String [] dbKeywords = {"DATABASE", "SCHEMA"}; |
| for (String kw: dbKeywords) { |
| ParsesOk(String.format("CREATE %s Foo", kw)); |
| ParsesOk(String.format("CREATE %s IF NOT EXISTS Foo", kw)); |
| |
| ParsesOk(String.format("CREATE %s Foo COMMENT 'Some comment'", kw)); |
| ParsesOk(String.format("CREATE %s Foo LOCATION '/hdfs_location'", kw)); |
| ParsesOk(String.format("CREATE %s Foo LOCATION '/hdfs_location'", kw)); |
| ParsesOk(String.format( |
| "CREATE %s Foo COMMENT 'comment' LOCATION '/hdfs_location'", kw)); |
| |
| // Only string literals are supported |
| ParserError(String.format("CREATE %s Foo COMMENT mytable", kw)); |
| ParserError(String.format("CREATE %s Foo LOCATION /hdfs_location", kw)); |
| |
| // COMMENT needs to be *before* LOCATION |
| ParserError(String.format( |
| "CREATE %s Foo LOCATION '/hdfs/location' COMMENT 'comment'", kw)); |
| |
| ParserError(String.format("CREATE %s Foo COMMENT LOCATION '/hdfs_location'", kw)); |
| ParserError(String.format("CREATE %s Foo LOCATION", kw)); |
| ParserError(String.format("CREATE %s Foo LOCATION 'dfsd' 'dafdsf'", kw)); |
| |
| ParserError(String.format("CREATE Foo")); |
| ParserError(String.format("CREATE %s 'Foo'", kw)); |
| ParserError(String.format("CREATE %s", kw)); |
| ParserError(String.format("CREATE %s IF EXISTS Foo", kw)); |
| |
| ParserError(String.format("CREATE %sS Foo", kw)); |
| } |
| } |
| |
| @Test |
| public void TestCreateFunction() { |
| ParsesOk("CREATE FUNCTION Foo() RETURNS INT LOCATION 'f.jar' SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION Foo(INT, INT) RETURNS STRING LOCATION " + |
| "'f.jar' SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION Foo(INT, DOUBLE) RETURNS STRING LOCATION " + |
| "'f.jar' SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION Foo() RETURNS STRING LOCATION " + |
| "'f.jar' SYMBOL='class.Udf' COMMENT='hi'"); |
| ParsesOk("CREATE FUNCTION IF NOT EXISTS Foo() RETURNS INT LOCATION 'foo.jar' " + |
| "SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION foo LOCATION 'f.jar' SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION db.foo LOCATION 'f.jar' SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION IF NOT EXISTS foo LOCATION 'f.jar' SYMBOL='class.Udf'"); |
| ParsesOk("CREATE FUNCTION IF NOT EXISTS db.foo LOCATION 'f.jar' SYMBOL='class.Udf'"); |
| |
| // Try more interesting function names |
| ParsesOk("CREATE FUNCTION User.Foo() RETURNS INT LOCATION 'a'"); |
| ParsesOk("CREATE FUNCTION `Foo`() RETURNS INT LOCATION 'a'"); |
| ParsesOk("CREATE FUNCTION `Foo.Bar`() RETURNS INT LOCATION 'a'"); |
| ParsesOk("CREATE FUNCTION `Foo`.Bar() RETURNS INT LOCATION 'a'"); |
| |
| // Bad function name |
| ParserError("CREATE FUNCTION User.() RETURNS INT LOCATION 'a'"); |
| ParserError("CREATE FUNCTION User.Foo.() RETURNS INT LOCATION 'a'"); |
| ParserError("CREATE FUNCTION User..Foo() RETURNS INT LOCATION 'a'"); |
| ParserError("CREATE FUNCTION Foo() LOCATION 'a.jar' SYMBOL='class.Udf"); |
| // Bad function name that parses but won't analyze. |
| ParsesOk("CREATE FUNCTION A.B.C.D.Foo() RETURNS INT LOCATION 'a'"); |
| |
| // Missing location |
| ParserError("CREATE FUNCTION FOO() RETURNS INT"); |
| |
| // Missing return type |
| ParserError("CREATE FUNCTION FOO() LOCATION 'foo.jar'"); |
| |
| // Bad opt args |
| ParserError("CREATE FUNCTION Foo() RETURNS INT SYMBOL='1' LOCATION 'a'"); |
| ParserError("CREATE FUNCTION Foo() RETURNS INT LOCATION 'a' SYMBOL"); |
| ParserError("CREATE FUNCTION Foo() RETURNS INT LOCATION 'a' SYMBOL='1' SYMBOL='2'"); |
| ParserError("CREATE FUNCTION IF NOT EXISTS db.foo LOCATION 'f.jar' SYMBOL='1'" + |
| " SYMBOL='2'"); |
| |
| // Missing arguments |
| ParserError("CREATE FUNCTION Foo RETURNS INT LOCATION 'f.jar'"); |
| ParserError("CREATE FUNCTION Foo(INT,) RETURNS INT LOCATION 'f.jar'"); |
| ParserError("CREATE FUNCTION FOO RETURNS INT LOCATION 'foo.jar'"); |
| |
| // NULL return type or argument type. |
| ParserError("CREATE FUNCTION Foo(NULL) RETURNS INT LOCATION 'f.jar'"); |
| ParserError("CREATE FUNCTION Foo(NULL, INT) RETURNS INT LOCATION 'f.jar'"); |
| ParserError("CREATE FUNCTION Foo(INT, NULL) RETURNS INT LOCATION 'f.jar'"); |
| ParserError("CREATE FUNCTION Foo() RETURNS NULL LOCATION 'f.jar'"); |
| } |
| |
| @Test |
| public void TestVariadicCreateFunctions() { |
| String fnCreates[] = {"CREATE FUNCTION ", "CREATE AGGREGATE FUNCTION " }; |
| for (String fnCreate: fnCreates) { |
| ParsesOk(fnCreate + "Foo(int...) RETURNS INT LOCATION 'f.jar'"); |
| ParsesOk(fnCreate + "Foo(int ...) RETURNS INT LOCATION 'f.jar'"); |
| ParsesOk(fnCreate + "Foo(int, double ...) RETURNS INT LOCATION 'f.jar'"); |
| |
| ParserError(fnCreate + "Foo(...) RETURNS INT LOCATION 'f.jar'"); |
| ParserError(fnCreate + "Foo(int..., double) RETURNS INT LOCATION 'f.jar'"); |
| ParserError(fnCreate + "Foo(int) RETURNS INT... LOCATION 'f.jar'"); |
| ParserError(fnCreate + "Foo(int. . .) RETURNS INT... LOCATION 'f.jar'"); |
| ParserError(fnCreate + "Foo(int, ...) RETURNS INT... LOCATION 'f.jar'"); |
| } |
| } |
| |
| @Test |
| public void TestCreateAggregate() { |
| String loc = " LOCATION 'f.so' UPDATE_FN='class' "; |
| String c = "CREATE AGGREGATE FUNCTION Foo() RETURNS INT "; |
| |
| ParsesOk(c + loc); |
| ParsesOk(c + "INTERMEDIATE STRING" + loc + "comment='c'"); |
| ParsesOk(c + loc + "comment='abcd'"); |
| ParsesOk(c + loc + "init_fn='InitFnSymbol'"); |
| ParsesOk(c + loc + "init_fn='I' merge_fn='M'"); |
| ParsesOk(c + loc + "merge_fn='M' init_fn='I'"); |
| ParsesOk(c + loc + "merge_fn='M' Init_fn='I' serialize_fn='S' Finalize_fn='F'"); |
| ParsesOk(c + loc + "Init_fn='M' Finalize_fn='I' merge_fn='S' serialize_fn='F'"); |
| ParsesOk(c + loc + "merge_fn='M'"); |
| ParsesOk(c + "INTERMEDIATE CHAR(10)" + loc); |
| |
| ParserError("CREATE UNKNOWN FUNCTION " + "Foo() RETURNS INT" + loc); |
| ParserError(c + loc + "init_fn='1' init_fn='1'"); |
| ParserError(c + loc + "unknown='1'"); |
| |
| // CHAR must specify size |
| ParserError(c + "INTERMEDIATE CHAR()" + loc); |
| ParserError(c + "INTERMEDIATE CHAR(ab)" + loc); |
| ParserError(c + "INTERMEDIATE CHAR('')" + loc); |
| ParserError(c + "INTERMEDIATE CHAR('10')" + loc); |
| ParserError(c + "INTERMEDIATE CHAR(-10)" + loc); |
| // Parses okay, will fail in analysis |
| ParsesOk(c + "INTERMEDIATE CHAR(0)" + loc); |
| |
| // Optional args must be at the end |
| ParserError("CREATE UNKNOWN FUNCTION " + "Foo() RETURNS INT" + loc); |
| ParserError("CREATE AGGREGATE FUNCTION Foo() init_fn='1' RETURNS INT" + loc); |
| ParserError(c + "init_fn='1'" + loc); |
| |
| // Variadic args |
| ParsesOk("CREATE AGGREGATE FUNCTION Foo(INT...) RETURNS INT LOCATION 'f.jar'"); |
| } |
| |
| @Test |
| public void TestAlterTableAddColumn() { |
| for (String keyword: new String[]{"", "IF NOT EXISTS"}) { |
| ParsesOk(String.format("ALTER TABLE Foo ADD COLUMN %s i int", keyword)); |
| ParsesOk(String.format("ALTER TABLE TestDb.Foo ADD COLUMN %s i int", keyword)); |
| |
| ParserError(String.format("ALTER TestDb.Foo ADD COLUMN %s", keyword)); |
| ParserError(String.format("ALTER Foo ADD COLUMN %s", keyword)); |
| ParserError(String.format("ALTER TABLE TestDb.Foo ADD COLUMN %s (i int)", keyword)); |
| ParserError(String.format("ALTER TABLE Foo ADD COLUMN %s (i int)", keyword)); |
| ParserError(String.format("ALTER Foo %s ADD COLUMN i int", keyword)); |
| ParserError(String.format("ALTER TestDb.Foo %s ADD COLUMN i int", keyword)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableAddReplaceColumns() { |
| String[] addReplaceKw = {"ADD", "ADD IF NOT EXISTS", "REPLACE"}; |
| for (String addReplace: addReplaceKw) { |
| ParsesOk(String.format( |
| "ALTER TABLE Foo %s COLUMNS (i int, s string)", addReplace)); |
| ParsesOk(String.format( |
| "ALTER TABLE TestDb.Foo %s COLUMNS (i int, s string)", addReplace)); |
| ParsesOk(String.format( |
| "ALTER TABLE TestDb.Foo %s COLUMNS (i int)", addReplace)); |
| ParsesOk(String.format( |
| "ALTER TABLE TestDb.Foo %s COLUMNS (i int comment 'hi')", addReplace)); |
| // Kudu column options |
| ParsesOk(String.format("ALTER TABLE Foo %s COLUMNS (i int PRIMARY KEY NOT NULL " + |
| "ENCODING RLE COMPRESSION SNAPPY BLOCK_SIZE 1024 DEFAULT 10, " + |
| "j string NULL ENCODING PLAIN_ENCODING COMPRESSION LZ4 BLOCK_SIZE 10 " + |
| "DEFAULT 'test')", addReplace)); |
| |
| // Negative syntax tests |
| ParserError(String.format("ALTER TABLE TestDb.Foo %s COLUMNS i int", addReplace)); |
| ParserError(String.format( |
| "ALTER TABLE TestDb.Foo %s COLUMNS (int i)", addReplace)); |
| ParserError(String.format( |
| "ALTER TABLE TestDb.Foo %s COLUMNS (i int COMMENT)", addReplace)); |
| ParserError(String.format("ALTER TestDb.Foo %s COLUMNS (i int)", addReplace)); |
| ParserError(String.format("ALTER TestDb.Foo %s COLUMNS", addReplace)); |
| ParserError(String.format("ALTER TestDb.Foo %s COLUMNS ()", addReplace)); |
| ParserError(String.format("ALTER Foo %s COLUMNS (i int, s string)", addReplace)); |
| ParserError(String.format("ALTER TABLE %s COLUMNS (i int, s string)", addReplace)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableAddPartition() { |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (i=1)"); |
| ParsesOk("ALTER TABLE TestDb.Foo ADD IF NOT EXISTS PARTITION (i=1, s='Hello')"); |
| ParsesOk("ALTER TABLE TestDb.Foo ADD PARTITION (i=1, s='Hello') LOCATION '/a/b'"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (i=NULL)"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (i=NULL, j=2, k=NULL)"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (i=abc, j=(5*8+10), k=!true and false)"); |
| |
| // Multiple partition specs |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (i=1, s='one') " + |
| "PARTITION (i=2, s='two') PARTITION (i=3, s='three')"); |
| ParsesOk("ALTER TABLE TestDb.Foo ADD PARTITION (i=1, s='one') LOCATION 'a/b' " + |
| "PARTITION (i=2, s='two') LOCATION 'c/d' " + |
| "PARTITION (i=3, s='three') " + |
| "PARTITION (i=4, s='four') LOCATION 'e/f'"); |
| ParsesOk("ALTER TABLE TestDb.Foo ADD IF NOT EXISTS " + |
| "PARTITION (i=1, s='one') " + |
| "PARTITION (i=2, s='two') LOCATION 'c/d'"); |
| ParserError("ALTER TABLE TestDb.Foo ADD " + |
| "PARTITION (i=1, s='one') " + |
| "IF NOT EXISTS PARTITION (i=2, s='two') LOCATION 'c/d'"); |
| |
| // Location needs to be a string literal |
| ParserError("ALTER TABLE TestDb.Foo ADD PARTITION (i=1, s='Hello') LOCATION a/b"); |
| |
| // Caching ops |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) CACHED IN 'pool'"); |
| ParserError("ALTER TABLE Foo ADD PARTITION (j=2) CACHED 'pool'"); |
| ParserError("ALTER TABLE Foo ADD PARTITION (j=2) CACHED IN"); |
| ParserError("ALTER TABLE Foo ADD PARTITION (j=2) CACHED"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) CACHED IN 'pool' WITH replication = 3"); |
| ParserError("ALTER TABLE Foo ADD PARTITION (j=2) CACHED IN 'pool' " + |
| "with replication = -1"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) UNCACHED"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) LOCATION 'a/b' UNCACHED"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) LOCATION 'a/b' CACHED IN 'pool'"); |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) LOCATION 'a/b' CACHED IN 'pool' " + |
| "with replication = 3"); |
| ParserError("ALTER TABLE Foo ADD PARTITION (j=2) CACHED IN 'pool' LOCATION 'a/b'"); |
| ParserError("ALTER TABLE Foo ADD PARTITION (j=2) UNCACHED LOCATION 'a/b'"); |
| |
| // Multiple partition specs with caching ops |
| ParsesOk("ALTER TABLE Foo ADD PARTITION (j=2) CACHED IN 'pool' " + |
| "PARTITION (j=3) UNCACHED " + |
| "PARTITION (j=4) CACHED IN 'pool' WITH replication = 3 " + |
| "PARTITION (j=5) LOCATION 'a/b' CACHED IN 'pool' " + |
| "PARTITION (j=5) LOCATION 'c/d' CACHED IN 'pool' with replication = 3"); |
| |
| ParserError("ALTER TABLE Foo ADD IF EXISTS PARTITION (i=1, s='Hello')"); |
| ParserError("ALTER TABLE TestDb.Foo ADD (i=1, s='Hello')"); |
| ParserError("ALTER TABLE TestDb.Foo ADD (i=1)"); |
| ParserError("ALTER TABLE Foo (i=1)"); |
| ParserError("ALTER TABLE TestDb.Foo PARTITION (i=1)"); |
| ParserError("ALTER TABLE Foo ADD PARTITION"); |
| ParserError("ALTER TABLE TestDb.Foo ADD PARTITION ()"); |
| ParserError("ALTER Foo ADD PARTITION (i=1)"); |
| ParserError("ALTER TABLE ADD PARTITION (i=1)"); |
| ParserError("ALTER TABLE ADD"); |
| ParserError("ALTER TABLE DROP"); |
| |
| // Kudu range partitions |
| String[] ifNotExistsOption = {"IF NOT EXISTS", ""}; |
| for (String option: ifNotExistsOption) { |
| ParsesOk(String.format("ALTER TABLE Foo ADD %s RANGE PARTITION 10 < VALUES < 20", |
| option)); |
| ParsesOk(String.format("ALTER TABLE Foo ADD %s RANGE PARTITION VALUE = 100", |
| option)); |
| ParserError(String.format("ALTER TABLE Foo ADD %s RANGE PARTITION 10 < VALUES " + |
| "<= 20, PARTITION 20 < VALUES <= 30", option)); |
| ParserError(String.format("ALTER TABLE Foo ADD %s (RANGE PARTITION 10 < VALUES " + |
| "<= 20)", option)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableDropColumn() { |
| // KW_COLUMN is optional |
| String[] columnKw = {"COLUMN", ""}; |
| for (String kw: columnKw) { |
| ParsesOk(String.format("ALTER TABLE Foo DROP %s col1", kw)); |
| ParsesOk(String.format("ALTER TABLE TestDb.Foo DROP %s col1", kw)); |
| |
| // Negative syntax tests |
| ParserError(String.format("ALTER TABLE TestDb.Foo DROP %s col1, col2", kw)); |
| ParserError(String.format("ALTER TABLE TestDb.Foo DROP %s", kw)); |
| ParserError(String.format("ALTER TABLE Foo DROP %s 'col1'", kw)); |
| ParserError(String.format("ALTER Foo DROP %s col1", kw)); |
| ParserError(String.format("ALTER TABLE DROP %s col1", kw)); |
| ParserError(String.format("ALTER TABLE DROP %s", kw)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableDropPartition() { |
| // PURGE is optional |
| String[] purgeKw = {"PURGE", ""}; |
| for (String kw: purgeKw) { |
| ParsesOk(String.format("ALTER TABLE Foo DROP PARTITION (i=1) %s", kw)); |
| ParsesOk(String.format("ALTER TABLE TestDb.Foo DROP IF EXISTS " |
| + "PARTITION (i=1, s='Hello') %s", kw)); |
| ParsesOk(String.format("ALTER TABLE Foo DROP PARTITION (i=NULL) %s", kw)); |
| ParsesOk(String.format("ALTER TABLE Foo DROP PARTITION (i=NULL, " |
| + "j=2, k=NULL) %s", kw)); |
| ParsesOk(String.format("ALTER TABLE Foo DROP PARTITION (i=abc, " |
| + "j=(5*8+10), k=!true and false) %s", kw)); |
| |
| ParserError(String.format("ALTER TABLE Foo DROP IF NOT EXISTS " |
| + "PARTITION (i=1, s='Hello') %s", kw)); |
| ParserError(String.format("ALTER TABLE TestDb.Foo DROP (i=1, s='Hello') %s", kw)); |
| ParserError(String.format("ALTER TABLE TestDb.Foo DROP (i=1) %s", kw)); |
| ParserError(String.format("ALTER TABLE Foo (i=1) %s", kw)); |
| ParserError(String.format("ALTER TABLE TestDb.Foo PARTITION (i=1) %s", kw)); |
| ParserError(String.format("ALTER TABLE Foo DROP PARTITION %s", kw)); |
| ParserError(String.format("ALTER TABLE TestDb.Foo DROP PARTITION () %s", kw)); |
| ParserError(String.format("ALTER Foo DROP PARTITION (i=1) %s", kw)); |
| ParserError(String.format("ALTER TABLE DROP PARTITION (i=1) %s", kw)); |
| } |
| |
| // Kudu range partitions |
| String[] ifExistsOption = {"IF EXISTS", ""}; |
| for (String option: ifExistsOption) { |
| ParsesOk(String.format("ALTER TABLE Foo DROP %s RANGE PARTITION 10 < VALUES < 20", |
| option)); |
| ParsesOk(String.format("ALTER TABLE Foo DROP %s RANGE PARTITION VALUE = 100", |
| option)); |
| ParserError(String.format("ALTER TABLE Foo DROP %s RANGE PARTITION 10 < VALUES " + |
| "<= 20, PARTITION 20 < VALUES <= 30", option)); |
| ParserError(String.format("ALTER TABLE Foo DROP %s (RANGE PARTITION 10 < VALUES " + |
| "<= 20)", option)); |
| ParserError(String.format("ALTER TABLE Foo DROP %s RANGE PARTITION VALUE = 100 " + |
| "PURGE", option)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableChangeColumn() { |
| // KW_COLUMN is optional |
| String[] columnKw = {"COLUMN", ""}; |
| for (String kw: columnKw) { |
| ParsesOk(String.format("ALTER TABLE Foo.Bar CHANGE %s c1 c2 int", kw)); |
| ParsesOk(String.format("ALTER TABLE Foo CHANGE %s c1 c2 int comment 'hi'", kw)); |
| // Kudu column options |
| ParsesOk(String.format("ALTER TABLE Foo CHANGE %s c1 c2 int comment 'hi' " + |
| "NULL ENCODING PLAIN_ENCODING COMPRESSION LZ4 DEFAULT 10 BLOCK_SIZE 1024", kw)); |
| |
| // Negative syntax tests |
| ParserError(String.format("ALTER TABLE Foo CHANGE %s c1 int c2", kw)); |
| ParserError(String.format("ALTER TABLE Foo CHANGE %s col1 int", kw)); |
| ParserError(String.format("ALTER TABLE Foo CHANGE %s col1", kw)); |
| ParserError(String.format("ALTER TABLE Foo CHANGE %s", kw)); |
| ParserError(String.format("ALTER TABLE CHANGE %s c1 c2 int", kw)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableSet() { |
| // Supported file formats |
| String [] supportedFileFormats = |
| {"TEXTFILE", "SEQUENCEFILE", "PARQUET", "PARQUETFILE", "RCFILE", "AVRO"}; |
| for (String format: supportedFileFormats) { |
| ParsesOk("ALTER TABLE Foo SET FILEFORMAT " + format); |
| ParsesOk("ALTER TABLE TestDb.Foo SET FILEFORMAT " + format); |
| ParsesOk("ALTER TABLE TestDb.Foo PARTITION (a=1) SET FILEFORMAT " + format); |
| ParsesOk("ALTER TABLE Foo PARTITION (s='str') SET FILEFORMAT " + format); |
| ParserError("ALTER TABLE TestDb.Foo PARTITION (i=5) SET " + format); |
| ParserError("ALTER TABLE TestDb.Foo SET " + format); |
| ParserError("ALTER TABLE TestDb.Foo " + format); |
| } |
| ParserError("ALTER TABLE TestDb.Foo SET FILEFORMAT"); |
| |
| ParsesOk("ALTER TABLE Foo SET LOCATION '/a/b/c'"); |
| ParsesOk("ALTER TABLE TestDb.Foo SET LOCATION '/a/b/c'"); |
| |
| ParsesOk("ALTER TABLE Foo PARTITION (i=1,s='str') SET LOCATION '/a/i=1/s=str'"); |
| ParsesOk("ALTER TABLE Foo PARTITION (s='str') SET LOCATION '/a/i=1/s=str'"); |
| |
| ParserError("ALTER TABLE Foo PARTITION () SET LOCATION '/a'"); |
| ParserError("ALTER TABLE Foo PARTITION () SET FILEFORMAT PARQUETFILE"); |
| ParserError("ALTER TABLE Foo PARTITION (,) SET FILEFORMAT PARQUET"); |
| ParserError("ALTER TABLE Foo PARTITION (a=1) SET FILEFORMAT"); |
| ParserError("ALTER TABLE Foo PARTITION (a=1) SET LOCATION"); |
| ParserError("ALTER TABLE TestDb.Foo SET LOCATION abc"); |
| ParserError("ALTER TABLE TestDb.Foo SET LOCATION"); |
| ParserError("ALTER TABLE TestDb.Foo SET"); |
| |
| ParsesOk("ALTER TABLE Foo SET ROW FORMAT DELIMITED FIELDS TERMINATED BY ','"); |
| ParsesOk("ALTER TABLE Foo SET ROW FORMAT DELIMITED LINES TERMINATED BY '\n'"); |
| ParsesOk("ALTER TABLE Foo SET ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' " + |
| "LINES TERMINATED BY '\n'"); |
| ParsesOk("ALTER TABLE Foo SET ROW FORMAT DELIMITED FIELDS TERMINATED BY ',' " + |
| "ESCAPED BY '\' LINES TERMINATED BY '\n'"); |
| ParsesOk("ALTER TABLE Foo PARTITION (i=1) SET ROW FORMAT DELIMITED " + |
| "FIELDS TERMINATED BY ','"); |
| ParsesOk("ALTER TABLE Foo PARTITION (i=1) SET ROW FORMAT DELIMITED " + |
| "LINES TERMINATED BY '\n'"); |
| ParsesOk("ALTER TABLE Foo PARTITION (i=1) SET ROW FORMAT DELIMITED " + |
| "FIELDS TERMINATED BY ',' LINES TERMINATED BY '\n'"); |
| ParsesOk("ALTER TABLE Foo PARTITION (i=1) SET ROW FORMAT DELIMITED " + |
| "FIELDS TERMINATED BY ',' ESCAPED BY '\' LINES TERMINATED BY '\n'"); |
| ParserError("ALTER TABLE Foo SET ROW FORMAT"); |
| ParserError("ALTER TABLE Foo SET ROW FORMAT DELIMITED FIELDS"); |
| ParserError("ALTER TABLE Foo PARTITION () SET ROW FORMAT DELIMITED " + |
| "FIELDS TERMINATED BY ','"); |
| ParserError("ALTER TABLE Foo PARTITION (i=1) SET ROW FORMAT"); |
| |
| String[] tblPropTypes = {"TBLPROPERTIES", "SERDEPROPERTIES"}; |
| String[] partClauses = {"", "PARTITION(k1=10, k2=20)"}; |
| for (String propType: tblPropTypes) { |
| for (String part: partClauses) { |
| ParsesOk(String.format("ALTER TABLE Foo %s SET %s ('a'='b')", part, propType)); |
| ParsesOk(String.format("ALTER TABLE Foo %s SET %s ('abc'='123')", |
| part, propType)); |
| ParsesOk(String.format("ALTER TABLE Foo %s SET %s ('abc'='123', 'a'='1')", |
| part, propType)); |
| ParsesOk(String.format("ALTER TABLE Foo %s SET %s ('a'='1', 'b'='2', 'c'='3')", |
| part, propType)); |
| ParserError(String.format("ALTER TABLE Foo %s SET %s ( )", part, propType)); |
| ParserError(String.format("ALTER TABLE Foo %s SET %s ('a', 'b')", |
| part, propType)); |
| ParserError(String.format("ALTER TABLE Foo %s SET %s ('a'='b',)", |
| part, propType)); |
| ParserError(String.format("ALTER TABLE Foo %s SET %s ('a'=b)", part, propType)); |
| ParserError(String.format("ALTER TABLE Foo %s SET %s (a='b')", part, propType)); |
| ParserError(String.format("ALTER TABLE Foo %s SET %s (a=b)", part, propType)); |
| } |
| } |
| |
| // Test SET COLUMN STATS. |
| ParsesOk("ALTER TABLE Foo SET COLUMN STATS col ('numDVs'='10')"); |
| ParsesOk("ALTER TABLE Foo SET COLUMN STATS col ('numDVs'='10','maxSize'='20')"); |
| ParsesOk("ALTER TABLE TestDb.Foo SET COLUMN STATS col ('avgSize'='20')"); |
| ParserError("ALTER TABLE SET COLUMN STATS col ('numDVs'='10'"); |
| ParserError("ALTER TABLE Foo SET COLUMN STATS ('numDVs'='10'"); |
| ParserError("ALTER TABLE Foo SET COLUMN STATS col"); |
| ParserError("ALTER TABLE Foo SET COLUMN STATS col ()"); |
| ParserError("ALTER TABLE Foo SET COLUMN STATS col (numDVs='10')"); |
| ParserError("ALTER TABLE Foo SET COLUMN STATS col ('numDVs'=10)"); |
| ParserError("ALTER TABLE Foo PARTITION (p=1) SET COLUMN STATS col ('avgSize'='20')"); |
| |
| for (String cacheClause: Lists.newArrayList("UNCACHED", "CACHED in 'pool'", |
| "CACHED in 'pool' with replication = 4")) { |
| ParsesOk("ALTER TABLE Foo SET " + cacheClause); |
| ParsesOk("ALTER TABLE Foo PARTITION(j=0) SET " + cacheClause); |
| ParserError("ALTER TABLE Foo PARTITION(j=0) " + cacheClause); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableSortBy() { |
| ParsesOk("ALTER TABLE TEST SORT BY (int_col, id)"); |
| ParsesOk("ALTER TABLE TEST SORT BY ()"); |
| ParserError("ALTER TABLE TEST PARTITION (year=2009, month=4) SORT BY (int_col, id)"); |
| } |
| |
| @Test |
| public void TestAlterTableZSortBy() { |
| BackendConfig.INSTANCE.setZOrderSortUnlocked(true); |
| |
| ParsesOk("ALTER TABLE TEST SORT BY ZORDER (int_col, id)"); |
| ParsesOk("ALTER TABLE TEST SORT BY ZORDER ()"); |
| ParserError("ALTER TABLE TEST PARTITION (year=2009, month=4) SORT BY ZORDER " + |
| "(int_col, id)"); |
| |
| BackendConfig.INSTANCE.setZOrderSortUnlocked(false); |
| } |
| |
| @Test |
| public void TestAlterTableOrViewRename() { |
| for (String entity: Lists.newArrayList("TABLE", "VIEW")) { |
| ParsesOk(String.format("ALTER %s TestDb.Foo RENAME TO TestDb.Foo2", entity)); |
| ParsesOk(String.format("ALTER %s Foo RENAME TO TestDb.Foo2", entity)); |
| ParsesOk(String.format("ALTER %s TestDb.Foo RENAME TO Foo2", entity)); |
| ParsesOk(String.format("ALTER %s Foo RENAME TO Foo2", entity)); |
| ParserError(String.format("ALTER %s Foo RENAME TO 'Foo2'", entity)); |
| ParserError(String.format("ALTER %s Foo RENAME Foo2", entity)); |
| ParserError(String.format("ALTER %s Foo RENAME TO", entity)); |
| ParserError(String.format("ALTER %s Foo TO Foo2", entity)); |
| ParserError(String.format("ALTER %s Foo TO Foo2", entity)); |
| } |
| } |
| |
| @Test |
| public void TestAlterTableRecoverPartitions() { |
| ParsesOk("ALTER TABLE TestDb.Foo RECOVER PARTITIONS"); |
| ParsesOk("ALTER TABLE Foo RECOVER PARTITIONS"); |
| |
| ParserError("ALTER TABLE Foo RECOVER PARTITIONS ()"); |
| ParserError("ALTER TABLE Foo RECOVER PARTITIONS (i=1)"); |
| ParserError("ALTER TABLE RECOVER"); |
| ParserError("ALTER TABLE RECOVER PARTITIONS"); |
| ParserError("ALTER TABLE Foo RECOVER"); |
| ParserError("ALTER TABLE Foo RECOVER PARTITION"); |
| } |
| |
| @Test |
| public void TestAlterTableAlterColumn() { |
| for (String column : Lists.newArrayList("", "COLUMN")) { |
| ParsesOk(String.format("ALTER TABLE foo ALTER %s bar SET default 0", column)); |
| ParsesOk(String.format( |
| "ALTER TABLE foo ALTER %s bar SET default 0 block_size 0", column)); |
| ParsesOk(String.format("ALTER TABLE foo ALTER %s bar DROP default", column)); |
| |
| ParserError(String.format("ALTER TABLE foo ALTER %s bar", column)); |
| ParserError(String.format("ALTER TABLE foo ALTER %s bar SET default", column)); |
| ParserError(String.format("ALTER TABLE foo ALTER %s bar SET error 0", column)); |
| ParserError( |
| String.format("ALTER TABLE foo ALTER %s bar SET default 0 error 0", column)); |
| ParserError(String.format("ALTER TABLE foo ALTER %s bar DROP comment", column)); |
| } |
| } |
| |
| @Test |
| public void TestCreateTable() { |
| // Support unqualified and fully-qualified table names |
| ParsesOk("CREATE TABLE Foo (i int)"); |
| ParsesOk("CREATE TABLE Foo.Bar (i int)"); |
| ParsesOk("CREATE TABLE IF NOT EXISTS Foo.Bar (i int)"); |
| ParsesOk("CREATE TABLE Foo.Bar2 LIKE Foo.Bar1"); |
| ParsesOk("CREATE TABLE IF NOT EXISTS Bar2 LIKE Bar1"); |
| ParsesOk("CREATE EXTERNAL TABLE IF NOT EXISTS Bar2 LIKE Bar1"); |
| ParsesOk("CREATE EXTERNAL TABLE IF NOT EXISTS Bar2 LIKE Bar1 LOCATION '/a/b'"); |
| ParsesOk("CREATE TABLE Foo2 LIKE Foo COMMENT 'sdafsdf'"); |
| ParsesOk("CREATE TABLE Foo2 LIKE Foo COMMENT ''"); |
| ParsesOk("CREATE TABLE Foo2 LIKE Foo STORED AS PARQUETFILE"); |
| ParsesOk("CREATE TABLE Foo2 LIKE Foo COMMENT 'tbl' " + |
| "STORED AS PARQUETFILE LOCATION '/a/b'"); |
| ParsesOk("CREATE TABLE Foo2 LIKE Foo STORED AS TEXTFILE LOCATION '/a/b'"); |
| ParsesOk("CREATE TABLE Foo LIKE PARQUET '/user/foo'"); |
| |
| // Table and column names starting with digits. |
| ParsesOk("CREATE TABLE 01_Foo (01_i int, 02_j string)"); |
| |
| // Unpartitioned tables |
| ParsesOk("CREATE TABLE Foo (i int, s string)"); |
| ParsesOk("CREATE EXTERNAL TABLE Foo (i int, s string)"); |
| ParsesOk("CREATE EXTERNAL TABLE Foo (i int, s string) LOCATION '/test-warehouse/'"); |
| ParsesOk("CREATE TABLE Foo (i int, s string) COMMENT 'hello' LOCATION '/a/b/'"); |
| ParsesOk("CREATE TABLE Foo (i int, s string) COMMENT 'hello' LOCATION '/a/b/' " + |
| "TBLPROPERTIES ('123'='1234')"); |
| // No column definitions. |
| ParsesOk("CREATE TABLE Foo COMMENT 'hello' LOCATION '/a/b/' " + |
| "TBLPROPERTIES ('123'='1234')"); |
| |
| // Partitioned tables |
| ParsesOk("CREATE TABLE Foo (i int) PARTITIONED BY (j string)"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITIONED BY (s string, d double)"); |
| ParsesOk("CREATE TABLE Foo (i int, s string) PARTITIONED BY (s string, d double)" + |
| " COMMENT 'hello' LOCATION '/a/b/'"); |
| // No column definitions. |
| ParsesOk("CREATE TABLE Foo PARTITIONED BY (s string, d double)" + |
| " COMMENT 'hello' LOCATION '/a/b/'"); |
| ParserError("CREATE TABLE Foo (i int) PARTITIONED BY (int)"); |
| ParserError("CREATE TABLE Foo (i int) PARTITIONED BY ()"); |
| ParserError("CREATE TABLE Foo (i int) PARTITIONED BY"); |
| |
| // Sort by clause |
| ParsesOk("CREATE TABLE Foo (i int, j int) SORT BY ()"); |
| ParsesOk("CREATE TABLE Foo (i int) SORT BY (i)"); |
| ParsesOk("CREATE TABLE Foo (i int) SORT BY (j)"); |
| ParsesOk("CREATE TABLE Foo (i int, j int) SORT BY (i,j)"); |
| ParsesOk("CREATE EXTERNAL TABLE Foo (i int, s string) SORT BY (s) " + |
| "LOCATION '/test-warehouse/'"); |
| ParsesOk("CREATE TABLE Foo (i int, s string) SORT BY (s) COMMENT 'hello' " + |
| "LOCATION '/a/b/' TBLPROPERTIES ('123'='1234')"); |
| |
| // SORT BY must be the first table option |
| ParserError("CREATE TABLE Foo (i int, s string) COMMENT 'hello' SORT BY (s) " + |
| "LOCATION '/a/b/' TBLPROPERTIES ('123'='1234')"); |
| ParserError("CREATE TABLE Foo (i int, s string) COMMENT 'hello' LOCATION '/a/b/' " + |
| "SORT BY (s) TBLPROPERTIES ('123'='1234')"); |
| ParserError("CREATE TABLE Foo (i int, s string) COMMENT 'hello' LOCATION '/a/b/' " + |
| "TBLPROPERTIES ('123'='1234') SORT BY (s)"); |
| |
| // Malformed SORT BY clauses |
| ParserError("CREATE TABLE Foo (i int, j int) SORT BY"); |
| ParserError("CREATE TABLE Foo (i int, j int) SORT BY (i,)"); |
| ParserError("CREATE TABLE Foo (i int, j int) SORT BY (int)"); |
| |
| // Create table like other table with sort columns |
| ParsesOk("CREATE TABLE Foo SORT BY(bar) LIKE Baz STORED AS TEXTFILE LOCATION '/a/b'"); |
| ParserError("CREATE TABLE SORT BY(bar) Foo LIKE Baz STORED AS TEXTFILE " + |
| "LOCATION '/a/b'"); |
| // SORT BY must be the first table option |
| ParserError("CREATE TABLE Foo LIKE Baz STORED AS TEXTFILE LOCATION '/a/b' " + |
| "SORT BY(bar)"); |
| |
| // CTAS with sort columns |
| ParsesOk("CREATE TABLE Foo SORT BY(bar) AS SELECT * FROM BAR"); |
| ParserError("CREATE TABLE Foo AS SELECT * FROM BAR SORT BY(bar)"); |
| |
| // Create table like file with sort columns |
| ParsesOk("CREATE TABLE Foo LIKE PARQUET '/user/foo' SORT BY (id)"); |
| ParserError("CREATE TABLE Foo SORT BY (id) LIKE PARQUET '/user/foo'"); |
| |
| // SORT BY ZORDER clause |
| BackendConfig.INSTANCE.setZOrderSortUnlocked(true); |
| |
| ParsesOk("CREATE TABLE Foo (i int, j int) SORT BY ZORDER ()"); |
| ParsesOk("CREATE TABLE Foo (i int) SORT BY ZORDER (i)"); |
| ParsesOk("CREATE TABLE Foo (i int) SORT BY ZORDER (j)"); |
| ParsesOk("CREATE TABLE Foo (i int, j int) SORT BY ZORDER (i,j)"); |
| ParsesOk("CREATE EXTERNAL TABLE Foo (i int, s string) SORT BY ZORDER (s) " + |
| "LOCATION '/test-warehouse/'"); |
| ParsesOk("CREATE TABLE Foo (i int, s string) SORT BY ZORDER (s) COMMENT 'hello' " + |
| "LOCATION '/a/b/' TBLPROPERTIES ('123'='1234')"); |
| |
| |
| // SORT BY ZORDER must be the first table option |
| ParserError("CREATE TABLE Foo (i int, s string) COMMENT 'hello' SORT BY ZORDER (s) " + |
| "LOCATION '/a/b/' TBLPROPERTIES ('123'='1234')"); |
| ParserError("CREATE TABLE Foo (i int, s string) COMMENT 'hello' LOCATION '/a/b/' " + |
| "SORT BY ZORDER (s) TBLPROPERTIES ('123'='1234')"); |
| ParserError("CREATE TABLE Foo (i int, s string) COMMENT 'hello' LOCATION '/a/b/' " + |
| "TBLPROPERTIES ('123'='1234') SORT BY ZORDER (s)"); |
| |
| // Malformed SORT BY ZORDER clauses |
| ParserError("CREATE TABLE Foo (i int, j int) SORT BY ZORDER"); |
| ParserError("CREATE TABLE Foo (i int, j int) SORT BY ZORDER (i,)"); |
| ParserError("CREATE TABLE Foo (i int, j int) SORT BY ZORDER (int)"); |
| |
| // Create table like other table with zsort columns |
| ParsesOk("CREATE TABLE Foo SORT BY ZORDER(bar) LIKE Baz STORED AS TEXTFILE " + |
| "LOCATION '/a/b'"); |
| ParserError("CREATE TABLE SORT BY ZORDER(bar) Foo LIKE Baz STORED AS TEXTFILE " + |
| "LOCATION '/a/b'"); |
| // SORT BY ZORDER must be the first table option |
| ParserError("CREATE TABLE Foo LIKE Baz STORED AS TEXTFILE LOCATION '/a/b' " + |
| "SORT BY ZORDER(bar)"); |
| |
| // CTAS with zsort columns |
| ParsesOk("CREATE TABLE Foo SORT BY ZORDER(bar) AS SELECT * FROM BAR"); |
| ParserError("CREATE TABLE Foo AS SELECT * FROM BAR SORT BY ZORDER(bar)"); |
| |
| // Create table like file with zsort columns |
| ParsesOk("CREATE TABLE Foo LIKE PARQUET '/user/foo' SORT BY ZORDER (id)"); |
| ParserError("CREATE TABLE Foo SORT BY ZORDER (id) LIKE PARQUET '/user/foo'"); |
| |
| BackendConfig.INSTANCE.setZOrderSortUnlocked(false); |
| |
| // Column comments |
| ParsesOk("CREATE TABLE Foo (i int COMMENT 'hello', s string)"); |
| ParsesOk("CREATE TABLE Foo (i int COMMENT 'hello', s string COMMENT 'hi')"); |
| ParsesOk("CREATE TABLE T (i int COMMENT 'hi') PARTITIONED BY (j int COMMENT 'bye')"); |
| |
| // Supported file formats |
| String [] supportedFileFormats = |
| {"TEXTFILE", "SEQUENCEFILE", "PARQUET", "PARQUETFILE", "RCFILE", "AVRO"}; |
| for (String format: supportedFileFormats) { |
| ParsesOk("CREATE TABLE Foo (i int, s string) STORED AS " + format); |
| ParsesOk("CREATE EXTERNAL TABLE Foo (i int, s string) STORED AS " + format); |
| ParsesOk(String.format( |
| "CREATE TABLE Foo (i int, s string) STORED AS %s LOCATION '/b'", format)); |
| ParsesOk(String.format( |
| "CREATE EXTERNAL TABLE Foo (f float) COMMENT 'c' STORED AS %s LOCATION '/b'", |
| format)); |
| // No column definitions. |
| ParsesOk(String.format( |
| "CREATE EXTERNAL TABLE Foo COMMENT 'c' STORED AS %s LOCATION '/b'", format)); |
| ParserError(String.format("CREATE EXTERNAL TABLE t PRIMARY KEYS (i) STORED AS " + |
| "%s", format)); |
| } |
| |
| ParsesOk("CREATE TABLE foo (i INT) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE foo (i INT PRIMARY KEY) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE foo (i INT, j INT, PRIMARY KEY (i, j)) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE foo (i INT, j INT, PRIMARY KEY (j, i)) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE foo (i INT PRIMARY KEY, PRIMARY KEY(i)) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE foo (i INT PRIMARY KEY, j INT PRIMARY KEY) STORED AS KUDU"); |
| ParserError("CREATE TABLE foo (i INT) PRIMARY KEY (i) STORED AS KUDU"); |
| ParserError("CREATE TABLE foo (i INT, PRIMARY KEY) STORED AS KUDU"); |
| ParserError("CREATE TABLE foo (PRIMARY KEY(a), a INT) STORED AS KUDU"); |
| ParserError("CREATE TABLE foo (i INT) PRIMARY KEY (i) STORED AS KUDU"); |
| |
| // Table Properties |
| String[] tblPropTypes = {"TBLPROPERTIES", "WITH SERDEPROPERTIES"}; |
| for (String propType: tblPropTypes) { |
| ParsesOk(String.format( |
| "CREATE TABLE Foo (i int) %s ('a'='b', 'c'='d', 'e'='f')", propType)); |
| ParserError(String.format("CREATE TABLE Foo (i int) %s", propType)); |
| ParserError(String.format("CREATE TABLE Foo (i int) %s ()", propType)); |
| ParserError(String.format("CREATE TABLE Foo (i int) %s ('a')", propType)); |
| ParserError(String.format("CREATE TABLE Foo (i int) %s ('a'=)", propType)); |
| ParserError(String.format("CREATE TABLE Foo (i int) %s ('a'=c)", propType)); |
| ParserError(String.format("CREATE TABLE Foo (i int) %s (a='c')", propType)); |
| } |
| ParsesOk("CREATE TABLE Foo (i int) WITH SERDEPROPERTIES ('a'='b') " + |
| "TBLPROPERTIES ('c'='d', 'e'='f')"); |
| // TBLPROPERTIES must go after SERDEPROPERTIES |
| ParserError("CREATE TABLE Foo (i int) TBLPROPERTIES ('c'='d', 'e'='f') " + |
| "WITH SERDEPROPERTIES ('a'='b')"); |
| |
| ParserError("CREATE TABLE Foo (i int) SERDEPROPERTIES ('a'='b')"); |
| |
| ParserError("CREATE TABLE Foo (i int, s string) STORED AS SEQFILE"); |
| ParserError("CREATE TABLE Foo (i int, s string) STORED TEXTFILE"); |
| ParserError("CREATE TABLE Foo LIKE Bar STORED AS TEXT"); |
| ParserError("CREATE TABLE Foo LIKE Bar COMMENT"); |
| ParserError("CREATE TABLE Foo LIKE Bar STORED TEXTFILE"); |
| ParserError("CREATE TABLE Foo LIKE Bar STORED AS"); |
| ParserError("CREATE TABLE Foo LIKE Bar LOCATION"); |
| |
| // Row format syntax |
| ParsesOk("CREATE TABLE T (i int) ROW FORMAT DELIMITED"); |
| ParsesOk("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS TERMINATED BY '|'"); |
| ParsesOk("CREATE TABLE T (i int) ROW FORMAT DELIMITED LINES TERMINATED BY '|'"); |
| ParsesOk("CREATE TABLE T (i int) ROW FORMAT DELIMITED ESCAPED BY '\'"); |
| ParsesOk("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\0'" + |
| " ESCAPED BY '\3' LINES TERMINATED BY '\1'"); |
| ParsesOk("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS TERMINATED BY '\0'" + |
| " LINES TERMINATED BY '\1' STORED AS TEXTFILE"); |
| ParsesOk("CREATE TABLE T (i int) COMMENT 'hi' ROW FORMAT DELIMITED STORED AS RCFILE"); |
| ParsesOk("CREATE TABLE T (i int) COMMENT 'hello' ROW FORMAT DELIMITED FIELDS " + |
| "TERMINATED BY '\0' LINES TERMINATED BY '\1' STORED AS TEXTFILE LOCATION '/a'"); |
| |
| // Negative row format syntax |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED TERMINATED BY '\0'"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS TERMINATED BY"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED LINES TERMINATED BY"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED ESCAPED BY"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS TERMINATED '|'"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS TERMINATED BY |"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED FIELDS BY '\0'"); |
| ParserError("CREATE TABLE T (i int) ROW FORMAT DELIMITED LINES BY '\n'"); |
| ParserError("CREATE TABLE T (i int) FIELDS TERMINATED BY '\0'"); |
| ParserError("CREATE TABLE T (i int) ROWS TERMINATED BY '\0'"); |
| ParserError("CREATE TABLE T (i int) ESCAPED BY '\0'"); |
| |
| // Order should be: [comment] [partition by cols] [row format] [serdeproperties (..)] |
| // [stored as FILEFORMAT] [location] [cache spec] [tblproperties (...)] |
| ParserError("CREATE TABLE Foo (d double) COMMENT 'c' PARTITIONED BY (i int)"); |
| ParserError("CREATE TABLE Foo (d double) STORED AS TEXTFILE COMMENT 'c'"); |
| ParserError("CREATE TABLE Foo (d double) STORED AS TEXTFILE ROW FORMAT DELIMITED"); |
| ParserError("CREATE TABLE Foo (d double) ROW FORMAT DELIMITED COMMENT 'c'"); |
| ParserError("CREATE TABLE Foo (d double) LOCATION 'a' COMMENT 'c'"); |
| ParserError("CREATE TABLE Foo (d double) UNCACHED LOCATION '/a/b'"); |
| ParserError("CREATE TABLE Foo (d double) CACHED IN 'pool' LOCATION '/a/b'"); |
| ParserError("CREATE TABLE Foo (d double) CACHED IN 'pool' REPLICATION = 8 " + |
| "LOCATION '/a/b'"); |
| ParserError("CREATE TABLE Foo (d double) LOCATION 'a' COMMENT 'c' STORED AS RCFILE"); |
| ParserError("CREATE TABLE Foo (d double) LOCATION 'a' STORED AS RCFILE"); |
| ParserError("CREATE TABLE Foo (d double) TBLPROPERTIES('a'='b') LOCATION 'a'"); |
| ParserError("CREATE TABLE Foo (i int) LOCATION 'a' WITH SERDEPROPERTIES('a'='b')"); |
| |
| // Location and comment need to be string literals, file format is not |
| ParserError("CREATE TABLE Foo (d double) LOCATION a"); |
| ParserError("CREATE TABLE Foo (d double) COMMENT c"); |
| ParserError("CREATE TABLE Foo (d double COMMENT c)"); |
| ParserError("CREATE TABLE Foo (d double COMMENT 'c') PARTITIONED BY (j COMMENT hi)"); |
| ParserError("CREATE TABLE Foo (d double) STORED AS 'TEXTFILE'"); |
| |
| // Caching |
| ParsesOk("CREATE TABLE Foo (i int) CACHED IN 'myPool'"); |
| ParsesOk("CREATE TABLE Foo (i int) CACHED IN 'myPool' WITH REPLICATION = 4"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITIONED BY(j int) CACHED IN 'myPool'"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITIONED BY(j int) CACHED IN 'myPool'" + |
| " WITH REPLICATION = 4"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITIONED BY(j int) CACHED IN 'myPool'"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITIONED BY(j int) LOCATION '/a' " + |
| "CACHED IN 'myPool'"); |
| ParserError("CREATE TABLE Foo (i int) CACHED IN myPool"); |
| ParserError("CREATE TABLE Foo (i int) PARTITIONED BY(j int) CACHED IN"); |
| ParserError("CREATE TABLE Foo (i int) CACHED 'myPool'"); |
| ParserError("CREATE TABLE Foo (i int) IN 'myPool'"); |
| ParserError("CREATE TABLE Foo (i int) PARTITIONED BY(j int) CACHED IN 'myPool' " + |
| "LOCATION '/a'"); |
| ParserError("CREATE TABLE Foo (i int) CACHED IN 'myPool' WITH REPLICATION = -1"); |
| ParserError("CREATE TABLE Foo (i int) CACHED IN 'myPool' WITH REPLICATION = 1.0"); |
| ParserError("CREATE TABLE Foo (i int) CACHED IN 'myPool' " + |
| "WITH REPLICATION = cast(1 as double)"); |
| |
| // Invalid syntax |
| ParserError("CREATE TABLE IF EXISTS Foo.Bar (i int)"); |
| ParserError("CREATE TABLE Bar LIKE Bar2 (i int)"); |
| ParserError("CREATE IF NOT EXISTS TABLE Foo.Bar (i int)"); |
| ParserError("CREATE TABLE Foo (d double) STORED TEXTFILE"); |
| ParserError("CREATE TABLE Foo (d double) AS TEXTFILE"); |
| ParserError("CREATE TABLE Foo i int"); |
| ParserError("CREATE TABLE Foo (i intt)"); |
| ParserError("CREATE TABLE Foo (int i)"); |
| ParserError("CREATE TABLE Foo (i int,)"); |
| ParserError("CREATE TABLE Foo ()"); |
| ParserError("CREATE TABLE"); |
| ParserError("CREATE EXTERNAL"); |
| ParserError("CREATE"); |
| |
| // Valid syntax for tables PRODUCED BY DATA SOURCE |
| ParsesOk("CREATE TABLE Foo (i int, s string) PRODUCED BY DATA SOURCE Bar"); |
| ParsesOk("CREATE TABLE Foo (i int, s string) PRODUCED BY DATA SOURCE Bar(\"\")"); |
| ParsesOk("CREATE TABLE Foo (i int) PRODUCED BY DATA SOURCE " + |
| "Bar(\"Foo \\!@#$%^&*()\")"); |
| ParsesOk("CREATE TABLE IF NOT EXISTS Foo (i int) PRODUCED BY DATA SOURCE Bar(\"\")"); |
| |
| // Invalid syntax for tables PRODUCED BY DATA SOURCE |
| ParserError("CREATE TABLE Foo (i int) PRODUCED BY DATA Foo"); |
| ParserError("CREATE TABLE Foo (i int) PRODUCED BY DATA SRC Foo"); |
| ParserError("CREATE TABLE Foo (i int) PRODUCED BY DATA SOURCE Foo.Bar"); |
| ParserError("CREATE TABLE Foo (i int) PRODUCED BY DATA SOURCE Foo()"); |
| ParserError("CREATE EXTERNAL TABLE Foo (i int) PRODUCED BY DATA SOURCE Foo(\"\")"); |
| ParserError("CREATE TABLE Foo (i int) PRODUCED BY DATA SOURCE Foo(\"\") " + |
| "LOCATION 'x'"); |
| ParserError("CREATE TABLE Foo (i int) PRODUCED BY DATA SOURCE Foo(\"\") " + |
| "ROW FORMAT DELIMITED"); |
| ParserError("CREATE TABLE Foo (i int) PARTITIONED BY (j string) PRODUCED BY DATA " + |
| "SOURCE Foo(\"\")"); |
| |
| |
| // Flexible partitioning |
| ParsesOk("CREATE TABLE Foo (i int) PARTITION BY HASH(i) PARTITIONS 4"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITION BY HASH(i) PARTITIONS 4, " + |
| "HASH(a) PARTITIONS 2"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITION BY HASH PARTITIONS 4"); |
| ParsesOk("CREATE TABLE Foo (i int, k int) PARTITION BY HASH PARTITIONS 4," + |
| " HASH(k) PARTITIONS 4"); |
| ParserError("CREATE TABLE Foo (i int) PARTITION BY HASH(i)"); |
| ParserError("CREATE EXTERNAL TABLE Foo PARTITION BY HASH PARTITIONS 4"); |
| |
| // Range partitioning |
| ParsesOk("CREATE TABLE Foo (i int) PARTITION BY RANGE (PARTITION VALUE = 10)"); |
| ParsesOk("CREATE TABLE Foo (i int) PARTITION BY RANGE(i) " + |
| "(PARTITION 1 <= VALUES < 10, PARTITION 10 <= VALUES < 20, " + |
| "PARTITION 21 < VALUES <= 30, PARTITION VALUE = 50)"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE(a) " + |
| "(PARTITION 10 <= VALUES)"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE(a) " + |
| "(PARTITION VALUES < 10)"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION VALUE = 10, PARTITION VALUE = 20)"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE(a) " + |
| "(PARTITION VALUES <= 10, PARTITION VALUE = 20)"); |
| ParsesOk("CREATE TABLE Foo (a int, b int) PARTITION BY RANGE(a, b) " + |
| "(PARTITION VALUE = (2001, 1), PARTITION VALUE = (2001, 2), " + |
| "PARTITION VALUE = (2002, 1))"); |
| ParsesOk("CREATE TABLE Foo (a int, b string) PARTITION BY " + |
| "HASH (a) PARTITIONS 3, RANGE (a, b) (PARTITION VALUE = (1, 'abc'), " + |
| "PARTITION VALUE = (2, 'def'))"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION VALUE = 1 + 1) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION 1 + 1 < VALUES) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE Foo (a int, b int) PARTITION BY RANGE (a) " + |
| "(PARTITION b < VALUES <= a) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION now() <= VALUES, PARTITION VALUE = add_months(now(), 2)) " + |
| "STORED AS KUDU"); |
| |
| ParserError("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) ()"); |
| ParserError("CREATE TABLE Foo (a int) PARTITION BY HASH (a) PARTITIONS 4, " + |
| "RANGE (a) (PARTITION VALUE = 10), RANGE (a) (PARTITION VALUES < 10)"); |
| ParserError("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION VALUE = 10), HASH (a) PARTITIONS 3"); |
| ParserError("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION VALUES = 10) STORED AS KUDU"); |
| ParserError("CREATE TABLE Foo (a int) PARTITION BY RANGE (a) " + |
| "(PARTITION 10 < VALUE < 20) STORED AS KUDU"); |
| |
| // Column options for Kudu tables |
| String[] encodings = {"encoding auto_encoding", "encoding plain_encoding", |
| "encoding prefix_encoding", "encoding group_varint", "encoding rle", |
| "encoding dict_encoding", "encoding bit_shuffle", "encoding unknown", ""}; |
| String[] compression = {"compression default_compression", |
| "compression no_compression", "compression snappy", "compression lz4", |
| "compression zlib", "compression unknown", ""}; |
| |
| String[] nullability = {"not null", "null", ""}; |
| String[] defaultVal = {"default 10", ""}; |
| String[] blockSize = {"block_size 4096", ""}; |
| for (String enc: encodings) { |
| for (String comp: compression) { |
| for (String nul: nullability) { |
| for (String def: defaultVal) { |
| for (String block: blockSize) { |
| ParsesOk(String.format("CREATE TABLE Foo (i int PRIMARY KEY " + |
| "%s %s %s %s %s) STORED AS KUDU", nul, enc, comp, def, block)); |
| ParsesOk(String.format("CREATE TABLE Foo (i int PRIMARY KEY " + |
| "%s %s %s %s %s) STORED AS KUDU", block, nul, enc, comp, def)); |
| ParsesOk(String.format("CREATE TABLE Foo (i int PRIMARY KEY " + |
| "%s %s %s %s %s) STORED AS KUDU", def, block, nul, enc, comp)); |
| ParsesOk(String.format("CREATE TABLE Foo (i int PRIMARY KEY " + |
| "%s %s %s %s %s) STORED AS KUDU", comp, def, block, nul, enc)); |
| ParsesOk(String.format("CREATE TABLE Foo (i int PRIMARY KEY " + |
| "%s %s %s %s %s) STORED AS KUDU", enc, comp, def, block, nul)); |
| ParsesOk(String.format("CREATE TABLE Foo (i int PRIMARY KEY " + |
| "%s %s %s %s %s) STORED AS KUDU", enc, comp, block, def, nul)); |
| } |
| } |
| } |
| } |
| } |
| // Column option is specified multiple times for the same column |
| ParserError("CREATE TABLE Foo(a int PRIMARY KEY ENCODING RLE ENCODING PLAIN) " + |
| "STORED AS KUDU"); |
| // Constant expr used in DEFAULT |
| ParsesOk("CREATE TABLE Foo(a int PRIMARY KEY, b int DEFAULT 1+1) STORED AS KUDU"); |
| ParsesOk("CREATE TABLE Foo(a int PRIMARY KEY, b float DEFAULT cast(1.1 as float)) " + |
| "STORED AS KUDU"); |
| // Non-literal value used in BLOCK_SIZE |
| ParserError("CREATE TABLE Foo(a int PRIMARY KEY, b int BLOCK_SIZE 1+1) " + |
| "STORED AS KUDU"); |
| ParserError("CREATE TABLE Foo(a int PRIMARY KEY BLOCK_SIZE -1) STORED AS KUDU"); |
| } |
| |
| @Test |
| public void TestCreateDataSource() { |
| ParsesOk("CREATE DATA SOURCE foo LOCATION '/foo.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParsesOk("CREATE DATA SOURCE foo LOCATION \"/foo.jar\" CLASS \"com.bar.Foo\" " + |
| "API_VERSION \"V1\""); |
| ParsesOk("CREATE DATA SOURCE foo LOCATION '/x/foo@hi_^!#.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| |
| ParserError("CREATE DATA foo LOCATION '/foo.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SRC foo.bar LOCATION '/foo.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo.bar LOCATION '/x/foo.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION /x/foo.jar CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION '/x/foo.jar' CLASS com.bar.Foo " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION '/x/foo.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION V1"); |
| ParserError("CREATE DATA SOURCE LOCATION '/x/foo.jar' CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION CLASS 'com.bar.Foo' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION '/foo.jar' API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION '/foo.jar' CLASS API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION '/foo.jar' CLASS 'com.bar.Foo'"); |
| ParserError("CREATE DATA SOURCE foo LOCATION '/foo.jar' CLASS 'Foo' API_VERSION"); |
| ParserError("CREATE DATA SOURCE foo CLASS 'com.bar.Foo' LOCATION '/x/foo.jar' " + |
| "API_VERSION 'V1'"); |
| ParserError("CREATE DATA SOURCE foo CLASS 'com.bar.Foo' API_VERSION 'V1' " + |
| "LOCATION '/x/foo.jar' "); |
| ParserError("CREATE DATA SOURCE foo API_VERSION 'V1' LOCATION '/x/foo.jar' " + |
| "CLASS 'com.bar.Foo'"); |
| } |
| |
| @Test |
| public void TestDropDataSource() { |
| ParsesOk("DROP DATA SOURCE foo"); |
| |
| ParserError("DROP DATA foo"); |
| ParserError("DROP DATA SRC foo"); |
| ParserError("DROP DATA SOURCE foo.bar"); |
| ParserError("DROP DATA SOURCE"); |
| } |
| |
| @Test |
| public void TestCreateView() { |
| ParsesOk("CREATE VIEW Bar AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Bar COMMENT 'test' AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Bar (x, y, z) AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Bar (x, y COMMENT 'foo', z) AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Bar (x, y, z) COMMENT 'test' AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW IF NOT EXISTS Bar AS SELECT a, b, c from t"); |
| |
| ParsesOk("CREATE VIEW Foo.Bar AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Foo.Bar COMMENT 'test' AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Foo.Bar (x, y, z) AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Foo.Bar (x, y, z COMMENT 'foo') AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW Foo.Bar (x, y, z) COMMENT 'test' AS SELECT a, b, c from t"); |
| ParsesOk("CREATE VIEW IF NOT EXISTS Foo.Bar AS SELECT a, b, c from t"); |
| |
| // Test all valid query statements as view definitions. |
| ParsesOk("CREATE VIEW Bar AS SELECT 1, 2, 3"); |
| ParsesOk("CREATE VIEW Bar AS VALUES(1, 2, 3)"); |
| ParsesOk("CREATE VIEW Bar AS SELECT 1, 2, 3 UNION ALL select 4, 5, 6"); |
| ParsesOk("CREATE VIEW Bar AS WITH t AS (SELECT 1, 2, 3) SELECT * FROM t"); |
| |
| // Mismatched number of columns in column definition and view definition parses ok. |
| ParsesOk("CREATE VIEW Bar (x, y) AS SELECT 1, 2, 3"); |
| |
| // No view name. |
| ParserError("CREATE VIEW AS SELECT c FROM t"); |
| // Missing AS keyword |
| ParserError("CREATE VIEW Bar SELECT c FROM t"); |
| // Empty column definition not allowed. |
| ParserError("CREATE VIEW Foo.Bar () AS SELECT c FROM t"); |
| // Column definitions cannot include types. |
| ParserError("CREATE VIEW Foo.Bar (x int) AS SELECT c FROM t"); |
| ParserError("CREATE VIEW Foo.Bar (x int COMMENT 'x') AS SELECT c FROM t"); |
| // A type does not parse as an identifier. |
| ParserError("CREATE VIEW Foo.Bar (int COMMENT 'x') AS SELECT c FROM t"); |
| // Missing view definition. |
| ParserError("CREATE VIEW Foo.Bar (x) AS"); |
| // Invalid view definitions. A view definition must be a query statement. |
| ParserError("CREATE VIEW Foo.Bar (x) AS INSERT INTO t select * from t"); |
| ParserError("CREATE VIEW Foo.Bar (x) AS UPSERT INTO t select * from t"); |
| ParserError("CREATE VIEW Foo.Bar (x) AS CREATE TABLE Wrong (i int)"); |
| ParserError("CREATE VIEW Foo.Bar (x) AS ALTER TABLE Foo COLUMNS (i int, s string)"); |
| ParserError("CREATE VIEW Foo.Bar (x) AS CREATE VIEW Foo.Bar AS SELECT 1"); |
| ParserError("CREATE VIEW Foo.Bar (x) AS ALTER VIEW Foo.Bar AS SELECT 1"); |
| } |
| |
| @Test |
| public void TestAlterView() { |
| ParsesOk("ALTER VIEW Bar AS SELECT 1, 2, 3"); |
| ParsesOk("ALTER VIEW Bar AS SELECT a, b, c FROM t"); |
| ParsesOk("ALTER VIEW Bar AS VALUES(1, 2, 3)"); |
| ParsesOk("ALTER VIEW Bar AS SELECT 1, 2, 3 UNION ALL select 4, 5, 6"); |
| ParsesOk("ALTER VIEW Bar (x, y, z) AS SELECT a, b, c from t"); |
| ParsesOk("ALTER VIEW Bar (x, y COMMENT 'foo', z) AS SELECT a, b, c from t"); |
| |
| ParsesOk("ALTER VIEW Foo.Bar AS SELECT 1, 2, 3"); |
| ParsesOk("ALTER VIEW Foo.Bar AS SELECT a, b, c FROM t"); |
| ParsesOk("ALTER VIEW Foo.Bar AS VALUES(1, 2, 3)"); |
| ParsesOk("ALTER VIEW Foo.Bar AS SELECT 1, 2, 3 UNION ALL select 4, 5, 6"); |
| ParsesOk("ALTER VIEW Foo.Bar AS WITH t AS (SELECT 1, 2, 3) SELECT * FROM t"); |
| ParsesOk("ALTER VIEW Foo.Bar (x, y, z) AS SELECT a, b, c from t"); |
| ParsesOk("ALTER VIEW Foo.Bar (x, y, z COMMENT 'foo') AS SELECT a, b, c from t"); |
| |
| // Mismatched number of columns in column definition and view definition parses ok. |
| ParsesOk("ALTER VIEW Bar (x, y) AS SELECT 1, 2, 3"); |
| |
| // Must be ALTER VIEW not ALTER TABLE. |
| ParserError("ALTER TABLE Foo.Bar AS SELECT 1, 2, 3"); |
| // Missing view name. |
| ParserError("ALTER VIEW AS SELECT 1, 2, 3"); |
| // Missing AS name. |
| ParserError("ALTER VIEW Foo.Bar SELECT 1, 2, 3"); |
| // Missing view definition. |
| ParserError("ALTER VIEW Foo.Bar AS"); |
| // Empty column definition not allowed. |
| ParserError("ALTER VIEW Foo.Bar () AS SELECT c FROM t"); |
| // Column definitions cannot include types. |
| ParserError("ALTER VIEW Foo.Bar (x int) AS SELECT c FROM t"); |
| ParserError("ALTER VIEW Foo.Bar (x int COMMENT 'x') AS SELECT c FROM t"); |
| // A type does not parse as an identifier. |
| ParserError("ALTER VIEW Foo.Bar (int COMMENT 'x') AS SELECT c FROM t"); |
| |
| // Invalid view definitions. A view definition must be a query statement. |
| ParserError("ALTER VIEW Foo.Bar AS INSERT INTO t select * from t"); |
| ParserError("ALTER VIEW Foo.Bar AS UPSERT INTO t select * from t"); |
| ParserError("ALTER VIEW Foo.Bar AS CREATE TABLE Wrong (i int)"); |
| ParserError("ALTER VIEW Foo.Bar AS ALTER TABLE Foo COLUMNS (i int, s string)"); |
| ParserError("ALTER VIEW Foo.Bar AS CREATE VIEW Foo.Bar AS SELECT 1, 2, 3"); |
| ParserError("ALTER VIEW Foo.Bar AS ALTER VIEW Foo.Bar AS SELECT 1, 2, 3"); |
| } |
| |
| @Test |
| public void TestCreateTableAsSelect() { |
| ParsesOk("CREATE TABLE Foo AS SELECT 1, 2, 3"); |
| ParsesOk("CREATE TABLE Foo AS SELECT * from foo.bar"); |
| ParsesOk("CREATE TABLE Foo.Bar AS SELECT int_col, bool_col from tbl limit 10"); |
| ParsesOk("CREATE TABLE Foo.Bar LOCATION '/a/b' AS SELECT * from foo"); |
| ParsesOk("CREATE TABLE IF NOT EXISTS Foo.Bar LOCATION '/a/b' AS SELECT * from foo"); |
| ParsesOk("CREATE TABLE Foo STORED AS PARQUET AS SELECT 1"); |
| ParsesOk("CREATE TABLE Foo ROW FORMAT DELIMITED STORED AS PARQUETFILE AS SELECT 1"); |
| ParsesOk("CREATE TABLE Foo TBLPROPERTIES ('a'='b', 'c'='d') AS SELECT * from bar"); |
| ParsesOk("CREATE TABLE Foo PRIMARY KEY (a, b) AS SELECT * from bar"); |
| ParsesOk("CREATE TABLE Foo PRIMARY KEY (a, b) PARTITION BY HASH PARTITIONS 2 " + |
| "AS SELECT * from bar"); |
| ParsesOk("CREATE TABLE Foo PRIMARY KEY (a, b) PARTITION BY HASH (b) PARTITIONS 2 " + |
| "AS SELECT * from bar"); |
| |
| // With clause works |
| ParsesOk("CREATE TABLE Foo AS with t1 as (select 1) select * from t1"); |
| |
| // Incomplete AS SELECT statement |
| ParserError("CREATE TABLE Foo ROW FORMAT DELIMITED STORED AS PARQUET AS SELECT"); |
| ParserError("CREATE TABLE Foo ROW FORMAT DELIMITED STORED AS PARQUET AS WITH"); |
| ParserError("CREATE TABLE Foo ROW FORMAT DELIMITED STORED AS PARQUET AS"); |
| |
| // INSERT/UPSERT statements are not allowed |
| ParserError("CREATE TABLE Foo AS INSERT INTO Foo SELECT 1"); |
| ParserError("CREATE TABLE Foo AS UPSERT INTO Foo SELECT 1"); |
| |
| // Column and partition definitions not allowed |
| ParserError("CREATE TABLE Foo(i int) AS SELECT 1"); |
| ParserError("CREATE TABLE Foo PARTITIONED BY(i int) AS SELECT 1"); |
| |
| // Partitioned by syntax following insert into syntax |
| ParsesOk("CREATE TABLE Foo PARTITIONED BY (a) AS SELECT 1"); |
| ParsesOk("CREATE TABLE Foo PARTITIONED BY (a) ROW FORMAT DELIMITED STORED AS " + |
| "PARQUETFILE AS SELECT 1"); |
| ParsesOk("CREATE TABLE Foo PARTITIONED BY (a) AS SELECT 1, 2"); |
| ParsesOk("CREATE TABLE Foo PARTITIONED BY (a) AS SELECT * from Bar"); |
| ParsesOk("CREATE TABLE Foo PARTITIONED BY (a, b) AS SELECT * from Bar"); |
| ParserError("CREATE TABLE Foo PARTITIONED BY (a=2, b) AS SELECT * from Bar"); |
| ParserError("CREATE TABLE Foo PARTITIONED BY (a, b=2) AS SELECT * from Bar"); |
| |
| // Flexible partitioning |
| ParsesOk("CREATE TABLE Foo PRIMARY KEY (i) PARTITION BY HASH(i) PARTITIONS 4 AS " + |
| "SELECT 1"); |
| ParserError("CREATE TABLE Foo PARTITION BY HASH(i) PARTITIONS 4 AS SELECT 1"); |
| ParsesOk("CREATE TABLE Foo PRIMARY KEY (a) PARTITION BY HASH(a) PARTITIONS 4 " + |
| "TBLPROPERTIES ('a'='b', 'c'='d') AS SELECT * from bar"); |
| ParsesOk("CREATE TABLE Foo PRIMARY KEY (a) PARTITION BY RANGE(a) " + |
| "(PARTITION 1 < VALUES < 10, PARTITION 10 <= VALUES < 20, PARTITION VALUE = 30) " + |
| "STORED AS KUDU AS SELECT * FROM Bar"); |
| } |
| |
| @Test |
| public void TestDrop() { |
| // PURGE is optional |
| String[] purgeKw = {"PURGE", ""}; |
| for (String kw: purgeKw) { |
| ParsesOk(String.format("DROP TABLE Foo %s", kw)); |
| ParsesOk(String.format("DROP TABLE Foo.Bar %s", kw)); |
| ParsesOk(String.format("DROP TABLE IF EXISTS Foo %s", kw)); |
| ParsesOk(String.format("DROP TABLE IF EXISTS Foo.Bar %s", kw)); |
| } |
| |
| ParsesOk("DROP VIEW Foo"); |
| ParsesOk("DROP VIEW Foo.Bar"); |
| ParsesOk("DROP VIEW IF EXISTS Foo.Bar"); |
| ParsesOk("DROP DATABASE Foo"); |
| ParsesOk("DROP DATABASE Foo CASCADE"); |
| ParsesOk("DROP DATABASE Foo RESTRICT"); |
| ParsesOk("DROP SCHEMA Foo"); |
| ParsesOk("DROP DATABASE IF EXISTS Foo"); |
| ParsesOk("DROP DATABASE IF EXISTS Foo CASCADE"); |
| ParsesOk("DROP DATABASE IF EXISTS Foo RESTRICT"); |
| ParsesOk("DROP SCHEMA IF EXISTS Foo"); |
| ParsesOk("DROP FUNCTION Foo()"); |
| ParsesOk("DROP AGGREGATE FUNCTION Foo(INT)"); |
| ParsesOk("DROP FUNCTION Foo.Foo(INT)"); |
| ParsesOk("DROP AGGREGATE FUNCTION IF EXISTS Foo()"); |
| ParsesOk("DROP FUNCTION IF EXISTS Foo(INT)"); |
| ParsesOk("DROP FUNCTION IF EXISTS Foo(INT...)"); |
| ParsesOk("DROP FUNCTION Foo"); |
| ParsesOk("DROP FUNCTION IF EXISTS Foo"); |
| |
| ParserError("DROP"); |
| ParserError("DROP Foo"); |
| ParserError("DROP DATABASE Foo.Bar"); |
| ParserError("DROP SCHEMA Foo.Bar"); |
| ParserError("DROP SCHEMA Foo PURGE"); |
| ParserError("DROP DATABASE Foo Bar"); |
| ParserError("DROP DATABASE Foo PURGE"); |
| ParserError("DROP DATABASE CASCADE Foo"); |
| ParserError("DROP DATABASE CASCADE RESTRICT Foo"); |
| ParserError("DROP DATABASE RESTRICT CASCADE Foo"); |
| ParserError("DROP CASCADE DATABASE IF EXISTS Foo"); |
| ParserError("DROP RESTRICT DATABASE IF EXISTS Foo"); |
| ParserError("DROP SCHEMA Foo Bar"); |
| ParserError("DROP TABLE IF Foo"); |
| ParserError("DROP TABLE EXISTS Foo"); |
| ParserError("DROP IF EXISTS TABLE Foo"); |
| ParserError("DROP TBL Foo"); |
| ParserError("DROP VIEW IF Foo"); |
| ParserError("DROP VIEW EXISTS Foo"); |
| ParserError("DROP IF EXISTS VIEW Foo"); |
| ParserError("DROP VIW Foo"); |
| ParserError("DROP VIEW Foo purge"); |
| ParserError("DROP FUNCTION Foo)"); |
| ParserError("DROP FUNCTION Foo("); |
| ParserError("DROP FUNCTION Foo PURGE"); |
| ParserError("DROP FUNCTION"); |
| ParserError("DROP BLAH FUNCTION"); |
| ParserError("DROP IF EXISTS FUNCTION Foo()"); |
| ParserError("DROP FUNCTION Foo RETURNS INT"); |
| ParserError("DROP FUNCTION Foo(INT) RETURNS INT"); |
| ParserError("DROP FUNCTION Foo.(INT) RETURNS INT"); |
| ParserError("DROP FUNCTION Foo..(INT) RETURNS INT"); |
| ParserError("DROP FUNCTION Foo(NULL) RETURNS INT"); |
| ParserError("DROP FUNCTION Foo(INT) RETURNS NULL"); |
| ParserError("DROP BLAH FUNCTION IF EXISTS Foo.A.Foo(INT)"); |
| ParserError("DROP FUNCTION IF EXISTS Foo(...)"); |
| } |
| |
| @Test |
| public void TestTruncateTable() { |
| ParsesOk("TRUNCATE TABLE Foo"); |
| ParsesOk("TRUNCATE TABLE Foo.Bar"); |
| ParsesOk("TRUNCATE Foo"); |
| ParsesOk("TRUNCATE Foo.Bar"); |
| |
| ParserError("TRUNCATE"); |
| ParserError("TRUNCATE TABLE"); |
| ParserError("TRUNCATE TBL Foo"); |
| ParserError("TRUNCATE VIEW Foo"); |
| ParserError("TRUNCATE DATABASE Foo"); |
| } |
| |
| @Test |
| public void TestLoadData() { |
| ParsesOk("LOAD DATA INPATH '/a/b' INTO TABLE Foo"); |
| ParsesOk("LOAD DATA INPATH '/a/b' INTO TABLE Foo.Bar"); |
| ParsesOk("LOAD DATA INPATH '/a/b' OVERWRITE INTO TABLE Foo.Bar"); |
| ParsesOk("LOAD DATA INPATH '/a/b' INTO TABLE Foo PARTITION(a=1, b='asdf')"); |
| ParsesOk("LOAD DATA INPATH '/a/b' INTO TABLE Foo PARTITION(a=1)"); |
| |
| ParserError("LOAD DATA INPATH '/a/b' INTO Foo PARTITION(a=1)"); |
| ParserError("LOAD DATA INPATH '/a/b' INTO Foo PARTITION(a)"); |
| ParserError("LOAD DATA INPATH '/a/b' INTO Foo PARTITION"); |
| ParserError("LOAD DATA INPATH /a/b/c INTO Foo"); |
| ParserError("LOAD DATA INPATH /a/b/c INTO Foo"); |
| |
| // Loading data from a 'LOCAL' path is not supported. |
| ParserError("LOAD DATA LOCAL INPATH '/a/b' INTO TABLE Foo"); |
| } |
| |
| /** |
| * Wraps the given typeDefs in a CREATE TABLE and CAST and runs ParsesOk(). |
| * Also tests that the type is parsed correctly in ARRAY, MAP, and STRUCT types. |
| */ |
| private void TypeDefsParseOk(String... typeDefs) { |
| for (String typeDefStr: typeDefs) { |
| ParsesOk(String.format("CREATE TABLE t (i %s)", typeDefStr)); |
| ParsesOk(String.format("SELECT CAST (i AS %s)", typeDefStr)); |
| // Test typeDefStr in complex types. |
| ParsesOk(String.format("CREATE TABLE t (i MAP<%s, %s>)", typeDefStr, typeDefStr)); |
| ParsesOk(String.format("CREATE TABLE t (i ARRAY<%s>)", typeDefStr)); |
| ParsesOk(String.format("CREATE TABLE t (i STRUCT<f:%s>)", typeDefStr)); |
| } |
| } |
| |
| /** |
| * Asserts that the given typeDefs fail to parse. |
| */ |
| private void TypeDefsError(String... typeDefs) { |
| for (String typeDefStr: typeDefs) { |
| ParserError(String.format("CREATE TABLE t (i %s)", typeDefStr)); |
| ParserError(String.format("SELECT CAST (i AS %s)", typeDefStr)); |
| } |
| } |
| |
| @Test |
| public void TestTypes() { |
| // Test primitive types. |
| TypeDefsParseOk("BOOLEAN"); |
| TypeDefsParseOk("TINYINT"); |
| TypeDefsParseOk("SMALLINT"); |
| TypeDefsParseOk("INT", "INTEGER"); |
| TypeDefsParseOk("BIGINT"); |
| TypeDefsParseOk("FLOAT"); |
| TypeDefsParseOk("DOUBLE", "REAL"); |
| TypeDefsParseOk("STRING"); |
| TypeDefsParseOk("CHAR(1)", "CHAR(20)"); |
| TypeDefsParseOk("VARCHAR(1)", "VARCHAR(20)"); |
| TypeDefsParseOk("BINARY"); |
| TypeDefsParseOk("DECIMAL"); |
| TypeDefsParseOk("TIMESTAMP"); |
| TypeDefsParseOk("DATE"); |
| |
| // Test decimal. |
| TypeDefsParseOk("DECIMAL"); |
| TypeDefsParseOk("DECIMAL(1)"); |
| TypeDefsParseOk("DECIMAL(1, 2)"); |
| TypeDefsParseOk("DECIMAL(2, 1)"); |
| TypeDefsParseOk("DECIMAL(6, 6)"); |
| TypeDefsParseOk("DECIMAL(100, 0)"); |
| TypeDefsParseOk("DECIMAL(0, 0)"); |
| |
| TypeDefsError("DECIMAL()"); |
| TypeDefsError("DECIMAL(a)"); |
| TypeDefsError("DECIMAL(1, a)"); |
| TypeDefsError("DECIMAL(1, 2, 3)"); |
| TypeDefsError("DECIMAL(-1)"); |
| |
| // Test complex types. |
| TypeDefsParseOk("ARRAY<BIGINT>"); |
| TypeDefsParseOk("MAP<TINYINT, DOUBLE>"); |
| TypeDefsParseOk("STRUCT<f:TINYINT>"); |
| TypeDefsParseOk("STRUCT<a:TINYINT, b:BIGINT, c:DOUBLE>"); |
| TypeDefsParseOk("STRUCT<a:TINYINT COMMENT 'x', b:BIGINT, c:DOUBLE COMMENT 'y'>"); |
| |
| // Test that struct-field names can be identifiers or keywords even if unquoted. |
| // This behavior is needed to parse type strings from the Hive Metastore which |
| // may have unquoted identifiers corresponding to keywords. |
| for (String keyword: SqlScanner.keywordMap.keySet()) { |
| // Skip keywords that are not valid field/column names in the Metastore. |
| if (!MetastoreShim.validateName(keyword)) continue; |
| String structType = "STRUCT<" + keyword + ":INT>"; |
| TypeDefsParseOk(structType); |
| } |
| |
| TypeDefsError("CHAR()"); |
| TypeDefsError("CHAR(1, 1)"); |
| TypeDefsError("ARRAY<>"); |
| TypeDefsError("ARRAY BIGINT"); |
| TypeDefsError("MAP<>"); |
| TypeDefsError("MAP<TINYINT>"); |
| TypeDefsError("MAP<TINYINT, BIGINT, DOUBLE>"); |
| TypeDefsError("STRUCT<>"); |
| TypeDefsError("STRUCT<TINYINT>"); |
| TypeDefsError("STRUCT<a TINYINT>"); |
| TypeDefsError("STRUCT<'a':TINYINT>"); |
| } |
| |
| @Test |
| public void TestResetMetadata() { |
| ParsesOk("invalidate metadata"); |
| ParsesOk("invalidate metadata Foo"); |
| ParsesOk("invalidate metadata Foo.S"); |
| ParsesOk("refresh Foo"); |
| ParsesOk("refresh Foo.S"); |
| ParsesOk("refresh Foo partition (col=2)"); |
| ParsesOk("refresh Foo.S partition (col=2)"); |
| ParsesOk("refresh Foo.S partition (col1 = 2, col2 = 3)"); |
| ParsesOk("refresh functions Foo"); |
| ParsesOk("refresh authorization"); |
| |
| ParserError("invalidate"); |
| ParserError("invalidate metadata Foo.S.S"); |
| ParserError("invalidate metadata partition (col=2)"); |
| ParserError("invalidate metadata Foo.S partition (col=2)"); |
| ParserError("REFRESH Foo.S.S"); |
| ParserError("refresh"); |
| ParserError("refresh Foo.S partition (col1 = 2, col2)"); |
| ParserError("refresh Foo.S partition ()"); |
| ParserError("refresh functions Foo.S"); |
| ParserError("refresh authorization Foo"); |
| } |
| |
| @Test |
| public void TestComputeDropStats() { |
| String[] prefixes = {"compute", "drop"}; |
| String[] okSuffixes = {"stats bar", "stats `bar`", "stats foo.bar", |
| "stats `foo`.`bar`"}; |
| String[] okComputeSuffixes = {"(ab)", "(ab, bc)", "()"}; |
| String[] errorSuffixes = { |
| // Missing table name. |
| "stats", |
| // Missing 'stats' keyword. |
| "`bar`", |
| // Cannot use string literal as table name. |
| "stats 'foo'", |
| // Cannot analyze multiple tables in one stmt. |
| "stats foo bar" |
| }; |
| |
| for (String prefix: prefixes) { |
| for (String suffix: okSuffixes) { |
| ParsesOk(prefix + " " + suffix); |
| } |
| for (String suffix: errorSuffixes) { |
| ParserError(prefix + " " + suffix); |
| } |
| } |
| for (String suffix: okSuffixes) { |
| for (String computeSuffix: okComputeSuffixes) { |
| ParsesOk("compute" + " " + suffix + " " + computeSuffix); |
| } |
| } |
| } |
| |
| @Test |
| public void TestGetErrorMsg() { |
| |
| // missing select |
| ParserError("c, b, c from t", |
| "Syntax error in line 1:\n" + |
| "c, b, c from t\n" + |
| "^\n" + |
| "Encountered: IDENTIFIER\n" + |
| "Expected: ALTER, COMMENT, COMPUTE, COPY, CREATE, DELETE, DESCRIBE, DROP, " + |
| "EXPLAIN, GRANT, INSERT, INVALIDATE, LOAD, REFRESH, REVOKE, SELECT, SET, " + |
| "SHOW, TRUNCATE, UPDATE, UPSERT, USE, VALUES, WITH\n"); |
| |
| // missing select list |
| ParserError("select from t", |
| "Syntax error in line 1:\n" + |
| "select from t\n" + |
| " ^\n" + |
| "Encountered: FROM\n" + |
| "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, IF, " + |
| "INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, STRAIGHT_JOIN, TRUNCATE, TRUE, " + |
| "IDENTIFIER"); |
| |
| // missing from |
| ParserError("select c, b, c where a = 5", |
| "Syntax error in line 1:\n" + |
| "select c, b, c where a = 5\n" + |
| " ^\n" + |
| "Encountered: WHERE\n" + |
| "Expected: AND, AS, BETWEEN, DEFAULT, DIV, FROM, ILIKE, IN, IREGEXP, IS, LIKE, " + |
| "LIMIT, NOT, OR, ORDER, REGEXP, RLIKE, UNION, COMMA, IDENTIFIER\n"); |
| |
| // missing table list |
| ParserError("select c, b, c from where a = 5", |
| "Syntax error in line 1:\n" + |
| "select c, b, c from where a = 5\n" + |
| " ^\n" + |
| "Encountered: WHERE\n" + |
| "Expected: DEFAULT, IDENTIFIER\n"); |
| |
| // missing predicate in where clause (no group by) |
| ParserError("select c, b, c from t where", |
| "Syntax error in line 1:\n" + |
| "select c, b, c from t where\n" + |
| " ^\n" + |
| "Encountered: EOF\n" + |
| "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, IF, INTERVAL, LEFT, NOT, " + |
| "NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER"); |
| |
| // missing predicate in where clause (group by) |
| ParserError("select c, b, c from t where group by a, b", |
| "Syntax error in line 1:\n" + |
| "select c, b, c from t where group by a, b\n" + |
| " ^\n" + |
| "Encountered: GROUP\n" + |
| "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, IF, INTERVAL, LEFT, NOT, " + |
| "NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER"); |
| |
| // unmatched string literal starting with " |
| ParserError("select c, \"b, c from t", |
| "Unmatched string literal in line 1:\n" + |
| "select c, \"b, c from t\n" + |
| " ^\n"); |
| |
| // unmatched string literal starting with ' |
| ParserError("select c, 'b, c from t", |
| "Unmatched string literal in line 1:\n" + |
| "select c, 'b, c from t\n" + |
| " ^\n"); |
| |
| // test placement of error indicator ^ on queries with multiple lines |
| ParserError("select (i + 5)(1 - i) from t", |
| "Syntax error in line 1:\n" + |
| "select (i + 5)(1 - i) from t\n" + |
| " ^\n" + |
| "Encountered: (\n" + |
| "Expected:"); |
| |
| ParserError("select (i + 5)\n(1 - i) from t", |
| "Syntax error in line 2:\n" + |
| "(1 - i) from t\n" + |
| "^\n" + |
| "Encountered: (\n" + |
| "Expected"); |
| |
| ParserError("select (i + 5)\n(1 - i)\nfrom t", |
| "Syntax error in line 2:\n" + |
| "(1 - i)\n" + |
| "^\n" + |
| "Encountered: (\n" + |
| "Expected"); |
| |
| // Long line: error in the middle |
| ParserError("select c, b, c,c,c,c,c,c,c,c,c,a a a,c,c,c,c,c,c,c,cd,c,d,d,,c, from t", |
| "Syntax error in line 1:\n" + |
| "... b, c,c,c,c,c,c,c,c,c,a a a,c,c,c,c,c,c,c,cd,c,d,d,,c,...\n" + |
| " ^\n" + |
| "Encountered: IDENTIFIER\n" + |
| "Expected: CROSS, FROM, FULL, GROUP, HAVING, INNER, JOIN, LEFT, LIMIT, OFFSET, " + |
| "ON, ORDER, RIGHT, STRAIGHT_JOIN, TABLESAMPLE, UNION, USING, WHERE, COMMA\n"); |
| |
| // Long line: error close to the start |
| ParserError("select a a a, b, c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,cd,c,d,d,,c, from t", |
| "Syntax error in line 1:\n" + |
| "select a a a, b, c,c,c,c,c,c,c,c,c,c,c,...\n" + |
| " ^\n" + |
| "Encountered: IDENTIFIER\n" + |
| "Expected: CROSS, FROM, FULL, GROUP, HAVING, INNER, JOIN, LEFT, LIMIT, OFFSET, " + |
| "ON, ORDER, RIGHT, STRAIGHT_JOIN, TABLESAMPLE, UNION, USING, WHERE, COMMA\n"); |
| |
| // Long line: error close to the end |
| ParserError("select a, b, c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,c,cd,c,d,d, ,c, from t", |
| "Syntax error in line 1:\n" + |
| "...c,c,c,c,c,c,c,c,cd,c,d,d, ,c, from t\n" + |
| " ^\n" + |
| "Encountered: COMMA\n" + |
| "Expected: CASE, CAST, DATE, DEFAULT, EXISTS, FALSE, IF, INTERVAL, LEFT, NOT, " + |
| "NULL, REPLACE, RIGHT, TRUNCATE, TRUE, IDENTIFIER"); |
| |
| // Parsing identifiers that have different names printed as EXPECTED |
| ParserError("DROP DATA SRC foo", |
| "Syntax error in line 1:\n" + |
| "DROP DATA SRC foo\n" + |
| " ^\n" + |
| "Encountered: IDENTIFIER\n" + |
| "Expected: SOURCE\n"); |
| ParserError("SHOW DATA SRCS", |
| "Syntax error in line 1:\n" + |
| "SHOW DATA SRCS\n" + |
| " ^\n" + |
| "Encountered: IDENTIFIER\n" + |
| "Expected: SOURCES\n"); |
| ParserError("USE ` `", |
| "Syntax error in line 1:\n" + |
| "USE ` `\n" + |
| " ^\n" + |
| "Encountered: EMPTY IDENTIFIER\n" + |
| "Expected: DEFAULT, IDENTIFIER\n"); |
| |
| // Expecting = token |
| ParserError("SET foo", |
| "Syntax error in line 1:\n" + |
| "SET foo\n" + |
| " ^\n" + |
| "Encountered: EOF\n" + |
| "Expected: =\n"); |
| |
| // End with \n |
| ParserError("SELECT\n", |
| "Syntax error in line 2:\n" + |
| "\n" + |
| "^\n" + |
| "Encountered: EOF\n" + |
| "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, " + |
| "IF, INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, " + |
| "STRAIGHT_JOIN, TRUNCATE, TRUE, IDENTIFIER\n"); |
| ParserError("SELECT\n\n", |
| "Syntax error in line 3:\n" + |
| "\n" + |
| "^\n" + |
| "Encountered: EOF\n" + |
| "Expected: ALL, CASE, CAST, DATE, DEFAULT, DISTINCT, EXISTS, FALSE, " + |
| "IF, INTERVAL, LEFT, NOT, NULL, REPLACE, RIGHT, " + |
| "STRAIGHT_JOIN, TRUNCATE, TRUE, IDENTIFIER\n"); |
| } |
| |
| @Test |
| public void TestExplain() { |
| ParsesOk("explain select a from tbl"); |
| ParsesOk("explain insert into tbl select a, b, c, d from tbl"); |
| ParsesOk("explain upsert into tbl select a, b, c, d from tbl"); |
| ParserError("explain"); |
| // cannot EXPLAIN an explain stmt |
| ParserError("explain explain select a from tbl"); |
| // cannot EXPLAIN DDL stmt |
| ParserError("explain CREATE TABLE Foo (i int)"); |
| } |
| |
| @Test |
| public void TestSubqueries() { |
| // Binary nested predicates |
| String subquery = "(SELECT count(*) FROM bar)"; |
| String[] operators = {"=", "!=", "<>", ">", ">=", "<", "<=", "<=>", |
| "IS DISTINCT FROM", "IS NOT DISTINCT FROM"}; |
| for (String op: operators) { |
| ParsesOk(String.format("SELECT * FROM foo WHERE a %s %s", op, subquery)); |
| ParsesOk(String.format("SELECT * FROM foo WHERE %s %s a", subquery, op)); |
| } |
| // Binary predicate with an arithmetic expr |
| ParsesOk("SELECT * FROM foo WHERE a+1 > (SELECT count(a) FROM bar)"); |
| ParsesOk("SELECT * FROM foo WHERE (SELECT count(a) FROM bar) < a+1"); |
| |
| // [NOT] IN nested predicates |
| ParsesOk("SELECT * FROM foo WHERE a IN (SELECT a FROM bar)"); |
| ParsesOk("SELECT * FROM foo WHERE a NOT IN (SELECT a FROM bar)"); |
| |
| // [NOT] EXISTS predicates |
| ParsesOk("SELECT * FROM foo WHERE EXISTS (SELECT a FROM bar WHERE b > 0)"); |
| ParsesOk("SELECT * FROM foo WHERE NOT EXISTS (SELECT a FROM bar WHERE b > 0)"); |
| ParsesOk("SELECT * FROM foo WHERE NOT (EXISTS (SELECT a FROM bar))"); |
| |
| // Compound nested predicates |
| ParsesOk("SELECT * FROM foo WHERE a = (SELECT count(a) FROM bar) AND " + |
| "b != (SELECT count(b) FROM baz) and c IN (SELECT c FROM qux)"); |
| ParsesOk("SELECT * FROM foo WHERE EXISTS (SELECT a FROM bar WHERE b < 0) AND " + |
| "NOT EXISTS (SELECT a FROM baz WHERE b > 0)"); |
| ParsesOk("SELECT * FROM foo WHERE EXISTS (SELECT a from bar) AND " + |
| "NOT EXISTS (SELECT a FROM baz) AND b IN (SELECT b FROM bar) AND " + |
| "c NOT IN (SELECT c FROM qux) AND d = (SELECT max(d) FROM quux)"); |
| |
| // Nested parentheses |
| ParsesOk("SELECT * FROM foo WHERE EXISTS ((SELECT * FROM bar))"); |
| ParsesOk("SELECT * FROM foo WHERE EXISTS (((SELECT * FROM bar)))"); |
| ParsesOk("SELECT * FROM foo WHERE a IN ((SELECT a FROM bar))"); |
| ParsesOk("SELECT * FROM foo WHERE a = ((SELECT max(a) FROM bar))"); |
| |
| // More than one nesting level |
| ParsesOk("SELECT * FROM foo WHERE a IN (SELECT a FROM bar WHERE b IN " + |
| "(SELECT b FROM baz))"); |
| ParsesOk("SELECT * FROM foo WHERE EXISTS (SELECT a FROM bar WHERE b NOT IN " + |
| "(SELECT b FROM baz WHERE c < 10 AND d = (SELECT max(d) FROM qux)))"); |
| |
| // Binary predicate between subqueries |
| for (String op: operators) { |
| ParsesOk(String.format("SELECT * FROM foo WHERE %s %s %s", subquery, op, |
| subquery)); |
| } |
| |
| // Malformed nested subqueries |
| // Missing or misplaced parenthesis around a subquery |
| ParserError("SELECT * FROM foo WHERE a IN SELECT a FROM bar"); |
| ParserError("SELECT * FROM foo WHERE a = SELECT count(*) FROM bar"); |
| ParserError("SELECT * FROM foo WHERE EXISTS SELECT * FROM bar"); |
| ParserError("SELECT * FROM foo WHERE a IN (SELECT a FROM bar"); |
| ParserError("SELECT * FROM foo WHERE a IN SELECT a FROM bar)"); |
| ParserError("SELECT * FROM foo WHERE a IN (SELECT) a FROM bar"); |
| |
| // Invalid syntax for [NOT] EXISTS |
| ParserError("SELECT * FROM foo WHERE a EXISTS (SELECT * FROM bar)"); |
| ParserError("SELECT * FROM foo WHERE a NOT EXISTS (SELECT * FROM bar)"); |
| |
| // Set operations between subqueries |
| ParserError("SELECT * FROM foo WHERE EXISTS ((SELECT a FROM bar) UNION " + |
| "(SELECT a FROM baz))"); |
| |
| // Nested predicate in the HAVING clause |
| ParsesOk("SELECT a, count(*) FROM foo GROUP BY a HAVING count(*) > " + |
| "(SELECT count(*) FROM bar)"); |
| ParsesOk("SELECT a, count(*) FROM foo GROUP BY a HAVING 10 > " + |
| "(SELECT count(*) FROM bar)"); |
| |
| // Subquery in the SELECT clause |
| ParsesOk("SELECT a, b, (SELECT c FROM foo) FROM foo"); |
| ParsesOk("SELECT (SELECT a FROM foo), b, c FROM bar"); |
| ParsesOk("SELECT (SELECT (SELECT a FROM foo) FROM bar) FROM baz"); |
| ParsesOk("SELECT (SELECT a FROM foo)"); |
| |
| // Malformed subquery in the SELECT clause |
| ParserError("SELECT SELECT a FROM foo FROM bar"); |
| ParserError("SELECT (SELECT a FROM foo FROM bar"); |
| ParserError("SELECT SELECT a FROM foo) FROM bar"); |
| ParserError("SELECT (SELECT) a FROM foo"); |
| |
| // Subquery in the GROUP BY clause |
| ParsesOk("SELECT a, count(*) FROM foo GROUP BY (SELECT a FROM bar)"); |
| ParsesOk("SELECT a, count(*) FROM foo GROUP BY a, (SELECT b FROM bar)"); |
| |
| // Malformed subquery in the GROUP BY clause |
| ParserError("SELECT a, count(*) FROM foo GROUP BY SELECT a FROM bar"); |
| ParserError("SELECT a, count(*) FROM foo GROUP BY (SELECT) a FROM bar"); |
| ParserError("SELECT a, count(*) FROM foo GROUP BY (SELECT a FROM bar"); |
| |
| // Subquery in the ORDER BY clause |
| ParsesOk("SELECT a, b FROM foo ORDER BY (SELECT a FROM bar)"); |
| ParsesOk("SELECT a, b FROM foo ORDER BY (SELECT a FROM bar) DESC"); |
| ParsesOk("SELECT a, b FROM foo ORDER BY a ASC, (SELECT a FROM bar) DESC"); |
| |
| // Malformed subquery in the ORDER BY clause |
| ParserError("SELECT a, count(*) FROM foo ORDER BY SELECT a FROM bar"); |
| ParserError("SELECT a, count(*) FROM foo ORDER BY (SELECT) a FROM bar DESC"); |
| ParserError("SELECT a, count(*) FROM foo ORDER BY (SELECT a FROM bar ASC"); |
| } |
| |
| @Test |
| public void TestSet() { |
| ParsesOk("SET foo='bar'"); |
| ParsesOk("SET foo=\"bar\""); |
| ParsesOk("SET foo=bar"); |
| ParsesOk("SET foo = bar"); |
| ParsesOk("SET foo=1"); |
| ParsesOk("SET foo=true"); |
| ParsesOk("SET foo=false"); |
| ParsesOk("SET foo=1.2"); |
| ParsesOk("SET foo=null"); |
| ParsesOk("SET foo=10g"); |
| ParsesOk("SET `foo`=0"); |
| ParsesOk("SET foo=''"); |
| ParsesOk("SET"); |
| |
| ParserError("SET foo"); |
| ParserError("SET foo="); |
| ParserError("SET foo=1+2"); |
| ParserError("SET foo = '10"); |
| } |
| |
| @Test |
| public void TestCreateDropRole() { |
| ParsesOk("CREATE ROLE foo"); |
| ParsesOk("DROP ROLE foo"); |
| ParsesOk("DROP ROLE foo"); |
| ParsesOk("CREATE ROLE `role`"); |
| ParsesOk("DROP ROLE `role`"); |
| ParserError("CREATE ROLE"); |
| ParserError("DROP ROLE"); |
| ParserError("CREATE ROLE 'foo'"); |
| ParserError("DROP ROLE 'foo'"); |
| } |
| |
| @Test |
| public void TestGrantRevokeRole() { |
| ParsesOk("GRANT ROLE foo TO GROUP bar"); |
| ParsesOk("REVOKE ROLE foo FROM GROUP bar"); |
| ParsesOk("GRANT ROLE `foo` TO GROUP `bar`"); |
| |
| ParserError("GRANT ROLE foo TO GROUP"); |
| ParserError("GRANT ROLE foo FROM GROUP bar"); |
| |
| ParserError("REVOKE ROLE foo FROM GROUP"); |
| ParserError("REVOKE ROLE foo TO GROUP bar"); |
| } |
| |
| @Test |
| public void TestGrantRevokePrivilege() { |
| String[][] grantRevoke = {{"GRANT", "TO"}, {"REVOKE", "FROM"}}; |
| String[] resources = {"SERVER", "SERVER foo", "DATABASE foo", "TABLE foo", |
| "URI 'foo'"}; |
| String[] badResources = {"DATABASE", "TABLE", "URI", "URI foo", "TABLE 'foo'", |
| "SERVER 'foo'", "DATABASE 'foo'"}; |
| String[] privileges = {"SELECT", "INSERT", "ALL", "REFRESH", "CREATE", "ALTER", |
| "DROP"}; |
| String[] badPrivileges = {"UPDATE", "DELETE", "UPSERT", "FAKE"}; |
| String[] columnPrivResource = { |
| "SELECT (a, b) ON TABLE foo", |
| "SELECT () on TABLE foo", |
| "INSERT (a, b) ON TABLE foo", |
| "ALL (a, b) ON TABLE foo" |
| }; |
| String[] badColumnPrivResource = { |
| "SELECT (a,) ON TABLE foo", |
| "SELECT (*) ON TABLE foo", |
| "SELECT (a), b ON TABLE foo", |
| "SELECT ((a)) ON TABLE foo", |
| "SELECT (a, b) ON URI foo", |
| "SELECT ON TABLE (a, b) foo" |
| }; |
| String[] idents = {"myRole", "GROUP myGroup", "USER user", "ROLE myRole"}; |
| String[] badIdents = {"GROUP", "ROLE", "GROUP group", "GROUP role", "USER role", |
| "FOOBAR foobar", ""}; |
| |
| // Good SQL |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .forEach(this::ParsesOk); |
| |
| // Good SQL with grant option |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .stream() |
| .filter(s -> s.contains("GRANT")) |
| .map(s -> s + " WITH GRANT OPTION") |
| .forEach(this::ParsesOk); |
| |
| // Good SQL with revoke option |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .stream() |
| .filter(s -> s.contains("REVOKE")) |
| .map(s -> s.replace("REVOKE", "REVOKE GRANT OPTION FOR")) |
| .forEach(this::ParsesOk); |
| |
| // Column SQL |
| createColumnPrivSql(grantRevoke, columnPrivResource, idents) |
| .forEach(this::ParsesOk); |
| |
| // Bad Resources |
| createPrivSQL(grantRevoke, badResources, privileges, idents) |
| .forEach(this::ParserError); |
| |
| // Bad privileges |
| createPrivSQL(grantRevoke, resources, badPrivileges, idents) |
| .forEach(this::ParserError); |
| |
| // Bad idents |
| createPrivSQL(grantRevoke, resources, privileges, badIdents) |
| .forEach(this::ParserError); |
| |
| // Bad SQL with grant option |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .stream() |
| .filter(s -> s.contains("GRANT")) |
| .map(s -> s + " WITH GRANT") |
| .forEach(this::ParserError); |
| |
| // Bad SQL with grant option |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .stream() |
| .filter(s -> s.contains("GRANT")) |
| .map(s -> s + " WITH") |
| .forEach(this::ParserError); |
| |
| // Bad SQL with revoke option (omit for) |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .stream() |
| .filter(s -> s.contains("REVOKE")) |
| .map(s -> s.replace("REVOKE", "REVOKE GRANT OPTION")) |
| .forEach(this::ParserError); |
| |
| // Bad SQL with revoke option (omit option for) |
| createPrivSQL(grantRevoke, resources, privileges, idents) |
| .stream() |
| .filter(s -> s.contains("REVOKE")) |
| .map(s -> s.replace("REVOKE", "REVOKE GRANT")) |
| .forEach(this::ParserError); |
| } |
| |
| private static List<String> createPrivSQL(String[][] formats, String[] resources, |
| String[] privileges, String[] idents) { |
| List<String> result = new ArrayList<>(); |
| |
| for (String[] formatStr : formats) { |
| for (String resource : resources) { |
| for (String privilege : privileges) { |
| for (String ident : idents) { |
| result.add(String.format("%s %s ON %s %s %s", formatStr[0], privilege, |
| resource, formatStr[1], ident)); |
| } |
| } |
| } |
| } |
| return result; |
| } |
| |
| private static List<String> createColumnPrivSql(String[][] formats, |
| String[] privResource, String[] idents) { |
| List<String> result = new ArrayList<>(); |
| |
| for (String[] formatStr : formats) { |
| for (String pr : privResource) { |
| for (String ident : idents) { |
| result.add(String.format("%s %s %s %s", formatStr[0], pr, formatStr[1], |
| ident)); |
| } |
| } |
| } |
| return result; |
| } |
| |
| @Test |
| public void TestShowRoles() { |
| ParsesOk("SHOW ROLES"); |
| ParsesOk("SHOW CURRENT ROLES"); |
| ParsesOk("SHOW ROLE GRANT GROUP myGroup"); |
| ParserError("SHOW ROLES blah"); |
| ParserError("SHOW ROLE GRANT GROUP"); |
| ParserError("SHOW CURRENT"); |
| ParserError("SHOW ROLE"); |
| ParserError("SHOW"); |
| } |
| |
| @Test |
| public void TestShowGrantPrincipal() { |
| for (String type: new String[]{"ROLE", "USER", "GROUP"}) { |
| // Show all grants on a particular principal type. |
| ParsesOk(String.format("SHOW GRANT %s foo", type)); |
| |
| // Show grants on a specific object |
| ParsesOk(String.format("SHOW GRANT %s foo ON SERVER", type)); |
| ParsesOk(String.format("SHOW GRANT %s foo ON DATABASE foo", type)); |
| ParsesOk(String.format("SHOW GRANT %s foo ON TABLE foo", type)); |
| ParsesOk(String.format("SHOW GRANT %s foo ON TABLE foo.bar", type)); |
| ParsesOk(String.format("SHOW GRANT %s foo ON COLUMN bar.baz", type)); |
| ParsesOk(String.format("SHOW GRANT %s foo ON COLUMN foo.bar.baz", type)); |
| ParsesOk(String.format("SHOW GRANT %s foo ON URI '/abc/123'", type)); |
| |
| ParserError(String.format("SHOW GRANT %s", type)); |
| ParserError(String.format("SHOW GRANT %s foo ON SERVER foo", type)); |
| ParserError(String.format("SHOW GRANT %s foo ON DATABASE", type)); |
| ParserError(String.format("SHOW GRANT %s foo ON TABLE", type)); |
| ParserError(String.format("SHOW GRANT %s foo ON COLUMN", type)); |
| ParserError(String.format("SHOW GRANT %s foo ON URI abc", type)); |
| } |
| ParserError("SHOW GRANT FOO bar"); |
| } |
| |
| @Test |
| public void TestShowCreateFunction() { |
| ParsesOk("SHOW CREATE FUNCTION foo"); |
| ParsesOk("SHOW CREATE FUNCTION foo.bar"); |
| ParsesOk("SHOW CREATE AGGREGATE FUNCTION foo"); |
| ParsesOk("SHOW CREATE AGGREGATE FUNCTION foo.bar"); |
| |
| ParserError("SHOW CREATE FUNCTION"); |
| ParserError("SHOW CREATE AGGREGATE FUNCTION"); |
| ParserError("SHOW CREATE ANALYTIC FUNCTION foo"); |
| ParserError("SHOW CREATE AGGREGATE ANALYTIC FUNCTION foo"); |
| } |
| |
| @Test |
| public void TestComputeStats() { |
| ParsesOk("COMPUTE STATS alltypes"); |
| ParsesOk("COMPUTE STATS functional.alltypes"); |
| ParsesOk("COMPUTE STATS alltypes TABLESAMPLE SYSTEM(10)"); |
| ParsesOk("COMPUTE STATS alltypes TABLESAMPLE SYSTEM(10) REPEATABLE(10)"); |
| ParsesOk("COMPUTE STATS functional.alltypes TABLESAMPLE SYSTEM(10) REPEATABLE(10)"); |
| ParserError("COMPUTE functional.alltypes"); |
| ParserError("COMPUTE STATS ON functional.alltypes"); |
| ParserError("COMPUTE STATS"); |
| } |
| |
| @Test |
| public void TestComputeIncrementalStats() { |
| ParsesOk("COMPUTE INCREMENTAL STATS functional.alltypes"); |
| ParsesOk( |
| "COMPUTE INCREMENTAL STATS functional.alltypes PARTITION(month=10, year=2010)"); |
| ParsesOk( |
| "DROP INCREMENTAL STATS functional.alltypes PARTITION(month=10, year=2010)"); |
| ParserError("COMPUTE INCREMENTAL STATS"); |
| ParserError("COMPUTE INCREMENTAL functional.alltypes"); |
| ParserError("DROP INCREMENTAL STATS functional.alltypes"); |
| ParserError("COMPUTE INCREMENTAL STATS functional.alltypes TABLESAMPLE SYSTEM(10)"); |
| } |
| |
| @Test |
| public void TestSemiColon() { |
| ParserError(";", "Syntax error"); |
| ParsesOk("SELECT 1;"); |
| ParsesOk(" SELECT 1 ; "); |
| ParsesOk(" SELECT 1 ; "); |
| ParserError("SELECT 1; SELECT 2;", |
| "Syntax error in line 1:\n" + |
| "SELECT 1; SELECT 2;\n" + |
| " ^\n" + |
| "Encountered: SELECT\n" + |
| "Expected"); |
| ParsesOk("SELECT 1;;;"); |
| ParsesOk("SELECT 1 FROM functional.alltypestiny WHERE 1 = (SELECT 1);"); |
| ParserError("SELECT 1 FROM functional.alltypestiny WHERE 1 = (SELECT 1;)", |
| "Syntax error"); |
| ParserError("SELECT 1 FROM functional.alltypestiny WHERE 1 = (SELECT 1;);", |
| "Syntax error"); |
| ParsesOk("CREATE TABLE functional.test_table (col INT);"); |
| ParsesOk("DESCRIBE functional.alltypes;"); |
| ParsesOk("SET num_nodes=1;"); |
| } |
| |
| @Test |
| public void TestCommentOn() { |
| ParsesOk("COMMENT ON DATABASE db IS 'comment'"); |
| ParsesOk("COMMENT ON DATABASE db IS ''"); |
| ParsesOk("COMMENT ON DATABASE db IS NULL"); |
| ParserError("COMMENT ON DATABASE IS 'comment'"); |
| ParserError("COMMENT ON DATABASE db IS"); |
| |
| for (String tbl : new String[]{"db.t", "t"}) { |
| ParsesOk(String.format("COMMENT ON TABLE %s IS 'comment'", tbl)); |
| ParsesOk(String.format("COMMENT ON TABLE %s IS ''", tbl)); |
| ParsesOk(String.format("COMMENT ON TABLE %s IS NULL", tbl)); |
| |
| ParsesOk(String.format("COMMENT ON VIEW %s IS 'comment'", tbl)); |
| ParsesOk(String.format("COMMENT ON VIEW %s IS ''", tbl)); |
| ParsesOk(String.format("COMMENT ON VIEW %s IS NULL", tbl)); |
| } |
| ParserError("COMMENT ON TABLE IS 'comment'"); |
| ParserError("COMMENT ON TABLE tbl IS"); |
| |
| ParserError("COMMENT ON VIEW IS 'comment'"); |
| ParserError("COMMENT ON VIEW tbl IS"); |
| |
| for (String col : new String[]{"db.tbl.col", "tbl.col"}) { |
| ParsesOk(String.format("COMMENT ON COLUMN %s IS 'comment'", col)); |
| ParsesOk(String.format("COMMENT ON COLUMN %s IS ''", col)); |
| ParsesOk(String.format("COMMENT ON COLUMN %s IS NULL", col)); |
| } |
| ParserError("COMMENT on col IS 'comment'"); |
| ParserError("COMMENT on db.tbl.col IS"); |
| ParserError("COMMENT on tbl.col IS"); |
| } |
| |
| @Test |
| public void TestAlterDatabaseSetOwner() { |
| for (String valid : new String[]{"foo", "user", "owner"}) { |
| ParsesOk(String.format("ALTER DATABASE %s SET OWNER USER %s", valid, valid)); |
| ParsesOk(String.format("ALTER DATABASE %s SET OWNER ROLE %s", valid, valid)); |
| } |
| |
| for (String invalid : new String[]{"'foo'", "''", "NULL"}) { |
| ParserError(String.format("ALTER DATABASE %s SET OWNER ROLE %s", invalid, invalid)); |
| ParserError(String.format("ALTER DATABASE %s SET OWNER USER %s", invalid, invalid)); |
| } |
| |
| ParserError("ALTER DATABASE db SET ABC USER foo"); |
| ParserError("ALTER DATABASE db SET ABC ROLE foo"); |
| ParserError("ALTER DATABASE db SET OWNER ABC foo"); |
| ParserError("ALTER DATABASE db SET OWNER USER"); |
| ParserError("ALTER DATABASE SET OWNER foo"); |
| ParserError("ALTER DATABASE SET OWNER USER foo"); |
| ParserError("ALTER DATABASE db SET OWNER ROLE"); |
| ParserError("ALTER DATABASE SET OWNER ROLE foo"); |
| ParserError("ALTER DATABASE SET OWNER"); |
| } |
| |
| @Test |
| public void TestAlterTableOrViewSetOwner() { |
| for (String type : new String[]{"TABLE", "VIEW"}) { |
| for (String valid : new String[]{"foo", "user", "owner"}) { |
| ParsesOk(String.format("ALTER %s %s SET OWNER USER %s", type, valid, valid)); |
| ParsesOk(String.format("ALTER %s %s SET OWNER ROLE %s", type, valid, valid)); |
| } |
| |
| for (String invalid : new String[]{"'foo'", "''", "NULL"}) { |
| ParserError(String.format("ALTER %s %s SET OWNER ROLE %s", type, invalid, invalid)); |
| ParserError(String.format("ALTER %s %s SET OWNER USER %s", type, invalid, invalid)); |
| } |
| |
| ParserError(String.format("ALTER %s tbl PARTITION(i=1) SET OWNER ROLE foo", type)); |
| ParserError(String.format("ALTER %s tbl SET ABC USER foo", type)); |
| ParserError(String.format("ALTER %s tbl SET ABC ROLE foo", type)); |
| ParserError(String.format("ALTER %s tbl SET OWNER ABC foo", type)); |
| ParserError(String.format("ALTER %s tbl SET OWNER USER", type)); |
| ParserError(String.format("ALTER %s SET OWNER foo", type)); |
| ParserError(String.format("ALTER %s SET OWNER USER foo", type)); |
| ParserError(String.format("ALTER %s tbl SET OWNER ROLE", type)); |
| ParserError(String.format("ALTER %s SET OWNER ROLE foo", type)); |
| ParserError(String.format("ALTER %s SET OWNER", type)); |
| } |
| } |
| |
| @Test |
| public void TestAdminFns() { |
| // Any combination of whitespace is ok. |
| ParsesOk(":foobar()"); |
| ParsesOk(": foobar()"); |
| ParsesOk(":\tfoobar()"); |
| ParsesOk(" :\tfoobar()"); |
| ParsesOk("\n:foobar()"); |
| ParsesOk("\n:foobar(123)"); |
| ParsesOk("\n:foobar(123, 456)"); |
| ParsesOk("\n:foobar('foo', 'bar')"); |
| ParsesOk("\n:foobar('foo', 'bar', 1, -1, 1234, 99, false)"); |
| |
| // Any identifier is supported. |
| ParsesOk(": 1a()"); |
| |
| // Must be prefixed with colon. |
| ParserError("foobar()"); |
| ParserError(" foobar()"); |
| |
| // Non-identifiers not supported. |
| ParserError(": 1()"); |
| ParserError(": 'string'()"); |
| ParserError(": a.b()"); |
| |
| // Must be single function with parens. Cannot have multiple statements. |
| ParserError(": shutdown"); |
| ParserError(": shutdown foo"); |
| ParserError(": shutdown() other()"); |
| ParserError(": shutdown(); other()"); |
| ParserError(": shutdown(), other()"); |
| ParserError(": shutdown() :other()"); |
| ParserError(": shutdown() :other()"); |
| ParserError(": shutdown('hostA'); :shutdown('hostB');"); |
| } |
| |
| @Test |
| public void TestIdentifier() { |
| ParsesOk("select * from foo.bar_123"); |
| ParsesOk("select * from foo. bar_123"); |
| ParsesOk("select * from foo .bar_123"); |
| ParsesOk("select * from foo . bar_123"); |
| |
| ParsesOk("select * from foo.123_bar"); |
| ParsesOk("select * from foo. 123_bar"); |
| ParsesOk("select * from foo .123_bar"); |
| ParsesOk("select * from foo . 123_bar"); |
| |
| ParsesOk("select * from 123_foo.bar"); |
| ParsesOk("select * from 123_foo. bar"); |
| ParsesOk("select * from 123_foo .bar"); |
| ParsesOk("select * from 123_foo . bar"); |
| |
| ParsesOk("select * from 123_foo.123_bar"); |
| ParsesOk("select * from 123_foo. 123_bar"); |
| ParsesOk("select * from 123_foo .123_bar"); |
| ParsesOk("select * from 123_foo . 123_bar"); |
| |
| // We do not allow identifiers that are ambiguous with exponential. |
| ParserError("select * from foo.4e1"); |
| ParserError("select * from 4e1.bar"); |
| |
| ParserError("select * from .123_bar"); |
| ParserError("select * from . 123_bar"); |
| } |
| |
| @Test |
| public void TestLineBreakEnd() { |
| ParserError("--test\n"); |
| ParserError("--test\n "); |
| ParserError("SELECT\n"); |
| ParserError("SELECT\n "); |
| ParserError("SHOW\n"); |
| ParserError("SHOW\n "); |
| ParserError("INSERT\n"); |
| ParserError("INSERT\n "); |
| ParserError("DROP\n"); |
| ParserError("DROP\n "); |
| ParserError(" \n"); |
| ParserError("\n "); |
| ParserError("\n"); |
| |
| ParserError("SELECT\n\n"); |
| ParserError("SELECT\n\n\n"); |
| ParserError("SELECT\n\n \n"); |
| ParserError("SELECT\n \n\n"); |
| ParserError("SELECT\n\n "); |
| ParserError("SELECT \n\n"); |
| |
| ParsesOk("--test\nSELECT 1\n"); |
| ParsesOk("--test\nSELECT 1\n "); |
| } |
| |
| @Test |
| public void TestCreateBucketedTable() { |
| ParserError("Create table bucketed_tbl(order_id int, order_name string)" |
| + "clustered by (order_id) into 5 buckets", "Syntax error"); |
| } |
| } |