You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@calcite.apache.org by jh...@apache.org on 2019/03/30 02:19:39 UTC

[calcite] branch master updated (1018d25 -> 9bce62c)

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

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


    from 1018d25  [CALCITE-2900] RelStructuredTypeFlattener generates wrong types on nested columns (Will Yu)
     new a3c0abd  [CALCITE-1515] In RelBuilder, add functionScan method to create TableFunctionScan (Chunwei Lei)
     new 22fd34f  [CALCITE-2920] In RelBuilder, add antiJoin method (Ruben Quesada Lopez)
     new 136c9c5  [CALCITE-2928] When resolving user-defined functions (UDFs), use the case-sensitivity of the current connection (Danny Chan)
     new 9bce62c  Site: Improve documentation for MySQL-specific JSON operators

The 4 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../calcite/prepare/CalciteCatalogReader.java      |   6 +-
 .../org/apache/calcite/rel/core/RelFactories.java  |  32 +++++
 .../apache/calcite/rel/core/TableFunctionScan.java |   5 +-
 .../rel/logical/LogicalTableFunctionScan.java      |   9 +-
 .../java/org/apache/calcite/runtime/FlatLists.java |   9 +-
 .../org/apache/calcite/runtime/SqlFunctions.java   |   6 +-
 .../java/org/apache/calcite/sql/SemiJoinType.java  |  29 +++-
 .../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 +-
 .../java/org/apache/calcite/tools/RelBuilder.java  | 148 ++++++++++++++++++++-
 .../prepare/LookupOperatorOverloadsTest.java       |  20 ++-
 .../calcite/sql/test/SqlOperatorBaseTest.java      |  22 ++-
 .../org/apache/calcite/test/CalciteAssert.java     |  24 +++-
 .../java/org/apache/calcite/test/LatticeTest.java  |  31 +++--
 .../apache/calcite/test/MockSqlOperatorTable.java  |  23 ++++
 .../org/apache/calcite/test/RelBuilderTest.java    |  76 +++++++++++
 .../org/apache/calcite/test/SqlValidatorTest.java  |  56 +++++++-
 .../test/enumerable/EnumerableCorrelateTest.java   |  65 +++++++++
 site/_docs/algebra.md                              |   4 +
 site/_docs/reference.md                            | 130 ++++++++++++------
 36 files changed, 778 insertions(+), 234 deletions(-)


[calcite] 01/04: [CALCITE-1515] In RelBuilder, add functionScan method to create TableFunctionScan (Chunwei Lei)

Posted by jh...@apache.org.
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 a3c0abd920e65afd47abc2afffd645d6f851ad15
Author: Chunwei Lei <ch...@alibaba-inc.com>
AuthorDate: Mon Mar 11 20:27:15 2019 +0800

    [CALCITE-1515] In RelBuilder, add functionScan method to create TableFunctionScan (Chunwei Lei)
    
    Allow RelBuilder.functionScan() to have 0 relational inputs, rework
    the RexCall produced by the CURSOR function, and add an overload of
    functionScan with "(RexNode...)" arguments. (Julian Hyde)
    
    Close apache/calcite#1102
---
 .../org/apache/calcite/rel/core/RelFactories.java  | 32 ++++++++++
 .../apache/calcite/rel/core/TableFunctionScan.java |  5 +-
 .../rel/logical/LogicalTableFunctionScan.java      |  9 +--
 .../java/org/apache/calcite/tools/RelBuilder.java  | 69 ++++++++++++++++++++++
 .../org/apache/calcite/test/RelBuilderTest.java    | 54 +++++++++++++++++
 site/_docs/algebra.md                              |  3 +
 6 files changed, 166 insertions(+), 6 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
index 5980f3d..681c690 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java
@@ -36,9 +36,11 @@ import org.apache.calcite.rel.logical.LogicalProject;
 import org.apache.calcite.rel.logical.LogicalSnapshot;
 import org.apache.calcite.rel.logical.LogicalSort;
 import org.apache.calcite.rel.logical.LogicalSortExchange;
+import org.apache.calcite.rel.logical.LogicalTableFunctionScan;
 import org.apache.calcite.rel.logical.LogicalTableScan;
 import org.apache.calcite.rel.logical.LogicalUnion;
 import org.apache.calcite.rel.logical.LogicalValues;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rex.RexLiteral;
 import org.apache.calcite.rex.RexNode;
@@ -51,6 +53,7 @@ import org.apache.calcite.util.ImmutableBitSet;
 
 import com.google.common.collect.ImmutableList;
 
