--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.CurrentOfNode\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
+import org.apache.derby.iapi.services.context.ContextManager;\r
+\r
+import org.apache.derby.iapi.sql.compile.CompilerContext;\r
+import org.apache.derby.iapi.sql.compile.OptimizablePredicateList;\r
+import org.apache.derby.iapi.sql.compile.Optimizer;\r
+import org.apache.derby.iapi.sql.compile.CostEstimate;\r
+import org.apache.derby.iapi.sql.compile.OptimizableList;\r
+import org.apache.derby.iapi.sql.compile.Optimizable;\r
+import org.apache.derby.iapi.sql.compile.RequiredRowOrdering;\r
+import org.apache.derby.iapi.sql.compile.RowOrdering;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+\r
+import org.apache.derby.iapi.sql.dictionary.DataDictionary;\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;\r
+import org.apache.derby.iapi.sql.dictionary.ConglomerateDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.TableDescriptor;\r
+\r
+import org.apache.derby.iapi.types.TypeId;\r
+\r
+import org.apache.derby.iapi.sql.execute.ExecCursorTableReference;\r
+import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;\r
+\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.iapi.sql.Activation;\r
+\r
+import org.apache.derby.iapi.reference.SQLState;\r
+\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.reference.ClassName;\r
+\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.impl.sql.compile.ActivationClassBuilder;\r
+\r
+import org.apache.derby.iapi.util.JBitSet;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+\r
+import java.util.Properties;\r
+\r
+/**\r
+ * The CurrentOf operator is used by positioned DELETE \r
+ * and UPDATE to get the current row and location\r
+ * for the target cursor. The bind() operations for \r
+ * positioned DELETE and UPDATE add a column to \r
+ * the select list under the statement for the row location \r
+ * accessible from this node.\r
+ *\r
+ * This node is placed in the from clause of the select\r
+ * generated for the delete or update operation. It acts\r
+ * much like a FromBaseTable, using the information about\r
+ * the target table of the cursor to provide information.\r
+ *\r
+ */\r
+public final class CurrentOfNode extends FromTable {\r
+\r
+ private String cursorName;\r
+ private ExecPreparedStatement preStmt;\r
+ private TableName exposedTableName;\r
+ private TableName baseTableName;\r
+ private CostEstimate singleScanCostEstimate;\r
+\r
+ //\r
+ // initializers\r
+ //\r
+ public void init( Object correlationName, Object cursor, Object tableProperties)\r
+ {\r
+ super.init(correlationName, tableProperties);\r
+ cursorName = (String) cursor;\r
+ }\r
+\r
+ /*\r
+ * Optimizable interface\r
+ */\r
+\r
+ /**\r
+ * @see Optimizable#estimateCost\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public CostEstimate estimateCost(OptimizablePredicateList predList,\r
+ ConglomerateDescriptor cd,\r
+ CostEstimate outerCost,\r
+ Optimizer optimizer,\r
+ RowOrdering rowOrdering)\r
+ throws StandardException\r
+ {\r
+ /*\r
+ ** Get the cost of a single scan of this result set.\r
+ **\r
+ ** Assume for now that the cost of a CURRENT OF is zero, with one row\r
+ ** fetched. Is this true, and if not, does it make a difference?\r
+ ** CURRENT OF can only occur when there is only one table in the\r
+ ** FROM list, and when the only "predicate" is the WHERE CURRENT OF,\r
+ ** so there's nothing to optimize in this case.\r
+ */\r
+ if (singleScanCostEstimate == null)\r
+ {\r
+ singleScanCostEstimate = optimizer.newCostEstimate();\r
+ }\r
+\r
+ singleScanCostEstimate.setCost(0.0d, 1.0d, 1.0d);\r
+ getBestAccessPath().setCostEstimate(singleScanCostEstimate);\r
+ getBestSortAvoidancePath().setCostEstimate(singleScanCostEstimate);\r
+\r
+ return singleScanCostEstimate;\r
+ }\r
+\r
+ //\r
+ // FromTable interface\r
+ //\r
+\r
+ /**\r
+ * Binding this FromTable means finding the prepared statement\r
+ * for the cursor and creating the result columns (the columns\r
+ * updatable on that cursor).\r
+ * \r
+ * We expect someone else to verify that the target table\r
+ * of the positioned update or delete is the table under this cursor.\r
+ *\r
+ * @param dataDictionary The DataDictionary to use for binding\r
+ * @param fromListParam FromList to use/append to.\r
+ *\r
+ * @return ResultSetNode Returns this.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ResultSetNode bindNonVTITables(DataDictionary dataDictionary, \r
+ FromList fromListParam) \r
+ throws StandardException {\r
+\r
+ // verify that the cursor exists\r
+\r
+ preStmt = getCursorStatement();\r
+\r
+ if (preStmt == null) {\r
+ throw StandardException.newException(SQLState.LANG_CURSOR_NOT_FOUND, \r
+ cursorName);\r
+ }\r
+ \r
+ preStmt.rePrepare(getLanguageConnectionContext());\r
+\r
+ // verify that the cursor is updatable (UPDATE is responsible\r
+ // for checking that the right columns are updatable)\r
+ if (preStmt.getUpdateMode() != CursorNode.UPDATE)\r
+ {\r
+ String printableString = (cursorName == null) ? "" : cursorName;\r
+ throw StandardException.newException(SQLState.LANG_CURSOR_NOT_UPDATABLE, printableString);\r
+ }\r
+\r
+ ExecCursorTableReference refTab = preStmt.getTargetTable();\r
+ String schemaName = refTab.getSchemaName();\r
+ exposedTableName = makeTableName(null, refTab.getExposedName());\r
+ baseTableName = makeTableName(schemaName,\r
+ refTab.getBaseName());\r
+ SchemaDescriptor tableSchema = null;\r
+ tableSchema = getSchemaDescriptor(refTab.getSchemaName());\r
+\r
+ /*\r
+ ** This will only happen when we are binding against a publication\r
+ ** dictionary w/o the schema we are interested in.\r
+ */\r
+ if (tableSchema == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_SCHEMA_DOES_NOT_EXIST, refTab.getSchemaName());\r
+ }\r
+\r
+ /* Create dependency on target table, in case table not named in \r
+ * positioned update/delete. Make sure we find the table descriptor,\r
+ * we may fail to find it if we are binding a publication.\r
+ */\r
+ TableDescriptor td = getTableDescriptor(refTab.getBaseName(), tableSchema);\r
+\r
+ if (td == null)\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_TABLE_NOT_FOUND, refTab.getBaseName());\r
+ }\r
+\r
+\r
+ /*\r
+ ** Add all the result columns from the target table.\r
+ ** For now, all updatable cursors have all columns\r
+ ** from the target table. In the future, we should\r
+ ** relax this so that the cursor may do a partial\r
+ ** read and then the current of should make sure that\r
+ ** it can go to the base table to get all of the \r
+ ** columns needed by the referencing positioned\r
+ ** DML. In the future, we'll probably need to get\r
+ ** the result columns from preparedStatement and\r
+ ** turn them into an RCL that we can run with.\r
+ */\r
+ resultColumns = (ResultColumnList) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+ ColumnDescriptorList cdl = td.getColumnDescriptorList();\r
+ int cdlSize = cdl.size();\r
+\r
+ for (int index = 0; index < cdlSize; index++)\r
+ {\r
+ /* Build a ResultColumn/BaseColumnNode pair for the column */\r
+ ColumnDescriptor colDesc = (ColumnDescriptor) cdl.elementAt(index);\r
+\r
+ BaseColumnNode bcn = (BaseColumnNode) getNodeFactory().getNode(\r
+ C_NodeTypes.BASE_COLUMN_NODE,\r
+ colDesc.getColumnName(),\r
+ exposedTableName,\r
+ colDesc.getType(),\r
+ getContextManager());\r
+ ResultColumn rc = (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ colDesc,\r
+ bcn,\r
+ getContextManager());\r
+\r
+ /* Build the ResultColumnList to return */\r
+ resultColumns.addResultColumn(rc);\r
+ }\r
+\r
+ /* Assign the tableNumber */\r
+ if (tableNumber == -1) // allow re-bind, in which case use old number\r
+ tableNumber = getCompilerContext().getNextTableNumber();\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Bind the expressions in this ResultSetNode. This means binding the\r
+ * sub-expressions, as well as figuring out what the return type is for\r
+ * each expression.\r
+ *\r
+ * @param fromListParam FromList to use/append to.\r
+ */\r
+ public void bindExpressions(FromList fromListParam)\r
+ {\r
+ /* No expressions to bind for a CurrentOfNode.\r
+ * NOTE - too involved to optimize so that this method\r
+ * doesn't get called, so just do nothing.\r
+ */\r
+ }\r
+\r
+ /**\r
+ * Try to find a ResultColumn in the table represented by this CurrentOfNode\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) \r
+ throws StandardException {\r
+\r
+ ResultColumn resultColumn = null;\r
+ TableName columnsTableName;\r
+\r
+ columnsTableName = columnReference.getTableNameNode();\r
+\r
+ if(columnsTableName != null)\r
+ if(columnsTableName.getSchemaName() == null && correlationName == null)\r
+ columnsTableName.bind(this.getDataDictionary());\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(preStmt!=null, "must have prepared statement");\r
+ }\r
+\r
+ /*\r
+ * We use the base table name of the target table.\r
+ * This is necessary since we will be comparing with the table in\r
+ * the delete or update statement which doesn't have a correlation\r
+ * name. The select for which this column is created might have a\r
+ * correlation name and so we won't find it if we look for exposed names\r
+ * We shouldn't have to worry about multiple table since there should be\r
+ * only one table. Beetle 4419\r
+ */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(baseTableName!=null,"no name on target table");\r
+ }\r
+\r
+ if(baseTableName != null)\r
+ if(baseTableName.getSchemaName() == null && correlationName == null)\r
+ baseTableName.bind(this.getDataDictionary());\r
+\r
+ /*\r
+ * If the column did not specify a name, or the specified name\r
+ * matches the table we're looking at, see whether the column\r
+ * is in this table, and also whether it is in the for update list.\r
+ */\r
+ if (\r
+ (columnsTableName == null) ||\r
+ (columnsTableName.getFullTableName().equals(baseTableName.getFullTableName())) ||\r
+ ((correlationName != null) && correlationName.equals( columnsTableName.getTableName()))\r
+ )\r
+ {\r
+ boolean notfound = false;\r
+\r
+ resultColumn =\r
+ resultColumns.getResultColumn(columnReference.getColumnName());\r
+\r
+ if (resultColumn != null) \r
+ {\r
+ // If we found the ResultColumn, set the ColumnReference's\r
+ // table number accordingly. Note: we used to only set\r
+ // the tableNumber for correlated references (as part of\r
+ // changes for DERBY-171) but inspection of code (esp.\r
+ // the comments in FromList.bindColumnReferences() and\r
+ // the getMatchingColumn() methods on other FromTables)\r
+ // suggests that we should always set the table number\r
+ // if we've found the ResultColumn. So we do that here.\r
+ columnReference.setTableNumber( tableNumber );\r
+\r
+ // If there is a result column, are we really updating it?\r
+ // If so, verify that the column is updatable as well\r
+ notfound = \r
+ (resultColumn.updatableByCursor() &&\r
+ !foundString(\r
+ preStmt.getUpdateColumns(), \r
+ columnReference.getColumnName()));\r
+ }\r
+ else \r
+ {\r
+ notfound = true;\r
+ }\r
+\r
+ if (notfound)\r
+ {\r
+ String printableString = (cursorName == null) ? "" : cursorName;\r
+ throw StandardException.newException(SQLState.LANG_COLUMN_NOT_UPDATABLE_IN_CURSOR, \r
+ columnReference.getColumnName(), printableString);\r
+ }\r
+ }\r
+\r
+ return resultColumn;\r
+ }\r
+\r
+ /**\r
+ * Preprocess a CurrentOfNode. For a CurrentOfNode, this simply means allocating\r
+ * a referenced table map to avoid downstream NullPointerExceptions.\r
+ * NOTE: There are no bits set in the referenced table map.\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
+ /* Generate an empty referenced table map */\r
+ referencedTableMap = new JBitSet(numTables);\r
+ return this;\r
+ }\r
+\r
+ /** \r
+ * Optimize this CurrentOfNode. Nothing to do.\r
+ *\r
+ * @param dataDictionary The DataDictionary to use for optimization\r
+ * @param predicateList The PredicateList to optimize. This should\r
+ * be a single-table predicate with the table\r
+ * the same as the table in this FromTable.\r
+ * @param outerRows The number of outer joining rows\r
+ *\r
+ * @return ResultSetNode The top of the optimized subtree.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public ResultSetNode optimize(DataDictionary dataDictionary,\r
+ PredicateList predicateList,\r
+ double outerRows) \r
+ throws StandardException {\r
+ /* Get an optimizer so we can get a cost */\r
+ Optimizer optimizer = getOptimizer(\r
+ (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ this,\r
+ getContextManager()),\r
+ predicateList,\r
+ dataDictionary,\r
+ (RequiredRowOrdering) null);\r
+\r
+ /* Assume there is no cost associated with fetching the current row */\r
+ bestCostEstimate = optimizer.newCostEstimate();\r
+ bestCostEstimate.setCost(0.0d, outerRows, outerRows);\r
+\r
+ return this;\r
+ }\r
+\r
+ /**\r
+ * Generation on a CurrentOfNode creates a scan on the\r
+ * cursor, CurrentOfResultSet.\r
+ * <p>\r
+ * This routine will generate and return a call of the form:\r
+ * <pre><verbatim>\r
+ ResultSetFactory.getCurrentOfResultSet(cursorName)\r
+ </verbatim></pre>\r
+ *\r
+ * @param acb The ActivationClassBuilder for the class being built\r
+ * @param mb The execute() method to be built\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void generate(ActivationClassBuilder acb,\r
+ MethodBuilder mb)\r
+ throws StandardException {\r
+\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(!statementResultSet, \r
+ "CurrentOfNode not expected to be statement node");\r
+\r
+ /* Get the next ResultSet #, so that we can number this ResultSetNode, its\r
+ * ResultColumnList and ResultSet.\r
+ */\r
+ assignResultSetNumber();\r
+\r
+ mb.pushThis(); // for the putField\r
+\r
+ // The generated java returned by this method is the expression:\r
+ // ResultSetFactory.getCurrentOfResultSet(\r
+ // #cursorName(), this, resultSetNumber)\r
+\r
+ acb.pushGetResultSetFactoryExpression(mb);\r
+\r
+ mb.push(cursorName);\r
+ acb.pushThisAsActivation(mb);\r
+ mb.push(resultSetNumber);\r
+ \r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, "getCurrentOfResultSet",\r
+ ClassName.NoPutResultSet, 3);\r
+\r
+ mb.cast(ClassName.CursorResultSet);\r
+\r
+ // the current of scan generator is what we return\r
+ /* This table is the target of an update or a delete, so we must \r
+ * wrap the Expression up in an assignment expression before \r
+ * returning. Delete or update use the field that is set\r
+ * to calculate the CurrentRowLocation value.\r
+ * NOTE - scanExpress is a ResultSet. We will need to cast it to the\r
+ * appropriate subclass.\r
+ * For example, for a DELETE, instead of returning a call to the \r
+ * ResultSetFactory, we will generate and return:\r
+ * this.SCANRESULTSET = (cast to appropriate ResultSet type) \r
+ * The outer cast back to ResultSet is needed so that\r
+ * we invoke the appropriate method in the call to the ResultSetFactory\r
+ */\r
+\r
+ mb.putField((String) null, acb.getRowLocationScanResultSetName(), ClassName.CursorResultSet);\r
+ mb.cast(ClassName.NoPutResultSet);\r
+\r
+ // add a check at activation reset time to see if the cursor has\r
+ // changed underneath us. Doing it in the constructor allows the\r
+ // compilation to happen \r
+ MethodBuilder rmb = acb.startResetMethod();\r
+\r
+ rmb.pushThis();\r
+ rmb.push(cursorName);\r
+ rmb.push(preStmt.getObjectName());\r
+ rmb.callMethod(VMOpcode.INVOKEVIRTUAL, ClassName.BaseActivation, "checkPositionedStatement",\r
+ "void", 2);\r
+\r
+ rmb.methodReturn();\r
+ rmb.complete();\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
+ public void printSubNodes(int depth) {\r
+ if (SanityManager.DEBUG) {\r
+ super.printSubNodes(depth);\r
+\r
+ printLabel(depth, "cursor: ");\r
+ }\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
+ public String toString() {\r
+ if (SanityManager.DEBUG) {\r
+ return "preparedStatement: " +\r
+ (preStmt == null? "no prepared statement yet\n" :\r
+ preStmt.toString() + "\n")+\r
+ cursorName + "\n" +\r
+ super.toString();\r
+ } else {\r
+ return "";\r
+ }\r
+ }\r
+\r
+ //\r
+ // class interface\r
+ //\r
+\r
+ public String getExposedName()\r
+ {\r
+ return exposedTableName.getFullTableName();\r
+ }\r
+ public TableName getExposedTableName()\r
+ {\r
+ return exposedTableName;\r
+ }\r
+\r
+ public TableName getBaseCursorTargetTableName()\r
+ {\r
+ return baseTableName;\r
+ }\r
+\r
+ public String getCursorName() \r
+ {\r
+ return cursorName;\r
+ }\r
+\r
+ /**\r
+ * Return the CursorNode associated with a positioned update/delete.\r
+ * \r
+ * @return CursorNode The associated CursorNode.\r
+ *\r
+ */\r
+ ExecPreparedStatement getCursorStatement()\r
+ {\r
+ Activation activation = getLanguageConnectionContext().lookupCursorActivation(cursorName);\r
+\r
+ if (activation == null)\r
+ return null;\r
+\r
+ return activation.getPreparedStatement();\r
+ }\r
+\r
+ /**\r
+ * Get the lock mode for this table as the target of an update statement\r
+ * (a delete or update). This is implemented only for base tables and\r
+ * CurrentOfNodes.\r
+ *\r
+ * @see TransactionController\r
+ *\r
+ * @return The lock mode\r
+ */\r
+ public int updateTargetLockMode()\r
+ {\r
+ /* Do row locking for positioned update/delete */\r
+ return TransactionController.MODE_RECORD;\r
+ }\r
+}\r