This is an automated email from the ASF dual-hosted git repository.

jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git

commit 136c9c5d75cf7b0809c3bde3525956d2c58470c7
Author: yuzhao.cyz <[email protected]>
AuthorDate: Thu Mar 28 14:04:03 2019 +0800

    [CALCITE-2928] When resolving user-defined functions (UDFs), use the 
case-sensitivity of the current connection (Danny Chan)
    
    Move SqlUtil.makeCall to SqlValidator.makeNullaryCall; it just makes
    sense now that it is using SqlOperatorTable and SqlNameMatcher from the
    validator.
    
    Close apache/calcite#1137
---
 .../calcite/prepare/CalciteCatalogReader.java      |  6 +-
 .../java/org/apache/calcite/sql/SqlBasicCall.java  |  2 +-
 .../main/java/org/apache/calcite/sql/SqlCall.java  |  4 +-
 .../java/org/apache/calcite/sql/SqlFunction.java   |  7 +-
 .../java/org/apache/calcite/sql/SqlIdentifier.java | 10 +--
 .../java/org/apache/calcite/sql/SqlOperator.java   |  7 +-
 .../org/apache/calcite/sql/SqlOperatorTable.java   |  6 +-
 .../main/java/org/apache/calcite/sql/SqlUtil.java  | 78 +++++++++-------------
 .../calcite/sql/parser/SqlAbstractParserImpl.java  |  4 +-
 .../calcite/sql/util/ChainedSqlOperatorTable.java  |  6 +-
 .../calcite/sql/util/ListSqlOperatorTable.java     |  6 +-
 .../sql/util/ReflectiveSqlOperatorTable.java       | 71 ++++++++++++++------
 .../apache/calcite/sql/validate/AggChecker.java    |  6 +-
 .../org/apache/calcite/sql/validate/AggFinder.java | 11 +--
 .../apache/calcite/sql/validate/AggVisitor.java    | 16 +++--
 .../apache/calcite/sql/validate/SqlValidator.java  |  8 +++
 .../calcite/sql/validate/SqlValidatorImpl.java     | 70 ++++++++++++-------
 .../calcite/sql/validate/SqlValidatorUtil.java     |  3 +-
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  2 +-
 .../prepare/LookupOperatorOverloadsTest.java       | 20 ++++--
 .../calcite/sql/test/SqlOperatorBaseTest.java      | 22 +++---
 .../apache/calcite/test/MockSqlOperatorTable.java  | 23 +++++++
 .../org/apache/calcite/test/SqlValidatorTest.java  | 56 +++++++++++++++-
 23 files changed, 291 insertions(+), 153 deletions(-)

diff --git 
a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java 
b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
index 9685239..d2dcf5b 100644
--- a/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
+++ b/core/src/main/java/org/apache/calcite/prepare/CalciteCatalogReader.java
@@ -163,7 +163,8 @@ public class CalciteCatalogReader implements 
Prepare.CatalogReader {
               Iterables.concat(schemaNames, Util.skipLast(names)), 
nameMatcher);
       if (schema != null) {
         final String name = Util.last(names);
-        functions2.addAll(schema.getFunctions(name, true));
+        boolean caseSensitive = nameMatcher.isCaseSensitive();
+        functions2.addAll(schema.getFunctions(name, caseSensitive));
       }
     }
     return functions2;
