diff --git a/core/src/main/codegen/templates/Parser.jj b/core/src/main/codegen/templates/Parser.jj index 57b491767348..9c62511bf41d 100644 --- a/core/src/main/codegen/templates/Parser.jj +++ b/core/src/main/codegen/templates/Parser.jj @@ -1934,7 +1934,7 @@ SqlNode JoinTable(SqlNode e) : joinType, e2, using, - new SqlNodeList(list.getList(), Span.of(using).end(this))); + new SqlNodeList(list, Span.of(using).end(this))); } | { diff --git a/core/src/main/java/org/apache/calcite/materialize/LatticeSuggester.java b/core/src/main/java/org/apache/calcite/materialize/LatticeSuggester.java index bae862e88e49..657161869215 100644 --- a/core/src/main/java/org/apache/calcite/materialize/LatticeSuggester.java +++ b/core/src/main/java/org/apache/calcite/materialize/LatticeSuggester.java @@ -17,7 +17,9 @@ package org.apache.calcite.materialize; import org.apache.calcite.jdbc.CalciteSchema; +import org.apache.calcite.plan.RelOptAbstractTable; import org.apache.calcite.plan.RelOptCostImpl; +import org.apache.calcite.plan.RelOptTable; import org.apache.calcite.plan.RelOptUtil; import org.apache.calcite.plan.hep.HepPlanner; import org.apache.calcite.plan.hep.HepProgram; @@ -30,12 +32,24 @@ import org.apache.calcite.rel.core.Project; import org.apache.calcite.rel.core.SetOp; import org.apache.calcite.rel.core.Sort; +import org.apache.calcite.rel.core.TableFunctionScan; import org.apache.calcite.rel.core.TableScan; import org.apache.calcite.rel.rules.CoreRules; +import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeField; +import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; +import org.apache.calcite.rex.RexLiteral; import org.apache.calcite.rex.RexNode; import org.apache.calcite.sql.SqlAggFunction; +import org.apache.calcite.sql.SqlFunction; +import org.apache.calcite.sql.SqlFunctionCategory; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlOperatorBinding; +import org.apache.calcite.sql.SqlTableFunction; +import org.apache.calcite.sql.type.OperandTypes; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlReturnTypeInference; import org.apache.calcite.tools.FrameworkConfig; import org.apache.calcite.util.CompositeList; import org.apache.calcite.util.ImmutableBitSet; @@ -64,7 +78,9 @@ import java.util.Map; import java.util.Objects; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; +import java.util.function.Supplier; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -90,6 +106,15 @@ public class LatticeSuggester { /** Whether to try to extend an existing lattice when adding a lattice. */ private final boolean evolve; + /** Generates unique names. */ + private final Supplier nameGenerator = new Supplier() { + final AtomicInteger nextInt = new AtomicInteger(); + + @Override public String get() { + return "$table" + nextInt.incrementAndGet(); + } + }; + /** Creates a LatticeSuggester. */ public LatticeSuggester(FrameworkConfig config) { this.evolve = config.isEvolveLattice(); @@ -526,13 +551,39 @@ private ColRef toColRef(RexNode e, String alias) { } else if (r instanceof TableScan) { final TableScan scan = (TableScan) r; final TableRef tableRef = q.tableRef(scan); - final int fieldCount = r.getRowType().getFieldCount(); - return new Frame(fieldCount, ImmutableList.of(), + final RelDataType rowType = r.getRowType(); + return new Frame(rowType.getFieldCount(), ImmutableList.of(), ImmutableList.of(), ImmutableSet.of(tableRef)) { @Override ColRef column(int offset) { - if (offset >= scan.getTable().getRowType().getFieldCount()) { + if (offset >= rowType.getFieldCount()) { throw new IndexOutOfBoundsException("field " + offset - + " out of range in " + scan.getTable().getRowType()); + + " out of range in " + rowType); + } + return new BaseColRef(tableRef, offset); + } + }; + } else if (r instanceof TableFunctionScan) { + final TableFunctionScan scan = (TableFunctionScan) r; + final RelNode input = scan.getInputs().get(0); + String name = null; + if (scan.getCall() instanceof RexCall) { + final RexCall call = (RexCall) scan.getCall(); + if (call.getOperator() == WrapFunction.INSTANCE) { + name = ((RexLiteral) call.operands.get(1)).getValueAs(String.class); + } + } + if (name == null) { + name = nameGenerator.get(); + } + final RelOptTable dummyTable = new DummyTable(input, name); + final TableRef tableRef = q.tableRef(scan, dummyTable); + final RelDataType rowType = r.getRowType(); + return new Frame(rowType.getFieldCount(), ImmutableList.of(), + ImmutableList.of(), ImmutableSet.of(tableRef)) { + @Override ColRef column(int offset) { + if (offset >= rowType.getFieldCount()) { + throw new IndexOutOfBoundsException("field " + offset + + " out of range in " + rowType); } return new BaseColRef(tableRef, offset); } @@ -554,11 +605,15 @@ private static class Query { } TableRef tableRef(TableScan scan) { + return tableRef(scan, scan.getTable()); + } + + TableRef tableRef(RelNode scan, RelOptTable table) { final TableRef r = tableRefs.get(scan.getId()); if (r != null) { return r; } - final LatticeTable t = space.register(scan.getTable()); + final LatticeTable t = space.register(table); final TableRef r2 = new TableRef(t, tableRefs.size()); tableRefs.put(scan.getId(), r2); return r2; @@ -812,4 +867,43 @@ private MutableMeasure(SqlAggFunction aggregate, boolean distinct, } } + /** Dummy table that represents a RelNode that is to be treated as a leaf + * by the lattice suggester. The name is unique within this run of the + * suggester. */ + private static class DummyTable extends RelOptAbstractTable { + final RelNode scan; + + DummyTable(RelNode scan, String name) { + super(null, name, scan.getRowType()); + this.scan = scan; + } + + @Override public T unwrap(Class clazz) { + if (RelNode.class.isAssignableFrom(clazz) + && clazz.isInstance(scan)) { + return clazz.cast(scan); + } + return super.unwrap(clazz); + } + } + + /** "WRAP" user-defined table function. */ + public static class WrapFunction extends SqlFunction + implements SqlTableFunction { + public static final WrapFunction INSTANCE = new WrapFunction(); + + WrapFunction() { + super("WRAP", SqlKind.OTHER_FUNCTION, ReturnTypes.CURSOR, + null, OperandTypes.VARIADIC, + SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION); + } + + @Override public SqlReturnTypeInference getRowTypeInference() { + return WrapFunction::cursor0; + } + + private static RelDataType cursor0(SqlOperatorBinding opBinding) { + return opBinding.getCursorOperand(0); + } + } } diff --git a/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java b/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java index e9bdde465959..574cd39c0d4a 100644 --- a/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java +++ b/core/src/main/java/org/apache/calcite/prepare/PlannerImpl.java @@ -109,7 +109,9 @@ public PlannerImpl(FrameworkConfig config) { this.programs = config.getPrograms(); this.parserConfig = config.getParserConfig(); this.sqlValidatorConfig = config.getSqlValidatorConfig(); - this.sqlToRelConverterConfig = config.getSqlToRelConverterConfig(); + this.sqlToRelConverterConfig = config.getSqlToRelConverterConfig() + .addPostStep(SqlToRelConverter::flattenTypesStep) + .addPostStep(RelDecorrelator::decorrelateQueryStep); this.state = State.STATE_0_CLOSED; this.traitDefs = config.getTraitDefs(); this.convertletTable = config.getConvertletTable(); @@ -252,7 +254,6 @@ private void ready() { createCatalogReader(), cluster, convertletTable, config); root = sqlToRelConverter.convertQuery(validatedSqlNode, false, true); - root = root.withRel(sqlToRelConverter.flattenTypes(root.rel, true)); final RelBuilder relBuilder = config.getRelBuilderFactory().create(cluster, null); root = root.withRel( diff --git a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java index e8098889ce4e..6f3036cf7503 100644 --- a/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java +++ b/core/src/main/java/org/apache/calcite/rel/core/RelFactories.java @@ -511,7 +511,7 @@ private static class TableFunctionScanFactoryImpl if (call.getOperator() instanceof SqlTableFunction) { final SqlOperatorBinding callBinding = new RexCallBinding(cluster.getTypeFactory(), call.getOperator(), - call.operands, ImmutableList.of()); + call.operands, ImmutableList.of(), inputs); final SqlTableFunction operator = (SqlTableFunction) call.getOperator(); final SqlReturnTypeInference rowTypeInference = operator.getRowTypeInference(); 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 304a33765e9f..0bb111cdc2f6 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexBuilder.java +++ b/core/src/main/java/org/apache/calcite/rex/RexBuilder.java @@ -281,7 +281,7 @@ public RelDataType deriveReturnType( SqlOperator op, List exprs) { return op.inferReturnType( - new RexCallBinding(typeFactory, op, exprs, + new RexCallBinding(typeFactory, op, exprs, ImmutableList.of(), ImmutableList.of())); } diff --git a/core/src/main/java/org/apache/calcite/rex/RexCallBinding.java b/core/src/main/java/org/apache/calcite/rex/RexCallBinding.java index 8883b4dfda21..0738d0004d5e 100644 --- a/core/src/main/java/org/apache/calcite/rex/RexCallBinding.java +++ b/core/src/main/java/org/apache/calcite/rex/RexCallBinding.java @@ -18,6 +18,7 @@ import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelFieldCollation; +import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.runtime.CalciteException; @@ -41,19 +42,28 @@ public class RexCallBinding extends SqlOperatorBinding { //~ Instance fields -------------------------------------------------------- private final List operands; - private final List inputCollations; + private final List inputs; //~ Constructors ----------------------------------------------------------- + @Deprecated // to be removed before 2.0 public RexCallBinding( RelDataTypeFactory typeFactory, SqlOperator sqlOperator, List operands, List inputCollations) { + this(typeFactory, sqlOperator, operands, inputCollations, + ImmutableList.of()); + } + + public RexCallBinding(RelDataTypeFactory typeFactory, SqlOperator sqlOperator, + List operands, List inputCollations, + List inputs) { super(typeFactory, sqlOperator); this.operands = ImmutableList.copyOf(operands); this.inputCollations = ImmutableList.copyOf(inputCollations); + this.inputs = ImmutableList.copyOf(inputs); } /** Creates a binding of the appropriate type. */ @@ -68,7 +78,7 @@ public static RexCallBinding create(RelDataTypeFactory typeFactory, break; } return new RexCallBinding(typeFactory, call.getOperator(), - call.getOperands(), inputCollations); + call.getOperands(), inputCollations, ImmutableList.of()); } //~ Methods ---------------------------------------------------------------- @@ -128,16 +138,18 @@ public List operands() { return operands; } - // implement SqlOperatorBinding @Override public int getOperandCount() { return operands.size(); } - // implement SqlOperatorBinding @Override public RelDataType getOperandType(int ordinal) { return operands.get(ordinal).getType(); } + @Override public RelDataType getCursorOperand(int ordinal) { + return inputs.get(ordinal).getRowType(); + } + @Override public CalciteException newError( Resources.ExInst e) { return SqlUtil.newContextException(SqlParserPos.ZERO, e); @@ -153,7 +165,8 @@ private static class RexCastCallBinding extends RexCallBinding { SqlOperator sqlOperator, List operands, RelDataType type, List inputCollations) { - super(typeFactory, sqlOperator, operands, inputCollations); + super(typeFactory, sqlOperator, operands, inputCollations, + ImmutableList.of()); this.type = type; } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java b/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java index 8448ef4e338e..00823bb249bc 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlCallBinding.java @@ -369,9 +369,8 @@ private T valueAs(SqlNode node, Class clazz) { if (!SqlUtil.isCallTo(operand, SqlStdOperatorTable.ROW)) { return null; } - for (SqlNode id : ((SqlCall) operand).getOperandList()) { - columnList.add(((SqlIdentifier) id).getSimple()); - } + columnList.addAll( + SqlIdentifier.simpleNames(((SqlCall) operand).getOperandList())); return validator.getParentCursor(paramName); } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlHint.java b/core/src/main/java/org/apache/calcite/sql/SqlHint.java index 558afb5159f6..7e7b2c05094f 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlHint.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlHint.java @@ -18,6 +18,7 @@ import org.apache.calcite.sql.parser.SqlParserPos; import org.apache.calcite.util.NlsString; +import org.apache.calcite.util.Util; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; @@ -25,7 +26,6 @@ import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; /** * A SqlHint is a node of a parse tree which represents @@ -111,12 +111,9 @@ public HintOptionFormat getOptionFormat() { */ public List getOptionList() { if (optionFormat == HintOptionFormat.ID_LIST) { - final List attrs = options.getList().stream() - .map(node -> ((SqlIdentifier) node).getSimple()) - .collect(Collectors.toList()); - return ImmutableList.copyOf(attrs); + return ImmutableList.copyOf(SqlIdentifier.simpleNames(options)); } else if (optionFormat == HintOptionFormat.LITERAL_LIST) { - final List attrs = options.getList().stream() + return options.stream() .map(node -> { SqlLiteral literal = (SqlLiteral) node; Comparable comparable = SqlLiteral.value(literal); @@ -124,8 +121,7 @@ public List getOptionList() { ? ((NlsString) comparable).getValue() : comparable.toString(); }) - .collect(Collectors.toList()); - return ImmutableList.copyOf(attrs); + .collect(Util.toImmutableList()); } else { return ImmutableList.of(); } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java index 3db2bad0419e..c0a067d24b3f 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlIdentifier.java @@ -329,6 +329,18 @@ public String getSimple() { return names.get(0); } + /** Returns the simple names in a list of identifiers. + * Assumes that the list consists of are not-null, simple identifiers. */ + public static List simpleNames(List list) { + return Util.transform(list, n -> ((SqlIdentifier) n).getSimple()); + } + + /** Returns the simple names in a iterable of identifiers. + * Assumes that the iterable consists of not-null, simple identifiers. */ + public static Iterable simpleNames(Iterable list) { + return Util.transform(list, n -> ((SqlIdentifier) n).getSimple()); + } + /** * Returns whether this identifier is a star, such as "*" or "foo.bar.*". */ diff --git a/core/src/main/java/org/apache/calcite/sql/SqlNode.java b/core/src/main/java/org/apache/calcite/sql/SqlNode.java index 73341be62a39..343c30410cbe 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlNode.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlNode.java @@ -363,7 +363,7 @@ public static boolean equalDeep(List operands0, * @return a {@code Collector} that collects all the input elements into a * {@link SqlNodeList}, in encounter order */ - public static Collector, SqlNodeList> + public static Collector, SqlNodeList> toList() { return toList(SqlParserPos.ZERO); } @@ -377,9 +377,9 @@ public static boolean equalDeep(List operands0, * @return a {@code Collector} that collects all the input elements into a * {@link SqlNodeList}, in encounter order */ - public static Collector, SqlNodeList> + public static Collector, SqlNodeList> toList(SqlParserPos pos) { return Collector.of(ArrayList::new, ArrayList::add, Util::combine, - list -> new SqlNodeList(list, pos)); + list -> SqlNodeList.of(pos, list)); } } diff --git a/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java b/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java index 0d17ada30365..2f4eebe98b44 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlNodeList.java @@ -26,8 +26,13 @@ import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.RandomAccess; +import javax.annotation.Nonnull; /** * A SqlNodeList is a list of {@link SqlNode}s. It is also a @@ -35,18 +40,14 @@ * * @see SqlNode#toList() */ -public class SqlNodeList extends SqlNode implements Iterable { +public class SqlNodeList extends SqlNode implements List, RandomAccess { //~ Static fields/initializers --------------------------------------------- /** * An immutable, empty SqlNodeList. */ public static final SqlNodeList EMPTY = - new SqlNodeList(SqlParserPos.ZERO) { - @Override public void add(SqlNode node) { - throw new UnsupportedOperationException(); - } - }; + new SqlNodeList(ImmutableList.of(), SqlParserPos.ZERO); /** * A SqlNodeList that has a single element that is an empty list. @@ -66,12 +67,20 @@ public class SqlNodeList extends SqlNode implements Iterable { //~ Constructors ----------------------------------------------------------- + /** Creates a SqlNodeList with a given backing list. + * + *

Because SqlNodeList implements {@link RandomAccess}, the backing list + * should allow O(1) access to elements. */ + private SqlNodeList(SqlParserPos pos, List list) { + super(pos); + this.list = Objects.requireNonNull(list); + } + /** - * Creates an empty SqlNodeList. + * Creates a SqlNodeList that is initially empty. */ public SqlNodeList(SqlParserPos pos) { - super(pos); - list = new ArrayList<>(); + this(pos, new ArrayList<>()); } /** @@ -81,39 +90,132 @@ public SqlNodeList(SqlParserPos pos) { public SqlNodeList( Collection collection, SqlParserPos pos) { - super(pos); - list = new ArrayList<>(collection); + this(pos, new ArrayList<>(collection)); + } + + /** + * Creates a SqlNodeList with a given backing list. + * Does not copy the list. + */ + public static SqlNodeList of(SqlParserPos pos, List list) { + return new SqlNodeList(pos, list); } //~ Methods ---------------------------------------------------------------- - // implement Iterable - @Override public Iterator iterator() { + // List, Collection and Iterable methods + + + @Override public int hashCode() { + return list.hashCode(); + } + + @Override public boolean equals(Object o) { + return this == o + || o instanceof SqlNodeList && list.equals(((SqlNodeList) o).list) + || o instanceof List && list.equals(o); + } + + @Override public boolean isEmpty() { + return list.isEmpty(); + } + + @Override public int size() { + return list.size(); + } + + @Override public @Nonnull Iterator iterator() { return list.iterator(); } - public List getList() { - return list; + @Override public ListIterator listIterator() { + return list.listIterator(); } - public void add(SqlNode node) { - list.add(node); + @Override public ListIterator listIterator(int index) { + return list.listIterator(index); } - @Override public SqlNodeList clone(SqlParserPos pos) { - return new SqlNodeList(list, pos); + @Override public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); } - public SqlNode get(int n) { + @Override public SqlNode get(int n) { return list.get(n); } - public SqlNode set(int n, SqlNode node) { + @Override public SqlNode set(int n, SqlNode node) { return list.set(n, node); } - public int size() { - return list.size(); + @Override public boolean contains(Object o) { + return list.contains(o); + } + + @Override public boolean containsAll(Collection c) { + return list.containsAll(c); + } + + @Override public int indexOf(Object o) { + return list.indexOf(o); + } + + @Override public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + @Override public @Nonnull SqlNode[] toArray() { + return list.toArray(new SqlNode[0]); + } + + @Override public @Nonnull T[] toArray(T[] a) { + return list.toArray(a); + } + + @Override public boolean add(SqlNode node) { + return list.add(node); + } + + @Override public void add(int index, SqlNode element) { + list.add(index, element); + } + + @Override public boolean addAll(Collection c) { + return list.addAll(c); + } + + @Override public boolean addAll(int index, Collection c) { + return list.addAll(index, c); + } + + @Override public void clear() { + list.clear(); + } + + @Override public boolean remove(Object o) { + return list.remove(o); + } + + @Override public SqlNode remove(int index) { + return list.remove(index); + } + + @Override public boolean removeAll(Collection c) { + return list.removeAll(c); + } + + @Override public boolean retainAll(Collection c) { + return list.retainAll(c); + } + + // SqlNodeList-specific methods + + public List getList() { + return list; + } + + @Override public SqlNodeList clone(SqlParserPos pos) { + return new SqlNodeList(list, pos); } @Override public void unparse( @@ -165,40 +267,30 @@ void andOrList(SqlWriter writer, SqlBinaryOperator sepOp) { return litmus.succeed(); } - public SqlNode[] toArray() { - return list.toArray(new SqlNode[0]); - } - public static boolean isEmptyList(final SqlNode node) { - if (node instanceof SqlNodeList) { - if (0 == ((SqlNodeList) node).size()) { - return true; - } - } - return false; + return node instanceof SqlNodeList + && ((SqlNodeList) node).isEmpty(); } public static SqlNodeList of(SqlNode node1) { - SqlNodeList list = new SqlNodeList(SqlParserPos.ZERO); + final List list = new ArrayList<>(1); list.add(node1); - return list; + return new SqlNodeList(SqlParserPos.ZERO, list); } public static SqlNodeList of(SqlNode node1, SqlNode node2) { - SqlNodeList list = new SqlNodeList(SqlParserPos.ZERO); + final List list = new ArrayList<>(2); list.add(node1); list.add(node2); - return list; + return new SqlNodeList(SqlParserPos.ZERO, list); } public static SqlNodeList of(SqlNode node1, SqlNode node2, SqlNode... nodes) { - SqlNodeList list = new SqlNodeList(SqlParserPos.ZERO); + final List list = new ArrayList<>(nodes.length + 2); list.add(node1); list.add(node2); - for (SqlNode node : nodes) { - list.add(node); - } - return list; + Collections.addAll(list, nodes); + return new SqlNodeList(SqlParserPos.ZERO, list); } @Override public void validateExpr(SqlValidator validator, SqlValidatorScope scope) { diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java index 5c93326c6dee..7f746c50183a 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java @@ -232,39 +232,52 @@ public int getRightPrec() { public abstract SqlSyntax getSyntax(); /** - * Creates a call to this operator with an array of operands. + * Creates a call to this operator with a list of operands. * *

The position of the resulting call is the union of the {@code pos} * and the positions of all of the operands. * * @param functionQualifier Function qualifier (e.g. "DISTINCT"), or null * @param pos Parser position of the identifier of the call - * @param operands Array of operands + * @param operands List of operands */ - public SqlCall createCall( + public final SqlCall createCall( SqlLiteral functionQualifier, SqlParserPos pos, - SqlNode... operands) { - pos = pos.plusAll(Arrays.asList(operands)); - return new SqlBasicCall(this, operands, pos, false, functionQualifier); + Iterable operands) { + return createCall(functionQualifier, pos, + Iterables.toArray(operands, SqlNode.class)); } /** - * Creates a call to this operator with a list of operands. + * Creates a call to this operator with an array of operands. * *

The position of the resulting call is the union of the {@code pos} * and the positions of all of the operands. * * @param functionQualifier Function qualifier (e.g. "DISTINCT"), or null * @param pos Parser position of the identifier of the call - * @param operands List of operands + * @param operands Array of operands */ + public SqlCall createCall( + SqlLiteral functionQualifier, + SqlParserPos pos, + SqlNode... operands) { + pos = pos.plusAll(Arrays.asList(operands)); + return new SqlBasicCall(this, operands, pos, false, functionQualifier); + } + + /** Not supported. Choose between + * {@link #createCall(SqlLiteral, SqlParserPos, SqlNode...)} and + * {@link #createCall(SqlParserPos, List)}. The ambiguity arises because + * {@link SqlNodeList} extends {@link SqlNode} + * and also implements {@code List}. */ + @Deprecated public final SqlCall createCall( SqlLiteral functionQualifier, SqlParserPos pos, - Iterable operands) { - return createCall(functionQualifier, pos, - Iterables.toArray(operands, SqlNode.class)); + SqlNodeList operands) { + throw new UnsupportedOperationException(); } /** @@ -315,6 +328,18 @@ public final SqlCall createCall( operandList.toArray(new SqlNode[0])); } + /** Not supported. Choose between + * {@link #createCall(SqlParserPos, SqlNode...)} and + * {@link #createCall(SqlParserPos, List)}. The ambiguity arises because + * {@link SqlNodeList} extends {@link SqlNode} + * and also implements {@code List}. */ + @Deprecated + public SqlCall createCall( + SqlParserPos pos, + SqlNodeList operands) { + throw new UnsupportedOperationException(); + } + /** * Rewrites a call to this operator. Some operators are implemented as * trivial rewrites (e.g. NULLIF becomes CASE). However, we don't do this at diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java b/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java index fce400a630ff..f6e81f4e96f6 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlOperatorBinding.java @@ -219,8 +219,6 @@ public List collectOperandTypes() { * Returns the rowtype of the ordinalth operand, which is a * cursor. * - *

This is only implemented for {@link SqlCallBinding}. - * * @param ordinal Ordinal of the operand * @return Rowtype of the query underlying the cursor */ diff --git a/core/src/main/java/org/apache/calcite/sql/SqlPivot.java b/core/src/main/java/org/apache/calcite/sql/SqlPivot.java index dce7dec5c855..a96a8b3a692e 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlPivot.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlPivot.java @@ -101,7 +101,7 @@ public SqlPivot(SqlParserPos pos, SqlNode query, SqlNodeList aggList, } private static SqlNodeList stripList(SqlNodeList list) { - return list.getList().stream().map(SqlPivot::strip) + return list.stream().map(SqlPivot::strip) .collect(SqlNode.toList(list.pos)); } @@ -152,7 +152,7 @@ public void forEachNameValues(BiConsumer consumer) { private static String pivotAlias(SqlNode node) { if (node instanceof SqlNodeList) { - return ((SqlNodeList) node).getList().stream() + return ((SqlNodeList) node).stream() .map(SqlPivot::pivotAlias).collect(Collectors.joining("_")); } return node.toString(); diff --git a/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java b/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java index 82a1d75117f0..83f0e69b1330 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlWindowTableFunction.java @@ -16,6 +16,7 @@ */ package org.apache.calcite.sql; +import org.apache.calcite.linq4j.Ord; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rel.type.RelDataTypeFactory; import org.apache.calcite.rel.type.RelDataTypeField; @@ -233,13 +234,13 @@ boolean checkIntervalOperands(SqlCallBinding callBinding, int startPos) { void validateColumnNames(SqlValidator validator, List fieldNames, List columnNames) { final SqlNameMatcher matcher = validator.getCatalogReader().nameMatcher(); - for (SqlNode columnName : columnNames) { - final String name = ((SqlIdentifier) columnName).getSimple(); + Ord.forEach(SqlIdentifier.simpleNames(columnNames), (name, i) -> { if (matcher.indexOf(fieldNames, name) < 0) { + final SqlIdentifier columnName = (SqlIdentifier) columnNames.get(i); throw SqlUtil.newContextException(columnName.getParserPosition(), RESOURCE.unknownIdentifier(name)); } - } + }); } } } diff --git a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateFunction.java b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateFunction.java index 5e4929d9bbd5..72cc2040fce7 100644 --- a/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateFunction.java +++ b/core/src/main/java/org/apache/calcite/sql/ddl/SqlCreateFunction.java @@ -82,7 +82,7 @@ public SqlCreateFunction(SqlParserPos pos, boolean replace, @SuppressWarnings("unchecked") private List> pairs() { - return Util.pairs((List) usingList.getList()); + return Util.pairs((List) usingList); } @Override public SqlOperator getOperator() { diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCase.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCase.java index 2c7f5b2748c9..5e7691fe4fdc 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCase.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCase.java @@ -71,16 +71,15 @@ public SqlCase(SqlParserPos pos, SqlNode value, SqlNodeList whenList, public static SqlCase createSwitched(SqlParserPos pos, SqlNode value, SqlNodeList whenList, SqlNodeList thenList, SqlNode elseClause) { if (null != value) { - List list = whenList.getList(); - for (int i = 0; i < list.size(); i++) { - SqlNode e = list.get(i); + for (int i = 0; i < whenList.size(); i++) { + SqlNode e = whenList.get(i); final SqlCall call; if (e instanceof SqlNodeList) { call = SqlStdOperatorTable.IN.createCall(pos, value, e); } else { call = SqlStdOperatorTable.EQUALS.createCall(pos, value, e); } - list.set(i, call); + whenList.set(i, call); } } diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java b/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java index bdbfb06aec29..dceddfd2e1f6 100644 --- a/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java +++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlCaseOperator.java @@ -231,8 +231,7 @@ private RelDataType inferTypeFromValidator( final SqlNodeList whenOperands = caseCall.getWhenOperands(); final RelDataTypeFactory typeFactory = callBinding.getTypeFactory(); - final int size = thenList.getList().size(); - for (int i = 0; i < size; i++) { + for (int i = 0; i < thenList.size(); i++) { SqlNode node = thenList.get(i); RelDataType type = SqlTypeUtil.deriveType(callBinding, node); SqlNode operand = whenOperands.get(i); diff --git a/core/src/main/java/org/apache/calcite/sql/parser/Span.java b/core/src/main/java/org/apache/calcite/sql/parser/Span.java index 062a9c8fc876..07d5a125c7d3 100644 --- a/core/src/main/java/org/apache/calcite/sql/parser/Span.java +++ b/core/src/main/java/org/apache/calcite/sql/parser/Span.java @@ -17,6 +17,7 @@ package org.apache.calcite.sql.parser; import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; import java.util.ArrayList; import java.util.Collection; @@ -78,6 +79,13 @@ public static Span of(Collection nodes) { return new Span().addAll(nodes); } + /** Creates a Span of a node list. */ + public static Span of(SqlNodeList nodeList) { + // SqlNodeList has its own position, so just that position, not all of the + // constituent nodes. + return new Span().add(nodeList); + } + /** Adds a node's position to the list, * and returns this Span. */ public Span add(SqlNode n) { diff --git a/core/src/main/java/org/apache/calcite/sql/util/SqlShuttle.java b/core/src/main/java/org/apache/calcite/sql/util/SqlShuttle.java index a38d1b635e7e..629ab6d1cd71 100644 --- a/core/src/main/java/org/apache/calcite/sql/util/SqlShuttle.java +++ b/core/src/main/java/org/apache/calcite/sql/util/SqlShuttle.java @@ -69,10 +69,8 @@ public class SqlShuttle extends SqlBasicVisitor { @Override public SqlNode visit(SqlNodeList nodeList) { boolean update = false; - List exprs = nodeList.getList(); - int exprCount = exprs.size(); - List newList = new ArrayList<>(exprCount); - for (SqlNode operand : exprs) { + final List newList = new ArrayList<>(nodeList.size()); + for (SqlNode operand : nodeList) { SqlNode clonedOperand; if (operand == null) { clonedOperand = null; @@ -85,7 +83,7 @@ public class SqlShuttle extends SqlBasicVisitor { newList.add(clonedOperand); } if (update) { - return new SqlNodeList(newList, nodeList.getParserPosition()); + return SqlNodeList.of(nodeList.getParserPosition(), newList); } else { return nodeList; } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java b/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java index 3a4ca99e0149..d4b8177a7ec8 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AggFinder.java @@ -18,6 +18,7 @@ import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlNodeList; import org.apache.calcite.sql.SqlOperatorTable; import org.apache.calcite.util.Util; @@ -64,6 +65,12 @@ public SqlCall findAgg(SqlNode node) { } } + // SqlNodeList extends SqlNode and implements List, so this method + // disambiguates + public SqlCall findAgg(SqlNodeList nodes) { + return findAgg((List) nodes); + } + public SqlCall findAgg(List nodes) { try { for (SqlNode node : nodes) { diff --git a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java index fc2973688fff..65227d04a257 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/AliasNamespace.java @@ -95,9 +95,7 @@ protected AliasNamespace( } else { // Alias is 'AS t (c0, ..., cN)' final List columnNames = Util.skip(operands, 2); - final List nameList = - Util.transform(columnNames, operand -> - ((SqlIdentifier) operand).getSimple()); + final List nameList = SqlIdentifier.simpleNames(columnNames); final int i = Util.firstDuplicate(nameList); if (i >= 0) { final SqlIdentifier id = (SqlIdentifier) columnNames.get(i); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java index 1c823d1fcba4..9d7794557fc3 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java @@ -449,8 +449,8 @@ private static SqlNode expandExprFromJoin(SqlJoin join, SqlIdentifier identifier return identifier; } - for (SqlNode node : (SqlNodeList) join.getCondition()) { - final String name = ((SqlIdentifier) node).getSimple(); + for (String name + : SqlIdentifier.simpleNames((SqlNodeList) join.getCondition())) { if (identifier.getSimple().equals(name)) { final List qualifiedNode = new ArrayList<>(); for (ScopeChild child : scope.children) { @@ -492,8 +492,8 @@ public List usingNames(SqlJoin join) { case USING: final ImmutableList.Builder list = ImmutableList.builder(); final Set names = catalogReader.nameMatcher().createSet(); - for (SqlNode node : (SqlNodeList) join.getCondition()) { - final String name = ((SqlIdentifier) node).getSimple(); + for (String name + : SqlIdentifier.simpleNames((SqlNodeList) join.getCondition())) { if (names.add(name)) { list.add(name); } @@ -1229,8 +1229,6 @@ protected SqlNode performUnconditionalRewrites( return null; } - SqlNode newOperand; - // first transform operands and invoke generic call rewrite if (node instanceof SqlCall) { if (node instanceof SqlMerge) { @@ -1251,7 +1249,7 @@ protected SqlNode performUnconditionalRewrites( } else { childUnderFrom = false; } - newOperand = + SqlNode newOperand = performUnconditionalRewrites(operand, childUnderFrom); if (newOperand != null && newOperand != operand) { call.setOperand(i, newOperand); @@ -1278,15 +1276,15 @@ protected SqlNode performUnconditionalRewrites( node = call.getOperator().rewriteCall(this, call); } } else if (node instanceof SqlNodeList) { - SqlNodeList list = (SqlNodeList) node; - for (int i = 0, count = list.size(); i < count; i++) { + final SqlNodeList list = (SqlNodeList) node; + for (int i = 0; i < list.size(); i++) { SqlNode operand = list.get(i); - newOperand = + SqlNode newOperand = performUnconditionalRewrites( operand, false); if (newOperand != null) { - list.getList().set(i, newOperand); + list.set(i, newOperand); } } } @@ -1915,11 +1913,11 @@ protected void inferUnknownTypes( final RelDataType whenType = caseCall.getValueOperand() == null ? booleanType : unknownType; - for (SqlNode sqlNode : caseCall.getWhenOperands().getList()) { + for (SqlNode sqlNode : caseCall.getWhenOperands()) { inferUnknownTypes(whenType, scope, sqlNode); } RelDataType returnType = deriveType(scope, node); - for (SqlNode sqlNode : caseCall.getThenOperands().getList()) { + for (SqlNode sqlNode : caseCall.getThenOperands()) { inferUnknownTypes(returnType, scope, sqlNode); } @@ -3840,11 +3838,10 @@ private boolean isSortCompatible(SelectScope scope, SqlNode node, } } + @SuppressWarnings({"unchecked", "rawtypes"}) protected void validateWindowClause(SqlSelect select) { final SqlNodeList windowList = select.getWindowList(); - @SuppressWarnings("unchecked") final List windows = - (List) windowList.getList(); - if (windows.isEmpty()) { + if (windowList.isEmpty()) { return; } @@ -3853,7 +3850,7 @@ protected void validateWindowClause(SqlSelect select) { // 1. ensure window names are simple // 2. ensure they are unique within this scope - for (SqlWindow window : windows) { + for (SqlWindow window : (List) (List) windowList) { SqlIdentifier declName = window.getDeclName(); if (!declName.isSimple()) { throw newValidationError(declName, RESOURCE.windowNameMustBeSimple()); @@ -3868,17 +3865,17 @@ protected void validateWindowClause(SqlSelect select) { // 7.10 rule 2 // Check for pairs of windows which are equivalent. - for (int i = 0; i < windows.size(); i++) { - SqlNode window1 = windows.get(i); - for (int j = i + 1; j < windows.size(); j++) { - SqlNode window2 = windows.get(j); + for (int i = 0; i < windowList.size(); i++) { + SqlNode window1 = windowList.get(i); + for (int j = i + 1; j < windowList.size(); j++) { + SqlNode window2 = windowList.get(j); if (window1.equalsDeep(window2, Litmus.IGNORE)) { throw newValidationError(window2, RESOURCE.dupWindowSpec()); } } } - for (SqlWindow window : windows) { + for (SqlWindow window : (List) (List) windowList) { final SqlNodeList expandedOrderList = (SqlNodeList) expand(window.getOrderList(), windowScope); window.setOrderList(expandedOrderList); @@ -3908,7 +3905,7 @@ protected void validateWindowClause(SqlSelect select) { RESOURCE.columnCountMismatch()); } SqlValidatorUtil.checkIdentifierListForDuplicates( - withItem.columnList.getList(), validationErrorFunction); + withItem.columnList, validationErrorFunction); } else { // Luckily, field names have not been make unique yet. final List fieldNames = @@ -4544,21 +4541,21 @@ private void checkConstraint( SqlValidatorUtil.mapNameToIndex(tableRowType.getFieldList()); // Validate update values against the view constraint. - final List targets = update.getTargetColumnList().getList(); - final List sources = update.getSourceExpressionList().getList(); - for (final Pair column : Pair.zip(targets, sources)) { - final String columnName = ((SqlIdentifier) column.left).getSimple(); + final List targetNames = + SqlIdentifier.simpleNames(update.getTargetColumnList()); + final List sources = update.getSourceExpressionList(); + Pair.forEach(targetNames, sources, (columnName, expr) -> { final Integer columnIndex = nameToIndex.get(columnName); if (projectMap.containsKey(columnIndex)) { final RexNode columnConstraint = projectMap.get(columnIndex); final ValidationError validationError = - new ValidationError(column.right, + new ValidationError(expr, RESOURCE.viewConstraintNotSatisfied(columnName, Util.last(validatorTable.getQualifiedName()))); - RelOptUtil.validateValueAgainstConstraint(column.right, + RelOptUtil.validateValueAgainstConstraint(expr, columnConstraint, validationError); } - } + }); } } @@ -5261,7 +5258,7 @@ SqlValidatorNamespace lookupFieldNamespace(RelDataType rowType, String name) { RESOURCE.cannotUseWithinWithoutOrderBy()); } - SqlNode firstOrderByColumn = orderBy.getList().get(0); + SqlNode firstOrderByColumn = orderBy.get(0); SqlIdentifier identifier; if (firstOrderByColumn instanceof SqlBasicCall) { identifier = (SqlIdentifier) ((SqlBasicCall) firstOrderByColumn).getOperands()[0]; @@ -5388,7 +5385,7 @@ private SqlNode navigationInMeasure(SqlNode node, boolean allRows) { private void validateDefinitions(SqlMatchRecognize mr, MatchRecognizeScope scope) { final Set aliases = catalogReader.nameMatcher().createSet(); - for (SqlNode item : mr.getPatternDefList().getList()) { + for (SqlNode item : mr.getPatternDefList()) { final String alias = alias(item); if (!aliases.add(alias)) { throw newValidationError(item, @@ -5398,7 +5395,7 @@ private void validateDefinitions(SqlMatchRecognize mr, } final List sqlNodes = new ArrayList<>(); - for (SqlNode item : mr.getPatternDefList().getList()) { + for (SqlNode item : mr.getPatternDefList()) { final String alias = alias(item); SqlNode expand = expand(item, scope); expand = navigationInDefine(expand, alias); @@ -5581,7 +5578,7 @@ private SqlNode navigationInDefine(SqlNode node, String alpha) { case IGNORED: // rewrite the order list to empty if (orderList != null) { - orderList.getList().clear(); + orderList.clear(); } break; case FORBIDDEN: diff --git a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java index f2409d5b4d95..15200457b78c 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorUtil.java @@ -175,11 +175,10 @@ public static List getExtendedColumns( /** Converts a list of extended columns * (of the form [name0, type0, name1, type1, ...]) * into a list of (name, type) pairs. */ + @SuppressWarnings({"unchecked", "rawtypes"}) private static List> pairs( SqlNodeList extendedColumns) { - final List list = extendedColumns.getList(); - //noinspection unchecked - return Util.pairs(list); + return Util.pairs((List) extendedColumns); } /** diff --git a/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java index 3ff1ed667c57..2d6b9a459970 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/TableNamespace.java @@ -86,7 +86,7 @@ private TableNamespace(SqlValidatorImpl validator, SqlValidatorTable table, * be present if you ask for them. Phoenix uses them, for instance, to access * rarely used fields in the underlying HBase table. */ public TableNamespace extend(SqlNodeList extendList) { - final List identifierList = Util.quotientList(extendList.getList(), 2, 0); + final List identifierList = Util.quotientList(extendList, 2, 0); SqlValidatorUtil.checkIdentifierListForDuplicates( identifierList, validator.getValidationErrorFunction()); final ImmutableList.Builder builder = @@ -147,7 +147,7 @@ private void checkExtendedColumnTypes(SqlNodeList extendList) { if (!extType.equals(baseType)) { // Get the extended column node that failed validation. final SqlNode extColNode = - Iterables.find(extendList.getList(), + Iterables.find(extendList, sqlNode -> sqlNode instanceof SqlIdentifier && Util.last(((SqlIdentifier) sqlNode).names).equals( extendedField.getName())); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java index cd3fc4c88088..e69c9e9a7624 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/WithItemNamespace.java @@ -43,11 +43,9 @@ class WithItemNamespace extends AbstractNamespace { } final RelDataTypeFactory.Builder builder = validator.getTypeFactory().builder(); - for (Pair pair - : Pair.zip(withItem.columnList, rowType.getFieldList())) { - builder.add(((SqlIdentifier) pair.left).getSimple(), - pair.right.getType()); - } + Pair.forEach(SqlIdentifier.simpleNames(withItem.columnList), + rowType.getFieldList(), + (name, field) -> builder.add(name, field.getType())); return builder.build(); } diff --git a/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java b/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java index 364116a4e5ce..a9672bffe269 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/WithNamespace.java @@ -53,7 +53,7 @@ public class WithNamespace extends AbstractNamespace { validator.validateWithItem((SqlWithItem) withItem); } final SqlValidatorScope scope2 = - validator.getWithScope(Util.last(with.withList.getList())); + validator.getWithScope(Util.last(with.withList)); validator.validateQuery(with.body, scope2, targetRowType); final RelDataType rowType = validator.getValidatedNodeType(with.body); validator.setValidatedNodeType(with, rowType); diff --git a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java index 858bb3c50917..e5593bc83223 100644 --- a/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java +++ b/core/src/main/java/org/apache/calcite/sql/validate/implicit/AbstractTypeCoercion.java @@ -153,7 +153,7 @@ protected boolean coerceColumnType( // when expanding star/dynamic-star. // See SqlToRelConverter#convertSelectList for details. - if (index >= nodeList.getList().size()) { + if (index >= nodeList.size()) { // Can only happen when there is a star(*) in the column, // just return true. return true; diff --git a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java index 5a6b364d8928..771765fbfcdd 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/RelDecorrelator.java @@ -33,6 +33,7 @@ import org.apache.calcite.rel.RelCollation; import org.apache.calcite.rel.RelHomogeneousShuttle; import org.apache.calcite.rel.RelNode; +import org.apache.calcite.rel.RelRoot; import org.apache.calcite.rel.core.Aggregate; import org.apache.calcite.rel.core.AggregateCall; import org.apache.calcite.rel.core.Correlate; @@ -228,6 +229,12 @@ public static RelNode decorrelateQuery(RelNode rootRel, return newRootRel; } + /** A {@link SqlToRelConverter.PostStep} that decorrelates a query. */ + public static RelRoot decorrelateQueryStep(RelBuilder relBuilder, + RelRoot root) { + return root.withRel(decorrelateQuery(root.rel, relBuilder)); + } + private void setCurrent(RelNode root, Correlate corRel) { currentRel = corRel; if (corRel != null) { 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 39ca7dd030b1..353d937af818 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/SqlToRelConverter.java @@ -205,6 +205,7 @@ import java.util.function.UnaryOperator; import java.util.stream.Collectors; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import static org.apache.calcite.sql.SqlUtil.stripAs; @@ -225,6 +226,8 @@ public class SqlToRelConverter { ImmutableBeans.create(Config.class) .withRelBuilderFactory(RelFactories.LOGICAL_BUILDER) .withRelBuilderConfigTransform(c -> c.withPushJoinCondition(true)) + .withPostStep(PostStep::identity) + .withFromTrace(FromTrace::noOp) .withHintStrategyTable(HintStrategyTable.EMPTY); protected static final Logger SQL2REL_LOGGER = @@ -467,6 +470,18 @@ private void checkConvertedType(SqlNode query, RelNode result) { } } + /** A {@link PostStep} that flattens types. + * + *

Similar to {@link #flattenTypes(RelNode, boolean)} but static, and + * therefore cannot expand views. */ + public static RelRoot flattenTypesStep(RelBuilder relBuilder, RelRoot root) { + final RelOptCluster cluster = relBuilder.getCluster(); + RelStructuredTypeFlattener typeFlattener = + new RelStructuredTypeFlattener(relBuilder, relBuilder.getRexBuilder(), + ViewExpanders.simpleContext(cluster), true); + return root.withRel(typeFlattener.rewrite(root.rel)); + } + public RelNode flattenTypes( RelNode rootRel, boolean restructure) { @@ -597,9 +612,12 @@ public RelRoot convertQuery( } // propagate the hints. result = RelOptUtil.propagateRelHints(result, false); - return RelRoot.of(result, validatedRowType, query.getKind()) + final RelRoot root = RelRoot.of(result, validatedRowType, query.getKind()) .withCollation(collation) .withHints(hints); + + // Apply post-steps. The default post-steps flatten types and decorrelate. + return config.postStep().apply(relBuilder, root); } private static boolean isStream(SqlNode query) { @@ -833,7 +851,7 @@ protected void convertOrder( SqlNode fetch) { if (removeSortInSubQuery(bb.top) || select.getOrderList() == null - || select.getOrderList().getList().isEmpty()) { + || select.getOrderList().isEmpty()) { assert removeSortInSubQuery(bb.top) || collation.getFieldCollations().isEmpty(); if ((offset == null || (offset instanceof SqlLiteral @@ -1389,7 +1407,7 @@ private RexNode translateIn(RelOptUtil.Logic logic, RelNode root, } private static boolean containsNullLiteral(SqlNodeList valueList) { - for (SqlNode node : valueList.getList()) { + for (SqlNode node : valueList) { if (node instanceof SqlLiteral) { SqlLiteral lit = (SqlLiteral) node; if (lit.getValue() == null) { @@ -1627,7 +1645,7 @@ private RelNode convertQueryOrInList( return convertRowValues( bb, seek, - ((SqlNodeList) seek).getList(), + (SqlNodeList) seek, false, targetRowType); } else { @@ -2060,10 +2078,20 @@ private RexNode convertOver(Blackboard bb, SqlNode node) { } } - protected void convertFrom( + protected final void convertFrom( Blackboard bb, SqlNode from) { - convertFrom(bb, from, Collections.emptyList()); + convertFrom2(bb, from, null); + } + + protected final void convertFrom2( + Blackboard bb, + SqlNode from, + List fieldNames) { + convertFrom(bb, from, fieldNames); + final SqlValidatorNamespace namespace = + from == null ? null : validator.getNamespace(from); + config.fromTrace().apply(from, namespace, fieldNames, bb.root); } /** @@ -2082,12 +2110,12 @@ protected void convertFrom( *

  • a query ("(SELECT * FROM EMP WHERE GENDER = 'F')"), *
  • or any combination of the above. * - * @param fieldNames Field aliases, usually come from AS clause + * @param fieldNames Field aliases, usually come from AS clause, or null */ protected void convertFrom( Blackboard bb, SqlNode from, - List fieldNames) { + @Nullable List fieldNames) { if (from == null) { bb.setRoot(LogicalValues.createOneRow(cluster), false); return; @@ -2099,14 +2127,10 @@ protected void convertFrom( case AS: call = (SqlCall) from; SqlNode firstOperand = call.operand(0); - final List fieldNameList = new ArrayList<>(); - if (call.operandCount() > 2) { - for (SqlNode node : Util.skip(call.getOperandList(), 2)) { - fieldNameList.add(((SqlIdentifier) node).getSimple()); - } - - } - convertFrom(bb, firstOperand, fieldNameList); + final List fieldNameList = call.operandCount() > 2 + ? SqlIdentifier.simpleNames(Util.skip(call.getOperandList(), 2)) + : null; + convertFrom2(bb, firstOperand, fieldNameList); return; case MATCH_RECOGNIZE: @@ -2118,7 +2142,8 @@ protected void convertFrom( return; case WITH_ITEM: - convertFrom(bb, ((SqlWithItem) from).query); + final SqlWithItem withItem = (SqlWithItem) from; + convertFrom2(bb, withItem.query, null); return; case WITH: @@ -2188,7 +2213,7 @@ protected void convertFrom( case VALUES: convertValuesImpl(bb, (SqlCall) from, null); - if (fieldNames.size() > 0) { + if (fieldNames != null) { bb.setRoot(relBuilder.push(bb.root).rename(fieldNames).build(), true); } return; @@ -2237,7 +2262,7 @@ private void convertUnnest(Blackboard bb, SqlCall call, List fieldNames) .push(child) .project(exprs) .uncollect(Collections.emptyList(), operator.withOrdinality) - .rename(fieldNames) + .applyIf(fieldNames != null, r -> r.rename(fieldNames)) .build(); } bb.setRoot(uncollect, true); @@ -2335,12 +2360,9 @@ protected void convertMatchRecognize(Blackboard bb, List operands = ((SqlCall) node).getOperandList(); SqlIdentifier left = (SqlIdentifier) operands.get(0); patternVarsSet.add(left.getSimple()); - SqlNodeList rights = (SqlNodeList) operands.get(1); - final TreeSet list = new TreeSet<>(); - for (SqlNode right : rights) { - assert right instanceof SqlIdentifier; - list.add(((SqlIdentifier) right).getSimple()); - } + final SqlNodeList rights = (SqlNodeList) operands.get(1); + final TreeSet list = + new TreeSet<>(SqlIdentifier.simpleNames(rights.getList())); subsetMap.put(left.getSimple(), list); } @@ -2481,7 +2503,7 @@ protected void convertPivot(Blackboard bb, SqlPivot pivot) { pivot.forEachNameValues((alias, nodeList) -> valueList.add( Pair.of(alias, - nodeList.getList().stream().map(bb::convertExpression) + nodeList.stream().map(bb::convertExpression) .collect(Util.toImmutableList())))); final RelNode rel = @@ -2936,16 +2958,9 @@ private RexNode convertUsingCondition( SqlJoin join, SqlValidatorNamespace leftNamespace, SqlValidatorNamespace rightNamespace) { - SqlNode condition = join.getCondition(); - - final SqlNodeList list = (SqlNodeList) condition; - final List nameList = new ArrayList<>(); - for (SqlNode columnName : list) { - final SqlIdentifier id = (SqlIdentifier) columnName; - String name = id.getSimple(); - nameList.add(name); - } - return convertUsing(leftNamespace, rightNamespace, nameList); + final SqlNodeList list = (SqlNodeList) join.getCondition(); + return convertUsing(leftNamespace, rightNamespace, + ImmutableList.copyOf(SqlIdentifier.simpleNames(list))); } /** @@ -5562,8 +5577,7 @@ private void translateAgg(SqlCall call, SqlNode filter, collation = RelCollations.EMPTY; } else { collation = RelCollations.of( - orderList.getList() - .stream() + orderList.stream() .map(order -> bb.convertSortExpression(order, RelFieldCollation.Direction.ASCENDING, @@ -5791,7 +5805,7 @@ private class HistogramShuttle extends RexShuttle { rexBuilder.getTypeFactory(), SqlStdOperatorTable.HISTOGRAM_AGG, exprs, - ImmutableList.of()); + ImmutableList.of(), ImmutableList.of()); RexNode over = rexBuilder.makeOver( @@ -5938,7 +5952,7 @@ private static class AggregateFinder extends SqlBasicVisitor { final SqlNode aggCall = call.getOperandList().get(0); final SqlNodeList orderList = (SqlNodeList) call.getOperandList().get(1); list.add(aggCall); - orderList.getList().forEach(this.orderList::add); + this.orderList.addAll(orderList); return null; } @@ -6100,6 +6114,59 @@ default Config addRelBuilderConfigTransform( /** Sets {@link #getHintStrategyTable()}. */ Config withHintStrategyTable(HintStrategyTable hintStrategyTable); + + /** Returns the transform to apply at the end. + * Default is the identity. */ + @ImmutableBeans.Property(required = true) + PostStep postStep(); + + /** Sets {@link #postStep()}. */ + Config withPostStep(PostStep step); + + /** Adds a transform to {@link #postStep()}. */ + default Config addPostStep(PostStep step) { + return withPostStep((RelBuilder b, RelRoot r) -> + step.apply(b, postStep().apply(b, r))); + } + + /** Returns the tracer to be called after converting a FROM item. + * Default is a no-op. */ + @ImmutableBeans.Property(required = true) + FromTrace fromTrace(); + + /** Sets {@link #fromTrace()}. */ + Config withFromTrace(FromTrace fromTrace); + } + + /** A transform that is applied by SqlToRelConverter just before returning + * the relational expression. + * + *

    The list of post-steps is defined in the Config by calling + * {@link Config#withPostStep(PostStep)}. The default post-step returns root + * unchanged. + * + *

    You can {@link Config#addPostStep add} built-in post-steps to + * {@link #flattenTypesStep flatten types} and + * {@link RelDecorrelator#decorrelateQueryStep decorrelate}. + */ + @FunctionalInterface + public interface PostStep { + RelRoot apply(RelBuilder relBuilder, RelRoot root); + + static RelRoot identity(RelBuilder relBuilder, RelRoot root) { + return root; + } + } + + /** Called after converting an item in the FROM clause. */ + @FunctionalInterface + public interface FromTrace { + void apply(SqlNode node, SqlValidatorNamespace namespace, + List fieldNames, RelNode root); + + static void noOp(SqlNode node, SqlValidatorNamespace namespace, + List fieldNames, RelNode r) { + } } /** Builder for a {@link Config}. */ diff --git a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java index eb4ecc597e1a..e1bc9848161d 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java @@ -756,7 +756,7 @@ public RexNode convertAggregateFunction( if (returnType == null) { RexCallBinding binding = new RexCallBinding(cx.getTypeFactory(), fun, exprs, - ImmutableList.of()) { + ImmutableList.of(), ImmutableList.of()) { @Override public int getGroupCount() { return groupCount; } @@ -1037,10 +1037,8 @@ public RexNode convertRow( } final RexBuilder rexBuilder = cx.getRexBuilder(); final List columns = new ArrayList<>(); - for (SqlNode operand : call.getOperandList()) { - columns.add( - rexBuilder.makeLiteral( - ((SqlIdentifier) operand).getSimple())); + for (String operand : SqlIdentifier.simpleNames(call.getOperandList())) { + columns.add(rexBuilder.makeLiteral(operand)); } final RelDataType type = rexBuilder.deriveReturnType(SqlStdOperatorTable.COLUMN_LIST, columns); 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 11b77d82c3e7..df7065125b53 100644 --- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java +++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java @@ -220,6 +220,39 @@ public RelBuilder transform(UnaryOperator transform) { return new RelBuilder(context, cluster, relOptSchema); } + /** Performs an action on this RelBuilder if a condition is true. + * + *

    For example, consider the following code: + * + *

    +   *   RelNode filterAndRename(RelBuilder relBuilder, RelNode rel,
    +   *       RexNode condition, List<String> fieldNames) {
    +   *     relBuilder.push(rel)
    +   *         .filter(condition);
    +   *     if (fieldNames != null) {
    +   *       relBuilder.rename(fieldNames);
    +   *     }
    +   *     return relBuilder
    +   *         .build();
    + *
    + * + *

    The pipeline is disruptived by the 'if'. The {@code applyIf} method + * allows you to perform the flow as a single pipeline: + * + *

    +   *   RelNode filterAndRename(RelBuilder relBuilder, RelNode rel,
    +   *       RexNode condition, List<String> fieldNames) {
    +   *     return relBuilder.push(rel)
    +   *         .filter(condition)
    +   *         .applyIf(fieldNames != null, r -> r.rename(fieldNames))
    +   *         .build();
    + *
    + */ + public RelBuilder applyIf(boolean condition, + UnaryOperator action) { + return condition ? action.apply(this) : this; + } + /** Converts this RelBuilder to a string. * The string is the string representation of all of the RelNodes on the stack. */ @Override public String toString() { diff --git a/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java b/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java index 5ff1e95ac3e1..06c485e8fc75 100644 --- a/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java +++ b/core/src/test/java/org/apache/calcite/materialize/LatticeSuggesterTest.java @@ -17,15 +17,28 @@ package org.apache.calcite.materialize; import org.apache.calcite.prepare.PlannerImpl; +import org.apache.calcite.rel.RelNode; import org.apache.calcite.rel.RelRoot; +import org.apache.calcite.rel.RelShuttleImpl; +import org.apache.calcite.rel.core.TableFunctionScan; +import org.apache.calcite.rel.core.Union; +import org.apache.calcite.rel.logical.LogicalProject; +import org.apache.calcite.rel.logical.LogicalTableFunctionScan; +import org.apache.calcite.rel.logical.LogicalUnion; +import org.apache.calcite.rel.logical.LogicalValues; import org.apache.calcite.schema.SchemaPlus; +import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlDialect; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlKind; import org.apache.calcite.sql.SqlNode; import org.apache.calcite.sql.SqlOperatorTable; import org.apache.calcite.sql.fun.SqlLibrary; import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory; import org.apache.calcite.sql.parser.SqlParseException; import org.apache.calcite.sql.parser.SqlParser; +import org.apache.calcite.sql.validate.SqlValidatorNamespace; +import org.apache.calcite.sql2rel.SqlToRelConverter; import org.apache.calcite.statistic.MapSqlStatisticProvider; import org.apache.calcite.statistic.QuerySqlStatisticProvider; import org.apache.calcite.test.CalciteAssert; @@ -33,8 +46,11 @@ import org.apache.calcite.tools.FrameworkConfig; import org.apache.calcite.tools.Frameworks; import org.apache.calcite.tools.Planner; +import org.apache.calcite.tools.RelBuilder; import org.apache.calcite.tools.RelConversionException; import org.apache.calcite.tools.ValidationException; +import org.apache.calcite.util.Pair; +import org.apache.calcite.util.TestUtil; import org.apache.calcite.util.Util; import com.google.common.collect.ImmutableList; @@ -47,17 +63,22 @@ import org.junit.jupiter.api.Tag; import org.junit.jupiter.api.Test; +import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Comparator; import java.util.EnumSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.function.UnaryOperator; import java.util.stream.Collectors; +import javax.annotation.Nullable; import static org.hamcrest.CoreMatchers.allOf; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.isA; /** * Unit tests for {@link LatticeSuggester}. @@ -66,7 +87,7 @@ class LatticeSuggesterTest { /** Some basic query patterns on the Scott schema with "EMP" and "DEPT" * tables. */ - @Test void testEmpDept() throws Exception { + @Test void testEmpDept() { final Tester t = new Tester(); final String q0 = "select dept.dname, count(*), sum(sal)\n" + "from emp\n" @@ -137,7 +158,7 @@ class LatticeSuggesterTest { assertThat(t.s.space.g.toString(), is(expected)); } - @Test void testFoodmart() throws Exception { + @Test void testFoodmart() { final Tester t = new Tester().foodmart(); final String q = "select \"t\".\"the_year\" as \"c0\",\n" + " \"t\".\"quarter\" as \"c1\",\n" @@ -177,7 +198,7 @@ class LatticeSuggesterTest { assertThat(t.s.space.g.toString(), is(expected)); } - @Test void testAggregateExpression() throws Exception { + @Test void testAggregateExpression() { final Tester t = new Tester().foodmart(); final String q = "select \"t\".\"the_year\" as \"c0\",\n" + " \"pc\".\"product_family\" as \"c1\",\n" @@ -234,7 +255,7 @@ protected boolean matchesSafely(List lattices) { final List actualNameList = lattice.columns.stream() .filter(c -> c instanceof Lattice.DerivedColumn) - .map(c -> ((Lattice.DerivedColumn) c).alias) + .map(c -> c.alias) .collect(Collectors.toList()); return actualNameList.equals(nameList); } @@ -242,7 +263,7 @@ protected boolean matchesSafely(List lattices) { } @Tag("slow") - @Test void testSharedSnowflake() throws Exception { + @Test void testSharedSnowflake() { final Tester t = new Tester().foodmart(); // foodmart query 5827 (also 5828, 5830, 5832) uses the "region" table // twice: once via "store" and once via "customer"; @@ -273,17 +294,27 @@ protected boolean matchesSafely(List lattices) { isGraphs(g, "[SUM(sales_fact_1997.unit_sales)]")); } - @Test void testExpressionInAggregate() throws Exception { + @Test void testExpressionInAggregate() { final Tester t = new Tester().withEvolve(true).foodmart(); - final FoodMartQuerySet set = FoodMartQuerySet.instance(); + final FoodMartQuerySet set; + try { + set = FoodMartQuerySet.instance(); + } catch (IOException e) { + throw TestUtil.rethrow(e); + } for (int id : new int[]{392, 393}) { t.addQuery(set.queries.get(id).sql); } } - private void checkFoodMartAll(boolean evolve) throws Exception { + private void checkFoodMartAll(boolean evolve) { final Tester t = new Tester().foodmart().withEvolve(evolve); - final FoodMartQuerySet set = FoodMartQuerySet.instance(); + final FoodMartQuerySet set; + try { + set = FoodMartQuerySet.instance(); + } catch (IOException e) { + throw TestUtil.rethrow(e); + } for (FoodMartQuerySet.FoodmartQuery query : set.queries.values()) { if (query.sql.contains("\"agg_10_foo_fact\"") || query.sql.contains("\"agg_line_class\"") @@ -394,16 +425,16 @@ private void checkFoodMartAll(boolean evolve) throws Exception { } @Tag("slow") - @Test void testFoodMartAll() throws Exception { + @Test void testFoodMartAll() { checkFoodMartAll(false); } @Tag("slow") - @Test void testFoodMartAllEvolve() throws Exception { + @Test void testFoodMartAllEvolve() { checkFoodMartAll(true); } - @Test void testContains() throws Exception { + @Test void testContains() { final Tester t = new Tester().foodmart(); final LatticeRootNode fNode = t.node("select *\n" + "from \"sales_fact_1997\""); @@ -425,7 +456,7 @@ private void checkFoodMartAll(boolean evolve) throws Exception { assertThat(fcpNode.contains(fcpNode), is(true)); } - @Test void testEvolve() throws Exception { + @Test void testEvolve() { final Tester t = new Tester().foodmart().withEvolve(true); final String q0 = "select count(*)\n" @@ -488,7 +519,7 @@ private void checkFoodMartAll(boolean evolve) throws Exception { is(l3)); } - @Test void testExpression() throws Exception { + @Test void testExpression() { final Tester t = new Tester().foodmart().withEvolve(true); final String q0 = "select\n" @@ -515,7 +546,7 @@ private void checkFoodMartAll(boolean evolve) throws Exception { /** As {@link #testExpression()} but with multiple queries. * Some expressions are measures in one query and dimensions in another. */ - @Test void testExpressionEvolution() throws Exception { + @Test void testExpressionEvolution() { final Tester t = new Tester().foodmart().withEvolve(true); // q0 uses n10 as a measure, n11 as a measure, n12 as a dimension @@ -571,7 +602,7 @@ private void checkDerivedColumn(Lattice lattice, List tables, assertThat(lattice.isAlwaysMeasure(dc0), is(alwaysMeasure)); } - @Test void testExpressionInJoin() throws Exception { + @Test void testExpressionInJoin() { final Tester t = new Tester().foodmart().withEvolve(true); final String q0 = "select\n" @@ -599,7 +630,7 @@ private void checkDerivedColumn(Lattice lattice, List tables, /** Tests a number of features only available in Redshift: the {@code CONCAT} * and {@code CONVERT_TIMEZONE} functions. */ - @Test void testRedshiftDialect() throws Exception { + @Test void testRedshiftDialect() { final Tester t = new Tester().foodmart().withEvolve(true) .withDialect(SqlDialect.DatabaseProduct.REDSHIFT.getDialect()) .withLibrary(SqlLibrary.POSTGRESQL); @@ -622,7 +653,7 @@ private void checkDerivedColumn(Lattice lattice, List tables, /** Tests a number of features only available in BigQuery: back-ticks; * GROUP BY ordinal; case-insensitive unquoted identifiers; * the {@code COUNTIF} aggregate function. */ - @Test void testBigQueryDialect() throws Exception { + @Test void testBigQueryDialect() { final Tester t = new Tester().foodmart().withEvolve(true) .withDialect(SqlDialect.DatabaseProduct.BIG_QUERY.getDialect()) .withLibrary(SqlLibrary.BIG_QUERY); @@ -639,7 +670,7 @@ private void checkDerivedColumn(Lattice lattice, List tables, /** A tricky case involving a CTE (WITH), a join condition that references an * expression, a complex WHERE clause, and some other queries. */ - @Test void testJoinUsingExpression() throws Exception { + @Test void testJoinUsingExpression() { final Tester t = new Tester().foodmart().withEvolve(true); final String q0 = "with c as (select\n" @@ -675,12 +706,91 @@ private void checkDerivedColumn(Lattice lattice, List tables, assertThat(t.s.latticeMap.size(), is(3)); } - @Test void testDerivedColRef() throws Exception { - final FrameworkConfig config = Frameworks.newConfigBuilder() - .defaultSchema(Tester.schemaFrom(CalciteAssert.SchemaSpec.SCOTT)) - .statisticProvider(QuerySqlStatisticProvider.SILENT_CACHING_INSTANCE) - .build(); - final Tester t = new Tester(config).foodmart().withEvolve(true); + /** UNION in sub-query in FROM clause becomes a table. */ + @Test void testInlineUnion() { + final Foo foo = new Foo(); + final Tester t = new Tester().foodmart().withConfig(b -> + b.sqlToRelConverterConfig( + SqlToRelConverter.config() + .withFromTrace(foo::fromTrace) + .withPostStep(foo::postStep))); + + final String q0 = "select min(c2.\"fname\") as \"customer.min_name\"\n" + + "from (select \"customer_id\", \"fname\" from \"customer\"\n" + + " where \"customer_id\" < 100\n" + + " union all\n" + + " select \"customer_id\", \"lname\" from \"customer\"\n" + + " where \"customer_id\" > 1000) as c2\n" + + "join \"sales_fact_1997\" as s\n" + + "on c2.\"customer_id\" = s.\"customer_id\""; + t.addQuery(q0); + assertThat(t.s.latticeMap.size(), is(1)); + final Map.Entry entry = + Iterables.getOnlyElement(t.s.latticeMap.entrySet()); + assertThat(entry.getKey(), + is("sales_fact_1997 ($table1:customer_id):[MIN($table1.fname)]")); + assertThat(entry.getValue().rootNode.descendants.size(), is(2)); + final LatticeNode node = entry.getValue().rootNode.descendants.get(1); + assertThat(node.alias, is("$table1")); + assertThat(node.table.t.getRowType().toString(), + is("RecordType(INTEGER customer_id, VARCHAR(30) fname)")); + assertThat(node.table.t.unwrap(RelNode.class), isA(LogicalUnion.class)); + assertThat(t.s.space.g.toString(), + is("graph(vertices: [[$table1], [foodmart, sales_fact_1997]], " + + "edges: [Step([foodmart, sales_fact_1997]," + + " [$table1], customer_id:customer_id)])")); + } + + /** UNION in WITH clause becomes a table. + * + *

    Very similar to {@link #testInlineUnion()} except that the + * row type is different, and the alias "c" is reused. */ + @Test void testWithUnion() { + final Foo foo = new Foo(); + final Tester t = new Tester().foodmart().withConfig(b -> + b.sqlToRelConverterConfig( + SqlToRelConverter.config() + .withFromTrace(foo::fromTrace) + .withPostStep(foo::postStep))); + + final String q0 = "with c (id, name)\n" + + " as (select \"customer_id\", \"fname\" from \"customer\"\n" + + " where \"customer_id\" < 100\n" + + " union all\n" + + " select \"customer_id\", \"lname\" from \"customer\"\n" + + " where \"customer_id\" > 1000)\n" + + "select min(c2.name) as \"customer.min_name\"\n" + + "from c as c2\n" + + "join \"sales_fact_1997\" as s on c2.id = s.\"customer_id\""; + t.addQuery(q0); + assertThat(t.s.latticeMap.size(), is(1)); + final Map.Entry entry = + Iterables.getOnlyElement(t.s.latticeMap.entrySet()); + assertThat(entry.getKey(), + is("sales_fact_1997 (C:customer_id):[MIN(C.NAME)]")); + assertThat(entry.getValue().rootNode.descendants.size(), is(2)); + + // Alias and row type are different from testInlineUnion + final LatticeNode node = entry.getValue().rootNode.descendants.get(1); + assertThat(node.alias, is("C")); + assertThat(node.table.t.getRowType().toString(), + is("RecordType(INTEGER ID, VARCHAR(30) NAME)")); + RelNode r = node.table.t.unwrap(RelNode.class); + if (r instanceof LogicalProject) { + r = r.getInput(0); + } + assertThat(r, isA(LogicalTableFunctionScan.class)); + assertThat(t.s.space.g.toString(), + is("graph(vertices: [[C], [foodmart, sales_fact_1997]], " + + "edges: [Step([foodmart, sales_fact_1997]," + + " [C], customer_id:ID)])")); + } + + @Test void testDerivedColRef() { + final Tester t = new Tester().foodmart() + .withConfig(b -> + b.statisticProvider(QuerySqlStatisticProvider.SILENT_CACHING_INSTANCE)) + .withEvolve(true); final String q0 = "select\n" + " min(c.\"fname\") as \"customer.count\"\n" @@ -709,18 +819,17 @@ private void checkDerivedColumn(Lattice lattice, List tables, *

    The query has a join, and so we have to execute statistics queries * to deduce the direction of the foreign key. */ - @Test void testFoodmartSimpleJoin() throws Exception { + @Test void testFoodmartSimpleJoin() { checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec.JDBC_FOODMART); checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec.FAKE_FOODMART); } - private void checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec schemaSpec) - throws Exception { - final FrameworkConfig config = Frameworks.newConfigBuilder() - .defaultSchema(Tester.schemaFrom(schemaSpec)) - .statisticProvider(QuerySqlStatisticProvider.SILENT_CACHING_INSTANCE) - .build(); - final Tester t = new Tester(config); + private void checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec schemaSpec) { + final Tester t = new Tester() + .withConfig(b -> + b.defaultSchema(Tester.schemaFrom(schemaSpec)) + .statisticProvider( + QuerySqlStatisticProvider.SILENT_CACHING_INSTANCE)); final String q = "select *\n" + "from \"time_by_day\" as \"t\",\n" + " \"sales_fact_1997\" as \"s\"\n" @@ -729,14 +838,14 @@ private void checkFoodmartSimpleJoin(CalciteAssert.SchemaSpec schemaSpec) assertThat(t.addQuery(q), isGraphs(g, "[]")); } - @Test void testUnion() throws Exception { + @Test void testUnion() { checkUnion("union"); checkUnion("union all"); checkUnion("intersect"); checkUnion("except"); } - private void checkUnion(String setOp) throws Exception { + private void checkUnion(String setOp) { final Tester t = new Tester().foodmart().withEvolve(true); final String q = "select \"t\".\"time_id\"\n" + "from \"time_by_day\" as \"t\",\n" @@ -793,17 +902,75 @@ public void describeTo(Description description) { }; } + /** Collects the names of WITH items, and applies those names when we later + * see the relational expressions generated from those items. */ + private static class Foo { + final Map> map = + new HashMap<>(); + + void fromTrace(SqlNode node, SqlValidatorNamespace namespace, + List fieldNames, RelNode r) { + map.put(r.getId(), Pair.of(node, namespace)); + } + + /** A {@link SqlToRelConverter.PostStep} that wraps each {@link Union} + * in a {@link TableFunctionScan}. */ + RelRoot postStep(RelBuilder relBuilder, RelRoot root) { + return root.withRel(root.rel.accept(new WrapUnionShuttle(relBuilder))); + } + + /** Shuttle that replaces UNION and VALUES with a wrapper table function. */ + class WrapUnionShuttle extends RelShuttleImpl { + final RelBuilder builder; + + private WrapUnionShuttle(RelBuilder builder) { + this.builder = builder; + } + + @Override public RelNode visit(LogicalValues values) { + return wrap(values, null, null); + } + + @Override public RelNode visit(LogicalUnion union) { + final Pair p = map.get(union.getId()); + if (p == null) { + return wrap(union, null, null); + } + final SqlNode node = p.left; + String name = node.getKind() == SqlKind.AS + && ((SqlCall) node).operand(0) instanceof SqlIdentifier + ? ((SqlCall) node).operand(0).getSimple() + : null; + return wrap(union, name, p.right); + } + + private RelNode wrap(RelNode r, String name, + @Nullable SqlValidatorNamespace namespace) { + builder.push(r); + if (namespace != null) { + builder.rename(namespace.getRowType().getFieldNames()); + } + return builder + .functionScan(LatticeSuggester.WrapFunction.INSTANCE, 1, + builder.cursor(1, 0), builder.literal(name)) + .build(); + } + } + } + /** Test helper. */ private static class Tester { final LatticeSuggester s; private final FrameworkConfig config; + private static final FrameworkConfig CONFIG = + Frameworks.newConfigBuilder() + .defaultSchema(schemaFrom(CalciteAssert.SchemaSpec.SCOTT)) + .statisticProvider(MapSqlStatisticProvider.INSTANCE) + .build(); + Tester() { - this( - Frameworks.newConfigBuilder() - .defaultSchema(schemaFrom(CalciteAssert.SchemaSpec.SCOTT)) - .statisticProvider(MapSqlStatisticProvider.INSTANCE) - .build()); + this(CONFIG); } private Tester(FrameworkConfig config) { @@ -811,36 +978,36 @@ private Tester(FrameworkConfig config) { s = new LatticeSuggester(config); } - Tester withConfig(FrameworkConfig config) { - return new Tester(config); + Tester withConfig(UnaryOperator transform) { + return new Tester( + transform.apply(Frameworks.newConfigBuilder(config)) + .build()); } Tester foodmart() { return schema(CalciteAssert.SchemaSpec.JDBC_FOODMART); } - private Tester schema(CalciteAssert.SchemaSpec schemaSpec) { - return withConfig(builder() - .defaultSchema(schemaFrom(schemaSpec)) - .build()); - } - - private Frameworks.ConfigBuilder builder() { - return Frameworks.newConfigBuilder(config); + Tester schema(CalciteAssert.SchemaSpec schemaSpec) { + return withConfig(b -> b.defaultSchema(schemaFrom(schemaSpec))); } - List addQuery(String q) throws SqlParseException, - ValidationException, RelConversionException { + List addQuery(String q) { final Planner planner = new PlannerImpl(config); - final SqlNode node = planner.parse(q); - final SqlNode node2 = planner.validate(node); - final RelRoot root = planner.rel(node2); - return s.addQuery(root.project()); + try { + final SqlNode node = planner.parse(q); + final SqlNode node2 = planner.validate(node); + final RelRoot root = planner.rel(node2); + return s.addQuery(root.project()); + } catch (SqlParseException + | ValidationException + | RelConversionException e) { + throw TestUtil.rethrow(e); + } } /** Parses a query returns its graph. */ - LatticeRootNode node(String q) throws SqlParseException, - ValidationException, RelConversionException { + LatticeRootNode node(String q) { final List list = addQuery(q); assertThat(list.size(), is(1)); return list.get(0).rootNode; @@ -852,14 +1019,12 @@ private static SchemaPlus schemaFrom(CalciteAssert.SchemaSpec spec) { } Tester withEvolve(boolean evolve) { - return withConfig(builder().evolveLattice(evolve).build()); + return withConfig(b -> b.evolveLattice(evolve)); } private Tester withParser(UnaryOperator transform) { - return withConfig( - builder() - .parserConfig(transform.apply(config.getParserConfig())) - .build()); + return withConfig(b -> + b.parserConfig(transform.apply(config.getParserConfig()))); } Tester withDialect(SqlDialect dialect) { @@ -869,7 +1034,7 @@ Tester withDialect(SqlDialect dialect) { Tester withLibrary(SqlLibrary library) { SqlOperatorTable opTab = SqlLibraryOperatorTableFactory.INSTANCE .getOperatorTable(EnumSet.of(SqlLibrary.STANDARD, library)); - return withConfig(builder().operatorTable(opTab).build()); + return withConfig(b -> b.operatorTable(opTab)); } } } diff --git a/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java b/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java new file mode 100644 index 000000000000..7cd8f3aa941a --- /dev/null +++ b/core/src/test/java/org/apache/calcite/sql/SqlNodeTest.java @@ -0,0 +1,76 @@ +/* + * 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.util.Util; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; + +/** + * Test of {@link SqlNode} and other SQL AST classes. + */ +class SqlNodeTest { + @Test void testSqlNodeList() { + SqlParserPos zero = SqlParserPos.ZERO; + checkList(new SqlNodeList(zero)); + checkList(SqlNodeList.SINGLETON_STAR); + checkList(SqlNodeList.SINGLETON_EMPTY); + checkList( + SqlNodeList.of(zero, + Arrays.asList(SqlLiteral.createCharString("x", zero), + new SqlIdentifier("y", zero)))); + } + + /** Compares a list to its own backing list. */ + private void checkList(SqlNodeList nodeList) { + checkLists(nodeList, nodeList.getList(), 0); + } + + /** Checks that two lists are identical. */ + private void checkLists(List list0, List list1, int depth) { + assertThat(list0.hashCode(), is(list1.hashCode())); + assertThat(list0.equals(list1), is(true)); + assertThat(list0.size(), is(list1.size())); + assertThat(list0.isEmpty(), is(list1.isEmpty())); + if (!list0.isEmpty()) { + assertThat(list0.get(0), sameInstance(list1.get(0))); + assertThat(Util.last(list0), sameInstance(Util.last(list1))); + if (depth == 0) { + checkLists(Util.skip(list0, 1), Util.skip(list1, 1), depth + 1); + } + } + assertThat(collect(list0), is(list1)); + assertThat(collect(list1), is(list0)); + } + + private static List collect(Iterable iterable) { + final List list = new ArrayList<>(); + for (E e: iterable) { + list.add(e); + } + return list; + } +} diff --git a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java index 45c5641e72ba..9ad7714364a5 100644 --- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java +++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java @@ -9607,7 +9607,7 @@ private UnaryOperator randomize(Random random) { private String toSqlString(SqlNodeList sqlNodeList, UnaryOperator transform) { - return sqlNodeList.getList().stream() + return sqlNodeList.stream() .map(node -> node.toSqlString(transform).getSql()) .collect(Collectors.joining(";")); } diff --git a/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java b/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java index 176f05a4d784..fe56ac8612bd 100644 --- a/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java +++ b/core/src/test/java/org/apache/calcite/sql/parser/parserextensiontesting/SqlCreateTable.java @@ -83,9 +83,9 @@ public SqlCreateTable(SqlParserPos pos, SqlIdentifier name, /** Calls an action for each (name, type) pair from {@code columnList}, in which * they alternate. */ - @SuppressWarnings({"unchecked"}) + @SuppressWarnings({"unchecked", "rawtypes"}) public void forEachNameType(BiConsumer consumer) { - final List list = columnList.getList(); + final List list = columnList; Pair.forEach((List) Util.quotientList(list, 2, 0), Util.quotientList((List) list, 2, 1), consumer); } diff --git a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeUtilTest.java b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeUtilTest.java index 06de5ec71362..a39ae85c533f 100644 --- a/core/src/test/java/org/apache/calcite/sql/type/SqlTypeUtilTest.java +++ b/core/src/test/java/org/apache/calcite/sql/type/SqlTypeUtilTest.java @@ -171,10 +171,8 @@ class SqlTypeUtilTest { SqlRowTypeNameSpec rowSpec = (SqlRowTypeNameSpec) convertTypeToSpec(f.structOfInt).getTypeNameSpec(); - List fieldNames = rowSpec.getFieldNames() - .stream() - .map(SqlIdentifier::getSimple) - .collect(Collectors.toList()); + List fieldNames = + SqlIdentifier.simpleNames(rowSpec.getFieldNames()); List fieldTypeNames = rowSpec.getFieldTypes() .stream() .map(f -> f.getTypeName().getSimple()) diff --git a/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java b/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java index 674a54e7ce77..d2e7e0840699 100644 --- a/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java +++ b/piglet/src/main/java/org/apache/calcite/piglet/PigRelToSqlConverter.java @@ -83,7 +83,7 @@ public class PigRelToSqlConverter extends RelToSqlConverter { groupBy = new SqlNodeList(cubeRollupList, POS); } - return buildAggregate(e, builder, selectList, groupBy.getList()).result(); + return buildAggregate(e, builder, selectList, groupBy).result(); } // CHECKSTYLE: IGNORE 1 diff --git a/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java b/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java index 78f39b17b7ba..4a068811b9e1 100644 --- a/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java +++ b/server/src/main/java/org/apache/calcite/server/ServerDdlExecutor.java @@ -433,7 +433,7 @@ public void execute(SqlCreateTable create, } final List columnList; if (create.columnList != null) { - columnList = create.columnList.getList(); + columnList = create.columnList; } else { if (queryRowType == null) { // "CREATE TABLE t" is invalid; because there is no "AS query" we need