+import java.lang.reflect.Type;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -100,6 +103,9 @@ public class RelFactories {
   public static final TableScanFactory DEFAULT_TABLE_SCAN_FACTORY =
       new TableScanFactoryImpl();
 
+  public static final TableFunctionScanFactory
+      DEFAULT_TABLE_FUNCTION_SCAN_FACTORY = new TableFunctionScanFactoryImpl();
+
   public static final SnapshotFactory DEFAULT_SNAPSHOT_FACTORY =
       new SnapshotFactoryImpl();
 
@@ -499,6 +505,32 @@ public class RelFactories {
   }
 
   /**
+   * Can create a {@link TableFunctionScan}
+   * of the appropriate type for a rule's calling convention.
+   */
+  public interface TableFunctionScanFactory {
+    /** Creates a {@link TableFunctionScan}. */
+    RelNode createTableFunctionScan(RelOptCluster cluster,
+        List<RelNode> inputs, RexNode rexCall, Type elementType,
+        Set<RelColumnMapping> columnMappings);
+  }
+
+  /**
+   * Implementation of
+   * {@link TableFunctionScanFactory}
+   * that returns a {@link TableFunctionScan}.
+   */
+  private static class TableFunctionScanFactoryImpl
+      implements TableFunctionScanFactory {
+    @Override public RelNode createTableFunctionScan(RelOptCluster cluster,
+        List<RelNode> inputs, RexNode rexCall, Type elementType,
+        Set<RelColumnMapping> columnMappings) {
+      return LogicalTableFunctionScan.create(cluster, inputs, rexCall,
+          elementType, rexCall.getType(), columnMappings);
+    }
+  }
+
+  /**
    * Can create a {@link Snapshot} of
    * the appropriate type for a rule's calling convention.
    */
diff --git a/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java b/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java
index cdedbd0..0951609 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/TableFunctionScan.java
@@ -64,6 +64,7 @@ public abstract class TableFunctionScan extends AbstractRelNode {
    *
    * @param cluster        Cluster that this relational expression belongs to
    * @param inputs         0 or more relational inputs
+   * @param traitSet       Trait set
    * @param rexCall        Function invocation expression
    * @param elementType    Element type of the collection that will implement
    *                       this table
@@ -72,13 +73,13 @@ public abstract class TableFunctionScan extends AbstractRelNode {
    */
   protected TableFunctionScan(
       RelOptCluster cluster,
-      RelTraitSet traits,
+      RelTraitSet traitSet,
       List<RelNode> inputs,
       RexNode rexCall,
       Type elementType,
       RelDataType rowType,
       Set<RelColumnMapping> columnMappings) {
-    super(cluster, traits);
+    super(cluster, traitSet);
     this.rexCall = rexCall;
     this.elementType = elementType;
     this.rowType = rowType;
diff --git a/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java b/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java
index ec18685..2852652 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalTableFunctionScan.java
@@ -45,11 +45,12 @@ public class LogicalTableFunctionScan extends TableFunctionScan {
    *
    * @param cluster        Cluster that this relational expression belongs to
    * @param inputs         0 or more relational inputs
-   * @param rexCall        function invocation expression
-   * @param elementType    element type of the collection that will implement
+   * @param traitSet       Trait set
+   * @param rexCall        Function invocation expression
+   * @param elementType    Element type of the collection that will implement
    *                       this table
-   * @param rowType        row type produced by function
-   * @param columnMappings column mappings associated with this function
+   * @param rowType        Row type produced by function
+   * @param columnMappings Column mappings associated with this function
    */
   public LogicalTableFunctionScan(
       RelOptCluster cluster,
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 462d6cb..0d2972b 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -44,11 +44,13 @@ import org.apache.calcite.rel.core.RelFactories;
 import org.apache.calcite.rel.core.SemiJoin;
 import org.apache.calcite.rel.core.Snapshot;
 import org.apache.calcite.rel.core.Sort;
+import org.apache.calcite.rel.core.TableFunctionScan;
 import org.apache.calcite.rel.core.TableScan;
 import org.apache.calcite.rel.core.Union;
 import org.apache.calcite.rel.core.Values;
 import org.apache.calcite.rel.logical.LogicalFilter;
 import org.apache.calcite.rel.logical.LogicalProject;
+import org.apache.calcite.rel.metadata.RelColumnMapping;
 import org.apache.calcite.rel.metadata.RelMetadataQuery;
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.rel.type.RelDataTypeFactory;
@@ -72,7 +74,9 @@ import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.type.SqlReturnTypeInference;
 import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.type.TableFunctionReturnTypeInference;
 import org.apache.calcite.sql.validate.SqlValidatorUtil;
 import org.apache.calcite.util.Holder;
 import org.apache.calcite.util.ImmutableBitSet;
@@ -143,6 +147,7 @@ public class RelBuilder {
   private final RelFactories.CorrelateFactory correlateFactory;
   private final RelFactories.ValuesFactory valuesFactory;
   private final RelFactories.TableScanFactory scanFactory;
+  private final RelFactories.TableFunctionScanFactory tableFunctionScanFactory;
   private final RelFactories.SnapshotFactory snapshotFactory;
   private final RelFactories.MatchFactory matchFactory;
   private final Deque<Frame> stack = new ArrayDeque<>();
@@ -193,6 +198,9 @@ public class RelBuilder {
     this.scanFactory =
         Util.first(context.unwrap(RelFactories.TableScanFactory.class),
             RelFactories.DEFAULT_TABLE_SCAN_FACTORY);
+    this.tableFunctionScanFactory =
+        Util.first(context.unwrap(RelFactories.TableFunctionScanFactory.class),
+            RelFactories.DEFAULT_TABLE_FUNCTION_SCAN_FACTORY);
     this.snapshotFactory =
         Util.first(context.unwrap(RelFactories.SnapshotFactory.class),
             RelFactories.DEFAULT_SNAPSHOT_FACTORY);
@@ -1045,6 +1053,67 @@ public class RelBuilder {
     return this;
   }
 
+
+  /**
+   * Gets column mappings of the operator.
+   *
+   * @param op operator instance
+   * @return column mappings associated with this function
+   */
+  private Set<RelColumnMapping> getColumnMappings(SqlOperator op) {
+    SqlReturnTypeInference inference = op.getReturnTypeInference();
+    if (inference instanceof TableFunctionReturnTypeInference) {
+      return ((TableFunctionReturnTypeInference) inference).getColumnMappings();
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   * Creates a RexCall to the {@code CURSOR} function by ordinal.
+   *
+   * @param inputCount Number of inputs
+   * @param ordinal The reference to the relational input
+   * @return RexCall to CURSOR function
+   */
+  public RexNode cursor(int inputCount, int ordinal) {
+    if (inputCount <= ordinal || ordinal < 0) {
+      throw new IllegalArgumentException("bad input count or ordinal");
+    }
+    // Refer to the "ordinal"th input as if it were a field
+    // (because that's how things are laid out inside a TableFunctionScan)
+    final RelNode input = peek(inputCount, ordinal);
+    return call(SqlStdOperatorTable.CURSOR,
+        getRexBuilder().makeInputRef(input.getRowType(), ordinal));
+  }
+
+  /** Creates a {@link TableFunctionScan}. */
+  public RelBuilder functionScan(SqlOperator operator,
+      int inputCount, RexNode... operands) {
+    return functionScan(operator, inputCount, ImmutableList.copyOf(operands));
+  }
+
+  /** Creates a {@link TableFunctionScan}. */
+  public RelBuilder functionScan(SqlOperator operator,
+      int inputCount, Iterable<? extends RexNode> operands) {
+    if (inputCount < 0 || inputCount > stack.size()) {
+      throw new IllegalArgumentException("bad input count");
+    }
+
+    // Gets inputs.
+    final List<RelNode> inputs = new LinkedList<>();
+    for (int i = 0; i < inputCount; i++) {
+      inputs.add(0, build());
+    }
+
+    final RexNode call = call(operator, ImmutableList.copyOf(operands));
+    final RelNode functionScan =
+        tableFunctionScanFactory.createTableFunctionScan(cluster, inputs,
+            call, null, getColumnMappings(operator));
+    push(functionScan);
+    return this;
+  }
+
   /** Creates a {@link Filter} of an array of
    * predicates.
    *
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 3ec2859..75feb03 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -43,6 +43,7 @@ import org.apache.calcite.schema.SchemaPlus;
 import org.apache.calcite.schema.impl.ViewTable;
 import org.apache.calcite.schema.impl.ViewTableMacro;
 import org.apache.calcite.sql.SqlMatchRecognize;
+import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
 import org.apache.calcite.sql.parser.SqlParser;
 import org.apache.calcite.sql.type.SqlTypeName;
@@ -75,12 +76,14 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Locale;
+import java.util.NoSuchElementException;
 import java.util.TreeSet;
 
 import static org.apache.calcite.test.Matchers.hasTree;
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -298,6 +301,57 @@ public class RelBuilderTest {
     assertThat(root, hasTree(expected));
   }
 
+  @Test public void testTableFunctionScan() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM TABLE(
+    //       DEDUP(CURSOR(select * from emp),
+    //             CURSOR(select * from DEPT), 'NAME'))
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final SqlOperator dedupFunction =
+        new MockSqlOperatorTable.DedupFunction();
+    RelNode root = builder.scan("EMP")
+        .scan("DEPT")
+        .functionScan(dedupFunction, 2, builder.cursor(2, 0),
+            builder.cursor(2, 1))
+        .build();
+    final String expected = "LogicalTableFunctionScan("
+        + "invocation=[DEDUP(CURSOR($0), CURSOR($1))], "
+        + "rowType=[RecordType(VARCHAR(1024) NAME)])\n"
+        + "  LogicalTableScan(table=[[scott, EMP]])\n"
+        + "  LogicalTableScan(table=[[scott, DEPT]])\n";
+    assertThat(root, hasTree(expected));
+
+    // Make sure that the builder's stack is empty.
+    try {
+      RelNode node = builder.build();
+      fail("expected error, got " + node);
+    } catch (NoSuchElementException e) {
+      assertNull(e.getMessage());
+    }
+  }
+
+  @Test public void testTableFunctionScanZeroInputs() {
+    // Equivalent SQL:
+    //   SELECT *
+    //   FROM TABLE(RAMP(3))
+    final RelBuilder builder = RelBuilder.create(config().build());
+    final SqlOperator rampFunction = new MockSqlOperatorTable.RampFunction();
+    RelNode root = builder.functionScan(rampFunction, 0, builder.literal(3))
+        .build();
+    final String expected = "LogicalTableFunctionScan(invocation=[RAMP(3)], "
+        + "rowType=[RecordType(INTEGER I)])\n";
+    assertThat(root, hasTree(expected));
+
+    // Make sure that the builder's stack is empty.
+    try {
+      RelNode node = builder.build();
+      fail("expected error, got " + node);
+    } catch (NoSuchElementException e) {
+      assertNull(e.getMessage());
+    }
+  }
+
   @Test public void testJoinTemporalTable() {
     // Equivalent SQL:
     //   SELECT *
diff --git a/site/_docs/algebra.md b/site/_docs/algebra.md
index fc51905..a68bb89 100644
--- a/site/_docs/algebra.md
+++ b/site/_docs/algebra.md
@@ -259,6 +259,7 @@ return the `RelBuilder`.
 | Method              | Description
 |:------------------- |:-----------
 | `scan(tableName)` | Creates a [TableScan]({{ site.apiRoot }}/org/apache/calcite/rel/core/TableScan.html).
+| `functionScan(operator, n, expr...)`<br/>`functionScan(operator, n, exprList)` | Creates a [TableFunctionScan]({{ site.apiRoot }}/org/apache/calcite/rel/core/TableFunctionScan.html) of the `n` most recent relational expressions.
 | `values(fieldNames, value...)`<br/>`values(rowType, tupleList)` | Creates a [Values]({{ site.apiRoot }}/org/apache/calcite/rel/core/Values.html).
 | `filter(expr...)`<br/>`filter(exprList)` | Creates a [Filter]({{ site.apiRoot }}/org/apache/calcite/rel/core/Filter.html) over the AND of the given predicates.
 | `project(expr...)`<br/>`project(exprList [, fieldNames])` | Creates a [Project]({{ site.apiRoot }}/org/apache/calcite/rel/core/Project.html). To override the default name, wrap expressions using `alias`, or specify the `fieldNames` argument.
@@ -304,6 +305,7 @@ Argument types:
 * `subsets` Map whose key is String, value is a sorted set of String
 * `distribution` [RelDistribution]({{ site.apiRoot }}/org/apache/calcite/rel/RelDistribution.html)
 * `collation` [RelCollation]({{ site.apiRoot }}/org/apache/calcite/rel/RelCollation.html)
+* `operator` [SqlOperator]({{ site.apiRoot }}/org/apache/calcite/sql/SqlOperator.html)
 
 The builder methods perform various optimizations, including:
 
@@ -364,6 +366,7 @@ added to the stack.
 | `desc(expr)` | Changes sort direction to descending (only valid as an argument to `sort` or `sortLimit`)
 | `nullsFirst(expr)` | Changes sort order to nulls first (only valid as an argument to `sort` or `sortLimit`)
 | `nullsLast(expr)` | Changes sort order to nulls last (only valid as an argument to `sort` or `sortLimit`)
+| `cursor(n, input)` | Reference to `input`th (0-based) relational input of a `TableFunctionScan` with `n` inputs (see `functionScan`)
 
 #### Pattern methods
 


[calcite] 02/04: [CALCITE-2920] In RelBuilder, add antiJoin method (Ruben Quesada Lopez)

Posted by jh...@apache.org.
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 22fd34f892a2232d393bd5906f8bb1f21f1dff50
Author: rubenada <ru...@gmail.com>
AuthorDate: Fri Mar 15 16:56:54 2019 +0100

    [CALCITE-2920] In RelBuilder, add antiJoin method (Ruben Quesada Lopez)
    
    There is no AntiJoin relational operator (for now), so generate a
    Correlate instead.
    
    Close apache/calcite#1110
---
 .../java/org/apache/calcite/runtime/FlatLists.java |  9 ++-
 .../java/org/apache/calcite/sql/SemiJoinType.java  | 29 ++++++--
 .../java/org/apache/calcite/tools/RelBuilder.java  | 79 +++++++++++++++++++++-
 .../org/apache/calcite/test/CalciteAssert.java     | 24 ++++++-
 .../java/org/apache/calcite/test/LatticeTest.java  | 31 ++++-----
 .../org/apache/calcite/test/RelBuilderTest.java    | 22 ++++++
 .../test/enumerable/EnumerableCorrelateTest.java   | 65 ++++++++++++++++++
 site/_docs/algebra.md                              |  1 +
 8 files changed, 231 insertions(+), 29 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
index a2418f0..a6ea656 100644
--- a/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
+++ b/core/src/main/java/org/apache/calcite/runtime/FlatLists.java
@@ -263,7 +263,14 @@ public class FlatLists {
   /** Returns a map that consists of a given map plus an (key, value),
    * guaranteed to be an {@link ImmutableMap}. */
   public static <K, V> ImmutableMap<K, V> append(Map<K, V> map, K k, V v) {
-    return ImmutableMap.<K, V>builder().putAll(map).put(k, v).build();
+    final ImmutableMap.Builder<K, V> builder = ImmutableMap.builder();
+    builder.put(k, v);
+    map.forEach((k2, v2) -> {
+      if (!k.equals(k2)) {
+        builder.put(k2, v2);
+      }
+    });
+    return builder.build();
   }
 
   /** Base class for flat lists.
diff --git a/core/src/main/java/org/apache/calcite/sql/SemiJoinType.java b/core/src/main/java/org/apache/calcite/sql/SemiJoinType.java
index 473e812..2569d76 100644
--- a/core/src/main/java/org/apache/calcite/sql/SemiJoinType.java
+++ b/core/src/main/java/org/apache/calcite/sql/SemiJoinType.java
@@ -28,25 +28,40 @@ import java.util.Locale;
  */
 public enum SemiJoinType {
   /**
-   * Inner join
+   * Inner join.
    */
   INNER,
 
   /**
-   * Left-outer join
+   * Left-outer join.
    */
   LEFT,
 
   /**
-   * Semi-join
-   * <p>Similar to from A ... where a in (select b from B ...)</p>
+   * Semi-join.
+   *
+   * <p>For example, {@code EMP semi-join DEPT} finds all {@code EMP} records
+   * that have a corresponding {@code DEPT} record:
+   *
+   * <blockquote><pre>
+   * SELECT * FROM EMP
+   * WHERE EXISTS (SELECT 1 FROM DEPT
+   *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+   * </blockquote>
    */
   SEMI,
 
   /**
-   * Anti-join
-   * <p>Similar to from A ... where a NOT in (select b from B ...)</p>
-   * <p>Note: if B.b is nullable and B has nulls, no rows must be returned</p>
+   * Anti-join.
+   *
+   * <p>For example, {@code EMP anti-join DEPT} finds all {@code EMP} records
+   * that do not have a corresponding {@code DEPT} record:
+   *
+   * <blockquote><pre>
+   * SELECT * FROM EMP
+   * WHERE NOT EXISTS (SELECT 1 FROM DEPT
+   *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+   * </blockquote>
    */
   ANTI;
 
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 0d2972b..68492c9 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -1788,7 +1788,23 @@ public class RelBuilder {
     return join(joinType, conditions);
   }
 
-  /** Creates a {@link SemiJoin}. */
+  /** Creates a {@link SemiJoin}.
+   *
+   * <p>A semi-join is a form of join that combines two relational expressions
+   * according to some condition, and outputs only rows from the left input for
+   * which at least one row from the right input matches. It only outputs
+   * columns from the left input, and ignores duplicates on the right.
+   *
+   * <p>For example, {@code EMP semi-join DEPT} finds all {@code EMP} records
+   * that do not have a corresponding {@code DEPT} record, similar to the
+   * following SQL:
+   *
+   * <blockquote><pre>
+   * SELECT * FROM EMP
+   * WHERE EXISTS (SELECT 1 FROM DEPT
+   *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+   * </blockquote>
+   */
   public RelBuilder semiJoin(Iterable<? extends RexNode> conditions) {
     final Frame right = stack.pop();
     final RelNode semiJoin =
@@ -1797,11 +1813,70 @@ public class RelBuilder {
     return this;
   }
 
-  /** Creates a {@link SemiJoin}. */
+  /** Creates a {@link SemiJoin}.
+   *
+   * @see #semiJoin(Iterable) */
   public RelBuilder semiJoin(RexNode... conditions) {
     return semiJoin(ImmutableList.copyOf(conditions));
   }
 
+  /** Creates an anti-join.
+   *
+   * <p>An anti-join is a form of join that combines two relational expressions
+   * according to some condition, but outputs only rows from the left input
+   * for which no rows from the right input match.
+   *
+   * <p>For example, {@code EMP anti-join DEPT} finds all {@code EMP} records
+   * that do not have a corresponding {@code DEPT} record, similar to the
+   * following SQL:
+   *
+   * <blockquote><pre>
+   * SELECT * FROM EMP
+   * WHERE NOT EXISTS (SELECT 1 FROM DEPT
+   *     WHERE DEPT.DEPTNO = EMP.DEPTNO)</pre>
+   * </blockquote>
+   */
+  public RelBuilder antiJoin(Iterable<? extends RexNode> conditions) {
+    // There is currently no "AntiJoin" relational expression, so we
+    // simulate it using a LogicalCorrelate with SemiJoinType.ANTI.
+    final RexBuilder rexBuilder = getRexBuilder();
+    final RelNode right = build();
+    final RelNode left = peek();
+    final int leftFieldCount = left.getRowType().getFieldCount();
+    final CorrelationId correlationId = cluster.createCorrel();
+    final RexNode corrVar =
+        rexBuilder.makeCorrel(left.getRowType(), correlationId);
+    final ImmutableBitSet.Builder requiredColumns = ImmutableBitSet.builder();
+
+    // Replace all references of left input with FieldAccess(corrVar, field)
+    final RexNode condition = and(conditions).accept(new RexShuttle() {
+      @Override public RexNode visitInputRef(RexInputRef input) {
+        final int field = input.getIndex();
+        if (field >= leftFieldCount) {
+          return rexBuilder.makeInputRef(input.getType(), input.getIndex()
+              - leftFieldCount);
+        }
+        requiredColumns.set(field);
+        return rexBuilder.makeFieldAccess(corrVar, field);
+      }
+    });
+
+    final RelNode right2 = push(right).filter(condition).build();
+
+    final RelNode antiJoin =
+        correlateFactory.createCorrelate(left, right2, correlationId,
+            requiredColumns.build(), SemiJoinType.ANTI);
+    replaceTop(antiJoin);
+    return this;
+  }
+
+  /** Creates an anti-join.
+   *
+   * @see #antiJoin(Iterable) */
+  public RelBuilder antiJoin(RexNode... conditions) {
+    return antiJoin(ImmutableList.copyOf(conditions));
+  }
+
   /** Assigns a table alias to the top entry on the stack. */
   public RelBuilder as(final String alias) {
     final Frame pair = stack.pop();
diff --git a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
index 26e29f2..943b2fd 100644
--- a/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
+++ b/core/src/test/java/org/apache/calcite/test/CalciteAssert.java
@@ -22,6 +22,7 @@ import org.apache.calcite.adapter.java.ReflectiveSchema;
 import org.apache.calcite.adapter.jdbc.JdbcSchema;
 import org.apache.calcite.avatica.ConnectionProperty;
 import org.apache.calcite.avatica.util.DateTimeUtils;
+import org.apache.calcite.config.CalciteConnectionConfigImpl;
 import org.apache.calcite.config.CalciteConnectionProperty;
 import org.apache.calcite.config.CalciteSystemProperty;
 import org.apache.calcite.config.Lex;
@@ -31,6 +32,7 @@ import org.apache.calcite.jdbc.CalcitePrepare;
 import org.apache.calcite.jdbc.CalciteSchema;
 import org.apache.calcite.materialize.Lattice;
 import org.apache.calcite.model.ModelHandler;
+import org.apache.calcite.plan.Contexts;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.RelNode;
 import org.apache.calcite.runtime.CalciteException;
@@ -47,6 +49,7 @@ import org.apache.calcite.schema.impl.ViewTableMacro;
 import org.apache.calcite.sql.validate.SqlConformanceEnum;
 import org.apache.calcite.sql.validate.SqlValidatorException;
 import org.apache.calcite.tools.FrameworkConfig;
+import org.apache.calcite.tools.Frameworks;
 import org.apache.calcite.tools.RelBuilder;
 import org.apache.calcite.util.Closer;
 import org.apache.calcite.util.Holder;
@@ -1368,7 +1371,7 @@ public class CalciteAssert {
       try (Connection c = createConnection()) {
         f.accept(c);
       } catch (SQLException e) {
-        TestUtil.rethrow(e);
+        throw TestUtil.rethrow(e);
       }
       return this;
     }
@@ -1570,7 +1573,7 @@ public class CalciteAssert {
       if (plan != null) {
         return;
       }
-      addHook(Hook.JAVA_PLAN, (Consumer<String>) a0 -> plan = a0);
+      addHook(Hook.JAVA_PLAN, this::setPlan);
       withConnection(connection -> {
         assertQuery(connection, sql, limit, materializationsEnabled,
             hooks, null, checkUpdate, null);
@@ -1578,6 +1581,10 @@ public class CalciteAssert {
       });
     }
 
+    private void setPlan(String plan) {
+      this.plan = plan;
+    }
+
     /** Runs the query and applies a checker to the generated third-party
      * queries. The checker should throw to fail the test if it does not see
      * what it wants. This method can be used to check whether a particular
@@ -1655,10 +1662,21 @@ public class CalciteAssert {
       return withHook(Hook.STRING_TO_QUERY,
           (Consumer<Pair<FrameworkConfig, Holder<CalcitePrepare.Query>>>)
           pair -> {
-            final RelBuilder b = RelBuilder.create(pair.left);
+            final FrameworkConfig config = forceDecorrelate(pair.left);
+            final RelBuilder b = RelBuilder.create(config);
             pair.right.set(CalcitePrepare.Query.of(relFn.apply(b)));
           });
     }
+
+    /** Creates a {@link FrameworkConfig} that does not decorrelate. */
+    private FrameworkConfig forceDecorrelate(FrameworkConfig config) {
+      return Frameworks.newConfigBuilder(config)
+          .context(
+              Contexts.of(new CalciteConnectionConfigImpl(new Properties())
+                  .set(CalciteConnectionProperty.FORCE_DECORRELATE,
+                      Boolean.toString(false))))
+          .build();
+    }
   }
 
   /** Fluent interface for building a metadata query to be tested. */
diff --git a/core/src/test/java/org/apache/calcite/test/LatticeTest.java b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
index ca7cacd..b4cbcba 100644
--- a/core/src/test/java/org/apache/calcite/test/LatticeTest.java
+++ b/core/src/test/java/org/apache/calcite/test/LatticeTest.java
@@ -21,11 +21,11 @@ import org.apache.calcite.materialize.Lattice;
 import org.apache.calcite.materialize.Lattices;
 import org.apache.calcite.materialize.MaterializationService;
 import org.apache.calcite.plan.RelOptPlanner;
+import org.apache.calcite.plan.RelOptRule;
 import org.apache.calcite.plan.RelOptUtil;
 import org.apache.calcite.rel.rules.AbstractMaterializedViewRule;
 import org.apache.calcite.runtime.Hook;
 import org.apache.calcite.schema.SchemaPlus;
-import org.apache.calcite.test.CalciteAssert.AssertQuery;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.TestUtil;
 
@@ -502,35 +502,34 @@ public class LatticeTest {
 
   private void checkTileAlgorithm(String statisticProvider,
       String expectedExplain) {
+    final RelOptRule[] rules = {
+        AbstractMaterializedViewRule.INSTANCE_PROJECT_FILTER,
+        AbstractMaterializedViewRule.INSTANCE_FILTER,
+        AbstractMaterializedViewRule.INSTANCE_PROJECT_JOIN,
+        AbstractMaterializedViewRule.INSTANCE_JOIN,
+        AbstractMaterializedViewRule.INSTANCE_PROJECT_AGGREGATE,
+        AbstractMaterializedViewRule.INSTANCE_AGGREGATE
+    };
     MaterializationService.setThreadLocal();
     MaterializationService.instance().clear();
-    AssertQuery that = foodmartLatticeModel(statisticProvider)
+    foodmartLatticeModel(statisticProvider)
         .query("select distinct t.\"the_year\", t.\"quarter\"\n"
             + "from \"foodmart\".\"sales_fact_1997\" as s\n"
             + "join \"foodmart\".\"time_by_day\" as t using (\"time_id\")\n")
-        .enableMaterializations(true);
+        .enableMaterializations(true)
 
     // Disable materialization rules from this test. For some reason, there is
     // a weird interaction between these rules and the lattice rewriting that
     // produces non-deterministic rewriting (even when only lattices are present).
     // For more context, see
     // <a href="https://issues.apache.org/jira/browse/CALCITE-2953">[CALCITE-2953]</a>.
-    that.withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner -> {
-      ImmutableList
-          .of(
-              AbstractMaterializedViewRule.INSTANCE_PROJECT_FILTER,
-              AbstractMaterializedViewRule.INSTANCE_FILTER,
-              AbstractMaterializedViewRule.INSTANCE_PROJECT_JOIN,
-              AbstractMaterializedViewRule.INSTANCE_JOIN,
-              AbstractMaterializedViewRule.INSTANCE_PROJECT_AGGREGATE,
-              AbstractMaterializedViewRule.INSTANCE_AGGREGATE)
-          .forEach(planner::removeRule);
-    });
+        .withHook(Hook.PLANNER, (Consumer<RelOptPlanner>) planner ->
+            Arrays.asList(rules).forEach(planner::removeRule))
 
     // disable for MySQL; times out running star-join query
     // disable for H2; it thinks our generated SQL has invalid syntax
-    that.enable(CalciteAssert.DB != CalciteAssert.DatabaseInstance.MYSQL
-        && CalciteAssert.DB != CalciteAssert.DatabaseInstance.H2)
+        .enable(CalciteAssert.DB != CalciteAssert.DatabaseInstance.MYSQL
+            && CalciteAssert.DB != CalciteAssert.DatabaseInstance.H2)
         .explainContains(expectedExplain)
         .returnsUnordered("the_year=1997; quarter=Q1",
             "the_year=1997; quarter=Q2",
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 75feb03..dc0240f 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -1501,6 +1501,28 @@ public class RelBuilderTest {
     assertThat(root, hasTree(expected));
   }
 
+  @Test public void testAntiJoin() {
+    // Equivalent SQL:
+    //   SELECT * FROM dept d
+    //   WHERE NOT EXISTS (SELECT 1 FROM emp e WHERE e.deptno = d.deptno)
+    final RelBuilder builder = RelBuilder.create(config().build());
+    RelNode root = builder
+        .scan("DEPT")
+        .scan("EMP")
+        .antiJoin(
+            builder.equals(
+                builder.field(2, 0, "DEPTNO"),
+                builder.field(2, 1, "DEPTNO")))
+        .build();
+    // AntiJoin implemented as LogicalCorrelate with joinType=anti
+    final String expected = ""
+        + "LogicalCorrelate(correlation=[$cor0], joinType=[anti], requiredColumns=[{0}])\n"
+        + "  LogicalTableScan(table=[[scott, DEPT]])\n"
+        + "  LogicalFilter(condition=[=($cor0.DEPTNO, $7)])\n"
+        + "    LogicalTableScan(table=[[scott, EMP]])\n";
+    assertThat(root, hasTree(expected));
+  }
+
   @Test public void testAlias() {
     // Equivalent SQL:
     //   SELECT *
diff --git a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java
index 9e3793a..451d95d 100644
--- a/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java
+++ b/core/src/test/java/org/apache/calcite/test/enumerable/EnumerableCorrelateTest.java
@@ -133,6 +133,71 @@ public class EnumerableCorrelateTest {
             "empid=200");
   }
 
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-2920">[CALCITE-2920]
+   * RelBuilder: new method to create an anti-join</a>. */
+  @Test public void antiJoinCorrelate() {
+    tester(false, new JdbcTest.HrSchema())
+        .query("?")
+        .withRel(
+            // Retrieve departments without employees. Equivalent SQL:
+            //   SELECT d.deptno, d.name FROM depts d
+            //   WHERE NOT EXISTS (SELECT 1 FROM emps e WHERE e.deptno = d.deptno)
+            builder -> builder
+                .scan("s", "depts").as("d")
+                .scan("s", "emps").as("e")
+                .antiJoin(
+                    builder.equals(
+                        builder.field(2, "d", "deptno"),
+                        builder.field(2, "e", "deptno")))
+                    .project(
+                        builder.field("deptno"),
+                        builder.field("name"))
+                    .build())
+        .returnsUnordered(
+            "deptno=30; name=Marketing",
+            "deptno=40; name=HR");
+  }
+
+  /** Test case for
+   * <a href="https://issues.apache.org/jira/browse/CALCITE-2920">[CALCITE-2920]
+   * RelBuilder: new method to create an antijoin</a> */
+  @Test public void antiJoinCorrelateWithNullValues() {
+    final Integer salesDeptNo = 10;
+    tester(false, new JdbcTest.HrSchema())
+        .query("?")
+        .withRel(
+            // Retrieve employees from any department other than Sales (deptno 10) whose
+            // commission is different from any Sales employee commission. Since there
+            // is a Sales employee with null commission, the goal is to validate that antiJoin
+            // behaves as a NOT EXISTS (and returns results), and not as a NOT IN (which would
+            // not return any result due to its null handling). Equivalent SQL:
+            //   SELECT empOther.empid, empOther.name FROM emps empOther
+            //   WHERE empOther.deptno <> 10 AND NOT EXISTS
+            //     (SELECT 1 FROM emps empSales
+            //      WHERE empSales.deptno = 10 AND empSales.commission = empOther.commission)
+            builder -> builder
+                .scan("s", "emps").as("empOther")
+                .filter(
+                    builder.notEquals(
+                        builder.field("empOther", "deptno"),
+                        builder.literal(salesDeptNo)))
+                .scan("s", "emps").as("empSales")
+                .filter(
+                    builder.equals(
+                        builder.field("empSales", "deptno"),
+                        builder.literal(salesDeptNo)))
+                .antiJoin(
+                    builder.equals(
+                        builder.field(2, "empOther", "commission"),
+                        builder.field(2, "empSales", "commission")))
+                .project(
+                    builder.field("empid"),
+                    builder.field("name"))
+                .build())
+        .returnsUnordered("empid=200; name=Eric");
+  }
+
   private CalciteAssert.AssertThat tester(boolean forceDecorrelate,
       Object schema) {
     return CalciteAssert.that()
diff --git a/site/_docs/algebra.md b/site/_docs/algebra.md
index a68bb89..8802f81 100644
--- a/site/_docs/algebra.md
+++ b/site/_docs/algebra.md
@@ -275,6 +275,7 @@ return the `RelBuilder`.
 | `sortExchange(distribution, collation)` | Creates a [SortExchange]({{ site.apiRoot }}/org/apache/calcite/rel/core/SortExchange.html).
 | `join(joinType, expr...)`<br/>`join(joinType, exprList)`<br/>`join(joinType, fieldName...)` | Creates a [Join]({{ site.apiRoot }}/org/apache/calcite/rel/core/Join.html) of the two most recent relational expressions.<br/><br/>The first form joins on a boolean expression (multiple conditions are combined using AND).<br/><br/>The last form joins on named fields; each side must have a field of each name.
 | `semiJoin(expr)` | Creates a [SemiJoin]({{ site.apiRoot }}/org/apache/calcite/rel/core/SemiJoin.html) of the two most recent relational expressions.
+| `antiJoin(expr)` | Creates an AntiJoin of the two most recent relational expressions.
 | `union(all [, n])` | Creates a [Union]({{ site.apiRoot }}/org/apache/calcite/rel/core/Union.html) of the `n` (default two) most recent relational expressions.
 | `intersect(all [, n])` | Creates an [Intersect]({{ site.apiRoot }}/org/apache/calcite/rel/core/Intersect.html) of the `n` (default two) most recent relational expressions.
 | `minus(all)` | Creates a [Minus]({{ site.apiRoot }}/org/apache/calcite/rel/core/Minus.html) of the two most recent relational expressions.


[calcite] 04/04: Site: Improve documentation for MySQL-specific JSON operators

Posted by jh...@apache.org.
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 9bce62c6b6abd13d551d0eed1f1e14b024bbb2ae
Author: Hongze Zhang <ho...@apache.org>
AuthorDate: Mon Mar 11 15:46:08 2019 +0800

    Site: Improve documentation for MySQL-specific JSON operators
    
    Also, simplify a bit on type handling logic for JSON_TYPE function.
---
 .../org/apache/calcite/runtime/SqlFunctions.java   |   6 +-
 site/_docs/reference.md                            | 130 ++++++++++++++-------
 2 files changed, 90 insertions(+), 46 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index c508ea0..2ab9a3a 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -2756,13 +2756,9 @@ public class SqlFunctions {
       } else if (o == null) {
         result = "NULL";
       } else {
-        result = "unknown";
-      }
-      if (result.equals("unknown")) {
         throw RESOURCE.invalidInputForJsonType(o.toString()).ex();
-      } else {
-        return result;
       }
+      return result;
     } catch (Exception ex) {
       throw RESOURCE.invalidInputForJsonType(o.toString()).ex();
     }
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index a322b48..c1f6861 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -1294,7 +1294,7 @@ Not implemented:
 
 | Operator syntax | Description
 |:--------------- |:-----------
-| ELEMENT(value)  | Returns the sole element of a array or multiset; null if the collection is empty; throws if it has more than one element.
+| ELEMENT(value)  | Returns the sole element of an array or multiset; null if the collection is empty; throws if it has more than one element.
 | CARDINALITY(value) | Returns the number of elements in an array or multiset.
 | value MEMBER OF multiset | Returns whether the *value* is a member of *multiset*.
 | multiset IS A SET | Whether *multiset* is a set (has no duplicates).
@@ -1851,7 +1851,7 @@ The following functions modify 3D geometries.
 Not implemented:
 
 * ST_AddZ(geom, zToAdd) Adds *zToAdd* to the z-coordinate of *geom*
-* ST_Interpolate3DLine(geom) Returns *geom* with a interpolation of z values, or null if it is not a line-string or MULTILINESTRING
+* ST_Interpolate3DLine(geom) Returns *geom* with an interpolation of z values, or null if it is not a line-string or MULTILINESTRING
 * ST_MultiplyZ(geom, zFactor) Returns *geom* with its z-values multiplied by *zFactor*
 * ST_Reverse3DLine(geom [, sortOrder ]) Potentially reverses *geom* according to the z-values of its first and last coordinates
 * ST_UpdateZ(geom, newZ [, updateCondition ]) Updates the z-values of *geom*
@@ -1940,24 +1940,32 @@ Not implemented:
 
 ### JSON Functions
 
+In the following:
+
+* *jsonValue* is a character string containing a JSON value;
+* *path* is a character string containing a JSON path expression; mode flag `strict` or `lax` should be specified in the beginning of *path*.
+
 #### Query Functions
 
 | Operator syntax        | Description
 |:---------------------- |:-----------
-| JSON_EXISTS(value, path [ { TRUE &#124; FALSE &#124; UNKNOWN &#124; ERROR ) ON ERROR } ) | Test whether a JSON **value** satisfies a search criterion described using JSON path expression **path**
-| JSON_VALUE(value, path [ RETURNING type ] [ { ERROR &#124; NULL &#124; DEFAULT expr } ON EMPTY ] [ { ERROR &#124; NULL &#124; DEFAULT expr } ON ERROR ] ) | Extract an SQL scalar from a JSON **value** using JSON path expression **path**
-| JSON_QUERY(value, path [ { WITHOUT [ ARRAY ] &#124; WITH [ CONDITIONAL &#124; UNCONDITIONAL ] [ ARRAY ] } WRAPPER ] [ { ERROR &#124; NULL &#124; EMPTY ARRAY &#124; EMPTY OBJECT } ON EMPTY ] [ { ERROR &#124; NULL &#124; EMPTY ARRAY &#124; EMPTY OBJECT } ON ERROR ] ) | Extract an JSON object or an JSON array from a JSON **value** using JSON path expression **path**
+| JSON_EXISTS(jsonValue, path [ { TRUE &#124; FALSE &#124; UNKNOWN &#124; ERROR ) ON ERROR } ) | Whether a *jsonValue* satisfies a search criterion described using JSON path expression *path*
+| JSON_VALUE(jsonValue, path [ RETURNING type ] [ { ERROR &#124; NULL &#124; DEFAULT expr } ON EMPTY ] [ { ERROR &#124; NULL &#124; DEFAULT expr } ON ERROR ] ) | Extract an SQL scalar from a *jsonValue* using JSON path expression *path*
+| JSON_QUERY(jsonValue, path [ { WITHOUT [ ARRAY ] &#124; WITH [ CONDITIONAL &#124; UNCONDITIONAL ] [ ARRAY ] } WRAPPER ] [ { ERROR &#124; NULL &#124; EMPTY ARRAY &#124; EMPTY OBJECT } ON EMPTY ] [ { ERROR &#124; NULL &#124; EMPTY ARRAY &#124; EMPTY OBJECT } ON ERROR ] ) | Extract a JSON object or JSON array from *jsonValue* using the *path* JSON path expression
 
 Note:
 
-* The common structure `value, path` is JSON API common syntax. **value** is a character string type json input, and **path** is a JSON path expression (in character string type too), mode flag **strict** or **lax** should be specified in the beginning of **path**.
-* **ON ERROR** clause, and **ON EMPTY** clause define the fallback behavior of the function when an error is thrown or a null value is about to be returned.
-* **ARRAY WRAPPER** clause defines how to represent JSON array result in JSON_QUERY function. Following is a comparision to demonstrate the difference among different wrapper behaviors.
+* The `ON ERROR` and `ON EMPTY` clauses define the fallback
+  behavior of the function when an error is thrown or a null value
+  is about to be returned.
+* The `ARRAY WRAPPER` clause defines how to represent a JSON array result
+  in `JSON_QUERY` function. The following examples compare the wrapper
+  behaviors.
 
 Example Data:
 
 ```JSON
-{ "a": "[1,2]", "b": [1,2], "c": "hi"}
+{"a": "[1,2]", "b": [1,2], "c": "hi"}
 ```
 
 Comparison:
@@ -1977,42 +1985,73 @@ Not implemented:
 
 | Operator syntax        | Description
 |:---------------------- |:-----------
-| JSON_OBJECT( { [ KEY ] name VALUE value [ FORMAT JSON ] &#124; name : value [ FORMAT JSON ] } * [ { NULL &#124; ABSENT } ON NULL ] ) | Construct json object using a series of key (**name**) value (**value**) pairs
-| JSON_OBJECTAGG( { [ KEY ] name VALUE value [ FORMAT JSON ] &#124; name : value [ FORMAT JSON ] } [ { NULL &#124; ABSENT } ON NULL ] ) | Aggregate function to construct json object using a key (**name**) value (**value**) pair
-| JSON_ARRAY( { value [ FORMAT JSON ] } * [ { NULL &#124; ABSENT } ON NULL ] ) | Construct json array using a series of values (**value**)
-| JSON_ARRAYAGG( value [ FORMAT JSON ] [ ORDER BY orderItem [, orderItem ]* ] [ { NULL &#124; ABSENT } ON NULL ] ) | Aggregate function to construct json array using a value (**value**)
+| JSON_OBJECT( { [ KEY ] name VALUE value [ FORMAT JSON ] &#124; name : value [ FORMAT JSON ] } * [ { NULL &#124; ABSENT } ON NULL ] ) | Construct JSON object using a series of key (*name*) value (*value*) pairs
+| JSON_OBJECTAGG( { [ KEY ] name VALUE value [ FORMAT JSON ] &#124; name : value [ FORMAT JSON ] } [ { NULL &#124; ABSENT } ON NULL ] ) | Aggregate function to construct a JSON object using a key (*name*) value (*value*) pair
+| JSON_ARRAY( { value [ FORMAT JSON ] } * [ { NULL &#124; ABSENT } ON NULL ] ) | Construct a JSON array using a series of values (*value*)
+| JSON_ARRAYAGG( value [ FORMAT JSON ] [ ORDER BY orderItem [, orderItem ]* ] [ { NULL &#124; ABSENT } ON NULL ] ) | Aggregate function to construct a JSON array using a value (*value*)
 
 Note:
 
-* The flag **FORMAT JSON** indicates the value is formatted as JSON character string. When **FORMAT JSON** is used, value should be de-parse from JSON character string to SQL structured value.
-* **ON NULL** clause defines how the JSON output represents null value. The default null behavior of **JSON_OBJECT** and **JSON_OBJECTAGG** is *NULL ON NULL*, and for **JSON_ARRAY** and **JSON_ARRAYAGG** it is *ABSENT ON NULL*.
-* If **ORDER BY** clause is provided, **JSON_ARRAYAGG** will sort the input rows by the specified order before performing aggregation.
+* The flag `FORMAT JSON` indicates the value is formatted as JSON
+  character string. When `FORMAT JSON` is used, the value should be
+  de-parse from JSON character string to a SQL structured value.
+* `ON NULL` clause defines how the JSON output represents null
+  values. The default null behavior of `JSON_OBJECT` and
+  `JSON_OBJECTAGG` is `NULL ON NULL`, and for `JSON_ARRAY` and
+  `JSON_ARRAYAGG` it is `ABSENT ON NULL`.
+* If `ORDER BY` clause is provided, `JSON_ARRAYAGG` sorts the
+  input rows into the specified order before performing aggregation.
 
 #### Comparison Operators
 
-| Operator syntax                                   | Description
-|:------------------------------------------------- |:-----------
-| value IS JSON [ VALUE ]                           | Whether *value* is a json value, *value* is in character string type
-| value IS NOT JSON [ VALUE ]                       | Whether *value* is not a json value, *value* is in character string type
-| value IS JSON SCALAR                              | Whether *value* is a json scalar value, *value* is in character string type
-| value IS NOT JSON SCALAR                          | Whether *value* is not a json scalar value, *value* is in character string type
-| value IS JSON OBJECT                              | Whether *value* is a json object, *value* is in character string type
-| value IS NOT JSON OBJECT                          | Whether *value* is not a json object, *value* is in character string type
-| value IS JSON ARRAY                               | Whether *value* is a json array, *value* is in character string type
-| value IS NOT JSON ARRAY                           | Whether *value* is not a json array, *value* is in character string type
+| Operator syntax                   | Description
+|:--------------------------------- |:-----------
+| jsonValue IS JSON [ VALUE ]       | Whether *jsonValue* is a JSON value
+| jsonValue IS NOT JSON [ VALUE ]   | Whether *jsonValue* is not a JSON value
+| jsonValue IS JSON SCALAR          | Whether *jsonValue* is a JSON scalar value
+| jsonValue IS NOT JSON SCALAR      | Whether *jsonValue* is not a JSON scalar value
+| jsonValue IS JSON OBJECT          | Whether *jsonValue* is a JSON object
+| jsonValue IS NOT JSON OBJECT      | Whether *jsonValue* is not a JSON object
+| jsonValue IS JSON ARRAY           | Whether *jsonValue* is a JSON array
+| jsonValue IS NOT JSON ARRAY       | Whether *jsonValue* is not a JSON array
 
 #### MySQL Specific Operators
 
-| Operator syntax                                   | Description
-|:------------------------------------------------- |:-----------
-| JSON_TYPE(value)                                  | Returns a string indicating the type of a JSON **value**. This can be an object, an array, or a scalar type
-| JSON_DEPTH(value)                                 | Returns a integer indicating the depth of a JSON **value**. This can be an object, an array, or a scalar type
-| JSON_PRETTY(value)                                | Returns a pretty-printing of JSON **value**.
-| JSON_LENGTH(value)                                | Returns a integer indicating the length of a JSON **value**. This can be an object, an array, or a scalar type
+| Operator syntax                   | Description
+|:--------------------------------- |:-----------
+| JSON_TYPE(jsonValue)              | Returns a string value indicating the type of a *jsonValue*
+| JSON_DEPTH(jsonValue)             | Returns an integer value indicating the depth of a *jsonValue*
+| JSON_PRETTY(jsonValue)            | Returns a pretty-printing of *jsonValue*
+| JSON_LENGTH(jsonValue [, path ])  | Returns a integer indicating the length of *jsonValue*
 
-* JSON_TYPE
+Note:
 
-Example SQL:
+* `JSON_TYPE` / `JSON_DEPTH` return null if the argument is null
+* `JSON_TYPE` / `JSON_DEPTH` / `JSON_PRETTY` throw error if the argument is not a valid JSON value
+* `JSON_TYPE` generally returns an upper-case string flag indicating the type of the JSON input. Currently supported supported type flags are:
+  * INTEGER
+  * STRING
+  * FLOAT
+  * DOUBLE
+  * LONG
+  * BOOLEAN
+  * DATE
+  * OBJECT
+  * ARRAY
+  * NULL
+* `JSON_DEPTH` defines a JSON value's depth as follows:
+  * An empty array, empty object, or scalar value has depth 1;
+  * A non-empty array containing only elements of depth 1 or non-empty object containing only member values of depth 1 has depth 2;
+  * Otherwise, a JSON document has depth greater than 2.
+* `JSON_LENGTH` defines a JSON value's length as follows:
+  * A scalar value has length 1;
+  * The length of array or object is the number of elements is contains.
+
+Usage Examples:
+
+##### JSON_TYPE example
+
+SQL
 
 ```SQL
 SELECT JSON_TYPE(v) AS c1
@@ -2023,15 +2062,15 @@ FROM (VALUES ('{"a": [10, true],"b": "[10, true]"}')) AS t(v)
 LIMIT 10;
 ```
 
-Result:
+Result
 
 | c1     | c2    | c3      | c4      |
 | ------ | ----- | ------- | ------- |
 | OBJECT | ARRAY | INTEGER | BOOLEAN |
 
-* JSON_DEPTH
+##### JSON_DEPTH example
 
-Example SQL:
+SQL
 
 ```SQL
 SELECT JSON_DEPTH(v) AS c1
@@ -2042,15 +2081,15 @@ FROM (VALUES ('{"a": [10, true],"b": "[10, true]"}')) AS t(v)
 LIMIT 10;
 ```
 
-Result:
+Result
 
 | c1     | c2    | c3      | c4      |
 | ------ | ----- | ------- | ------- |
 | 3      | 2     | 1       | 1       |
 
-* JSON_LENGTH
+##### JSON_LENGTH example
 
-Example SQL:
+SQL
 
 ```SQL
 SELECT JSON_LENGTH(v) AS c1
@@ -2061,12 +2100,21 @@ FROM (VALUES ('{"a": [10, true]}')) AS t(v)
 LIMIT 10;
 ```
 
-Result:
+Result
 
 | c1     | c2    | c3      | c4      |
 | ------ | ----- | ------- | ------- |
 | 1      | 2     | 1       | 1       |
 
+Not implemented:
+
+* JSON_LENGTH
+* JSON_INSERT
+* JSON_SET
+* JSON_REPLACE
+* JSON_REMOVE
+* JSON_KEYS
+
 ## User-defined functions
 
 Calcite is extensible. You can define each kind of function using user code.


[calcite] 03/04: [CALCITE-2928] When resolving user-defined functions (UDFs), use the case-sensitivity of the current connection (Danny Chan)

Posted by jh...@apache.org.
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 <yu...@alibaba-inc.com>
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>. */