@@ -246,7 +247,8 @@ public class CalciteCatalogReader implements 
Prepare.CatalogReader {
   public void lookupOperatorOverloads(final SqlIdentifier opName,
       SqlFunctionCategory category,
       SqlSyntax syntax,
-      List<SqlOperator> operatorList) {
+      List<SqlOperator> operatorList,
+      SqlNameMatcher nameMatcher) {
     if (syntax != SqlSyntax.FUNCTION) {
       return;
     }
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlBasicCall.java 
b/core/src/main/java/org/apache/calcite/sql/SqlBasicCall.java
index d9e0322..ad28e0a 100755
--- a/core/src/main/java/org/apache/calcite/sql/SqlBasicCall.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlBasicCall.java
@@ -38,7 +38,7 @@ public class SqlBasicCall extends SqlCall {
     this(operator, operands, pos, false, null);
   }
 
-  protected SqlBasicCall(
+  public SqlBasicCall(
       SqlOperator operator,
       SqlNode[] operands,
       SqlParserPos pos,
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlCall.java 
b/core/src/main/java/org/apache/calcite/sql/SqlCall.java
index b44a3e0..49a5f74 100755
--- a/core/src/main/java/org/apache/calcite/sql/SqlCall.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlCall.java
@@ -188,7 +188,9 @@ public abstract class SqlCall extends SqlNode {
    * @return boolean true if function call to COUNT(*)
    */
   public boolean isCountStar() {
-    if (getOperator().isName("COUNT") && operandCount() == 1) {
+    SqlOperator sqlOperator = getOperator();
+    if (sqlOperator.getName().equals("COUNT")
+        && operandCount() == 1) {
       final SqlNode parm = operand(0);
       if (parm instanceof SqlIdentifier) {
         SqlIdentifier id = (SqlIdentifier) parm;
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlFunction.java 
b/core/src/main/java/org/apache/calcite/sql/SqlFunction.java
index 597b7ff..266a37c 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlFunction.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlFunction.java
@@ -235,7 +235,9 @@ public class SqlFunction extends SqlOperator {
 
     final SqlFunction function =
         (SqlFunction) SqlUtil.lookupRoutine(validator.getOperatorTable(),
-            getNameAsId(), argTypes, argNames, getFunctionType(), 
SqlSyntax.FUNCTION, getKind());
+            getNameAsId(), argTypes, argNames, getFunctionType(),
+            SqlSyntax.FUNCTION, getKind(),
+            validator.getCatalogReader().nameMatcher());
     try {
       // if we have a match on function name and parameter count, but
       // couldn't find a function with  a COLUMN_LIST type, retry, but
@@ -248,7 +250,8 @@ public class SqlFunction extends SqlOperator {
         if (function == null
             && SqlUtil.matchRoutinesByParameterCount(
                 validator.getOperatorTable(), getNameAsId(), argTypes,
-                getFunctionType())) {
+                getFunctionType(),
+                validator.getCatalogReader().nameMatcher())) {
           // remove the already validated node types corresponding to
           // row arguments before re-validating
           for (SqlNode operand : args) {
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java 
b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
index fa20d45..fff6270 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java
@@ -302,10 +302,7 @@ public class SqlIdentifier extends SqlNode {
   public void validateExpr(SqlValidator validator, SqlValidatorScope scope) {
     // First check for builtin functions which don't have parentheses,
     // like "LOCALTIME".
-    SqlCall call =
-        SqlUtil.makeCall(
-            validator.getOperatorTable(),
-            this);
+    final SqlCall call = validator.makeNullaryCall(this);
     if (call != null) {
       validator.validateCall(call, scope);
       return;
@@ -379,10 +376,7 @@ public class SqlIdentifier extends SqlNode {
     // First check for builtin functions which don't have parentheses,
     // like "LOCALTIME".
     final SqlValidator validator = scope.getValidator();
-    SqlCall call =
-        SqlUtil.makeCall(
-            validator.getOperatorTable(),
-            this);
+    final SqlCall call = validator.makeNullaryCall(this);
     if (call != null) {
       return call.getMonotonicity(scope);
     }
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java 
b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
index eeee2c3..cb1b2af 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
@@ -365,8 +365,8 @@ public abstract class SqlOperator {
     return name.equals(other.name) && kind == other.kind;
   }
 
-  public boolean isName(String testName) {
-    return name.equals(testName);
+  public boolean isName(String testName, boolean caseSensitive) {
+    return caseSensitive ? name.equals(testName) : 
name.equalsIgnoreCase(testName);
   }
 
   @Override public int hashCode() {
@@ -509,7 +509,8 @@ public abstract class SqlOperator {
 
     final SqlOperator sqlOperator =
         SqlUtil.lookupRoutine(validator.getOperatorTable(), getNameAsId(),
-            argTypes, null, null, getSyntax(), getKind());
+            argTypes, null, null, getSyntax(), getKind(),
+            validator.getCatalogReader().nameMatcher());
 
     ((SqlBasicCall) call).setOperator(sqlOperator);
     RelDataType type = call.getOperator().validateOperands(validator, scope, 
call);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/SqlOperatorTable.java
index adfe6fa..86fc8fc 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOperatorTable.java
@@ -16,6 +16,8 @@
  */
 package org.apache.calcite.sql;
 
+import org.apache.calcite.sql.validate.SqlNameMatcher;
+
 import java.util.List;
 
 /**
@@ -35,11 +37,13 @@ public interface SqlOperatorTable {
    *                 operator
    * @param syntax   syntax type of operator
    * @param operatorList mutable list to which to append matches
+   * @param nameMatcher Name matcher
    */
   void lookupOperatorOverloads(SqlIdentifier opName,
       SqlFunctionCategory category,
       SqlSyntax syntax,
-      List<SqlOperator> operatorList);
+      List<SqlOperator> operatorList,
+      SqlNameMatcher nameMatcher);
 
   /**
    * Retrieves a list of all functions and operators in this table. Used for
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java 
b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
index 8b26190..9314d35 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlUtil.java
@@ -31,6 +31,7 @@ import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
 import org.apache.calcite.sql.util.SqlBasicVisitor;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
 import org.apache.calcite.util.BarfingInvocationHandler;
 import org.apache.calcite.util.ConversionUtil;
 import org.apache.calcite.util.Glossary;
@@ -356,12 +357,13 @@ public abstract class SqlUtil {
    * Looks up a (possibly overloaded) routine based on name and argument
    * types.
    *
-   * @param opTab    operator table to search
-   * @param funcName name of function being invoked
-   * @param argTypes argument types
-   * @param argNames argument names, or null if call by position
-   * @param category whether a function or a procedure. (If a procedure is
-   *                 being invoked, the overload rules are simpler.)
+   * @param opTab         operator table to search
+   * @param funcName      name of function being invoked
+   * @param argTypes      argument types
+   * @param argNames      argument names, or null if call by position
+   * @param category      whether a function or a procedure. (If a procedure is
+   *                      being invoked, the overload rules are simpler.)
+   * @param nameMatcher   Whether to look up the function case-sensitively
    * @return matching routine, or null if none found
    *
    * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4
@@ -369,7 +371,7 @@ public abstract class SqlUtil {
   public static SqlOperator lookupRoutine(SqlOperatorTable opTab,
       SqlIdentifier funcName, List<RelDataType> argTypes,
       List<String> argNames, SqlFunctionCategory category,
-      SqlSyntax syntax, SqlKind sqlKind) {
+      SqlSyntax syntax, SqlKind sqlKind, SqlNameMatcher nameMatcher) {
     Iterator<SqlOperator> list =
         lookupSubjectRoutines(
             opTab,
@@ -378,7 +380,8 @@ public abstract class SqlUtil {
             argNames,
             syntax,
             sqlKind,
-            category);
+            category,
+            nameMatcher);
     if (list.hasNext()) {
       // return first on schema path
       return list.next();
@@ -401,7 +404,8 @@ public abstract class SqlUtil {
    * @param argNames  argument names, or null if call by position
    * @param sqlSyntax the SqlSyntax of the SqlOperator being looked up
    * @param sqlKind   the SqlKind of the SqlOperator being looked up
-   * @param category category of routine to look up
+   * @param category  Category of routine to look up
+   * @param nameMatcher Whether to look up the function case-sensitively
    * @return list of matching routines
    * @see Glossary#SQL99 SQL:1999 Part 2 Section 10.4
    */
@@ -412,10 +416,12 @@ public abstract class SqlUtil {
       List<String> argNames,
       SqlSyntax sqlSyntax,
       SqlKind sqlKind,
-      SqlFunctionCategory category) {
+      SqlFunctionCategory category,
+      SqlNameMatcher nameMatcher) {
     // start with all routines matching by name
     Iterator<SqlOperator> routines =
-        lookupSubjectRoutinesByName(opTab, funcName, sqlSyntax, category);
+        lookupSubjectRoutinesByName(opTab, funcName, sqlSyntax, category,
+            nameMatcher);
 
     // first pass:  eliminate routines which don't accept the given
     // number of arguments
@@ -453,20 +459,23 @@ public abstract class SqlUtil {
    * Determines whether there is a routine matching the given name and number
    * of arguments.
    *
-   * @param opTab    operator table to search
-   * @param funcName name of function being invoked
-   * @param argTypes argument types
-   * @param category category of routine to look up
+   * @param opTab         operator table to search
+   * @param funcName      name of function being invoked
+   * @param argTypes      argument types
+   * @param category      category of routine to look up
+   * @param nameMatcher   Whether to look up the function case-sensitively
    * @return true if match found
    */
   public static boolean matchRoutinesByParameterCount(
       SqlOperatorTable opTab,
       SqlIdentifier funcName,
       List<RelDataType> argTypes,
-      SqlFunctionCategory category) {
+      SqlFunctionCategory category,
+      SqlNameMatcher nameMatcher) {
     // start with all routines matching by name
     Iterator<SqlOperator> routines =
-        lookupSubjectRoutinesByName(opTab, funcName, SqlSyntax.FUNCTION, 
category);
+        lookupSubjectRoutinesByName(opTab, funcName, SqlSyntax.FUNCTION,
+            category, nameMatcher);
 
     // first pass:  eliminate routines which don't accept the given
     // number of arguments
@@ -479,9 +488,11 @@ public abstract class SqlUtil {
       SqlOperatorTable opTab,
       SqlIdentifier funcName,
       final SqlSyntax syntax,
-      SqlFunctionCategory category) {
+      SqlFunctionCategory category,
+      SqlNameMatcher nameMatcher) {
     final List<SqlOperator> sqlOperators = new ArrayList<>();
-    opTab.lookupOperatorOverloads(funcName, category, syntax, sqlOperators);
+    opTab.lookupOperatorOverloads(funcName, category, syntax, sqlOperators,
+        nameMatcher);
     switch (syntax) {
     case FUNCTION:
       return Iterators.filter(sqlOperators.iterator(),
@@ -655,35 +666,6 @@ public abstract class SqlUtil {
     }
   }
 
-  /**
-   * If an identifier is a legitimate call to a function which has no
-   * arguments and requires no parentheses (for example "CURRENT_USER"),
-   * returns a call to that function, otherwise returns null.
-   */
-  public static SqlCall makeCall(
-      SqlOperatorTable opTab,
-      SqlIdentifier id) {
-    if (id.names.size() == 1 && !id.isComponentQuoted(0)) {
-      final List<SqlOperator> list = new ArrayList<>();
-      opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list);
-      for (SqlOperator operator : list) {
-        if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
-          // Even though this looks like an identifier, it is a
-          // actually a call to a function. Construct a fake
-          // call to this function, so we can use the regular
-          // operator validation.
-          return new SqlBasicCall(
-              operator,
-              SqlNode.EMPTY_ARRAY,
-              id.getParserPosition(),
-              true,
-              null);
-        }
-      }
-    }
-    return null;
-  }
-
   public static String deriveAliasFromOrdinal(int ordinal) {
     // Use a '$' so that queries can't easily reference the
     // generated name.
diff --git 
a/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java 
b/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java
index 12a8dcc..4645b43 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java
@@ -27,6 +27,7 @@ import org.apache.calcite.sql.SqlSyntax;
 import org.apache.calcite.sql.SqlUnresolvedFunction;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlNameMatchers;
 import org.apache.calcite.util.Glossary;
 
 import com.google.common.collect.ImmutableList;
@@ -392,7 +393,8 @@ public abstract class SqlAbstractParserImpl {
     /// name when regenerating SQL).
     if (funName.isSimple()) {
       final List<SqlOperator> list = new ArrayList<>();
-      opTab.lookupOperatorOverloads(funName, funcType, SqlSyntax.FUNCTION, 
list);
+      opTab.lookupOperatorOverloads(funName, funcType, SqlSyntax.FUNCTION, 
list,
+          SqlNameMatchers.withCaseSensitive(funName.isComponentQuoted(0)));
       if (list.size() == 1) {
         fun = list.get(0);
       }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/util/ChainedSqlOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/util/ChainedSqlOperatorTable.java
index 835ae50..0359b49 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/util/ChainedSqlOperatorTable.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/util/ChainedSqlOperatorTable.java
@@ -21,6 +21,7 @@ import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.SqlSyntax;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
 
 import com.google.common.collect.ImmutableList;
 
@@ -67,9 +68,10 @@ public class ChainedSqlOperatorTable implements 
SqlOperatorTable {
 
   public void lookupOperatorOverloads(SqlIdentifier opName,
       SqlFunctionCategory category, SqlSyntax syntax,
-      List<SqlOperator> operatorList) {
+      List<SqlOperator> operatorList, SqlNameMatcher nameMatcher) {
     for (SqlOperatorTable table : tableList) {
-      table.lookupOperatorOverloads(opName, category, syntax, operatorList);
+      table.lookupOperatorOverloads(opName, category, syntax, operatorList,
+          nameMatcher);
     }
   }
 
diff --git 
a/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java 
b/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java
index 2b36f36..6a1c5d8 100644
--- a/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/util/ListSqlOperatorTable.java
@@ -22,6 +22,7 @@ import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.SqlSyntax;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -54,13 +55,14 @@ public class ListSqlOperatorTable implements 
SqlOperatorTable {
   public void lookupOperatorOverloads(SqlIdentifier opName,
       SqlFunctionCategory category,
       SqlSyntax syntax,
-      List<SqlOperator> operatorList) {
+      List<SqlOperator> operatorList,
+      SqlNameMatcher nameMatcher) {
     for (SqlOperator operator : this.operatorList) {
       if (operator.getSyntax() != syntax) {
         continue;
       }
       if (!opName.isSimple()
-          || !operator.isName(opName.getSimple())) {
+          || !nameMatcher.matches(operator.getName(), opName.getSimple())) {
         continue;
       }
       if (category != null
diff --git 
a/core/src/main/java/org/apache/calcite/sql/util/ReflectiveSqlOperatorTable.java
 
b/core/src/main/java/org/apache/calcite/sql/util/ReflectiveSqlOperatorTable.java
index 0812b24..fbf9557 100644
--- 
a/core/src/main/java/org/apache/calcite/sql/util/ReflectiveSqlOperatorTable.java
+++ 
b/core/src/main/java/org/apache/calcite/sql/util/ReflectiveSqlOperatorTable.java
@@ -22,6 +22,8 @@ import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorTable;
 import org.apache.calcite.sql.SqlSyntax;
+import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
 import org.apache.calcite.util.Pair;
 import org.apache.calcite.util.Util;
 
@@ -43,7 +45,11 @@ public abstract class ReflectiveSqlOperatorTable implements 
SqlOperatorTable {
 
   //~ Instance fields --------------------------------------------------------
 
-  private final Multimap<Key, SqlOperator> operators = HashMultimap.create();
+  private final Multimap<CaseSensitiveKey, SqlOperator> caseSensitiveOperators 
=
+      HashMultimap.create();
+
+  private final Multimap<CaseInsensitiveKey, SqlOperator> 
caseInsensitiveOperators =
+      HashMultimap.create();
 
   //~ Constructors -----------------------------------------------------------
 
@@ -80,9 +86,8 @@ public abstract class ReflectiveSqlOperatorTable implements 
SqlOperatorTable {
 
   // implement SqlOperatorTable
   public void lookupOperatorOverloads(SqlIdentifier opName,
-      SqlFunctionCategory category,
-      SqlSyntax syntax,
-      List<SqlOperator> operatorList) {
+      SqlFunctionCategory category, SqlSyntax syntax,
+      List<SqlOperator> operatorList, SqlNameMatcher nameMatcher) {
     // NOTE jvs 3-Mar-2005:  ignore category until someone cares
 
     String simpleName;
@@ -97,10 +102,8 @@ public abstract class ReflectiveSqlOperatorTable implements 
SqlOperatorTable {
       simpleName = opName.getSimple();
     }
 
-    // Always look up built-in operators case-insensitively. Even in sessions
-    // with unquotedCasing=UNCHANGED and caseSensitive=true.
     final Collection<SqlOperator> list =
-        operators.get(new Key(simpleName, syntax));
+        lookUpOperators(simpleName, syntax, nameMatcher);
     if (list.isEmpty()) {
       return;
     }
@@ -121,7 +124,8 @@ public abstract class ReflectiveSqlOperatorTable implements 
SqlOperatorTable {
     case BINARY:
     case PREFIX:
     case POSTFIX:
-      for (SqlOperator extra : operators.get(new Key(simpleName, syntax))) {
+      for (SqlOperator extra
+          : lookUpOperators(simpleName, syntax, nameMatcher)) {
         // REVIEW: should only search operators added during this method?
         if (extra != null && !operatorList.contains(extra)) {
           operatorList.add(extra);
@@ -132,32 +136,57 @@ public abstract class ReflectiveSqlOperatorTable 
implements SqlOperatorTable {
   }
 
   /**
+   * Look up operators based on case-sensitiveness.
+   */
+  private Collection<SqlOperator> lookUpOperators(String name, SqlSyntax 
syntax,
+      SqlNameMatcher nameMatcher) {
+    // Case sensitive only works for UDFs.
+    // Always look up built-in operators case-insensitively. Even in sessions
+    // with unquotedCasing=UNCHANGED and caseSensitive=true.
+    if (nameMatcher.isCaseSensitive()
+        && !(this instanceof SqlStdOperatorTable)) {
+      return caseSensitiveOperators.get(new CaseSensitiveKey(name, syntax));
+    } else {
+      return caseInsensitiveOperators.get(new CaseInsensitiveKey(name, 
syntax));
+    }
+  }
+
+  /**
    * Registers a function or operator in the table.
    */
   public void register(SqlOperator op) {
-    operators.put(new Key(op.getName(), op.getSyntax()), op);
+    // Register both for case-sensitive and case-insensitive look up.
+    caseSensitiveOperators.put(new CaseSensitiveKey(op.getName(), 
op.getSyntax()), op);
+    caseInsensitiveOperators.put(new CaseInsensitiveKey(op.getName(), 
op.getSyntax()), op);
   }
 
   public List<SqlOperator> getOperatorList() {
-    return ImmutableList.copyOf(operators.values());
+    return ImmutableList.copyOf(caseSensitiveOperators.values());
   }
 
   /** Key for looking up operators. The name is stored in upper-case because we
    * store case-insensitively, even in a case-sensitive session. */
-  private static class Key extends Pair<String, SqlSyntax> {
-    Key(String name, SqlSyntax syntax) {
+  private static class CaseInsensitiveKey extends Pair<String, SqlSyntax> {
+    CaseInsensitiveKey(String name, SqlSyntax syntax) {
       super(name.toUpperCase(Locale.ROOT), normalize(syntax));
     }
+  }
 
-    private static SqlSyntax normalize(SqlSyntax syntax) {
-      switch (syntax) {
-      case BINARY:
-      case PREFIX:
-      case POSTFIX:
-        return syntax;
-      default:
-        return SqlSyntax.FUNCTION;
-      }
+  /** Key for looking up operators. The name kept as what it is to look up 
case-sensitively. */
+  private static class CaseSensitiveKey extends Pair<String, SqlSyntax> {
+    CaseSensitiveKey(String name, SqlSyntax syntax) {
+      super(name, normalize(syntax));
+    }
+  }
+
+  private static SqlSyntax normalize(SqlSyntax syntax) {
+    switch (syntax) {
+    case BINARY:
+    case PREFIX:
+    case POSTFIX:
+      return syntax;
+    default:
+      return SqlSyntax.FUNCTION;
     }
   }
 }
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java 
b/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
index 2962459..e14f2ca 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AggChecker.java
@@ -22,7 +22,6 @@ import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlNode;
 import org.apache.calcite.sql.SqlNodeList;
 import org.apache.calcite.sql.SqlSelect;
-import org.apache.calcite.sql.SqlUtil;
 import org.apache.calcite.sql.SqlWindow;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.util.SqlBasicVisitor;
@@ -96,10 +95,7 @@ class AggChecker extends SqlBasicVisitor<Void> {
     }
 
     // Is it a call to a parentheses-free function?
-    SqlCall call =
-        SqlUtil.makeCall(
-            validator.getOperatorTable(),
-            id);
+    final SqlCall call = validator.makeNullaryCall(id);
     if (call != null) {
       return call.accept(this);
     }
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java 
b/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java
index 86f58b1..98721af 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java
@@ -39,10 +39,11 @@ class AggFinder extends AggVisitor {
    * @param aggregate Whether to find non-windowed aggregate calls
    * @param group Whether to find group functions (e.g. {@code TUMBLE})
    * @param delegate Finder to which to delegate when processing the arguments
+   * @param nameMatcher Whether to match the agg function case-sensitively
    */
   AggFinder(SqlOperatorTable opTab, boolean over, boolean aggregate,
-      boolean group, AggFinder delegate) {
-    super(opTab, over, aggregate, group, delegate);
+      boolean group, AggFinder delegate, SqlNameMatcher nameMatcher) {
+    super(opTab, over, aggregate, group, delegate, nameMatcher);
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -83,7 +84,7 @@ class AggFinder extends AggVisitor {
    * then returns the list of all aggregates found. */
   Iterable<SqlCall> findAll(Iterable<SqlNode> nodes) {
     final AggIterable aggIterable =
-        new AggIterable(opTab, over, aggregate, group, delegate);
+        new AggIterable(opTab, over, aggregate, group, delegate, nameMatcher);
     for (SqlNode node : nodes) {
       node.accept(aggIterable);
     }
@@ -95,8 +96,8 @@ class AggFinder extends AggVisitor {
     private final List<SqlCall> calls = new ArrayList<>();
 
     AggIterable(SqlOperatorTable opTab, boolean over, boolean aggregate,
-        boolean group, AggFinder delegate) {
-      super(opTab, over, aggregate, group, delegate);
+        boolean group, AggFinder delegate, SqlNameMatcher nameMatcher) {
+      super(opTab, over, aggregate, group, delegate, nameMatcher);
     }
 
     @Override protected Void found(SqlCall call) {
diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java 
b/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java
index 44dfcfa..b6f2c34 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/AggVisitor.java
@@ -27,6 +27,8 @@ import org.apache.calcite.sql.util.SqlBasicVisitor;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
+import javax.annotation.Nullable;
 
 /** Visitor that can find aggregate and windowed aggregate functions.
  *
@@ -37,10 +39,11 @@ abstract class AggVisitor extends SqlBasicVisitor<Void> {
   protected final boolean over;
   protected final AggFinder delegate;
   /** Whether to find regular (non-windowed) aggregates. */
-  protected boolean aggregate;
+  protected final boolean aggregate;
   /** Whether to find group functions (e.g. {@code TUMBLE})
    * or group auxiliary functions (e.g. {@code TUMBLE_START}). */
-  protected boolean group;
+  protected final boolean group;
+  protected final SqlNameMatcher nameMatcher;
 
   /**
    * Creates an AggVisitor.
@@ -51,14 +54,16 @@ abstract class AggVisitor extends SqlBasicVisitor<Void> {
    * @param aggregate Whether to find non-windowed aggregate calls
    * @param group Whether to find group functions (e.g. {@code TUMBLE})
    * @param delegate Finder to which to delegate when processing the arguments
+   * @param nameMatcher Whether to match the agg function names 
case-sensitively
    */
   AggVisitor(SqlOperatorTable opTab, boolean over, boolean aggregate,
-      boolean group, AggFinder delegate) {
+      boolean group, @Nullable AggFinder delegate, SqlNameMatcher nameMatcher) 
{
     this.group = group;
     this.over = over;
     this.aggregate = aggregate;
     this.delegate = delegate;
-    this.opTab = opTab;
+    this.opTab = Objects.requireNonNull(opTab);
+    this.nameMatcher = Objects.requireNonNull(nameMatcher);
   }
 
   public Void visit(SqlCall call) {
@@ -83,7 +88,8 @@ abstract class AggVisitor extends SqlBasicVisitor<Void> {
       if (sqlFunction.getFunctionType().isUserDefinedNotSpecificFunction()) {
         final List<SqlOperator> list = new ArrayList<>();
         opTab.lookupOperatorOverloads(sqlFunction.getSqlIdentifier(),
-            sqlFunction.getFunctionType(), SqlSyntax.FUNCTION, list);
+            sqlFunction.getFunctionType(), SqlSyntax.FUNCTION, list,
+            nameMatcher);
         for (SqlOperator operator2 : list) {
           if (operator2.isAggregator() && !operator2.requiresOver()) {
             // If nested aggregates disallowed or found aggregate at invalid
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
index d385e14..5c2bb13 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
@@ -45,6 +45,7 @@ import org.apache.calcite.sql.SqlWithItem;
 
 import java.util.List;
 import java.util.Map;
+import javax.annotation.Nullable;
 
 /**
  * Validates the parse tree of a SQL statement, and provides semantic
@@ -312,6 +313,13 @@ public interface SqlValidator {
       List<SqlNode> operands);
 
   /**
+   * If an identifier is a legitimate call to a function that has no
+   * arguments and requires no parentheses (for example "CURRENT_USER"),
+   * returns a call to that function, otherwise returns null.
+   */
+  @Nullable SqlCall makeNullaryCall(SqlIdentifier id);
+
+  /**
    * Derives the type of a node in a given scope. If the type has already been
    * inferred, returns the previous type.
    *
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 c0b8846..f293dea 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
@@ -133,6 +133,7 @@ import java.util.Objects;
 import java.util.Set;
 import java.util.function.Supplier;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 
 import static org.apache.calcite.sql.SqlUtil.stripAs;
 import static org.apache.calcite.util.Static.RESOURCE;
@@ -310,11 +311,15 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
 
     rewriteCalls = true;
     expandColumnReferences = true;
-    aggFinder = new AggFinder(opTab, false, true, false, null);
-    aggOrOverFinder = new AggFinder(opTab, true, true, false, null);
-    overFinder = new AggFinder(opTab, true, false, false, aggOrOverFinder);
-    groupFinder = new AggFinder(opTab, false, false, true, null);
-    aggOrOverOrGroupFinder = new AggFinder(opTab, true, true, true, null);
+    final SqlNameMatcher nameMatcher = catalogReader.nameMatcher();
+    aggFinder = new AggFinder(opTab, false, true, false, null, nameMatcher);
+    aggOrOverFinder =
+        new AggFinder(opTab, true, true, false, null, nameMatcher);
+    overFinder = new AggFinder(opTab, true, false, false, aggOrOverFinder,
+        nameMatcher);
+    groupFinder = new AggFinder(opTab, false, false, true, null, nameMatcher);
+    aggOrOverOrGroupFinder = new AggFinder(opTab, true, true, true, null,
+        nameMatcher);
   }
 
   //~ Methods ----------------------------------------------------------------
@@ -879,10 +884,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
               op.getName(),
               pos);
 
-      final SqlCall call =
-          SqlUtil.makeCall(
-              validator.getOperatorTable(),
-              curOpId);
+      final SqlCall call = validator.makeNullaryCall(curOpId);
       if (call != null) {
         result.add(
             new SqlMonikerImpl(
@@ -1178,7 +1180,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
         // not, we'll handle it later during overload resolution.
         final List<SqlOperator> overloads = new ArrayList<>();
         opTab.lookupOperatorOverloads(function.getNameAsId(),
-            function.getFunctionType(), SqlSyntax.FUNCTION, overloads);
+            function.getFunctionType(), SqlSyntax.FUNCTION, overloads,
+            catalogReader.nameMatcher());
         if (overloads.size() == 1) {
           ((SqlBasicCall) call).setOperator(overloads.get(0));
         }
@@ -1624,6 +1627,25 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     nodeToTypeMap.remove(node);
   }
 
+  @Nullable public SqlCall makeNullaryCall(SqlIdentifier id) {
+    if (id.names.size() == 1 && !id.isComponentQuoted(0)) {
+      final List<SqlOperator> list = new ArrayList<>();
+      opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list,
+          catalogReader.nameMatcher());
+      for (SqlOperator operator : list) {
+        if (operator.getSyntax() == SqlSyntax.FUNCTION_ID) {
+          // Even though this looks like an identifier, it is a
+          // actually a call to a function. Construct a fake
+          // call to this function, so we can use the regular
+          // operator validation.
+          return new SqlBasicCall(operator, SqlNode.EMPTY_ARRAY,
+              id.getParserPosition(), true, null);
+        }
+      }
+    }
+    return null;
+  }
+
   public RelDataType deriveType(
       SqlValidatorScope scope,
       SqlNode expr) {
@@ -1717,7 +1739,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     // For builtins, we can give a better error message
     final List<SqlOperator> overloads = new ArrayList<>();
     opTab.lookupOperatorOverloads(unresolvedFunction.getNameAsId(), null,
-        SqlSyntax.FUNCTION, overloads);
+        SqlSyntax.FUNCTION, overloads, catalogReader.nameMatcher());
     if (overloads.size() == 1) {
       SqlFunction fun = (SqlFunction) overloads.get(0);
       if ((fun.getSqlIdentifier() == null)
@@ -2790,7 +2812,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
 
   protected boolean isNestedAggregateWindow(SqlNode node) {
     AggFinder nestedAggFinder =
-        new AggFinder(opTab, false, false, false, aggFinder);
+        new AggFinder(opTab, false, false, false, aggFinder,
+            catalogReader.nameMatcher());
     return nestedAggFinder.findAgg(node) != null;
   }
 
@@ -3110,8 +3133,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     // if it's not a SqlIdentifier then that's fine, it'll be validated 
somewhere else.
     if (leftOrRight instanceof SqlIdentifier) {
       SqlIdentifier from = (SqlIdentifier) leftOrRight;
-      Table table = findTable(catalogReader.getRootSchema(), 
Util.last(from.names),
-              catalogReader.nameMatcher().isCaseSensitive());
+      Table table = findTable(catalogReader.getRootSchema(),
+          Util.last(from.names));
       String name = Util.last(identifier.names);
 
       if (table != null && table.isRolledUp(name)) {
@@ -3469,8 +3492,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
   }
 
   private Pair<String, String> findTableColumnPair(SqlIdentifier identifier,
-                                                   SqlValidatorScope scope) {
-    SqlCall call = SqlUtil.makeCall(getOperatorTable(), identifier);
+      SqlValidatorScope scope) {
+    final SqlCall call = makeNullaryCall(identifier);
     if (call != null) {
       return null;
     }
@@ -3523,7 +3546,8 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     return false;
   }
 
-  private Table findTable(CalciteSchema schema, String tableName, boolean 
caseSensitive) {
+  private Table findTable(CalciteSchema schema, String tableName) {
+    boolean caseSensitive = catalogReader.nameMatcher().isCaseSensitive();
     CalciteSchema.TableEntry entry = schema.getTable(tableName, caseSensitive);
     if (entry != null) {
       return entry.getTable();
@@ -3531,7 +3555,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
 
     // Check sub schemas
     for (CalciteSchema subSchema : schema.getSubSchemaMap().values()) {
-      Table table = findTable(subSchema, tableName, caseSensitive);
+      Table table = findTable(subSchema, tableName);
       if (table != null) {
         return table;
       }
@@ -3559,8 +3583,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     if (names == null || names.size() == 0) {
       return null;
     } else if (names.size() == 1) {
-      return findTable(catalogReader.getRootSchema(), names.get(0),
-              catalogReader.nameMatcher().isCaseSensitive());
+      return findTable(catalogReader.getRootSchema(), names.get(0));
     }
 
     CalciteSchema.TableEntry entry =
@@ -5595,7 +5618,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     public RelDataType visit(SqlIdentifier id) {
       // First check for builtin functions which don't have parentheses,
       // like "LOCALTIME".
-      SqlCall call = SqlUtil.makeCall(opTab, id);
+      final SqlCall call = makeNullaryCall(id);
       if (call != null) {
         return call.getOperator().validateOperands(
             SqlValidatorImpl.this,
@@ -5710,10 +5733,7 @@ public class SqlValidatorImpl implements 
SqlValidatorWithHints {
     @Override public SqlNode visit(SqlIdentifier id) {
       // First check for builtin functions which don't have
       // parentheses, like "LOCALTIME".
-      SqlCall call =
-          SqlUtil.makeCall(
-              validator.getOperatorTable(),
-              id);
+      final SqlCall call = validator.makeNullaryCall(id);
       if (call != null) {
         return call.accept(this);
       }
diff --git 
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java 
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
index de3bcf6..ad0e241 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java
@@ -1132,7 +1132,8 @@ public class SqlValidatorUtil {
     public SqlNode visit(SqlIdentifier id) {
       // First check for builtin functions which don't have parentheses,
       // like "LOCALTIME".
-      final SqlCall call = 
SqlUtil.makeCall(getScope().getValidator().getOperatorTable(), id);
+      SqlValidator validator = getScope().getValidator();
+      final SqlCall call = validator.makeNullaryCall(id);
       if (call != null) {
         return call;
       }
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 95abb79..d746ba9 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -3618,7 +3618,7 @@ public class SqlToRelConverter {
       Blackboard bb,
       SqlIdentifier identifier) {
     // first check for reserved identifiers like CURRENT_USER
-    final SqlCall call = SqlUtil.makeCall(opTab, identifier);
+    final SqlCall call = bb.getValidator().makeNullaryCall(identifier);
     if (call != null) {
       return bb.convertExpression(call);
     }
diff --git 
a/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
 
b/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
index 69a4c1d..db9a924 100644
--- 
a/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
+++ 
b/core/src/test/java/org/apache/calcite/prepare/LookupOperatorOverloadsTest.java
@@ -29,6 +29,8 @@ import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlSyntax;
 import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlNameMatcher;
+import org.apache.calcite.sql.validate.SqlNameMatchers;
 import org.apache.calcite.sql.validate.SqlUserDefinedTableFunction;
 import org.apache.calcite.util.Smalls;
 
@@ -120,7 +122,17 @@ public class LookupOperatorOverloadsTest {
     check(cats, USER_DEFINED_FUNCTION, USER_DEFINED_TABLE_FUNCTION);
   }
 
-  @Test public void test() throws SQLException {
+  @Test public void testLookupCaseSensitively() throws SQLException {
+    checkInternal(true);
+  }
+
+  @Test public void testLookupCaseInSensitively() throws SQLException {
+    checkInternal(false);
+  }
+
+  private void checkInternal(boolean caseSensitive) throws SQLException {
+    final SqlNameMatcher nameMatcher =
+        SqlNameMatchers.withCaseSensitive(caseSensitive);
     final String schemaName = "MySchema";
     final String funcName = "MyFUNC";
     final String anotherName = "AnotherFunc";
@@ -152,13 +164,13 @@ public class LookupOperatorOverloadsTest {
               SqlParserPos.ZERO, null);
       reader.lookupOperatorOverloads(myFuncIdentifier,
           SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION, SqlSyntax.FUNCTION,
-          operatorList);
+          operatorList, nameMatcher);
       checkFunctionType(2, funcName, operatorList);
 
       operatorList.clear();
       reader.lookupOperatorOverloads(myFuncIdentifier,
           SqlFunctionCategory.USER_DEFINED_FUNCTION, SqlSyntax.FUNCTION,
-          operatorList);
+          operatorList, nameMatcher);
       checkFunctionType(0, null, operatorList);
 
       operatorList.clear();
@@ -167,7 +179,7 @@ public class LookupOperatorOverloadsTest {
               SqlParserPos.ZERO, null);
       reader.lookupOperatorOverloads(anotherFuncIdentifier,
           SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION, SqlSyntax.FUNCTION,
-          operatorList);
+          operatorList, nameMatcher);
       checkFunctionType(1, anotherName, operatorList);
     }
   }
diff --git 
a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java 
b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index 56e9ef3..f078920 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -48,6 +48,7 @@ import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
 import org.apache.calcite.sql.util.SqlString;
 import org.apache.calcite.sql.validate.SqlConformance;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
+import org.apache.calcite.sql.validate.SqlNameMatchers;
 import org.apache.calcite.sql.validate.SqlValidatorImpl;
 import org.apache.calcite.sql.validate.SqlValidatorScope;
 import org.apache.calcite.test.CalciteAssert;
@@ -75,7 +76,6 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
 import java.util.Collections;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -347,19 +347,13 @@ public abstract class SqlOperatorBaseTest {
     for (SqlOperator sqlOperator : operatorTable.getOperatorList()) {
       String operatorName = sqlOperator.getName();
       List<SqlOperator> routines = new ArrayList<>();
-      operatorTable.lookupOperatorOverloads(
-          new SqlIdentifier(operatorName, SqlParserPos.ZERO),
-          null,
-          sqlOperator.getSyntax(),
-          routines);
-
-      Iterator<SqlOperator> iter = routines.iterator();
-      while (iter.hasNext()) {
-        SqlOperator operator = iter.next();
-        if (!sqlOperator.getClass().isInstance(operator)) {
-          iter.remove();
-        }
-      }
+      final SqlIdentifier id =
+          new SqlIdentifier(operatorName, SqlParserPos.ZERO);
+      operatorTable.lookupOperatorOverloads(id, null, sqlOperator.getSyntax(),
+          routines, SqlNameMatchers.withCaseSensitive(true));
+
+      routines.removeIf(operator ->
+          !sqlOperator.getClass().isInstance(operator));
       assertThat(routines.size(), equalTo(1));
       assertThat(sqlOperator, equalTo(routines.get(0)));
     }
diff --git 
a/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java 
b/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java
index 02e7526..78b842d 100644
--- a/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java
+++ b/core/src/test/java/org/apache/calcite/test/MockSqlOperatorTable.java
@@ -20,10 +20,12 @@ import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
 import org.apache.calcite.sql.SqlFunction;
 import org.apache.calcite.sql.SqlFunctionCategory;
+import org.apache.calcite.sql.SqlIdentifier;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.SqlOperatorBinding;
 import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.OperandTypes;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.util.ChainedSqlOperatorTable;
@@ -61,6 +63,7 @@ public class MockSqlOperatorTable extends 
ChainedSqlOperatorTable {
     // using reflection when we are deserializing from JSON.
     opTab.addOperator(new RampFunction());
     opTab.addOperator(new DedupFunction());
+    opTab.addOperator(new MyFunction());
   }
 
   /** "RAMP" user-defined function. */
@@ -102,6 +105,26 @@ public class MockSqlOperatorTable extends 
ChainedSqlOperatorTable {
           .build();
     }
   }
+
+  /** "MYFUN" user-defined scalar function. */
+  public static class MyFunction extends SqlFunction {
+    public MyFunction() {
+      super("MYFUN",
+          new SqlIdentifier("MYFUN", SqlParserPos.ZERO),
+          SqlKind.OTHER_FUNCTION,
+          null,
+          null,
+          OperandTypes.NUMERIC,
+          null,
+          SqlFunctionCategory.USER_DEFINED_FUNCTION);
+    }
+
+    public RelDataType inferReturnType(SqlOperatorBinding opBinding) {
+      final RelDataTypeFactory typeFactory =
+          opBinding.getTypeFactory();
+      return typeFactory.createSqlType(SqlTypeName.BIGINT);
+    }
+  }
 }
 
 // End MockSqlOperatorTable.java
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java 
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index 57776b2..7905ba5 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1223,7 +1223,7 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
       checkExp("\"POSITION\"('b' in 'alphabet')");
 
       // convert and translate not yet implemented
-      //        checkExp("\"CONVERT\"('b' using converstion)");
+      //        checkExp("\"CONVERT\"('b' using conversion)");
       //        checkExp("\"TRANSLATE\"('b' using translation)");
       checkExp("\"OVERLAY\"('a' PLAcing 'b' from 1)");
       checkExp("\"SUBSTRING\"('a' from 1)");
@@ -8455,6 +8455,60 @@ public class SqlValidatorTest extends 
SqlValidatorTestCase {
         "Expression 'EMPNO' is not being grouped");
   }
 
+  /** Tests using case-insensitive matching of user-defined functions. */
+  @Test public void testCaseInsensitiveUdfs() {
+    final SqlTester tester1 = tester
+        .withCaseSensitive(false)
+        .withQuoting(Quoting.BRACKET);
+    final MockSqlOperatorTable operatorTable =
+        new MockSqlOperatorTable(SqlStdOperatorTable.instance());
+    MockSqlOperatorTable.addRamp(operatorTable);
+    tester1.withOperatorTable(operatorTable);
+    final SqlTester tester2 = tester.withQuoting(Quoting.BRACKET);
+    tester2.withOperatorTable(operatorTable);
+
+    // test table function lookup case-insensitively.
+    tester1.checkQuery("select * from dept, lateral table(ramp(dept.deptno))");
+    tester1.checkQuery("select * from dept, lateral table(RAMP(dept.deptno))");
+    tester1.checkQuery("select * from dept, lateral 
table([RAMP](dept.deptno))");
+    tester1.checkQuery("select * from dept, lateral 
table([Ramp](dept.deptno))");
+    // test scalar function lookup case-insensitively.
+    tester1.checkQuery("select myfun(EMPNO) from EMP");
+    tester1.checkQuery("select MYFUN(empno) from emp");
+    tester1.checkQuery("select [MYFUN]([empno]) from [emp]");
+    tester1.checkQuery("select [Myfun]([E].[empno]) from [emp] as e");
+    tester1.checkQuery("select t.[x] from (\n"
+        + "  select [Myfun]([E].[empno]) as x from [emp] as e) as [t]");
+
+    // correlating variable
+    tester1.checkQuery(
+        "select * from emp as [e] where exists (\n"
+            + "select 1 from dept where dept.deptno = myfun([E].deptno))");
+    tester2.checkQueryFails(
+        "select * from emp as [e] where exists (\n"
+            + "select 1 from dept where dept.deptno = ^[myfun]([e].deptno)^)",
+        "No match found for function signature myfun\\(<NUMERIC>\\).*");
+  }
+
+  /** Tests using case-sensitive matching of builtin functions. */
+  @Test public void testCaseSensitiveBuiltinFunction() {
+    final SqlTester tester1 = tester
+        .withCaseSensitive(true)
+        .withQuoting(Quoting.BRACKET);
+    tester1.withOperatorTable(SqlStdOperatorTable.instance());
+
+    tester1.checkQuery("select sum(empno) from EMP group by ename, empno");
+    tester1.checkQuery("select [sum](empno) from EMP group by ename, empno");
+    tester1.checkQuery("select [SUM](empno) from EMP group by ename, empno");
+    tester1.checkQuery("select SUM(empno) from EMP group by ename, empno");
+    tester1.checkQuery("select Sum(empno) from EMP group by ename, empno");
+    tester1.checkQuery("select count(empno) from EMP group by ename, empno");
+    tester1.checkQuery("select [count](empno) from EMP group by ename, empno");
+    tester1.checkQuery("select [COUNT](empno) from EMP group by ename, empno");
+    tester1.checkQuery("select COUNT(empno) from EMP group by ename, empno");
+    tester1.checkQuery("select Count(empno) from EMP group by ename, empno");
+  }
+
   /** Test case for
    * <a href="https://issues.apache.org/jira/browse/CALCITE-319";>[CALCITE-319]
    * Table aliases should follow case-sensitivity policy</a>. */

Reply via email to