mihaibudiu commented on code in PR #5349:
URL: https://github.com/apache/calcite/pull/5349#discussion_r4371805641


##########
core/src/main/codegen/templates/Parser.jj:
##########
@@ -915,6 +923,138 @@ SqlNode LeafQuery(ExprContext exprContext) :
     e = TableConstructor() { return e; }
 |
     e = ExplicitTable(getPos()) { return e; }
+|
+    e = FromQuery() { return e; }
+}
+
+/**
+ * Parses a standalone FROM clause. Only valid under conformance levels
+ * that allow pipe syntax.
+ */
+SqlNode FromQuery() :
+{
+    final SqlNode fromClause;
+    final Span s;
+}
+{
+    <FROM> {
+        s = span();
+        if (!this.conformance.isPipeSyntaxAllowed()) {
+            throw SqlUtil.newContextException(getPos(),
+                RESOURCE.pipeSyntaxNotAllowed());
+        }
+    }
+    fromClause = FromClause()
+    // Report helpful error messages for common mistakes. (Generally, FROM
+    // must be followed by pipe operators.)
+    [
+        LOOKAHEAD(1)
+        (
+            <WHERE> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.pipeClauseNotAllowed("WHERE", "|> WHERE"));
+            }
+        |
+            <ORDER> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("ORDER BY"));
+            }
+        |
+            <LIMIT> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("LIMIT"));
+            }
+        |
+            <OFFSET> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("OFFSET"));
+            }
+        |
+            <FETCH> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("FETCH"));
+            }
+        |
+            ( <UNION> | <INTERSECT> | <EXCEPT> | <SET_MINUS> ) {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryInSetOpNotAllowed());
+            }
+        )
+    ]
+    { return new SqlFromQuery(s.end(this), fromClause); }
+}
+
+/**
+ * Reads optional pipe operators and applies them to the query {@code e}.
+ *
+ * <p>If {@code with}, {@code e} is a WITH, or an ORDER BY of a WITH, that is
+ * not in parentheses. Then the operators go inside the WITH, so that they can
+ * see its CTEs.
+ */
+SqlNode PipeOpt(SqlNode e, boolean with) :
+{
+    SqlWith w = null;
+}
+{
+    {
+        if (with && getToken(1).kind == PIPE) {
+            if (e instanceof SqlOrderBy) {
+                final SqlOrderBy orderBy = (SqlOrderBy) e;
+                w = (SqlWith) orderBy.query;
+                orderBy.setOperand(0, w.body);
+            } else {
+                w = (SqlWith) e;
+                e = w.body;
+            }
+        }
+    }
+    ( e = PipeOperator(e) )*
+    {
+        if (w != null) {
+            w.setOperand(1, e);
+            return w;
+        }
+        return e;
+    }
+}
+
+/**
+ * Reads a single pipe operator and applies it to the query {@code e}.
+ */
+SqlNode PipeOperator(SqlNode e) :
+{
+    final Span s;
+}
+{
+    <PIPE> {
+        s = span();
+        if (!this.conformance.isPipeSyntaxAllowed()) {
+            throw SqlUtil.newContextException(getPos(),
+                RESOURCE.pipeSyntaxNotAllowed());
+        }
+        if (!e.isA(SqlKind.QUERY)) {
+            // A pipe operator applies only to a query.
+            checkNonQueryExpression(ExprContext.ACCEPT_QUERY);
+        }
+    }
+    (
+        e = PipeWhere(s, e)
+    )
+    { return e; }
+}
+
+/**
+ * Parses a pipe WHERE operator, and applies it to the query {@code e}.

Review Comment:
   what does it mean to "apply it to e"?
   I don't see e being modified.
   Maybe it applies the filter to the result produced by e.



##########
core/src/main/codegen/templates/Parser.jj:
##########
@@ -3977,6 +4117,18 @@ void AddSetOpQuery(List<Object> list, ExprContext 
exprContext) :
         pos = getPos();
         checkQueryExpression(exprContext);
     }
+    [
+        // A FROM query must be in parentheses to follow a set operator.
+        LOOKAHEAD(1)
+        <FROM> {
+            if (!this.conformance.isPipeSyntaxAllowed()) {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.pipeSyntaxNotAllowed());
+            }
+            throw SqlUtil.newContextException(getPos(),

Review Comment:
   if this will be done later, maybe you can add a comment.



##########
core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java:
##########
@@ -118,6 +119,18 @@ public TypeCoercionImpl(RelDataTypeFactory typeFactory, 
SqlValidator validator)
     case WITH:
       SqlNode body = ((SqlWith) query).body;
       return rowTypeCoercion(validator.getOverScope(query), body, columnIndex, 
targetType);
+    case FROM_QUERY:
+    case PIPE_OPERATOR:
+      // A query in pipe syntax is validated and converted as its surrogate

Review Comment:
   can you define "surrogate"?
   Is this a word used in the definition of the pipe syntax?



##########
site/_docs/reference.md:
##########
@@ -490,6 +495,23 @@ becomes a grouping key; for example,
 but is not standard SQL and is only allowed in certain
 [conformance levels]({{ site.apiRoot 
}}/org/apache/calcite/sql/validate/SqlConformance.html#isFromRequired--).
 
+*pipeOperator* and a standalone `FROM` are only allowed in certain

Review Comment:
   Is there a spec for pipe you can point to?



##########
core/src/main/java/org/apache/calcite/sql/validate/implicit/TypeCoercionImpl.java:
##########
@@ -667,7 +680,10 @@ private boolean coalesceCoercion(SqlCallBinding 
callBinding) {
     int colCount = type1.isStruct() ? type1.getFieldCount() : 1;
     RelDataType[] argTypes = new RelDataType[2];
     argTypes[0] = type1;
-    final boolean isSubQuery = node2 instanceof SqlSelect;
+    // Not isA(SqlKind.QUERY): coercing a WITH as a sub-query gives it a type

Review Comment:
   I don't understand this comment.



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.

Review Comment:
   offered?



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;

Review Comment:
   I see that "surrogate" is used consistently in many places. 
   I would rename this field surrogateSelect. 



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;
+  /** Scope that the query is written in, and that its input and surrogate
+   * are validated in. */
+  private final SqlValidatorScope scope;
+
+  /** Scope children that an operator applied to this one can see. Computed
+   * on demand, once this namespace has been validated. */
+  private @Nullable List<ScopeChild> outChildren;
+
+  //~ Constructors -----------------------------------------------------------
+
+  PipeNamespace(SqlValidatorImpl validator, SqlNode node,
+      SqlSelect select, SqlValidatorScope scope,
+      SqlNode enclosingNode) {
+    super(validator, enclosingNode);
+    this.node = node;

Review Comment:
   is any node legal here?
   Maybe you can make a list of legal nodes if not all are allowed, e.g., using 
an assert.



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;
+  /** Scope that the query is written in, and that its input and surrogate
+   * are validated in. */
+  private final SqlValidatorScope scope;
+
+  /** Scope children that an operator applied to this one can see. Computed
+   * on demand, once this namespace has been validated. */
+  private @Nullable List<ScopeChild> outChildren;
+
+  //~ Constructors -----------------------------------------------------------
+
+  PipeNamespace(SqlValidatorImpl validator, SqlNode node,
+      SqlSelect select, SqlValidatorScope scope,
+      SqlNode enclosingNode) {
+    super(validator, enclosingNode);
+    this.node = node;
+    this.select = select;
+    this.scope = scope;
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  @Override public SqlNode getNode() {
+    return node;
+  }
+
+  /** Returns this operator's surrogate SELECT. */
+  public SqlSelect getSelect() {
+    return select;
+  }
+
+  @Override protected RelDataType validateImpl(RelDataType targetRowType) {
+    if (node instanceof SqlPipeOperator) {
+      // Validate the input first, so its tables can take the placeholder's
+      // place in the surrogate's scope.
+      final SqlNode input = ((SqlPipeOperator) node).getQuery();
+      validator.validateQuery(input, scope, validator.unknownType);
+      final SelectScope selectScope =
+          (SelectScope) validator.getFromScope(select);
+      selectScope.replaceChildren(validator.pipeChildren(input));
+    }
+    validator.validateQuery(select, scope, targetRowType);
+    filterRequirement = delegate().getFilterRequirement();
+    return delegate().getRowType();
+  }
+
+  /** Returns the namespace of the surrogate SELECT, which answers
+   * what this query is asked about its rows, such as their modality. */
+  private SqlValidatorNamespace delegate() {
+    return validator.getNamespaceOrThrow(select);
+  }
+
+  @Override public boolean supportsModality(SqlModality modality) {
+    return delegate().supportsModality(modality);
+  }
+
+  @Override public SqlMonotonicity getMonotonicity(String columnName) {
+    return delegate().getMonotonicity(columnName);
+  }
+
+  /** Returns the scope children that an operator applied to this one can
+   * see. This will be the children of the surrogate's scope if this query
+   * passes them through, otherwise it will be an anonymous table. */

Review Comment:
   Is this concept of an anonymous table introduced in this PR?
   If yes, maybe it deserves an explanation.
   



##########
testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java:
##########
@@ -4929,6 +4929,245 @@ void checkPeriodPredicate(Checker checker) {
         .fails("(?s)Encountered \"select\".*");
   }
 
+  @Test void testFromQuery() {
+    final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
+    sql("from emp")
+        .withConformance(lenient)
+        .ok("FROM `EMP`");
+    sql("from emp join dept using (deptno)")
+        .withConformance(lenient)
+        .ok("FROM `EMP`\n"
+            + "INNER JOIN `DEPT` USING (`DEPTNO`)");
+    sql("from (from emp)")
+        .withConformance(lenient)
+        .ok("FROM (FROM `EMP`)");
+  }
+
+  @Test void testFromQueryOrderedAndSetOp() {
+    final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
+    sql("(from emp) order by empno")
+        .withConformance(lenient)
+        .ok("(FROM `EMP`)\n"
+            + "ORDER BY `EMPNO`");
+    sql("(from emp) union all (from dept)")
+        .withConformance(lenient)
+        .ok("(FROM `EMP`)\n"
+            + "UNION ALL\n"
+            + "(FROM `DEPT`)");
+    // A FROM query must be in parentheses to be an operand of a
+    // set operator.
+    final String error = "A FROM query must be in parentheses to be an"
+        + " operand of UNION, INTERSECT or EXCEPT";
+    sql("from emp ^union^ all (from dept)")
+        .withConformance(lenient)
+        .fails(error);
+    sql("select * from emp except ^from^ dept")
+        .withConformance(lenient)
+        .fails(error);
+  }
+
+  @Test void testFromQueryConformance() {
+    final String error =
+        "Pipe syntax is not allowed under the current SQL conformance level";
+    final SqlConformanceEnum conformance = SqlConformanceEnum.DEFAULT;
+    sql("^from^ emp")
+        .withConformance(conformance)

Review Comment:
   Are the withConformance calls needed?



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;
+  /** Scope that the query is written in, and that its input and surrogate

Review Comment:
   This comment is not particularly helpful, but I don't know how to improve it.
   I am not sure how a query is "written in a scope". A scope is really implied 
by the syntax of a query.



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeInput.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Node that represents the input of a pipe operator in its
+ * {@link SqlPipeOperator#surrogateSelect}.
+ *
+ * @see SqlKind#PIPE_INPUT
+ */
+public class SqlPipeInput extends SqlCall {
+  /** Definition of the pipe-input placeholder. */
+  public static final SqlSpecialOperator OPERATOR =
+      new SqlSpecialOperator("PIPE_INPUT", SqlKind.PIPE_INPUT) {
+        @Override public SqlCall createCall(
+            @Nullable SqlLiteral functionQualifier, SqlParserPos pos,

Review Comment:
   same question



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;
+  /** Scope that the query is written in, and that its input and surrogate
+   * are validated in. */
+  private final SqlValidatorScope scope;
+
+  /** Scope children that an operator applied to this one can see. Computed
+   * on demand, once this namespace has been validated. */
+  private @Nullable List<ScopeChild> outChildren;
+
+  //~ Constructors -----------------------------------------------------------
+
+  PipeNamespace(SqlValidatorImpl validator, SqlNode node,
+      SqlSelect select, SqlValidatorScope scope,
+      SqlNode enclosingNode) {
+    super(validator, enclosingNode);
+    this.node = node;
+    this.select = select;
+    this.scope = scope;
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  @Override public SqlNode getNode() {
+    return node;
+  }
+
+  /** Returns this operator's surrogate SELECT. */
+  public SqlSelect getSelect() {
+    return select;
+  }
+
+  @Override protected RelDataType validateImpl(RelDataType targetRowType) {
+    if (node instanceof SqlPipeOperator) {
+      // Validate the input first, so its tables can take the placeholder's
+      // place in the surrogate's scope.
+      final SqlNode input = ((SqlPipeOperator) node).getQuery();
+      validator.validateQuery(input, scope, validator.unknownType);
+      final SelectScope selectScope =
+          (SelectScope) validator.getFromScope(select);
+      selectScope.replaceChildren(validator.pipeChildren(input));
+    }
+    validator.validateQuery(select, scope, targetRowType);
+    filterRequirement = delegate().getFilterRequirement();
+    return delegate().getRowType();
+  }
+
+  /** Returns the namespace of the surrogate SELECT, which answers
+   * what this query is asked about its rows, such as their modality. */
+  private SqlValidatorNamespace delegate() {
+    return validator.getNamespaceOrThrow(select);
+  }
+
+  @Override public boolean supportsModality(SqlModality modality) {
+    return delegate().supportsModality(modality);
+  }
+
+  @Override public SqlMonotonicity getMonotonicity(String columnName) {
+    return delegate().getMonotonicity(columnName);
+  }
+
+  /** Returns the scope children that an operator applied to this one can
+   * see. This will be the children of the surrogate's scope if this query
+   * passes them through, otherwise it will be an anonymous table. */
+  List<ScopeChild> outChildren() {
+    if (outChildren == null) {
+      final SelectScope selectScope =
+          (SelectScope) validator.getFromScope(select);
+      outChildren = passesThrough(selectScope)
+          ? ImmutableList.copyOf(selectScope.children)
+          : validator.anonymousPipeChild(node, this);
+    }
+    return outChildren;
+  }
+
+  /** Returns whether this query's columns are the same as those of the

Review Comment:
   this seems to only check that the column NAMES are the same.
   



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeInput.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Node that represents the input of a pipe operator in its

Review Comment:
   do not describe how something is implemented. Describe what this object is.



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeOperator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Base class for parse tree nodes that represent operators in SQL pipe
+ * syntax; for example, {@code FROM emp |> WHERE deptno = 10}.
+ *
+ * <p>A pipe operator stays in the parse tree through validation. It is
+ * validated and converted as its {@link #surrogateSelect}, which reads the
+ * operator's input through a {@link SqlPipeInput}. For example, the
+ * surrogate of {@code |> WHERE c} is {@code SELECT * FROM input WHERE c}.
+ *
+ * @see SqlConformance#isPipeSyntaxAllowed()
+ * @see SqlKind#PIPE_OPERATOR
+ */
+public abstract class SqlPipeOperator extends SqlPipeQuery {
+  /** The {@code |>} token, with which each operator's name starts. */

Review Comment:
   this comment is not useful



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeInput.java:
##########
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Node that represents the input of a pipe operator in its
+ * {@link SqlPipeOperator#surrogateSelect}.
+ *
+ * @see SqlKind#PIPE_INPUT
+ */
+public class SqlPipeInput extends SqlCall {
+  /** Definition of the pipe-input placeholder. */
+  public static final SqlSpecialOperator OPERATOR =
+      new SqlSpecialOperator("PIPE_INPUT", SqlKind.PIPE_INPUT) {
+        @Override public SqlCall createCall(
+            @Nullable SqlLiteral functionQualifier, SqlParserPos pos,
+            @Nullable SqlNode... operands) {
+          return new SqlPipeInput(pos, requireNonNull(operands[0], "input"));
+        }
+
+        @Override public void unparse(SqlWriter writer, SqlCall call,
+            int leftPrec, int rightPrec) {
+          ((SqlPipeInput) call).input.unparse(writer, leftPrec, rightPrec);
+        }
+      };
+
+  private SqlNode input;
+
+  //~ Constructors -----------------------------------------------------------
+
+  private SqlPipeInput(SqlParserPos pos, SqlNode input) {
+    super(pos);
+    this.input = input;
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Creates a placeholder for the input of {@code pipe}. */
+  public static SqlPipeInput of(SqlPipeOperator pipe) {
+    return new SqlPipeInput(pipe.getParserPosition(), pipe.getQuery());
+  }
+
+  /** Returns the query that this node stands for. */
+  public SqlNode getInput() {
+    return input;
+  }
+
+  @Override public SqlOperator getOperator() {
+    return OPERATOR;
+  }
+
+  @Override public List<SqlNode> getOperandList() {
+    return ImmutableList.of(input);
+  }
+
+  @Override public void setOperand(int i, @Nullable SqlNode operand) {
+    if (i == 0) {
+      input = requireNonNull(operand, "operand");
+    } else {
+      super.setOperand(i, operand);

Review Comment:
   If you only accept an argument, then maybe this is wrong



##########
core/src/main/codegen/templates/Parser.jj:
##########
@@ -915,6 +923,138 @@ SqlNode LeafQuery(ExprContext exprContext) :
     e = TableConstructor() { return e; }
 |
     e = ExplicitTable(getPos()) { return e; }
+|
+    e = FromQuery() { return e; }
+}
+
+/**
+ * Parses a standalone FROM clause. Only valid under conformance levels
+ * that allow pipe syntax.
+ */
+SqlNode FromQuery() :
+{
+    final SqlNode fromClause;
+    final Span s;
+}
+{
+    <FROM> {
+        s = span();
+        if (!this.conformance.isPipeSyntaxAllowed()) {
+            throw SqlUtil.newContextException(getPos(),
+                RESOURCE.pipeSyntaxNotAllowed());
+        }
+    }
+    fromClause = FromClause()
+    // Report helpful error messages for common mistakes. (Generally, FROM
+    // must be followed by pipe operators.)
+    [
+        LOOKAHEAD(1)
+        (
+            <WHERE> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.pipeClauseNotAllowed("WHERE", "|> WHERE"));
+            }
+        |
+            <ORDER> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("ORDER BY"));
+            }
+        |
+            <LIMIT> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("LIMIT"));
+            }
+        |
+            <OFFSET> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("OFFSET"));
+            }
+        |
+            <FETCH> {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryClauseNotAllowed("FETCH"));
+            }
+        |
+            ( <UNION> | <INTERSECT> | <EXCEPT> | <SET_MINUS> ) {
+                throw SqlUtil.newContextException(getPos(),
+                    RESOURCE.fromQueryInSetOpNotAllowed());
+            }
+        )
+    ]
+    { return new SqlFromQuery(s.end(this), fromClause); }
+}
+
+/**
+ * Reads optional pipe operators and applies them to the query {@code e}.
+ *
+ * <p>If {@code with}, {@code e} is a WITH, or an ORDER BY of a WITH, that is

Review Comment:
   can this be {@code with e}?.
   How can it not be a WITH if it's a with?
   The period should probably be not here.
   Who is "they" who can "see"?



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeOperator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Base class for parse tree nodes that represent operators in SQL pipe
+ * syntax; for example, {@code FROM emp |> WHERE deptno = 10}.
+ *
+ * <p>A pipe operator stays in the parse tree through validation. It is
+ * validated and converted as its {@link #surrogateSelect}, which reads the
+ * operator's input through a {@link SqlPipeInput}. For example, the
+ * surrogate of {@code |> WHERE c} is {@code SELECT * FROM input WHERE c}.
+ *
+ * @see SqlConformance#isPipeSyntaxAllowed()
+ * @see SqlKind#PIPE_OPERATOR
+ */
+public abstract class SqlPipeOperator extends SqlPipeQuery {
+  /** The {@code |>} token, with which each operator's name starts. */
+  public static final String PIPE = "|>";
+
+  protected SqlNode query;
+
+  //~ Constructors -----------------------------------------------------------
+
+  protected SqlPipeOperator(SqlParserPos pos, SqlNode query) {
+    super(pos);
+    this.query = requireNonNull(query, "query");
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Creates a {@code SELECT} for {@link #surrogateSelect} to return, which
+   * reads this operator's input through a {@link SqlPipeInput}. */
+  protected SqlSelect surrogate(@Nullable SqlNodeList keywordList,
+      SqlNodeList selectList) {
+    return surrogate(keywordList, selectList, SqlPipeInput.of(this));
+  }
+
+  /** Returns the query that this pipe operator applies to. */
+  public SqlNode getQuery() {
+    return query;
+  }
+
+  @Override public void setOperand(int i, @Nullable SqlNode operand) {
+    if (i == 0) {
+      query = requireNonNull(operand, "operand");
+    } else {
+      super.setOperand(i, operand);
+    }
+  }
+
+  @Override public final SqlKind getKind() {
+    return SqlKind.PIPE_OPERATOR;
+  }
+
+  /** Definition of a pipe operator. Unparses the input query, then the
+   * operator's keyword, then a tail supplied by the sub-class. */
+  public abstract static class Operator extends SqlPipeQuery.Operator {
+    protected Operator(String name) {
+      super(name, SqlKind.PIPE_OPERATOR);
+    }
+
+    @Override public SqlSyntax getSyntax() {
+      return SqlSyntax.POSTFIX;
+    }
+
+    @Override public final void unparse(SqlWriter writer, SqlCall call,
+        int leftPrec, int rightPrec) {
+      final SqlPipeOperator pipe = (SqlPipeOperator) call;
+      // An ORDER_BY frame starts each operator on a new line, unindented.
+      final SqlWriter.Frame frame =
+          writer.startList(SqlWriter.FrameTypeEnum.ORDER_BY);
+      final SqlNode query = pipe.query;
+      if (query instanceof SqlWith
+          || query instanceof SqlOrderBy
+              && ((SqlOrderBy) query).query instanceof SqlWith) {
+        // Without parentheses, the operator would be re-parsed into the WITH.
+        final SqlWriter.Frame frame1 = writer.startList("(", ")");
+        query.unparse(writer, 0, 0);
+        writer.endList(frame1);
+      } else {
+        query.unparse(writer, 0, 0);
+      }
+      writer.sep(getName());
+      unparseTail(writer, pipe);
+      writer.endList(frame);
+    }
+
+    /** Unparses whatever follows the operator's keyword. */
+    protected abstract void unparseTail(SqlWriter writer,

Review Comment:
   Is this really necessary? How many implementation will it have?



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeOperator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Base class for parse tree nodes that represent operators in SQL pipe
+ * syntax; for example, {@code FROM emp |> WHERE deptno = 10}.
+ *
+ * <p>A pipe operator stays in the parse tree through validation. It is

Review Comment:
   Reorganize this to start with the definition of a surrogate select; it is a 
key concept of the implementation.
   In fact, I notice that the base class has the surrogate definition; define 
it there.



##########
core/src/main/java/org/apache/calcite/sql/SqlKind.java:
##########
@@ -593,6 +595,24 @@ public enum SqlKind {
    */
   EXPLICIT_TABLE,
 
+  /**
+   * Standalone {@code FROM}, e.g. {@code FROM t}, in pipe syntax.
+   */
+  FROM_QUERY,
+
+  /**
+   * Pipe operator, e.g. {@code |> WHERE c}.
+   */
+  PIPE_OPERATOR,

Review Comment:
   Can we not call this an "operator"? SqlOperator means something else.



##########
testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java:
##########
@@ -4929,6 +4929,245 @@ void checkPeriodPredicate(Checker checker) {
         .fails("(?s)Encountered \"select\".*");
   }
 
+  @Test void testFromQuery() {
+    final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
+    sql("from emp")
+        .withConformance(lenient)
+        .ok("FROM `EMP`");
+    sql("from emp join dept using (deptno)")
+        .withConformance(lenient)
+        .ok("FROM `EMP`\n"
+            + "INNER JOIN `DEPT` USING (`DEPTNO`)");
+    sql("from (from emp)")
+        .withConformance(lenient)
+        .ok("FROM (FROM `EMP`)");
+  }
+
+  @Test void testFromQueryOrderedAndSetOp() {
+    final SqlConformanceEnum lenient = SqlConformanceEnum.LENIENT;
+    sql("(from emp) order by empno")
+        .withConformance(lenient)
+        .ok("(FROM `EMP`)\n"
+            + "ORDER BY `EMPNO`");
+    sql("(from emp) union all (from dept)")
+        .withConformance(lenient)
+        .ok("(FROM `EMP`)\n"
+            + "UNION ALL\n"
+            + "(FROM `DEPT`)");
+    // A FROM query must be in parentheses to be an operand of a

Review Comment:
   this comment is redundant with the next error



##########
core/src/test/resources/sql/pipe.iq:
##########
@@ -0,0 +1,135 @@
+# pipe.iq - Tests for SQL pipe syntax
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to you under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Pipe syntax is only allowed under conformance levels that support it;
+# scott-lenient uses LENIENT conformance.
+!use scott-lenient
+!set outputformat mysql

Review Comment:
   Can you add a comment explaining how these results were validated?



##########
testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java:
##########
@@ -4929,6 +4929,245 @@ void checkPeriodPredicate(Checker checker) {
         .fails("(?s)Encountered \"select\".*");
   }
 
+  @Test void testFromQuery() {

Review Comment:
   Maybe you can add a comment pointing out that the next set of tests is for 
the pipe syntax



##########
core/src/test/resources/sql/some.iq:
##########
@@ -1034,4 +1034,19 @@ select
 
 !ok
 
+# ALL over a WITH whose column needs coercion; the left operand is nullable
+# INTEGER and the right is TINYINT.
+select count(*) as c from emp

Review Comment:
   Is this change related to the current feature?



##########
core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java:
##########
@@ -1924,6 +1934,95 @@ private void checkCallTypesConsistent(String sql) {
     sql("table emp").ok();
   }
 
+  @Test void testFromQuery() {
+    sql("from emp").withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** Tests that a pipeline over a streaming query is still streaming. */
+  @Test void testPipeWhereOverStream() {
+    sql("select stream * from orders |> where productid = 1")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** As {@link #testPipeWhereOverStream()}, but the pipeline starts from a
+   * WITH whose body is the streaming query. */
+  @Test void testPipeWhereOverStreamingWith() {
+    sql("(with d as (select * from dept)\n"
+        + "  select stream * from orders) |> where productid = 1")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  @Test void testPipeWhere() {
+    sql("from emp |> where deptno = 10")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** Tests that {@code |> WHERE} keeps the order of its input. The input's
+   * sort key, which is not one of its columns, stays as a hidden column. */
+  @Test void testPipeWhereKeepsOrder() {
+    final SqlToRelFixture f =
+        sql("select ename, deptno from emp order by sal desc\n"
+            + "|> where deptno = 10")
+            .withConformance(SqlConformanceEnum.LENIENT);
+    f.ok();
+    assertThat(f.toRoot().collation, hasToString("[2 DESC]"));
+  }
+
+  /** Tests a correlated sub-query in a {@code |> WHERE} whose input has a
+   * hidden column for its sort key. */
+  @Test void testPipeWhereCorrelatedHiddenSortKey() {
+    sql("select ename, deptno as dno from emp order by sal + 1 desc\n"
+        + "|> where exists (select 1 from dept as d where d.deptno = dno)")
+        .withConformance(SqlConformanceEnum.LENIENT)
+        .withDecorrelate(false).ok();
+  }
+
+  /** Tests a pipe query used as an expression sub-query, correlated on a
+   * table alias of the enclosing pipeline. */
+  @Test void testPipeQueryAsCorrelatedSubQuery() {
+    sql("from emp as e\n"
+        + "|> where exists (from dept |> where deptno = e.deptno)")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** Tests that implicit type coercion casts the column of a pipe query that
+   * is an {@code IN} sub-query, as it would for a SELECT. */
+  @Test void testPipeWhereInSubQueryCoercion() {
+    sql("select * from emp\n"

Review Comment:
   I don't really know how this tests checks coercion.



##########
core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java:
##########
@@ -648,12 +653,9 @@ public RelRoot convertQuery(
         result = new LogicalDelta(cluster, result.getTraitSet(), result);
       }
     }
-    RelCollation collation = RelCollations.EMPTY;
-    if (!query.isA(SqlKind.DML)) {
-      if (isOrdered(query)) {
-        collation = requiredCollation(result);
-      }
-    }
+    final RelCollation collation = query.isA(SqlKind.DML)

Review Comment:
   is this change related to this PR?
   If not, is it a bug-fix?
   Or is it just cosmetic?



##########
core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java:
##########
@@ -3590,8 +3651,19 @@ protected RelNode createAsofJoin(
       leaves.put(r, r.getRowType().getFieldCount());
     }
 
-    // If there are field mappings (due to aggregation), rewrite the RelNode 
tree
-    // to update correlation variable row type and field indices
+    // The rel of a pipe operator's input may also have trailing columns for
+    // sort keys (see convertNonAggregateSelectList), which the correlation
+    // variable lacks. Give it the rel's row type.

Review Comment:
   what is "it"?
   What does it mean to give something to a type?



##########
core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java:
##########
@@ -3725,6 +3747,49 @@ private void registerQuery(
     }
   }
 
+  /**
+   * Registers a query written in pipe syntax. Each is registered along with

Review Comment:
   What does "each" mean? I see only one query.



##########
core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java:
##########
@@ -2861,10 +2897,31 @@ protected void convertFrom(
     case INTERSECT:
     case EXCEPT:
     case UNION:
+    case FROM_QUERY:
+    case PIPE_OPERATOR:
       final RelNode rel = convertQueryRecursive(from, false, null).project();
       bb.setRoot(rel, true);
       return;
 
+    case PIPE_INPUT:
+      // The input's order matters only if the operator is at the top of the
+      // statement. Then convert the input as top, so that its sort is kept.
+      final boolean keepOrder = bb.top;
+      SqlNode pipeInput = ((SqlPipeInput) from).getInput();
+      while (pipeInput instanceof SqlWith) {
+        // convertWith would convert the body as the top of the statement.

Review Comment:
   I also don't know what the top of a statement is



##########
core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java:
##########
@@ -3725,6 +3747,49 @@ private void registerQuery(
     }
   }
 
+  /**
+   * Registers a query written in pipe syntax. Each is registered along with
+   * its {@link SqlPipeQuery#surrogateSelect()}.
+   */
+  private void registerPipe(SqlValidatorScope parentScope,
+      @Nullable SqlValidatorScope usingScope, SqlNode node,
+      SqlNode enclosingNode, @Nullable String alias, boolean forceNullable) {
+    if (node instanceof SqlPipeOperator) {
+      // PipeNamespace decides, when it is validated, what this operator sees
+      // of its input.
+      final SqlNode input = ((SqlPipeOperator) node).getQuery();
+      registerQuery(parentScope, null, input, input, null, false);
+    }
+    final SqlSelect surrogate = ((SqlPipeQuery) node).surrogateSelect();
+    registerQuery(parentScope, null, surrogate, node, null, false);
+    // The query's expressions are resolved in its surrogate's scopes, so the
+    // query itself needs only the scope it is written in.

Review Comment:
   what does "needs" mean?



##########
core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java:
##########
@@ -3498,6 +3555,7 @@ protected RelNode createAsofJoin(
     final List<CorrelationId> correlNames = new ArrayList<>();
     // Mapping from (correlId, originalFieldIndex) to projectedFieldIndex for 
aggregation
     final Map<Pair<CorrelationId, Integer>, Integer> fieldMapping = new 
HashMap<>();
+    int correlFieldCount = Integer.MAX_VALUE;

Review Comment:
   This is a bit ugly. Why not just use `null`?



##########
core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java:
##########
@@ -4469,6 +4544,27 @@ private RelNode createUnion(SqlCall call,
         .build();
   }
 
+  /**
+   * Converts a query written in pipe syntax, by converting its surrogate

Review Comment:
   maybe this is true, but there's nothing about surrogates in this body.
   In general I would not document the implementation in the javadoc, only in 
the body.



##########
core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java:
##########
@@ -3725,6 +3747,49 @@ private void registerQuery(
     }
   }
 
+  /**
+   * Registers a query written in pipe syntax. Each is registered along with

Review Comment:
   the comment about registering the surrogate has to be in the code, on line 
3763.
   This is the essential trick which makes this work easily: pretend that a 
pipe query is a regular SELECT. Say this in a comment.



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;
+  /** Scope that the query is written in, and that its input and surrogate
+   * are validated in. */
+  private final SqlValidatorScope scope;
+
+  /** Scope children that an operator applied to this one can see. Computed
+   * on demand, once this namespace has been validated. */
+  private @Nullable List<ScopeChild> outChildren;
+
+  //~ Constructors -----------------------------------------------------------
+
+  PipeNamespace(SqlValidatorImpl validator, SqlNode node,
+      SqlSelect select, SqlValidatorScope scope,
+      SqlNode enclosingNode) {
+    super(validator, enclosingNode);
+    this.node = node;
+    this.select = select;
+    this.scope = scope;
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  @Override public SqlNode getNode() {
+    return node;
+  }
+
+  /** Returns this operator's surrogate SELECT. */
+  public SqlSelect getSelect() {
+    return select;
+  }
+
+  @Override protected RelDataType validateImpl(RelDataType targetRowType) {
+    if (node instanceof SqlPipeOperator) {
+      // Validate the input first, so its tables can take the placeholder's
+      // place in the surrogate's scope.
+      final SqlNode input = ((SqlPipeOperator) node).getQuery();
+      validator.validateQuery(input, scope, validator.unknownType);
+      final SelectScope selectScope =
+          (SelectScope) validator.getFromScope(select);
+      selectScope.replaceChildren(validator.pipeChildren(input));
+    }
+    validator.validateQuery(select, scope, targetRowType);
+    filterRequirement = delegate().getFilterRequirement();
+    return delegate().getRowType();
+  }
+
+  /** Returns the namespace of the surrogate SELECT, which answers
+   * what this query is asked about its rows, such as their modality. */
+  private SqlValidatorNamespace delegate() {

Review Comment:
   This function does not have a good name.
   getSurrogateNamespace()?
   The comment is not very useful either -- does this namespace do different 
things from other namespaces?



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see
+ * {@link SqlPipeOperator#surrogateSelect()}.
+ */
+public class PipeNamespace extends AbstractNamespace {
+  private final SqlNode node;
+  /** This operator's surrogate SELECT. */
+  private final SqlSelect select;
+  /** Scope that the query is written in, and that its input and surrogate
+   * are validated in. */
+  private final SqlValidatorScope scope;
+
+  /** Scope children that an operator applied to this one can see. Computed
+   * on demand, once this namespace has been validated. */
+  private @Nullable List<ScopeChild> outChildren;
+
+  //~ Constructors -----------------------------------------------------------
+
+  PipeNamespace(SqlValidatorImpl validator, SqlNode node,
+      SqlSelect select, SqlValidatorScope scope,
+      SqlNode enclosingNode) {
+    super(validator, enclosingNode);
+    this.node = node;
+    this.select = select;
+    this.scope = scope;
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  @Override public SqlNode getNode() {
+    return node;
+  }
+
+  /** Returns this operator's surrogate SELECT. */
+  public SqlSelect getSelect() {
+    return select;
+  }
+
+  @Override protected RelDataType validateImpl(RelDataType targetRowType) {
+    if (node instanceof SqlPipeOperator) {
+      // Validate the input first, so its tables can take the placeholder's

Review Comment:
   what is a placeholder?



##########
core/src/main/java/org/apache/calcite/sql/validate/PipeNamespace.java:
##########
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql.validate;
+
+import org.apache.calcite.rel.type.RelDataType;
+import org.apache.calcite.rel.type.RelDataTypeField;
+import org.apache.calcite.sql.SqlIdentifier;
+import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlPipeOperator;
+import org.apache.calcite.sql.SqlSelect;
+import org.apache.calcite.sql.SqlUtil;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Namespace offered by a query written in SQL pipe syntax.
+ *
+ * <p>The query is validated through its surrogate SELECT; see

Review Comment:
   this comment does not seem to belong to the javadoc. It should perhaps be on 
a method.



##########
core/src/main/java/org/apache/calcite/sql/SqlFromQuery.java:
##########
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import com.google.common.collect.ImmutableList;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import java.util.List;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Parse tree node that represents a standalone {@code FROM} query in SQL pipe
+ * syntax; for example, {@code FROM emp}.
+ *
+ * @see SqlConformance#isPipeSyntaxAllowed()
+ */
+public class SqlFromQuery extends SqlPipeQuery {
+  public static final SqlSpecialOperator OPERATOR = new Operator() {
+    @Override public SqlCall createCall(@Nullable SqlLiteral functionQualifier,

Review Comment:
   why does this take an arbitrary number of operands which are then ignored?



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeOperator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Base class for parse tree nodes that represent operators in SQL pipe
+ * syntax; for example, {@code FROM emp |> WHERE deptno = 10}.
+ *
+ * <p>A pipe operator stays in the parse tree through validation. It is
+ * validated and converted as its {@link #surrogateSelect}, which reads the
+ * operator's input through a {@link SqlPipeInput}. For example, the
+ * surrogate of {@code |> WHERE c} is {@code SELECT * FROM input WHERE c}.
+ *
+ * @see SqlConformance#isPipeSyntaxAllowed()
+ * @see SqlKind#PIPE_OPERATOR
+ */
+public abstract class SqlPipeOperator extends SqlPipeQuery {
+  /** The {@code |>} token, with which each operator's name starts. */
+  public static final String PIPE = "|>";
+
+  protected SqlNode query;
+
+  //~ Constructors -----------------------------------------------------------
+
+  protected SqlPipeOperator(SqlParserPos pos, SqlNode query) {
+    super(pos);
+    this.query = requireNonNull(query, "query");
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Creates a {@code SELECT} for {@link #surrogateSelect} to return, which

Review Comment:
   not "to return", use the `@returns` JavaDoc annotation.



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeOperator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Base class for parse tree nodes that represent operators in SQL pipe
+ * syntax; for example, {@code FROM emp |> WHERE deptno = 10}.
+ *
+ * <p>A pipe operator stays in the parse tree through validation. It is
+ * validated and converted as its {@link #surrogateSelect}, which reads the
+ * operator's input through a {@link SqlPipeInput}. For example, the
+ * surrogate of {@code |> WHERE c} is {@code SELECT * FROM input WHERE c}.
+ *
+ * @see SqlConformance#isPipeSyntaxAllowed()
+ * @see SqlKind#PIPE_OPERATOR
+ */
+public abstract class SqlPipeOperator extends SqlPipeQuery {
+  /** The {@code |>} token, with which each operator's name starts. */
+  public static final String PIPE = "|>";
+
+  protected SqlNode query;
+
+  //~ Constructors -----------------------------------------------------------
+
+  protected SqlPipeOperator(SqlParserPos pos, SqlNode query) {
+    super(pos);
+    this.query = requireNonNull(query, "query");
+  }
+
+  //~ Methods ----------------------------------------------------------------
+
+  /** Creates a {@code SELECT} for {@link #surrogateSelect} to return, which
+   * reads this operator's input through a {@link SqlPipeInput}. */
+  protected SqlSelect surrogate(@Nullable SqlNodeList keywordList,
+      SqlNodeList selectList) {
+    return surrogate(keywordList, selectList, SqlPipeInput.of(this));
+  }
+
+  /** Returns the query that this pipe operator applies to. */
+  public SqlNode getQuery() {
+    return query;
+  }
+
+  @Override public void setOperand(int i, @Nullable SqlNode operand) {
+    if (i == 0) {
+      query = requireNonNull(operand, "operand");

Review Comment:
   You should probably fail if i > 0



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeQuery.java:
##########
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.util.SqlBasicVisitor;
+import org.apache.calcite.sql.util.SqlVisitor;
+import org.apache.calcite.sql.validate.SqlConformance;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Base class for parse tree nodes that represent a query written in SQL pipe
+ * syntax: a standalone {@code FROM} query, which is the head of a pipeline,
+ * or a {@link SqlPipeOperator} applied to a query. The query an operator is

Review Comment:
   Why does this JavaDoc describe the grammar? The grammar description does not 
belong in this class, unless it is somehow relevant to the implementation.



##########
core/src/test/java/org/apache/calcite/test/SqlToRelConverterTest.java:
##########
@@ -1924,6 +1934,95 @@ private void checkCallTypesConsistent(String sql) {
     sql("table emp").ok();
   }
 
+  @Test void testFromQuery() {
+    sql("from emp").withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** Tests that a pipeline over a streaming query is still streaming. */
+  @Test void testPipeWhereOverStream() {
+    sql("select stream * from orders |> where productid = 1")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** As {@link #testPipeWhereOverStream()}, but the pipeline starts from a
+   * WITH whose body is the streaming query. */
+  @Test void testPipeWhereOverStreamingWith() {
+    sql("(with d as (select * from dept)\n"
+        + "  select stream * from orders) |> where productid = 1")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  @Test void testPipeWhere() {
+    sql("from emp |> where deptno = 10")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** Tests that {@code |> WHERE} keeps the order of its input. The input's
+   * sort key, which is not one of its columns, stays as a hidden column. */
+  @Test void testPipeWhereKeepsOrder() {
+    final SqlToRelFixture f =
+        sql("select ename, deptno from emp order by sal desc\n"
+            + "|> where deptno = 10")
+            .withConformance(SqlConformanceEnum.LENIENT);
+    f.ok();
+    assertThat(f.toRoot().collation, hasToString("[2 DESC]"));
+  }
+
+  /** Tests a correlated sub-query in a {@code |> WHERE} whose input has a
+   * hidden column for its sort key. */
+  @Test void testPipeWhereCorrelatedHiddenSortKey() {
+    sql("select ename, deptno as dno from emp order by sal + 1 desc\n"
+        + "|> where exists (select 1 from dept as d where d.deptno = dno)")
+        .withConformance(SqlConformanceEnum.LENIENT)
+        .withDecorrelate(false).ok();
+  }
+
+  /** Tests a pipe query used as an expression sub-query, correlated on a
+   * table alias of the enclosing pipeline. */
+  @Test void testPipeQueryAsCorrelatedSubQuery() {
+    sql("from emp as e\n"
+        + "|> where exists (from dept |> where deptno = e.deptno)")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  /** Tests that implicit type coercion casts the column of a pipe query that
+   * is an {@code IN} sub-query, as it would for a SELECT. */
+  @Test void testPipeWhereInSubQueryCoercion() {
+    sql("select * from emp\n"
+        + "where cast(deptno as bigint) in\n"
+        + "  (select deptno from dept |> where deptno > 0)")
+        .withConformance(SqlConformanceEnum.LENIENT)
+        .withExpand(false)
+        .ok();
+  }
+
+  @Test void testPipeWhereWrapsSelect() {
+    sql("select ename, sal from emp |> where sal > 100")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  @Test void testPipeWhereAfterUnion() {
+    sql("select deptno from emp union all select deptno from dept\n"
+        + "|> where deptno = 10")
+        .withConformance(SqlConformanceEnum.LENIENT).ok();
+  }
+
+  @Test void testPipeWhereAfterWith() {
+    // The operator applies to the body of the WITH, above its ORDER BY.

Review Comment:
   which operator?



##########
core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java:
##########
@@ -2861,10 +2897,31 @@ protected void convertFrom(
     case INTERSECT:
     case EXCEPT:
     case UNION:
+    case FROM_QUERY:
+    case PIPE_OPERATOR:
       final RelNode rel = convertQueryRecursive(from, false, null).project();
       bb.setRoot(rel, true);
       return;
 
+    case PIPE_INPUT:
+      // The input's order matters only if the operator is at the top of the
+      // statement. Then convert the input as top, so that its sort is kept.

Review Comment:
   what does "convert the input as top" mean?
   



##########
core/src/main/java/org/apache/calcite/sql/SqlPipeOperator.java:
##########
@@ -0,0 +1,114 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.calcite.sql;
+
+import org.apache.calcite.sql.parser.SqlParserPos;
+import org.apache.calcite.sql.validate.SqlConformance;
+
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+import static java.util.Objects.requireNonNull;
+
+/**
+ * Base class for parse tree nodes that represent operators in SQL pipe
+ * syntax; for example, {@code FROM emp |> WHERE deptno = 10}.
+ *
+ * <p>A pipe operator stays in the parse tree through validation. It is
+ * validated and converted as its {@link #surrogateSelect}, which reads the
+ * operator's input through a {@link SqlPipeInput}. For example, the
+ * surrogate of {@code |> WHERE c} is {@code SELECT * FROM input WHERE c}.
+ *
+ * @see SqlConformance#isPipeSyntaxAllowed()
+ * @see SqlKind#PIPE_OPERATOR
+ */
+public abstract class SqlPipeOperator extends SqlPipeQuery {

Review Comment:
   This class is misnamed, because it does not extend SqlOperator, as one would 
expect from its name.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to