/** * 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; import com.foundationdb.server.error.AmbiguousColumNameException; import com.foundationdb.server.error.CorrelationNameAlreadyUsedException; import com.foundationdb.server.error.JoinNodeAdditionException; import com.foundationdb.server.error.NoSuchColumnException; import com.foundationdb.server.error.NoSuchFunctionException; import com.foundationdb.server.error.NoSuchTableException; import com.foundationdb.server.error.NoTableSpecifiedInQueryException; import com.foundationdb.server.error.ProcedureCalledAsFunctionException; import com.foundationdb.server.error.SQLParserInternalException; import com.foundationdb.server.error.SelectExistsErrorException; import com.foundationdb.server.error.SubqueryOneColumnException; import com.foundationdb.server.error.TableIsBadSubqueryException; import com.foundationdb.server.error.UnsupportedFullOuterJoinException; import com.foundationdb.server.error.ViewHasBadSubqueryException; import com.foundationdb.server.error.WholeGroupQueryException; import com.foundationdb.sql.StandardException; import com.foundationdb.sql.parser.*; import com.foundationdb.sql.views.ViewDefinition; import com.foundationdb.ais.model.AkibanInformationSchema; import com.foundationdb.ais.model.Column; import com.foundationdb.ais.model.Columnar; import com.foundationdb.ais.model.Join; import com.foundationdb.ais.model.JoinColumn; import com.foundationdb.ais.model.Routine; import com.foundationdb.ais.model.Table; import com.foundationdb.ais.model.View; import java.util.*; public class AISBinder implements Visitor { private AkibanInformationSchema ais; private String defaultSchemaName; private Deque<BindingContext> bindingContexts; private Set<QueryTreeNode> visited; private boolean resultColumnsAvailableBroadly; private boolean allowSubqueryMultipleColumns; private Set<ValueNode> havingClauses; private AISBinderContext context; private boolean expandViews; private FunctionDefined functionDefined; public AISBinder(AkibanInformationSchema ais, String defaultSchemaName) { this.ais = ais; this.defaultSchemaName = defaultSchemaName; } public String getDefaultSchemaName() { return defaultSchemaName; } public void setDefaultSchemaName(String defaultSchemaName) { this.defaultSchemaName = defaultSchemaName; } public boolean isResultColumnsAvailableBroadly() { return resultColumnsAvailableBroadly; } public void setResultColumnsAvailableBroadly(boolean resultColumnsAvailableBroadly) { this.resultColumnsAvailableBroadly = resultColumnsAvailableBroadly; } public boolean isAllowSubqueryMultipleColumns() { return allowSubqueryMultipleColumns; } public void setAllowSubqueryMultipleColumns(boolean allowSubqueryMultipleColumns) { this.allowSubqueryMultipleColumns = allowSubqueryMultipleColumns; } public interface FunctionDefined { public boolean isDefined(String name); } public void setFunctionDefined(FunctionDefined functionDefined) { this.functionDefined = functionDefined; } public AISBinderContext getContext() { return context; } protected void setContext(AISBinderContext context) { this.context = context; } public void bind(StatementNode stmt) throws StandardException { bind(stmt, true); } public void bind(QueryTreeNode node, boolean expandViews) throws StandardException { this.expandViews = expandViews; visited = new HashSet<>(); bindingContexts = new ArrayDeque<>(); havingClauses = new HashSet<>(); try { node.accept(this); } finally { visited = null; bindingContexts = null; } } /* Hierarchical Visitor */ public boolean visitBefore(QueryTreeNode node) { boolean first = visited.add(node); if (first) { switch (node.getNodeType()) { case NodeTypes.SUBQUERY_NODE: subqueryNode((SubqueryNode)node); break; case NodeTypes.SELECT_NODE: selectNode((SelectNode)node); break; case NodeTypes.COLUMN_REFERENCE: columnReference((ColumnReference)node); break; case NodeTypes.INSERT_NODE: case NodeTypes.UPDATE_NODE: case NodeTypes.DELETE_NODE: dmlModStatementNode((DMLModStatementNode)node); break; case NodeTypes.UNION_NODE: unionNode((UnionNode)node); break; case NodeTypes.INTERSECT_OR_EXCEPT_NODE: intersectOrExceptNode((IntersectOrExceptNode)node); break; case NodeTypes.JAVA_TO_SQL_VALUE_NODE: javaValueNode(((JavaToSQLValueNode)node).getJavaValueNode()); } //TODO Add default case for nodes that are not implemented in either switch so easier to find bug } switch (node.getNodeType()) { case NodeTypes.CURSOR_NODE: case NodeTypes.DELETE_NODE: case NodeTypes.INSERT_NODE: case NodeTypes.UPDATE_NODE: pushBindingContext(((DMLStatementNode)node).getResultSetNode()); break; case NodeTypes.FROM_SUBQUERY: pushBindingContext(((FromSubquery)node).getSubquery()); break; case NodeTypes.SUBQUERY_NODE: pushBindingContext(((SubqueryNode)node).getResultSet()); break; case NodeTypes.ORDER_BY_LIST: case NodeTypes.GROUP_BY_LIST: getBindingContext().resultColumnsAvailableContext = node; break; default: if (havingClauses.contains(node)) // No special node type. getBindingContext().resultColumnsAvailableContext = node; break; } return first; } public void visitAfter(QueryTreeNode node) { switch (node.getNodeType()) { case NodeTypes.CURSOR_NODE: case NodeTypes.FROM_SUBQUERY: case NodeTypes.SUBQUERY_NODE: case NodeTypes.DELETE_NODE: case NodeTypes.INSERT_NODE: case NodeTypes.UPDATE_NODE: popBindingContext(); break; case NodeTypes.ORDER_BY_LIST: case NodeTypes.GROUP_BY_LIST: getBindingContext().resultColumnsAvailableContext = null; break; default: if (havingClauses.contains(node)) getBindingContext().resultColumnsAvailableContext = null; break; } } /* Specific node types */ protected void subqueryNode(SubqueryNode subqueryNode) { // The LHS of a subquery operator is bound in the outer context. if (subqueryNode.getLeftOperand() != null) { try { subqueryNode.getLeftOperand().accept(this); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } } ResultSetNode resultSet = subqueryNode.getResultSet(); ResultColumnList resultColumns = resultSet.getResultColumns(); // The parser does not enforce the fact that a subquery can only // return a single column, so we must check here. if (resultColumns.size() != 1) { switch (subqueryNode.getSubqueryType()) { case IN: case NOT_IN: case EXISTS: case NOT_EXISTS: break; case EXPRESSION: if (allowSubqueryMultipleColumns) break; /* else falls through */ default: throw new SubqueryOneColumnException(); } } SubqueryNode.SubqueryType subqueryType = subqueryNode.getSubqueryType(); /* Verify the usage of "*" in the select list: * o Only valid in EXISTS subqueries * o If the AllResultColumn is qualified, then we have to verify * that the qualification is a valid exposed name. * NOTE: The exposed name can come from an outer query block. */ verifySelectStarSubquery(resultSet, subqueryType); /* For an EXISTS subquery: * o If the SELECT list is a "*", then we convert it to a true. * (We need to do the conversion since we don't want the "*" to * get expanded.) */ if (subqueryType == SubqueryNode.SubqueryType.EXISTS) { try { resultSet = setResultToBooleanTrueNode(resultSet); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } subqueryNode.setResultSet(resultSet); } } protected void verifySelectStarSubquery(ResultSetNode resultSet, SubqueryNode.SubqueryType subqueryType) { if (resultSet instanceof SetOperatorNode) { SetOperatorNode setOperatorNode = (SetOperatorNode)resultSet; verifySelectStarSubquery(setOperatorNode.getLeftResultSet(), subqueryType); verifySelectStarSubquery(setOperatorNode.getRightResultSet(), subqueryType); return; } if (!(resultSet.getResultColumns().get(0) instanceof AllResultColumn)) { return; } // Select * currently only valid for EXISTS/NOT EXISTS. if ((subqueryType != SubqueryNode.SubqueryType.EXISTS) && (!allowSubqueryMultipleColumns || (subqueryType != SubqueryNode.SubqueryType.EXPRESSION))) { throw new SelectExistsErrorException (); } } /** * Set the result column for the subquery to a boolean true, * Useful for transformations such as * changing: * where exists (select ... from ...) * to: * where (select true from ...) * * NOTE: No transformation is performed if the ResultColumn.expression is * already the correct boolean constant. * * This method is used during binding of EXISTS predicates to map * a subquery's result column list into a single TRUE node. For * SELECT and VALUES subqueries this transformation is pretty * straightforward. But for set operators (ex. INTERSECT) we have * to do some extra work. To see why, assume we have the following * query: * * select * from ( values 'BAD' ) as T * where exists ((values 1) intersect (values 2)) * * If we treated the INTERSECT in this query the same way that we * treat SELECT/VALUES subqueries then the above query would get * transformed into: * * select * from ( values 'BAD' ) as T * where exists ((values TRUE) intersect (values TRUE)) * * Since both children of the INTERSECT would then have the same value, * the result of set operation would be a single value (TRUE), which * means the WHERE clause would evaluate to TRUE and thus the query * would return one row with value 'BAD'. That would be wrong. * * To avoid this problem, we internally wrap this SetOperatorNode * inside a "SELECT *" subquery and then we change the new SelectNode's * result column list (as opposed to *this* nodes' result column list) * to a singe boolean true node: * * select * from ( values 'BAD' ) as T where exists * SELECT TRUE FROM ((values 1) intersect (values 2)) * * In this case the left and right children of the INTERSECT retain * their values, which ensures that the result of the intersect * operation will be correct. Since (1 intersect 2) is an empty * result set, the internally generated SELECT node will return * zero rows, which in turn means the WHERE predicate will return * NULL (an empty result set from a SubqueryNode is treated as NULL * at execution time; see impl/sql/execute/AnyResultSet). Since * NULL is not the same as TRUE the query will correctly return * zero rows. DERBY-2370. * * @exception StandardException Thrown on error */ public ResultSetNode setResultToBooleanTrueNode(ResultSetNode resultSet) throws StandardException { NodeFactory nodeFactory = resultSet.getNodeFactory(); SQLParserContext parserContext = resultSet.getParserContext(); if (resultSet instanceof SetOperatorNode) { // First create a FromList to hold this node (and only this node). FromList fromList = (FromList)nodeFactory.getNode(NodeTypes.FROM_LIST, parserContext); fromList.addFromTable((SetOperatorNode)resultSet); // Now create a ResultColumnList that simply holds the "*". ResultColumnList rcl = (ResultColumnList) nodeFactory.getNode(NodeTypes.RESULT_COLUMN_LIST, parserContext); ResultColumn allResultColumn = (ResultColumn) nodeFactory.getNode(NodeTypes.ALL_RESULT_COLUMN, null, parserContext); rcl.addResultColumn(allResultColumn); /* Create a new SELECT node of the form: * SELECT * FROM <thisSetOperatorNode> */ resultSet = (ResultSetNode) nodeFactory.getNode(NodeTypes.SELECT_NODE, rcl, // ResultColumns null, // AGGREGATE list fromList, // FROM list null, // WHERE clause null, // GROUP BY list null, // having clause null, /* window list */ parserContext); /* And finally, transform the "*" in the new SELECT node * into a TRUE constant node. This ultimately gives us: * * SELECT TRUE FROM <thisSetOperatorNode> * * which has a single result column that is a boolean TRUE * constant. So we're done. */ } ResultColumnList resultColumns = resultSet.getResultColumns(); ResultColumn resultColumn = resultColumns.get(0); if (resultColumns.get(0) instanceof AllResultColumn) { resultColumn = (ResultColumn)nodeFactory.getNode(NodeTypes.RESULT_COLUMN, "", null, parserContext); } else if (resultColumn.getExpression().isBooleanTrue()) { // Nothing to do if query is already select TRUE ... return resultSet; } BooleanConstantNode booleanNode = (BooleanConstantNode) nodeFactory.getNode(NodeTypes.BOOLEAN_CONSTANT_NODE, Boolean.TRUE, parserContext); resultColumn.setExpression(booleanNode); resultColumn.setType(booleanNode.getType()); resultColumns.set(0, resultColumn); return resultSet; } protected void selectNode(SelectNode selectNode) { FromList fromList = selectNode.getFromList(); int size = fromList.size(); for (int i = 0; i < size; i++) { FromTable fromTable = fromList.get(i); FromTable newFromTable = fromTable(fromTable, false); if (newFromTable != fromTable) fromList.set(i, newFromTable); } for (int i = 0; i < size; i++) { addFromTable(fromList.get(i)); } expandAllsAndNameColumns(selectNode.getResultColumns(), fromList); if (selectNode.getHavingClause() != null) havingClauses.add(selectNode.getHavingClause()); } // Process a FROM list table, finding the table binding. protected FromTable fromTable(FromTable fromTable, boolean nullable) { switch (fromTable.getNodeType()) { case NodeTypes.FROM_BASE_TABLE: return fromBaseTable((FromBaseTable)fromTable, nullable); case NodeTypes.JOIN_NODE: return joinNode((JoinNode)fromTable, nullable); case NodeTypes.HALF_OUTER_JOIN_NODE: return joinNode((HalfOuterJoinNode)fromTable, nullable); case NodeTypes.FULL_OUTER_JOIN_NODE: throw new UnsupportedFullOuterJoinException(); case NodeTypes.FROM_SUBQUERY: return fromSubquery((FromSubquery)fromTable); default: // Subqueries in SELECT don't see earlier FROM list tables. try { return (FromTable)fromTable.accept(this); } catch (StandardException e) { throw new TableIsBadSubqueryException (fromTable.getOrigTableName().getSchemaName(), fromTable.getOrigTableName().getTableName(), e.getMessage()); } } } protected FromTable fromBaseTable(FromBaseTable fromBaseTable, boolean nullable) { TableName origName = fromBaseTable.getOrigTableName(); String schemaName = origName.getSchemaName(); if (schemaName == null) schemaName = defaultSchemaName; String tableName = origName.getTableName(); Columnar table = null; View view = ais.getView(schemaName, tableName); if (view != null) { if (!context.isAccessible(view.getName())) { view = null; } else if (expandViews) { ViewDefinition viewdef = context.getViewDefinition(view); FromSubquery viewSubquery; try { viewSubquery = viewdef.copySubquery(fromBaseTable.getParserContext()); } catch (StandardException ex) { throw new ViewHasBadSubqueryException(origName.toString(), ex.getMessage()); } if (fromBaseTable.getCorrelationName() != null) tableName = fromBaseTable.getCorrelationName(); viewSubquery.setCorrelationName(tableName); return fromTable(viewSubquery, false); } else { table = view; // Shallow reference within another view definition. } } if (table == null) table = lookupTableName(origName, schemaName, tableName); origName.setUserData(table); fromBaseTable.setUserData(new TableBinding(table, nullable)); return fromBaseTable; } protected FromTable joinNode(JoinNode joinNode, boolean nullable) { joinNode.setLeftResultSet(fromTable((FromTable)joinNode.getLeftResultSet(), nullable)); joinNode.setRightResultSet(fromTable((FromTable)joinNode.getRightResultSet(), nullable)); return joinNode; } protected FromTable joinNode(HalfOuterJoinNode joinNode, boolean nullable) { joinNode.setLeftResultSet(fromTable((FromTable)joinNode.getLeftResultSet(), nullable || joinNode.isRightOuterJoin())); joinNode.setRightResultSet(fromTable((FromTable)joinNode.getRightResultSet(), nullable || !joinNode.isRightOuterJoin())); return joinNode; } protected FromSubquery fromSubquery(FromSubquery fromSubquery) { // Do the subquery body first to get types, etc. try { fromSubquery.accept(this); if (fromSubquery.getResultColumns() == null) { ResultSetNode inner = fromSubquery.getSubquery(); ResultColumnList innerRCL = inner.getResultColumns(); if (innerRCL != null) { NodeFactory nodeFactory = fromSubquery.getNodeFactory(); SQLParserContext parserContext = fromSubquery.getParserContext(); ResultColumnList outerRCL = (ResultColumnList) nodeFactory.getNode(NodeTypes.RESULT_COLUMN_LIST, parserContext); int ncols = 0; for (ResultColumn innerColumn : innerRCL) { guaranteeColumnName(innerColumn); ValueNode valueNode = (ValueNode) nodeFactory.getNode(NodeTypes.VIRTUAL_COLUMN_NODE, inner, innerColumn, ++ncols, parserContext); ResultColumn outerColumn = (ResultColumn) nodeFactory.getNode(NodeTypes.RESULT_COLUMN, innerColumn.getName(), valueNode, parserContext); outerRCL.addResultColumn(outerColumn); //valueNode.setUserData(new ColumnBinding(inner, innerColumn)); } fromSubquery.setResultColumns(outerRCL); } } } catch (StandardException e) { throw new TableIsBadSubqueryException("", fromSubquery.getExposedName(), e.getMessage()); } return fromSubquery; } protected void addFromTable(FromTable fromTable) { if (fromTable instanceof JoinNode) { try { addJoinNode((JoinNode)fromTable); } catch (StandardException e) { throw new JoinNodeAdditionException (fromTable.getOrigTableName().getSchemaName(), fromTable.getOrigTableName().getTableName(), e.getMessage()); } return; } BindingContext bindingContext = getBindingContext(); bindingContext.tables.add(fromTable); if (fromTable.getCorrelationName() != null) { FromTable prevTable = bindingContext.correlationNames.put(fromTable.getCorrelationName(), fromTable); if ((prevTable != null) && bindingContext.tables.contains(prevTable)) { // Other occurrence in this same context. throw new CorrelationNameAlreadyUsedException(fromTable.getCorrelationName()); } } } protected void addJoinNode(JoinNode joinNode) throws StandardException { FromTable fromLeft = (FromTable)joinNode.getLeftResultSet(); FromTable fromRight = (FromTable)joinNode.getRightResultSet(); if (joinNode.getNodeType() == NodeTypes.FULL_OUTER_JOIN_NODE) { throw new UnsupportedFullOuterJoinException(); } addFromTable(fromLeft); addFromTable(fromRight); if ((joinNode.getUsingClause() != null) || joinNode.isNaturalJoin()) { // Replace USING clause with equivalent equality predicates, all bound up. NodeFactory nodeFactory = joinNode.getNodeFactory(); SQLParserContext parserContext = joinNode.getParserContext(); ValueNode conditions = null; if (joinNode.getUsingClause() != null) { for (ResultColumn rc : joinNode.getUsingClause()) { String columnName = rc.getName(); ColumnBinding leftBinding = getColumnBinding(fromLeft, columnName); if (leftBinding == null) throw new NoSuchColumnException(columnName, rc); ColumnBinding rightBinding = getColumnBinding(fromRight, columnName); if (rightBinding == null) throw new NoSuchColumnException(columnName, rc); conditions = addJoinEquality(conditions, columnName, leftBinding, rightBinding, nodeFactory, parserContext); BindingContext bindingContext = getBindingContext(); // USING is tricky case, since join columns do not repeat in expansion. // (see 7.5 of sql1992 spec) if (joinNode.getNodeType() == NodeTypes.FULL_OUTER_JOIN_NODE) { throw new UnsupportedFullOuterJoinException(); } else if (joinNode.getNodeType() == NodeTypes.HALF_OUTER_JOIN_NODE && ((HalfOuterJoinNode)joinNode).isRightOuterJoin()) { // We use the value from the right table on a RIGHT OUTER JOIN // so ignore the column in the left table bindingContext.applyUsingColumn(fromLeft, columnName); } else { // We use the value from the left table on an INNER JOIN or // LEFT OUTER JOIN so ignore the column in the right table bindingContext.applyUsingColumn(fromRight, columnName); } } } else if (joinNode.isNaturalJoin()) { Map<String,ColumnBinding> leftCols = new HashMap<>(); getUniqueColumnBindings(fromLeft, leftCols); Map<String,ColumnBinding> rightCols = new HashMap<>(); getUniqueColumnBindings(fromRight, rightCols); for (String columnName : leftCols.keySet()) { ColumnBinding rightBinding = rightCols.get(columnName); if (rightBinding == null) continue; ColumnBinding leftBinding = leftCols.get(columnName); conditions = addJoinEquality(conditions, columnName, leftBinding, rightBinding, nodeFactory, parserContext); } } if (joinNode.getJoinClause() == null) { joinNode.setJoinClause(conditions); } else { joinNode.setJoinClause((AndNode)nodeFactory.getNode(NodeTypes.AND_NODE, joinNode.getJoinClause(), conditions, parserContext)); } } // Take care of any remaining column bindings in the ON clause. if (joinNode.getJoinClause() != null) joinNode.getJoinClause().accept(this); } protected ValueNode addJoinEquality(ValueNode conditions, String columnName, ColumnBinding leftBinding, ColumnBinding rightBinding, NodeFactory nodeFactory, SQLParserContext parserContext) throws StandardException { ColumnReference leftCR = (ColumnReference) nodeFactory.getNode(NodeTypes.COLUMN_REFERENCE, columnName, leftBinding.getFromTable().getTableName(), parserContext); ColumnReference rightCR = (ColumnReference) nodeFactory.getNode(NodeTypes.COLUMN_REFERENCE, columnName, rightBinding.getFromTable().getTableName(), parserContext); ValueNode condition = (ValueNode) nodeFactory.getNode(NodeTypes.BINARY_EQUALS_OPERATOR_NODE, leftCR, rightCR, parserContext); if (conditions == null) { conditions = condition; } else { conditions = (AndNode)nodeFactory.getNode(NodeTypes.AND_NODE, conditions, condition, parserContext); } return conditions; } protected boolean columnReferencesView(ColumnBinding columnBinding) { return columnBinding.getFromTable() != null && columnBinding.getFromTable().getUserData() instanceof TableBinding && ((TableBinding)columnBinding.getFromTable().getUserData()).getTable() instanceof View; } protected void columnReference(ColumnReference columnReference) { ColumnBinding columnBinding = (ColumnBinding)columnReference.getUserData(); if (columnBinding != null) { if (!expandViews || !columnReferencesView(columnBinding)) { return; } columnBinding = null; } String columnName = columnReference.getColumnName(); if (columnReference.getTableNameNode() != null) { FromTable fromTable = findFromTable(columnReference.getTableNameNode()); columnBinding = getColumnBinding(fromTable, columnName); if (columnBinding == null) throw new NoSuchColumnException(columnName, columnReference); } else { if (resultColumnsAvailable(getBindingContext().resultColumnsAvailableContext, columnReference)) { ResultColumnList resultColumns = getBindingContext().resultColumns; if (resultColumns != null) { ResultColumn resultColumn = resultColumns.getResultColumn(columnName); if (resultColumn != null) { if (resultColumn.getExpression() instanceof ColumnReference) { columnBinding = (ColumnBinding)((ColumnReference)resultColumn.getExpression()).getUserData(); } if (columnBinding == null) columnBinding = new ColumnBinding(null, resultColumn); } } } if (columnBinding == null) { boolean ambiguous = false; outer: for (BindingContext bindingContext : bindingContexts) { ColumnBinding contextBinding = null; for (FromTable fromTable : bindingContext.tables) { ColumnBinding tableBinding = getColumnBinding(fromTable, columnName); if (tableBinding != null && !bindingContext.ignoreDueToUsing(fromTable, columnName)) { if (contextBinding != null) { ambiguous = true; break outer; } contextBinding = tableBinding; } } if (contextBinding != null) { columnBinding = contextBinding; break; } } if (columnBinding == null) { if (ambiguous) throw new AmbiguousColumNameException(columnName, columnReference); else throw new NoSuchColumnException(columnName, columnReference); } } } columnReference.setUserData(columnBinding); } protected boolean resultColumnsAvailable(QueryTreeNode context, ColumnReference columnReference) { if (context == null) return false; if (!resultColumnsAvailableBroadly) { // The column ref must be immediately in the order / group by list, not // in a sub-expression. switch (context.getNodeType()) { case NodeTypes.ORDER_BY_LIST: for (OrderByColumn column : ((OrderByList)context)) { if (column.getExpression() == columnReference) { return true; } } break; case NodeTypes.GROUP_BY_LIST: for (GroupByColumn column : ((GroupByList)context)) { if (column.getColumnExpression() == columnReference) { return true; } } break; } return false; } return true; } protected Table lookupTableName(TableName origName, String schemaName, String tableName) { Table result = ais.getTable(schemaName, tableName); if ((result == null) || ((context != null) && !context.isAccessible(result.getName()))) throw new NoSuchTableException(schemaName, tableName, origName); return result; } protected FromTable findFromTable(TableName tableNameNode){ String schemaName = tableNameNode.getSchemaName(); String tableName = tableNameNode.getTableName(); if (schemaName == null) { FromTable fromTable = getBindingContext().correlationNames.get(tableName); if (fromTable != null) return fromTable; schemaName = defaultSchemaName; } for (BindingContext bindingContext : bindingContexts) { for (FromTable fromTable : bindingContext.tables) { if ((fromTable instanceof FromBaseTable) && // Not allowed to reference correlated by underlying name. (fromTable.getCorrelationName() == null)) { FromBaseTable fromBaseTable = (FromBaseTable)fromTable; TableBinding tableBinding = (TableBinding)fromBaseTable.getUserData(); assert (tableBinding != null) : "table not bound yet"; Columnar table = tableBinding.getTable(); if (table.getName().getSchemaName().equalsIgnoreCase(schemaName) && table.getName().getTableName().equalsIgnoreCase(tableName)) { return fromBaseTable; } } } } throw new NoSuchTableException(schemaName, tableName, tableNameNode); } protected void getUniqueColumnBindings(FromTable fromTable, Map<String,ColumnBinding> bindings) throws StandardException { if (fromTable instanceof FromBaseTable) { FromBaseTable fromBaseTable = (FromBaseTable)fromTable; TableBinding tableBinding = (TableBinding)fromBaseTable.getUserData(); assert (tableBinding != null) : "table not bound yet"; Columnar table = tableBinding.getTable(); for (Column column : table.getColumns()) { ColumnBinding prev = bindings.put(column.getName(), new ColumnBinding(fromTable, column, tableBinding.isNullable())); if (prev != null) throw new StandardException("Duplicate column name " + column.getName() + " not allowed with NATURAL JOIN"); } } else if (fromTable instanceof FromSubquery) { FromSubquery fromSubquery = (FromSubquery)fromTable; for (ResultColumn resultColumn : fromSubquery.getResultColumns()) { ColumnBinding prev = bindings.put(resultColumn.getName(), new ColumnBinding(fromTable, resultColumn)); if (prev != null) throw new StandardException("Duplicate column name " + resultColumn.getName() + " not allowed with NATURAL JOIN"); } } else if (fromTable instanceof JoinNode) { JoinNode joinNode = (JoinNode)fromTable; getUniqueColumnBindings((FromTable)joinNode.getLeftResultSet(), bindings); getUniqueColumnBindings((FromTable)joinNode.getRightResultSet(), bindings); } } protected ColumnBinding getColumnBinding(FromTable fromTable, String columnName) { if (fromTable instanceof FromBaseTable) { FromBaseTable fromBaseTable = (FromBaseTable)fromTable; TableBinding tableBinding = (TableBinding)fromBaseTable.getUserData(); assert (tableBinding != null) : "table not bound yet"; Columnar table = tableBinding.getTable(); Column column = table.getColumn(columnName); if (column == null) return null; return new ColumnBinding(fromTable, column, tableBinding.isNullable()); } else if (fromTable instanceof FromSubquery) { FromSubquery fromSubquery = (FromSubquery)fromTable; ResultColumnList columns = fromSubquery.getResultColumns(); if (columns == null) columns = fromSubquery.getSubquery().getResultColumns(); ResultColumn resultColumn = columns.getResultColumn(columnName); if (resultColumn == null) return null; return new ColumnBinding(fromTable, resultColumn); } else if (fromTable instanceof JoinNode) { JoinNode joinNode = (JoinNode)fromTable; FromTable leftTable = (FromTable) joinNode.getLeftResultSet(); ColumnBinding leftBinding = getColumnBinding(leftTable, columnName); if (leftBinding != null && !getBindingContext().ignoreDueToUsing(leftTable, columnName)) { return leftBinding; } else { return getColumnBinding((FromTable) joinNode.getRightResultSet(), columnName); } } else { assert false; return null; } } /** * Expand any *'s in the ResultColumnList. In addition, we will guarantee that * each ResultColumn has a name. (All generated names will be unique across the * entire statement.) * * @exception StandardException Thrown on error */ public void expandAllsAndNameColumns(ResultColumnList rcl, FromList fromList) { if (rcl == null) return; boolean expanded = false; ResultColumnList allExpansion; TableName fullTableName; for (int index = 0; index < rcl.size(); index++) { ResultColumn rc = rcl.get(index); if (rc instanceof AllResultColumn) { AllResultColumn arc = (AllResultColumn)rc; expanded = true; fullTableName = arc.getTableNameObject(); boolean recursive = arc.isRecursive(); if (recursive && !allowSubqueryMultipleColumns) { throw new WholeGroupQueryException(); } allExpansion = expandAll(fullTableName, fromList, recursive); // Make sure that every column has a name. for (ResultColumn nrc : allExpansion) { guaranteeColumnName(nrc); } // Replace the AllResultColumn with the expanded list. rcl.remove(index); for (int inner = 0; inner < allExpansion.size(); inner++) { rcl.add(index + inner, allExpansion.get(inner)); } index += allExpansion.size() - 1; // TODO: This is where Derby remembered the original size in // case other things get added to the RCL. } else { // Make sure that every column has a name. guaranteeColumnName(rc); } } } /** * Generate a unique (across the entire statement) column name for unnamed * ResultColumns * * @exception StandardException Thrown on error */ protected void guaranteeColumnName(ResultColumn rc) { if (rc.getName() == null) { rc.setName(((SQLParser)rc.getParserContext()).generateColumnName()); rc.setNameGenerated(true); } } /** * Expand a "*" into the appropriate ResultColumnList. If the "*" * is unqualified it will expand into a list of all columns in all * of the base tables in the from list at the current nesting level; * otherwise it will expand into a list of all of the columns in the * base table that matches the qualification. * * @param allTableName The qualification on the "*" as a String * @param fromList The select list * * @return ResultColumnList representing expansion * * @exception StandardException Thrown on error */ protected ResultColumnList expandAll(TableName allTableName, FromList fromList, boolean recursive) { ResultColumnList resultColumnList = null; ResultColumnList tempRCList = null; for (FromTable fromTable : fromList) { tempRCList = getAllResultColumns(allTableName, fromTable, recursive); if (tempRCList == null) continue; /* Expand the column list and append to the list that * we will return. */ if (resultColumnList == null) resultColumnList = tempRCList; else resultColumnList.addAll(tempRCList); // If the "*" is qualified, then we can stop the expansion as // soon as we find the matching table. if (allTableName != null) break; } // Give an error if the qualification name did not match an exposed name. if (resultColumnList == null) { if (allTableName == null) { throw new NoTableSpecifiedInQueryException (); } else { throw new NoSuchTableException(allTableName.getSchemaName(), allTableName.getTableName(), allTableName); } } return resultColumnList; } protected ResultColumnList getAllResultColumns(TableName allTableName, ResultSetNode fromTable, boolean recursive) { try { switch (fromTable.getNodeType()) { case NodeTypes.FROM_BASE_TABLE: return getAllResultColumns(allTableName, (FromBaseTable)fromTable, recursive); case NodeTypes.JOIN_NODE: case NodeTypes.HALF_OUTER_JOIN_NODE: return getAllResultColumns(allTableName, (JoinNode)fromTable, recursive); case NodeTypes.FROM_SUBQUERY: return getAllResultColumns(allTableName, (FromSubquery)fromTable); default: return null; } } catch (StandardException ex) { throw new SQLParserInternalException(ex); } } protected ResultColumnList getAllResultColumns(TableName allTableName, FromBaseTable fromTable, boolean recursive) throws StandardException { TableName exposedName = fromTable.getExposedTableName(); if ((allTableName != null) && !allTableName.equals(exposedName)) return null; NodeFactory nodeFactory = fromTable.getNodeFactory(); SQLParserContext parserContext = fromTable.getParserContext(); ResultColumnList rcList = (ResultColumnList) nodeFactory.getNode(NodeTypes.RESULT_COLUMN_LIST, parserContext); TableBinding tableBinding = (TableBinding)fromTable.getUserData(); Columnar table = tableBinding.getTable(); for (Column column : table.getColumns()) { String columnName = column.getName(); ValueNode valueNode = (ValueNode) nodeFactory.getNode(NodeTypes.COLUMN_REFERENCE, columnName, exposedName, parserContext); ResultColumn resultColumn = (ResultColumn) nodeFactory.getNode(NodeTypes.RESULT_COLUMN, columnName, valueNode, parserContext); rcList.addResultColumn(resultColumn); // Easy to do binding right here. valueNode.setUserData(new ColumnBinding(fromTable, column, tableBinding.isNullable())); } if (recursive && (table instanceof Table)) { for (Join child : ((Table)table).getChildJoins()) { rcList.addResultColumn(childJoinSubquery(fromTable, child)); } } return rcList; } protected ResultColumnList getAllResultColumns(TableName allTableName, JoinNode fromJoin, boolean recursive) throws StandardException { ResultColumnList leftRCL = getAllResultColumns(allTableName, fromJoin.getLogicalLeftResultSet(), recursive); ResultColumnList rightRCL = getAllResultColumns(allTableName, fromJoin.getLogicalRightResultSet(), recursive); if (leftRCL == null) return rightRCL; else if (rightRCL == null) return leftRCL; if (fromJoin.getUsingClause() == null) { leftRCL.addAll(rightRCL); return leftRCL; } else { ResultColumnList joinRCL; // USING is tricky case, since join columns do not repeat in expansion. // (see 7.5 of sql1992 spec) if (fromJoin.getNodeType() == NodeTypes.FULL_OUTER_JOIN_NODE) { throw new UnsupportedFullOuterJoinException(); } else if (fromJoin.getNodeType() == NodeTypes.HALF_OUTER_JOIN_NODE && ((HalfOuterJoinNode)fromJoin).isRightOuterJoin()) { joinRCL = rightRCL.getJoinColumns(fromJoin.getUsingClause()); } else { joinRCL = leftRCL.getJoinColumns(fromJoin.getUsingClause()); } leftRCL.removeJoinColumns(fromJoin.getUsingClause()); joinRCL.addAll(leftRCL); rightRCL.removeJoinColumns(fromJoin.getUsingClause()); joinRCL.addAll(rightRCL); return joinRCL; } } protected ResultColumnList getAllResultColumns(TableName allTableName, FromSubquery fromSubquery) throws StandardException { NodeFactory nodeFactory = fromSubquery.getNodeFactory(); SQLParserContext parserContext = fromSubquery.getParserContext(); TableName exposedName = (TableName) nodeFactory.getNode(NodeTypes.TABLE_NAME, null, fromSubquery.getExposedName(), parserContext); if ((allTableName != null) && !allTableName.equals(exposedName)) return null; ResultColumnList rcList = (ResultColumnList) nodeFactory.getNode(NodeTypes.RESULT_COLUMN_LIST, parserContext); for (ResultColumn resultColumn : fromSubquery.getResultColumns()) { String columnName = resultColumn.getName(); boolean isNameGenerated = resultColumn.isNameGenerated(); TableName tableName = exposedName; ValueNode valueNode = (ValueNode) nodeFactory.getNode(NodeTypes.COLUMN_REFERENCE, columnName, tableName, parserContext); resultColumn = (ResultColumn) nodeFactory.getNode(NodeTypes.RESULT_COLUMN, columnName, valueNode, parserContext); resultColumn.setNameGenerated(isNameGenerated); rcList.add(resultColumn); } return rcList; } /** Make a nested result set for this child (and so on recursively). */ protected ResultColumn childJoinSubquery(FromBaseTable parentTable, Join child) throws StandardException { NodeFactory nodeFactory = parentTable.getNodeFactory(); SQLParserContext parserContext = parentTable.getParserContext(); Table childAisTable = child.getChild(); Object childName = nodeFactory.getNode(NodeTypes.TABLE_NAME, childAisTable.getName().getSchemaName(), childAisTable.getName().getTableName(), parserContext); FromBaseTable childTable = (FromBaseTable) nodeFactory.getNode(NodeTypes.FROM_BASE_TABLE, childName, childAisTable.getName().getTableName(), null, null, null, parserContext); childTable.setUserData(new TableBinding(childAisTable, false)); ValueNode whereClause = null; for (JoinColumn join : child.getJoinColumns()) { ColumnReference parentPK = (ColumnReference) nodeFactory.getNode(NodeTypes.COLUMN_REFERENCE, join.getParent().getName(), parentTable.getTableName(), parserContext); parentPK.setUserData(new ColumnBinding(parentTable, join.getParent(), false)); ColumnReference childFK = (ColumnReference) nodeFactory.getNode(NodeTypes.COLUMN_REFERENCE, join.getChild().getName(), childName, parserContext); childFK.setUserData(new ColumnBinding(childTable, join.getChild(), false)); ValueNode equals = (ValueNode) nodeFactory.getNode(NodeTypes.BINARY_EQUALS_OPERATOR_NODE, parentPK, childFK, parserContext); if (whereClause == null) { whereClause = equals; } else { whereClause = (ValueNode) nodeFactory.getNode(NodeTypes.AND_NODE, whereClause, equals, parserContext); } } FromList fromList = (FromList) nodeFactory.getNode(NodeTypes.FROM_LIST, parserContext); fromList.addFromTable(childTable); ResultColumnList rcl = getAllResultColumns(null, childTable, true); SelectNode selectNode = (SelectNode) nodeFactory.getNode(NodeTypes.SELECT_NODE, rcl, null, fromList, whereClause, null, null, null, parserContext); SubqueryNode subquery = (SubqueryNode) nodeFactory.getNode(NodeTypes.SUBQUERY_NODE, selectNode, SubqueryNode.SubqueryType.EXPRESSION, null, null, null, null, parserContext); ResultColumn resultColumn = (ResultColumn) nodeFactory.getNode(NodeTypes.RESULT_COLUMN, childAisTable.getNameForOutput(), subquery, parserContext); return resultColumn; } protected void dmlModStatementNode(DMLModStatementNode node) { TableName tableName = node.getTargetTableName(); String schemaName = tableName.getSchemaName(); if (schemaName == null) schemaName = defaultSchemaName; Table table = lookupTableName(tableName, schemaName, tableName.getTableName()); tableName.setUserData(table); ResultColumnList targetColumns = null; if (node instanceof InsertNode) targetColumns = ((InsertNode)node).getTargetColumnList(); else targetColumns = node.getResultSetNode().getResultColumns(); if (targetColumns != null) { for (ResultColumn targetColumn : targetColumns) { ColumnReference columnReference = targetColumn.getReference(); String columnName = columnReference.getColumnName(); Column column = table.getColumn(columnName); if (column == null) throw new NoSuchColumnException(columnName, columnReference); ColumnBinding columnBinding = new ColumnBinding(null, column, false); columnReference.setUserData(columnBinding); } } if (node.getReturningList() != null) { ResultColumnList rcl = node.getReturningList(); NodeFactory nodeFactory = node.getNodeFactory(); SQLParserContext parserContext = node.getParserContext(); FromBaseTable fromTable = null; FromList fromList = null; try { fromTable = (FromBaseTable)nodeFactory.getNode(NodeTypes.FROM_BASE_TABLE, tableName, null, rcl, null, null, parserContext); fromTable.setUserData(new TableBinding(table, false)); fromList = (FromList)nodeFactory.getNode(NodeTypes.FROM_LIST, Boolean.FALSE, parserContext); fromList.add(fromTable); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } node.setUserData(fromTable); for (int index = 0; index < rcl.size(); index ++) { ResultColumn rc = rcl.get(index); if (rc instanceof AllResultColumn) { AllResultColumn arc = (AllResultColumn)rc; ResultColumnList allExpansion = expandAll(tableName, fromList, false); // Make sure that every column has a name. for (ResultColumn nrc : allExpansion) { guaranteeColumnName(nrc); } // Replace the AllResultColumn with the expanded list. rcl.remove(index); for (int inner = 0; inner < allExpansion.size(); inner++) { rcl.add(index + inner, allExpansion.get(inner)); } index += allExpansion.size() - 1; } else { // Make sure that every column has a name. guaranteeColumnName(rc); } } pushBindingContext(null); getBindingContext().tables.add(fromTable); try { rcl.accept(this); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } popBindingContext(); } } protected void unionNode(UnionNode node) { pushBindingContext(null); try { node.getLeftResultSet().accept(this); node.setResultColumns(node.copyResultColumnsFromLeft()); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } popBindingContext(); pushBindingContext(null); try { node.getRightResultSet().accept(this); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } popBindingContext(); } protected void intersectOrExceptNode(IntersectOrExceptNode node) { pushBindingContext(null); try { node.getLeftResultSet().accept(this); node.setResultColumns(node.copyResultColumnsFromLeft()); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } popBindingContext(); pushBindingContext(null); try { node.getRightResultSet().accept(this); } catch (StandardException ex) { throw new SQLParserInternalException(ex); } popBindingContext(); } protected void javaValueNode(JavaValueNode javaValue) { if ((javaValue instanceof StaticMethodCallNode) && (functionDefined != null)) { StaticMethodCallNode methodCall = (StaticMethodCallNode)javaValue; Routine routine = null; if ((methodCall.getProcedureName() != null) && (methodCall.getProcedureName().hasSchema())) { // Qualified name is always a routine and an immediate error if not. routine = ais.getRoutine(methodCall.getProcedureName().getSchemaName(), methodCall.getProcedureName().getTableName()); if ((routine == null) || !context.isAccessible(routine.getName())) { throw new NoSuchFunctionException(methodCall.getProcedureName().toString()); } } else if (!functionDefined.isDefined(methodCall.getMethodName())) { // Unqualified only if not a built-in function and error deferred. routine = ais.getRoutine(defaultSchemaName, methodCall.getMethodName()); if ((routine != null) && !context.isAccessible(routine.getName())) { routine = null; } } if (routine != null) { if (routine.getReturnValue() == null) { throw new ProcedureCalledAsFunctionException(routine.getName()); } methodCall.setUserData(routine); } } } protected static class BindingContext { Collection<FromTable> tables = new ArrayList<>(); Map<String,FromTable> correlationNames = new HashMap<>(); ResultColumnList resultColumns; QueryTreeNode resultColumnsAvailableContext; private Map<FromTable, Set<String>> ignoredColumnsDueToUsing = new HashMap<>(); public void applyUsingColumn(FromTable table, String columnName) { Set<String> columnNames = ignoredColumnsDueToUsing.get(table); if (columnNames == null) { columnNames = new HashSet<>(); ignoredColumnsDueToUsing.put(table, columnNames); } columnNames.add(columnName); } public boolean ignoreDueToUsing(FromTable table, String columnName) { Set<String> columnNames = ignoredColumnsDueToUsing.get(table); return columnNames != null && columnNames.contains(columnName); } } protected BindingContext getBindingContext() { return bindingContexts.peek(); } protected void pushBindingContext(ResultSetNode resultSet) { BindingContext next = new BindingContext(); if (!bindingContexts.isEmpty()) { // Initially inherit all the same correlation names (but can overwrite). next.correlationNames.putAll(bindingContexts.peek().correlationNames); } if (resultSet != null) { next.resultColumns = resultSet.getResultColumns(); } bindingContexts.push(next); } protected void popBindingContext() { bindingContexts.pop(); } /* Visitor interface. This is messy. Perhaps there should be an abstract class which makes the common Visitor interface into a Hierarchical Vistor pattern. */ // To understand why this works, see QueryTreeNode.accept(). public Visitable visit(Visitable node) { visitAfter((QueryTreeNode)node); return node; } public boolean skipChildren(Visitable node) { return ! visitBefore((QueryTreeNode)node); } public boolean visitChildrenFirst(Visitable node) { return true; } public boolean stopTraversal() { return false; } }