--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.FromSubquery\r
+\r
+ Licensed to the Apache Software Foundation (ASF) under one or more\r
+ contributor license agreements. See the NOTICE file distributed with\r
+ this work for additional information regarding copyright ownership.\r
+ The ASF licenses this file to you under the Apache License, Version 2.0\r
+ (the "License"); you may not use this file except in compliance with\r
+ the License. You may obtain a copy of the License at\r
+\r
+ http://www.apache.org/licenses/LICENSE-2.0\r
+\r
+ Unless required by applicable law or agreed to in writing, software\r
+ distributed under the License is distributed on an "AS IS" BASIS,\r
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ See the License for the specific language governing permissions and\r
+ limitations under the License.\r
+\r
+ */\r
+\r
+package org.apache.derby.impl.sql.compile;\r
+\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.util.JBitSet;\r
+\r
+\r
+/**\r
+ * A FromSubquery represents a subquery in the FROM list of a DML statement.\r
+ *\r
+ * The current implementation of this class is only\r
+ * sufficient for Insert's need to push a new\r
+ * select on top of the one the user specified,\r
+ * to make the selected structure match that\r
+ * of the insert target table.\r
+ *\r
+ */\r
+public class FromSubquery extends FromTable\r
+{\r
+ ResultSetNode subquery;\r
+\r
+ /**\r
+ * Intializer for a table in a FROM list.\r
+ *\r
+ * @param subquery The subquery\r
+ * @param correlationName The correlation name\r
+ * @param derivedRCL The derived column list\r
+ * @param tableProperties Properties list associated with the table\r
+ */\r
+ public void init(\r
+ Object subquery,\r
+ Object correlationName,\r
+ Object derivedRCL,\r
+ Object tableProperties)\r
+ {\r
+ super.init(correlationName, tableProperties);\r
+ this.subquery = (ResultSetNode) subquery;\r
+ resultColumns = (ResultColumnList) derivedRCL;\r
+ }\r
+\r
+ /**\r
+ * Convert this object to a String. See comments in QueryTreeNode.java\r
+ * for how this should be done for tree printing.\r
+ *\r
+ * @return This object as a String\r
+ */\r
+\r
+ public String toString()\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ return super.toString();\r
+ }\r
+ else\r
+ {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Prints the sub-nodes of this object. See QueryTreeNode.java for\r
+ * how tree printing is supposed to work.\r
+ *\r
+ * @param depth The depth of this node in the tree\r
+ */\r
+\r
+ public void printSubNodes(int depth)\r
+ {\r
+ if (SanityManager.DEBUG) {\r
+ super.printSubNodes(depth);\r
+\r
+ if (subquery != null)\r
+ {\r
+ printLabel(depth, "subquery: ");\r
+ subquery.treePrint(depth + 1);\r
+ }\r
+ }\r
+ }\r
+\r
+ /** \r
+ * Return the "subquery" from this node.\r
+ *\r
+ * @return ResultSetNode The "subquery" from this node.\r
+ */\r
+ public ResultSetNode getSubquery()\r
+ {\r
+ return subquery;\r
+ }\r
+\r
+ /** \r
+ * Determine whether or not the specified name is an exposed name in\r
+ * the current query block.\r
+ *\r
+ * @param name The specified name to search for as an exposed name.\r
+ * @param schemaName Schema name, if non-null.\r
+ * @param exactMatch Whether or not we need an exact match on specified schema and table\r
+ * names or match on table id.\r
+ *\r
+ * @return The FromTable, if any, with the exposed name.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ protected FromTable getFromTableByName(String name, String schemaName, boolean exactMatch)\r
+ throws StandardException\r
+ {\r
+ return super.getFromTableByName(name, schemaName, exactMatch);\r
+ }\r
+\r
+ /**\r
+ * Bind this subquery that appears in the FROM list.\r
+ *\r
+ * @param dataDictionary The DataDictionary to use for binding\r
+ * @param fromListParam FromList to use/append to.\r
+ *\r
+ * @return ResultSetNode The bound FromSubquery.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultSetNode bindNonVTITables(DataDictionary dataDictionary, \r
+ FromList fromListParam) \r
+ throws StandardException\r
+ {\r
+ /* Assign the tableNumber */\r
+ if (tableNumber == -1) // allow re-bind, in which case use old number\r
+ tableNumber = getCompilerContext().getNextTableNumber();\r
+\r
+ subquery = subquery.bindNonVTITables(dataDictionary, fromListParam);\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Bind this subquery that appears in the FROM list.\r
+ *\r
+ * @param fromListParam FromList to use/append to.\r
+ *\r
+ * @return ResultSetNode The bound FromSubquery.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultSetNode bindVTITables(FromList fromListParam) \r
+ throws StandardException\r
+ {\r
+ subquery = subquery.bindVTITables(fromListParam);\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Check for (and reject) ? parameters directly under the ResultColumns.\r
+ * This is done for SELECT statements. For FromSubquery, we\r
+ * simply pass the check through to the subquery.\r
+ *\r
+ * @exception StandardException Thrown if a ? parameter found\r
+ * directly under a ResultColumn\r
+ */\r
+\r
+ public void rejectParameters() throws StandardException\r
+ {\r
+ subquery.rejectParameters();\r
+ }\r
+\r
+ /**\r
+ * Bind the expressions in this FromSubquery. This means \r
+ * binding the sub-expressions, as well as figuring out what the return \r
+ * type is for each expression.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void bindExpressions(FromList fromListParam)\r
+ throws StandardException\r
+ {\r
+ FromList emptyFromList =\r
+ (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager());\r
+ ResultColumnList derivedRCL = resultColumns;\r
+ ResultColumnList subqueryRCL;\r
+ FromList nestedFromList;\r
+\r
+ /* From subqueries cannot be correlated, so we pass an empty FromList\r
+ * to subquery.bindExpressions() and .bindResultColumns()\r
+ */\r
+ \r
+ nestedFromList = emptyFromList;\r
+ subquery.bindExpressions(nestedFromList);\r
+ subquery.bindResultColumns(nestedFromList);\r
+\r
+ /* Now that we've bound the expressions in the subquery, we \r
+ * can propagate the subquery's RCL up to the FromSubquery.\r
+ * Get the subquery's RCL, assign shallow copy back to\r
+ * it and create new VirtualColumnNodes for the original's\r
+ * ResultColumn.expressions.\r
+ * NOTE: If the size of the derived column list is less than\r
+ * the size of the subquery's RCL and the derived column list is marked\r
+ * for allowing a size mismatch, then we have a select * view\r
+ * on top of a table that has had columns added to it via alter table.\r
+ * In this case, we trim out the columns that have been added to\r
+ * the table since the view was created.\r
+ */\r
+ subqueryRCL = subquery.getResultColumns();\r
+ if (resultColumns != null && resultColumns.getCountMismatchAllowed() &&\r
+ resultColumns.size() < subqueryRCL.size())\r
+ {\r
+ for (int index = subqueryRCL.size() - 1; \r
+ index >= resultColumns.size(); \r
+ index--)\r
+ {\r
+ subqueryRCL.removeElementAt(index);\r
+ }\r
+ }\r
+\r
+ subquery.setResultColumns(subqueryRCL.copyListAndObjects());\r
+ subqueryRCL.genVirtualColumnNodes(subquery, subquery.getResultColumns());\r
+ resultColumns = subqueryRCL;\r
+\r
+ /* Propagate the name info from the derived column list */\r
+ if (derivedRCL != null)\r
+ {\r
+ resultColumns.propagateDCLInfo(derivedRCL, correlationName);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Try to find a ResultColumn in the table represented by this FromBaseTable\r
+ * that matches the name in the given ColumnReference.\r
+ *\r
+ * @param columnReference The columnReference whose name we're looking\r
+ * for in the given table.\r
+ *\r
+ * @return A ResultColumn whose expression is the ColumnNode\r
+ * that matches the ColumnReference.\r
+ * Returns null if there is no match.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultColumn getMatchingColumn(ColumnReference columnReference) throws StandardException\r
+ {\r
+ ResultColumn resultColumn = null;\r
+ String columnsTableName;\r
+\r
+ /*\r
+ ** RESOLVE: When we add support for schemas, check to see if\r
+ ** the column name specifies a schema, and if so, if this\r
+ ** table is in that schema.\r
+ */\r
+\r
+ columnsTableName = columnReference.getTableName();\r
+\r
+ // post 681, 1 may be no longer needed. 5 is the default case\r
+ // now but what happens if the condition is false? Investigate.\r
+ if (columnReference.getGeneratedToReplaceAggregate()) // 1\r
+ {\r
+ resultColumn = resultColumns.getResultColumn(columnReference.getColumnName());\r
+ }\r
+ else if (columnsTableName == null || columnsTableName.equals(correlationName)) // 5?\r
+ {\r
+ resultColumn = resultColumns.getAtMostOneResultColumn(columnReference, correlationName, false);\r
+ }\r
+ \r
+\r
+ if (resultColumn != null)\r
+ {\r
+ columnReference.setTableNumber(tableNumber);\r
+ }\r
+\r
+ return resultColumn;\r
+ }\r
+\r
+ /**\r
+ * Preprocess a ResultSetNode - this currently means:\r
+ * o Generating a referenced table map for each ResultSetNode.\r
+ * o Putting the WHERE and HAVING clauses in conjunctive normal form (CNF).\r
+ * o Converting the WHERE and HAVING clauses into PredicateLists and\r
+ * classifying them.\r
+ * o Ensuring that a ProjectRestrictNode is generated on top of every \r
+ * FromBaseTable and generated in place of every FromSubquery. \r
+ * o Pushing single table predicates down to the new ProjectRestrictNodes.\r
+ *\r
+ * @param numTables The number of tables in the DML Statement\r
+ * @param gbl The group by list, if any\r
+ * @param fromList The from list, if any\r
+ *\r
+ * @return ResultSetNode at top of preprocessed tree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultSetNode preprocess(int numTables,\r
+ GroupByList gbl,\r
+ FromList fromList)\r
+ throws StandardException\r
+ {\r
+ /* We want to chop out the FromSubquery from the tree and replace it \r
+ * with a ProjectRestrictNode. One complication is that there may be \r
+ * ColumnReferences above us which point to the FromSubquery's RCL.\r
+ * What we want to return is a tree with a PRN with the\r
+ * FromSubquery's RCL on top. (In addition, we don't want to be\r
+ * introducing any redundant ProjectRestrictNodes.)\r
+ * Another complication is that we want to be able to only only push\r
+ * projections and restrictions down to this ProjectRestrict, but\r
+ * we want to be able to push them through as well.\r
+ * So, we:\r
+ * o call subquery.preprocess() which returns a tree with\r
+ * a SelectNode or a RowResultSetNode on top. \r
+ * o If the FSqry is flattenable(), then we return (so that the\r
+ * caller can then call flatten()), otherwise we:\r
+ * o generate a PRN, whose RCL is the FSqry's RCL, on top of the result.\r
+ * o create a referencedTableMap for the PRN which represents \r
+ * the FSqry's tableNumber, since ColumnReferences in the outer\r
+ * query block would be referring to that one. \r
+ * (This will allow us to push restrictions down to the PRN.)\r
+ */\r
+\r
+ subquery = subquery.preprocess(numTables, gbl, fromList);\r
+\r
+ /* Return if the FSqry is flattenable() \r
+ * NOTE: We can't flatten a FromSubquery if there is a group by list\r
+ * because the group by list must be ColumnReferences. For:\r
+ * select c1 from v1 group by c1,\r
+ * where v1 is select 1 from t1\r
+ * The expression under the last redundant ResultColumn is an IntConstantNode,\r
+ * not a ColumnReference.\r
+ * We also do not flatten a subquery if tableProperties is non-null,\r
+ * as the user is specifying 1 or more properties for the derived table,\r
+ * which could potentially be lost on the flattening.\r
+ * RESOLVE - this is too restrictive.\r
+ */\r
+ if ((gbl == null || gbl.size() == 0) &&\r
+ tableProperties == null &&\r
+ subquery.flattenableInFromSubquery(fromList))\r
+ {\r
+ /* Set our table map to the subquery's table map. */\r
+ setReferencedTableMap(subquery.getReferencedTableMap());\r
+ return this;\r
+ }\r
+\r
+ return extractSubquery(numTables);\r
+ }\r
+\r
+ /**\r
+ * Extract out and return the subquery, with a PRN on top.\r
+ * (See FromSubquery.preprocess() for more details.)\r
+ *\r
+ * @param numTables The number of tables in the DML Statement\r
+ *\r
+ * @return ResultSetNode at top of extracted tree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public ResultSetNode extractSubquery(int numTables)\r
+ throws StandardException\r
+ {\r
+ JBitSet newJBS;\r
+ ResultSetNode newPRN;\r
+\r
+ newPRN = (ResultSetNode) getNodeFactory().getNode(\r
+ C_NodeTypes.PROJECT_RESTRICT_NODE,\r
+ subquery, /* Child ResultSet */\r
+ resultColumns, /* Projection */\r
+ null, /* Restriction */\r
+ null, /* Restriction as PredicateList */\r
+ null, /* Subquerys in Projection */\r
+ null, /* Subquerys in Restriction */\r
+ tableProperties,\r
+ getContextManager() );\r
+\r
+ /* Set up the PRN's referencedTableMap */\r
+ newJBS = new JBitSet(numTables);\r
+ newJBS.set(tableNumber);\r
+ newPRN.setReferencedTableMap(newJBS);\r
+ ((FromTable) newPRN).setTableNumber(tableNumber);\r
+\r
+ return newPRN;\r
+ }\r
+\r
+ /**\r
+ * Flatten this FSqry into the outer query block. The steps in\r
+ * flattening are:\r
+ * o Mark all ResultColumns as redundant, so that they are "skipped over"\r
+ * at generate().\r
+ * o Append the wherePredicates to the outer list.\r
+ * o Return the fromList so that the caller will merge the 2 lists \r
+ * RESOLVE - FSqrys with subqueries are currently not flattenable. Some of\r
+ * them can be flattened, however. We need to merge the subquery list when\r
+ * we relax this restriction.\r
+ *\r
+ * NOTE: This method returns NULL when flattening RowResultSetNodes\r
+ * (the node for a VALUES clause). The reason is that no reference\r
+ * is left to the RowResultSetNode after flattening is done - the\r
+ * expressions point directly to the ValueNodes in the RowResultSetNode's\r
+ * ResultColumnList.\r
+ *\r
+ * @param rcl The RCL from the outer query\r
+ * @param outerPList PredicateList to append wherePredicates to.\r
+ * @param sql The SubqueryList from the outer query\r
+ * @param gbl The group by list, if any\r
+ *\r
+ * @return FromList The fromList from the underlying SelectNode.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public FromList flatten(ResultColumnList rcl,\r
+ PredicateList outerPList,\r
+ SubqueryList sql,\r
+ GroupByList gbl)\r
+\r
+ throws StandardException\r
+ {\r
+ FromList fromList = null;\r
+ SelectNode selectNode;\r
+\r
+ resultColumns.setRedundant();\r
+\r
+ subquery.getResultColumns().setRedundant();\r
+\r
+ /*\r
+ ** RESOLVE: Each type of result set should know how to remap itself.\r
+ */\r
+ if (subquery instanceof SelectNode)\r
+ {\r
+ selectNode = (SelectNode) subquery;\r
+ fromList = selectNode.getFromList();\r
+\r
+ // selectNode.getResultColumns().setRedundant();\r
+\r
+ if (selectNode.getWherePredicates().size() > 0)\r
+ {\r
+ outerPList.destructiveAppend(selectNode.getWherePredicates());\r
+ }\r
+\r
+ if (selectNode.getWhereSubquerys().size() > 0)\r
+ {\r
+ sql.destructiveAppend(selectNode.getWhereSubquerys());\r
+ }\r
+ }\r
+ else if ( ! (subquery instanceof RowResultSetNode))\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.THROWASSERT("subquery expected to be either a SelectNode or a RowResultSetNode, but is a " + subquery.getClass().getName());\r
+ }\r
+ }\r
+\r
+ /* Remap all ColumnReferences from the outer query to this node.\r
+ * (We replace those ColumnReferences with clones of the matching\r
+ * expression in the SELECT's RCL.\r
+ */\r
+ rcl.remapColumnReferencesToExpressions();\r
+ outerPList.remapColumnReferencesToExpressions();\r
+ if (gbl != null)\r
+ {\r
+ gbl.remapColumnReferencesToExpressions();\r
+ }\r
+\r
+ return fromList;\r
+ }\r
+\r
+ /**\r
+ * Get the exposed name for this table, which is the name that can\r
+ * be used to refer to it in the rest of the query.\r
+ *\r
+ * @return The exposed name for this table.\r
+ */\r
+\r
+ public String getExposedName()\r
+ {\r
+ return correlationName;\r
+ }\r
+\r
+ /**\r
+ * Expand a "*" into a ResultColumnList with all of the\r
+ * result columns from the subquery.\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ResultColumnList getAllResultColumns(TableName allTableName)\r
+ throws StandardException\r
+ {\r
+ ResultColumnList rcList = null;\r
+ TableName exposedName;\r
+ TableName toCompare;\r
+\r
+\r
+ if(allTableName != null)\r
+ toCompare = makeTableName(allTableName.getSchemaName(),correlationName);\r
+ else\r
+ toCompare = makeTableName(null,correlationName);\r
+ \r
+ if ( allTableName != null &&\r
+ ! allTableName.equals(toCompare))\r
+ {\r
+ return null;\r
+ }\r
+\r
+ /* Cache exposed name for this table.\r
+ * The exposed name becomes the qualifier for each column\r
+ * in the expanded list.\r
+ */\r
+ exposedName = makeTableName(null, correlationName);\r
+\r
+ rcList = (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+\r
+ /* Build a new result column list based off of resultColumns.\r
+ * NOTE: This method will capture any column renaming due to \r
+ * a derived column list.\r
+ */\r
+ int rclSize = resultColumns.size();\r
+ for (int index = 0; index < rclSize; index++)\r
+ {\r
+ ResultColumn resultColumn = (ResultColumn) resultColumns.elementAt(index);\r
+ ValueNode valueNode;\r
+ String columnName;\r
+\r
+ if (resultColumn.isGenerated())\r
+ {\r
+ continue;\r
+ }\r
+\r
+ // Build a ResultColumn/ColumnReference pair for the column //\r
+ columnName = resultColumn.getName();\r
+ boolean isNameGenerated = resultColumn.isNameGenerated();\r
+\r
+ /* If this node was generated for a GROUP BY, then tablename for the CR, if any,\r
+ * comes from the source RC.\r
+ */\r
+ TableName tableName;\r
+\r
+ tableName = exposedName;\r
+\r
+ valueNode = (ValueNode) getNodeFactory().getNode(\r
+ C_NodeTypes.COLUMN_REFERENCE,\r
+ columnName,\r
+ tableName,\r
+ getContextManager());\r
+ resultColumn = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ columnName,\r
+ valueNode,\r
+ getContextManager());\r
+\r
+ resultColumn.setNameGenerated(isNameGenerated);\r
+ // Build the ResultColumnList to return //\r
+ rcList.addResultColumn(resultColumn);\r
+ }\r
+ return rcList;\r
+ }\r
+\r
+ /** \r
+ * @see QueryTreeNode#disablePrivilegeCollection\r
+ */\r
+ public void disablePrivilegeCollection()\r
+ {\r
+ super.disablePrivilegeCollection();\r
+ subquery.disablePrivilegeCollection();\r
+ }\r
+\r
+ /**\r
+ * Search to see if a query references the specifed table name.\r
+ *\r
+ * @param name Table name (String) to search for.\r
+ * @param baseTable Whether or not name is for a base table\r
+ *\r
+ * @return true if found, else false\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public boolean referencesTarget(String name, boolean baseTable)\r
+ throws StandardException\r
+ {\r
+ return subquery.referencesTarget(name, baseTable);\r
+ }\r
+\r
+ /**\r
+ * Return true if the node references SESSION schema tables (temporary or permanent)\r
+ *\r
+ * @return true if references SESSION schema tables, else false\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public boolean referencesSessionSchema()\r
+ throws StandardException\r
+ {\r
+ return subquery.referencesSessionSchema();\r
+ }\r
+\r
+ /**\r
+ * Bind any untyped null nodes to the types in the given ResultColumnList.\r
+ *\r
+ * @param bindingRCL The ResultColumnList with the types to bind to.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void bindUntypedNullsToResultColumns(ResultColumnList bindingRCL)\r
+ throws StandardException\r
+ {\r
+ subquery.bindUntypedNullsToResultColumns(bindingRCL);\r
+ }\r
+\r
+ /**\r
+ * Decrement (query block) level (0-based) for this FromTable.\r
+ * This is useful when flattening a subquery.\r
+ *\r
+ * @param decrement The amount to decrement by.\r
+ */\r
+ void decrementLevel(int decrement)\r
+ {\r
+ super.decrementLevel(decrement);\r
+ subquery.decrementLevel(decrement);\r
+ }\r
+}\r