Move code from JdbcImplementor and JdbcRules to new class SqlImplementor
Project: http://git-wip-us.apache.org/repos/asf/calcite/repo Commit: http://git-wip-us.apache.org/repos/asf/calcite/commit/980d9f81 Tree: http://git-wip-us.apache.org/repos/asf/calcite/tree/980d9f81 Diff: http://git-wip-us.apache.org/repos/asf/calcite/diff/980d9f81 Branch: refs/heads/master Commit: 980d9f811af4e0ff32fc1487e469a51585db48a6 Parents: eedd3dc Author: Julian Hyde <[email protected]> Authored: Mon Dec 7 17:47:31 2015 -0800 Committer: Julian Hyde <[email protected]> Committed: Wed Dec 9 13:40:24 2015 -0800 ---------------------------------------------------------------------- .../calcite/adapter/jdbc/JdbcImplementor.java | 549 +----------- .../apache/calcite/adapter/jdbc/JdbcRules.java | 301 +------ .../calcite/rel/rel2sql/RelToSqlConverter.java | 8 +- .../calcite/rel/rel2sql/SqlImplementor.java | 826 +++++++++++++++++++ 4 files changed, 858 insertions(+), 826 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/calcite/blob/980d9f81/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java index 02f9e07..f66c5f1 100644 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java +++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcImplementor.java @@ -17,566 +17,23 @@ package org.apache.calcite.adapter.jdbc; import org.apache.calcite.adapter.java.JavaTypeFactory; -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.AggregateCall; -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.RexLocalRef; -import org.apache.calcite.rex.RexNode; -import org.apache.calcite.rex.RexProgram; -import org.apache.calcite.sql.SqlBinaryOperator; -import org.apache.calcite.sql.SqlCall; -import org.apache.calcite.sql.SqlDataTypeSpec; +import org.apache.calcite.rel.rel2sql.SqlImplementor; import org.apache.calcite.sql.SqlDialect; -import org.apache.calcite.sql.SqlFunction; -import org.apache.calcite.sql.SqlFunctionCategory; -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.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.fun.SqlCase; -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.BasicSqlType; -import org.apache.calcite.sql.type.ReturnTypes; -import org.apache.calcite.sql.type.SqlTypeName; -import org.apache.calcite.sql.validate.SqlValidatorUtil; -import org.apache.calcite.util.Pair; import org.apache.calcite.util.Util; -import com.google.common.collect.ImmutableList; - -import java.util.AbstractList; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collection; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Set; - /** * State for generating a SQL statement. */ -public class JdbcImplementor { - public static final SqlParserPos POS = SqlParserPos.ZERO; - - /** Oracle's {@code SUBSTR} function. - * Oracle does not support {@link SqlStdOperatorTable#SUBSTRING}. */ - public static final SqlFunction ORACLE_SUBSTR = - new SqlFunction("SUBSTR", SqlKind.OTHER_FUNCTION, - ReturnTypes.ARG0_NULLABLE_VARYING, null, null, - SqlFunctionCategory.STRING); - - final SqlDialect dialect; - private final Set<String> aliasSet = new LinkedHashSet<>(); - +public class JdbcImplementor extends SqlImplementor { public JdbcImplementor(SqlDialect dialect, JavaTypeFactory typeFactory) { - this.dialect = dialect; + super(dialect); Util.discard(typeFactory); } - /** Creates a result based on a single relational expression. */ - public Result result(SqlNode node, Collection<Clause> clauses, RelNode rel) { - final String alias2 = SqlValidatorUtil.getAlias(node, -1); - final String alias3 = alias2 != null ? alias2 : "t"; - final String alias4 = - SqlValidatorUtil.uniquify( - alias3, aliasSet, SqlValidatorUtil.EXPR_SUGGESTER); - final String alias5 = alias2 == null || !alias2.equals(alias4) ? alias4 - : null; - return new Result(node, clauses, alias5, - Collections.singletonList(Pair.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 List<Pair<String, RelDataType>> list = new ArrayList<>(); - list.addAll(leftResult.aliases); - list.addAll(rightResult.aliases); - return new Result(join, Expressions.list(Clause.FROM), null, list); - } - - /** 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 SqlCall - && (((SqlCall) node).getOperator() instanceof SqlSetOperator - || ((SqlCall) node).getOperator() == SqlStdOperatorTable.AS) - : node; - return new SqlSelect(POS, SqlNodeList.EMPTY, null, node, null, null, null, - SqlNodeList.EMPTY, null, null, null); - } - public Result visitChild(int i, RelNode e) { return ((JdbcRel) e).implement(this); } - - /** Context for translating a {@link RexNode} expression (within a - * {@link RelNode}) into a {@link SqlNode} expression (within a SQL parse - * tree). */ - public abstract class Context { - final int fieldCount; - - protected Context(int fieldCount) { - this.fieldCount = fieldCount; - } - - public abstract SqlNode field(int ordinal); - - /** Converts an expression from {@link RexNode} to {@link SqlNode} - * format. */ - SqlNode toSql(RexProgram program, RexNode rex) { - 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 LITERAL: - final RexLiteral literal = (RexLiteral) rex; - if (literal.getTypeName() == SqlTypeName.SYMBOL) { - final SqlLiteral.SqlSymbol symbol = - (SqlLiteral.SqlSymbol) 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.getValue().toString(), - POS); - case APPROXIMATE_NUMERIC: - return SqlLiteral.createApproxNumeric( - literal.getValue().toString(), POS); - case BOOLEAN: - return SqlLiteral.createBoolean((Boolean) literal.getValue(), POS); - case DATE: - return SqlLiteral.createDate((Calendar) literal.getValue(), POS); - case TIME: - return SqlLiteral.createTime((Calendar) literal.getValue(), - literal.getType().getPrecision(), POS); - case TIMESTAMP: - return SqlLiteral.createTimestamp((Calendar) literal.getValue(), - 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); - - default: - final RexCall call = (RexCall) rex; - final SqlOperator op = call.getOperator(); - final List<SqlNode> nodeList = toSql(program, call.getOperands()); - switch (rex.getKind()) { - case CAST: - nodeList.add(toSql(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 left-deep binary tree. - return createLeftCall(op, nodeList); - } - if (op == SqlStdOperatorTable.SUBSTRING) { - switch (dialect.getDatabaseProduct()) { - case ORACLE: - return ORACLE_SUBSTR.createCall(new SqlNodeList(nodeList, POS)); - } - } - return op.createCall(new SqlNodeList(nodeList, POS)); - } - } - - private SqlNode createLeftCall(SqlOperator op, List<SqlNode> nodeList) { - if (nodeList.size() == 2) { - return op.createCall(new SqlNodeList(nodeList, POS)); - } - final List<SqlNode> butLast = Util.skipLast(nodeList); - final SqlNode last = nodeList.get(nodeList.size() - 1); - final SqlNode call = createLeftCall(op, butLast); - return op.createCall(new SqlNodeList(ImmutableList.of(call, last), POS)); - } - - private SqlNode toSql(RelDataType type) { - switch (dialect.getDatabaseProduct()) { - case MYSQL: - switch (type.getSqlTypeName()) { - case VARCHAR: - // MySQL doesn't have a VARCHAR type, only CHAR. - return new SqlDataTypeSpec(new SqlIdentifier("CHAR", POS), - type.getPrecision(), -1, null, null, POS); - case INTEGER: - return new SqlDataTypeSpec(new SqlIdentifier("_UNSIGNED", POS), - type.getPrecision(), -1, null, null, POS); - } - break; - } - if (type instanceof BasicSqlType) { - return new SqlDataTypeSpec( - new SqlIdentifier(type.getSqlTypeName().name(), POS), - type.getPrecision(), - type.getScale(), - type.getCharset() != null - && dialect.supportsCharSet() - ? type.getCharset().name() - : null, - null, - POS); - } - throw new AssertionError(type); // TODO: implement - } - - 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; - } - }; - } - - /** Converts a call to an aggregate function to an expression. */ - public SqlNode toSql(AggregateCall aggCall) { - SqlOperator op = aggCall.getAggregation(); - if (op instanceof SqlSumEmptyIsZeroAggFunction) { - op = SqlStdOperatorTable.SUM; - } - final List<SqlNode> operands = Expressions.list(); - for (int arg : aggCall.getArgList()) { - operands.add(field(arg)); - } - return op.createCall( - aggCall.isDistinct() ? SqlSelectKeyword.DISTINCT.symbol(POS) : null, - POS, operands.toArray(new SqlNode[operands.size()])); - } - - /** 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); - } - 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 JdbcImplementor implementor() { - return JdbcImplementor.this; - } - } - - private static int computeFieldCount( - List<Pair<String, RelDataType>> aliases) { - int x = 0; - for (Pair<String, RelDataType> alias : aliases) { - x += alias.right.getFieldCount(); - } - return x; - } - - Context aliasContext(List<Pair<String, RelDataType>> aliases, - boolean qualified) { - return new AliasContext(aliases, qualified); - } - - Context joinContext(Context leftContext, Context rightContext) { - return new JoinContext(leftContext, rightContext); - } - - /** Implementation of Context that precedes field references with their - * "table alias" based on the current sub-query's FROM clause. */ - public class AliasContext extends Context { - private final boolean qualified; - private final List<Pair<String, RelDataType>> aliases; - - /** Creates an AliasContext; use {@link #aliasContext(List, boolean)}. */ - private AliasContext(List<Pair<String, RelDataType>> aliases, - boolean qualified) { - super(computeFieldCount(aliases)); - this.aliases = aliases; - this.qualified = qualified; - } - - public SqlNode field(int ordinal) { - for (Pair<String, RelDataType> alias : aliases) { - final List<RelDataTypeField> fields = alias.right.getFieldList(); - if (ordinal < fields.size()) { - RelDataTypeField field = fields.get(ordinal); - return new SqlIdentifier(!qualified - ? ImmutableList.of(field.getName()) - : ImmutableList.of(alias.left, 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 Context { - private final JdbcImplementor.Context leftContext; - private final JdbcImplementor.Context rightContext; - - /** Creates a JoinContext; use {@link #joinContext(Context, Context)}. */ - private JoinContext(Context leftContext, Context rightContext) { - super(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; - private final String neededAlias; - private final List<Pair<String, RelDataType>> aliases; - final Expressions.FluentList<Clause> clauses; - - private Result(SqlNode node, Collection<Clause> clauses, String neededAlias, - List<Pair<String, RelDataType>> aliases) { - this.node = node; - this.neededAlias = neededAlias; - 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> - * - * <p>When you have called - * {@link Builder#setSelect(org.apache.calcite.sql.SqlNodeList)}, - * {@link Builder#setWhere(org.apache.calcite.sql.SqlNode)} etc. call - * {@link Builder#result(org.apache.calcite.sql.SqlNode, java.util.Collection, org.apache.calcite.rel.RelNode)} - * to fix the new query.</p> - * - * @param rel Relational expression being implemented - * @param clauses Clauses that will be generated to implement current - * relational expression - * @return A builder - */ - public Builder builder(JdbcRel rel, Clause... clauses) { - final Clause maxClause = maxClause(); - boolean needNew = false; - for (Clause clause : clauses) { - if (maxClause.ordinal() >= clause.ordinal()) { - 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(selectList.size()) { - @Override public SqlNode field(int ordinal) { - final SqlNode selectItem = selectList.get(ordinal); - switch (selectItem.getKind()) { - case AS: - return ((SqlCall) selectItem).operand(0); - } - return selectItem; - } - }; - } else { - newContext = aliasContext(aliases, aliases.size() > 1); - } - return new Builder(rel, clauseList, select, newContext); - } - - // 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. */ - SqlSelect asSelect() { - if (node instanceof SqlSelect) { - return (SqlSelect) node; - } - return wrapSelect(node); - } - - /** Converts a non-query node into a SELECT node. Set operators (UNION, - * INTERSECT, EXCEPT) remain as is. */ - public SqlNode asQuery() { - if (node instanceof SqlCall - && ((SqlCall) node).getOperator() instanceof SqlSetOperator) { - return node; - } - 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); - } - } - - /** Builder. */ - public class Builder { - private final JdbcRel rel; - private final List<Clause> clauses; - private final SqlSelect select; - public final Context context; - - public Builder(JdbcRel rel, List<Clause> clauses, SqlSelect select, - Context context) { - this.rel = rel; - this.clauses = clauses; - this.select = select; - this.context = context; - } - - 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 setOrderBy(SqlNodeList nodeList) { - assert clauses.contains(Clause.ORDER_BY); - select.setOrderBy(nodeList); - } - - public Result result() { - return JdbcImplementor.this.result(select, clauses, rel); - } - } - - /** Clauses in a SQL query. Ordered by evaluation order. - * SELECT is set only when there is a NON-TRIVIAL SELECT clause. */ - enum Clause { - FROM, WHERE, GROUP_BY, HAVING, SELECT, SET_OP, ORDER_BY - } } // End JdbcImplementor.java http://git-wip-us.apache.org/repos/asf/calcite/blob/980d9f81/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java index cd91515..aaec1d9 100644 --- a/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java +++ b/core/src/main/java/org/apache/calcite/adapter/jdbc/JdbcRules.java @@ -16,7 +16,6 @@ */ package org.apache.calcite.adapter.jdbc; -import org.apache.calcite.linq4j.Ord; import org.apache.calcite.linq4j.Queryable; import org.apache.calcite.linq4j.tree.Expression; import org.apache.calcite.linq4j.tree.Expressions; @@ -59,6 +58,7 @@ import org.apache.calcite.rel.logical.LogicalTableModify; import org.apache.calcite.rel.logical.LogicalUnion; import org.apache.calcite.rel.logical.LogicalValues; import org.apache.calcite.rel.metadata.RelMetadataQuery; +import org.apache.calcite.rel.rel2sql.SqlImplementor; import org.apache.calcite.rel.type.RelDataType; import org.apache.calcite.rex.RexCall; import org.apache.calcite.rex.RexInputRef; @@ -69,30 +69,18 @@ import org.apache.calcite.rex.RexNode; import org.apache.calcite.rex.RexProgram; import org.apache.calcite.schema.ModifiableTable; import org.apache.calcite.sql.JoinConditionType; -import org.apache.calcite.sql.JoinType; import org.apache.calcite.sql.SqlAggFunction; -import org.apache.calcite.sql.SqlBasicCall; -import org.apache.calcite.sql.SqlCall; import org.apache.calcite.sql.SqlDialect; -import org.apache.calcite.sql.SqlFunction; -import org.apache.calcite.sql.SqlFunctionCategory; 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.SqlNode; import org.apache.calcite.sql.SqlNodeList; -import org.apache.calcite.sql.SqlOperator; import org.apache.calcite.sql.SqlSelect; import org.apache.calcite.sql.SqlSetOperator; -import org.apache.calcite.sql.fun.SqlCase; import org.apache.calcite.sql.fun.SqlSingleValueAggFunction; import org.apache.calcite.sql.fun.SqlStdOperatorTable; import org.apache.calcite.sql.parser.SqlParserPos; -import org.apache.calcite.sql.type.InferTypes; -import org.apache.calcite.sql.type.OperandTypes; -import org.apache.calcite.sql.type.ReturnTypes; -import org.apache.calcite.sql.validate.SqlValidatorUtil; import org.apache.calcite.util.ImmutableBitSet; import org.apache.calcite.util.Pair; import org.apache.calcite.util.Util; @@ -150,53 +138,6 @@ public class JdbcRules { MYSQL_AGG_FUNCS = builder.build(); } - private static 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 = SqlStdOperatorTable.AS.createCall( - POS, node, new SqlIdentifier(name, POS)); - } - selectList.add(node); - } - - private static JdbcImplementor.Result setOpToSql(JdbcImplementor implementor, - SqlSetOperator operator, JdbcRel rel) { - List<SqlNode> list = Expressions.list(); - for (Ord<RelNode> input : Ord.zip(rel.getInputs())) { - final JdbcImplementor.Result result = - implementor.visitChild(input.i, input.e); - list.add(result.asSelect()); - } - final SqlCall node = operator.createCall(new SqlNodeList(list, POS)); - final List<JdbcImplementor.Clause> clauses = - Expressions.list(JdbcImplementor.Clause.SET_OP); - return implementor.result(node, clauses, rel); - } - - private static boolean isStar(List<RexNode> exps, RelDataType inputRowType) { - 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(); - } - - private static boolean isStar(RexProgram program) { - int i = 0; - for (RexLocalRef ref : program.getProjectList()) { - if (ref.getIndex() != i++) { - return false; - } - } - return i == program.getInputRowType().getFieldCount(); - } - /** Abstract base class for rule that converts to JDBC. */ abstract static class JdbcConverterRule extends ConverterRule { protected final JdbcConvention out; @@ -247,7 +188,7 @@ public class JdbcRules { * Returns whether a condition is supported by {@link JdbcJoin}. * * <p>Corresponds to the capabilities of - * {@link JdbcJoin#convertConditionToSqlNode}. + * {@link SqlImplementor#convertConditionToSqlNode}. * * @param node Condition * @return Whether condition is supported @@ -334,7 +275,7 @@ public class JdbcRules { final JdbcImplementor.Context leftContext = leftResult.qualifiedContext(); final JdbcImplementor.Context rightContext = rightResult.qualifiedContext(); - SqlNode sqlCondition = convertConditionToSqlNode(condition, + SqlNode sqlCondition = SqlImplementor.convertConditionToSqlNode(condition, leftContext, rightContext, left.getRowType().getFieldCount()); @@ -342,117 +283,13 @@ public class JdbcRules { new SqlJoin(POS, leftResult.asFrom(), SqlLiteral.createBoolean(false, POS), - joinType(joinType).symbol(POS), + SqlImplementor.joinType(joinType).symbol(POS), rightResult.asFrom(), JoinConditionType.ON.symbol(POS), sqlCondition); return implementor.result(join, leftResult, rightResult); } - /** - * Convert {@link RexNode} condition into {@link SqlNode} - * - * @param node condition Node - * @param leftContext LeftContext - * @param rightContext RightContext - * @param leftFieldCount Number of field on left result - * @return SqlJoin which represent the condition - */ - private SqlNode convertConditionToSqlNode(RexNode node, - JdbcImplementor.Context leftContext, - JdbcImplementor.Context rightContext, int leftFieldCount) { - if (!(node instanceof RexCall)) { - throw new AssertionError(node); - } - final List<RexNode> operands; - final SqlOperator op; - 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); - 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: - 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)); - } - } - final JdbcImplementor.Context joinContext = - leftContext.implementor().joinContext(leftContext, rightContext); - return joinContext.toSql(null, node); - } - throw new AssertionError(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); - } - } - - private 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); - } - } } /** @@ -529,11 +366,11 @@ public class JdbcRules { ? x.builder(this, JdbcImplementor.Clause.FROM, JdbcImplementor.Clause.WHERE) : x.builder(this, JdbcImplementor.Clause.FROM); - if (!isStar(program)) { + if (!SqlImplementor.isStar(program)) { final List<SqlNode> selectList = new ArrayList<>(); for (RexLocalRef ref : program.getProjectList()) { SqlNode sqlExpr = builder.context.toSql(program, ref); - addSelect(selectList, sqlExpr, getRowType()); + implementor.addSelect(selectList, sqlExpr, getRowType()); } builder.setSelect(new SqlNodeList(selectList, POS)); } @@ -602,7 +439,7 @@ public class JdbcRules { public JdbcImplementor.Result implement(JdbcImplementor implementor) { JdbcImplementor.Result x = implementor.visitChild(0, getInput()); - if (isStar(exps, getInput().getRowType())) { + if (SqlImplementor.isStar(exps, getInput().getRowType())) { return x; } final JdbcImplementor.Builder builder = @@ -610,7 +447,7 @@ public class JdbcRules { final List<SqlNode> selectList = new ArrayList<>(); for (RexNode ref : exps) { SqlNode sqlExpr = builder.context.toSql(null, ref); - addSelect(selectList, sqlExpr, getRowType()); + implementor.addSelect(selectList, sqlExpr, getRowType()); } builder.setSelect(new SqlNodeList(selectList, POS)); return builder.result(); @@ -693,6 +530,18 @@ public class JdbcRules { } } + /** Returns whether this JDBC data source can implement a given aggregate + * function. */ + private static boolean canImplement(SqlAggFunction aggregation, + SqlDialect sqlDialect) { + switch (sqlDialect.getDatabaseProduct()) { + case MYSQL: + return MYSQL_AGG_FUNCS.contains(aggregation); + default: + return AGG_FUNCS.contains(aggregation); + } + } + /** Aggregate operator implemented in JDBC convention. */ public static class JdbcAggregate extends Aggregate implements JdbcRel { public JdbcAggregate( @@ -717,18 +566,6 @@ public class JdbcRules { } } - /** Returns whether this JDBC data source can implement a given aggregate - * function. */ - private boolean canImplement(SqlAggFunction aggregation, - SqlDialect sqlDialect) { - switch (sqlDialect.getDatabaseProduct()) { - case MYSQL: - return MYSQL_AGG_FUNCS.contains(aggregation); - default: - return AGG_FUNCS.contains(aggregation); - } - } - @Override public JdbcAggregate copy(RelTraitSet traitSet, RelNode input, boolean indicator, ImmutableBitSet groupSet, List<ImmutableBitSet> groupSets, List<AggregateCall> aggCalls) { @@ -751,16 +588,16 @@ public class JdbcRules { final List<SqlNode> selectList = new ArrayList<>(); for (int group : groupSet) { final SqlNode field = builder.context.field(group); - addSelect(selectList, field, getRowType()); + implementor.addSelect(selectList, field, getRowType()); groupByList.add(field); } for (AggregateCall aggCall : aggCalls) { SqlNode aggCallSqlNode = builder.context.toSql(aggCall); if (aggCall.getAggregation() instanceof SqlSingleValueAggFunction) { aggCallSqlNode = - rewriteSingleValueExpr(aggCallSqlNode, implementor.dialect); + SqlImplementor.rewriteSingleValueExpr(aggCallSqlNode, implementor.dialect); } - addSelect(selectList, aggCallSqlNode, getRowType()); + implementor.addSelect(selectList, aggCallSqlNode, getRowType()); } builder.setSelect(new SqlNodeList(selectList, POS)); if (!groupByList.isEmpty() || aggCalls.isEmpty()) { @@ -771,84 +608,6 @@ public class JdbcRules { return builder.result(); } - /** Rewrite SINGLE_VALUE into expression based on database variants - * E.g. HSQLDB, MYSQL, ORACLE, etc - */ - private SqlNode rewriteSingleValueExpr(SqlNode aggCall, - SqlDialect sqlDialect) { - final SqlNode operand = ((SqlBasicCall) aggCall).operand(0); - final SqlNode caseOperand; - final SqlNode elseExpr; - final SqlNode countCall = - SqlStdOperatorTable.COUNT.createCall(POS, operand); - - final SqlLiteral nullLiteral = SqlLiteral.createNull(POS); - final SqlNode wrappedOperand; - switch (sqlDialect.getDatabaseProduct()) { - case MYSQL: - case HSQLDB: - // For MySQL, generate - // CASE COUNT(*) - // WHEN 0 THEN NULL - // WHEN 1 THEN <result> - // ELSE (SELECT NULL UNION ALL SELECT NULL) - // END - // - // For hsqldb, generate - // CASE COUNT(*) - // WHEN 0 THEN NULL - // WHEN 1 THEN MIN(<result>) - // ELSE (VALUES 1 UNION ALL VALUES 1) - // END - caseOperand = countCall; - - final SqlNodeList selectList = new SqlNodeList(POS); - selectList.add(nullLiteral); - final SqlNode unionOperand; - switch (sqlDialect.getDatabaseProduct()) { - case MYSQL: - wrappedOperand = operand; - unionOperand = new SqlSelect(POS, SqlNodeList.EMPTY, selectList, - null, null, null, null, SqlNodeList.EMPTY, null, null, null); - break; - default: - wrappedOperand = SqlStdOperatorTable.MIN.createCall(POS, operand); - unionOperand = SqlStdOperatorTable.VALUES.createCall(POS, - SqlLiteral.createApproxNumeric("0", POS)); - } - - SqlCall unionAll = SqlStdOperatorTable.UNION_ALL - .createCall(POS, unionOperand, unionOperand); - - final SqlNodeList subQuery = new SqlNodeList(POS); - subQuery.add(unionAll); - - final SqlNodeList selectList2 = new SqlNodeList(POS); - selectList2.add(nullLiteral); - elseExpr = SqlStdOperatorTable.SCALAR_QUERY.createCall(POS, subQuery); - break; - - default: - LOGGER.fine("SINGLE_VALUE rewrite not supported for " - + sqlDialect.getDatabaseProduct()); - return aggCall; - } - - final SqlNodeList whenList = new SqlNodeList(POS); - whenList.add(SqlLiteral.createExactNumeric("0", POS)); - whenList.add(SqlLiteral.createExactNumeric("1", POS)); - - final SqlNodeList thenList = new SqlNodeList(POS); - thenList.add(nullLiteral); - thenList.add(wrappedOperand); - - SqlNode caseExpr = - new SqlCase(POS, caseOperand, whenList, thenList, elseExpr); - - LOGGER.fine("SINGLE_VALUE rewritten into [" + caseExpr + "]"); - - return caseExpr; - } } /** @@ -905,7 +664,7 @@ public class JdbcRules { && implementor.dialect.getDatabaseProduct() == SqlDialect.DatabaseProduct.MYSQL) { orderByList.add( - ISNULL_FUNCTION.createCall(POS, + SqlImplementor.ISNULL_FUNCTION.createCall(POS, builder.context.field(fieldCollation.getFieldIndex()))); fieldCollation = new RelFieldCollation(fieldCollation.getFieldIndex(), fieldCollation.getDirection()); @@ -917,12 +676,6 @@ public class JdbcRules { } } - /** MySQL specific function. */ - private static final SqlFunction ISNULL_FUNCTION = - new SqlFunction("ISNULL", SqlKind.OTHER_FUNCTION, - ReturnTypes.BOOLEAN, InferTypes.FIRST_KNOWN, - OperandTypes.ANY, SqlFunctionCategory.SYSTEM); - /** * Rule to convert an {@link org.apache.calcite.rel.logical.LogicalUnion} to a * {@link org.apache.calcite.adapter.jdbc.JdbcRules.JdbcUnion}. @@ -964,7 +717,7 @@ public class JdbcRules { final SqlSetOperator operator = all ? SqlStdOperatorTable.UNION_ALL : SqlStdOperatorTable.UNION; - return setOpToSql(implementor, operator, this); + return SqlImplementor.setOpToSql(implementor, operator, this); } } @@ -1008,7 +761,7 @@ public class JdbcRules { } public JdbcImplementor.Result implement(JdbcImplementor implementor) { - return setOpToSql(implementor, + return SqlImplementor.setOpToSql(implementor, all ? SqlStdOperatorTable.INTERSECT_ALL : SqlStdOperatorTable.INTERSECT, @@ -1051,7 +804,7 @@ public class JdbcRules { } public JdbcImplementor.Result implement(JdbcImplementor implementor) { - return setOpToSql(implementor, + return SqlImplementor.setOpToSql(implementor, all ? SqlStdOperatorTable.EXCEPT_ALL : SqlStdOperatorTable.EXCEPT, http://git-wip-us.apache.org/repos/asf/calcite/blob/980d9f81/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java index 2a5fdba..4e51116 100644 --- a/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/RelToSqlConverter.java @@ -86,7 +86,6 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.HashMap; -import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -95,19 +94,16 @@ import java.util.logging.Logger; /** * Utility to convert RelNodes to SqlNode */ -public class RelToSqlConverter { +public class RelToSqlConverter extends SqlImplementor { private static final Logger LOGGER = Logger.getLogger(RelToSqlConverter.class.getName()); public static final SqlParserPos POS = SqlParserPos.ZERO; - final SqlDialect dialect; - private final Set<String> aliasSet = new LinkedHashSet<>(); - private final Map<String, SqlNode> ordinalMap = new HashMap<>(); public RelToSqlConverter(SqlDialect dialect) { - this.dialect = dialect; + super(dialect); } /** http://git-wip-us.apache.org/repos/asf/calcite/blob/980d9f81/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java new file mode 100644 index 0000000..dcae6b1 --- /dev/null +++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java @@ -0,0 +1,826 @@ +/* + * 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.adapter.jdbc.JdbcRel; +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.AggregateCall; +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.RexInputRef; +import org.apache.calcite.rex.RexLiteral; +import org.apache.calcite.rex.RexLocalRef; +import org.apache.calcite.rex.RexNode; +import org.apache.calcite.rex.RexProgram; +import org.apache.calcite.sql.JoinType; +import org.apache.calcite.sql.SqlBasicCall; +import org.apache.calcite.sql.SqlBinaryOperator; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlDataTypeSpec; +import org.apache.calcite.sql.SqlDialect; +import org.apache.calcite.sql.SqlFunction; +import org.apache.calcite.sql.SqlFunctionCategory; +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.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.fun.SqlCase; +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.BasicSqlType; +import org.apache.calcite.sql.type.InferTypes; +import org.apache.calcite.sql.type.OperandTypes; +import org.apache.calcite.sql.type.ReturnTypes; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.sql.validate.SqlValidatorUtil; +import org.apache.calcite.util.Pair; +import org.apache.calcite.util.Util; + +import com.google.common.collect.ImmutableList; + +import java.util.AbstractList; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collection; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Set; +import java.util.logging.Logger; + +/** + * State for generating a SQL statement. + */ +public abstract class SqlImplementor { + private static final Logger LOGGER = + Logger.getLogger(SqlImplementor.class.getName()); + + public static final SqlParserPos POS = SqlParserPos.ZERO; + + /** Oracle's {@code SUBSTR} function. + * Oracle does not support {@link SqlStdOperatorTable#SUBSTRING}. */ + public static final SqlFunction ORACLE_SUBSTR = + new SqlFunction("SUBSTR", SqlKind.OTHER_FUNCTION, + ReturnTypes.ARG0_NULLABLE_VARYING, null, null, + SqlFunctionCategory.STRING); + + /** MySQL specific function. */ + public static final SqlFunction ISNULL_FUNCTION = + new SqlFunction("ISNULL", SqlKind.OTHER_FUNCTION, + ReturnTypes.BOOLEAN, InferTypes.FIRST_KNOWN, + OperandTypes.ANY, SqlFunctionCategory.SYSTEM); + + public final SqlDialect dialect; + protected final Set<String> aliasSet = new LinkedHashSet<>(); + + protected SqlImplementor(SqlDialect dialect) { + this.dialect = dialect; + } + + public abstract Result visitChild(int i, RelNode e); + + /** Rewrite SINGLE_VALUE into expression based on database variants + * E.g. HSQLDB, MYSQL, ORACLE, etc + */ + public static SqlNode rewriteSingleValueExpr(SqlNode aggCall, + SqlDialect sqlDialect) { + final SqlNode operand = ((SqlBasicCall) aggCall).operand(0); + final SqlNode caseOperand; + final SqlNode elseExpr; + final SqlNode countCall = + SqlStdOperatorTable.COUNT.createCall(POS, operand); + + final SqlLiteral nullLiteral = SqlLiteral.createNull(POS); + final SqlNode wrappedOperand; + switch (sqlDialect.getDatabaseProduct()) { + case MYSQL: + case HSQLDB: + // For MySQL, generate + // CASE COUNT(*) + // WHEN 0 THEN NULL + // WHEN 1 THEN <result> + // ELSE (SELECT NULL UNION ALL SELECT NULL) + // END + // + // For hsqldb, generate + // CASE COUNT(*) + // WHEN 0 THEN NULL + // WHEN 1 THEN MIN(<result>) + // ELSE (VALUES 1 UNION ALL VALUES 1) + // END + caseOperand = countCall; + + final SqlNodeList selectList = new SqlNodeList(POS); + selectList.add(nullLiteral); + final SqlNode unionOperand; + switch (sqlDialect.getDatabaseProduct()) { + case MYSQL: + wrappedOperand = operand; + unionOperand = new SqlSelect(POS, SqlNodeList.EMPTY, selectList, + null, null, null, null, SqlNodeList.EMPTY, null, null, null); + break; + default: + wrappedOperand = SqlStdOperatorTable.MIN.createCall(POS, operand); + unionOperand = SqlStdOperatorTable.VALUES.createCall(POS, + SqlLiteral.createApproxNumeric("0", POS)); + } + + SqlCall unionAll = SqlStdOperatorTable.UNION_ALL + .createCall(POS, unionOperand, unionOperand); + + final SqlNodeList subQuery = new SqlNodeList(POS); + subQuery.add(unionAll); + + final SqlNodeList selectList2 = new SqlNodeList(POS); + selectList2.add(nullLiteral); + elseExpr = SqlStdOperatorTable.SCALAR_QUERY.createCall(POS, subQuery); + break; + + default: + LOGGER.fine("SINGLE_VALUE rewrite not supported for " + + sqlDialect.getDatabaseProduct()); + return aggCall; + } + + final SqlNodeList whenList = new SqlNodeList(POS); + whenList.add(SqlLiteral.createExactNumeric("0", POS)); + whenList.add(SqlLiteral.createExactNumeric("1", POS)); + + final SqlNodeList thenList = new SqlNodeList(POS); + thenList.add(nullLiteral); + thenList.add(wrappedOperand); + + SqlNode caseExpr = + new SqlCase(POS, caseOperand, whenList, thenList, elseExpr); + + LOGGER.fine("SINGLE_VALUE rewritten into [" + caseExpr + "]"); + + return caseExpr; + } + + 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 = SqlStdOperatorTable.AS.createCall( + POS, node, new SqlIdentifier(name, POS)); + } + selectList.add(node); + } + + public static boolean isStar(List<RexNode> exps, RelDataType inputRowType) { + 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(); + } + + 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 static Result setOpToSql(SqlImplementor implementor, + SqlSetOperator operator, RelNode rel) { + List<SqlNode> list = Expressions.list(); + for (Ord<RelNode> input : Ord.zip(rel.getInputs())) { + final Result result = + implementor.visitChild(input.i, input.e); + list.add(result.asSelect()); + } + final SqlCall node = operator.createCall(new SqlNodeList(list, POS)); + final List<Clause> clauses = + Expressions.list(Clause.SET_OP); + return implementor.result(node, clauses, rel); + } + + /** + * Convert {@link RexNode} condition into {@link SqlNode} + * + * @param node condition Node + * @param leftContext LeftContext + * @param rightContext RightContext + * @param leftFieldCount Number of field on left result + * @return SqlJoin which represent the condition + */ + public static SqlNode convertConditionToSqlNode(RexNode node, + Context leftContext, + Context rightContext, int leftFieldCount) { + if (!(node instanceof RexCall)) { + throw new AssertionError(node); + } + final List<RexNode> operands; + final SqlOperator op; + 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); + 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: + 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)); + } + } + final Context joinContext = + leftContext.implementor().joinContext(leftContext, rightContext); + return joinContext.toSql(null, node); + } + throw new AssertionError(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) { + final String alias2 = SqlValidatorUtil.getAlias(node, -1); + final String alias3 = alias2 != null ? alias2 : "t"; + final String alias4 = + SqlValidatorUtil.uniquify( + alias3, aliasSet, SqlValidatorUtil.EXPR_SUGGESTER); + final String alias5 = alias2 == null || !alias2.equals(alias4) ? alias4 + : null; + return new Result(node, clauses, alias5, + Collections.singletonList(Pair.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 List<Pair<String, RelDataType>> list = new ArrayList<>(); + list.addAll(leftResult.aliases); + list.addAll(rightResult.aliases); + return new Result(join, Expressions.list(Clause.FROM), null, list); + } + + /** 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 SqlCall + && (((SqlCall) node).getOperator() instanceof SqlSetOperator + || ((SqlCall) node).getOperator() == SqlStdOperatorTable.AS) + : node; + return new SqlSelect(POS, SqlNodeList.EMPTY, null, node, null, null, null, + SqlNodeList.EMPTY, null, null, null); + } + + /** Context for translating a {@link RexNode} expression (within a + * {@link RelNode}) into a {@link SqlNode} expression (within a SQL parse + * tree). */ + public abstract class Context { + final int fieldCount; + + protected Context(int fieldCount) { + this.fieldCount = fieldCount; + } + + public abstract SqlNode field(int ordinal); + + /** Converts an expression from {@link RexNode} to {@link SqlNode} + * format. */ + public SqlNode toSql(RexProgram program, RexNode rex) { + 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 LITERAL: + final RexLiteral literal = (RexLiteral) rex; + if (literal.getTypeName() == SqlTypeName.SYMBOL) { + final SqlLiteral.SqlSymbol symbol = + (SqlLiteral.SqlSymbol) 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.getValue().toString(), + POS); + case APPROXIMATE_NUMERIC: + return SqlLiteral.createApproxNumeric( + literal.getValue().toString(), POS); + case BOOLEAN: + return SqlLiteral.createBoolean((Boolean) literal.getValue(), POS); + case DATE: + return SqlLiteral.createDate((Calendar) literal.getValue(), POS); + case TIME: + return SqlLiteral.createTime((Calendar) literal.getValue(), + literal.getType().getPrecision(), POS); + case TIMESTAMP: + return SqlLiteral.createTimestamp((Calendar) literal.getValue(), + 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); + + default: + final RexCall call = (RexCall) rex; + final SqlOperator op = call.getOperator(); + final List<SqlNode> nodeList = toSql(program, call.getOperands()); + switch (rex.getKind()) { + case CAST: + nodeList.add(toSql(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 left-deep binary tree. + return createLeftCall(op, nodeList); + } + if (op == SqlStdOperatorTable.SUBSTRING) { + switch (dialect.getDatabaseProduct()) { + case ORACLE: + return ORACLE_SUBSTR.createCall(new SqlNodeList(nodeList, POS)); + } + } + return op.createCall(new SqlNodeList(nodeList, POS)); + } + } + + private SqlNode createLeftCall(SqlOperator op, List<SqlNode> nodeList) { + if (nodeList.size() == 2) { + return op.createCall(new SqlNodeList(nodeList, POS)); + } + final List<SqlNode> butLast = Util.skipLast(nodeList); + final SqlNode last = nodeList.get(nodeList.size() - 1); + final SqlNode call = createLeftCall(op, butLast); + return op.createCall(new SqlNodeList(ImmutableList.of(call, last), POS)); + } + + private SqlNode toSql(RelDataType type) { + switch (dialect.getDatabaseProduct()) { + case MYSQL: + switch (type.getSqlTypeName()) { + case VARCHAR: + // MySQL doesn't have a VARCHAR type, only CHAR. + return new SqlDataTypeSpec(new SqlIdentifier("CHAR", POS), + type.getPrecision(), -1, null, null, POS); + case INTEGER: + return new SqlDataTypeSpec(new SqlIdentifier("_UNSIGNED", POS), + type.getPrecision(), -1, null, null, POS); + } + break; + } + if (type instanceof BasicSqlType) { + return new SqlDataTypeSpec( + new SqlIdentifier(type.getSqlTypeName().name(), POS), + type.getPrecision(), + type.getScale(), + type.getCharset() != null + && dialect.supportsCharSet() + ? type.getCharset().name() + : null, + null, + POS); + } + throw new AssertionError(type); // TODO: implement + } + + 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; + } + }; + } + + /** Converts a call to an aggregate function to an expression. */ + public SqlNode toSql(AggregateCall aggCall) { + SqlOperator op = aggCall.getAggregation(); + if (op instanceof SqlSumEmptyIsZeroAggFunction) { + op = SqlStdOperatorTable.SUM; + } + final List<SqlNode> operands = Expressions.list(); + for (int arg : aggCall.getArgList()) { + operands.add(field(arg)); + } + return op.createCall( + aggCall.isDistinct() ? SqlSelectKeyword.DISTINCT.symbol(POS) : null, + POS, operands.toArray(new SqlNode[operands.size()])); + } + + /** 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); + } + 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() { + return SqlImplementor.this; + } + } + + private static int computeFieldCount( + List<Pair<String, RelDataType>> aliases) { + int x = 0; + for (Pair<String, RelDataType> alias : aliases) { + x += alias.right.getFieldCount(); + } + return x; + } + + public Context aliasContext(List<Pair<String, RelDataType>> aliases, + boolean qualified) { + return new AliasContext(aliases, qualified); + } + + public Context joinContext(Context leftContext, Context rightContext) { + return new JoinContext(leftContext, rightContext); + } + + /** Implementation of Context that precedes field references with their + * "table alias" based on the current sub-query's FROM clause. */ + public class AliasContext extends Context { + private final boolean qualified; + private final List<Pair<String, RelDataType>> aliases; + + /** Creates an AliasContext; use {@link #aliasContext(List, boolean)}. */ + private AliasContext(List<Pair<String, RelDataType>> aliases, + boolean qualified) { + super(computeFieldCount(aliases)); + this.aliases = aliases; + this.qualified = qualified; + } + + public SqlNode field(int ordinal) { + for (Pair<String, RelDataType> alias : aliases) { + final List<RelDataTypeField> fields = alias.right.getFieldList(); + if (ordinal < fields.size()) { + RelDataTypeField field = fields.get(ordinal); + return new SqlIdentifier(!qualified + ? ImmutableList.of(field.getName()) + : ImmutableList.of(alias.left, 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 Context { + private final SqlImplementor.Context leftContext; + private final SqlImplementor.Context rightContext; + + /** Creates a JoinContext; use {@link #joinContext(Context, Context)}. */ + private JoinContext(Context leftContext, Context rightContext) { + super(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; + private final String neededAlias; + public final List<Pair<String, RelDataType>> aliases; + final Expressions.FluentList<Clause> clauses; + + public Result(SqlNode node, Collection<Clause> clauses, String neededAlias, + List<Pair<String, RelDataType>> aliases) { + this.node = node; + this.neededAlias = neededAlias; + 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> + * + * <p>When you have called + * {@link Builder#setSelect(SqlNodeList)}, + * {@link Builder#setWhere(SqlNode)} etc. call + * {@link Builder#result(SqlNode, Collection, RelNode)} + * to fix the new query.</p> + * + * @param rel Relational expression being implemented + * @param clauses Clauses that will be generated to implement current + * relational expression + * @return A builder + */ + public Builder builder(JdbcRel rel, Clause... clauses) { + final Clause maxClause = maxClause(); + boolean needNew = false; + for (Clause clause : clauses) { + if (maxClause.ordinal() >= clause.ordinal()) { + 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(selectList.size()) { + @Override public SqlNode field(int ordinal) { + final SqlNode selectItem = selectList.get(ordinal); + switch (selectItem.getKind()) { + case AS: + return ((SqlCall) selectItem).operand(0); + } + return selectItem; + } + }; + } else { + newContext = aliasContext(aliases, aliases.size() > 1); + } + return new Builder(rel, clauseList, select, newContext); + } + + // 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; + } + return wrapSelect(node); + } + + /** Converts a non-query node into a SELECT node. Set operators (UNION, + * INTERSECT, EXCEPT) remain as is. */ + public SqlNode asQuery() { + if (node instanceof SqlCall + && ((SqlCall) node).getOperator() instanceof SqlSetOperator) { + return node; + } + 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); + } + } + + /** Builder. */ + public class Builder { + private final JdbcRel rel; + private final List<Clause> clauses; + private final SqlSelect select; + public final Context context; + + public Builder(JdbcRel rel, List<Clause> clauses, SqlSelect select, + Context context) { + this.rel = rel; + this.clauses = clauses; + this.select = select; + this.context = context; + } + + 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 setOrderBy(SqlNodeList nodeList) { + assert clauses.contains(Clause.ORDER_BY); + select.setOrderBy(nodeList); + } + + public Result result() { + return SqlImplementor.this.result(select, clauses, rel); + } + } + + /** 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 + } +} + +// End SqlImplementor.java
