| /* |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to you under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.apache.calcite.test; |
| |
| import org.apache.calcite.adapter.enumerable.CallImplementor; |
| import org.apache.calcite.adapter.java.ReflectiveSchema; |
| import org.apache.calcite.jdbc.CalciteConnection; |
| import org.apache.calcite.linq4j.Ord; |
| import org.apache.calcite.linq4j.function.SemiStrict; |
| import org.apache.calcite.linq4j.tree.Expressions; |
| import org.apache.calcite.linq4j.tree.Types; |
| import org.apache.calcite.rel.type.RelDataType; |
| import org.apache.calcite.rel.type.RelDataTypeFactory; |
| import org.apache.calcite.rel.type.RelProtoDataType; |
| import org.apache.calcite.schema.FunctionParameter; |
| import org.apache.calcite.schema.ImplementableFunction; |
| import org.apache.calcite.schema.ScalarFunction; |
| import org.apache.calcite.schema.SchemaPlus; |
| import org.apache.calcite.schema.impl.AbstractSchema; |
| import org.apache.calcite.schema.impl.ScalarFunctionImpl; |
| import org.apache.calcite.schema.impl.ViewTable; |
| import org.apache.calcite.sql.type.SqlTypeName; |
| import org.apache.calcite.test.schemata.hr.HrSchema; |
| import org.apache.calcite.util.Smalls; |
| |
| import com.google.common.collect.ImmutableList; |
| |
| import org.junit.jupiter.api.Disabled; |
| import org.junit.jupiter.api.Test; |
| |
| import java.lang.reflect.Method; |
| import java.sql.Connection; |
| import java.sql.DriverManager; |
| import java.sql.ResultSet; |
| import java.sql.Statement; |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.concurrent.atomic.AtomicInteger; |
| |
| import static org.hamcrest.CoreMatchers.is; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| |
| /** |
| * Tests for user-defined functions; |
| * includes user-defined aggregate functions |
| * but user-defined table functions are in {@link TableFunctionTest}. |
| * |
| * @see Smalls |
| */ |
| class UdfTest { |
| private CalciteAssert.AssertThat withUdf() { |
| final String model = "{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " tables: [\n" |
| + " {\n" |
| + " name: 'EMPLOYEES',\n" |
| + " type: 'custom',\n" |
| + " factory: '" |
| + JdbcTest.EmpDeptTableFactory.class.getName() |
| + "',\n" |
| + " operand: {'foo': true, 'bar': 345}\n" |
| + " }\n" |
| + " ],\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_PLUS',\n" |
| + " className: '" |
| + Smalls.MyPlusFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_DET_PLUS',\n" |
| + " className: '" |
| + Smalls.MyDeterministicPlusFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_LEFT',\n" |
| + " className: '" |
| + Smalls.MyLeftFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'ABCDE',\n" |
| + " className: '" |
| + Smalls.MyAbcdeFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_STR',\n" |
| + " className: '" |
| + Smalls.MyToStringFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_DOUBLE',\n" |
| + " className: '" |
| + Smalls.MyDoubleFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_EXCEPTION',\n" |
| + " className: '" |
| + Smalls.MyExceptionFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'COUNT_ARGS',\n" |
| + " className: '" |
| + Smalls.CountArgs0Function.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'COUNT_ARGS',\n" |
| + " className: '" |
| + Smalls.CountArgs1Function.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'COUNT_ARGS',\n" |
| + " className: '" |
| + Smalls.CountArgs1NullableFunction.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'COUNT_ARGS',\n" |
| + " className: '" |
| + Smalls.CountArgs2Function.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_ABS',\n" |
| + " className: '" |
| + java.lang.Math.class.getName() |
| + "',\n" |
| + " methodName: 'abs'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'NULL4',\n" |
| + " className: '" |
| + Smalls.Null4Function.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'NULL8',\n" |
| + " className: '" |
| + Smalls.Null8Function.class.getName() |
| + "'\n" |
| + " },\n" |
| + " {\n" |
| + " className: '" |
| + Smalls.MultipleFunction.class.getName() |
| + "',\n" |
| + " methodName: '*'\n" |
| + " },\n" |
| + " {\n" |
| + " className: '" |
| + Smalls.AllTypesFunction.class.getName() |
| + "',\n" |
| + " methodName: '*'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}"; |
| return CalciteAssert.model(model); |
| } |
| |
| /** Tests a user-defined function that is defined in terms of a class with |
| * non-static methods. */ |
| @Disabled("[CALCITE-1561] Intermittent test failures") |
| @Test void testUserDefinedFunction() { |
| final String sql = "select \"adhoc\".my_plus(\"deptno\", 100) as p\n" |
| + "from \"adhoc\".EMPLOYEES"; |
| final AtomicInteger c = Smalls.MyPlusFunction.INSTANCE_COUNT.get(); |
| final int before = c.get(); |
| withUdf().query(sql).returnsUnordered("P=110", |
| "P=120", |
| "P=110", |
| "P=110"); |
| final int after = c.get(); |
| assertThat(after, is(before + 4)); |
| } |
| |
| /** As {@link #testUserDefinedFunction()}, but checks that the class is |
| * instantiated exactly once, per |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1548">[CALCITE-1548] |
| * Instantiate function objects once per query</a>. */ |
| @Test void testUserDefinedFunctionInstanceCount() { |
| final String sql = "select \"adhoc\".my_det_plus(\"deptno\", 100) as p\n" |
| + "from \"adhoc\".EMPLOYEES"; |
| final AtomicInteger c = Smalls.MyDeterministicPlusFunction.INSTANCE_COUNT.get(); |
| final int before = c.get(); |
| withUdf().query(sql).returnsUnordered("P=110", |
| "P=120", |
| "P=110", |
| "P=110"); |
| final int after = c.get(); |
| assertThat(after, is(before + 1)); |
| } |
| |
| @Test void testUserDefinedFunctionB() { |
| final String sql = "select \"adhoc\".my_double(\"deptno\") as p\n" |
| + "from \"adhoc\".EMPLOYEES"; |
| final String expected = "P=20\n" |
| + "P=40\n" |
| + "P=20\n" |
| + "P=20\n"; |
| withUdf().query(sql).returns(expected); |
| } |
| |
| @Test void testUserDefinedFunctionWithNull() { |
| final String sql = "select \"adhoc\".my_det_plus(\"deptno\", 1 + null) as p\n" |
| + "from \"adhoc\".EMPLOYEES where 1 > 0 or nullif(null, 1) is null"; |
| final AtomicInteger c = Smalls.MyDeterministicPlusFunction.INSTANCE_COUNT.get(); |
| final int before = c.get(); |
| withUdf() |
| .query(sql) |
| .returnsUnordered("P=null", |
| "P=null", |
| "P=null", |
| "P=null"); |
| final int after = c.get(); |
| assertThat(after, is(before + 1)); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-3195">[CALCITE-3195] |
| * Handle a UDF that throws checked exceptions in the Enumerable code generator</a>. */ |
| @Test void testUserDefinedFunctionWithException() { |
| final String sql1 = "select \"adhoc\".my_exception(\"deptno\") as p\n" |
| + "from \"adhoc\".EMPLOYEES"; |
| final String expected1 = "P=20\n" |
| + "P=30\n" |
| + "P=20\n" |
| + "P=20\n"; |
| withUdf().query(sql1).returns(expected1); |
| |
| final String sql2 = "select cast(\"adhoc\".my_exception(\"deptno\") as double) as p\n" |
| + "from \"adhoc\".EMPLOYEES"; |
| final String expected2 = "P=20.0\n" |
| + "P=30.0\n" |
| + "P=20.0\n" |
| + "P=20.0\n"; |
| withUdf().query(sql2).returns(expected2); |
| |
| final String sql3 = "select \"adhoc\".my_exception(\"deptno\" * 2 + 11) as p\n" |
| + "from \"adhoc\".EMPLOYEES"; |
| final String expected3 = "P=41\n" |
| + "P=61\n" |
| + "P=41\n" |
| + "P=41\n"; |
| withUdf().query(sql3).returns(expected3); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-937">[CALCITE-937] |
| * User-defined function within view</a>. */ |
| @Test void testUserDefinedFunctionInView() throws Exception { |
| Class.forName("org.apache.calcite.jdbc.Driver"); |
| Connection connection = DriverManager.getConnection("jdbc:calcite:"); |
| CalciteConnection calciteConnection = |
| connection.unwrap(CalciteConnection.class); |
| SchemaPlus rootSchema = calciteConnection.getRootSchema(); |
| rootSchema.add("hr", new ReflectiveSchema(new HrSchema())); |
| |
| SchemaPlus post = rootSchema.add("POST", new AbstractSchema()); |
| post.add("MY_INCREMENT", |
| ScalarFunctionImpl.create(Smalls.MyIncrement.class, "eval")); |
| |
| final String viewSql = "select \"empid\" as EMPLOYEE_ID,\n" |
| + " \"name\" || ' ' || \"name\" as EMPLOYEE_NAME,\n" |
| + " \"salary\" as EMPLOYEE_SALARY,\n" |
| + " POST.MY_INCREMENT(\"empid\", 10) as INCREMENTED_SALARY\n" |
| + "from \"hr\".\"emps\""; |
| post.add("V_EMP", |
| ViewTable.viewMacro(post, viewSql, ImmutableList.of(), |
| ImmutableList.of("POST", "V_EMP"), null)); |
| |
| final String result = "" |
| + "EMPLOYEE_ID=100; EMPLOYEE_NAME=Bill Bill;" |
| + " EMPLOYEE_SALARY=10000.0; INCREMENTED_SALARY=110.0\n" |
| + "EMPLOYEE_ID=200; EMPLOYEE_NAME=Eric Eric;" |
| + " EMPLOYEE_SALARY=8000.0; INCREMENTED_SALARY=220.0\n" |
| + "EMPLOYEE_ID=150; EMPLOYEE_NAME=Sebastian Sebastian;" |
| + " EMPLOYEE_SALARY=7000.0; INCREMENTED_SALARY=165.0\n" |
| + "EMPLOYEE_ID=110; EMPLOYEE_NAME=Theodore Theodore;" |
| + " EMPLOYEE_SALARY=11500.0; INCREMENTED_SALARY=121.0\n"; |
| |
| Statement statement = connection.createStatement(); |
| ResultSet resultSet = statement.executeQuery(viewSql); |
| assertThat(CalciteAssert.toString(resultSet), is(result)); |
| resultSet.close(); |
| |
| ResultSet viewResultSet = |
| statement.executeQuery("select * from \"POST\".\"V_EMP\""); |
| assertThat(CalciteAssert.toString(viewResultSet), is(result)); |
| statement.close(); |
| connection.close(); |
| } |
| |
| /** |
| * Tests that IS NULL/IS NOT NULL is properly implemented for non-strict |
| * functions. |
| */ |
| @Test void testNotNullImplementor() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query( |
| "select upper(\"adhoc\".my_str(\"name\")) as p from \"adhoc\".EMPLOYEES") |
| .returns("P=<BILL>\n" |
| + "P=<ERIC>\n" |
| + "P=<SEBASTIAN>\n" |
| + "P=<THEODORE>\n"); |
| with.query("select \"name\" as p from \"adhoc\".EMPLOYEES\n" |
| + "where \"adhoc\".my_str(\"name\") is not null") |
| .returns("P=Bill\n" |
| + "P=Eric\n" |
| + "P=Sebastian\n" |
| + "P=Theodore\n"); |
| with.query("select \"name\" as p from \"adhoc\".EMPLOYEES\n" |
| + "where \"adhoc\".my_str(upper(\"name\")) is not null") |
| .returns("P=Bill\n" |
| + "P=Eric\n" |
| + "P=Sebastian\n" |
| + "P=Theodore\n"); |
| with.query("select \"name\" as p from \"adhoc\".EMPLOYEES\n" |
| + "where upper(\"adhoc\".my_str(\"name\")) is not null") |
| .returns("P=Bill\n" |
| + "P=Eric\n" |
| + "P=Sebastian\n" |
| + "P=Theodore\n"); |
| with.query("select \"name\" as p from \"adhoc\".EMPLOYEES\n" |
| + "where \"adhoc\".my_str(\"name\") is null") |
| .returns(""); |
| with.query("select \"name\" as p from \"adhoc\".EMPLOYEES\n" |
| + "where \"adhoc\".my_str(upper(\"adhoc\".my_str(\"name\")" |
| + ")) ='8'") |
| .returns(""); |
| } |
| |
| /** Tests that we generate the appropriate checks for a "semi-strict" |
| * function. |
| * |
| * <p>The difference between "strict" and "semi-strict" functions is that a |
| * "semi-strict" function might return null even if none of its arguments |
| * are null. (Both always return null if one of their arguments is null.) |
| * Thus, a nasty function is more unpredictable. |
| * |
| * @see SemiStrict */ |
| @Test void testSemiStrict() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| final String sql = "select\n" |
| + " \"adhoc\".null4(upper(\"name\")) as p\n" |
| + " from \"adhoc\".EMPLOYEES"; |
| with.query(sql) |
| .returnsUnordered("P=null", |
| "P=null", |
| "P=SEBASTIAN", |
| "P=THEODORE"); |
| // my_str is non-strict; it must be called when args are null |
| final String sql2 = "select\n" |
| + " \"adhoc\".my_str(upper(\"adhoc\".null4(\"name\"))) as p\n" |
| + " from \"adhoc\".EMPLOYEES"; |
| with.query(sql2) |
| .returnsUnordered("P=<null>", |
| "P=<null>", |
| "P=<SEBASTIAN>", |
| "P=<THEODORE>"); |
| // null8 throws NPE if its argument is null, |
| // so we had better know that null4 might return null |
| final String sql3 = "select\n" |
| + " \"adhoc\".null8(\"adhoc\".null4(\"name\")) as p\n" |
| + " from \"adhoc\".EMPLOYEES"; |
| with.query(sql3) |
| .returnsUnordered("P=null", |
| "P=null", |
| "P=Sebastian", |
| "P=null"); |
| } |
| |
| /** Tests derived return type of user-defined function. */ |
| @Test void testUdfDerivedReturnType() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query( |
| "select max(\"adhoc\".my_double(\"deptno\")) as p from \"adhoc\".EMPLOYEES") |
| .returns("P=40\n"); |
| with.query("select max(\"adhoc\".my_str(\"name\")) as p\n" |
| + "from \"adhoc\".EMPLOYEES\n" |
| + "where \"adhoc\".my_str(\"name\") is null") |
| .returns("P=null\n"); |
| } |
| |
| /** Tests a user-defined function that has multiple overloads. */ |
| @Test void testUdfOverloaded() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values (\"adhoc\".count_args(),\n" |
| + " \"adhoc\".count_args(0),\n" |
| + " \"adhoc\".count_args(0, 0))") |
| .returns("EXPR$0=0; EXPR$1=1; EXPR$2=2\n"); |
| with.query("select max(\"adhoc\".count_args()) as p0,\n" |
| + " min(\"adhoc\".count_args(0)) as p1,\n" |
| + " max(\"adhoc\".count_args(0, 0)) as p2\n" |
| + "from \"adhoc\".EMPLOYEES limit 1") |
| .returns("P0=0; P1=1; P2=2\n"); |
| } |
| |
| @Test void testUdfOverloadedNullable() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values (\"adhoc\".count_args(),\n" |
| + " \"adhoc\".count_args(cast(null as smallint)),\n" |
| + " \"adhoc\".count_args(0, 0))") |
| .returns("EXPR$0=0; EXPR$1=-1; EXPR$2=2\n"); |
| } |
| |
| /** Tests passing parameters to user-defined function by name. */ |
| @Test void testUdfArgumentName() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| // arguments in physical order |
| with.query("values (\"adhoc\".my_left(\"s\" => 'hello', \"n\" => 3))") |
| .returns("EXPR$0=hel\n"); |
| // arguments in reverse order |
| with.query("values (\"adhoc\".my_left(\"n\" => 3, \"s\" => 'hello'))") |
| .returns("EXPR$0=hel\n"); |
| with.query("values (\"adhoc\".my_left(\"n\" => 1 + 2, \"s\" => 'hello'))") |
| .returns("EXPR$0=hel\n"); |
| // duplicate argument names |
| with.query("values (\"adhoc\".my_left(\"n\" => 3, \"n\" => 2, \"s\" => 'hello'))") |
| .throws_("Duplicate argument name 'n'"); |
| // invalid argument names |
| with.query("values (\"adhoc\".my_left(\"n\" => 3, \"m\" => 2, \"s\" => 'h'))") |
| .throws_("No match found for function signature " |
| + "MY_LEFT(n => <NUMERIC>, m => <NUMERIC>, s => <CHARACTER>)"); |
| // missing arguments |
| with.query("values (\"adhoc\".my_left(\"n\" => 3))") |
| .throws_("No match found for function signature MY_LEFT(n => <NUMERIC>)"); |
| with.query("values (\"adhoc\".my_left(\"s\" => 'hello'))") |
| .throws_("No match found for function signature MY_LEFT(s => <CHARACTER>)"); |
| // arguments of wrong type, will do implicitly type coercion. |
| with.query("values (\"adhoc\".my_left(\"n\" => 'hello', \"s\" => 'x'))") |
| .throws_("java.lang.NumberFormatException: For input string: \"hello\""); |
| with.query("values (\"adhoc\".my_left(\"n\" => '1', \"s\" => 'x'))") |
| .returns("EXPR$0=x\n"); |
| with.query("values (\"adhoc\".my_left(\"n\" => 1, \"s\" => 0))") |
| .returns("EXPR$0=0\n"); |
| } |
| |
| /** Tests calling a user-defined function some of whose parameters are |
| * optional. */ |
| @Test void testUdfArgumentOptional() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values (\"adhoc\".abcde(a=>1,b=>2,c=>3,d=>4,e=>5))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: 4, e: 5}\n"); |
| with.query("values (\"adhoc\".abcde(1,2,3,4,CAST(NULL AS INTEGER)))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: 4, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(a=>1,b=>2,c=>3,d=>4))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: 4, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(a=>1,b=>2,c=>3))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: null, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(a=>1,e=>5,c=>3))") |
| .returns("EXPR$0={a: 1, b: null, c: 3, d: null, e: 5}\n"); |
| with.query("values (\"adhoc\".abcde(1,2,3))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: null, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(1,2,3,4))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: 4, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(1,2,3,4,5))") |
| .returns("EXPR$0={a: 1, b: 2, c: 3, d: 4, e: 5}\n"); |
| with.query("values (\"adhoc\".abcde(1,2))") |
| .throws_("No match found for function signature ABCDE(<NUMERIC>, <NUMERIC>)"); |
| with.query("values (\"adhoc\".abcde(1,DEFAULT,3))") |
| .returns("EXPR$0={a: 1, b: null, c: 3, d: null, e: null}\n"); |
| // implicit type coercion. |
| with.query("values (\"adhoc\".abcde(1,DEFAULT,'abcde'))") |
| .throws_("java.lang.NumberFormatException: For input string: \"abcde\""); |
| with.query("values (\"adhoc\".abcde(1,DEFAULT,'123'))") |
| .returns("EXPR$0={a: 1, b: null, c: 123, d: null, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(true))") |
| .throws_("No match found for function signature ABCDE(<BOOLEAN>)"); |
| with.query("values (\"adhoc\".abcde(true,DEFAULT))") |
| .throws_("No match found for function signature ABCDE(<BOOLEAN>, <ANY>)"); |
| with.query("values (\"adhoc\".abcde(1,DEFAULT,3,DEFAULT))") |
| .returns("EXPR$0={a: 1, b: null, c: 3, d: null, e: null}\n"); |
| with.query("values (\"adhoc\".abcde(1,2,DEFAULT))") |
| .throws_("DEFAULT is only allowed for optional parameters"); |
| with.query("values (\"adhoc\".abcde(a=>1,b=>2,c=>DEFAULT))") |
| .throws_("DEFAULT is only allowed for optional parameters"); |
| with.query("values (\"adhoc\".abcde(a=>1,b=>DEFAULT,c=>3))") |
| .returns("EXPR$0={a: 1, b: null, c: 3, d: null, e: null}\n"); |
| } |
| |
| /** Test for |
| * {@link org.apache.calcite.runtime.CalciteResource#requireDefaultConstructor(String)}. */ |
| @Test void testUserDefinedFunction2() { |
| String message = "Declaring class " |
| + "'org.apache.calcite.util.Smalls$AwkwardFunction' of non-static " |
| + "user-defined function must have a public constructor with zero " |
| + "parameters"; |
| withBadUdf(Smalls.AwkwardFunction.class).connectThrows(message); |
| } |
| |
| /** Tests user-defined function, with multiple methods per class. */ |
| @Test void testUserDefinedFunctionWithMethodName() { |
| // java.lang.Math has abs(int) and abs(double). |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values abs(-4)").returnsValue("4"); |
| with.query("values abs(-4.5)").returnsValue("4.5"); |
| |
| // 3 overloads of "fun1", another method "fun2", but method "nonStatic" |
| // cannot be used as a function |
| with.query("values \"adhoc\".\"fun1\"(2)").returnsValue("4"); |
| with.query("values \"adhoc\".\"fun1\"(2, 3)").returnsValue("5"); |
| with.query("values \"adhoc\".\"fun1\"('Foo Bar')").returnsValue("foo bar"); |
| with.query("values \"adhoc\".\"fun2\"(10)").returnsValue("30"); |
| with.query("values \"adhoc\".\"nonStatic\"(2)") |
| .throws_("No match found for function signature nonStatic(<NUMERIC>)"); |
| } |
| |
| /** Tests user-defined aggregate function. */ |
| @Test void testUserDefinedAggregateFunction() { |
| final String empDept = JdbcTest.EmpDeptTableFactory.class.getName(); |
| final String sum = Smalls.MyStaticSumFunction.class.getName(); |
| final String sum2 = Smalls.MySumFunction.class.getName(); |
| final CalciteAssert.AssertThat with = CalciteAssert.model("{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " tables: [\n" |
| + " {\n" |
| + " name: 'EMPLOYEES',\n" |
| + " type: 'custom',\n" |
| + " factory: '" + empDept + "',\n" |
| + " operand: {'foo': true, 'bar': 345}\n" |
| + " }\n" |
| + " ],\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_SUM',\n" |
| + " className: '" + sum + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_SUM2',\n" |
| + " className: '" + sum2 + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}") |
| .withDefaultSchema("adhoc"); |
| with.withDefaultSchema(null) |
| .query( |
| "select \"adhoc\".my_sum(\"deptno\") as p from \"adhoc\".EMPLOYEES\n") |
| .returns("P=50\n"); |
| with.query("select my_sum(\"empid\"), \"deptno\" as p from EMPLOYEES\n") |
| .throws_( |
| "Expression 'deptno' is not being grouped"); |
| with.query("select my_sum(\"deptno\") as p from EMPLOYEES\n") |
| .returns("P=50\n"); |
| // implicit type coercion. |
| with.query("select my_sum(\"name\") as p from EMPLOYEES\n") |
| .throws_("java.lang.NumberFormatException: For input string: \"Bill\""); |
| with.query("select my_sum(\"deptno\", 1) as p from EMPLOYEES\n") |
| .throws_( |
| "No match found for function signature MY_SUM(<NUMERIC>, <NUMERIC>)"); |
| with.query("select my_sum() as p from EMPLOYEES\n") |
| .throws_( |
| "No match found for function signature MY_SUM()"); |
| with.query("select \"deptno\", my_sum(\"deptno\") as p from EMPLOYEES\n" |
| + "group by \"deptno\"") |
| .returnsUnordered( |
| "deptno=20; P=20", |
| "deptno=10; P=30"); |
| with.query("select \"deptno\", my_sum2(\"deptno\") as p from EMPLOYEES\n" |
| + "group by \"deptno\"") |
| .returnsUnordered("deptno=20; P=20", "deptno=10; P=30"); |
| } |
| |
| /** Tests user-defined aggregate function. */ |
| @Test void testUserDefinedAggregateFunctionWithMultipleParameters() { |
| final String empDept = JdbcTest.EmpDeptTableFactory.class.getName(); |
| final String sum21 = Smalls.MyTwoParamsSumFunctionFilter1.class.getName(); |
| final String sum22 = Smalls.MyTwoParamsSumFunctionFilter2.class.getName(); |
| final String sum31 = Smalls.MyThreeParamsSumFunctionWithFilter1.class.getName(); |
| final String sum32 = Smalls.MyThreeParamsSumFunctionWithFilter2.class.getName(); |
| final CalciteAssert.AssertThat with = CalciteAssert.model("{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " tables: [\n" |
| + " {\n" |
| + " name: 'EMPLOYEES',\n" |
| + " type: 'custom',\n" |
| + " factory: '" + empDept + "',\n" |
| + " operand: {'foo': true, 'bar': 345}\n" |
| + " }\n" |
| + " ],\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_SUM2',\n" |
| + " className: '" + sum21 + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_SUM2',\n" |
| + " className: '" + sum22 + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_SUM3',\n" |
| + " className: '" + sum31 + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_SUM3',\n" |
| + " className: '" + sum32 + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}") |
| .withDefaultSchema("adhoc"); |
| with.withDefaultSchema(null) |
| .query("select \"adhoc\".my_sum3(\"deptno\",\"name\",'Eric') as p\n" |
| + "from \"adhoc\".EMPLOYEES\n") |
| .returns("P=20\n"); |
| with.query("select \"adhoc\".my_sum3(\"empid\",\"deptno\",\"commission\") as p " |
| + "from \"adhoc\".EMPLOYEES\n") |
| .returns("P=330\n"); |
| with.query("select \"adhoc\".my_sum3(\"empid\",\"deptno\",\"commission\"),\n" |
| + " \"name\"\n" |
| + "from \"adhoc\".EMPLOYEES\n") |
| .throws_("Expression 'name' is not being grouped"); |
| with.query("select \"name\",\n" |
| + " \"adhoc\".my_sum3(\"empid\",\"deptno\",\"commission\") as p\n" |
| + "from \"adhoc\".EMPLOYEES\n" |
| + "group by \"name\"") |
| .returnsUnordered("name=Theodore; P=0", |
| "name=Eric; P=220", |
| "name=Bill; P=110", |
| "name=Sebastian; P=0"); |
| // implicit type coercion. |
| with.query("select \"adhoc\".my_sum3(\"empid\",\"deptno\",\"salary\") as p\n" |
| + "from \"adhoc\".EMPLOYEES\n"); |
| with.query("select \"adhoc\".my_sum3(\"empid\",\"deptno\",\"name\") as p\n" |
| + "from \"adhoc\".EMPLOYEES\n"); |
| with.query("select \"adhoc\".my_sum2(\"commission\",250) as p\n" |
| + "from \"adhoc\".EMPLOYEES\n") |
| .returns("P=1500\n"); |
| // implicit type coercion. |
| with.query("select \"adhoc\".my_sum2(\"name\",250) as p\n" |
| + "from \"adhoc\".EMPLOYEES\n") |
| .throws_("java.lang.NumberFormatException: For input string: \"Bill\""); |
| // implicit type coercion. |
| with.query("select \"adhoc\".my_sum2(\"empid\",0.0) as p\n" |
| + "from \"adhoc\".EMPLOYEES\n") |
| .returns("P=560\n"); |
| } |
| |
| /** Test for |
| * {@link org.apache.calcite.runtime.CalciteResource#firstParameterOfAdd(String)}. */ |
| @Test void testUserDefinedAggregateFunction3() { |
| String message = "Caused by: java.lang.RuntimeException: In user-defined " |
| + "aggregate class 'org.apache.calcite.util.Smalls$SumFunctionBadIAdd'" |
| + ", first parameter to 'add' method must be the accumulator (the " |
| + "return type of the 'init' method)"; |
| withBadUdf(Smalls.SumFunctionBadIAdd.class).connectThrows(message); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1434">[CALCITE-1434] |
| * AggregateFunctionImpl doesnt work if the class implements a generic |
| * interface</a>. */ |
| @Test void testUserDefinedAggregateFunctionImplementsInterface() { |
| final String empDept = JdbcTest.EmpDeptTableFactory.class.getName(); |
| final String mySum3 = Smalls.MySum3.class.getName(); |
| final String model = "{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " tables: [\n" |
| + " {\n" |
| + " name: 'EMPLOYEES',\n" |
| + " type: 'custom',\n" |
| + " factory: '" + empDept + "',\n" |
| + " operand: {'foo': true, 'bar': 345}\n" |
| + " }\n" |
| + " ],\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_SUM3',\n" |
| + " className: '" + mySum3 + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}"; |
| final CalciteAssert.AssertThat with = CalciteAssert.model(model) |
| .withDefaultSchema("adhoc"); |
| |
| with.query("select my_sum3(\"deptno\") as p from EMPLOYEES\n") |
| .returns("P=50\n"); |
| with.withDefaultSchema(null) |
| .query("select \"adhoc\".my_sum3(\"deptno\") as p\n" |
| + "from \"adhoc\".EMPLOYEES\n") |
| .returns("P=50\n"); |
| with.query("select my_sum3(\"empid\"), \"deptno\" as p from EMPLOYEES\n") |
| .throws_("Expression 'deptno' is not being grouped"); |
| with.query("select my_sum3(\"deptno\") as p from EMPLOYEES\n") |
| .returns("P=50\n"); |
| // implicit type coercion. |
| with.query("select my_sum3(\"name\") as p from EMPLOYEES\n") |
| .throws_("java.lang.NumberFormatException: For input string: \"Bill\""); |
| with.query("select my_sum3(\"deptno\", 1) as p from EMPLOYEES\n") |
| .throws_("No match found for function signature " |
| + "MY_SUM3(<NUMERIC>, <NUMERIC>)"); |
| with.query("select my_sum3() as p from EMPLOYEES\n") |
| .throws_("No match found for function signature MY_SUM3()"); |
| with.query("select \"deptno\", my_sum3(\"deptno\") as p from EMPLOYEES\n" |
| + "group by \"deptno\"") |
| .returnsUnordered("deptno=20; P=20", |
| "deptno=10; P=30"); |
| } |
| |
| private static CalciteAssert.AssertThat withBadUdf(Class<?> clazz) { |
| final String empDept = JdbcTest.EmpDeptTableFactory.class.getName(); |
| final String className = clazz.getName(); |
| return CalciteAssert.model("{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " tables: [\n" |
| + " {\n" |
| + " name: 'EMPLOYEES',\n" |
| + " type: 'custom',\n" |
| + " factory: '" + empDept + "',\n" |
| + " operand: {'foo': true, 'bar': 345}\n" |
| + " }\n" |
| + " ],\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'AWKWARD',\n" |
| + " className: '" + className + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}") |
| .withDefaultSchema("adhoc"); |
| } |
| |
| /** Tests user-defined aggregate function with FILTER. |
| * |
| * <p>Also tests that we do not try to push ADAF to JDBC source. */ |
| @Test void testUserDefinedAggregateFunctionWithFilter() { |
| final String sum = Smalls.MyStaticSumFunction.class.getName(); |
| final String sum2 = Smalls.MySumFunction.class.getName(); |
| final CalciteAssert.AssertThat with = CalciteAssert.model("{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + JdbcTest.SCOTT_SCHEMA |
| + ",\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_SUM',\n" |
| + " className: '" + sum + "'\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'MY_SUM2',\n" |
| + " className: '" + sum2 + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}") |
| .withDefaultSchema("adhoc"); |
| with.query("select deptno, \"adhoc\".my_sum(deptno) as p\n" |
| + "from scott.emp\n" |
| + "group by deptno\n") |
| .returns( |
| "DEPTNO=20; P=100\n" |
| + "DEPTNO=10; P=30\n" |
| + "DEPTNO=30; P=180\n"); |
| |
| with.query("select deptno,\n" |
| + " \"adhoc\".my_sum(deptno) filter (where job = 'CLERK') as c,\n" |
| + " \"adhoc\".my_sum(deptno) filter (where job = 'XXX') as x\n" |
| + "from scott.emp\n" |
| + "group by deptno\n") |
| .returns( |
| "DEPTNO=20; C=40; X=0\n" |
| + "DEPTNO=10; C=10; X=0\n" |
| + "DEPTNO=30; C=30; X=0\n"); |
| } |
| |
| /** Tests resolution of functions using schema paths. */ |
| @Test void testPath() { |
| final String name = Smalls.MyPlusFunction.class.getName(); |
| final CalciteAssert.AssertThat with = CalciteAssert.model("{\n" |
| + " version: '1.0',\n" |
| + " schemas: [\n" |
| + " {\n" |
| + " name: 'adhoc',\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_PLUS',\n" |
| + " className: '" + name + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'adhoc2',\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_PLUS2',\n" |
| + " className: '" + name + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " },\n" |
| + " {\n" |
| + " name: 'adhoc3',\n" |
| + " path: ['adhoc2','adhoc3'],\n" |
| + " functions: [\n" |
| + " {\n" |
| + " name: 'MY_PLUS3',\n" |
| + " className: '" + name + "'\n" |
| + " }\n" |
| + " ]\n" |
| + " }\n" |
| + " ]\n" |
| + "}"); |
| |
| final String err = "No match found for function signature"; |
| final String res = "EXPR$0=2\n"; |
| |
| // adhoc can see own function MY_PLUS but not adhoc2.MY_PLUS2 unless |
| // qualified |
| final CalciteAssert.AssertThat adhoc = with.withDefaultSchema("adhoc"); |
| adhoc.query("values MY_PLUS(1, 1)").returns(res); |
| adhoc.query("values MY_PLUS2(1, 1)").throws_(err); |
| adhoc.query("values \"adhoc2\".MY_PLUS(1, 1)").throws_(err); |
| adhoc.query("values \"adhoc2\".MY_PLUS2(1, 1)").returns(res); |
| |
| // adhoc2 can see own function MY_PLUS2 but not adhoc2.MY_PLUS unless |
| // qualified |
| final CalciteAssert.AssertThat adhoc2 = with.withDefaultSchema("adhoc2"); |
| adhoc2.query("values MY_PLUS2(1, 1)").returns(res); |
| adhoc2.query("values MY_PLUS(1, 1)").throws_(err); |
| adhoc2.query("values \"adhoc\".MY_PLUS(1, 1)").returns(res); |
| |
| // adhoc3 can see own adhoc2.MY_PLUS2 because in path, with or without |
| // qualification, but can only see adhoc.MY_PLUS with qualification |
| final CalciteAssert.AssertThat adhoc3 = with.withDefaultSchema("adhoc3"); |
| adhoc3.query("values MY_PLUS2(1, 1)").returns(res); |
| adhoc3.query("values MY_PLUS(1, 1)").throws_(err); |
| adhoc3.query("values \"adhoc\".MY_PLUS(1, 1)").returns(res); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-986">[CALCITE-986] |
| * User-defined function with Date or Timestamp parameters</a>. */ |
| @Test void testDate() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values \"adhoc\".\"dateFun\"(DATE '1970-01-01')") |
| .returnsValue("0"); |
| with.query("values \"adhoc\".\"dateFun\"(DATE '1970-01-02')") |
| .returnsValue("86400000"); |
| with.query("values \"adhoc\".\"dateFun\"(cast(null as date))") |
| .returnsValue("-1"); |
| with.query("values \"adhoc\".\"timeFun\"(TIME '00:00:00')") |
| .returnsValue("0"); |
| with.query("values \"adhoc\".\"timeFun\"(TIME '00:01:30')") |
| .returnsValue("90000"); |
| with.query("values \"adhoc\".\"timeFun\"(cast(null as time))") |
| .returnsValue("-1"); |
| with.query("values \"adhoc\".\"timestampFun\"(TIMESTAMP '1970-01-01 00:00:00')") |
| .returnsValue("0"); |
| with.query("values \"adhoc\".\"timestampFun\"(TIMESTAMP '1970-01-02 00:01:30')") |
| .returnsValue("86490000"); |
| with.query("values \"adhoc\".\"timestampFun\"(cast(null as timestamp))") |
| .returnsValue("-1"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1041">[CALCITE-1041] |
| * User-defined function returns DATE or TIMESTAMP value</a>. */ |
| @Test void testReturnDate() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values \"adhoc\".\"toDateFun\"(0)") |
| .returnsValue("1970-01-01"); |
| with.query("values \"adhoc\".\"toDateFun\"(1)") |
| .returnsValue("1970-01-02"); |
| with.query("values \"adhoc\".\"toDateFun\"(cast(null as bigint))") |
| .returnsValue(null); |
| with.query("values \"adhoc\".\"toTimeFun\"(0)") |
| .returnsValue("00:00:00"); |
| with.query("values \"adhoc\".\"toTimeFun\"(90000)") |
| .returnsValue("00:01:30"); |
| with.query("values \"adhoc\".\"toTimeFun\"(cast(null as bigint))") |
| .returnsValue(null); |
| with.query("values \"adhoc\".\"toTimestampFun\"(0)") |
| .returnsValue("1970-01-01 00:00:00"); |
| with.query("values \"adhoc\".\"toTimestampFun\"(86490000)") |
| .returnsValue("1970-01-02 00:01:30"); |
| with.query("values \"adhoc\".\"toTimestampFun\"(cast(null as bigint))") |
| .returnsValue(null); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1881">[CALCITE-1881] |
| * Can't distinguish overloaded user-defined functions that have DATE and |
| * TIMESTAMP arguments</a>. */ |
| @Test void testDateAndTimestamp() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values \"adhoc\".\"toLong\"(DATE '1970-01-15')") |
| .returns("EXPR$0=1209600000\n"); |
| with.query("values \"adhoc\".\"toLong\"(DATE '2002-08-11')") |
| .returns("EXPR$0=1029024000000\n"); |
| with.query("values \"adhoc\".\"toLong\"(DATE '2003-04-11')") |
| .returns("EXPR$0=1050019200000\n"); |
| with.query("values \"adhoc\".\"toLong\"(TIMESTAMP '2003-04-11 00:00:00')") |
| .returns("EXPR$0=1050019200000\n"); |
| with.query("values \"adhoc\".\"toLong\"(TIMESTAMP '2003-04-11 00:00:06')") |
| .returns("EXPR$0=1050019206000\n"); |
| with.query("values \"adhoc\".\"toLong\"(TIMESTAMP '2003-04-18 01:20:00')") |
| .returns("EXPR$0=1050628800000\n"); |
| with.query("values \"adhoc\".\"toLong\"(TIME '00:20:00')") |
| .returns("EXPR$0=1200000\n"); |
| with.query("values \"adhoc\".\"toLong\"(TIME '00:20:10')") |
| .returns("EXPR$0=1210000\n"); |
| with.query("values \"adhoc\".\"toLong\"(TIME '01:20:00')") |
| .returns("EXPR$0=4800000\n"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-2053">[CALCITE-2053] |
| * Overloaded user-defined functions that have Double and BigDecimal arguments |
| * will goes wrong </a>. */ |
| @Test void testBigDecimalAndLong() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("values \"adhoc\".\"toDouble\"(cast(1.0 as double))") |
| .returns("EXPR$0=1.0\n"); |
| with.query("values \"adhoc\".\"toDouble\"(cast(1.0 as decimal))") |
| .returns("EXPR$0=1.0\n"); |
| with.query("values \"adhoc\".\"toDouble\"(cast(1 as double))") |
| .returns("EXPR$0=1.0\n"); |
| with.query("values \"adhoc\".\"toDouble\"(cast(1 as decimal))") |
| .returns("EXPR$0=1.0\n"); |
| with.query("values \"adhoc\".\"toDouble\"(cast(1 as float))") |
| .returns("EXPR$0=1.0\n"); |
| with.query("values \"adhoc\".\"toDouble\"(cast(1.0 as float))") |
| .returns("EXPR$0=1.0\n"); |
| } |
| |
| /** Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1041">[CALCITE-1041] |
| * User-defined function returns DATE or TIMESTAMP value</a>. */ |
| @Test void testReturnDate2() { |
| final CalciteAssert.AssertThat with = withUdf(); |
| with.query("select * from (values 0) as t(c)\n" |
| + "where \"adhoc\".\"toTimestampFun\"(c) in (\n" |
| + " cast('1970-01-01 00:00:00' as timestamp),\n" |
| + " cast('1997-02-01 00:00:00' as timestamp))") |
| .returnsValue("0"); |
| with.query("select * from (values 0) as t(c)\n" |
| + "where \"adhoc\".\"toTimestampFun\"(c) in (\n" |
| + " timestamp '1970-01-01 00:00:00',\n" |
| + " timestamp '1997-02-01 00:00:00')") |
| .returnsValue("0"); |
| with.query("select * from (values 0) as t(c)\n" |
| + "where \"adhoc\".\"toTimestampFun\"(c) in (\n" |
| + " '1970-01-01 00:00:00',\n" |
| + " '1997-02-01 00:00:00')") |
| .returnsValue("0"); |
| } |
| |
| /** |
| * Test case for |
| * <a href="https://issues.apache.org/jira/browse/CALCITE-1834">[CALCITE-1834] |
| * User-defined function for Arrays</a>. |
| */ |
| @Test void testArrayUserDefinedFunction() throws Exception { |
| try (Connection connection = DriverManager.getConnection("jdbc:calcite:")) { |
| CalciteConnection calciteConnection = |
| connection.unwrap(CalciteConnection.class); |
| SchemaPlus rootSchema = calciteConnection.getRootSchema(); |
| rootSchema.add("hr", new ReflectiveSchema(new HrSchema())); |
| |
| SchemaPlus post = rootSchema.add("POST", new AbstractSchema()); |
| post.add("ARRAY_APPEND", new ArrayAppendDoubleFunction()); |
| post.add("ARRAY_APPEND", new ArrayAppendIntegerFunction()); |
| final String sql = "select \"empid\" as EMPLOYEE_ID,\n" |
| + " \"name\" || ' ' || \"name\" as EMPLOYEE_NAME,\n" |
| + " \"salary\" as EMPLOYEE_SALARY,\n" |
| + " POST.ARRAY_APPEND(ARRAY[1,2,3], \"deptno\") as DEPARTMENTS\n" |
| + "from \"hr\".\"emps\""; |
| |
| final String result = "" |
| + "EMPLOYEE_ID=100; EMPLOYEE_NAME=Bill Bill;" |
| + " EMPLOYEE_SALARY=10000.0; DEPARTMENTS=[1, 2, 3, 10]\n" |
| + "EMPLOYEE_ID=200; EMPLOYEE_NAME=Eric Eric;" |
| + " EMPLOYEE_SALARY=8000.0; DEPARTMENTS=[1, 2, 3, 20]\n" |
| + "EMPLOYEE_ID=150; EMPLOYEE_NAME=Sebastian Sebastian;" |
| + " EMPLOYEE_SALARY=7000.0; DEPARTMENTS=[1, 2, 3, 10]\n" |
| + "EMPLOYEE_ID=110; EMPLOYEE_NAME=Theodore Theodore;" |
| + " EMPLOYEE_SALARY=11500.0; DEPARTMENTS=[1, 2, 3, 10]\n"; |
| |
| try (Statement statement = connection.createStatement(); |
| ResultSet resultSet = statement.executeQuery(sql)) { |
| assertThat(CalciteAssert.toString(resultSet), is(result)); |
| } |
| } |
| } |
| |
| /** |
| * Base class for functions that append arrays. |
| */ |
| private abstract static class ArrayAppendScalarFunction |
| implements ScalarFunction, ImplementableFunction { |
| public List<FunctionParameter> getParameters() { |
| final List<FunctionParameter> parameters = new ArrayList<>(); |
| for (final Ord<RelProtoDataType> type : Ord.zip(getParams())) { |
| parameters.add( |
| new FunctionParameter() { |
| public int getOrdinal() { |
| return type.i; |
| } |
| |
| public String getName() { |
| return "arg" + type.i; |
| } |
| |
| public RelDataType getType(RelDataTypeFactory typeFactory) { |
| return type.e.apply(typeFactory); |
| } |
| |
| public boolean isOptional() { |
| return false; |
| } |
| }); |
| } |
| return parameters; |
| } |
| |
| protected abstract List<RelProtoDataType> getParams(); |
| |
| @Override public CallImplementor getImplementor() { |
| return (translator, call, nullAs) -> { |
| Method lookupMethod = |
| Types.lookupMethod(Smalls.AllTypesFunction.class, |
| "arrayAppendFun", List.class, Integer.class); |
| return Expressions.call(lookupMethod, |
| translator.translateList(call.getOperands(), nullAs)); |
| }; |
| } |
| } |
| |
| /** Function with signature "f(ARRAY OF INTEGER, INTEGER) returns ARRAY OF |
| * INTEGER". */ |
| private static class ArrayAppendIntegerFunction |
| extends ArrayAppendScalarFunction { |
| @Override public RelDataType getReturnType(RelDataTypeFactory typeFactory) { |
| return typeFactory.createArrayType( |
| typeFactory.createSqlType(SqlTypeName.INTEGER), -1); |
| } |
| |
| @Override public List<RelProtoDataType> getParams() { |
| return ImmutableList.of( |
| typeFactory -> typeFactory.createArrayType( |
| typeFactory.createSqlType(SqlTypeName.INTEGER), -1), |
| typeFactory -> typeFactory.createSqlType(SqlTypeName.INTEGER)); |
| } |
| } |
| |
| /** Function with signature "f(ARRAY OF DOUBLE, INTEGER) returns ARRAY OF |
| * DOUBLE". */ |
| private static class ArrayAppendDoubleFunction |
| extends ArrayAppendScalarFunction { |
| public RelDataType getReturnType(RelDataTypeFactory typeFactory) { |
| return typeFactory.createArrayType( |
| typeFactory.createSqlType(SqlTypeName.DOUBLE), -1); |
| } |
| |
| public List<RelProtoDataType> getParams() { |
| return ImmutableList.of( |
| typeFactory -> typeFactory.createArrayType( |
| typeFactory.createSqlType(SqlTypeName.DOUBLE), -1), |
| typeFactory -> typeFactory.createSqlType(SqlTypeName.INTEGER)); |
| } |
| } |
| |
| } |