/** * Copyright (C) 2009-2013 FoundationDB, LLC * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.foundationdb.sql.optimizer.rule; import com.foundationdb.sql.optimizer.plan.*; import com.foundationdb.sql.optimizer.plan.JoinNode; import com.foundationdb.sql.optimizer.plan.JoinNode.JoinType; import com.foundationdb.sql.optimizer.plan.ResultSet.ResultField; import com.foundationdb.sql.optimizer.plan.Sort.OrderByExpression; import com.foundationdb.sql.optimizer.plan.UpdateStatement.UpdateColumn; import static com.foundationdb.sql.optimizer.rule.PlanContext.*; import com.foundationdb.sql.optimizer.*; import com.foundationdb.sql.StandardException; import com.foundationdb.sql.parser.*; import com.foundationdb.sql.types.DataTypeDescriptor; import com.foundationdb.sql.types.TypeId; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.Group; import com.foundationdb.ais.model.Routine; import com.foundationdb.ais.model.Table; import com.foundationdb.server.error.DefaultOutsideInsertException; import com.foundationdb.server.error.InsertWrongCountException; import com.foundationdb.server.error.NoSuchTableException; import com.foundationdb.server.error.OrderGroupByIntegerOutOfRange; import com.foundationdb.server.error.OrderGroupByNonIntegerConstant; import com.foundationdb.server.error.ProtectedTableDDLException; import com.foundationdb.server.error.SQLParserInternalException; import com.foundationdb.server.error.SetWrongNumColumns; import com.foundationdb.server.error.UnsupportedSQLException; import com.foundationdb.server.error.UnsupportedGroupByRollupException; import com.foundationdb.server.error.WrongExpressionArityException; import com.foundationdb.server.types.TInstance; import com.foundationdb.server.types.TPreptimeValue; import com.foundationdb.server.types.common.types.TypesTranslator; import com.foundationdb.server.types.value.Value; import com.foundationdb.server.types.texpressions.Comparison; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; import java.sql.Types; /** Turn a parsed SQL AST into this package's format. */ public class ASTStatementLoader extends BaseRule { // TODO: Maybe move this into a separate class. public static final int IN_TO_OR_MAX_COUNT_DEFAULT = 1; private static final Logger logger = LoggerFactory.getLogger(ASTStatementLoader.class); @Override protected Logger getLogger() { return logger; } public static final WhiteboardMarker<AST> MARKER = new DefaultWhiteboardMarker<>(); /** Recover the {@link AST} put on the whiteboard when loaded. */ public static AST getAST(PlanContext plan) { return plan.getWhiteboard(MARKER); } @Override public void apply(PlanContext plan) { AST ast = (AST)plan.getPlan(); plan.putWhiteboard(MARKER, ast); try { plan.setPlan(new Loader((SchemaRulesContext)plan.getRulesContext()).toStatement(ast)); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } } static class Loader { private SchemaRulesContext rulesContext; private TypesTranslator typesTranslator; private List<ParameterNode> parameters; Loader(SchemaRulesContext rulesContext) { this.rulesContext = rulesContext; typesTranslator = rulesContext.getTypesTranslator(); pushEquivalenceFinder(); } private void pushEquivalenceFinder() { columnEquivalences.push(new EquivalenceFinder<ColumnExpression>() { @Override protected String describeElement(ColumnExpression element) { return element.getSQLsource().getTableName() + "." + element.getColumn().getName(); } }); } private void popEquivalenceFinder() { columnEquivalences.pop(); } private EquivalenceFinder<ColumnExpression> peekEquivalenceFinder() { return columnEquivalences.element(); } /** Convert given statement into appropriate intermediate form. */ public BaseStatement toStatement(AST ast) throws StandardException { parameters = ast.getParameters(); return toStatement(ast.getStatement()); } protected BaseStatement toStatement(DMLStatementNode stmt) throws StandardException { switch (stmt.getNodeType()) { case NodeTypes.CURSOR_NODE: return toSelectQuery((CursorNode)stmt); case NodeTypes.DELETE_NODE: return toDeleteStatement((DeleteNode)stmt); case NodeTypes.UPDATE_NODE: return toUpdateStatement((UpdateNode)stmt); case NodeTypes.INSERT_NODE: return toInsertStatement((InsertNode)stmt); default: throw new StandardException("Unsupported statement type: " + stmt.statementToString()); } } // SELECT protected SelectQuery toSelectQuery(CursorNode cursorNode) throws StandardException { PlanNode query = toQueryForSelect(cursorNode.getResultSetNode(), cursorNode.getOrderByList(), cursorNode.getOffsetClause(), cursorNode.getFetchFirstClause(), true); if (cursorNode.getUpdateMode() == CursorNode.UpdateMode.UPDATE) throw new UnsupportedSQLException("FOR UPDATE", cursorNode); return new SelectQuery(query, peekEquivalenceFinder()); } // UPDATE protected DMLStatement toUpdateStatement(UpdateNode updateNode) throws StandardException { ResultSetNode rs = updateNode.getResultSetNode(); PlanNode query = toQuery((SelectNode)rs); TableNode targetTable = getTargetTable(updateNode); TableSource selectTable = getTargetTableSource(updateNode); assert (selectTable.getTable() == targetTable); ResultColumnList rcl = rs.getResultColumns(); List<UpdateColumn> updateColumns = new ArrayList<>(rcl.size()); for (ResultColumn result : rcl) { Column column = getColumnReferenceColumn(result.getReference(), "result column"); ExpressionNode value = toExpression(result.getExpression()); updateColumns.add(new UpdateColumn(column, value)); } ReturningValues values = calculateReturningValues(updateNode.getReturningList(), (FromTable)updateNode.getUserData()); query = new UpdateStatement(query, targetTable, updateColumns, values.table); if (values.row != null) { query = new Project(query, values.row); } return new DMLStatement(query, BaseUpdateStatement.StatementType.UPDATE, selectTable, targetTable, values.results, values.table, peekEquivalenceFinder()); } // INSERT protected DMLStatement toInsertStatement(InsertNode insertNode) throws StandardException { PlanNode query = toQueryForSelect(insertNode.getResultSetNode(), insertNode.getOrderByList(), insertNode.getOffset(), insertNode.getFetchFirst(), false); if (query instanceof ResultSet) query = ((ResultSet)query).getInput(); TableNode targetTable = getTargetTable(insertNode); ResultColumnList rcl = insertNode.getTargetColumnList(); int ncols = insertNode.getResultSetNode().getResultColumns().size(); List<Column> targetColumns; if (rcl != null) { if (ncols != rcl.size()) throw new InsertWrongCountException(rcl.size(), ncols); targetColumns = new ArrayList<>(rcl.size()); for (ResultColumn resultColumn : rcl) { Column column = getColumnReferenceColumn(resultColumn.getReference(), "Unsupported target column"); targetColumns.add(column); } } else { // No explicit column list: use DDL order. List<Column> aisColumns = targetTable.getTable().getColumns(); if (ncols > aisColumns.size()) throw new InsertWrongCountException(aisColumns.size(), ncols); targetColumns = new ArrayList<>(ncols); for (int i = 0; i < ncols; i++) { targetColumns.add(aisColumns.get(i)); } } ReturningValues values = calculateReturningValues(insertNode.getReturningList(), (FromTable)insertNode.getUserData()); query = new InsertStatement(query, targetTable, targetColumns, values.table); if (values.row != null) { query = new Project(query, values.row); } return new DMLStatement(query, BaseUpdateStatement.StatementType.INSERT, null, targetTable, values.results, values.table, peekEquivalenceFinder()); } // DELETE protected DMLStatement toDeleteStatement(DeleteNode deleteNode) throws StandardException { PlanNode query = toQuery((SelectNode)deleteNode.getResultSetNode()); TableNode targetTable = getTargetTable(deleteNode); TableSource selectTable = getTargetTableSource(deleteNode); assert (selectTable.getTable() == targetTable); ReturningValues values = calculateReturningValues (deleteNode.getReturningList(), (FromTable)deleteNode.getUserData()); query = new DeleteStatement(query, targetTable, values.table); if (values.row != null) { query = new Project (query, values.row); } return new DMLStatement(query, BaseUpdateStatement.StatementType.DELETE, selectTable, targetTable, values.results, values.table, peekEquivalenceFinder()); } private class ReturningValues { public List<ExpressionNode> row = null; public TableSource table = null; public List<ResultField> results = null; } protected ReturningValues calculateReturningValues(ResultColumnList rcl, FromTable table) throws StandardException { ReturningValues values = new ReturningValues(); if (rcl != null) { values.table = (TableSource)toJoinNode(table, true); values.row = new ArrayList<>(rcl.size()); for (ResultColumn resultColumn : rcl) { values.row.add(toExpression(resultColumn.getExpression())); } values.results = resultColumns(rcl); } return values; } /** The query part of SELECT / INSERT, which might be VALUES / UNION */ protected PlanNode toQueryForSelect(ResultSetNode resultSet, OrderByList orderByList, ValueNode offsetClause, ValueNode fetchFirstClause, boolean needResultSet) throws StandardException { if (resultSet instanceof SelectNode) return toQueryForSelect((SelectNode)resultSet, orderByList, offsetClause, fetchFirstClause); else if (resultSet instanceof RowResultSetNode) { List<ExpressionNode> row = toExpressionsRow(resultSet); List<List<ExpressionNode>> rows = new ArrayList<>(1); rows.add(row); return newValues(rows, needResultSet, resultSet); } else if (resultSet instanceof RowsResultSetNode) { List<List<ExpressionNode>> rows = new ArrayList<>(); for (ResultSetNode row : ((RowsResultSetNode)resultSet).getRows()) { rows.add(toExpressionsRow(row)); } return newValues(rows, needResultSet, resultSet); } else if (resultSet instanceof UnionNode || resultSet instanceof IntersectOrExceptNode) { return newSetOperation(resultSet, orderByList, offsetClause, fetchFirstClause); } else throw new UnsupportedSQLException("Unsupported query", resultSet); } protected PlanNode newValues(List<List<ExpressionNode>> rows, boolean needResultSet, ResultSetNode resultSetNode) throws StandardException { ExpressionsSource expr = new ExpressionsSource(rows); if (needResultSet) return new ResultSet(expr, resultColumns(resultSetNode.getResultColumns())); else return expr; } // This is a little ugly. This looks down the Plan Node tree for the // inputs to the Union node, looking for Project (or Union), then // adds castExpressions to the Projects to ensure the two inputs // have the same types. // e.g. select 1 UNION select 'a' -> both output as INTs protected PlanNode newSetOperation(ResultSetNode setNode, OrderByList orderByList, ValueNode offsetClause, ValueNode fetchFirstClause) throws StandardException { SetOperatorNode setOperatorNode = (SetOperatorNode)setNode; String opName = ""; SetPlanNode.opEnum operationType = SetPlanNode.opEnum.NULL; if(setNode instanceof UnionNode) { opName = "Union"; operationType = SetPlanNode.opEnum.UNION; } else if(setNode instanceof IntersectOrExceptNode){ if(((IntersectOrExceptNode)setOperatorNode).getOperatorName().equals("INTERSECT")){ opName = "Intersect"; operationType = SetPlanNode.opEnum.INTERSECT; }else{ opName = "Except"; operationType = SetPlanNode.opEnum.EXCEPT; } }//recast to more specific class PlanNode left = toQueryForSelect(setOperatorNode.getLeftResultSet()); PlanNode right = toQueryForSelect(setOperatorNode.getRightResultSet()); List<ResultField> results = new ArrayList<>(setNode.getResultColumns().size()); List<ExpressionNode> projects = new ArrayList<>(setNode.getResultColumns().size()); if (((ResultSet)left).getFields().size() != ((ResultSet)right).getFields().size()) { throw new SetWrongNumColumns (((ResultSet)left).getFields().size(),((ResultSet)right).getFields().size()); } Project leftProject = getProject(left); Project rightProject= getProject(right); for (int i= 0; i < setNode.getResultColumns().size(); i++) { DataTypeDescriptor leftType = leftProject.getFields().get(i).getSQLtype(); DataTypeDescriptor rightType = rightProject.getFields().get(i).getSQLtype(); DataTypeDescriptor projectType = null; Project useProject = leftProject; // Case of SELECT null setNode SELECT null -> pick a type if (leftType == null && rightType == null) { projectType = new DataTypeDescriptor (TypeId.VARCHAR_ID, true); } else if (leftType == null) { projectType = rightType; useProject = rightProject; } else if (rightType == null) { projectType = leftType; } else { projectType = leftType.getDominantType(rightType); } assert (projectType != null); TInstance type = typesTranslator.typeForSQLType(projectType); //projectType = setNode.getResultColumns().get(i).getExpression().getType(); results.add(resultColumn(setNode.getResultColumns().get(i), i, projectType)); projects.add(new ColumnExpression (useProject, i, projectType, useProject.getFields().get(i).getSQLsource(), type)); } SetPlanNode newSetNode = new SetPlanNode(left, right, setOperatorNode.isAll(), opName); newSetNode.setOperationType(operationType); newSetNode.setResults(results); List<OrderByExpression> sorts = new ArrayList<>(); if (orderByList != null) { for (OrderByColumn orderByColumn : orderByList) { ExpressionNode expression = toOrderGroupBy(orderByColumn.getExpression(), projects, "ORDER"); sorts.add(new OrderByExpression(expression, orderByColumn.isAscending())); } } Project project = new Project (newSetNode, projects); PlanNode query = project; if (!sorts.isEmpty()) { query = new Sort(query, sorts); } if (( offsetClause != null) || fetchFirstClause != null){ query = toLimit(query, offsetClause, fetchFirstClause); } query = new ResultSet(query, results); return query; } protected Project getProject(PlanNode node) { PlanNode project = ((BasePlanWithInput)node).getInput(); if (project instanceof Project) { return (Project) project; } else if (!(project instanceof BasePlanWithInput)) return null; project = ((BasePlanWithInput)project).getInput(); if (project instanceof Project) return (Project)project; // Add a project on top of the (nested) union // to make sure the casts work on the way up return null; } protected ResultField resultColumn(ResultColumn result, int i, DataTypeDescriptor type) throws StandardException { String name = result.getName(); boolean nameDefaulted = (result.getExpression() instanceof ColumnReference) && (name == ((ColumnReference)result.getExpression()).getColumnName()); Column column = null; if (result.getExpression() instanceof ColumnReference) { ExpressionNode expr = toExpression(result.getExpression()); if (expr instanceof ColumnExpression) { column = ((ColumnExpression)expr).getColumn(); if ((column != null) && nameDefaulted) name = column.getName(); } } if (name == null) { name = "_SQL_COL_" + (i + 1); // Cf. SQLParser.generateColumnName() } return new ResultField(name, type, column); } protected List<ResultField> resultColumns(ResultColumnList rcl) throws StandardException { int nfields = rcl.size(); List<ResultField> results = new ArrayList<>(nfields); for (int i = 0; i < nfields; i++) { ResultColumn result = rcl.get(i); results.add(resultColumn(result, i, result.getType())); } return results; } /** A normal SELECT */ protected PlanNode toQueryForSelect(SelectNode selectNode, OrderByList orderByList, ValueNode offsetClause, ValueNode fetchFirstClause) throws StandardException { PlanNode query = toQuery(selectNode); ResultColumnList rcl = selectNode.getResultColumns(); List<ResultField> results = resultColumns (rcl); List<ExpressionNode> projects = new ArrayList<>(rcl.size()); for (ResultColumn result : rcl) { ExpressionNode expr = toExpression(result.getExpression()); projects.add(expr); } List<OrderByExpression> sorts = new ArrayList<>(); if (orderByList != null) { for (OrderByColumn orderByColumn : orderByList) { ExpressionNode expression = toOrderGroupBy(orderByColumn.getExpression(), projects, "ORDER"); sorts.add(new OrderByExpression(expression, orderByColumn.isAscending())); } } // Stupid but legal: // SELECT 1 FROM t ORDER BY MAX(c); // SELECT 1 FROM t HAVING MAX(c) > 0; if ((selectNode.getGroupByList() != null) || (selectNode.getHavingClause() != null) || hasAggregateFunction(projects) || hasAggregateFunctionA(sorts)) { query = toAggregateSource(query, selectNode.getGroupByList(), projects); query = new Select(query, toConditions(selectNode.getHavingClause(), projects)); } if (selectNode.hasWindows()) throw new UnsupportedSQLException("WINDOW", selectNode); do_distinct: { // Distinct puts the Project before any Sort so as to only do one sort. if (selectNode.isDistinct()) { Project project = new Project(query, projects); if (sorts.isEmpty()) { query = new Distinct(project); break do_distinct; } else if (adjustSortsForDistinct(sorts, project)) { query = new Sort(project, sorts); query = new Distinct(query, Distinct.Implementation.EXPLICIT_SORT); break do_distinct; } else { query = new AggregateSource(query, new ArrayList<>((projects))); // Don't break: treat like non-distinct case. } } if (!sorts.isEmpty()) { query = new Sort(query, sorts); } query = new Project(query, projects); } if ((offsetClause != null) || (fetchFirstClause != null)) query = toLimit(query, offsetClause, fetchFirstClause); query = new ResultSet(query, results); return query; } protected PlanNode toQueryForSelect(ResultSetNode resultSet) throws StandardException { return toQueryForSelect(resultSet, null, null, null, false); } protected List<ExpressionNode> toExpressionsRow(ResultSetNode resultSet) throws StandardException { ResultColumnList resultColumns = resultSet.getResultColumns(); List<ExpressionNode> row = new ArrayList<>(resultColumns.size()); for (ResultColumn resultColumn : resultColumns) { row.add(toExpression(resultColumn.getExpression())); } return row; } /** The common top-level join and select part of all statements. */ protected PlanNode toQuery(SelectNode selectNode) throws StandardException { PlanNode input; if (!selectNode.getFromList().isEmpty()) { Joinable joins = null; for (FromTable fromTable : selectNode.getFromList()) { if (joins == null) joins = toJoinNode(fromTable, true); else joins = joinNodes(joins, toJoinNode(fromTable, true), JoinType.INNER); } input = joins; } else { // No FROM list means one row with presumably constant Projects. input = new ExpressionsSource(Collections.singletonList(Collections.<ExpressionNode>emptyList())); } ConditionList conditions = toConditions(selectNode.getWhereClause()); return new Select(input, conditions); } protected Map<FromTable,Joinable> joinNodes = new HashMap<>(); protected Joinable toJoinNode(FromTable fromTable, boolean required) throws StandardException { Joinable result; if (fromTable instanceof FromBaseTable) { TableBinding tb = (TableBinding)fromTable.getUserData(); if (tb == null) throw new UnsupportedSQLException("FROM table", fromTable); Table aisTable = (Table)tb.getTable(); TableNode table = getTableNode(aisTable); String name = fromTable.getCorrelationName(); if (name == null) { if (aisTable.getName().getSchemaName().equals(rulesContext.getDefaultSchemaName())) name = aisTable.getName().getTableName(); else name = aisTable.getName().toString(); } result = new TableSource(table, required, name); } else if (fromTable instanceof com.foundationdb.sql.parser.JoinNode) { com.foundationdb.sql.parser.JoinNode joinNode = (com.foundationdb.sql.parser.JoinNode)fromTable; JoinType joinType; switch (joinNode.getNodeType()) { case NodeTypes.JOIN_NODE: joinType = JoinType.INNER; break; case NodeTypes.HALF_OUTER_JOIN_NODE: if (((HalfOuterJoinNode)joinNode).isRightOuterJoin()) joinType = JoinType.RIGHT; else joinType = JoinType.LEFT; break; default: throw new UnsupportedSQLException("Unsupported join type", joinNode); } JoinNode join = joinNodes(toJoinNode((FromTable)joinNode.getLeftResultSet(), required && (joinType != JoinType.RIGHT)), toJoinNode((FromTable)joinNode.getRightResultSet(), required && (joinType != JoinType.LEFT)), joinType); join.setJoinConditions(toConditions(joinNode.getJoinClause())); result = join; } else if (fromTable instanceof FromSubquery) { FromSubquery fromSubquery = (FromSubquery)fromTable; PlanNode subquery = toQueryForSelect(fromSubquery.getSubquery(), fromSubquery.getOrderByList(), fromSubquery.getOffset(), fromSubquery.getFetchFirst(), false); result = new SubquerySource(new Subquery(subquery, peekEquivalenceFinder()), fromSubquery.getExposedName()); } else throw new UnsupportedSQLException("Unsupported FROM non-table", fromTable); joinNodes.put(fromTable, result); return result; } protected JoinNode joinNodes(Joinable left, Joinable right, JoinType joinType) throws StandardException { return new JoinNode(left, right, joinType); } /** Add a set of conditions to input. */ protected ConditionList toConditions(ValueNode cnfClause) throws StandardException { return toConditions(cnfClause, null); } protected ConditionList toConditions(ValueNode cnfClause, List<ExpressionNode> projects) throws StandardException { ConditionList conditions = new ConditionList(); while (cnfClause != null) { if (cnfClause.isBooleanTrue()) break; if (!(cnfClause instanceof AndNode)) throw new UnsupportedSQLException("Unsupported complex WHERE", cnfClause); AndNode andNode = (AndNode)cnfClause; cnfClause = andNode.getRightOperand(); ValueNode condition = andNode.getLeftOperand(); addCondition(conditions, condition, projects); } return conditions; } /** Fill the given list with conditions from given AST node. * Takes a list because BETWEEN generates <em>two</em> conditions. */ protected void addCondition(List<ConditionExpression> conditions, ValueNode condition, List<ExpressionNode> projects) throws StandardException { DataTypeDescriptor conditionType = null; TInstance conditionInst; switch (condition.getNodeType()) { case NodeTypes.BINARY_EQUALS_OPERATOR_NODE: addComparisonCondition(conditions, projects, (BinaryOperatorNode)condition, Comparison.EQ); return; case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE: addComparisonCondition(conditions, projects, (BinaryOperatorNode)condition, Comparison.GT); return; case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE: addComparisonCondition(conditions, projects, (BinaryOperatorNode)condition, Comparison.GE); return; case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE: addComparisonCondition(conditions, projects, (BinaryOperatorNode)condition, Comparison.LT); return; case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE: addComparisonCondition(conditions, projects, (BinaryOperatorNode)condition, Comparison.LE); return; case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE: addComparisonCondition(conditions, projects, (BinaryOperatorNode)condition, Comparison.NE); return; case NodeTypes.BETWEEN_OPERATOR_NODE: addBetweenCondition(conditions, projects, (BetweenOperatorNode)condition); return; case NodeTypes.IN_LIST_OPERATOR_NODE: addInCondition(conditions, projects, (InListOperatorNode)condition); return; case NodeTypes.SUBQUERY_NODE: addSubqueryCondition(conditions, projects, (SubqueryNode)condition); return; case NodeTypes.LIKE_OPERATOR_NODE: addFunctionCondition(conditions, projects, (TernaryOperatorNode)condition); return; case NodeTypes.IS_NULL_NODE: case NodeTypes.IS_NOT_NULL_NODE: addIsNullCondition(conditions, projects, (IsNullNode)condition); return; case NodeTypes.IS_NODE: addIsCondition(conditions, projects, (IsNode)condition); return; case NodeTypes.OR_NODE: case NodeTypes.AND_NODE: case NodeTypes.NOT_NODE: addLogicalFunctionCondition(conditions, projects, condition); return; case NodeTypes.BOOLEAN_CONSTANT_NODE: conditions.add(new BooleanConstantExpression(((BooleanConstantNode)condition).getBooleanValue())); return; case NodeTypes.UNTYPED_NULL_CONSTANT_NODE: conditions.add(new BooleanConstantExpression(null)); return; case NodeTypes.PARAMETER_NODE: assert (parameters != null) && parameters.contains(condition) : condition; conditionType = condition.getType(); if (conditionType == null) { conditionType = new DataTypeDescriptor(TypeId.BOOLEAN_ID, true); condition.setType(conditionType); } conditionInst = typesTranslator.typeForSQLType(conditionType); conditions.add(new ParameterCondition(((ParameterNode)condition) .getParameterNumber(), conditionType, condition, conditionInst)); return; case NodeTypes.CAST_NODE: // Use given cast type if it's suitable for a condition. conditionType = condition.getType(); // CAST inside to BOOLEAN below. condition = ((CastNode)condition).getCastOperand(); break; case NodeTypes.JAVA_TO_SQL_VALUE_NODE: conditions.add((ConditionExpression) toExpression(((JavaToSQLValueNode)condition).getJavaValueNode(), condition, true, projects)); return; } // Anything else gets CAST to BOOLEAN, which may fail // later due to lack of a suitable cast. if (conditionType == null) conditionType = condition.getType(); if (conditionType == null) conditionType = new DataTypeDescriptor(TypeId.BOOLEAN_ID, true); else if (!conditionType.getTypeId().isBooleanTypeId()) conditionType = new DataTypeDescriptor(TypeId.BOOLEAN_ID, conditionType.isNullable()); conditionInst = typesTranslator.typeForSQLType(conditionType); conditions.add(new BooleanCastExpression(toExpression(condition, projects), conditionType, condition, conditionInst)); } protected void addComparisonCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, BinaryOperatorNode binop, Comparison op) throws StandardException { ExpressionNode left = toExpression(binop.getLeftOperand(), projects); ExpressionNode right = toExpression(binop.getRightOperand(), projects); TInstance type = typesTranslator.typeForSQLType(binop.getType()); conditions.add(new ComparisonCondition(op, left, right, binop.getType(), binop, type)); } protected void addBetweenCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, BetweenOperatorNode between) throws StandardException { ExpressionNode left = toExpression(between.getLeftOperand(), projects); ValueNodeList rightOperandList = between.getRightOperandList(); ExpressionNode right1 = toExpression(rightOperandList.get(0), projects); ExpressionNode right2 = toExpression(rightOperandList.get(1), projects); DataTypeDescriptor sqlType = between.getType(); TInstance type = typesTranslator.typeForSQLType(sqlType); conditions.add(new ComparisonCondition(Comparison.GE, left, right1, sqlType, null, type)); conditions.add(new ComparisonCondition(Comparison.LE, left, right2, sqlType, null, type)); } protected void addInCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, InListOperatorNode in) throws StandardException { RowConstructorNode lhs = in.getLeftOperand(); RowConstructorNode rhs = in.getRightOperandList(); ValueNodeList leftOperandList = lhs.getNodeList(); ValueNodeList rightOperandList = rhs.getNodeList(); ConditionExpression inCondition; if (rightOperandList.size() <= getInToOrMaxCount()) { inCondition = buildInConditionNested(in, projects); } else { List<List<ExpressionNode>> rows = new ArrayList<>(); for (ValueNode rightOperand : rightOperandList) { List<ExpressionNode> row = new ArrayList<>(1); flattenInSameShape(row, rightOperand, lhs, projects); rows.add(row); } ExpressionsSource source = new ExpressionsSource(rows); List<ConditionExpression> conds = new ArrayList<>(); flattenAnyComparisons(conds, leftOperandList, source, projects, in.getType()); ConditionExpression combined = null; for (ConditionExpression cond : conds) { combined = andConditions(combined, cond); } List<ExpressionNode> fields = new ArrayList<>(1); fields.add(combined); PlanNode subquery = new Project(source, fields); TInstance type = typesTranslator.typeForSQLType(in.getType()); inCondition = new AnyCondition(new Subquery(subquery, peekEquivalenceFinder()), in.getType(), in, type); } if (in.isNegated()) { inCondition = negateCondition(inCondition, in); } conditions.add(inCondition); } protected ConditionExpression getEqual(InListOperatorNode in, List<ExpressionNode> projects, ValueNode left, ValueNode right) throws StandardException { if (right instanceof RowConstructorNode) { if (left instanceof RowConstructorNode) { ValueNodeList leftList = ((RowConstructorNode)left).getNodeList(); ValueNodeList rightList = ((RowConstructorNode)right).getNodeList(); if (leftList.size() != rightList.size()) throw new IllegalArgumentException("mismatched columns count in IN " + "left : " + leftList.size() + ", right: " + rightList.size()); ConditionExpression result = null; for (int n = 0; n < leftList.size(); ++n) { ConditionExpression equalNode = getEqual(in, projects, leftList.get(n), rightList.get(n)); result = andConditions(result, equalNode); } return result; } else throw new IllegalArgumentException("mismatchec column count in IN"); } else { if (left instanceof RowConstructorNode) { ValueNodeList leftList = ((RowConstructorNode)left).getNodeList(); if (leftList.size() != 1) throw new IllegalArgumentException("mismatch columns count in IN"); left = leftList.get(0); } ExpressionNode rightExp = toExpression(right, projects); ExpressionNode leftExp = toExpression(left, projects); TInstance type = typesTranslator.typeForSQLType(in.getType()); return new ComparisonCondition(Comparison.EQ, leftExp, rightExp, in.getType(), in, type); } } protected ConditionExpression buildInConditionNested(InListOperatorNode in, List<ExpressionNode> projects) throws StandardException { RowConstructorNode leftRow = in.getLeftOperand(); RowConstructorNode rightRow = in.getRightOperandList(); ConditionExpression result = null; for (ValueNode rightNode : rightRow.getNodeList()) { ConditionExpression equalNode = getEqual(in, projects, leftRow, rightNode); if (result == null) result = equalNode; else { List<ConditionExpression> operands = new ArrayList<>(2); operands.add(result); operands.add(equalNode); TInstance type = typesTranslator.typeForSQLType(in.getType()); result = new LogicalFunctionCondition("or", operands, in.getType(), in, type); } } return result; } private void flattenInSameShape(List<ExpressionNode> row, ValueNode rightOperand, ValueNode leftOperand, List<ExpressionNode> projects) throws StandardException { if (rightOperand instanceof RowConstructorNode) { if (!(leftOperand instanceof RowConstructorNode)) throw new IllegalArgumentException("Row value given where single expected"); ValueNodeList leftList = ((RowConstructorNode)leftOperand).getNodeList(); ValueNodeList rightList = ((RowConstructorNode)rightOperand).getNodeList(); if (leftList.size() != rightList.size()) throw new IllegalArgumentException("mismatched columns count in IN " + "left : " + leftList.size() + ", right: " + rightList.size()); for (int i = 0; i < leftList.size(); i++) { flattenInSameShape(row, rightList.get(i), leftList.get(i), projects); } } else { if ((leftOperand instanceof RowConstructorNode) && (((RowConstructorNode)leftOperand).getNodeList().size() != 1)) throw new IllegalArgumentException("Single value given where row expected"); row.add(toExpression(rightOperand, projects)); } } private void flattenAnyComparisons(List<ConditionExpression> conds, ValueNodeList leftOperandList, ExpressionsSource source, List<ExpressionNode> projects, DataTypeDescriptor sqlType) throws StandardException { TInstance type = typesTranslator.typeForSQLType(sqlType); for (ValueNode leftOperand : leftOperandList) { if (leftOperand instanceof RowConstructorNode) { flattenAnyComparisons(conds, ((RowConstructorNode)leftOperand).getNodeList(), source, projects, sqlType); } else { ExpressionNode left = toExpression(leftOperand, projects); DataTypeDescriptor leftType = left.getSQLtype(); TInstance leftInst = typesTranslator.typeForSQLType(leftType); ConditionExpression cond = new ComparisonCondition(Comparison.EQ, left, new ColumnExpression(source, conds.size(), leftType, null, leftInst), sqlType, null, type); conds.add(cond); } } } protected void addSubqueryCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, SubqueryNode subqueryNode) throws StandardException { PlanNode subquery = toQueryForSelect(subqueryNode.getResultSet(), subqueryNode.getOrderByList(), subqueryNode.getOffset(), subqueryNode.getFetchFirst(), false); if (subquery instanceof ResultSet) subquery = ((ResultSet)subquery).getInput(); boolean negate = false; Comparison comp = Comparison.EQ; List<ExpressionNode> operands = null; ExpressionNode operand = null; boolean needOperand = false, multipleOperands = false; //ConditionList innerConds = null; switch (subqueryNode.getSubqueryType()) { case EXISTS: case EXPRESSION: break; case NOT_EXISTS: negate = true; break; case IN: multipleOperands = true; /* falls through */ case EQ_ANY: needOperand = true; break; case EQ_ALL: negate = true; comp = Comparison.NE; needOperand = true; break; case NE_ANY: comp = Comparison.NE; needOperand = true; break; case NOT_IN: multipleOperands = true; /* falls through */ case NE_ALL: negate = true; needOperand = true; break; case GT_ANY: comp = Comparison.GT; needOperand = true; break; case GT_ALL: negate = true; comp = Comparison.LE; needOperand = true; break; case GE_ANY: comp = Comparison.GE; needOperand = true; break; case GE_ALL: negate = true; comp = Comparison.LT; needOperand = true; break; case LT_ANY: comp = Comparison.LT; needOperand = true; break; case LT_ALL: negate = true; comp = Comparison.GE; needOperand = true; break; case LE_ANY: comp = Comparison.LE; needOperand = true; break; case LE_ALL: negate = true; comp = Comparison.GT; needOperand = true; break; case FROM: default: assert false; } boolean distinct = false; // TODO: This may not be right for c IN (SELECT x ... UNION SELECT y ...). // Maybe turn that into an OR and optimize each separately. { PlanWithInput prev = null; PlanNode plan = subquery; while (true) { if (!(plan instanceof BasePlanWithInput)) break; PlanNode next = ((BasePlanWithInput)plan).getInput(); if (plan instanceof Project) { Project project = (Project)plan; if (needOperand) { operands = project.getFields(); if (!multipleOperands && (operands.size() != 1)) throw new UnsupportedSQLException("Subquery must have exactly one column", subqueryNode); operand = project.getFields().get(0); } // Don't need project any more. if (prev != null) prev.replaceInput(plan, next); else subquery = next; break; } if (plan instanceof Distinct) { distinct = true; } else { prev = (PlanWithInput)plan; } plan = next; } } if ((operands == null) && (subquery instanceof ColumnSource) && (subquery instanceof TypedPlan)) { int nfields = ((TypedPlan)subquery).nFields(); if (!multipleOperands && (nfields != 1)) throw new UnsupportedSQLException("Subquery must have exactly one column", subqueryNode); operands = new ArrayList<>(nfields); for (int i = 0; i < nfields; i++) { operands.add(new ColumnExpression(((ColumnSource)subquery), i, null, null, null)); } if (nfields > 0) operand = operands.get(0); } if ((operands == null) && (subquery instanceof ColumnSource) && (subquery instanceof TypedPlan)) { int nfields = ((TypedPlan)subquery).nFields(); if (!multipleOperands && (nfields != 1)) throw new UnsupportedSQLException("Subquery must have exactly one column", subqueryNode); operands = new ArrayList<>(nfields); for (int i = 0; i < nfields; i++) { operands.add(new ColumnExpression(((ColumnSource)subquery), i, null, null, null)); } if (nfields > 0) operand = operands.get(0); } ConditionExpression condition; if (needOperand) { assert (operand != null); ValueNode leftOperand = subqueryNode.getLeftOperand(); ConditionExpression inner = null; if (multipleOperands) { if (leftOperand instanceof RowConstructorNode) { ValueNodeList leftOperands = ((RowConstructorNode)leftOperand).getNodeList(); if (operands.size() != leftOperands.size()) throw new IllegalArgumentException("mismatched columns count in IN " + "left : " + leftOperands.size() + ", right: " + operands.size()); for (int i = 0; i < leftOperands.size(); i++) { ExpressionNode left = toExpression(leftOperands.get(i) , projects); TInstance type = typesTranslator.typeForSQLType(subqueryNode.getType()); ConditionExpression cond = new ComparisonCondition(comp, left, operands.get(i), subqueryNode.getType(), null, type); inner = andConditions(inner, cond); } } else { if (operands.size() != 1) throw new IllegalArgumentException("Subquery must have exactly one column"); multipleOperands = false; } } if (!multipleOperands) { ExpressionNode left = toExpression(leftOperand, projects); TInstance type = typesTranslator.typeForSQLType(subqueryNode.getType()); inner = new ComparisonCondition(comp, left, operand, subqueryNode.getType(), subqueryNode, type); } // We take this condition back off from the top of the // physical plan and move it to the expression, but it's // easier to think about the scoping as evaluated at the // end of the inner query. List<ExpressionNode> fields = new ArrayList<>(1); fields.add(inner); subquery = new Project(subquery, fields); if (distinct) // See InConditionReverser#convert(Select,AnyCondition). subquery = new Distinct(subquery); TInstance type = typesTranslator.typeForSQLType(subqueryNode.getType()); condition = new AnyCondition(new Subquery(subquery, peekEquivalenceFinder()), subqueryNode.getType(), subqueryNode, type); } else if (subqueryNode.getSubqueryType() == SubqueryNode.SubqueryType.EXPRESSION) { ExpressionNode expression = toExpression(subqueryNode); condition = new BooleanCastExpression(expression, subqueryNode.getType(), subqueryNode, expression.getType()); } else { TInstance type = typesTranslator.typeForSQLType(subqueryNode.getType()); condition = new ExistsCondition(new Subquery(subquery, peekEquivalenceFinder()), subqueryNode.getType(), subqueryNode, type); } if (negate) { condition = negateCondition(condition, subqueryNode); } conditions.add(condition); } protected void addFunctionCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, UnaryOperatorNode unary) throws StandardException { List<ExpressionNode> operands = new ArrayList<>(1); operands.add(toExpression(unary.getOperand(), projects)); TInstance type = typesTranslator.typeForSQLType(unary.getType()); conditions.add(new FunctionCondition(unary.getMethodName(), operands, unary.getType(), unary, type)); } protected void addFunctionCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, BinaryOperatorNode binary) throws StandardException { List<ExpressionNode> operands = new ArrayList<>(2); operands.add(toExpression(binary.getLeftOperand(), projects)); operands.add(toExpression(binary.getRightOperand(), projects)); TInstance type = typesTranslator.typeForSQLType(binary.getType()); conditions.add(new FunctionCondition(binary.getMethodName(), operands, binary.getType(), binary, type)); } protected void addFunctionCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, TernaryOperatorNode ternary) throws StandardException { List<ExpressionNode> operands = new ArrayList<>(3); operands.add(toExpression(ternary.getReceiver(), projects)); operands.add(toExpression(ternary.getLeftOperand(), projects)); ValueNode third = ternary.getRightOperand(); if (third != null) operands.add(toExpression(third, projects)); TInstance type = typesTranslator.typeForSQLType(ternary.getType()); conditions.add(new FunctionCondition(ternary.getMethodName(), operands, ternary.getType(), ternary, type)); } protected void addIsNullCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, IsNullNode isNull) throws StandardException { List<ExpressionNode> operands = new ArrayList<>(1); operands.add(toExpression(isNull.getOperand(), projects)); String function = isNull.getMethodName(); boolean negated = false; if ("isNotNull".equals(function)) { function = "isNull"; negated = true; } TInstance type = typesTranslator.typeForSQLType(isNull.getType()); ConditionExpression cond = new FunctionCondition(function, operands, isNull.getType(), isNull, type); if (negated) { cond = negateCondition(cond, isNull); } conditions.add(cond); } protected void addIsCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, IsNode is) throws StandardException { List<ExpressionNode> operands = new ArrayList<>(1); operands.add(toCondition(is.getLeftOperand(), projects)); String function; Boolean value = (Boolean)((ConstantNode)is.getRightOperand()).getValue(); if (value == null) function = "isUnknown"; else if (value.booleanValue()) function = "isTrue"; else function = "isFalse"; TInstance type = typesTranslator.typeForSQLType(is.getType()); ConditionExpression cond = new FunctionCondition(function, operands, is.getType(), is, type); if (is.isNegated()) { cond = negateCondition(cond, is); } conditions.add(cond); } protected void addLogicalFunctionCondition(List<ConditionExpression> conditions, List<ExpressionNode> projects, ValueNode condition) throws StandardException { String functionName; List<ConditionExpression> operands = null; if (condition instanceof UnaryLogicalOperatorNode) { switch (condition.getNodeType()) { case NodeTypes.NOT_NODE: functionName = "not"; break; default: throw new UnsupportedSQLException("Unsuported condition", condition); } operands = new ArrayList<>(1); operands.add(toCondition(((UnaryLogicalOperatorNode)condition).getOperand(), projects)); } else if (condition instanceof BinaryLogicalOperatorNode) { ValueNode leftOperand = ((BinaryLogicalOperatorNode) condition).getLeftOperand(); ValueNode rightOperand = ((BinaryLogicalOperatorNode) condition).getRightOperand(); switch (condition.getNodeType()) { case NodeTypes.AND_NODE: // Can just fold straight into conjunction. addCondition(conditions, leftOperand, projects); if (!rightOperand.isBooleanTrue()) addCondition(conditions, rightOperand, projects); return; case NodeTypes.OR_NODE: if (rightOperand.isBooleanFalse()) { addCondition(conditions, leftOperand, projects); return; } functionName = "or"; break; default: throw new UnsupportedSQLException("Unsuported condition", condition); } operands = new ArrayList<>(2); operands.add(toCondition(leftOperand, projects)); operands.add(toCondition(rightOperand, projects)); } else throw new UnsupportedSQLException("Unsuported condition", condition); TInstance type = typesTranslator.typeForSQLType(condition.getType()); conditions.add(new LogicalFunctionCondition(functionName, operands, condition.getType(), condition, type)); } /** Is this a boolean condition used as a normal value? */ protected boolean isConditionExpression(ValueNode value) throws StandardException { switch (value.getNodeType()) { case NodeTypes.BINARY_EQUALS_OPERATOR_NODE: case NodeTypes.BINARY_GREATER_THAN_OPERATOR_NODE: case NodeTypes.BINARY_GREATER_EQUALS_OPERATOR_NODE: case NodeTypes.BINARY_LESS_THAN_OPERATOR_NODE: case NodeTypes.BINARY_LESS_EQUALS_OPERATOR_NODE: case NodeTypes.BINARY_NOT_EQUALS_OPERATOR_NODE: case NodeTypes.BETWEEN_OPERATOR_NODE: case NodeTypes.IN_LIST_OPERATOR_NODE: case NodeTypes.LIKE_OPERATOR_NODE: case NodeTypes.IS_NULL_NODE: case NodeTypes.IS_NOT_NULL_NODE: case NodeTypes.IS_NODE: case NodeTypes.OR_NODE: case NodeTypes.AND_NODE: case NodeTypes.NOT_NODE: return true; case NodeTypes.SUBQUERY_NODE: return (((SubqueryNode)value).getSubqueryType() != SubqueryNode.SubqueryType.EXPRESSION); default: return false; } } /** Get given condition as a single node. */ protected ConditionExpression toCondition(ValueNode condition, List<ExpressionNode> projects) throws StandardException { List<ConditionExpression> conditions = new ArrayList<>(1); addCondition(conditions, condition, projects); switch (conditions.size()) { case 0: return new BooleanConstantExpression(Boolean.TRUE); case 1: return conditions.get(0); case 2: // CASE WHEN x BETWEEN a AND b means multiple conditions from single one in AST. TInstance type = typesTranslator.typeForSQLType(condition.getType()); return new LogicalFunctionCondition("and", conditions, condition.getType(), condition, type); default: { // Make calls to binary AND function. ConditionExpression rhs = null; for (ConditionExpression lhs : conditions) { rhs = andConditions(rhs, lhs); } return rhs; } } } /** Combine AND of conditions (or <code>null</code>) with another one. */ protected ConditionExpression andConditions(ConditionExpression conds, ConditionExpression cond) { if (conds == null) return cond; else { List<ConditionExpression> operands = new ArrayList<>(2); operands.add(conds); operands.add(cond); return new LogicalFunctionCondition("and", operands, cond.getSQLtype(), null, typesTranslator.typeForSQLType(cond.getSQLtype())); } } /** Negate boolean condition. */ protected ConditionExpression negateCondition(ConditionExpression cond, ValueNode sql) { List<ConditionExpression> operands = new ArrayList<>(1); operands.add(cond); TInstance type = typesTranslator.typeForSQLType(sql.getType()); return new LogicalFunctionCondition("not", operands, sql.getType(), sql, type); } /** SELECT DISTINCT with sorting sorts by an input Project and * adds extra columns so as to only sort once for both * Distinct and the requested ordering. * Returns <code>false</code> if this is not possible and * DISTINCT should be turned into GROUP BY. */ protected boolean adjustSortsForDistinct(List<OrderByExpression> sorts, Project project) throws StandardException { List<ExpressionNode> exprs = project.getFields(); BitSet used = new BitSet(exprs.size()); int nSorts = sorts.size(); ExpressionNode[] adjustedOrderBys = new ExpressionNode[nSorts]; for (int i = 0; i < nSorts; i++) { OrderByExpression orderBy = sorts.get(i); ExpressionNode expr = orderBy.getExpression(); int idx = exprs.indexOf(expr); if (idx < 0) { if (isDistinctSortNotSelectGroupBy()) return false; throw new UnsupportedSQLException("SELECT DISTINCT requires that ORDER BY expressions be in the select list", expr.getSQLsource()); } adjustedOrderBys[i] = new ColumnExpression(project, idx, expr.getSQLtype(), expr.getSQLsource(), expr.getType()); used.set(idx); } // If we got here, it means each orderBy's expression is in the exprs list. As such, nSorts <= exprs.size for (int i = 0; i < exprs.size(); i++) { if (i < nSorts) sorts.get(i).setExpression(adjustedOrderBys[i]); if (!used.get(i)) { ExpressionNode expr = exprs.get(i); ExpressionNode cexpr = new ColumnExpression(project, i, expr.getSQLtype(), expr.getSQLsource(), expr.getType()); OrderByExpression orderBy = new OrderByExpression(cexpr, sorts.get(0).isAscending()); sorts.add(orderBy); } } return true; } private Boolean distinctSortNotSelectGroupBySetting = null; protected boolean isDistinctSortNotSelectGroupBy() { if (distinctSortNotSelectGroupBySetting == null) distinctSortNotSelectGroupBySetting = Boolean.valueOf(rulesContext.getProperty("distinctSortNotSelectGroupBy", "false")); return distinctSortNotSelectGroupBySetting; } /** LIMIT / OFFSET */ protected Limit toLimit(PlanNode input, ValueNode offsetClause, ValueNode limitClause) throws StandardException { int offset = 0, limit = -1; boolean offsetIsParameter = false, limitIsParameter = false; if (offsetClause != null) { if (offsetClause instanceof ParameterNode) { offset = limitParameter((ParameterNode)offsetClause); offsetIsParameter = true; } else { offset = getIntegerConstant(offsetClause, "OFFSET must be constant integer"); if (offset < 0) throw new UnsupportedSQLException("OFFSET must not be negative", offsetClause); } } if (limitClause != null) { if (limitClause instanceof ParameterNode) { limit = limitParameter((ParameterNode)limitClause); limitIsParameter = true; } else { limit = getIntegerConstant(limitClause, "LIMIT must be constant integer"); if (limit < 0) throw new UnsupportedSQLException("LIMIT must not be negative", limitClause); } } return new Limit(input, offset, offsetIsParameter, limit, limitIsParameter); } protected int limitParameter(ParameterNode param) throws StandardException { assert (parameters != null) && parameters.contains(param) : param; TInstance type; DataTypeDescriptor sqlType = param.getType(); if (sqlType == null) { type = typesTranslator.typeClassForJDBCType(Types.INTEGER).instance(true); sqlType = type.dataTypeDescriptor(); param.setType(sqlType); } else { type = typesTranslator.typeForSQLType(sqlType); } param.setUserData(type); return param.getParameterNumber(); } protected TableNode getTargetTable(DMLModStatementNode statement) throws StandardException { TableName tableName = statement.getTargetTableName(); Table table = (Table)tableName.getUserData(); if (table == null) throw new NoSuchTableException(tableName.getSchemaName(), tableName.getTableName()); if (table.isAISTable()) { throw new ProtectedTableDDLException (table.getName()); } return getTableNode(table); } protected TableSource getTargetTableSource(DMLModStatementNode statement) throws StandardException { FromTable firstTable = ((SelectNode)statement.getResultSetNode()).getFromList().get(0); return (TableSource)joinNodes.get(firstTable); } protected Map<Group,TableTree> groups = new HashMap<>(); protected Deque<EquivalenceFinder<ColumnExpression>> columnEquivalences = new ArrayDeque<>(1); protected TableNode getTableNode(Table table) throws StandardException { Group group = table.getGroup(); TableTree tables = groups.get(group); if (tables == null) { tables = new TableTree(); groups.put(group, tables); } return tables.addNode(table); } protected TableNode getColumnTableNode(Column column) throws StandardException { return getTableNode(column.getTable()); } /** Translate expression to intermediate form. */ protected ExpressionNode toExpression(ValueNode valueNode) throws StandardException { return toExpression(valueNode, null); } protected ExpressionNode toExpression(ValueNode valueNode, List<ExpressionNode> projects) throws StandardException { if (valueNode == null) { return ConstantExpression.typedNull(null, null, null); } DataTypeDescriptor sqlType = valueNode.getType(); TInstance type = typesTranslator.typeForSQLType(sqlType); if (valueNode instanceof ColumnReference) { ColumnBinding cb = (ColumnBinding)((ColumnReference)valueNode).getUserData(); if (cb == null) throw new UnsupportedSQLException("Unsupported column", valueNode); Joinable joinNode = joinNodes.get(cb.getFromTable()); if ((joinNode == null) && (cb.getFromTable() == null) && (projects != null) && (cb.getResultColumn() != null)) { // Alias: use result column expression. return projects.get(cb.getResultColumn().getColumnPosition()-1); } if (!(joinNode instanceof ColumnSource)) throw new UnsupportedSQLException("Unsupported column", valueNode); Column column = cb.getColumn(); if (column != null) return new ColumnExpression(((TableSource)joinNode), column, sqlType, valueNode); else return new ColumnExpression(((ColumnSource)joinNode), cb.getFromTable().getResultColumns().indexOf(cb.getResultColumn()), sqlType, valueNode, type); } else if (valueNode instanceof ConstantNode) { if (valueNode instanceof BooleanConstantNode) return new BooleanConstantExpression((Boolean)((ConstantNode)valueNode).getValue(), sqlType, valueNode, type); else if (valueNode instanceof UntypedNullConstantNode) { return ConstantExpression.typedNull(sqlType, valueNode, type); } else { Object value = ((ConstantNode)valueNode).getValue(); if (value instanceof Integer) { int ival = ((Integer)value).intValue(); if ((ival >= Byte.MIN_VALUE) && (ival <= Byte.MAX_VALUE)) value = new Byte((byte)ival); else if ((ival >= Short.MIN_VALUE) && (ival <= Short.MAX_VALUE)) value = new Short((short)ival); ExpressionNode constInt = new ConstantExpression(value, sqlType, valueNode, type); return constInt; } if ((value instanceof String) && ((sqlType != null) && (sqlType.getTypeId() == TypeId.CHAR_ID))) { // TODO: Make a char literal into a VARCHAR instead of a CHAR. // It shouldn't matter, but some of the overloads aren't quite // right. type = typesTranslator.typeForString((String) value); } return new ConstantExpression(value, sqlType, valueNode, type); } } else if (valueNode instanceof ParameterNode) { assert (parameters != null) && parameters.contains(valueNode) : valueNode; return new ParameterExpression(((ParameterNode)valueNode) .getParameterNumber(), sqlType, valueNode, type); } else if (valueNode instanceof CastNode) return new CastExpression(toExpression(((CastNode)valueNode) .getCastOperand(), projects), sqlType, valueNode, type); else if (valueNode instanceof AggregateNode) { AggregateNode aggregateNode = (AggregateNode)valueNode; String function = aggregateNode.getAggregateName(); ExpressionNode operand = null; if ("COUNT(*)".equals(function)) { function = "COUNT"; } else { operand = toExpression(aggregateNode.getOperand(), projects); } if (aggregateNode instanceof GroupConcatNode) { GroupConcatNode groupConcat = (GroupConcatNode) aggregateNode; List<OrderByExpression> sorts = null; OrderByList orderByList = groupConcat.getOrderBy(); if (orderByList != null) { sorts = new ArrayList<>(); for (OrderByColumn orderByColumn : orderByList) { ExpressionNode expression = toOrderGroupBy(orderByColumn.getExpression(), projects, "ORDER"); sorts.add(new OrderByExpression(expression, orderByColumn.isAscending())); } } return new AggregateFunctionExpression(function, operand, aggregateNode.isDistinct(), sqlType, valueNode, type, groupConcat.getSeparator(), sorts); } else return new AggregateFunctionExpression(function, operand, aggregateNode.isDistinct(), sqlType, valueNode, type, null, null); } else if (isConditionExpression(valueNode)) { return toCondition(valueNode, projects); } else if (valueNode instanceof UnaryOperatorNode) { if (valueNode instanceof WindowFunctionNode) { throw new UnsupportedSQLException("Window", valueNode); } UnaryOperatorNode unary = (UnaryOperatorNode)valueNode; List<ExpressionNode> operands = new ArrayList<>(1); operands.add(toExpression(unary.getOperand(), projects)); return new FunctionExpression(unary.getMethodName(), operands, sqlType, unary, type); } else if (valueNode instanceof BinaryOperatorNode) { BinaryOperatorNode binary = (BinaryOperatorNode)valueNode; List<ExpressionNode> operands = new ArrayList<>(2); int nodeType = valueNode.getNodeType(); switch (nodeType) { case NodeTypes.CONCATENATION_OPERATOR_NODE: // Operator is binary but function is nary: collapse. while (true) { operands.add(toExpression(binary.getLeftOperand(), projects)); ValueNode right = binary.getRightOperand(); if (right.getNodeType() != nodeType) { operands.add(toExpression(right, projects)); break; } binary = (BinaryOperatorNode)right; } break; default: operands.add(toExpression(binary.getLeftOperand(), projects)); operands.add(toExpression(binary.getRightOperand(), projects)); } return new FunctionExpression(binary.getMethodName(), operands, sqlType, binary, type); } else if (valueNode instanceof TernaryOperatorNode) { TernaryOperatorNode ternary = (TernaryOperatorNode)valueNode; List<ExpressionNode> operands = new ArrayList<>(3); operands.add(toExpression(ternary.getReceiver(), projects)); operands.add(toExpression(ternary.getLeftOperand(), projects)); // java null means not present ValueNode third = ternary.getRightOperand(); if (third != null) operands.add(toExpression(third, projects)); return new FunctionExpression(ternary.getMethodName(), operands, sqlType, ternary, type); } else if (valueNode instanceof CoalesceFunctionNode) { CoalesceFunctionNode coalesce = (CoalesceFunctionNode)valueNode; List<ExpressionNode> operands = new ArrayList<>(); for (ValueNode value : coalesce.getArgumentsList()) { operands.add(toExpression(value, projects)); } return new FunctionExpression(coalesce.getFunctionName(), operands, sqlType, coalesce, type); } else if (valueNode instanceof SubqueryNode) { SubqueryNode subqueryNode = (SubqueryNode)valueNode; pushEquivalenceFinder(); PlanNode subquerySelect = toQueryForSelect(subqueryNode.getResultSet(), subqueryNode.getOrderByList(), subqueryNode.getOffset(), subqueryNode.getFetchFirst(), false); Subquery subquery = new Subquery(subquerySelect, peekEquivalenceFinder()); popEquivalenceFinder(); if ((sqlType != null) && sqlType.getTypeId().isRowMultiSet()) return new SubqueryResultSetExpression(subquery, sqlType, subqueryNode, type); else return new SubqueryValueExpression(subquery, sqlType, subqueryNode, type); } else if (valueNode instanceof JavaToSQLValueNode) { return toExpression(((JavaToSQLValueNode)valueNode).getJavaValueNode(), valueNode, false, projects); } else if (valueNode instanceof CurrentDatetimeOperatorNode) { String functionName = FunctionsTypeComputer.currentDatetimeFunctionName((CurrentDatetimeOperatorNode)valueNode); if (functionName == null) throw new UnsupportedSQLException("Unsupported datetime function", valueNode); return new FunctionExpression(functionName, Collections.<ExpressionNode>emptyList(), sqlType, valueNode, type); } else if (valueNode instanceof SpecialFunctionNode) { String functionName = FunctionsTypeComputer.specialFunctionName((SpecialFunctionNode)valueNode); if (functionName == null) throw new UnsupportedSQLException("Unsupported special function", valueNode); return new FunctionExpression(functionName, Collections.<ExpressionNode>emptyList(), sqlType, valueNode, type); } else if (valueNode instanceof ConditionalNode) { ConditionalNode cond = (ConditionalNode)valueNode; return new FunctionExpression("if", Arrays.asList(toExpression(cond.getTestCondition(), projects), toExpression(cond.getThenNode(), projects), toExpression(cond.getElseNode(), projects)), sqlType, valueNode, type); } else if (valueNode instanceof SimpleCaseNode) { SimpleCaseNode caseNode = (SimpleCaseNode)valueNode; ExpressionNode operand = toExpression(caseNode.getOperand(), projects); int ncases = caseNode.getNumberOfCases(); ExpressionNode expr; if (caseNode.getElseValue() != null) expr = toExpression(caseNode.getElseValue(), projects); else expr = ConstantExpression.typedNull(sqlType, valueNode, type); for (int i = ncases - 1; i >= 0; i--) { ComparisonCondition cond = new ComparisonCondition(Comparison.EQ, operand, toExpression(caseNode.getCaseOperand(i), projects), sqlType, caseNode, type); expr = new FunctionExpression("if", Arrays.asList(cond, toExpression(caseNode.getResultValue(i), projects), expr), sqlType, caseNode, type); } return expr; } else if (valueNode instanceof NextSequenceNode) { NextSequenceNode seqNode = (NextSequenceNode)valueNode; List<ExpressionNode> params = new ArrayList<>(2); String schema = seqNode.getSequenceName().hasSchema() ? seqNode.getSequenceName().getSchemaName() : rulesContext.getDefaultSchemaName(); // Extract the (potential) schema name as the first parameter TInstance schemaType = typesTranslator.typeForString(schema); params.add(new ConstantExpression( new TPreptimeValue(new Value(schemaType, schema)))); // Extract the schema name as the second parameter String sequence = seqNode.getSequenceName().getTableName(); TInstance sequenceType = typesTranslator.typeForString(sequence); params.add(new ConstantExpression( new TPreptimeValue(new Value(sequenceType, sequence)))); return new FunctionExpression ("nextval", params, sqlType, valueNode, type); } else if (valueNode instanceof CurrentSequenceNode) { CurrentSequenceNode seqNode = (CurrentSequenceNode)valueNode; List<ExpressionNode> params = new ArrayList<>(2); String schema = seqNode.getSequenceName().hasSchema() ? seqNode.getSequenceName().getSchemaName() : rulesContext.getDefaultSchemaName(); // Extract the (potential) schema name as the first parameter TInstance schemaType = typesTranslator.typeForString(schema); params.add(new ConstantExpression( new TPreptimeValue(new Value(schemaType, schema)))); // Extract the schema name as the second parameter String sequence = seqNode.getSequenceName().getTableName(); TInstance sequenceType = typesTranslator.typeForString(sequence); params.add(new ConstantExpression( new TPreptimeValue(new Value(sequenceType, sequence)))); return new FunctionExpression ("currval", params, sqlType, valueNode, type); } else if (valueNode instanceof DefaultNode) { Column column = (Column)valueNode.getUserData(); if (column == null) throw new DefaultOutsideInsertException(valueNode); return new ColumnDefaultExpression(column, sqlType, valueNode, type); } else throw new UnsupportedSQLException("Unsupported operand", valueNode); } // TODO: Need to figure out return type. Maybe better to have // done this earlier and bound to a known function and elided the // Java stuff then. protected ExpressionNode toExpression(JavaValueNode javaToSQL, ValueNode valueNode, boolean asCondition, List<ExpressionNode> projects) throws StandardException { if (javaToSQL instanceof MethodCallNode) { MethodCallNode methodCall = (MethodCallNode)javaToSQL; List<ExpressionNode> operands = new ArrayList<>(); if (methodCall.getMethodParameters() != null) { for (JavaValueNode javaValue : methodCall.getMethodParameters()) { operands.add(toExpression(javaValue, null, false, projects)); } } DataTypeDescriptor sqlType = valueNode.getType(); TInstance type = typesTranslator.typeForSQLType(sqlType); Routine routine = (Routine)methodCall.getUserData(); if (routine != null) { if (asCondition) return new RoutineCondition(routine, operands, sqlType, valueNode, type); else return new RoutineExpression(routine, operands, sqlType, valueNode, type); } if (asCondition) return new FunctionCondition(methodCall.getMethodName(), operands, sqlType, valueNode, type); else if (AggregateFunctionExpression.class.getName().equals(methodCall.getJavaClassName())) { if (operands.size() != 1) throw new WrongExpressionArityException(2, operands.size()); return new AggregateFunctionExpression(methodCall.getMethodName(), operands.get(0), false, sqlType, valueNode, type, null, // *supposed* separator null); // order by list } else return new FunctionExpression(methodCall.getMethodName(), operands, sqlType, valueNode, type); } else if (javaToSQL instanceof SQLToJavaValueNode) { return toExpression(((SQLToJavaValueNode)javaToSQL).getSQLValueNode(), projects); } else throw new UnsupportedSQLException("Unsupported operand", valueNode); } /** Get the column that this node references or else return null * or throw given error. */ protected Column getColumnReferenceColumn(ValueNode value, String errmsg) throws StandardException { if (value instanceof ColumnReference) { ColumnReference cref = (ColumnReference)value; ColumnBinding cb = (ColumnBinding)cref.getUserData(); if (cb != null) { Column column = cb.getColumn(); if (column != null) return column; } } if (errmsg == null) return null; throw new UnsupportedSQLException(errmsg, value); } /** Get the constant integer value that this node represents or else throw error. */ protected int getIntegerConstant(ValueNode value, String errmsg) { if (value instanceof NumericConstantNode) { Object number = ((NumericConstantNode)value).getValue(); if (number instanceof Integer) return ((Integer)number).intValue(); } throw new UnsupportedSQLException(errmsg, value); } /** Value for ORDER / GROUP BY. * Can be:<ul> * <li>ordinary expression</li> * <li>ordinal index into the projects</li> * <li>alias of one of the projects</li></ul> */ protected ExpressionNode toOrderGroupBy(ValueNode valueNode, List<ExpressionNode> projects, String which) throws StandardException { ExpressionNode expression = toExpression(valueNode, projects); if (expression.isConstant()) { Object value = ((ConstantExpression)expression).getValue(); if ((value instanceof Long) || (value instanceof Integer) || (value instanceof Short) || (value instanceof Byte)) { int i = ((Number)value).intValue(); if ((i <= 0) || (i > projects.size())) throw new OrderGroupByIntegerOutOfRange(which, i, projects.size()); expression = (ExpressionNode)projects.get(i-1); } else throw new OrderGroupByNonIntegerConstant(which, expression.getSQLsource()); } return expression; } /** Construct an aggregating node. * This only sets the skeleton with the group by fields. Later, * aggregate functions from the result columns, HAVING & ORDER BY * clauses will be added there and the result column adjusted to * reflect this. */ protected AggregateSource toAggregateSource(PlanNode input, GroupByList groupByList, List<ExpressionNode> projects) throws StandardException { List<ExpressionNode> groupBy = new ArrayList<>(); if (groupByList != null) { if (groupByList.isRollup()) { throw new UnsupportedGroupByRollupException(); } for (GroupByColumn groupByColumn : groupByList) { groupBy.add(toOrderGroupBy(groupByColumn.getColumnExpression(), projects, "GROUP")); } } return new AggregateSource(input, groupBy); } /** Does any element include an aggregate function? */ protected boolean hasAggregateFunction(Collection<? extends ExpressionNode> c) { for (ExpressionNode expr : c) { if (hasAggregateFunction(expr)) return true; } return false; } /** Does any element include an aggregate function? */ protected boolean hasAggregateFunctionA(Collection<? extends AnnotatedExpression> c) { for (AnnotatedExpression aexpr : c) { if (hasAggregateFunction(aexpr.getExpression())) return true; } return false; } /** Does this expression include any aggregates? */ protected boolean hasAggregateFunction(ExpressionNode expr) { return HasAggregateFunction.of(expr); } public int getInToOrMaxCount() { String prop = rulesContext.getProperty("inToOrMaxCount"); if (prop != null) return Integer.valueOf(prop); else return IN_TO_OR_MAX_COUNT_DEFAULT; } } public static class HasAggregateFunction implements ExpressionVisitor { private boolean found = false; @Override public boolean visitEnter(ExpressionNode n) { return visit(n); } @Override public boolean visitLeave(ExpressionNode n) { return !found; } @Override public boolean visit(ExpressionNode n) { if (n instanceof AggregateFunctionExpression) { found = true; return false; } return true; } public static boolean of(ExpressionNode expr) { HasAggregateFunction haf = new HasAggregateFunction(); expr.accept(haf); return haf.found; } } }