blob: 093df28b5d0fab3816dd9a4d44489feee4f65be2 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package org.apache.impala.analysis;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.impala.catalog.AggregateFunction;
import org.apache.impala.catalog.FeTable;
import org.apache.impala.catalog.FeView;
import org.apache.impala.catalog.Function;
import org.apache.impala.catalog.KuduTable;
import org.apache.impala.catalog.ScalarFunction;
import org.apache.impala.catalog.Type;
import org.apache.impala.common.FrontendTestBase;
import org.apache.impala.thrift.TFunctionBinaryType;
import org.junit.Test;
import com.google.common.collect.Lists;
public class ToSqlUtilsTest extends FrontendTestBase {
@Test
public void testRemoveHiddenProperties() {
Map<String,String> props = new HashMap<>();
for (String kw : ToSqlUtils.HIDDEN_TABLE_PROPERTIES) {
props.put(kw, kw + "-value");
}
props.put("foo", "foo-value");
ToSqlUtils.removeHiddenTableProperties(props);
assertEquals(1, props.size());
assertEquals("foo-value", props.get("foo"));
}
@Test
public void testRemoveHiddenKuduProperties() {
Map<String,String> props = new HashMap<>();
props.put(KuduTable.KEY_TABLE_NAME, "kudu-value");
props.put("foo", "foo-value");
ToSqlUtils.removeHiddenKuduTableProperties(props);
assertEquals(1, props.size());
assertEquals("foo-value", props.get("foo"));
}
@Test
public void testGetSortColumns() {
Map<String,String> props = new HashMap<>();
props.put("foo", "foo-value");
// Returns null if no sort cols property
assertNull(ToSqlUtils.getSortColumns(props));
// Degenerate case
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, "");
List<String> sortCols = ToSqlUtils.getSortColumns(props);
assertTrue(sortCols.isEmpty());
// One column
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, "col1");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(1, sortCols.size());
assertEquals("col1", sortCols.get(0));
// One column with padding
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, " col1 ");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(1, sortCols.size());
assertEquals("col1", sortCols.get(0));
// One column with spaces in name
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, " col 1 ");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(1, sortCols.size());
assertEquals("col 1", sortCols.get(0));
// Spurious commas
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, ",col1,");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(1, sortCols.size());
assertEquals("col1", sortCols.get(0));
// Spurious commas and spaces
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, " , col1 , ");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(1, sortCols.size());
assertEquals("col1", sortCols.get(0));
// Two columns
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, "col1,col2");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(2, sortCols.size());
assertEquals("col1", sortCols.get(0));
assertEquals("col2", sortCols.get(1));
// Two columns with extra commas and spaces
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, " col1 ,, col2 ");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(2, sortCols.size());
assertEquals("col1", sortCols.get(0));
assertEquals("col2", sortCols.get(1));
// Three columns
props.put(AlterTableSortByStmt.TBL_PROP_SORT_COLUMNS, "col1,col2,col3");
sortCols = ToSqlUtils.getSortColumns(props);
assertEquals(3, sortCols.size());
assertEquals("col1", sortCols.get(0));
assertEquals("col2", sortCols.get(1));
assertEquals("col3", sortCols.get(2));
// Note: this method cannot handle a the pathological case of a
// quoted column with a comma in the name: `foo,bar`.
}
@Test
public void testSortOrder() {
Map<String,String> props = new HashMap<>();
props.put("foo", "foo-value");
// Sorting order is LEXICAL by default
assertEquals("LEXICAL", ToSqlUtils.getSortingOrder(props));
props.put(AlterTableSortByStmt.TBL_PROP_SORT_ORDER, "ZORDER");
// Returns true if zorder property set ZORDER
assertEquals("ZORDER", ToSqlUtils.getSortingOrder(props));
props.put(AlterTableSortByStmt.TBL_PROP_SORT_ORDER, "LEXICAL");
// Returns false if zorder property set to LEXICAL
assertEquals("LEXICAL", ToSqlUtils.getSortingOrder(props));
}
private FeTable getTable(String dbName, String tableName) {
FeTable table = catalog_.getOrLoadTable(dbName, tableName);
assertNotNull(table);
return table;
}
private FeView getView(String dbName, String tableName) {
FeTable table = getTable(dbName, tableName);
assertTrue(table instanceof FeView);
return (FeView) table;
}
@Test
public void testHiveNeedsQuotes() {
// Regular old ident
assertFalse(ToSqlUtils.hiveNeedsQuotes("foo"));
// Operator
assertTrue(ToSqlUtils.hiveNeedsQuotes("+"));
// Keyword
assertTrue(ToSqlUtils.hiveNeedsQuotes("SELECT"));
assertTrue(ToSqlUtils.hiveNeedsQuotes("select"));
assertTrue(ToSqlUtils.hiveNeedsQuotes("sElEcT"));
// Two idents
assertTrue(ToSqlUtils.hiveNeedsQuotes("foo bar"));
// Expression
assertTrue(ToSqlUtils.hiveNeedsQuotes("a+b"));
assertFalse(ToSqlUtils.hiveNeedsQuotes("123ab"));
assertTrue(ToSqlUtils.hiveNeedsQuotes("123.a"));
}
@Test
public void testImpalaNeedsQuotes() {
// Regular old ident
assertFalse(ToSqlUtils.impalaNeedsQuotes("foo"));
// Keyword
assertTrue(ToSqlUtils.impalaNeedsQuotes("SELECT"));
assertTrue(ToSqlUtils.impalaNeedsQuotes("select"));
assertTrue(ToSqlUtils.impalaNeedsQuotes("sElEcT"));
// Special case checks for numbers
assertTrue(ToSqlUtils.impalaNeedsQuotes("123"));
assertTrue(ToSqlUtils.impalaNeedsQuotes("123a"));
assertFalse(ToSqlUtils.impalaNeedsQuotes("a123"));
// Note: the Impala check can't detect multi-part
// symbols "a b" nor operators. Rely on the Hive
// version for that.
}
@Test
public void testGetIdentSql() {
// Hive & Impala keyword
assertEquals("`create`", ToSqlUtils.getIdentSql("create"));
// Hive-only keyword
assertEquals("`month`", ToSqlUtils.getIdentSql("month"));
// Impala keyword
assertEquals("`kudu`", ToSqlUtils.getIdentSql("kudu"));
// Number
assertEquals("`123`", ToSqlUtils.getIdentSql("123"));
// Starts with number
assertEquals("`123a`", ToSqlUtils.getIdentSql("123a"));
// Contains spaces
assertEquals("`a b`", ToSqlUtils.getIdentSql("a b"));
// Operator
assertEquals("`+`", ToSqlUtils.getIdentSql("+"));
// Simple identifier
assertEquals("foo", ToSqlUtils.getIdentSql("foo"));
// Comment characters in name
assertEquals("`foo#`", ToSqlUtils.getIdentSql("foo#"));
assertEquals("`foo#bar`", ToSqlUtils.getIdentSql("foo#bar"));
assertEquals("`foo--bar`", ToSqlUtils.getIdentSql("foo--bar"));
List<String> in = Lists.newArrayList("create", "foo");
List<String> out = ToSqlUtils.getIdentSqlList(in);
assertEquals(2, out.size());
assertEquals("`create`", out.get(0));
assertEquals("foo", out.get(1));
assertEquals("`create`.foo", ToSqlUtils.getPathSql(in));
}
@Test
public void tesToIdentSql() {
// Normal quoting
assertEquals("`create`", ToSqlUtils.identSql("create"));
assertEquals("foo", ToSqlUtils.identSql("foo"));
// Wildcard is special in test cases
assertEquals("*", ToSqlUtils.identSql("*"));
// Multi-part names
assertEquals("foo.`create`.*", ToSqlUtils.identSql("foo.create.*"));
}
@Test
public void testCreateViewSql() {
{
FeView view = getView("functional", "view_view");
String sql = ToSqlUtils.getCreateViewSql(view);
String expected =
"CREATE VIEW functional.view_view AS\n" +
"SELECT * FROM functional.alltypes_view";
assertEquals(expected, sql);
}
{
FeView view = getView("functional", "complex_view");
String sql = ToSqlUtils.getCreateViewSql(view);
String expected =
"CREATE VIEW functional.complex_view AS\n" +
"SELECT complex_view.`_c0` abc, complex_view.string_col xyz" +
" FROM (SELECT count(a.bigint_col), b.string_col" +
" FROM functional.alltypesagg a" +
" INNER JOIN functional.alltypestiny b ON a.id = b.id" +
" WHERE a.bigint_col < 50" +
" GROUP BY b.string_col" +
" HAVING count(a.bigint_col) > 1" +
" ORDER BY b.string_col ASC LIMIT 100) complex_view";
assertEquals(expected, sql);
}
}
@Test
public void testScalarFunctionSql() {
{
// Can't generate SQL for an unresolved function
List<Type> args = new ArrayList<>();
Function fn = Function.createFunction("mydb", "fn1",
args, Type.INT, false, TFunctionBinaryType.JAVA);
try {
ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
} catch (UnsupportedOperationException e) {
// Expected
}
}
{
// Java function, leave off location and symbol
List<Type> args = new ArrayList<>();
Function fn = new ScalarFunction(new FunctionName("mydb", "fn1"),
args, Type.INT, false);
fn.setBinaryType(TFunctionBinaryType.JAVA);
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE FUNCTION mydb.fn1\n";
assertEquals(expected, sql);
}
{
// Java function, with location and symbol
List<Type> args = new ArrayList<>();
ScalarFunction fn = new ScalarFunction(new FunctionName("mydb", "fn1"),
args, Type.INT, false);
fn.setBinaryType(TFunctionBinaryType.JAVA);
fn.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.jar"));
fn.setSymbolName("MyClass");
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE FUNCTION mydb.fn1\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.jar'\n" +
" SYMBOL='MyClass'\n";
assertEquals(expected, sql);
}
{
// Java function, with location and symbol
List<Type> args = Lists.newArrayList(Type.VARCHAR, Type.BOOLEAN);
ScalarFunction fn = new ScalarFunction(new FunctionName("mydb", "fn1"),
args, Type.INT, false);
fn.setBinaryType(TFunctionBinaryType.JAVA);
fn.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.jar"));
fn.setSymbolName("MyClass");
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE FUNCTION mydb.fn1\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.jar'\n" +
" SYMBOL='MyClass'\n";
assertEquals(expected, sql);
}
{
// C++ function, with location and symbol
List<Type> args = new ArrayList<>();
ScalarFunction fn = new ScalarFunction(new FunctionName("mydb", "fn1"),
args, Type.INT, false);
fn.setBinaryType(TFunctionBinaryType.NATIVE);
fn.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.so"));
fn.setSymbolName("myClass");
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE FUNCTION mydb.fn1()\n" +
" RETURNS INT\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.so'\n" +
" SYMBOL='myClass'\n";
assertEquals(expected, sql);
}
{
// C++ function, with location and symbol
List<Type> args = Lists.newArrayList(Type.VARCHAR, Type.BOOLEAN);
ScalarFunction fn = new ScalarFunction(new FunctionName("mydb", "fn1"),
args, Type.INT, false);
fn.setBinaryType(TFunctionBinaryType.NATIVE);
fn.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.so"));
fn.setSymbolName("myClass");
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE FUNCTION mydb.fn1(VARCHAR(*), BOOLEAN)\n" +
" RETURNS INT\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.so'\n" +
" SYMBOL='myClass'\n";
assertEquals(expected, sql);
}
}
@Test
public void testAggFnSql() {
{
// C++ aggregate function, with minimum state
List<Type> args = Lists.newArrayList(Type.INT, Type.BOOLEAN);
AggregateFunction fn = new AggregateFunction(new FunctionName("mydb", "fn1"),
args, Type.BIGINT, false);
fn.setBinaryType(TFunctionBinaryType.NATIVE);
fn.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.so"));
fn.setUpdateFnSymbol("Update");
fn.setInitFnSymbol("Init");
fn.setMergeFnSymbol("Merge");
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE AGGREGATE FUNCTION mydb.fn1(INT, BOOLEAN)\n" +
" RETURNS BIGINT\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.so'\n" +
" UPDATE_FN='Update'\n" +
" INIT_FN='Init'\n" +
" MERGE_FN='Merge'\n";
assertEquals(expected, sql);
}
{
// C++ aggregate function, with full state
List<Type> args = Lists.newArrayList(Type.INT, Type.BOOLEAN);
AggregateFunction fn = new AggregateFunction(new FunctionName("mydb", "fn1"),
args, Type.BIGINT, false);
fn.setBinaryType(TFunctionBinaryType.NATIVE);
fn.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.so"));
fn.setUpdateFnSymbol("Update");
fn.setInitFnSymbol("Init");
fn.setMergeFnSymbol("Merge");
fn.setFinalizeFnSymbol("Finalize");
fn.setSerializeFnSymbol("Serialize");
fn.setIntermediateType(Type.INT);
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn));
String expected = "CREATE AGGREGATE FUNCTION mydb.fn1(INT, BOOLEAN)\n" +
" RETURNS BIGINT\n" +
" INTERMEDIATE INT\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.so'\n" +
" UPDATE_FN='Update'\n" +
" INIT_FN='Init'\n" +
" MERGE_FN='Merge'\n" +
" SERIALIZE_FN='Serialize'\n" +
" FINALIZE_FN='Finalize'\n";
assertEquals(expected, sql);
}
}
@Test
public void testCreateFunctionSql() {
{
// Two functions, one C++, one Java
ScalarFunction fn1 = new ScalarFunction(new FunctionName("mydb", "fn1"),
new ArrayList<>(), Type.INT, false);
fn1.setBinaryType(TFunctionBinaryType.JAVA);
fn1.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.jar"));
fn1.setSymbolName("MyClass");
List<Type> args = Lists.newArrayList(Type.VARCHAR, Type.BOOLEAN);
ScalarFunction fn2 = new ScalarFunction(new FunctionName("mydb", "fn2"),
args, Type.INT, false);
fn2.setBinaryType(TFunctionBinaryType.NATIVE);
fn2.setLocation(new HdfsUri("hdfs://foo:123/fns/myfunc.so"));
fn2.setSymbolName("myClass");
String sql = ToSqlUtils.getCreateFunctionSql(Lists.newArrayList(fn1, fn2));
String expected =
"CREATE FUNCTION mydb.fn1\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.jar'\n" +
" SYMBOL='MyClass';\n" +
"CREATE FUNCTION mydb.fn2(VARCHAR(*), BOOLEAN)\n" +
" RETURNS INT\n" +
" LOCATION 'hdfs://foo:123/fns/myfunc.so'\n" +
" SYMBOL='myClass'\n";
assertEquals(expected, sql);
}
}
}