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>. */
