calcite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From jh...@apache.org
Subject [2/3] calcite git commit: [CALCITE-941] Named, optional and DEFAULT arguments to function calls
Date Sat, 31 Oct 2015 23:06:11 GMT
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(
+ *       &#64;Parameter(name = "s") String s,
+ *       &#64;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.
+


Mime
View raw message