blob: 8795cbb4707d7b3e0f8b7f9fc9a6f5d71622223b [file] [log] [blame]
/*
* 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.rel.rel2sql;
import org.apache.calcite.linq4j.Ord;
import org.apache.calcite.linq4j.tree.Expressions;
import org.apache.calcite.rel.RelFieldCollation;
import org.apache.calcite.rel.RelNode;
import org.apache.calcite.rel.core.Aggregate;
import org.apache.calcite.rel.core.AggregateCall;
import org.apache.calcite.rel.core.CorrelationId;
import org.apache.calcite.rel.core.JoinRelType;
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.RexCorrelVariable;
import org.apache.calcite.rex.RexDynamicParam;
import org.apache.calcite.rex.RexFieldAccess;
import org.apache.calcite.rex.RexFieldCollation;
import org.apache.calcite.rex.RexInputRef;
import org.apache.calcite.rex.RexLiteral;
import org.apache.calcite.rex.RexLocalRef;
import org.apache.calcite.rex.RexNode;
import org.apache.calcite.rex.RexOver;
import org.apache.calcite.rex.RexPatternFieldRef;
import org.apache.calcite.rex.RexProgram;
import org.apache.calcite.rex.RexSubQuery;
import org.apache.calcite.rex.RexWindow;
import org.apache.calcite.rex.RexWindowBound;
import org.apache.calcite.sql.JoinType;
import org.apache.calcite.sql.SqlAggFunction;
import org.apache.calcite.sql.SqlBasicCall;
import org.apache.calcite.sql.SqlBinaryOperator;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlDynamicParam;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlJoin;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlMatchRecognize;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlSelectKeyword;
import org.apache.calcite.sql.SqlSetOperator;
import org.apache.calcite.sql.SqlWindow;
import org.apache.calcite.sql.fun.SqlCase;
import org.apache.calcite.sql.fun.SqlCountAggFunction;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.fun.SqlSumEmptyIsZeroAggFunction;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlTypeFamily;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlValidatorUtil;
import org.apache.calcite.util.DateString;
import org.apache.calcite.util.TimeString;
import org.apache.calcite.util.TimestampString;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import java.math.BigDecimal;
import java.util.AbstractList;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.IntFunction;
import javax.annotation.Nonnull;
/**
* State for generating a SQL statement.
*/
public abstract class SqlImplementor {
// Always use quoted position, the "isQuoted" info is only used when
// unparsing a SqlIdentifier. For some rex nodes, saying RexInputRef, we have
// no idea about whether it is quoted or not for the original sql statement.
// So we just quote it.
public static final SqlParserPos POS = SqlParserPos.QUOTED_ZERO;
public final SqlDialect dialect;
protected final Set<String> aliasSet = new LinkedHashSet<>();
protected final Map<String, SqlNode> ordinalMap = new HashMap<>();
protected final Map<CorrelationId, Context> correlTableMap = new HashMap<>();
protected SqlImplementor(SqlDialect dialect) {
this.dialect = Objects.requireNonNull(dialect);
}
public abstract Result visitChild(int i, RelNode e);
public void addSelect(List<SqlNode> selectList, SqlNode node,
RelDataType rowType) {
String name = rowType.getFieldNames().get(selectList.size());
String alias = SqlValidatorUtil.getAlias(node, -1);
if (alias == null || !alias.equals(name)) {
node = as(node, name);
}
selectList.add(node);
}
/** Convenience method for creating column and table aliases.
*
* <p>{@code AS(e, "c")} creates "e AS c";
* {@code AS(e, "t", "c1", "c2"} creates "e AS t (c1, c2)". */
protected SqlCall as(SqlNode e, String alias, String... fieldNames) {
final List<SqlNode> operandList = new ArrayList<>();
operandList.add(e);
operandList.add(new SqlIdentifier(alias, POS));
for (String fieldName : fieldNames) {
operandList.add(new SqlIdentifier(fieldName, POS));
}
return SqlStdOperatorTable.AS.createCall(POS, operandList);
}
/** Returns whether a list of expressions projects all fields, in order,
* from the input, with the same names. */
public static boolean isStar(List<RexNode> exps, RelDataType inputRowType,
RelDataType projectRowType) {
assert exps.size() == projectRowType.getFieldCount();
int i = 0;
for (RexNode ref : exps) {
if (!(ref instanceof RexInputRef)) {
return false;
} else if (((RexInputRef) ref).getIndex() != i++) {
return false;
}
}
return i == inputRowType.getFieldCount()
&& inputRowType.getFieldNames().equals(projectRowType.getFieldNames());
}
public static boolean isStar(RexProgram program) {
int i = 0;
for (RexLocalRef ref : program.getProjectList()) {
if (ref.getIndex() != i++) {
return false;
}
}
return i == program.getInputRowType().getFieldCount();
}
public Result setOpToSql(SqlSetOperator operator, RelNode rel) {
SqlNode node = null;
for (Ord<RelNode> input : Ord.zip(rel.getInputs())) {
final Result result = visitChild(input.i, input.e);
if (node == null) {
node = result.asSelect();
} else {
node = operator.createCall(POS, node, result.asSelect());
}
}
final List<Clause> clauses =
Expressions.list(Clause.SET_OP);
return result(node, clauses, rel, null);
}
/**
* Converts a {@link RexNode} condition into a {@link SqlNode}.
*
* @param node Join condition
* @param leftContext Left context
* @param rightContext Right context
* @param leftFieldCount Number of fields on left result
* @return SqlNode that represents the condition
*/
public static SqlNode convertConditionToSqlNode(RexNode node,
Context leftContext,
Context rightContext,
int leftFieldCount,
SqlDialect dialect) {
if (node.isAlwaysTrue()) {
return SqlLiteral.createBoolean(true, POS);
}
if (node.isAlwaysFalse()) {
return SqlLiteral.createBoolean(false, POS);
}
if (node instanceof RexInputRef) {
Context joinContext = leftContext.implementor().joinContext(leftContext, rightContext);
return joinContext.toSql(null, node);
}
if (!(node instanceof RexCall)) {
throw new AssertionError(node);
}
final List<RexNode> operands;
final SqlOperator op;
final Context joinContext;
switch (node.getKind()) {
case AND:
case OR:
operands = ((RexCall) node).getOperands();
op = ((RexCall) node).getOperator();
SqlNode sqlCondition = null;
for (RexNode operand : operands) {
SqlNode x = convertConditionToSqlNode(operand, leftContext,
rightContext, leftFieldCount, dialect);
if (sqlCondition == null) {
sqlCondition = x;
} else {
sqlCondition = op.createCall(POS, sqlCondition, x);
}
}
return sqlCondition;
case EQUALS:
case IS_NOT_DISTINCT_FROM:
case NOT_EQUALS:
case GREATER_THAN:
case GREATER_THAN_OR_EQUAL:
case LESS_THAN:
case LESS_THAN_OR_EQUAL:
case LIKE:
node = stripCastFromString(node, dialect);
operands = ((RexCall) node).getOperands();
op = ((RexCall) node).getOperator();
if (operands.size() == 2
&& operands.get(0) instanceof RexInputRef
&& operands.get(1) instanceof RexInputRef) {
final RexInputRef op0 = (RexInputRef) operands.get(0);
final RexInputRef op1 = (RexInputRef) operands.get(1);
if (op0.getIndex() < leftFieldCount
&& op1.getIndex() >= leftFieldCount) {
// Arguments were of form 'op0 = op1'
return op.createCall(POS,
leftContext.field(op0.getIndex()),
rightContext.field(op1.getIndex() - leftFieldCount));
}
if (op1.getIndex() < leftFieldCount
&& op0.getIndex() >= leftFieldCount) {
// Arguments were of form 'op1 = op0'
return reverseOperatorDirection(op).createCall(POS,
leftContext.field(op1.getIndex()),
rightContext.field(op0.getIndex() - leftFieldCount));
}
}
joinContext =
leftContext.implementor().joinContext(leftContext, rightContext);
return joinContext.toSql(null, node);
case IS_NULL:
case IS_NOT_NULL:
operands = ((RexCall) node).getOperands();
if (operands.size() == 1
&& operands.get(0) instanceof RexInputRef) {
op = ((RexCall) node).getOperator();
final RexInputRef op0 = (RexInputRef) operands.get(0);
if (op0.getIndex() < leftFieldCount) {
return op.createCall(POS, leftContext.field(op0.getIndex()));
} else {
return op.createCall(POS,
rightContext.field(op0.getIndex() - leftFieldCount));
}
}
joinContext =
leftContext.implementor().joinContext(leftContext, rightContext);
return joinContext.toSql(null, node);
default:
throw new AssertionError(node);
}
}
/** Removes cast from string.
*
* <p>For example, {@code x > CAST('2015-01-07' AS DATE)}
* becomes {@code x > '2015-01-07'}.
*/
private static RexNode stripCastFromString(RexNode node, SqlDialect dialect) {
switch (node.getKind()) {
case EQUALS:
case IS_NOT_DISTINCT_FROM:
case NOT_EQUALS:
case GREATER_THAN:
case GREATER_THAN_OR_EQUAL:
case LESS_THAN:
case LESS_THAN_OR_EQUAL:
final RexCall call = (RexCall) node;
final RexNode o0 = call.operands.get(0);
final RexNode o1 = call.operands.get(1);
if (o0.getKind() == SqlKind.CAST
&& o1.getKind() != SqlKind.CAST) {
if (!dialect.supportsImplicitTypeCoercion((RexCall) o0)) {
// If the dialect does not support implicit type coercion,
// we definitely can not strip the cast.
return node;
}
final RexNode o0b = ((RexCall) o0).getOperands().get(0);
return call.clone(call.getType(), ImmutableList.of(o0b, o1));
}
if (o1.getKind() == SqlKind.CAST
&& o0.getKind() != SqlKind.CAST) {
if (!dialect.supportsImplicitTypeCoercion((RexCall) o1)) {
return node;
}
final RexNode o1b = ((RexCall) o1).getOperands().get(0);
return call.clone(call.getType(), ImmutableList.of(o0, o1b));
}
}
return node;
}
private static SqlOperator reverseOperatorDirection(SqlOperator op) {
switch (op.kind) {
case GREATER_THAN:
return SqlStdOperatorTable.LESS_THAN;
case GREATER_THAN_OR_EQUAL:
return SqlStdOperatorTable.LESS_THAN_OR_EQUAL;
case LESS_THAN:
return SqlStdOperatorTable.GREATER_THAN;
case LESS_THAN_OR_EQUAL:
return SqlStdOperatorTable.GREATER_THAN_OR_EQUAL;
case EQUALS:
case IS_NOT_DISTINCT_FROM:
case NOT_EQUALS:
return op;
default:
throw new AssertionError(op);
}
}
public static JoinType joinType(JoinRelType joinType) {
switch (joinType) {
case LEFT:
return JoinType.LEFT;
case RIGHT:
return JoinType.RIGHT;
case INNER:
return JoinType.INNER;
case FULL:
return JoinType.FULL;
default:
throw new AssertionError(joinType);
}
}
/** Creates a result based on a single relational expression. */
public Result result(SqlNode node, Collection<Clause> clauses,
RelNode rel, Map<String, RelDataType> aliases) {
assert aliases == null
|| aliases.size() < 2
|| aliases instanceof LinkedHashMap
|| aliases instanceof ImmutableMap
: "must use a Map implementation that preserves order";
final String alias2 = SqlValidatorUtil.getAlias(node, -1);
final String alias3 = alias2 != null ? alias2 : "t";
final String alias4 =
SqlValidatorUtil.uniquify(
alias3, aliasSet, SqlValidatorUtil.EXPR_SUGGESTER);
if (aliases != null
&& !aliases.isEmpty()
&& (!dialect.hasImplicitTableAlias()
|| aliases.size() > 1)) {
return new Result(node, clauses, alias4, rel.getRowType(), aliases);
}
final String alias5;
if (alias2 == null
|| !alias2.equals(alias4)
|| !dialect.hasImplicitTableAlias()) {
alias5 = alias4;
} else {
alias5 = null;
}
return new Result(node, clauses, alias5, rel.getRowType(),
ImmutableMap.of(alias4, rel.getRowType()));
}
/** Creates a result based on a join. (Each join could contain one or more
* relational expressions.) */
public Result result(SqlNode join, Result leftResult, Result rightResult) {
final ImmutableMap.Builder<String, RelDataType> builder =
ImmutableMap.builder();
collectAliases(builder, join,
Iterables.concat(leftResult.aliases.values(),
rightResult.aliases.values()).iterator());
return new Result(join, Expressions.list(Clause.FROM), null, null,
builder.build());
}
private void collectAliases(ImmutableMap.Builder<String, RelDataType> builder,
SqlNode node, Iterator<RelDataType> aliases) {
if (node instanceof SqlJoin) {
final SqlJoin join = (SqlJoin) node;
collectAliases(builder, join.getLeft(), aliases);
collectAliases(builder, join.getRight(), aliases);
} else {
final String alias = SqlValidatorUtil.getAlias(node, -1);
assert alias != null;
builder.put(alias, aliases.next());
}
}
/** Wraps a node in a SELECT statement that has no clauses:
* "SELECT ... FROM (node)". */
SqlSelect wrapSelect(SqlNode node) {
assert node instanceof SqlJoin
|| node instanceof SqlIdentifier
|| node instanceof SqlMatchRecognize
|| node instanceof SqlCall
&& (((SqlCall) node).getOperator() instanceof SqlSetOperator
|| ((SqlCall) node).getOperator() == SqlStdOperatorTable.AS
|| ((SqlCall) node).getOperator() == SqlStdOperatorTable.VALUES)
: node;
if (requiresAlias(node)) {
node = as(node, "t");
}
return new SqlSelect(POS, SqlNodeList.EMPTY, null, node, null, null, null,
SqlNodeList.EMPTY, null, null, null);
}
/** Returns whether we need to add an alias if this node is to be the FROM
* clause of a SELECT. */
private boolean requiresAlias(SqlNode node) {
if (!dialect.requiresAliasForFromItems()) {
return false;
}
switch (node.getKind()) {
case IDENTIFIER:
return !dialect.hasImplicitTableAlias();
case AS:
case JOIN:
case EXPLICIT_TABLE:
return false;
default:
return true;
}
}
/** Context for translating a {@link RexNode} expression (within a
* {@link RelNode}) into a {@link SqlNode} expression (within a SQL parse
* tree). */
public abstract static class Context {
final SqlDialect dialect;
final int fieldCount;
private final boolean ignoreCast;
protected Context(SqlDialect dialect, int fieldCount) {
this(dialect, fieldCount, false);
}
protected Context(SqlDialect dialect, int fieldCount, boolean ignoreCast) {
this.dialect = dialect;
this.fieldCount = fieldCount;
this.ignoreCast = ignoreCast;
}
public abstract SqlNode field(int ordinal);
/** Converts an expression from {@link RexNode} to {@link SqlNode}
* format.
*
* @param program Required only if {@code rex} contains {@link RexLocalRef}
* @param rex Expression to convert
*/
public SqlNode toSql(RexProgram program, RexNode rex) {
final RexSubQuery subQuery;
final SqlNode sqlSubQuery;
switch (rex.getKind()) {
case LOCAL_REF:
final int index = ((RexLocalRef) rex).getIndex();
return toSql(program, program.getExprList().get(index));
case INPUT_REF:
return field(((RexInputRef) rex).getIndex());
case FIELD_ACCESS:
final Deque<RexFieldAccess> accesses = new ArrayDeque<>();
RexNode referencedExpr = rex;
while (referencedExpr.getKind() == SqlKind.FIELD_ACCESS) {
accesses.offerLast((RexFieldAccess) referencedExpr);
referencedExpr = ((RexFieldAccess) referencedExpr).getReferenceExpr();
}
SqlIdentifier sqlIdentifier;
switch (referencedExpr.getKind()) {
case CORREL_VARIABLE:
final RexCorrelVariable variable = (RexCorrelVariable) referencedExpr;
final Context correlAliasContext = getAliasContext(variable);
final RexFieldAccess lastAccess = accesses.pollLast();
assert lastAccess != null;
sqlIdentifier = (SqlIdentifier) correlAliasContext
.field(lastAccess.getField().getIndex());
break;
case ROW:
final SqlNode expr = toSql(program, referencedExpr);
sqlIdentifier = new SqlIdentifier(expr.toString(), POS);
break;
default:
sqlIdentifier = (SqlIdentifier) toSql(program, referencedExpr);
}
int nameIndex = sqlIdentifier.names.size();
RexFieldAccess access;
while ((access = accesses.pollLast()) != null) {
sqlIdentifier = sqlIdentifier.add(nameIndex++, access.getField().getName(), POS);
}
return sqlIdentifier;
case PATTERN_INPUT_REF:
final RexPatternFieldRef ref = (RexPatternFieldRef) rex;
String pv = ref.getAlpha();
SqlNode refNode = field(ref.getIndex());
final SqlIdentifier id = (SqlIdentifier) refNode;
if (id.names.size() > 1) {
return id.setName(0, pv);
} else {
return new SqlIdentifier(ImmutableList.of(pv, id.names.get(0)), POS);
}
case LITERAL:
final RexLiteral literal = (RexLiteral) rex;
if (literal.getTypeName() == SqlTypeName.SYMBOL) {
final Enum symbol = (Enum) literal.getValue();
return SqlLiteral.createSymbol(symbol, POS);
}
switch (literal.getTypeName().getFamily()) {
case CHARACTER:
return SqlLiteral.createCharString((String) literal.getValue2(), POS);
case NUMERIC:
case EXACT_NUMERIC:
return SqlLiteral.createExactNumeric(
literal.getValueAs(BigDecimal.class).toPlainString(), POS);
case APPROXIMATE_NUMERIC:
return SqlLiteral.createApproxNumeric(
literal.getValueAs(BigDecimal.class).toPlainString(), POS);
case BOOLEAN:
return SqlLiteral.createBoolean(literal.getValueAs(Boolean.class),
POS);
case INTERVAL_YEAR_MONTH:
case INTERVAL_DAY_TIME:
final boolean negative = literal.getValueAs(Boolean.class);
return SqlLiteral.createInterval(negative ? -1 : 1,
literal.getValueAs(String.class),
literal.getType().getIntervalQualifier(), POS);
case DATE:
return SqlLiteral.createDate(literal.getValueAs(DateString.class),
POS);
case TIME:
return SqlLiteral.createTime(literal.getValueAs(TimeString.class),
literal.getType().getPrecision(), POS);
case TIMESTAMP:
return SqlLiteral.createTimestamp(
literal.getValueAs(TimestampString.class),
literal.getType().getPrecision(), POS);
case ANY:
case NULL:
switch (literal.getTypeName()) {
case NULL:
return SqlLiteral.createNull(POS);
// fall through
}
default:
throw new AssertionError(literal + ": " + literal.getTypeName());
}
case CASE:
final RexCall caseCall = (RexCall) rex;
final List<SqlNode> caseNodeList =
toSql(program, caseCall.getOperands());
final SqlNode valueNode;
final List<SqlNode> whenList = Expressions.list();
final List<SqlNode> thenList = Expressions.list();
final SqlNode elseNode;
if (caseNodeList.size() % 2 == 0) {
// switched:
// "case x when v1 then t1 when v2 then t2 ... else e end"
valueNode = caseNodeList.get(0);
for (int i = 1; i < caseNodeList.size() - 1; i += 2) {
whenList.add(caseNodeList.get(i));
thenList.add(caseNodeList.get(i + 1));
}
} else {
// other: "case when w1 then t1 when w2 then t2 ... else e end"
valueNode = null;
for (int i = 0; i < caseNodeList.size() - 1; i += 2) {
whenList.add(caseNodeList.get(i));
thenList.add(caseNodeList.get(i + 1));
}
}
elseNode = caseNodeList.get(caseNodeList.size() - 1);
return new SqlCase(POS, valueNode, new SqlNodeList(whenList, POS),
new SqlNodeList(thenList, POS), elseNode);
case DYNAMIC_PARAM:
final RexDynamicParam caseParam = (RexDynamicParam) rex;
return new SqlDynamicParam(caseParam.getIndex(), POS);
case IN:
if (rex instanceof RexSubQuery) {
subQuery = (RexSubQuery) rex;
sqlSubQuery =
implementor().visitChild(0, subQuery.rel).asQueryOrValues();
final List<RexNode> operands = subQuery.operands;
SqlNode op0;
if (operands.size() == 1) {
op0 = toSql(program, operands.get(0));
} else {
final List<SqlNode> cols = toSql(program, operands);
op0 = new SqlNodeList(cols, POS);
}
return subQuery.getOperator().createCall(POS, op0, sqlSubQuery);
} else {
final RexCall call = (RexCall) rex;
final List<SqlNode> cols = toSql(program, call.operands);
return call.getOperator().createCall(POS, cols.get(0),
new SqlNodeList(cols.subList(1, cols.size()), POS));
}
case EXISTS:
case SCALAR_QUERY:
subQuery = (RexSubQuery) rex;
sqlSubQuery =
implementor().visitChild(0, subQuery.rel).asQueryOrValues();
return subQuery.getOperator().createCall(POS, sqlSubQuery);
case NOT:
RexNode operand = ((RexCall) rex).operands.get(0);
final SqlNode node = toSql(program, operand);
switch (operand.getKind()) {
case IN:
return SqlStdOperatorTable.NOT_IN
.createCall(POS, ((SqlCall) node).getOperandList());
case LIKE:
return SqlStdOperatorTable.NOT_LIKE
.createCall(POS, ((SqlCall) node).getOperandList());
case SIMILAR:
return SqlStdOperatorTable.NOT_SIMILAR_TO
.createCall(POS, ((SqlCall) node).getOperandList());
default:
return SqlStdOperatorTable.NOT.createCall(POS, node);
}
default:
if (rex instanceof RexOver) {
return toSql(program, (RexOver) rex);
}
final RexCall call = (RexCall) stripCastFromString(rex, dialect);
SqlOperator op = call.getOperator();
switch (op.getKind()) {
case SUM0:
op = SqlStdOperatorTable.SUM;
}
final List<SqlNode> nodeList = toSql(program, call.getOperands());
switch (call.getKind()) {
case CAST:
if (ignoreCast) {
assert nodeList.size() == 1;
return nodeList.get(0);
} else {
nodeList.add(dialect.getCastSpec(call.getType()));
}
}
if (op instanceof SqlBinaryOperator && nodeList.size() > 2) {
// In RexNode trees, OR and AND have any number of children;
// SqlCall requires exactly 2. So, convert to a balanced binary
// tree for OR/AND, left-deep binary tree for others.
if (op.kind == SqlKind.OR || op.kind == SqlKind.AND) {
return createBalancedCall(op, nodeList, 0, nodeList.size());
} else {
return createLeftCall(op, nodeList);
}
}
return op.createCall(new SqlNodeList(nodeList, POS));
}
}
/** Converts an expression from {@link RexWindowBound} to {@link SqlNode}
* format.
*
* @param rexWindowBound Expression to convert
*/
public SqlNode toSql(RexWindowBound rexWindowBound) {
final SqlNode offsetLiteral =
rexWindowBound.getOffset() == null
? null
: SqlLiteral.createCharString(rexWindowBound.getOffset().toString(),
SqlParserPos.ZERO);
if (rexWindowBound.isPreceding()) {
return offsetLiteral == null
? SqlWindow.createUnboundedPreceding(POS)
: SqlWindow.createPreceding(offsetLiteral, POS);
} else if (rexWindowBound.isFollowing()) {
return offsetLiteral == null
? SqlWindow.createUnboundedFollowing(POS)
: SqlWindow.createFollowing(offsetLiteral, POS);
} else {
assert rexWindowBound.isCurrentRow();
return SqlWindow.createCurrentRow(POS);
}
}
protected Context getAliasContext(RexCorrelVariable variable) {
throw new UnsupportedOperationException();
}
private SqlCall toSql(RexProgram program, RexOver rexOver) {
final RexWindow rexWindow = rexOver.getWindow();
final SqlNodeList partitionList = new SqlNodeList(
toSql(program, rexWindow.partitionKeys), POS);
ImmutableList.Builder<SqlNode> orderNodes = ImmutableList.builder();
if (rexWindow.orderKeys != null) {
for (RexFieldCollation rfc : rexWindow.orderKeys) {
orderNodes.add(toSql(program, rfc));
}
}
final SqlNodeList orderList =
new SqlNodeList(orderNodes.build(), POS);
final SqlLiteral isRows =
SqlLiteral.createBoolean(rexWindow.isRows(), POS);
// null defaults to true.
// During parsing the allowPartial == false (e.g. disallow partial)
// is expand into CASE expression and is handled as a such.
// Not sure if we can collapse this CASE expression back into
// "disallow partial" and set the allowPartial = false.
final SqlLiteral allowPartial = null;
SqlAggFunction sqlAggregateFunction = rexOver.getAggOperator();
SqlNode lowerBound = null;
SqlNode upperBound = null;
if (sqlAggregateFunction.allowsFraming()) {
lowerBound = createSqlWindowBound(rexWindow.getLowerBound());
upperBound = createSqlWindowBound(rexWindow.getUpperBound());
}
final SqlWindow sqlWindow = SqlWindow.create(null, null, partitionList,
orderList, isRows, lowerBound, upperBound, allowPartial, POS);
final List<SqlNode> nodeList = toSql(program, rexOver.getOperands());
return createOverCall(sqlAggregateFunction, nodeList, sqlWindow);
}
private SqlCall createOverCall(SqlAggFunction op, List<SqlNode> operands,
SqlWindow window) {
if (op instanceof SqlSumEmptyIsZeroAggFunction) {
// Rewrite "SUM0(x) OVER w" to "COALESCE(SUM(x) OVER w, 0)"
final SqlCall node =
createOverCall(SqlStdOperatorTable.SUM, operands, window);
return SqlStdOperatorTable.COALESCE.createCall(POS, node,
SqlLiteral.createExactNumeric("0", POS));
}
final SqlCall aggFunctionCall = op.createCall(POS, operands);
return SqlStdOperatorTable.OVER.createCall(POS, aggFunctionCall,
window);
}
private SqlNode toSql(RexProgram program, RexFieldCollation rfc) {
SqlNode node = toSql(program, rfc.left);
switch (rfc.getDirection()) {
case DESCENDING:
case STRICTLY_DESCENDING:
node = SqlStdOperatorTable.DESC.createCall(POS, node);
}
if (rfc.getNullDirection()
!= dialect.defaultNullDirection(rfc.getDirection())) {
switch (rfc.getNullDirection()) {
case FIRST:
node = SqlStdOperatorTable.NULLS_FIRST.createCall(POS, node);
break;
case LAST:
node = SqlStdOperatorTable.NULLS_LAST.createCall(POS, node);
break;
}
}
return node;
}
private SqlNode createSqlWindowBound(RexWindowBound rexWindowBound) {
if (rexWindowBound.isCurrentRow()) {
return SqlWindow.createCurrentRow(POS);
}
if (rexWindowBound.isPreceding()) {
if (rexWindowBound.isUnbounded()) {
return SqlWindow.createUnboundedPreceding(POS);
} else {
SqlNode literal = toSql(null, rexWindowBound.getOffset());
return SqlWindow.createPreceding(literal, POS);
}
}
if (rexWindowBound.isFollowing()) {
if (rexWindowBound.isUnbounded()) {
return SqlWindow.createUnboundedFollowing(POS);
} else {
SqlNode literal = toSql(null, rexWindowBound.getOffset());
return SqlWindow.createFollowing(literal, POS);
}
}
throw new AssertionError("Unsupported Window bound: "
+ rexWindowBound);
}
private SqlNode createLeftCall(SqlOperator op, List<SqlNode> nodeList) {
SqlNode node = op.createCall(new SqlNodeList(nodeList.subList(0, 2), POS));
for (int i = 2; i < nodeList.size(); i++) {
node = op.createCall(new SqlNodeList(ImmutableList.of(node, nodeList.get(i)), POS));
}
return node;
}
/**
* Create a balanced binary call from sql node list,
* start inclusive, end exclusive.
*/
private SqlNode createBalancedCall(SqlOperator op,
List<SqlNode> nodeList, int start, int end) {
assert start < end && end <= nodeList.size();
if (start + 1 == end) {
return nodeList.get(start);
}
int mid = (end - start) / 2 + start;
SqlNode leftNode = createBalancedCall(op, nodeList, start, mid);
SqlNode rightNode = createBalancedCall(op, nodeList, mid, end);
return op.createCall(new SqlNodeList(ImmutableList.of(leftNode, rightNode), POS));
}
private List<SqlNode> toSql(RexProgram program, List<RexNode> operandList) {
final List<SqlNode> list = new ArrayList<>();
for (RexNode rex : operandList) {
list.add(toSql(program, rex));
}
return list;
}
public List<SqlNode> fieldList() {
return new AbstractList<SqlNode>() {
public SqlNode get(int index) {
return field(index);
}
public int size() {
return fieldCount;
}
};
}
void addOrderItem(List<SqlNode> orderByList, RelFieldCollation field) {
if (field.nullDirection != RelFieldCollation.NullDirection.UNSPECIFIED) {
final boolean first =
field.nullDirection == RelFieldCollation.NullDirection.FIRST;
SqlNode nullDirectionNode =
dialect.emulateNullDirection(field(field.getFieldIndex()),
first, field.direction.isDescending());
if (nullDirectionNode != null) {
orderByList.add(nullDirectionNode);
field = new RelFieldCollation(field.getFieldIndex(),
field.getDirection(),
RelFieldCollation.NullDirection.UNSPECIFIED);
}
}
orderByList.add(toSql(field));
}
/** Converts a call to an aggregate function to an expression. */
public SqlNode toSql(AggregateCall aggCall) {
final SqlOperator op = aggCall.getAggregation();
final List<SqlNode> operandList = Expressions.list();
for (int arg : aggCall.getArgList()) {
operandList.add(field(arg));
}
if ((op instanceof SqlCountAggFunction) && operandList.isEmpty()) {
// If there is no parameter in "count" function, add a star identifier to it
operandList.add(SqlIdentifier.star(POS));
}
final SqlLiteral qualifier =
aggCall.isDistinct() ? SqlSelectKeyword.DISTINCT.symbol(POS) : null;
final SqlNode[] operands = operandList.toArray(new SqlNode[0]);
List<SqlNode> orderByList = Expressions.list();
for (RelFieldCollation field : aggCall.collation.getFieldCollations()) {
addOrderItem(orderByList, field);
}
SqlNodeList orderList = new SqlNodeList(orderByList, POS);
if (op instanceof SqlSumEmptyIsZeroAggFunction) {
final SqlNode node =
withOrder(
SqlStdOperatorTable.SUM.createCall(qualifier, POS, operands),
orderList);
return SqlStdOperatorTable.COALESCE.createCall(POS, node,
SqlLiteral.createExactNumeric("0", POS));
} else {
return withOrder(op.createCall(qualifier, POS, operands), orderList);
}
}
/** Wraps a call in a {@link SqlKind#WITHIN_GROUP} call, if
* {@code orderList} is non-empty. */
private SqlNode withOrder(SqlCall call, SqlNodeList orderList) {
if (orderList == null || orderList.size() == 0) {
return call;
}
return SqlStdOperatorTable.WITHIN_GROUP.createCall(POS, call, orderList);
}
/** Converts a collation to an ORDER BY item. */
public SqlNode toSql(RelFieldCollation collation) {
SqlNode node = field(collation.getFieldIndex());
switch (collation.getDirection()) {
case DESCENDING:
case STRICTLY_DESCENDING:
node = SqlStdOperatorTable.DESC.createCall(POS, node);
}
if (collation.nullDirection != dialect.defaultNullDirection(collation.direction)) {
switch (collation.nullDirection) {
case FIRST:
node = SqlStdOperatorTable.NULLS_FIRST.createCall(POS, node);
break;
case LAST:
node = SqlStdOperatorTable.NULLS_LAST.createCall(POS, node);
break;
}
}
return node;
}
public SqlImplementor implementor() {
throw new UnsupportedOperationException();
}
}
/** Simple implementation of {@link Context} that cannot handle sub-queries
* or correlations. Because it is so simple, you do not need to create a
* {@link SqlImplementor} or {@link org.apache.calcite.tools.RelBuilder}
* to use it. It is a good way to convert a {@link RexNode} to SQL text. */
public static class SimpleContext extends Context {
@Nonnull private final IntFunction<SqlNode> field;
public SimpleContext(SqlDialect dialect, IntFunction<SqlNode> field) {
super(dialect, 0, false);
this.field = field;
}
public SqlNode field(int ordinal) {
return field.apply(ordinal);
}
}
/** Implementation of {@link Context} that has an enclosing
* {@link SqlImplementor} and can therefore do non-trivial expressions. */
protected abstract class BaseContext extends Context {
BaseContext(SqlDialect dialect, int fieldCount) {
super(dialect, fieldCount);
}
@Override protected Context getAliasContext(RexCorrelVariable variable) {
return correlTableMap.get(variable.id);
}
@Override public SqlImplementor implementor() {
return SqlImplementor.this;
}
}
private static int computeFieldCount(
Map<String, RelDataType> aliases) {
int x = 0;
for (RelDataType type : aliases.values()) {
x += type.getFieldCount();
}
return x;
}
public Context aliasContext(Map<String, RelDataType> aliases,
boolean qualified) {
return new AliasContext(dialect, aliases, qualified);
}
public Context joinContext(Context leftContext, Context rightContext) {
return new JoinContext(dialect, leftContext, rightContext);
}
public Context matchRecognizeContext(Context context) {
return new MatchRecognizeContext(dialect, ((AliasContext) context).aliases);
}
/**
* Context for translating MATCH_RECOGNIZE clause
*/
public class MatchRecognizeContext extends AliasContext {
protected MatchRecognizeContext(SqlDialect dialect,
Map<String, RelDataType> aliases) {
super(dialect, aliases, false);
}
@Override public SqlNode toSql(RexProgram program, RexNode rex) {
if (rex.getKind() == SqlKind.LITERAL) {
final RexLiteral literal = (RexLiteral) rex;
if (literal.getTypeName().getFamily() == SqlTypeFamily.CHARACTER) {
return new SqlIdentifier(RexLiteral.stringValue(literal), POS);
}
}
return super.toSql(program, rex);
}
}
/** Implementation of Context that precedes field references with their
* "table alias" based on the current sub-query's FROM clause. */
public class AliasContext extends BaseContext {
private final boolean qualified;
private final Map<String, RelDataType> aliases;
/** Creates an AliasContext; use {@link #aliasContext(Map, boolean)}. */
protected AliasContext(SqlDialect dialect,
Map<String, RelDataType> aliases, boolean qualified) {
super(dialect, computeFieldCount(aliases));
this.aliases = aliases;
this.qualified = qualified;
}
public SqlNode field(int ordinal) {
for (Map.Entry<String, RelDataType> alias : aliases.entrySet()) {
final List<RelDataTypeField> fields = alias.getValue().getFieldList();
if (ordinal < fields.size()) {
RelDataTypeField field = fields.get(ordinal);
final SqlNode mappedSqlNode =
ordinalMap.get(field.getName().toLowerCase(Locale.ROOT));
if (mappedSqlNode != null) {
return mappedSqlNode;
}
return new SqlIdentifier(!qualified
? ImmutableList.of(field.getName())
: ImmutableList.of(alias.getKey(), field.getName()),
POS);
}
ordinal -= fields.size();
}
throw new AssertionError(
"field ordinal " + ordinal + " out of range " + aliases);
}
}
/** Context for translating ON clause of a JOIN from {@link RexNode} to
* {@link SqlNode}. */
class JoinContext extends BaseContext {
private final SqlImplementor.Context leftContext;
private final SqlImplementor.Context rightContext;
/** Creates a JoinContext; use {@link #joinContext(Context, Context)}. */
private JoinContext(SqlDialect dialect, Context leftContext,
Context rightContext) {
super(dialect, leftContext.fieldCount + rightContext.fieldCount);
this.leftContext = leftContext;
this.rightContext = rightContext;
}
public SqlNode field(int ordinal) {
if (ordinal < leftContext.fieldCount) {
return leftContext.field(ordinal);
} else {
return rightContext.field(ordinal - leftContext.fieldCount);
}
}
}
/** Result of implementing a node. */
public class Result {
final SqlNode node;
final String neededAlias;
private final RelDataType neededType;
private final Map<String, RelDataType> aliases;
final Expressions.FluentList<Clause> clauses;
public Result(SqlNode node, Collection<Clause> clauses, String neededAlias,
RelDataType neededType, Map<String, RelDataType> aliases) {
this.node = node;
this.neededAlias = neededAlias;
this.neededType = neededType;
this.aliases = aliases;
this.clauses = Expressions.list(clauses);
}
/** Once you have a Result of implementing a child relational expression,
* call this method to create a Builder to implement the current relational
* expression by adding additional clauses to the SQL query.
*
* <p>You need to declare which clauses you intend to add. If the clauses
* are "later", you can add to the same query. For example, "GROUP BY" comes
* after "WHERE". But if they are the same or earlier, this method will
* start a new SELECT that wraps the previous result.
*
* <p>When you have called
* {@link Builder#setSelect(SqlNodeList)},
* {@link Builder#setWhere(SqlNode)} etc. call
* {@link Builder#result(SqlNode, Collection, RelNode, Map)}
* to fix the new query.
*
* @param rel Relational expression being implemented
* @param clauses Clauses that will be generated to implement current
* relational expression
* @return A builder
*/
public Builder builder(RelNode rel, Clause... clauses) {
final Clause maxClause = maxClause();
boolean needNew = false;
// If old and new clause are equal and belong to below set,
// then new SELECT wrap is not required
Set<Clause> nonWrapSet = ImmutableSet.of(Clause.SELECT);
for (Clause clause : clauses) {
if (maxClause.ordinal() > clause.ordinal()
|| (maxClause == clause && !nonWrapSet.contains(clause))) {
needNew = true;
}
}
if (rel instanceof Aggregate
&& !dialect.supportsNestedAggregations()
&& hasNestedAggregations((Aggregate) rel)) {
needNew = true;
}
SqlSelect select;
Expressions.FluentList<Clause> clauseList = Expressions.list();
if (needNew) {
select = subSelect();
} else {
select = asSelect();
clauseList.addAll(this.clauses);
}
clauseList.appendAll(clauses);
Context newContext;
final SqlNodeList selectList = select.getSelectList();
if (selectList != null) {
newContext = new Context(dialect, selectList.size()) {
public SqlNode field(int ordinal) {
final SqlNode selectItem = selectList.get(ordinal);
switch (selectItem.getKind()) {
case AS:
return ((SqlCall) selectItem).operand(0);
}
return selectItem;
}
};
} else {
boolean qualified =
!dialect.hasImplicitTableAlias() || aliases.size() > 1;
// basically, we did a subSelect() since needNew is set and neededAlias is not null
// now, we need to make sure that we need to update the alias context.
// if our aliases map has a single element: <neededAlias, rowType>,
// then we don't need to rewrite the alias but otherwise, it should be updated.
if (needNew
&& neededAlias != null
&& (aliases.size() != 1 || !aliases.containsKey(neededAlias))) {
final Map<String, RelDataType> newAliases =
ImmutableMap.of(neededAlias, rel.getInput(0).getRowType());
newContext = aliasContext(newAliases, qualified);
} else {
newContext = aliasContext(aliases, qualified);
}
}
return new Builder(rel, clauseList, select, newContext,
needNew ? null : aliases);
}
private boolean hasNestedAggregations(Aggregate rel) {
if (node instanceof SqlSelect) {
final SqlNodeList selectList = ((SqlSelect) node).getSelectList();
if (selectList != null) {
final Set<Integer> aggregatesArgs = new HashSet<>();
for (AggregateCall aggregateCall : rel.getAggCallList()) {
aggregatesArgs.addAll(aggregateCall.getArgList());
}
for (int aggregatesArg : aggregatesArgs) {
if (selectList.get(aggregatesArg) instanceof SqlBasicCall) {
final SqlBasicCall call =
(SqlBasicCall) selectList.get(aggregatesArg);
for (SqlNode operand : call.getOperands()) {
if (operand instanceof SqlCall
&& ((SqlCall) operand).getOperator() instanceof SqlAggFunction) {
return true;
}
}
}
}
}
}
return false;
}
// make private?
public Clause maxClause() {
Clause maxClause = null;
for (Clause clause : clauses) {
if (maxClause == null || clause.ordinal() > maxClause.ordinal()) {
maxClause = clause;
}
}
assert maxClause != null;
return maxClause;
}
/** Returns a node that can be included in the FROM clause or a JOIN. It has
* an alias that is unique within the query. The alias is implicit if it
* can be derived using the usual rules (For example, "SELECT * FROM emp" is
* equivalent to "SELECT * FROM emp AS emp".) */
public SqlNode asFrom() {
if (neededAlias != null) {
return SqlStdOperatorTable.AS.createCall(POS, node,
new SqlIdentifier(neededAlias, POS));
}
return node;
}
public SqlSelect subSelect() {
return wrapSelect(asFrom());
}
/** Converts a non-query node into a SELECT node. Set operators (UNION,
* INTERSECT, EXCEPT) remain as is. */
public SqlSelect asSelect() {
if (node instanceof SqlSelect) {
return (SqlSelect) node;
}
if (!dialect.hasImplicitTableAlias()) {
return wrapSelect(asFrom());
}
return wrapSelect(node);
}
/** Converts a non-query node into a SELECT node. Set operators (UNION,
* INTERSECT, EXCEPT) and DML operators (INSERT, UPDATE, DELETE, MERGE)
* remain as is. */
public SqlNode asStatement() {
switch (node.getKind()) {
case UNION:
case INTERSECT:
case EXCEPT:
case INSERT:
case UPDATE:
case DELETE:
case MERGE:
return node;
default:
return asSelect();
}
}
/** Converts a non-query node into a SELECT node. Set operators (UNION,
* INTERSECT, EXCEPT) and VALUES remain as is. */
public SqlNode asQueryOrValues() {
switch (node.getKind()) {
case UNION:
case INTERSECT:
case EXCEPT:
case VALUES:
return node;
default:
return asSelect();
}
}
/** Returns a context that always qualifies identifiers. Useful if the
* Context deals with just one arm of a join, yet we wish to generate
* a join condition that qualifies column names to disambiguate them. */
public Context qualifiedContext() {
return aliasContext(aliases, true);
}
/**
* In join, when the left and right nodes have been generated,
* update their alias with 'neededAlias' if not null.
*/
public Result resetAlias() {
if (neededAlias == null) {
return this;
} else {
return new Result(node, clauses, neededAlias, neededType,
ImmutableMap.of(neededAlias, neededType));
}
}
/**
* Sets the alias of the join or correlate just created.
*
* @param alias New alias
* @param type type of the node associated with the alias
*/
public Result resetAlias(String alias, RelDataType type) {
return new Result(node, clauses, alias, neededType,
ImmutableMap.of(alias, type));
}
}
/** Builder. */
public class Builder {
private final RelNode rel;
final List<Clause> clauses;
final SqlSelect select;
public final Context context;
private final Map<String, RelDataType> aliases;
public Builder(RelNode rel, List<Clause> clauses, SqlSelect select,
Context context, Map<String, RelDataType> aliases) {
this.rel = rel;
this.clauses = clauses;
this.select = select;
this.context = context;
this.aliases = aliases;
}
public void setSelect(SqlNodeList nodeList) {
select.setSelectList(nodeList);
}
public void setWhere(SqlNode node) {
assert clauses.contains(Clause.WHERE);
select.setWhere(node);
}
public void setGroupBy(SqlNodeList nodeList) {
assert clauses.contains(Clause.GROUP_BY);
select.setGroupBy(nodeList);
}
public void setHaving(SqlNode node) {
assert clauses.contains(Clause.HAVING);
select.setHaving(node);
}
public void setOrderBy(SqlNodeList nodeList) {
assert clauses.contains(Clause.ORDER_BY);
select.setOrderBy(nodeList);
}
public void setFetch(SqlNode fetch) {
assert clauses.contains(Clause.FETCH);
select.setFetch(fetch);
}
public void setOffset(SqlNode offset) {
assert clauses.contains(Clause.OFFSET);
select.setOffset(offset);
}
public void addOrderItem(List<SqlNode> orderByList,
RelFieldCollation field) {
context.addOrderItem(orderByList, field);
}
public Result result() {
return SqlImplementor.this.result(select, clauses, rel, aliases);
}
}
/** Clauses in a SQL query. Ordered by evaluation order.
* SELECT is set only when there is a NON-TRIVIAL SELECT clause. */
public enum Clause {
FROM, WHERE, GROUP_BY, HAVING, SELECT, SET_OP, ORDER_BY, FETCH, OFFSET
}
}
// End SqlImplementor.java