You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jc...@apache.org on 2015/11/11 13:25:41 UTC
[35/50] [abbrv] calcite git commit: [CALCITE-941] Named,
optional and DEFAULT arguments to function calls
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index d234b6d..f20273e 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -1480,7 +1480,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
if (call.operandCount() > 0) {
// This is not a default constructor invocation, and
// no user-defined constructor could be found
- throw handleUnresolvedFunction(call, unresolvedConstructor, argTypes);
+ throw handleUnresolvedFunction(call, unresolvedConstructor, argTypes,
+ null);
}
} else {
SqlCall testCall =
@@ -1513,10 +1514,9 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
return type;
}
- public CalciteException handleUnresolvedFunction(
- SqlCall call,
- SqlFunction unresolvedFunction,
- List<RelDataType> argTypes) {
+ public CalciteException handleUnresolvedFunction(SqlCall call,
+ SqlFunction unresolvedFunction, List<RelDataType> argTypes,
+ List<String> argNames) {
// For builtins, we can give a better error message
final List<SqlOperator> overloads = Lists.newArrayList();
opTab.lookupOperatorOverloads(unresolvedFunction.getNameAsId(), null,
@@ -1534,7 +1534,7 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
}
AssignableOperandTypeChecker typeChecking =
- new AssignableOperandTypeChecker(argTypes);
+ new AssignableOperandTypeChecker(argTypes, argNames);
String signature =
typeChecking.getAllowedSignatures(
unresolvedFunction,
@@ -1623,18 +1623,19 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
returnType);
}
} else if (node instanceof SqlCall) {
- SqlCall call = (SqlCall) node;
- SqlOperandTypeInference operandTypeInference =
+ final SqlCall call = (SqlCall) node;
+ final SqlOperandTypeInference operandTypeInference =
call.getOperator().getOperandTypeInference();
- List<SqlNode> operands = call.getOperandList();
- RelDataType[] operandTypes = new RelDataType[operands.size()];
+ final SqlCallBinding callBinding = new SqlCallBinding(this, scope, call);
+ final List<SqlNode> operands = callBinding.operands();
+ final RelDataType[] operandTypes = new RelDataType[operands.size()];
if (operandTypeInference == null) {
// TODO: eventually should assert(operandTypeInference != null)
// instead; for now just eat it
Arrays.fill(operandTypes, unknownType);
} else {
operandTypeInference.inferOperandTypes(
- new SqlCallBinding(this, scope, call),
+ callBinding,
inferredType,
operandTypes);
}
@@ -3982,10 +3983,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
// For example, "LOCALTIME()" is illegal. (It should be
// "LOCALTIME", which would have been handled as a
// SqlIdentifier.)
- throw handleUnresolvedFunction(
- call,
- (SqlFunction) operator,
- ImmutableList.<RelDataType>of());
+ throw handleUnresolvedFunction(call, (SqlFunction) operator,
+ ImmutableList.<RelDataType>of(), null);
}
SqlValidatorScope operandScope = scope.getOperandScope(call);
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
index a3d9ff7..b826cea 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -88,6 +88,7 @@ import org.apache.calcite.sql.SemiJoinType;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlCall;
+import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlDataTypeSpec;
import org.apache.calcite.sql.SqlDelete;
import org.apache.calcite.sql.SqlDynamicParam;
@@ -4173,7 +4174,8 @@ public class SqlToRelConverter {
return agg.lookupAggregates(call);
}
}
- return exprConverter.convertCall(this, call);
+ return exprConverter.convertCall(this,
+ new SqlCallBinding(validator, scope, call).permutedCall());
}
// implement SqlVisitor
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/java/org/apache/calcite/util/Compatible.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/Compatible.java b/core/src/main/java/org/apache/calcite/util/Compatible.java
index 2d1f1fd..f05e5f3 100644
--- a/core/src/main/java/org/apache/calcite/util/Compatible.java
+++ b/core/src/main/java/org/apache/calcite/util/Compatible.java
@@ -21,6 +21,7 @@ import com.google.common.collect.ImmutableSortedMap;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Maps;
+import java.lang.reflect.Array;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
@@ -60,6 +61,9 @@ public interface Compatible {
@Deprecated // to be removed before 2.0
void setSchema(Connection connection, String schema);
+ /** Calls {@link Method}.{@code getParameters()[i].getName()}. */
+ String getParameterName(Method method, int i);
+
/** Creates the implementation of Compatible suitable for the
* current environment. */
class Factory {
@@ -102,6 +106,20 @@ public interface Compatible {
connection.getClass().getMethod("setSchema", String.class);
return method1.invoke(connection, schema);
}
+ if (method.getName().equals("getParameterName")) {
+ final Method m = (Method) args[0];
+ final int i = (Integer) args[1];
+ try {
+ final Method method1 =
+ m.getClass().getMethod("getParameters");
+ Object parameters = method1.invoke(m);
+ final Object parameter = Array.get(parameters, i);
+ final Method method3 = parameter.getClass().getMethod("getName");
+ return method3.invoke(parameter);
+ } catch (NoSuchMethodException e) {
+ return "arg" + i;
+ }
+ }
return null;
}
});
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java b/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
index 8610dea..78b3dbe 100644
--- a/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
+++ b/core/src/main/java/org/apache/calcite/util/ImmutableIntList.java
@@ -16,6 +16,8 @@
*/
package org.apache.calcite.util;
+import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.function.Functions;
import org.apache.calcite.runtime.FlatLists;
import org.apache.calcite.util.mapping.Mappings;
@@ -24,7 +26,6 @@ import com.google.common.collect.Lists;
import com.google.common.collect.UnmodifiableListIterator;
import java.lang.reflect.Array;
-import java.util.AbstractList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
@@ -236,15 +237,14 @@ public class ImmutableIntList extends FlatLists.AbstractFlatList<Integer> {
*
* <p>For example, {@code range(1, 3)} contains [1, 2]. */
public static List<Integer> range(final int lower, final int upper) {
- return new AbstractList<Integer>() {
- @Override public Integer get(int index) {
- return lower + index;
- }
-
- @Override public int size() {
- return upper - lower;
- }
- };
+ return Functions.generate(upper - lower,
+ new Function1<Integer, Integer>() {
+ /** @see Bug#upgrade(String) Upgrade to {@code IntFunction} when we
+ * drop support for JDK 1.7 */
+ public Integer apply(Integer index) {
+ return lower + index;
+ }
+ });
}
/** Returns the identity list [0, ..., count - 1].
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/java/org/apache/calcite/util/ReflectUtil.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/ReflectUtil.java b/core/src/main/java/org/apache/calcite/util/ReflectUtil.java
index 4a29253..51b1e6b 100644
--- a/core/src/main/java/org/apache/calcite/util/ReflectUtil.java
+++ b/core/src/main/java/org/apache/calcite/util/ReflectUtil.java
@@ -16,8 +16,11 @@
*/
package org.apache.calcite.util;
+import org.apache.calcite.linq4j.function.Parameter;
+
import com.google.common.collect.ImmutableList;
+import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
@@ -38,7 +41,7 @@ public abstract class ReflectUtil {
private static Map<Class, Method> primitiveToByteBufferWriteMethod;
static {
- primitiveToBoxingMap = new HashMap<Class, Class>();
+ primitiveToBoxingMap = new HashMap<>();
primitiveToBoxingMap.put(Boolean.TYPE, Boolean.class);
primitiveToBoxingMap.put(Byte.TYPE, Byte.class);
primitiveToBoxingMap.put(Character.TYPE, Character.class);
@@ -48,11 +51,10 @@ public abstract class ReflectUtil {
primitiveToBoxingMap.put(Long.TYPE, Long.class);
primitiveToBoxingMap.put(Short.TYPE, Short.class);
- primitiveToByteBufferReadMethod = new HashMap<Class, Method>();
- primitiveToByteBufferWriteMethod = new HashMap<Class, Method>();
+ primitiveToByteBufferReadMethod = new HashMap<>();
+ primitiveToByteBufferWriteMethod = new HashMap<>();
Method[] methods = ByteBuffer.class.getDeclaredMethods();
- for (int i = 0; i < methods.length; ++i) {
- Method method = methods[i];
+ for (Method method : methods) {
Class[] paramTypes = method.getParameterTypes();
if (method.getName().startsWith("get")) {
if (!method.getReturnType().isPrimitive()) {
@@ -62,8 +64,7 @@ public abstract class ReflectUtil {
continue;
}
primitiveToByteBufferReadMethod.put(
- method.getReturnType(),
- method);
+ method.getReturnType(), method);
// special case for Boolean: treat as byte
if (method.getReturnType().equals(Byte.TYPE)) {
@@ -325,7 +326,7 @@ public abstract class ReflectUtil {
// the original visiteeClass has a diamond-shaped interface inheritance
// graph. (This is common, for example, in JMI.) The idea is to avoid
// iterating over a single interface's method more than once in a call.
- Map<Class<?>, Method> cache = new HashMap<Class<?>, Method>();
+ Map<Class<?>, Method> cache = new HashMap<>();
return lookupVisitMethod(
visitorClass,
@@ -375,14 +376,9 @@ public abstract class ReflectUtil {
}
Class<?>[] interfaces = visiteeClass.getInterfaces();
- for (int i = 0; i < interfaces.length; ++i) {
- Method method =
- lookupVisitMethod(
- visitorClass,
- interfaces[i],
- visitMethodName,
- paramTypes,
- cache);
+ for (Class<?> anInterface : interfaces) {
+ final Method method = lookupVisitMethod(visitorClass, anInterface,
+ visitMethodName, paramTypes, cache);
if (method != null) {
if (candidateMethod != null) {
if (!method.equals(candidateMethod)) {
@@ -427,8 +423,7 @@ public abstract class ReflectUtil {
assert ReflectiveVisitor.class.isAssignableFrom(visitorBaseClazz);
assert Object.class.isAssignableFrom(visiteeBaseClazz);
return new ReflectiveVisitDispatcher<R, E>() {
- final Map<List<Object>, Method> map =
- new HashMap<List<Object>, Method>();
+ final Map<List<Object>, Method> map = new HashMap<>();
public Method lookupVisitMethod(
Class<? extends R> visitorClass,
@@ -535,13 +530,8 @@ public abstract class ReflectUtil {
try {
final Object o = method.invoke(visitor, args);
return returnClazz.cast(o);
- } catch (IllegalAccessException e) {
- throw Util.newInternal(
- e,
- "While invoking method '" + method + "'");
- } catch (InvocationTargetException e) {
- throw Util.newInternal(
- e,
+ } catch (IllegalAccessException | InvocationTargetException e) {
+ throw Util.newInternal(e,
"While invoking method '" + method + "'");
}
}
@@ -557,8 +547,7 @@ public abstract class ReflectUtil {
methodName,
otherArgClassList);
if (method == null) {
- List<Class> classList =
- new ArrayList<Class>();
+ List<Class> classList = new ArrayList<>();
classList.add(arg0Clazz);
classList.addAll(otherArgClassList);
throw new IllegalArgumentException("Method not found: " + methodName
@@ -569,6 +558,26 @@ public abstract class ReflectUtil {
};
}
+ /** Derives the name of the {@code i}th parameter of a method. */
+ public static String getParameterName(Method method, int i) {
+ for (Annotation annotation : method.getParameterAnnotations()[i]) {
+ if (annotation.annotationType() == Parameter.class) {
+ return ((Parameter) annotation).name();
+ }
+ }
+ return Compatible.INSTANCE.getParameterName(method, i);
+ }
+
+ /** Derives whether the {@code i}th parameter of a method is optional. */
+ public static boolean isParameterOptional(Method method, int i) {
+ for (Annotation annotation : method.getParameterAnnotations()[i]) {
+ if (annotation.annotationType() == Parameter.class) {
+ return ((Parameter) annotation).optional();
+ }
+ }
+ return false;
+ }
+
//~ Inner Classes ----------------------------------------------------------
/**
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java b/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
index dcacb56..c53d300 100644
--- a/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
+++ b/core/src/main/java/org/apache/calcite/util/mapping/Mappings.java
@@ -399,6 +399,17 @@ public abstract class Mappings {
return new Permutation(IntList.toArray(targets));
}
+ /** Creates a bijection.
+ *
+ * <p>Throws if sources and targets are not one to one. */
+ public static Mapping bijection(Map<Integer, Integer> targets) {
+ final List<Integer> targetList = new ArrayList<>();
+ for (int i = 0; i < targets.size(); i++) {
+ targetList.add(targets.get(i));
+ }
+ return new Permutation(IntList.toArray(targetList));
+ }
+
/**
* Returns whether a mapping is the identity.
*/
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
----------------------------------------------------------------------
diff --git a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
index 9703e1d..9787ba9 100644
--- a/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
+++ b/core/src/main/resources/org/apache/calcite/runtime/CalciteResource.properties
@@ -132,6 +132,9 @@ OrderByOverlap=ORDER BY not allowed in both base and referenced windows
RefWindowWithFrame=Referenced window cannot have framing declarations
TypeNotSupported=Type ''{0}'' is not supported
FunctionQuantifierNotAllowed=DISTINCT/ALL not allowed with {0} function
+SomeButNotAllArgumentsAreNamed=Some but not all arguments are named
+DuplicateArgumentName=Duplicate argument name ''{0}''
+DefaultForOptionalParameter=DEFAULT is only allowed for optional parameters
AccessNotAllowed=Not allowed to perform {0} on {1}
MinMaxBadType=The {0} function does not support the {1} data type.
OnlyScalarSubqueryAllowed=Only scalar subqueries allowed in select list.
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index 4060bc9..80930ba 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -719,6 +719,40 @@ public class SqlParserTest {
checkExp("ln(power(2,2))", "LN(POWER(2, 2))");
}
+ @Test public void testFunctionNamedArgument() {
+ checkExp("foo(x => 1)",
+ "`FOO`(`X` => 1)");
+ checkExp("foo(x => 1, \"y\" => 'a', z => x <= y)",
+ "`FOO`(`X` => 1, `y` => 'a', `Z` => (`X` <= `Y`))");
+ checkExpFails("foo(x.y ^=>^ 1)",
+ "(?s).*Encountered \"=>\" at .*");
+ checkExpFails("foo(a => 1, x.y ^=>^ 2, c => 3)",
+ "(?s).*Encountered \"=>\" at .*");
+ }
+
+ @Test public void testFunctionDefaultArgument() {
+ checkExp("foo(1, DEFAULT, default, 'default', \"default\", 3)",
+ "`FOO`(1, DEFAULT, DEFAULT, 'default', `default`, 3)");
+ checkExp("foo(DEFAULT)",
+ "`FOO`(DEFAULT)");
+ checkExp("foo(x => 1, DEFAULT)",
+ "`FOO`(`X` => 1, DEFAULT)");
+ checkExp("foo(y => DEFAULT, x => 1)",
+ "`FOO`(`Y` => DEFAULT, `X` => 1)");
+ checkExp("foo(x => 1, y => DEFAULT)",
+ "`FOO`(`X` => 1, `Y` => DEFAULT)");
+ check("select sum(DISTINCT DEFAULT) from t group by x",
+ "SELECT SUM(DISTINCT DEFAULT)\n"
+ + "FROM `T`\n"
+ + "GROUP BY `X`");
+ checkExpFails("foo(x ^+^ DEFAULT)",
+ "(?s).*Encountered \"\\+ DEFAULT\" at .*");
+ checkExpFails("foo(0, x ^+^ DEFAULT + y)",
+ "(?s).*Encountered \"\\+ DEFAULT\" at .*");
+ checkExpFails("foo(0, DEFAULT ^+^ y)",
+ "(?s).*Encountered \"\\+\" at .*");
+ }
+
@Test public void testAggregateFilter() {
sql("select sum(sal) filter (where gender = 'F') as femaleSal,\n"
+ " sum(sal) filter (where true) allSal,\n"
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/test/java/org/apache/calcite/test/JdbcTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/test/JdbcTest.java b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
index 24dc2ce..96b97a1 100644
--- a/core/src/test/java/org/apache/calcite/test/JdbcTest.java
+++ b/core/src/test/java/org/apache/calcite/test/JdbcTest.java
@@ -45,6 +45,7 @@ import org.apache.calcite.linq4j.Queryable;
import org.apache.calcite.linq4j.function.Function0;
import org.apache.calcite.linq4j.function.Function1;
import org.apache.calcite.linq4j.function.Function2;
+import org.apache.calcite.linq4j.function.Parameter;
import org.apache.calcite.linq4j.tree.Types;
import org.apache.calcite.plan.RelOptCluster;
import org.apache.calcite.plan.RelOptTable;
@@ -1591,7 +1592,7 @@ public class JdbcTest {
+ "c0=Store 15; c1=1997; m0=52644.0700\n"
+ "c0=Store 11; c1=1997; m0=55058.7900\n",
"select \"customer\".\"yearly_income\" as \"c0\","
- + " \"customer\".\"education\" as \"c1\" \n"
+ + " \"customer\".\"education\" as \"c1\"\n"
+ "from \"customer\" as \"customer\",\n"
+ " \"sales_fact_1997\" as \"sales_fact_1997\"\n"
+ "where \"sales_fact_1997\".\"customer_id\" = \"customer\".\"customer_id\"\n"
@@ -2018,7 +2019,7 @@ public class JdbcTest {
final StringBuilder buf = new StringBuilder();
buf.append("select count(*)");
for (int i = 0; i < n; i++) {
- buf.append(i == 0 ? "\nfrom " : ",\n ")
+ buf.append(i == 0 ? "\nfrom " : ",\n")
.append("\"hr\".\"depts\" as d").append(i);
}
for (int i = 1; i < n; i++) {
@@ -5265,6 +5266,18 @@ public class JdbcTest {
+ "'\n"
+ " },\n"
+ " {\n"
+ + " name: 'MY_LEFT',\n"
+ + " className: '"
+ + MyLeftFunction.class.getName()
+ + "'\n"
+ + " },\n"
+ + " {\n"
+ + " name: 'ABCDE',\n"
+ + " className: '"
+ + MyAbcdeFunction.class.getName()
+ + "'\n"
+ + " },\n"
+ + " {\n"
+ " name: 'MY_STR',\n"
+ " className: '"
+ MyToStringFunction.class.getName()
@@ -5436,6 +5449,78 @@ public class JdbcTest {
.returns("P0=0; P1=1; P2=2\n");
}
+ /** Tests passing parameters to user-defined function by name. */
+ @Test public 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'\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>)\n");
+ // missing arguments
+ with.query("values (\"adhoc\".my_left(\"n\" => 3))")
+ .throws_("No match found for function signature MY_LEFT(n => <NUMERIC>)\n");
+ with.query("values (\"adhoc\".my_left(\"s\" => 'hello'))")
+ .throws_("No match found for function signature MY_LEFT(s => <CHARACTER>)\n");
+ // arguments of wrong type
+ with.query("values (\"adhoc\".my_left(\"n\" => 'hello', \"s\" => 'x'))")
+ .throws_("No match found for function signature "
+ + "MY_LEFT(n => <CHARACTER>, s => <CHARACTER>)\n");
+ with.query("values (\"adhoc\".my_left(\"n\" => 1, \"s\" => 0))")
+ .throws_("No match found for function signature "
+ + "MY_LEFT(n => <NUMERIC>, s => <NUMERIC>)\n");
+ }
+
+ /** Tests calling a user-defined function some of whose parameters are
+ * optional. */
+ @Test public 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");
+ with.query("values (\"adhoc\".abcde(1,DEFAULT,'abcde'))")
+ .throws_("No match found for function signature ABCDE(<NUMERIC>, <ANY>, <CHARACTER>)");
+ 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 public void testUserDefinedFunction2() throws Exception {
@@ -6214,10 +6299,10 @@ public class JdbcTest {
@Test public void testLexCaseInsensitiveSubQueryField() {
CalciteAssert.that()
.with(Lex.MYSQL)
- .query("select DID \n"
- + "from (select deptid as did \n"
+ .query("select DID\n"
+ + "from (select deptid as did\n"
+ " FROM\n"
- + " ( values (1), (2) ) as T1(deptid) \n"
+ + " ( values (1), (2) ) as T1(deptid)\n"
+ " ) ")
.returnsUnordered("DID=1", "DID=2");
}
@@ -6395,11 +6480,11 @@ public class JdbcTest {
CalciteAssert.that()
.with(Lex.ORACLE);
- with.query("select DID from (select DEPTID as did FROM \n "
+ with.query("select DID from (select DEPTID as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select x.DID from (select DEPTID as did FROM \n "
+ with.query("select x.DID from (select DEPTID as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X")
.returnsUnordered("DID=1", "DID=2");
}
@@ -6409,23 +6494,23 @@ public class JdbcTest {
CalciteAssert.that()
.with(Lex.MYSQL);
- with.query("select DID from (select deptid as did FROM \n "
+ with.query("select DID from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select x.DID from (select deptid as did FROM \n "
+ with.query("select x.DID from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select X.DID from (select deptid as did FROM \n "
+ with.query("select X.DID from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select X.DID2 from (select deptid as did FROM \n "
+ with.query("select X.DID2 from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X (DID2)")
.returnsUnordered("DID2=1", "DID2=2");
- with.query("select X.DID2 from (select deptid as did FROM \n "
+ with.query("select X.DID2 from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X (DID2)")
.returnsUnordered("DID2=1", "DID2=2");
}
@@ -6435,23 +6520,23 @@ public class JdbcTest {
CalciteAssert.that()
.with(Lex.MYSQL);
- with.query("select `DID` from (select deptid as did FROM \n "
+ with.query("select `DID` from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select `x`.`DID` from (select deptid as did FROM \n "
+ with.query("select `x`.`DID` from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select `X`.`DID` from (select deptid as did FROM \n "
+ with.query("select `X`.`DID` from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X ")
.returnsUnordered("DID=1", "DID=2");
- with.query("select `X`.`DID2` from (select deptid as did FROM \n "
+ with.query("select `X`.`DID2` from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X (DID2)")
.returnsUnordered("DID2=1", "DID2=2");
- with.query("select `X`.`DID2` from (select deptid as did FROM \n "
+ with.query("select `X`.`DID2` from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) X (DID2)")
.returnsUnordered("DID2=1", "DID2=2");
}
@@ -6459,7 +6544,7 @@ public class JdbcTest {
@Test public void testUnquotedCaseSensitiveSubQuerySqlServer() {
CalciteAssert.that()
.with(Lex.SQL_SERVER)
- .query("select DID from (select deptid as did FROM \n "
+ .query("select DID from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1(deptid) ) ")
.returnsUnordered("DID=1", "DID=2");
}
@@ -6467,7 +6552,7 @@ public class JdbcTest {
@Test public void testQuotedCaseSensitiveSubQuerySqlServer() {
CalciteAssert.that()
.with(Lex.SQL_SERVER)
- .query("select [DID] from (select deptid as did FROM \n "
+ .query("select [DID] from (select deptid as did FROM\n"
+ " ( values (1), (2) ) as T1([deptid]) ) ")
.returnsUnordered("DID=1", "DID=2");
}
@@ -6976,16 +7061,37 @@ public class JdbcTest {
public MyTable2[] mytable2 = { new MyTable2() };
}
- /** Example of a UDF with a non-static {@code eval} method. */
+ /** Example of a UDF with a non-static {@code eval} method,
+ * and named parameters. */
public static class MyPlusFunction {
- public int eval(int x, int y) {
+ public int eval(@Parameter(name = "x") int x, @Parameter(name = "y") int y) {
return x + y;
}
}
+ /** Example of a UDF with named parameters. */
+ public static class MyLeftFunction {
+ public String eval(@Parameter(name = "s") String s,
+ @Parameter(name = "n") int n) {
+ return s.substring(0, n);
+ }
+ }
+
+ /** Example of a UDF with named parameters, some of them optional. */
+ public static class MyAbcdeFunction {
+ public String eval(@Parameter(name = "A", optional = false) Integer a,
+ @Parameter(name = "B", optional = true) Integer b,
+ @Parameter(name = "C", optional = false) Integer c,
+ @Parameter(name = "D", optional = true) Integer d,
+ @Parameter(name = "E", optional = true) Integer e) {
+ return "{a: " + a + ", b: " + b + ", c: " + c + ", d: " + d + ", e: "
+ + e + "}";
+ }
+ }
+
/** Example of a non-strict UDF. (Does something useful when passed NULL.) */
public static class MyToStringFunction {
- public static String eval(Object o) {
+ public static String eval(@Parameter(name = "o") Object o) {
if (o == null) {
return "<null>";
}
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/core/src/test/java/org/apache/calcite/util/UtilTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/org/apache/calcite/util/UtilTest.java b/core/src/test/java/org/apache/calcite/util/UtilTest.java
index b814976..56336ce 100644
--- a/core/src/test/java/org/apache/calcite/util/UtilTest.java
+++ b/core/src/test/java/org/apache/calcite/util/UtilTest.java
@@ -21,6 +21,7 @@ import org.apache.calcite.avatica.util.Spaces;
import org.apache.calcite.examples.RelBuilderExample;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.linq4j.function.Function1;
+import org.apache.calcite.linq4j.function.Parameter;
import org.apache.calcite.runtime.FlatLists;
import org.apache.calcite.runtime.Resources;
import org.apache.calcite.sql.SqlDialect;
@@ -42,6 +43,7 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.lang.management.MemoryType;
+import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.sql.Timestamp;
import java.text.MessageFormat;
@@ -1414,6 +1416,17 @@ public class UtilTest {
assertThat(reverse.next().e, is("a"));
assertThat(reverse.hasNext(), is(false));
}
+
+ /** Tests {@link org.apache.calcite.util.ReflectUtil#getParameterName}. */
+ @Test public void testParameterName() throws NoSuchMethodException {
+ final Method method = UtilTest.class.getMethod("foo", int.class, int.class);
+ assertThat(ReflectUtil.getParameterName(method, 0), is("arg0"));
+ assertThat(ReflectUtil.getParameterName(method, 1), is("j"));
+ }
+
+ /** Dummy method for {@link #testParameterName()} to inspect. */
+ public static void foo(int i, @Parameter(name = "j") int j) {
+ }
}
// End UtilTest.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
----------------------------------------------------------------------
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
index bd7a446..8d6ccbf 100644
--- a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Functions.java
@@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.RandomAccess;
/**
* Utilities relating to functions.
@@ -422,14 +423,7 @@ public abstract class Functions {
if (size < 0) {
throw new IllegalArgumentException();
}
- return new AbstractList<E>() {
- public int size() {
- return size;
- }
- public E get(int index) {
- return fn.apply(index);
- }
- };
+ return new GeneratingList<>(size, fn);
}
/**
@@ -647,6 +641,26 @@ public abstract class Functions {
static final Ignore INSTANCE = new Ignore();
}
+
+ /** List that generates each element using a function. */
+ private static class GeneratingList<E> extends AbstractList<E>
+ implements RandomAccess {
+ private final int size;
+ private final Function1<Integer, E> fn;
+
+ public GeneratingList(int size, Function1<Integer, E> fn) {
+ this.size = size;
+ this.fn = fn;
+ }
+
+ public int size() {
+ return size;
+ }
+
+ public E get(int index) {
+ return fn.apply(index);
+ }
+ }
}
// End Functions.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/linq4j/src/main/java/org/apache/calcite/linq4j/function/Parameter.java
----------------------------------------------------------------------
diff --git a/linq4j/src/main/java/org/apache/calcite/linq4j/function/Parameter.java b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Parameter.java
new file mode 100644
index 0000000..9378bfc
--- /dev/null
+++ b/linq4j/src/main/java/org/apache/calcite/linq4j/function/Parameter.java
@@ -0,0 +1,86 @@
+/*
+ * 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.linq4j.function;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that supplies metadata about a function parameter.
+ *
+ * <p>A typical use is to derive names for the parameters of user-defined
+ * functions.
+ *
+ * <p>Here is an example:
+ *
+ * <blockquote><pre>
+ * public static class MyLeftFunction {
+ * public String eval(
+ * @Parameter(name = "s") String s,
+ * @Parameter(name = "n", optional = true) Integer n) {
+ * return s.substring(0, n == null ? 1 : n);
+ * }
+ * }</pre></blockquote>
+ *
+ * <p>The first parameter is named "s" and is mandatory,
+ * and the second parameter is named "n" and is optional.
+ *
+ * <p>If this annotation is present, it supersedes information that might be
+ * available via
+ * {@code Executable.getParameters()} (JDK 1.8 and above).
+ *
+ * <p>If the annotation is not specified, parameters will be named "arg0",
+ * "arg1" et cetera, and will be mandatory.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER })
+public @interface Parameter {
+ /** The name of the parameter.
+ *
+ * <p>The name is used when the function is called
+ * with parameter assignment, for example {@code foo(x => 1, y => 'a')}.
+ */
+ String name();
+
+ /** Returns whether the parameter is optional.
+ *
+ * <p>An optional parameter does not need to be specified when you call the
+ * function.
+ *
+ * <p>If you call a function using positional parameter syntax, you can omit
+ * optional parameters on the trailing edge. For example, if you have a
+ * function
+ * {@code baz(int a, int b optional, int c, int d optional, int e optional)}
+ * then you can call {@code baz(a, b, c, d, e)}
+ * or {@code baz(a, b, c, d)}
+ * or {@code baz(a, b, c)}
+ * but not {@code baz(a, b)} because {@code c} is not optional.
+ *
+ * <p>If you call a function using parameter name assignment syntax, you can
+ * omit any parameter that has a default value. For example, you can call
+ * {@code baz(a => 1, e => 5, c => 3)}, omitting optional parameters {@code b}
+ * and {@code d}.
+ *
+ * <p>Currently, the default value used when a parameter is not specified
+ * is NULL, and therefore optional parameters must be nullable.
+ */
+ boolean optional() default false;
+}
+
+// End Parameter.java
http://git-wip-us.apache.org/repos/asf/calcite/blob/d012b245/site/_docs/reference.md
----------------------------------------------------------------------
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index 7df7926..bb594ac 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -594,3 +594,96 @@ Not implemented:
| GROUPING(expression) | Returns 1 if expression is rolled up in the current row's grouping set, 0 otherwise
| GROUP_ID() | Returns an integer that uniquely identifies the combination of grouping keys
| GROUPING_ID(expression [, expression ] * ) | Returns a bit vector of the given grouping expressions
+
+### User-defined functions
+
+Calcite is extensible. You can define each kind of function using user code.
+For each kind of function there are often several ways to define a function,
+varying from convenient to efficient.
+
+To implement a *scalar function*, there are 3 options:
+
+* Create a class with a public static `eval` method. and register the class;
+* Create a class with a public non-static `eval` method. and a public
+ constructor with no arguments, and register the class;
+* Create a class with one or more public static methods, and register
+ each class and method.
+
+To implement an *aggregate function*:
+
+* Create a class with public static `init`, `add` and `result` methods, and
+ register the class;
+* Create a class with public non-static `init`, `add` and `result` methods, and
+ a public constructor with no arguments, and register the class.
+
+Optionally, add a public `merge` method to the class; this allows Calcite to
+generate code that merges sub-totals.
+
+Optionally, make your class implement the
+[SqlSplittableAggFunction]({{ site.apiRoot }}/org/apache/calcite/sql/SqlSplittableAggFunction.html)
+interface; this allows Calcite to decompose the function across several stages
+of aggregation, roll up from summary tables, and push it through joins.
+
+To implement a *table function*:
+
+* Create a class with a static `eval` method that returns
+ [TranslatableTable]({{ site.apiRoot }}/org/apache/calcite/schema/TranslatableTable.html)
+ or
+ [QueryableTable]({{ site.apiRoot }}/org/apache/calcite/schema/QueryableTable.html),
+ and register the class;
+* Create a class with a non-static `eval` method that returns
+ [TranslatableTable]({{ site.apiRoot }}/org/apache/calcite/schema/TranslatableTable.html)
+ or
+ [QueryableTable]({{ site.apiRoot }}/org/apache/calcite/schema/QueryableTable.html),
+ and register the class.
+
+Calcite deduces the parameter types and result type of a function from the
+parameter and return types of the Java method that implements it. Further, you
+can specify the name and optionality of each parameter using the
+[Parameter]({{ site.apiRoot }}/org/apache/calcite/linq4j/function/Parameter.html)
+annotation.
+
+### Calling functions with named and optional parameters
+
+Usually when you call a function, you need to specify all of its parameters,
+in order. But if the function has been defined with named and optional
+parameters
+
+Suppose you have a function `f`, declared as in the following pseudo syntax:
+
+```FUNCTION f(
+ INTEGER a,
+ INTEGER b DEFAULT NULL,
+ INTEGER c,
+ INTEGER d DEFAULT NULL,
+ INTEGER e DEFAULT NULL) RETURNS INTEGER```
+
+Note that all parameters have names, and parameters `b`, `d` and `e`
+have a default value of `NULL` and are therefore optional.
+(In Calcite, `NULL` is the only allowable default value for optional parameters;
+this may change
+[in future](https://issues.apache.org/jira/browse/CALCITE-947).)
+
+You can omit optional arguments at the end of the list, or use the `DEFAULT`
+keyword for any optional arguments.
+Here are some examples:
+
+* `f(1, 2, 3, 4, 5)` provides a value to each parameter, in order;
+* `f(1, 2, 3, 4)` omits `e`, which gets its default value, `NULL`;
+* `f(1, DEFAULT, 3)` omits `d` and `e`,
+ and specifies to use the default value of `b`;
+* `f(1, DEFAULT, 3, DEFAULT, DEFAULT)` has the same effect as the previous
+ example;
+* `f(1, 2)` is not legal, because `c` is not optional;
+* `f(1, 2, DEFAULT, 4)` is not legal, because `c` is not optional.
+
+You can specify arguments by name using the `=>` syntax.
+If one argument is named, they all must be.
+Arguments may be in any other, but must not specify any argument more than once,
+and you need to provide a value for every parameter which is not optional.
+Here are some examples:
+
+* `f(c => 3, d => 1, a => 0)` is equivalent to `f(0, NULL, 3, 1, NULL)`;
+* `f(c => 3, d => 1)` is not legal, because you have not specified a value for
+ `a` and `a` is not optional.
+