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

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


The following commit(s) were added to refs/heads/main by this push:
     new 82cdcc78db [CALCITE-7251] SEARCH and WINDOW operations should carry 
source position information
82cdcc78db is described below

commit 82cdcc78dbcb8df44827c1a9fe1126ccfffc21ac
Author: Mihai Budiu <[email protected]>
AuthorDate: Mon Oct 27 18:19:33 2025 -0700

    [CALCITE-7251] SEARCH and WINDOW operations should carry source position 
information
    
    Signed-off-by: Mihai Budiu <[email protected]>
---
 .../java/org/apache/calcite/rel/core/Window.java   | 26 +++++++-
 .../apache/calcite/rel/externalize/RelJson.java    | 16 ++++-
 .../apache/calcite/rel/logical/LogicalWindow.java  |  1 +
 .../rel/rules/ProjectWindowTransposeRule.java      |  2 +-
 .../calcite/rel/rules/ReduceExpressionsRule.java   |  3 +-
 .../java/org/apache/calcite/rex/RexBuilder.java    | 35 ++++++++--
 .../main/java/org/apache/calcite/rex/RexCall.java  |  2 +-
 .../java/org/apache/calcite/rex/RexCopier.java     |  2 +-
 .../main/java/org/apache/calcite/rex/RexOver.java  | 34 +++++++++-
 .../java/org/apache/calcite/rex/RexShuttle.java    |  1 +
 .../java/org/apache/calcite/rex/RexSimplify.java   | 43 ++++++++----
 .../main/java/org/apache/calcite/rex/RexUtil.java  | 78 ++++++++++++++++------
 .../apache/calcite/sql2rel/ConvertToChecked.java   |  2 +-
 .../apache/calcite/sql2rel/SqlToRelConverter.java  |  3 +-
 .../java/org/apache/calcite/tools/RelBuilder.java  | 49 +++++++++-----
 .../calcite/rel/externalize/RelJsonTest.java       | 45 +++++++++++++
 16 files changed, 276 insertions(+), 66 deletions(-)

diff --git a/core/src/main/java/org/apache/calcite/rel/core/Window.java 
b/core/src/main/java/org/apache/calcite/rel/core/Window.java
index fd4770db6e..2adf54c4f3 100644
--- a/core/src/main/java/org/apache/calcite/rel/core/Window.java
+++ b/core/src/main/java/org/apache/calcite/rel/core/Window.java
@@ -41,6 +41,7 @@
 import org.apache.calcite.rex.RexWindowBound;
 import org.apache.calcite.rex.RexWindowExclusion;
 import org.apache.calcite.sql.SqlAggFunction;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ImmutableBitSet;
 import org.apache.calcite.util.ImmutableIntList;
 import org.apache.calcite.util.Litmus;
