--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.compile.DeleteNode\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.reference.SQLState;\r
+import org.apache.derby.iapi.error.StandardException;\r
+\r
+import org.apache.derby.iapi.sql.conn.Authorizer;\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.TableDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.GenericDescriptorList;\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;\r
+import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;\r
+\r
+\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.iapi.sql.StatementType;\r
+\r
+import org.apache.derby.iapi.sql.compile.CompilerContext;\r
+import org.apache.derby.iapi.sql.compile.C_NodeTypes;\r
+import org.apache.derby.iapi.reference.ClassName;\r
+\r
+import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;\r
+\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ConstantAction;\r
+import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+\r
+import org.apache.derby.iapi.sql.Activation;\r
+\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+\r
+import org.apache.derby.iapi.services.compiler.MethodBuilder;\r
+\r
+import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+\r
+import org.apache.derby.vti.DeferModification;\r
+\r
+import org.apache.derby.catalog.UUID;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+\r
+import org.apache.derby.impl.sql.compile.ActivationClassBuilder;\r
+\r
+import org.apache.derby.impl.sql.execute.FKInfo;\r
+\r
+import java.lang.reflect.Modifier;\r
+import org.apache.derby.iapi.services.classfile.VMOpcode;\r
+import org.apache.derby.iapi.services.io.FormatableProperties;\r
+import java.util.Vector;\r
+import java.util.Hashtable;\r
+import java.util.Properties;\r
+import org.apache.derby.iapi.sql.compile.NodeFactory;\r
+import org.apache.derby.iapi.util.ReuseFactory;\r
+import org.apache.derby.iapi.sql.depend.Dependent;\r
+import org.apache.derby.iapi.sql.ResultDescription;\r
+import org.apache.derby.iapi.services.compiler.LocalField;\r
+\r
+\r
+/**\r
+ * A DeleteNode represents a DELETE statement. It is the top-level node\r
+ * for the statement.\r
+ *\r
+ * For positioned delete, there may be no from table specified.\r
+ * The from table will be derived from the cursor specification of\r
+ * the named cursor.\r
+ *\r
+ */\r
+\r
+public class DeleteNode extends DMLModStatementNode\r
+{\r
+ /* Column name for the RowLocation column in the ResultSet */\r
+ private static final String COLUMNNAME = "###RowLocationToDelete";\r
+\r
+ /* Filled in by bind. */\r
+ protected boolean deferred;\r
+ protected ExecRow emptyHeapRow;\r
+ protected FromTable targetTable;\r
+ protected FKInfo fkInfo;\r
+ protected FormatableBitSet readColsBitSet;\r
+\r
+ private ConstantAction[] dependentConstantActions;\r
+ private boolean cascadeDelete;\r
+ private StatementNode[] dependentNodes;\r
+\r
+ /**\r
+ * Initializer for a DeleteNode.\r
+ *\r
+ * @param targetTableName The name of the table to delete from\r
+ * @param queryExpression The query expression that will generate\r
+ * the rows to delete from the given table\r
+ */\r
+\r
+ public void init(Object targetTableName,\r
+ Object queryExpression)\r
+ {\r
+ super.init(queryExpression);\r
+ this.targetTableName = (TableName) targetTableName;\r
+ }\r
+\r
+ public String statementToString()\r
+ {\r
+ return "DELETE";\r
+ }\r
+\r
+ /**\r
+ * Bind this DeleteNode. This means looking up tables and columns and\r
+ * getting their types, and figuring out the result types of all\r
+ * expressions, as well as doing view resolution, permissions checking,\r
+ * etc.\r
+ * <p>\r
+ * If any indexes need to be updated, we add all the columns in the\r
+ * base table to the result column list, so that we can use the column\r
+ * values as look-up keys for the index rows to be deleted. Binding a\r
+ * delete will also massage the tree so that the ResultSetNode has \r
+ * column containing the RowLocation of the base row.\r
+ *\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+\r
+ public void bindStatement() throws StandardException\r
+ {\r
+ // We just need select privilege on the where clause tables\r
+ getCompilerContext().pushCurrentPrivType( Authorizer.SELECT_PRIV);\r
+ try\r
+ {\r
+ FromList fromList = (FromList) getNodeFactory().getNode(\r
+ C_NodeTypes.FROM_LIST,\r
+ getNodeFactory().doJoinOrderOptimization(),\r
+ getContextManager());\r
+ ResultColumn rowLocationColumn = null;\r
+ CurrentRowLocationNode rowLocationNode;\r
+ TableName cursorTargetTableName = null;\r
+ CurrentOfNode currentOfNode = null;\r
+ \r
+ DataDictionary dataDictionary = getDataDictionary();\r
+ super.bindTables(dataDictionary);\r
+\r
+ // wait to bind named target table until the underlying\r
+ // cursor is bound, so that we can get it from the\r
+ // cursor if this is a positioned delete.\r
+\r
+ // for positioned delete, get the cursor's target table.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(resultSet != null && resultSet instanceof SelectNode,\r
+ "Delete must have a select result set");\r
+\r
+ SelectNode sel;\r
+ sel = (SelectNode)resultSet;\r
+ targetTable = (FromTable) sel.fromList.elementAt(0);\r
+ if (targetTable instanceof CurrentOfNode)\r
+ {\r
+ currentOfNode = (CurrentOfNode) targetTable;\r
+\r
+ cursorTargetTableName = currentOfNode.getBaseCursorTargetTableName();\r
+ // instead of an assert, we might say the cursor is not updatable.\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(cursorTargetTableName != null);\r
+ }\r
+\r
+ if (targetTable instanceof FromVTI)\r
+ {\r
+ targetVTI = (FromVTI) targetTable;\r
+ targetVTI.setTarget();\r
+ }\r
+ else\r
+ {\r
+ // positioned delete can leave off the target table.\r
+ // we get it from the cursor supplying the position.\r
+ if (targetTableName == null)\r
+ {\r
+ // verify we have current of\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT(cursorTargetTableName!=null);\r
+\r
+ targetTableName = cursorTargetTableName;\r
+ }\r
+ // for positioned delete, we need to verify that\r
+ // the named table is the same as the cursor's target (base table name).\r
+ else if (cursorTargetTableName != null)\r
+ {\r
+ // this match requires that the named table in the delete\r
+ // be the same as a base name in the cursor.\r
+ if ( !targetTableName.equals(cursorTargetTableName))\r
+ {\r
+ throw StandardException.newException(SQLState.LANG_CURSOR_DELETE_MISMATCH, \r
+ targetTableName,\r
+ currentOfNode.getCursorName());\r
+ }\r
+ }\r
+ }\r
+ \r
+ // descriptor must exist, tables already bound.\r
+ verifyTargetTable();\r
+\r
+ /* Generate a select list for the ResultSetNode - CurrentRowLocation(). */\r
+ if (SanityManager.DEBUG)\r
+ SanityManager.ASSERT((resultSet.resultColumns == null),\r
+ "resultColumns is expected to be null until bind time");\r
+\r
+\r
+ if (targetTable instanceof FromVTI)\r
+ {\r
+ getResultColumnList();\r
+ resultColumnList = targetTable.getResultColumnsForList(null, \r
+ resultColumnList, null);\r
+\r
+ /* Set the new result column list in the result set */\r
+ resultSet.setResultColumns(resultColumnList);\r
+ }\r
+ else\r
+ {\r
+ /*\r
+ ** Start off assuming no columns from the base table\r
+ ** are needed in the rcl.\r
+ */\r
+\r
+ resultColumnList = new ResultColumnList();\r
+\r
+ FromBaseTable fbt = getResultColumnList(resultColumnList);\r
+\r
+ readColsBitSet = getReadMap(dataDictionary,\r
+ targetTableDescriptor);\r
+\r
+ resultColumnList = fbt.addColsToList(resultColumnList, readColsBitSet);\r
+\r
+ /*\r
+ ** If all bits are set, then behave as if we chose all\r
+ ** in the first place\r
+ */\r
+ int i = 1;\r
+ int size = targetTableDescriptor.getMaxColumnID();\r
+ for (; i <= size; i++)\r
+ {\r
+ if (!readColsBitSet.get(i))\r
+ {\r
+ break;\r
+ }\r
+ }\r
+\r
+ if (i > size)\r
+ {\r
+ readColsBitSet = null;\r
+ }\r
+\r
+ /*\r
+ ** Construct an empty heap row for use in our constant action.\r
+ */\r
+ emptyHeapRow = targetTableDescriptor.getEmptyExecRow();\r
+\r
+ /* Generate the RowLocation column */\r
+ rowLocationNode = (CurrentRowLocationNode) getNodeFactory().getNode(\r
+ C_NodeTypes.CURRENT_ROW_LOCATION_NODE,\r
+ getContextManager());\r
+ rowLocationColumn =\r
+ (ResultColumn) getNodeFactory().getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ COLUMNNAME,\r
+ rowLocationNode,\r
+ getContextManager());\r
+ rowLocationColumn.markGenerated();\r
+\r
+ /* Append to the ResultColumnList */\r
+ resultColumnList.addResultColumn(rowLocationColumn);\r
+\r
+ /* Force the added columns to take on the table's correlation name, if any */\r
+ correlateAddedColumns( resultColumnList, targetTable );\r
+ \r
+ /* Set the new result column list in the result set */\r
+ resultSet.setResultColumns(resultColumnList);\r
+ }\r
+\r
+ /* Bind the expressions before the ResultColumns are bound */\r
+ super.bindExpressions();\r
+\r
+ /* Bind untyped nulls directly under the result columns */\r
+ resultSet.getResultColumns().\r
+ bindUntypedNullsToResultColumns(resultColumnList);\r
+\r
+ if (! (targetTable instanceof FromVTI))\r
+ {\r
+ /* Bind the new ResultColumn */\r
+ rowLocationColumn.bindResultColumnToExpression();\r
+\r
+ bindConstraints(dataDictionary,\r
+ getNodeFactory(),\r
+ targetTableDescriptor,\r
+ null,\r
+ resultColumnList,\r
+ (int[]) null,\r
+ readColsBitSet,\r
+ false,\r
+ true); /* we alway include triggers in core language */\r
+\r
+ /* If the target table is also a source table, then\r
+ * the delete will have to be in deferred mode\r
+ * For deletes, this means that the target table appears in a\r
+ * subquery. Also, self-referencing foreign key deletes\r
+ * are deferred. And triggers cause the delete to be deferred.\r
+ */\r
+ if (resultSet.subqueryReferencesTarget(\r
+ targetTableDescriptor.getName(), true) ||\r
+ requiresDeferredProcessing())\r
+ {\r
+ deferred = true;\r
+ }\r
+ }\r
+ else\r
+ {\r
+ deferred = VTIDeferModPolicy.deferIt( DeferModification.DELETE_STATEMENT,\r
+ targetVTI,\r
+ null,\r
+ sel.getWhereClause());\r
+ }\r
+ sel = null; // done with sel\r
+\r
+ /* Verify that all underlying ResultSets reclaimed their FromList */\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(fromList.size() == 0,\r
+ "fromList.size() is expected to be 0, not " +\r
+ fromList.size() +\r
+ " on return from RS.bindExpressions()");\r
+ }\r
+\r
+ //In case of cascade delete , create nodes for\r
+ //the ref action dependent tables and bind them.\r
+ if(fkTableNames != null)\r
+ {\r
+ String currentTargetTableName = targetTableDescriptor.getSchemaName() +\r
+ "." + targetTableDescriptor.getName();\r
+\r
+ if(!isDependentTable){\r
+ //graph node\r
+ graphHashTable = new Hashtable();\r
+ }\r
+\r
+ /*Check whether the current tatget is already been explored.\r
+ *If we are seeing the same table name which we binded earlier\r
+ *means we have cyclic references.\r
+ */\r
+ if(!graphHashTable.containsKey(currentTargetTableName))\r
+ {\r
+ cascadeDelete = true;\r
+ int noDependents = fkTableNames.length;\r
+ dependentNodes = new StatementNode[noDependents];\r
+ graphHashTable.put(currentTargetTableName, new Integer(noDependents));\r
+ for(int i =0 ; i < noDependents ; i ++)\r
+ {\r
+ dependentNodes[i] = getDependentTableNode(fkTableNames[i],\r
+ fkRefActions[i],\r
+ fkColDescriptors[i]);\r
+ dependentNodes[i].bindStatement();\r
+ }\r
+ }\r
+ }\r
+ else\r
+ {\r
+ //case where current dependent table does not have dependent tables\r
+ if(isDependentTable)\r
+ {\r
+ String currentTargetTableName = targetTableDescriptor.getSchemaName()\r
+ + "." + targetTableDescriptor.getName();\r
+ graphHashTable.put(currentTargetTableName, new Integer(0));\r
+\r
+ }\r
+ }\r
+ if (isPrivilegeCollectionRequired())\r
+ {\r
+ getCompilerContext().pushCurrentPrivType( getPrivType());\r
+ getCompilerContext().addRequiredTablePriv( targetTableDescriptor);\r
+ getCompilerContext().popCurrentPrivType();\r
+ }\r
+ }\r
+ finally\r
+ {\r
+ getCompilerContext().popCurrentPrivType();\r
+ }\r
+ } // end of bind\r
+\r
+ int getPrivType()\r
+ {\r
+ return Authorizer.DELETE_PRIV;\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
+ //If delete table is on a SESSION schema table, then return true. \r
+ return resultSet.referencesSessionSchema();\r
+ }\r
+\r
+ /**\r
+ * Compile constants that Execution will use\r
+ *\r
+ * @exception StandardException Thrown on failure\r
+ */\r
+ public ConstantAction makeConstantAction() throws StandardException\r
+ {\r
+\r
+ /* Different constant actions for base tables and updatable VTIs */\r
+ if (targetTableDescriptor != null)\r
+ {\r
+ // Base table\r
+ int lockMode = resultSet.updateTargetLockMode();\r
+ long heapConglomId = targetTableDescriptor.getHeapConglomerateId();\r
+ TransactionController tc = getLanguageConnectionContext().getTransactionCompile();\r
+ StaticCompiledOpenConglomInfo[] indexSCOCIs = \r
+ new StaticCompiledOpenConglomInfo[indexConglomerateNumbers.length];\r
+\r
+ for (int index = 0; index < indexSCOCIs.length; index++)\r
+ {\r
+ indexSCOCIs[index] = tc.getStaticCompiledConglomInfo(indexConglomerateNumbers[index]);\r
+ }\r
+\r
+ /*\r
+ ** Do table locking if the table's lock granularity is\r
+ ** set to table.\r
+ */\r
+ if (targetTableDescriptor.getLockGranularity() == TableDescriptor.TABLE_LOCK_GRANULARITY)\r
+ {\r
+ lockMode = TransactionController.MODE_TABLE;\r
+ }\r
+\r
+ ResultDescription resultDescription = null;\r
+ if(isDependentTable)\r
+ {\r
+ //triggers need the result description ,\r
+ //dependent tables don't have a source from generation time\r
+ //to get the result description\r
+ resultDescription = makeResultDescription();\r
+ }\r
+\r
+\r
+ return getGenericConstantActionFactory().getDeleteConstantAction\r
+ ( heapConglomId,\r
+ targetTableDescriptor.getTableType(),\r
+ tc.getStaticCompiledConglomInfo(heapConglomId),\r
+ indicesToMaintain,\r
+ indexConglomerateNumbers,\r
+ indexSCOCIs,\r
+ emptyHeapRow,\r
+ deferred,\r
+ false,\r
+ targetTableDescriptor.getUUID(),\r
+ lockMode,\r
+ null, null, null, 0, null, null, \r
+ resultDescription,\r
+ getFKInfo(), \r
+ getTriggerInfo(), \r
+ (readColsBitSet == null) ? (FormatableBitSet)null : new FormatableBitSet(readColsBitSet),\r
+ getReadColMap(targetTableDescriptor.getNumberOfColumns(),readColsBitSet),\r
+ resultColumnList.getStreamStorableColIds(targetTableDescriptor.getNumberOfColumns()),\r
+ (readColsBitSet == null) ? \r
+ targetTableDescriptor.getNumberOfColumns() :\r
+ readColsBitSet.getNumBitsSet(), \r
+ (UUID) null,\r
+ resultSet.isOneRowResultSet(),\r
+ dependentConstantActions);\r
+ }\r
+ else\r
+ {\r
+ /* Return constant action for VTI\r
+ * NOTE: ConstantAction responsible for preserving instantiated\r
+ * VTIs for in-memory queries and for only preserving VTIs\r
+ * that implement Serializable for SPSs.\r
+ */\r
+ return getGenericConstantActionFactory().getUpdatableVTIConstantAction( DeferModification.DELETE_STATEMENT,\r
+ deferred);\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Code generation for delete.\r
+ * The generated code will contain:\r
+ * o A static member for the (xxx)ResultSet with the RowLocations\r
+ * o The static member will be assigned the appropriate ResultSet within\r
+ * the nested calls to get the ResultSets. (The appropriate cast to the\r
+ * (xxx)ResultSet will be generated.)\r
+ * o The CurrentRowLocation() in SelectNode's select list will generate\r
+ * a new method for returning the RowLocation as well as a call to\r
+ * that method which will be stuffed in the call to the \r
+ * ProjectRestrictResultSet.\r
+ * o In case of referential actions, this function generate an\r
+ * array of resultsets on its dependent tables.\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
+\r
+ // If the DML is on the temporary table, generate the code to\r
+ // mark temporary table as modified in the current UOW. After\r
+ // DERBY-827 this must be done in execute() since\r
+ // fillResultSet() will only be called once.\r
+ generateCodeForTemporaryTable(acb, acb.getExecuteMethod());\r
+\r
+ /* generate the parameters */\r
+ if(!isDependentTable)\r
+ generateParameterValueSet(acb);\r
+\r
+ acb.pushGetResultSetFactoryExpression(mb); \r
+ acb.newRowLocationScanResultSetName();\r
+ resultSet.generate(acb, mb); // arg 1\r
+\r
+ String resultSetGetter;\r
+ int argCount;\r
+ String parentResultSetId;\r
+\r
+ // Base table\r
+ if (targetTableDescriptor != null)\r
+ {\r
+ /* Create the declaration for the scan ResultSet which generates the\r
+ * RowLocations to be deleted.\r
+ * Note that the field cannot be static because there\r
+ * can be multiple activations of the same activation class,\r
+ * and they can't share this field. Only exprN fields can\r
+ * be shared (or, more generally, read-only fields).\r
+ * RESOLVE - Need to deal with the type of the field.\r
+ */\r
+\r
+ acb.newFieldDeclaration(Modifier.PRIVATE, \r
+ ClassName.CursorResultSet, \r
+ acb.getRowLocationScanResultSetName());\r
+\r
+ if(cascadeDelete || isDependentTable)\r
+ {\r
+ resultSetGetter = "getDeleteCascadeResultSet";\r
+ argCount = 4;\r
+ } \r
+ else\r
+ {\r
+ resultSetGetter = "getDeleteResultSet";\r
+ argCount = 1;\r
+ }\r
+ \r
+ } else {\r
+ argCount = 1;\r
+ resultSetGetter = "getDeleteVTIResultSet";\r
+ }\r
+\r
+ if(isDependentTable)\r
+ {\r
+ mb.push(acb.addItem(makeConstantAction()));\r
+ \r
+ }else\r
+ {\r
+ if(cascadeDelete)\r
+ {\r
+ mb.push(-1); //root table.\r
+ }\r
+ } \r
+\r
+ String resultSetArrayType = ClassName.ResultSet + "[]";\r
+ if(cascadeDelete)\r
+ {\r
+ parentResultSetId = targetTableDescriptor.getSchemaName() +\r
+ "." + targetTableDescriptor.getName();\r
+ // Generate the code to build the array\r
+ LocalField arrayField =\r
+ acb.newFieldDeclaration(Modifier.PRIVATE, resultSetArrayType);\r
+ mb.pushNewArray(ClassName.ResultSet, dependentNodes.length); // new ResultSet[size]\r
+ mb.setField(arrayField);\r
+ for(int index=0 ; index < dependentNodes.length ; index++)\r
+ {\r
+\r
+ dependentNodes[index].setRefActionInfo(fkIndexConglomNumbers[index],\r
+ fkColArrays[index],\r
+ parentResultSetId,\r
+ true);\r
+ mb.getField(arrayField); // first arg (resultset array reference)\r
+ /*beetle:5360 : if too many statements are added to a method, \r
+ *size of method can hit 65k limit, which will\r
+ *lead to the class format errors at load time.\r
+ *To avoid this problem, when number of statements added \r
+ *to a method is > 2048, remaing statements are added to a new function\r
+ *and called from the function which created the function.\r
+ *See Beetle 5135 or 4293 for further details on this type of problem.\r
+ */\r
+ if(mb.statementNumHitLimit(10))\r
+ {\r
+ MethodBuilder dmb = acb.newGeneratedFun(ClassName.ResultSet, Modifier.PRIVATE);\r
+ dependentNodes[index].generate(acb,dmb); //generates the resultset expression\r
+ dmb.methodReturn();\r
+ dmb.complete();\r
+ /* Generate the call to the new method */\r
+ mb.pushThis(); \r
+ //second arg will be generated by this call\r
+ mb.callMethod(VMOpcode.INVOKEVIRTUAL, (String) null, dmb.getName(), ClassName.ResultSet, 0);\r
+ }else\r
+ {\r
+ dependentNodes[index].generate(acb,mb); //generates the resultset expression\r
+ }\r
+\r
+ mb.setArrayElement(index);\r
+ } \r
+ mb.getField(arrayField); // fourth argument - array reference\r
+ }\r
+ else\r
+ {\r
+ if(isDependentTable)\r
+ {\r
+ mb.pushNull(resultSetArrayType); //No dependent tables for this table\r
+ }\r
+ }\r
+\r
+\r
+ if(cascadeDelete || isDependentTable)\r
+ {\r
+ parentResultSetId = targetTableDescriptor.getSchemaName() +\r
+ "." + targetTableDescriptor.getName();\r
+ mb.push(parentResultSetId);\r
+\r
+ }\r
+ mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null, resultSetGetter, ClassName.ResultSet, argCount);\r
+\r
+\r
+ if(!isDependentTable && cascadeDelete)\r
+ {\r
+ int numResultSets = acb.getRowCount();\r
+ if(numResultSets > 0)\r
+ {\r
+ //generate activation.raParentResultSets = new NoPutResultSet[size]\r
+ MethodBuilder constructor = acb.getConstructor();\r
+ constructor.pushThis();\r
+ constructor.pushNewArray(ClassName.CursorResultSet, numResultSets);\r
+ constructor.putField(ClassName.BaseActivation,\r
+ "raParentResultSets",\r
+ ClassName.CursorResultSet + "[]");\r
+ constructor.endStatement();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Return the type of statement, something from\r
+ * StatementType.\r
+ *\r
+ * @return the type of statement\r
+ */\r
+ protected final int getStatementType()\r
+ {\r
+ return StatementType.DELETE;\r
+ }\r
+\r
+\r
+ /**\r
+ * Gets the map of all columns which must be read out of the base table.\r
+ * These are the columns needed to:\r
+ *\r
+ * o maintain indices\r
+ * o maintain foreign keys\r
+ *\r
+ * The returned map is a FormatableBitSet with 1 bit for each column in the\r
+ * table plus an extra, unsued 0-bit. If a 1-based column id must\r
+ * be read from the base table, then the corresponding 1-based bit\r
+ * is turned ON in the returned FormatableBitSet.\r
+ *\r
+ * @param dd the data dictionary to look in\r
+ * @param baseTable the base table descriptor\r
+ *\r
+ * @return a FormatableBitSet of columns to be read out of the base table\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public FormatableBitSet getReadMap\r
+ (\r
+ DataDictionary dd,\r
+ TableDescriptor baseTable\r
+ )\r
+ throws StandardException\r
+ {\r
+ boolean[] needsDeferredProcessing = new boolean[1];\r
+ needsDeferredProcessing[0] = requiresDeferredProcessing();\r
+\r
+ Vector conglomVector = new Vector();\r
+ relevantTriggers = new GenericDescriptorList();\r
+\r
+ FormatableBitSet columnMap = DeleteNode.getDeleteReadMap(baseTable, conglomVector, relevantTriggers, needsDeferredProcessing );\r
+\r
+ markAffectedIndexes( conglomVector );\r
+\r
+ adjustDeferredFlag( needsDeferredProcessing[0] );\r
+\r
+ return columnMap;\r
+ }\r
+\r
+ /**\r
+ * In case of referential actions, we require to perform\r
+ * DML (UPDATE or DELETE) on the dependent tables. \r
+ * Following function returns the DML Node for the dependent table.\r
+ */\r
+ private StatementNode getDependentTableNode(String tableName, int refAction,\r
+ ColumnDescriptorList cdl) throws StandardException\r
+ {\r
+ StatementNode node=null;\r
+\r
+ int index = tableName.indexOf('.');\r
+ String schemaName = tableName.substring(0 , index);\r
+ String tName = tableName.substring(index+1);\r
+ if(refAction == StatementType.RA_CASCADE)\r
+ {\r
+ node = getEmptyDeleteNode(schemaName , tName);\r
+ ((DeleteNode)node).isDependentTable = true;\r
+ ((DeleteNode)node).graphHashTable = graphHashTable;\r
+ }\r
+\r
+ if(refAction == StatementType.RA_SETNULL)\r
+ {\r
+ node = getEmptyUpdateNode(schemaName , tName, cdl);\r
+ ((UpdateNode)node).isDependentTable = true;\r
+ ((UpdateNode)node).graphHashTable = graphHashTable;\r
+ }\r
+\r
+ return node;\r
+ }\r
+\r
+\r
+ private StatementNode getEmptyDeleteNode(String schemaName, String targetTableName)\r
+ throws StandardException\r
+ {\r
+\r
+ ValueNode whereClause = null;\r
+\r
+ TableName tableName = new TableName();\r
+ tableName.init(schemaName , targetTableName);\r
+\r
+ NodeFactory nodeFactory = getNodeFactory();\r
+ FromList fromList = (FromList) nodeFactory.getNode(C_NodeTypes.FROM_LIST, getContextManager());\r
+ FromTable fromTable = (FromTable) nodeFactory.getNode(\r
+ C_NodeTypes.FROM_BASE_TABLE,\r
+ tableName,\r
+ null,\r
+ ReuseFactory.getInteger(FromBaseTable.DELETE),\r
+ null,\r
+ getContextManager());\r
+\r
+ //we would like to use references index & table scan instead of \r
+ //what optimizer says for the dependent table scan.\r
+ Properties targetProperties = new FormatableProperties();\r
+ targetProperties.put("index", "null");\r
+ ((FromBaseTable) fromTable).setTableProperties(targetProperties);\r
+\r
+ fromList.addFromTable(fromTable);\r
+ SelectNode resultSet = (SelectNode) nodeFactory.getNode(\r
+ C_NodeTypes.SELECT_NODE,\r
+ null,\r
+ null, /* AGGREGATE list */\r
+ fromList, /* FROM list */\r
+ whereClause, /* WHERE clause */\r
+ null, /* GROUP BY list */\r
+ null, /* having clause */\r
+ getContextManager());\r
+\r
+ return (StatementNode) nodeFactory.getNode(\r
+ C_NodeTypes.DELETE_NODE,\r
+ tableName,\r
+ resultSet,\r
+ getContextManager());\r
+\r
+ }\r
+\r
+\r
+ \r
+ private StatementNode getEmptyUpdateNode(String schemaName, \r
+ String targetTableName,\r
+ ColumnDescriptorList cdl)\r
+ throws StandardException\r
+ {\r
+\r
+ ValueNode whereClause = null;\r
+\r
+ TableName tableName = new TableName();\r
+ tableName.init(schemaName , targetTableName);\r
+\r
+ NodeFactory nodeFactory = getNodeFactory();\r
+ FromList fromList = (FromList) nodeFactory.getNode(C_NodeTypes.FROM_LIST, getContextManager());\r
+ FromTable fromTable = (FromTable) nodeFactory.getNode(\r
+ C_NodeTypes.FROM_BASE_TABLE,\r
+ tableName,\r
+ null,\r
+ ReuseFactory.getInteger(FromBaseTable.DELETE),\r
+ null,\r
+ getContextManager());\r
+\r
+\r
+ //we would like to use references index & table scan instead of \r
+ //what optimizer says for the dependent table scan.\r
+ Properties targetProperties = new FormatableProperties();\r
+ targetProperties.put("index", "null");\r
+ ((FromBaseTable) fromTable).setTableProperties(targetProperties);\r
+\r
+ fromList.addFromTable(fromTable);\r
+\r
+ SelectNode resultSet = (SelectNode) nodeFactory.getNode(\r
+ C_NodeTypes.SELECT_NODE,\r
+ getSetClause(tableName, cdl),\r
+ null, /* AGGREGATE list */\r
+ fromList, /* FROM list */\r
+ whereClause, /* WHERE clause */\r
+ null, /* GROUP BY list */\r
+ null, /* having clause */\r
+ getContextManager());\r
+\r
+ return (StatementNode) nodeFactory.getNode(\r
+ C_NodeTypes.UPDATE_NODE,\r
+ tableName,\r
+ resultSet,\r
+ getContextManager());\r
+\r
+ }\r
+\r
+\r
+ \r
+ private ResultColumnList getSetClause(TableName tabName,\r
+ ColumnDescriptorList cdl)\r
+ throws StandardException\r
+ {\r
+ ResultColumn resultColumn;\r
+ ValueNode valueNode;\r
+\r
+ NodeFactory nodeFactory = getNodeFactory();\r
+ ResultColumnList columnList = (ResultColumnList) nodeFactory.getNode(\r
+ C_NodeTypes.RESULT_COLUMN_LIST,\r
+ getContextManager());\r
+\r
+ valueNode = (ValueNode) nodeFactory.getNode(C_NodeTypes.UNTYPED_NULL_CONSTANT_NODE,\r
+ getContextManager());\r
+ for(int index =0 ; index < cdl.size() ; index++)\r
+ {\r
+ ColumnDescriptor cd = (ColumnDescriptor) cdl.elementAt(index);\r
+ //only columns that are nullable need to be set to 'null' for ON\r
+ //DELETE SET NULL\r
+ if((cd.getType()).isNullable())\r
+ {\r
+ resultColumn = (ResultColumn) nodeFactory.getNode(\r
+ C_NodeTypes.RESULT_COLUMN,\r
+ cd,\r
+ valueNode,\r
+ getContextManager());\r
+\r
+ columnList.addResultColumn(resultColumn);\r
+ }\r
+ }\r
+ return columnList;\r
+ }\r
+\r
+\r
+ public void optimizeStatement() throws StandardException\r
+ {\r
+ if(cascadeDelete)\r
+ {\r
+ for(int index=0 ; index < dependentNodes.length ; index++)\r
+ {\r
+ dependentNodes[index].optimizeStatement();\r
+ }\r
+ }\r
+\r
+ super.optimizeStatement();\r
+ }\r
+\r
+ /**\r
+ * Builds a bitmap of all columns which should be read from the\r
+ * Store in order to satisfy an DELETE statement.\r
+ *\r
+ *\r
+ * 1) finds all indices on this table\r
+ * 2) adds the index columns to a bitmap of affected columns\r
+ * 3) adds the index descriptors to a list of conglomerate\r
+ * descriptors.\r
+ * 4) finds all DELETE triggers on the table\r
+ * 5) if there are any DELETE triggers, marks all columns in the bitmap\r
+ * 6) adds the triggers to an evolving list of triggers\r
+ *\r
+ * @param conglomVector OUT: vector of affected indices\r
+ * @param relevantTriggers IN/OUT. Passed in as an empty list. Filled in as we go.\r
+ * @param needsDeferredProcessing IN/OUT. true if the statement already needs\r
+ * deferred processing. set while evaluating this\r
+ * routine if a trigger requires\r
+ * deferred processing\r
+ *\r
+ * @return a FormatableBitSet of columns to be read out of the base table\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ private static FormatableBitSet getDeleteReadMap\r
+ (\r
+ TableDescriptor baseTable,\r
+ Vector conglomVector,\r
+ GenericDescriptorList relevantTriggers,\r
+ boolean[] needsDeferredProcessing\r
+ )\r
+ throws StandardException\r
+ {\r
+ int columnCount = baseTable.getMaxColumnID();\r
+ FormatableBitSet columnMap = new FormatableBitSet(columnCount + 1);\r
+\r
+ /* \r
+ ** Get a list of the indexes that need to be \r
+ ** updated. ColumnMap contains all indexed\r
+ ** columns where 1 or more columns in the index\r
+ ** are going to be modified.\r
+ **\r
+ ** Notice that we don't need to add constraint\r
+ ** columns. This is because we add all key constraints\r
+ ** (e.g. foreign keys) as a side effect of adding their\r
+ ** indexes above. And we don't need to deal with\r
+ ** check constraints on a delete.\r
+ **\r
+ ** Adding indexes also takes care of the replication \r
+ ** requirement of having the primary key.\r
+ */\r
+ DMLModStatementNode.getXAffectedIndexes(baseTable, null, columnMap, conglomVector );\r
+\r
+ /*\r
+ ** If we have any triggers, then get all the columns\r
+ ** because we don't know what the user will ultimately\r
+ ** reference.\r
+ */\r
+ baseTable.getAllRelevantTriggers( StatementType.DELETE, (int[])null, relevantTriggers );\r
+ if ( relevantTriggers.size() > 0 ) { needsDeferredProcessing[0] = true; }\r
+\r
+ if (relevantTriggers.size() > 0)\r
+ {\r
+ for (int i = 1; i <= columnCount; i++)\r
+ {\r
+ columnMap.set(i);\r
+ }\r
+ }\r
+\r
+ return columnMap;\r
+ }\r
+ \r
+ /*\r
+ * Force column references (particularly those added by the compiler)\r
+ * to use the correlation name on the base table, if any.\r
+ */\r
+ private void correlateAddedColumns( ResultColumnList rcl, FromTable fromTable )\r
+ throws StandardException\r
+ {\r
+ String correlationName = fromTable.getCorrelationName();\r
+\r
+ if ( correlationName == null ) { return; }\r
+\r
+ TableName correlationNameNode = makeTableName( null, correlationName );\r
+ int count = rcl.size();\r
+\r
+ for ( int i = 0; i < count; i++ )\r
+ {\r
+ ResultColumn column = (ResultColumn) rcl.elementAt( i );\r
+\r
+ ValueNode expression = column.getExpression();\r
+\r
+ if ( (expression != null) && (expression instanceof ColumnReference) )\r
+ {\r
+ ColumnReference reference = (ColumnReference) expression;\r
+ \r
+ reference.setTableNameNode( correlationNameNode );\r
+ }\r
+ }\r
+ \r
+ }\r
+ \r
+}\r