@@ -425,7 +426,7 @@ public RexWinAggCall(
         List<RexNode> operands,
         int ordinal,
         boolean distinct) {
-      this(aggFun, type, operands, ordinal, distinct, false);
+      this(SqlParserPos.ZERO, aggFun, type, operands, ordinal, distinct, 
false);
     }
 
     /**
@@ -436,6 +437,7 @@ public RexWinAggCall(
      * @param operands Operands to call
      * @param ordinal  Ordinal within its partition
      * @param distinct Eliminate duplicates before applying aggregate function
+     * @deprecated Use {@link RexWinAggCall#RexWinAggCall(SqlParserPos, 
SqlAggFunction, RelDataType, List, int, boolean, boolean)}
      */
     public RexWinAggCall(
         SqlAggFunction aggFun,
@@ -444,7 +446,27 @@ public RexWinAggCall(
         int ordinal,
         boolean distinct,
         boolean ignoreNulls) {
-      super(type, aggFun, operands);
+      this(SqlParserPos.ZERO, aggFun, type, operands, ordinal, distinct, 
ignoreNulls);
+    }
+
+    /**
+     * Creates a RexWinAggCall.
+     *
+     * @param aggFun   Aggregate function
+     * @param type     Result type
+     * @param operands Operands to call
+     * @param ordinal  Ordinal within its partition
+     * @param distinct Eliminate duplicates before applying aggregate function
+     */
+    public RexWinAggCall(
+        SqlParserPos pos,
+        SqlAggFunction aggFun,
+        RelDataType type,
+        List<RexNode> operands,
+        int ordinal,
+        boolean distinct,
+        boolean ignoreNulls) {
+      super(pos, type, aggFun, operands);
       this.ordinal = ordinal;
       this.distinct = distinct;
       this.ignoreNulls = ignoreNulls;
diff --git a/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java 
b/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
index 17a34411eb..2409ac6090 100644
--- a/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
+++ b/core/src/main/java/org/apache/calcite/rel/externalize/RelJson.java
@@ -298,6 +298,15 @@ private static RexNode translateInput(RelJson relJson, int 
input,
     throw new RuntimeException("input field " + input + " is out of range");
   }
 
+  public Object toJson(SqlParserPos pos) {
+    final Map<String, @Nullable Object> map = jsonBuilder().map();
+    map.put("line", pos.getLineNum());
+    map.put("column", pos.getColumnNum());
+    map.put("end_line", pos.getEndLineNum());
+    map.put("end_column", pos.getEndColumnNum());
+    return map;
+  }
+
   public Object toJson(RelCollationImpl node) {
     final List<Object> list = new ArrayList<>();
     for (RelFieldCollation fieldCollation : node.getFieldCollations()) {
@@ -455,6 +464,8 @@ public Object toJson(AggregateCall node) {
         || value instanceof String
         || value instanceof Boolean) {
       return value;
+    } else if (value instanceof SqlParserPos) {
+      return toJson((SqlParserPos) value);
     } else if (value instanceof RexNode) {
       return toJson((RexNode) value);
     } else if (value instanceof RexWindow) {
@@ -641,6 +652,9 @@ public Object toJson(RexNode node) {
       if (node instanceof RexCall) {
         final RexCall call = (RexCall) node;
         map = jsonBuilder().map();
+        if (call.getParserPosition() != SqlParserPos.ZERO) {
+          map.put("pos", toJson(call.getParserPosition()));
+        }
         map.put("op", toJson(call.getOperator()));
         final List<@Nullable Object> list = jsonBuilder().list();
         for (RexNode operand : call.getOperands()) {
@@ -797,7 +811,7 @@ public RexNode toRex(RelOptCluster cluster, Object o) {
             exclude = RexWindowExclusion.EXCLUDE_NO_OTHER;
           }
           final boolean distinct = get((Map<String, Object>) map, "distinct");
-          return rexBuilder.makeOver(type, operator, rexOperands, 
partitionKeys,
+          return rexBuilder.makeOver(SqlParserPos.ZERO, type, operator, 
rexOperands, partitionKeys,
               ImmutableList.copyOf(orderKeys),
               requireNonNull(lowerBound, "lowerBound"),
               requireNonNull(upperBound, "upperBound"),
diff --git 
a/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java 
b/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
index 1dcff97b18..d17b8aef85 100644
--- a/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
+++ b/core/src/main/java/org/apache/calcite/rel/logical/LogicalWindow.java
@@ -182,6 +182,7 @@ public static RelNode create(RelOptCluster cluster,
       for (RexOver over : entry.getValue()) {
         final RexWinAggCall aggCall =
             new RexWinAggCall(
+                over.getParserPosition(),
                 over.getAggOperator(),
                 over.getType(),
                 toInputRefs(over.operands),
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
index b2c366f14d..fac0f459ac 100644
--- 
a/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
+++ 
b/core/src/main/java/org/apache/calcite/rel/rules/ProjectWindowTransposeRule.java
@@ -121,7 +121,7 @@ public ProjectWindowTransposeRule(RelBuilderFactory 
relBuilderFactory) {
           boolean[] update = {false};
           final List<RexNode> clonedOperands = visitList(call.operands, 
update);
           if (update[0]) {
-            return new Window.RexWinAggCall(
+            return new Window.RexWinAggCall(call.getParserPosition(),
                 (SqlAggFunction) call.getOperator(), call.getType(),
                 clonedOperands, aggCall.ordinal, aggCall.distinct,
                 aggCall.ignoreNulls);
diff --git 
a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java 
b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
index 605f212a76..8a2e5b09b4 100644
--- a/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
+++ b/core/src/main/java/org/apache/calcite/rel/rules/ReduceExpressionsRule.java
@@ -579,7 +579,8 @@ public WindowReduceExpressionsRule(Class<? extends Window> 
windowClass,
           final List<RexNode> expList = new ArrayList<>(aggCall.getOperands());
           if (reduceExpressions(window, expList, predicates)) {
             aggCall =
-                new Window.RexWinAggCall((SqlAggFunction) 
aggCall.getOperator(),
+                new Window.RexWinAggCall(aggCall.getParserPosition(),
+                    (SqlAggFunction) aggCall.getOperator(),
                     aggCall.type, expList,
                     aggCall.ordinal, aggCall.distinct, aggCall.ignoreNulls);
             reduced = true;
diff --git a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java 
b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
index d5492f0634..b77fe02830 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java
@@ -459,8 +459,30 @@ public RexNode makeOver(
       boolean nullWhenCountZero,
       boolean distinct,
       boolean ignoreNulls) {
-    return makeOver(type, operator, exprs, partitionKeys, orderKeys, 
lowerBound, upperBound,
-        RexWindowExclusion.EXCLUDE_NO_OTHER, rows, allowPartial, 
nullWhenCountZero, distinct,
+    return makeOver(SqlParserPos.ZERO, type, operator, exprs, partitionKeys, 
orderKeys, lowerBound,
+        upperBound, RexWindowExclusion.EXCLUDE_NO_OTHER, rows, allowPartial, 
nullWhenCountZero,
+        distinct, ignoreNulls);
+  }
+
+  /**
+   * Creates a call to a windowed agg.
+   */
+  public RexNode makeOver(
+      RelDataType type,
+      SqlAggFunction operator,
+      List<RexNode> exprs,
+      List<RexNode> partitionKeys,
+      ImmutableList<RexFieldCollation> orderKeys,
+      RexWindowBound lowerBound,
+      RexWindowBound upperBound,
+      RexWindowExclusion exclude,
+      boolean rows,
+      boolean allowPartial,
+      boolean nullWhenCountZero,
+      boolean distinct,
+      boolean ignoreNulls) {
+    return makeOver(SqlParserPos.ZERO, type, operator, exprs, partitionKeys, 
orderKeys,
+        lowerBound, upperBound, exclude, rows, allowPartial, 
nullWhenCountZero, distinct,
         ignoreNulls);
   }
 
@@ -468,6 +490,7 @@ public RexNode makeOver(
    * Creates a call to a windowed agg.
    */
   public RexNode makeOver(
+      SqlParserPos pos,
       RelDataType type,
       SqlAggFunction operator,
       List<RexNode> exprs,
@@ -490,7 +513,7 @@ public RexNode makeOver(
             rows,
             exclude);
     RexNode result =
-        new RexOver(type, operator, exprs, window, distinct, ignoreNulls);
+        new RexOver(pos, type, operator, exprs, window, distinct, ignoreNulls);
 
     // This should be correct but need time to go over test results.
     // Also want to look at combing with section below.
@@ -500,12 +523,12 @@ public RexNode makeOver(
       result =
           makeCall(SqlStdOperatorTable.CASE,
               makeCall(SqlStdOperatorTable.GREATER_THAN,
-                  new RexOver(bigintType, SqlStdOperatorTable.COUNT, exprs,
+                  new RexOver(pos, bigintType, SqlStdOperatorTable.COUNT, 
exprs,
                       window, distinct, ignoreNulls),
                   makeLiteral(BigDecimal.ZERO, bigintType,
                       SqlTypeName.DECIMAL)),
               ensureType(type, // SUM0 is non-nullable, thus need a cast
-                  new RexOver(typeFactory.createTypeWithNullability(type, 
false),
+                  new RexOver(pos, typeFactory.createTypeWithNullability(type, 
false),
                       operator, exprs, window, distinct, ignoreNulls),
                   false),
               makeNullLiteral(type));
@@ -520,7 +543,7 @@ public RexNode makeOver(
               SqlStdOperatorTable.CASE,
               makeCall(
                   SqlStdOperatorTable.GREATER_THAN_OR_EQUAL,
-                  new RexOver(
+                  new RexOver(pos,
                       bigintType,
                       SqlStdOperatorTable.COUNT,
                       ImmutableList.of(),
diff --git a/core/src/main/java/org/apache/calcite/rex/RexCall.java 
b/core/src/main/java/org/apache/calcite/rex/RexCall.java
index ecd3cccb2c..5be1fead88 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexCall.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexCall.java
@@ -61,7 +61,7 @@ public class RexCall extends RexNode {
    * the source position, so the backend can produce runtime error messages
    * pointing to the original source position.
    * For calls that are can never generate runtime failures, this field may
-   * be ZERO.  Note that some optimizations may "lost" position information. */
+   * be ZERO.  Note that some optimizations may "lose" position information. */
   public final SqlParserPos pos;
   public final SqlOperator op;
   public final ImmutableList<RexNode> operands;
diff --git a/core/src/main/java/org/apache/calcite/rex/RexCopier.java 
b/core/src/main/java/org/apache/calcite/rex/RexCopier.java
index 1f5700e482..499d81b7b9 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexCopier.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexCopier.java
@@ -50,7 +50,7 @@ private RelDataType copy(RelDataType type) {
 
   @Override public RexNode visitOver(RexOver over) {
     final boolean[] update = null;
-    return new RexOver(copy(over.getType()), over.getAggOperator(),
+    return new RexOver(over.getParserPosition(), copy(over.getType()), 
over.getAggOperator(),
         visitList(over.getOperands(), update), visitWindow(over.getWindow()),
         over.isDistinct(), over.ignoreNulls());
   }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexOver.java 
b/core/src/main/java/org/apache/calcite/rex/RexOver.java
index 63a263e6b7..0b01838b38 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexOver.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexOver.java
@@ -19,6 +19,7 @@
 import org.apache.calcite.rel.type.RelDataType;
 import org.apache.calcite.sql.SqlAggFunction;
 import org.apache.calcite.sql.SqlWindow;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.util.ControlFlowException;
 import org.apache.calcite.util.Util;
 
@@ -58,6 +59,7 @@ public class RexOver extends RexCall {
    * <li>window = {@link SqlWindow}(ROWS 3 PRECEDING)
    * </ul>
    *
+   * @param pos      Parser position
    * @param type     Result type
    * @param op       Aggregate operator
    * @param operands Operands list
@@ -65,19 +67,49 @@ public class RexOver extends RexCall {
    * @param distinct Aggregate operator is applied on distinct elements
    */
   RexOver(
+      SqlParserPos pos,
       RelDataType type,
       SqlAggFunction op,
       List<RexNode> operands,
       RexWindow window,
       boolean distinct,
       boolean ignoreNulls) {
-    super(type, op, operands);
+    super(pos, type, op, operands);
     checkArgument(op.isAggregator());
     this.window = requireNonNull(window, "window");
     this.distinct = distinct;
     this.ignoreNulls = ignoreNulls;
   }
 
+  /**
+   * Creates a RexOver.
+   *
+   * <p>For example, "SUM(DISTINCT x) OVER (ROWS 3 PRECEDING)" is represented
+   * as:
+   *
+   * <ul>
+   * <li>type = Integer,
+   * <li>op = {@link org.apache.calcite.sql.fun.SqlStdOperatorTable#SUM},
+   * <li>operands = { {@link RexFieldAccess}("x") }
+   * <li>window = {@link SqlWindow}(ROWS 3 PRECEDING)
+   * </ul>
+   *
+   * @param type     Result type
+   * @param op       Aggregate operator
+   * @param operands Operands list
+   * @param window   Window specification
+   * @param distinct Aggregate operator is applied on distinct elements
+   */
+  RexOver(
+      RelDataType type,
+      SqlAggFunction op,
+      List<RexNode> operands,
+      RexWindow window,
+      boolean distinct,
+      boolean ignoreNulls) {
+    this(SqlParserPos.ZERO, type, op, operands, window, distinct, ignoreNulls);
+  }
+
   //~ Methods ----------------------------------------------------------------
 
   /**
diff --git a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java 
b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
index 2f6b4ec576..a3cf2b9d5e 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexShuttle.java
@@ -50,6 +50,7 @@ public class RexShuttle implements RexVisitor<RexNode> {
       // watch out for special operators like CAST and NEW where
       // the type is embedded in the original call.
       return new RexOver(
+          over.getParserPosition(),
           over.getType(),
           overAggregator,
           clonedOperands,
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java 
b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index 9bacc060a1..4a9677c27d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -28,6 +28,7 @@
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.SqlTypeCoercionRule;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
@@ -2380,7 +2381,7 @@ private RexNode simplifySearch(RexCall call, RexUnknownAs 
unknownAs) {
       RexLiteral literal = (RexLiteral) call.getOperands().get(1);
       final Sarg sarg = castNonNull(literal.getValueAs(Sarg.class));
       if (sarg.isAll() || sarg.isNone()) {
-        RexNode rexNode = RexUtil.simpleSarg(rexBuilder, searchOperand, sarg, 
unknownAs);
+        RexNode rexNode = RexUtil.simpleSarg(call.pos, rexBuilder, 
searchOperand, sarg, unknownAs);
         return simplify(rexNode, unknownAs);
       }
       // Remove null from sarg if the left-hand side is never null
@@ -2689,7 +2690,7 @@ private RexNode flattenAggregate(RexNode e) {
               rexBuilder.makeWindow(ImmutableList.of(), ImmutableList.of(),
                   RexWindowBounds.CURRENT_ROW, RexWindowBounds.CURRENT_ROW,
                   true);
-          return new RexOver(call.type, (SqlAggFunction) call.op, 
call.operands,
+          return new RexOver(call.pos, call.type, (SqlAggFunction) call.op, 
call.operands,
               w, false, false);
         }
         return super.visitCall(call);
@@ -3208,12 +3209,12 @@ private boolean accept_(RexNode e, List<RexNode> 
newTerms) {
       case SEARCH:
       case IS_NOT_DISTINCT_FROM:
       case IS_DISTINCT_FROM:
-        return accept2(((RexCall) e).operands.get(0),
+        return accept2(((RexCall) e).getParserPosition(), ((RexCall) 
e).operands.get(0),
             ((RexCall) e).operands.get(1), e.getKind(), newTerms);
       case IS_NULL:
       case IS_NOT_NULL:
         final RexNode arg = ((RexCall) e).operands.get(0);
-        return accept1(arg, e.getKind(), newTerms);
+        return accept1(((RexCall) e).getParserPosition(), arg, e.getKind(), 
newTerms);
       default:
         return false;
       }
@@ -3231,13 +3232,13 @@ private boolean accept_(RexNode e, List<RexNode> 
newTerms) {
      * @param newTerms the list to which the Sarg will be added if accepted
      * @return true if the operands can be converted to a Sarg, false otherwise
      */
-    private boolean accept2(RexNode left, RexNode right, SqlKind kind,
+    private boolean accept2(SqlParserPos pos, RexNode left, RexNode right, 
SqlKind kind,
         List<RexNode> newTerms) {
       if (right.isA(SqlKind.LITERAL) && RexUtil.isDeterministic(left)) {
-        return accept2b(left, kind, (RexLiteral) right, newTerms);
+        return accept2b(pos, left, kind, (RexLiteral) right, newTerms);
       }
       if (left.isA(SqlKind.LITERAL) && RexUtil.isDeterministic(right)) {
-        return accept2b(right, kind.reverse(), (RexLiteral) left, newTerms);
+        return accept2b(pos, right, kind.reverse(), (RexLiteral) left, 
newTerms);
       }
       return false;
     }
@@ -3255,10 +3256,10 @@ private static <E> E addFluent(List<? super E> list, E 
e) {
      * @param newTerms the list to which the Sarg is added
      * @return true since the operand is always converted to a Sarg
      */
-    private boolean accept1(RexNode e, SqlKind kind, List<RexNode> newTerms) {
+    private boolean accept1(SqlParserPos pos, RexNode e, SqlKind kind, 
List<RexNode> newTerms) {
       final RexSargBuilder b =
           map.computeIfAbsent(e, e2 ->
-              addFluent(newTerms, new RexSargBuilder(e2, rexBuilder, negate)));
+              addFluent(newTerms, new RexSargBuilder(pos, e2, rexBuilder, 
negate)));
       switch (negate ? kind.negate() : kind) {
       case IS_NULL:
         b.nullAs = b.nullAs.or(TRUE);
@@ -3283,7 +3284,7 @@ private boolean accept1(RexNode e, SqlKind kind, 
List<RexNode> newTerms) {
      * @param newTerms the list to which the Sarg is added if accepted
      * @return false if the literal operand is null, true otherwise
      */
-    private boolean accept2b(RexNode e, SqlKind kind,
+    private boolean accept2b(SqlParserPos pos, RexNode e, SqlKind kind,
         RexLiteral literal, List<RexNode> newTerms) {
       if (literal.getValue() == null) {
         // Cannot include expressions 'x > NULL' in a Sarg. Comparing to a NULL
@@ -3293,7 +3294,8 @@ private boolean accept2b(RexNode e, SqlKind kind,
       }
       final RexSargBuilder b =
           map.computeIfAbsent(e, e2 ->
-              addFluent(newTerms, new RexSargBuilder(e2, rexBuilder, negate)));
+              addFluent(newTerms, new RexSargBuilder(pos, e2, rexBuilder, 
negate)));
+      b.addPosition(pos);
       if (negate) {
         kind = kind.negateNullSafe();
       }
@@ -3374,10 +3376,10 @@ static RexNode fix(RexBuilder rexBuilder, RexNode term,
         if (isSmall && simpleSarg(sarg)) {
           // Expand small sargs into comparisons in order to avoid plan changes
           // and better readability.
-          return RexUtil.sargRef(rexBuilder, sargBuilder.ref, sarg,
+          return RexUtil.sargRef(sargBuilder.pos, rexBuilder, sargBuilder.ref, 
sarg,
               term.getType(), unknownAs);
         }
-        return rexBuilder.makeCall(SqlStdOperatorTable.SEARCH, sargBuilder.ref,
+        return rexBuilder.makeCall(sargBuilder.pos, 
SqlStdOperatorTable.SEARCH, sargBuilder.ref,
             rexBuilder.makeSearchArgumentLiteral(sarg, term.getType()));
       }
       return term;
@@ -3406,6 +3408,10 @@ static RexNode fix(RexBuilder rexBuilder, RexNode term,
    * {@code UNKNOWN OR FALSE OR UNKNOWN} returns {@code UNKNOWN};
    * {@code FALSE OR FALSE} returns {@code FALSE}. */
   private static class RexSargBuilder extends RexNode {
+    // The position is MUTABLE: it contains the SUM of the positions of
+    // all expressions that compose the search.  This is not ideal, but it's 
better
+    // than having no source position information at all.
+    SqlParserPos pos;
     final RexNode ref;
     final RexBuilder rexBuilder;
     final boolean negate;
@@ -3415,7 +3421,8 @@ private static class RexSargBuilder extends RexNode {
     boolean mergedSarg;
     RexUnknownAs nullAs = FALSE;
 
-    RexSargBuilder(RexNode ref, RexBuilder rexBuilder, boolean negate) {
+    RexSargBuilder(SqlParserPos pos, RexNode ref, RexBuilder rexBuilder, 
boolean negate) {
+      this.pos = pos;
       this.ref = requireNonNull(ref, "ref");
       this.rexBuilder = requireNonNull(rexBuilder, "rexBuilder");
       this.negate = negate;
@@ -3463,6 +3470,10 @@ <C extends Comparable<C>> Sarg<C> build(boolean negate) {
       throw new UnsupportedOperationException();
     }
 
+    public SqlParserPos getPos() {
+      return pos;
+    }
+
     @Override public int hashCode() {
       throw new UnsupportedOperationException();
     }
@@ -3471,6 +3482,10 @@ void addAll() {
       rangeSet.add(Range.all());
     }
 
+    void addPosition(SqlParserPos pos) {
+      this.pos = SqlParserPos.sum(ImmutableList.of(this.pos, pos));
+    }
+
     void addRange(Range<Comparable> range, RelDataType type) {
       addRange(range, type, UNKNOWN);
     }
diff --git a/core/src/main/java/org/apache/calcite/rex/RexUtil.java 
b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
index 2894b2fc7c..5904bba922 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexUtil.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexUtil.java
@@ -39,6 +39,7 @@
 import org.apache.calcite.sql.SqlKind;
 import org.apache.calcite.sql.SqlOperator;
 import org.apache.calcite.sql.fun.SqlStdOperatorTable;
+import org.apache.calcite.sql.parser.SqlParserPos;
 import org.apache.calcite.sql.type.SqlTypeFamily;
 import org.apache.calcite.sql.type.SqlTypeName;
 import org.apache.calcite.sql.type.SqlTypeUtil;
@@ -624,51 +625,70 @@ public static RexShuttle searchShuttle(RexBuilder 
rexBuilder,
     return new SearchExpandingShuttle(program, rexBuilder, maxComplexity);
   }
 
-  public static <C extends Comparable<C>> RexNode sargRef(RexBuilder 
rexBuilder,
+  public static <C extends Comparable<C>> RexNode sargRef(SqlParserPos pos, 
RexBuilder rexBuilder,
       RexNode ref, Sarg<C> sarg, RelDataType type, RexUnknownAs unknownAs) {
     if (sarg.isAll() || sarg.isNone()) {
-      return simpleSarg(rexBuilder, ref, sarg, unknownAs);
+      return simpleSarg(pos, rexBuilder, ref, sarg, unknownAs);
     }
     final List<RexNode> orList = new ArrayList<>();
     if (sarg.nullAs == RexUnknownAs.TRUE
         && unknownAs == RexUnknownAs.UNKNOWN) {
-      orList.add(rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, ref));
+      orList.add(rexBuilder.makeCall(pos, SqlStdOperatorTable.IS_NULL, ref));
     }
     if (sarg.isPoints()) {
       // Generate 'ref = value1 OR ... OR ref = valueN'
       sarg.rangeSet.asRanges().forEach(range ->
           orList.add(
-              rexBuilder.makeCall(SqlStdOperatorTable.EQUALS, ref,
+              rexBuilder.makeCall(pos, SqlStdOperatorTable.EQUALS, ref,
                   rexBuilder.makeLiteral(range.lowerEndpoint(),
                       type, true, true))));
     } else if (sarg.isComplementedPoints()) {
       // Generate 'ref <> value1 AND ... AND ref <> valueN'
       final List<RexNode> list = sarg.rangeSet.complement().asRanges().stream()
           .map(range ->
-              rexBuilder.makeCall(SqlStdOperatorTable.NOT_EQUALS, ref,
+              rexBuilder.makeCall(pos, SqlStdOperatorTable.NOT_EQUALS, ref,
                   rexBuilder.makeLiteral(range.lowerEndpoint(),
                       type, true, true)))
           .collect(toImmutableList());
       orList.add(composeConjunction(rexBuilder, list));
     } else {
       final RangeSets.Consumer<C> consumer =
-          new RangeToRex<>(ref, orList, rexBuilder, type);
+          new RangeToRex<>(pos, ref, orList, rexBuilder, type);
       RangeSets.forEach(sarg.rangeSet, consumer);
     }
     RexNode node = composeDisjunction(rexBuilder, orList);
     if (sarg.nullAs == RexUnknownAs.FALSE
         && unknownAs == RexUnknownAs.UNKNOWN) {
       node =
-          rexBuilder.makeCall(SqlStdOperatorTable.AND,
-              rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, ref),
+          rexBuilder.makeCall(pos, SqlStdOperatorTable.AND,
+              rexBuilder.makeCall(pos, SqlStdOperatorTable.IS_NOT_NULL, ref),
               node);
     }
     return node;
   }
 
-  /** Expands an 'all' or 'none' sarg. */
+  /**
+   * Create a sargRef object.
+   *
+   * @deprecated Use
+   * {@link RexUtil#sargRef(SqlParserPos, RexBuilder, RexNode, Sarg, 
RelDataType, RexUnknownAs)}. */
+  public static <C extends Comparable<C>> RexNode sargRef(RexBuilder 
rexBuilder,
+      RexNode ref, Sarg<C> sarg, RelDataType type, RexUnknownAs unknownAs) {
+    return sargRef(SqlParserPos.ZERO, rexBuilder, ref, sarg, type, unknownAs);
+  }
+
+  /** Expands an 'all' or 'none' sarg.
+   *
+   * @deprecated Use
+   * {@link RexUtil#simpleSarg(SqlParserPos, RexBuilder, RexNode, Sarg, 
RexUnknownAs)} */
   public static <C extends Comparable<C>> RexNode simpleSarg(RexBuilder 
rexBuilder,
       RexNode ref, Sarg<C> sarg, RexUnknownAs unknownAs) {
+    return simpleSarg(SqlParserPos.ZERO, rexBuilder, ref, sarg, unknownAs);
+  }
+
+  /** Expands an 'all' or 'none' sarg. */
+  public static <C extends Comparable<C>> RexNode simpleSarg(SqlParserPos pos,
+      RexBuilder rexBuilder, RexNode ref, Sarg<C> sarg, RexUnknownAs 
unknownAs) {
     assert sarg.isAll() || sarg.isNone();
     final RexUnknownAs nullAs =
         sarg.nullAs == RexUnknownAs.UNKNOWN ? unknownAs
@@ -678,11 +698,11 @@ public static <C extends Comparable<C>> RexNode 
simpleSarg(RexBuilder rexBuilder
       case TRUE:
         return rexBuilder.makeLiteral(true);
       case FALSE:
-        return rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, ref);
+        return rexBuilder.makeCall(pos, SqlStdOperatorTable.IS_NOT_NULL, ref);
       case UNKNOWN:
         // "x IS NOT NULL OR UNKNOWN"
-        return rexBuilder.makeCall(SqlStdOperatorTable.OR,
-            rexBuilder.makeCall(SqlStdOperatorTable.IS_NOT_NULL, ref),
+        return rexBuilder.makeCall(pos, SqlStdOperatorTable.OR,
+            rexBuilder.makeCall(pos, SqlStdOperatorTable.IS_NOT_NULL, ref),
             rexBuilder.makeNullLiteral(
                 rexBuilder.typeFactory.createSqlType(SqlTypeName.BOOLEAN)));
       }
@@ -690,12 +710,12 @@ public static <C extends Comparable<C>> RexNode 
simpleSarg(RexBuilder rexBuilder
     if (sarg.isNone()) {
       switch (nullAs) {
       case TRUE:
-        return rexBuilder.makeCall(SqlStdOperatorTable.IS_NULL, ref);
+        return rexBuilder.makeCall(pos, SqlStdOperatorTable.IS_NULL, ref);
       case FALSE:
         return rexBuilder.makeLiteral(false);
       case UNKNOWN:
         // "CASE WHEN x IS NULL THEN UNKNOWN ELSE FALSE END", or "x <> x"
-        return rexBuilder.makeCall(SqlStdOperatorTable.NOT_EQUALS, ref, ref);
+        return rexBuilder.makeCall(pos, SqlStdOperatorTable.NOT_EQUALS, ref, 
ref);
       }
     }
     throw new AssertionError();
@@ -1288,6 +1308,20 @@ public static RexNode composeConjunction(RexBuilder 
rexBuilder,
     return requireNonNull(e, "e");
   }
 
+  /** Summarize the position of all the nodes as the sum of all the positions. 
*/
+  static SqlParserPos summarizePosition(Iterable<? extends @Nullable RexNode> 
nodes) {
+    List<SqlParserPos> validPositions = new ArrayList<>();
+    for (RexNode node : nodes) {
+      if (node instanceof RexCall) {
+        SqlParserPos position = ((RexCall) node).getParserPosition();
+        if (!position.equals(SqlParserPos.ZERO)) {
+          validPositions.add(position);
+        }
+      }
+    }
+    return SqlParserPos.sum(validPositions);
+  }
+
   /**
    * Converts a collection of expressions into an AND.
    * If there are zero expressions, returns TRUE.
@@ -1310,7 +1344,8 @@ public static RexNode composeConjunction(RexBuilder 
rexBuilder,
       if (containsFalse(list)) {
         return rexBuilder.makeLiteral(false);
       }
-      return rexBuilder.makeCall(SqlStdOperatorTable.AND, list);
+      final SqlParserPos pos = summarizePosition(nodes);
+      return rexBuilder.makeCall(pos, SqlStdOperatorTable.AND, list);
     }
   }
 
@@ -1380,7 +1415,8 @@ public static RexNode composeDisjunction(RexBuilder 
rexBuilder,
       if (containsTrue(list)) {
         return rexBuilder.makeLiteral(true);
       }
-      return rexBuilder.makeCall(SqlStdOperatorTable.OR, list);
+      final SqlParserPos pos = summarizePosition(nodes);
+      return rexBuilder.makeCall(pos, SqlStdOperatorTable.OR, list);
     }
   }
 
@@ -3376,13 +3412,15 @@ public boolean anyContain(Iterable<? extends RexNode> 
nodes) {
    * @param <C> Value type */
   private static class RangeToRex<C extends Comparable<C>>
       implements RangeSets.Consumer<C> {
+    private final SqlParserPos pos;
     private final List<RexNode> list;
     private final RexBuilder rexBuilder;
     private final RelDataType type;
     private final RexNode ref;
 
-    RangeToRex(RexNode ref, List<RexNode> list, RexBuilder rexBuilder,
+    RangeToRex(SqlParserPos pos, RexNode ref, List<RexNode> list, RexBuilder 
rexBuilder,
         RelDataType type) {
+      this.pos = requireNonNull(pos, "pos");
       this.ref = requireNonNull(ref, "ref");
       this.list = requireNonNull(list, "list");
       this.rexBuilder = requireNonNull(rexBuilder, "rexBuilder");
@@ -3390,11 +3428,11 @@ private static class RangeToRex<C extends Comparable<C>>
     }
 
     private void addAnd(RexNode... nodes) {
-      list.add(rexBuilder.makeCall(SqlStdOperatorTable.AND, nodes));
+      list.add(rexBuilder.makeCall(pos, SqlStdOperatorTable.AND, nodes));
     }
 
     private RexNode op(SqlOperator op, C value) {
-      return rexBuilder.makeCall(op, ref,
+      return rexBuilder.makeCall(pos, op, ref,
           rexBuilder.makeLiteral(value, type, true, true));
     }
 
@@ -3485,7 +3523,7 @@ private static class SearchExpandingShuttle extends 
RexShuttle {
             (RexLiteral) deref(program, call.operands.get(1));
         final Sarg sarg = requireNonNull(literal.getValueAs(Sarg.class), 
"Sarg");
         if (maxComplexity < 0 || sarg.complexity() < maxComplexity) {
-          return sargRef(rexBuilder, ref, sarg, literal.getType(),
+          return sargRef(call.pos, rexBuilder, ref, sarg, literal.getType(),
               RexUnknownAs.UNKNOWN);
         }
         // Sarg is complex (therefore useful); fall through
diff --git 
a/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java 
b/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
index f2a44602d1..a5e658e54a 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/ConvertToChecked.java
@@ -101,7 +101,7 @@ class ConvertRexToChecked extends RexShuttle {
         } else {
           result = call;
         }
-        return builder.makeCast(call.getType(), result);
+        return builder.makeCast(call.getParserPosition(), call.getType(), 
result);
       } else if (!SqlTypeName.EXACT_TYPES.contains(resultType)) {
         // Do not rewrite operator if the type is e.g., DOUBLE or DATE
         operator = call.getOperator();
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 66d0ed8c72..7b1fc64bbb 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java
@@ -2574,7 +2574,7 @@ private void convertUnnest(Blackboard bb, SqlCall call, 
@Nullable List<String> f
     Ord.forEach(nodes, (node, i) -> {
       final RexNode e = bb.convertExpression(node);
       final String alias = SqlValidatorUtil.alias(node, i);
-      exprs.add(relBuilder.alias(e, alias));
+      exprs.add(relBuilder.alias(node.getParserPosition(), e, alias));
     });
     RelNode child =
         (null != bb.root) ? bb.root : LogicalValues.createOneRow(cluster);
@@ -5780,6 +5780,7 @@ && isConvertedSubq(rex)) {
             && kind == SqlKind.EXISTS) {
           fieldAccess =
               rexBuilder.makeCall(
+                  expr.getParserPosition(),
                   SqlStdOperatorTable.IS_NOT_NULL,
                   fieldAccess);
         }
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 2aca3fc58a..44a7999727 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -741,6 +741,11 @@ public RexNode call(SqlOperator operator, RexNode... 
operands) {
     return call(operator, ImmutableList.copyOf(operands));
   }
 
+  /** Creates a call to a scalar operator. */
+  public RexNode call(SqlParserPos pos, SqlOperator operator, RexNode... 
operands) {
+    return call(pos, operator, ImmutableList.copyOf(operands));
+  }
+
   /** Creates a call to a scalar operator. */
   private RexCall call(SqlParserPos pos, SqlOperator operator, List<RexNode> 
operandList) {
     switch (operator.getKind()) {
@@ -1219,7 +1224,7 @@ public RexNode cast(SqlParserPos pos, RexNode expr, 
SqlTypeName typeName, int pr
    *
    * @see #project
    */
-  public RexNode alias(RexNode expr, String alias) {
+  public RexNode alias(SqlParserPos pos, RexNode expr, String alias) {
     final RexNode aliasLiteral = literal(alias);
     switch (expr.getKind()) {
     case AS:
@@ -1231,10 +1236,14 @@ public RexNode alias(RexNode expr, String alias) {
       expr = call.operands.get(0);
       // strip current (incorrect) alias, and fall through
     default:
-      return call(SqlStdOperatorTable.AS, expr, aliasLiteral);
+      return call(pos, SqlStdOperatorTable.AS, expr, aliasLiteral);
     }
   }
 
+  public RexNode alias(RexNode expr, String alias) {
+    return alias(SqlParserPos.ZERO, expr, alias);
+  }
+
   private RexNode aliasMaybe(RexNode node, @Nullable String name) {
     return name == null ? node : alias(node, name);
   }
@@ -4628,7 +4637,7 @@ private class AggCallImpl implements AggCallPlus {
     }
 
     @Override public OverCall over() {
-      return new OverCallImpl(aggFunction, distinct, operands, ignoreNulls,
+      return new OverCallImpl(pos, aggFunction, distinct, operands, 
ignoreNulls,
           alias);
     }
 
@@ -4702,7 +4711,7 @@ private class AggCallImpl2 implements AggCallPlus {
     }
 
     @Override public OverCall over() {
-      return new OverCallImpl(aggregateCall.getAggregation(),
+      return new OverCallImpl(aggregateCall.getParserPosition(), 
aggregateCall.getAggregation(),
           aggregateCall.isDistinct(), operands, aggregateCall.ignoreNulls(),
           aggregateCall.name);
     }
@@ -4803,6 +4812,8 @@ private class AggCallImpl2 implements AggCallPlus {
    * does the same but also assigns an column alias.
    */
   public interface OverCall {
+    SqlParserPos getPosition();
+
     /** Performs an action on this OverCall. */
     default <R> R let(Function<OverCall, R> consumer) {
       return consumer.apply(this);
@@ -4894,6 +4905,7 @@ default OverCall rangeTo(RexWindowBound upper) {
 
   /** Implementation of {@link OverCall}. */
   private class OverCallImpl implements OverCall {
+    private final SqlParserPos pos;
     private final ImmutableList<RexNode> operands;
     private final boolean ignoreNulls;
     private final @Nullable String alias;
@@ -4908,12 +4920,13 @@ private class OverCallImpl implements OverCall {
     private final SqlAggFunction op;
     private final boolean distinct;
 
-    private OverCallImpl(SqlAggFunction op, boolean distinct,
+    private OverCallImpl(SqlParserPos pos, SqlAggFunction op, boolean distinct,
         ImmutableList<RexNode> operands, boolean ignoreNulls,
         @Nullable String alias, ImmutableList<RexNode> partitionKeys,
         ImmutableList<RexFieldCollation> sortKeys, boolean rows,
         RexWindowBound lowerBound, RexWindowBound upperBound,
         boolean nullWhenCountZero, boolean allowPartial, RexWindowExclusion 
exclude) {
+      this.pos = pos;
       this.op = op;
       this.distinct = distinct;
       this.operands = operands;
@@ -4930,14 +4943,18 @@ private OverCallImpl(SqlAggFunction op, boolean 
distinct,
     }
 
     /** Creates an OverCallImpl with default settings. */
-    OverCallImpl(SqlAggFunction op, boolean distinct,
+    OverCallImpl(SqlParserPos pos, SqlAggFunction op, boolean distinct,
         ImmutableList<RexNode> operands, boolean ignoreNulls,
         @Nullable String alias) {
-      this(op, distinct, operands, ignoreNulls, alias, ImmutableList.of(),
+      this(pos, op, distinct, operands, ignoreNulls, alias, ImmutableList.of(),
           ImmutableList.of(), true, RexWindowBounds.UNBOUNDED_PRECEDING,
           RexWindowBounds.UNBOUNDED_FOLLOWING, false, true, 
RexWindowExclusion.EXCLUDE_NO_OTHER);
     }
 
+    @Override public SqlParserPos getPosition() {
+      return pos;
+    }
+
     @Override public OverCall partitionBy(
         Iterable<? extends RexNode> expressions) {
       return partitionBy_(ImmutableList.copyOf(expressions));
@@ -4948,13 +4965,13 @@ private OverCallImpl(SqlAggFunction op, boolean 
distinct,
     }
 
     private OverCall partitionBy_(ImmutableList<RexNode> partitionKeys) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, rows, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
 
     private OverCall orderBy_(ImmutableList<RexFieldCollation> sortKeys) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, rows, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
@@ -4975,38 +4992,38 @@ private OverCall 
orderBy_(ImmutableList<RexFieldCollation> sortKeys) {
 
     @Override public OverCall rowsBetween(RexWindowBound lowerBound,
         RexWindowBound upperBound) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, true, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
 
     @Override public OverCall rangeBetween(RexWindowBound lowerBound,
         RexWindowBound upperBound) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, false, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
 
     @Override public OverCall exclude(RexWindowExclusion exclude) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, rows, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
 
     @Override public OverCall allowPartial(boolean allowPartial) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, rows, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
 
     @Override public OverCall nullWhenCountZero(boolean nullWhenCountZero) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, rows, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude);
     }
 
     @Override public RexNode as(String alias) {
-      return new OverCallImpl(op, distinct, operands, ignoreNulls, alias,
+      return new OverCallImpl(pos, op, distinct, operands, ignoreNulls, alias,
           partitionKeys, sortKeys, rows, lowerBound, upperBound,
           nullWhenCountZero, allowPartial, exclude).toRex();
     }
@@ -5021,7 +5038,7 @@ private OverCall 
orderBy_(ImmutableList<RexFieldCollation> sortKeys) {
           };
       final RelDataType type = op.inferReturnType(bind);
       final RexNode over = getRexBuilder()
-          .makeOver(type, op, operands, partitionKeys, sortKeys,
+          .makeOver(pos, type, op, operands, partitionKeys, sortKeys,
               lowerBound, upperBound, exclude, rows, allowPartial, 
nullWhenCountZero,
               distinct, ignoreNulls);
       return aliasMaybe(over, alias);
diff --git 
a/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java 
b/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
index 9bc8040b90..034365aede 100644
--- a/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
+++ b/core/src/test/java/org/apache/calcite/rel/externalize/RelJsonTest.java
@@ -104,6 +104,12 @@ plan, containsString("{\n"
         + "                }\n"
         + "              ],\n"
         + "              \"expression\": {\n"
+        + "                \"pos\": {\n"
+        + "                  \"line\": 1,\n"
+        + "                  \"column\": 32,\n"
+        + "                  \"end_line\": 1,\n"
+        + "                  \"end_column\": 36\n"
+        + "                },\n"
         + "                \"op\": {\n"
         + "                  \"name\": \">\",\n"
         + "                  \"kind\": \"GREATER_THAN\",\n"
@@ -129,4 +135,43 @@ plan, containsString("{\n"
         + "              }\n"
         + "            }"));
   }
+
+  /** Test case for <a 
href="https://issues.apache.org/jira/browse/CALCITE-7251";>[CALCITE-7251]
+   * SEARCH and WINDOW operations should carry source position 
information</a>. */
+  @Test void testSearchPosition()
+      throws SqlParseException, ValidationException, RelConversionException {
+    final String query = "SELECT val IN (1, 2, 3, 4)\n"
+        + "FROM (\n"
+        + "  VALUES (10), (30), (20), (40)\n"
+        + ") AS t(val)";
+    SqlOperatorTable opTab = SqlLibraryOperatorTableFactory.INSTANCE
+        .getOperatorTable(EnumSet.of(SqlLibrary.STANDARD, SqlLibrary.SPARK));
+    final SchemaPlus rootSchema = Frameworks.createRootSchema(true);
+    final FrameworkConfig config = Frameworks.newConfigBuilder()
+        .parserConfig(SqlParser.Config.DEFAULT)
+        .operatorTable(opTab)
+        .defaultSchema(rootSchema)
+        .build();
+    Planner planner = Frameworks.getPlanner(config);
+    SqlNode n = planner.parse(query);
+    n = planner.validate(n);
+    RelNode root = planner.rel(n).project();
+    String plan =
+        RelOptUtil.dumpPlan("-- Plan", root,
+            SqlExplainFormat.JSON, SqlExplainLevel.DIGEST_ATTRIBUTES);
+    assertThat(
+        plan, containsString("\"exprs\": [\n"
+            + "        {\n"
+            + "          \"pos\": {\n"
+            + "            \"line\": 1,\n"
+            + "            \"column\": 8,\n"
+            + "            \"end_line\": 1,\n"
+            + "            \"end_column\": 25\n"
+            + "          },\n"
+            + "          \"op\": {\n"
+            + "            \"name\": \"SEARCH\",\n"
+            + "            \"kind\": \"SEARCH\",\n"
+            + "            \"syntax\": \"INTERNAL\"\n"
+            + "          },"));
+  }
 }


Reply via email to