--- /dev/null
+/*\r
+\r
+ Derby - Class org.apache.derby.impl.sql.execute.DeleteResultSet\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.execute;\r
+\r
+import java.util.Properties;\r
+\r
+import org.apache.derby.iapi.db.TriggerExecutionContext;\r
+import org.apache.derby.iapi.error.StandardException;\r
+import org.apache.derby.iapi.reference.SQLState;\r
+import org.apache.derby.iapi.services.io.FormatableBitSet;\r
+import org.apache.derby.iapi.services.sanity.SanityManager;\r
+import org.apache.derby.iapi.sql.Activation;\r
+import org.apache.derby.iapi.sql.ResultDescription;\r
+import org.apache.derby.iapi.sql.ResultSet;\r
+import org.apache.derby.iapi.sql.execute.ConstantAction;\r
+import org.apache.derby.iapi.sql.execute.CursorResultSet;\r
+import org.apache.derby.iapi.sql.execute.ExecRow;\r
+import org.apache.derby.iapi.sql.execute.NoPutResultSet;\r
+import org.apache.derby.iapi.sql.execute.RowChanger;\r
+import org.apache.derby.iapi.store.access.ConglomerateController;\r
+import org.apache.derby.iapi.store.access.TransactionController;\r
+import org.apache.derby.iapi.types.DataValueDescriptor;\r
+import org.apache.derby.iapi.types.RowLocation;\r
+\r
+/**\r
+ * Delete the rows from the specified\r
+ * base table. This will cause constraints to be checked\r
+ * and triggers to be executed based on the c's and t's\r
+ * compiled into the insert plan.\r
+ */\r
+class DeleteResultSet extends DMLWriteResultSet\r
+{\r
+ private TransactionController tc;\r
+ DeleteConstantAction constants;\r
+ protected ResultDescription resultDescription;\r
+ protected NoPutResultSet source;\r
+ NoPutResultSet savedSource;\r
+ int numIndexes;\r
+ protected RowChanger rc;\r
+ private ExecRow row;\r
+\r
+ protected ConglomerateController deferredBaseCC;\r
+\r
+ protected TemporaryRowHolderImpl rowHolder;\r
+\r
+ private int numOpens; // number of opens w/o a close\r
+ private boolean firstExecute;\r
+\r
+ // cached across opens()s\r
+ private FormatableBitSet baseRowReadList; \r
+ private int rlColumnNumber;\r
+ protected FKInfo[] fkInfoArray;\r
+ private TriggerInfo triggerInfo;\r
+ private RISetChecker fkChecker;\r
+ private TriggerEventActivator triggerActivator;\r
+ private boolean noTriggersOrFks;\r
+\r
+ ExecRow deferredSparseRow; \r
+ ExecRow deferredBaseRow;\r
+ int lockMode; \r
+ protected boolean cascadeDelete;\r
+ ExecRow deferredRLRow = null;\r
+ int numberOfBaseColumns = 0;\r
+\r
+ /**\r
+ * Returns the description of the deleted rows.\r
+ * REVISIT: Do we want this to return NULL instead?\r
+ */\r
+ public ResultDescription getResultDescription()\r
+ {\r
+ return resultDescription;\r
+ }\r
+\r
+ /*\r
+ * class interface\r
+ *\r
+ */\r
+ DeleteResultSet\r
+ (\r
+ NoPutResultSet source,\r
+ Activation activation\r
+ )\r
+ throws StandardException\r
+ {\r
+ this(source, activation.getConstantAction(), activation);\r
+ }\r
+ /**\r
+ * REMIND: At present this takes just the conglomerate id\r
+ * of the table. We can expect this to expand to include\r
+ * passing information about triggers, constraints, and\r
+ * any additional conglomerates on the underlying table\r
+ * for access methods.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ DeleteResultSet\r
+ (\r
+ NoPutResultSet source,\r
+ ConstantAction passedInConstantAction,\r
+ Activation activation\r
+ )\r
+ throws StandardException\r
+ {\r
+ super(activation, passedInConstantAction);\r
+ this.source = source;\r
+\r
+ tc = activation.getTransactionController();\r
+ constants = (DeleteConstantAction) constantAction;\r
+ fkInfoArray = constants.getFKInfo();\r
+ triggerInfo = constants.getTriggerInfo();\r
+ noTriggersOrFks = ((fkInfoArray == null) && (triggerInfo == null));\r
+ baseRowReadList = constants.getBaseRowReadList();\r
+ if(source != null)\r
+ resultDescription = source.getResultDescription();\r
+ else\r
+ resultDescription = constants.resultDescription;\r
+\r
+ }\r
+\r
+ /**\r
+ @exception StandardException Standard Derby error policy\r
+ */\r
+ public void open() throws StandardException\r
+ {\r
+\r
+ setup();\r
+ boolean rowsFound = collectAffectedRows(); //this call also deletes rows , if not deferred\r
+ if (! rowsFound)\r
+ {\r
+ activation.addWarning(\r
+ StandardException.newWarning(\r
+ SQLState.LANG_NO_ROW_FOUND));\r
+ }\r
+\r
+ /*\r
+ ** If the delete is deferred, scan the temporary conglomerate to\r
+ ** get the RowLocations of the rows to be deleted. Re-fetch the\r
+ ** rows and delete them using the RowChanger.\r
+ */\r
+ if (constants.deferred)\r
+ {\r
+ runFkChecker(true); //check for only RESTRICT referential action rule violations\r
+ fireBeforeTriggers();\r
+ deleteDeferredRows();\r
+ runFkChecker(false); //check for all constraint violations\r
+ // apply \r
+ rc.finish();\r
+ fireAfterTriggers();\r
+ }\r
+\r
+ \r
+ /* Cache query plan text for source, before it gets blown away */\r
+ if (lcc.getRunTimeStatisticsMode())\r
+ {\r
+ /* savedSource nulled after run time statistics generation */\r
+ savedSource = source;\r
+ }\r
+\r
+ cleanUp();\r
+ endTime = getCurrentTimeMillis();\r
+\r
+ }\r
+ \r
+\r
+ //this routine open the source and find the dependent rows \r
+ void setup() throws StandardException\r
+ {\r
+ super.setup();\r
+\r
+ // Remember if this is the 1st execution\r
+ firstExecute = (rc == null);\r
+\r
+ try {\r
+\r
+ //open the source for the parent tables\r
+ if (numOpens++ == 0)\r
+ {\r
+ source.openCore();\r
+ }\r
+ else\r
+ {\r
+ source.reopenCore();\r
+ }\r
+ } catch (StandardException se) {\r
+ activation.checkStatementValidity();\r
+ throw se;\r
+\r
+ }\r
+\r
+ activation.checkStatementValidity();\r
+\r
+ /* Get or re-use the row changer.\r
+ * NOTE: We need to set ourself as the top result set\r
+ * if this is not the 1st execution. (Done in constructor\r
+ * for 1st execution.)\r
+ */\r
+ if (firstExecute)\r
+ {\r
+ rc = lcc.getLanguageConnectionFactory().getExecutionFactory().\r
+ getRowChanger( \r
+ constants.conglomId,\r
+ constants.heapSCOCI, \r
+ heapDCOCI,\r
+ constants.irgs,\r
+ constants.indexCIDS,\r
+ constants.indexSCOCIs,\r
+ indexDCOCIs,\r
+ constants.numColumns,\r
+ tc,\r
+ (int[])null,\r
+ baseRowReadList,\r
+ constants.getBaseRowReadMap(),\r
+ constants.getStreamStorableHeapColIds(),\r
+ activation);\r
+ }\r
+ else\r
+ {\r
+ lcc.getStatementContext().setTopResultSet(this, subqueryTrackingArray);\r
+ }\r
+ /* decode the lock mode for the execution isolation level */\r
+ lockMode = decodeLockMode(constants.lockMode);\r
+\r
+ /* Open the RowChanger before the source ResultSet so that\r
+ * the store will see the RowChanger's lock as a covering lock\r
+ * if it is a table lock.\r
+ */\r
+ rc.open(lockMode); \r
+\r
+ /* The source does not know whether or not we are doing a\r
+ * deferred mode delete. If we are, then we must clear the\r
+ * index scan info from the activation so that the row changer\r
+ * does not re-use that information (which won't be valid for\r
+ * a deferred mode delete).\r
+ */\r
+ if (constants.deferred || cascadeDelete)\r
+ {\r
+ activation.clearIndexScanInfo();\r
+ }\r
+\r
+ rowCount = 0;\r
+ if(!cascadeDelete)\r
+ row = getNextRowCore(source);\r
+\r
+ /*\r
+ ** We need the number of columns even if there are\r
+ ** no rows. Note that source.ressultDescription() may \r
+ ** be null on a rep target doing a refresh.\r
+ */\r
+ if (resultDescription == null)\r
+ {\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ /*\r
+ ** We NEED a result description when we are going to\r
+ ** to have to kick off a trigger. In a replicated environment\r
+ ** we don't get a result description when we are replaying\r
+ ** source xacts on the target, but we shouldn't be firing\r
+ ** a trigger in that case anyway.\r
+ */\r
+ SanityManager.ASSERT(triggerInfo == null, "result description is needed to supply to trigger result sets");\r
+ }\r
+ numberOfBaseColumns = (row == null) ? 0 : row.nColumns();\r
+ }\r
+ else\r
+ {\r
+ numberOfBaseColumns = resultDescription.getColumnCount();\r
+ }\r
+\r
+ numIndexes = constants.irgs.length;\r
+\r
+ if (constants.deferred || cascadeDelete)\r
+ {\r
+ Properties properties = new Properties();\r
+\r
+ // Get the properties on the old heap\r
+ rc.getHeapConglomerateController().getInternalTablePropertySet(properties);\r
+\r
+ /*\r
+ ** If deferred and fk or trigger, we are going to grab\r
+ ** the entire row. \r
+ **\r
+ ** If we are deferred w/o a fk, then we only\r
+ ** save the row location.\r
+ */\r
+ deferredRLRow = RowUtil.getEmptyValueRow(1, lcc);\r
+ rlColumnNumber = noTriggersOrFks ? 1: numberOfBaseColumns;\r
+ if(cascadeDelete)\r
+ {\r
+ rowHolder = new TemporaryRowHolderImpl(activation, properties, \r
+ (resultDescription != null) ?\r
+ resultDescription.truncateColumns(rlColumnNumber) :\r
+ null, false);\r
+\r
+\r
+ }else\r
+ {\r
+\r
+ rowHolder = new TemporaryRowHolderImpl(activation, properties, \r
+ (resultDescription != null) ?\r
+ resultDescription.truncateColumns(rlColumnNumber) :\r
+ null);\r
+\r
+ }\r
+\r
+ rc.setRowHolder(rowHolder);\r
+ }\r
+\r
+ if (fkInfoArray != null)\r
+ {\r
+ if (fkChecker == null)\r
+ {\r
+ fkChecker = new RISetChecker(tc, fkInfoArray);\r
+ }\r
+ else\r
+ {\r
+ fkChecker.reopen();\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ boolean collectAffectedRows() throws StandardException\r
+ { \r
+\r
+ DataValueDescriptor rlColumn;\r
+ RowLocation baseRowLocation;\r
+ boolean rowsFound = false;\r
+\r
+ if(cascadeDelete)\r
+ row = getNextRowCore(source);\r
+\r
+ while ( row != null )\r
+ {\r
+ /* By convention, the last column for a delete contains a SQLRef\r
+ * containing the RowLocation of the row to be deleted. If we're\r
+ * doing a deferred delete, store the RowLocations in the\r
+ * temporary conglomerate. If we're not doing a deferred delete,\r
+ * just delete the rows immediately.\r
+ */\r
+\r
+ rowsFound = true;\r
+\r
+ rlColumn = row.getColumn( row.nColumns() );\r
+ \r
+ if (constants.deferred || cascadeDelete)\r
+ {\r
+\r
+ /*\r
+ ** If we are deferred because of a trigger or foreign\r
+ ** key, we need to save off the entire row. Otherwise,\r
+ ** we just save the RID.\r
+ */\r
+ if (noTriggersOrFks)\r
+ {\r
+ deferredRLRow.setColumn(1, rlColumn);\r
+ rowHolder.insert(deferredRLRow);\r
+ }\r
+ else\r
+ {\r
+ rowHolder.insert(row);\r
+ }\r
+ \r
+ /*\r
+ ** If we haven't already, lets get a template to\r
+ ** use as a template for our rescan of the base table.\r
+ ** Do this now while we have a real row to use\r
+ ** as a copy.\r
+ **\r
+ ** There is one less column in the base row than\r
+ ** there is in source row, because the base row\r
+ ** doesn't contain the row location.\r
+ */\r
+ if (deferredBaseRow == null)\r
+ {\r
+ deferredBaseRow = RowUtil.getEmptyValueRow(numberOfBaseColumns - 1, lcc);\r
+ \r
+ RowUtil.copyCloneColumns(deferredBaseRow, row, \r
+ numberOfBaseColumns - 1);\r
+ deferredSparseRow = makeDeferredSparseRow(deferredBaseRow,\r
+ baseRowReadList,\r
+ lcc);\r
+ }\r
+ }\r
+ else\r
+ {\r
+ if (fkChecker != null)\r
+ {\r
+ fkChecker.doPKCheck(row, false);\r
+ }\r
+\r
+ baseRowLocation = \r
+ (RowLocation) (rlColumn).getObject();\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ SanityManager.ASSERT(baseRowLocation != null,\r
+ "baseRowLocation is null");\r
+ }\r
+\r
+ rc.deleteRow(row,baseRowLocation);\r
+ source.markRowAsDeleted();\r
+ }\r
+\r
+ rowCount++;\r
+\r
+ // No need to do a next on a single row source\r
+ if (constants.singleRowSource)\r
+ {\r
+ row = null;\r
+ }\r
+ else\r
+ {\r
+ row = getNextRowCore(source);\r
+ }\r
+ }\r
+\r
+ return rowsFound;\r
+ }\r
+\r
+\r
+ // execute the before triggers set on the table\r
+ void fireBeforeTriggers() throws StandardException\r
+ {\r
+\r
+ if (triggerInfo != null)\r
+ {\r
+ if (triggerActivator == null)\r
+ {\r
+ triggerActivator = new TriggerEventActivator(lcc, \r
+ tc, \r
+ constants.targetUUID,\r
+ triggerInfo,\r
+ TriggerExecutionContext.DELETE_EVENT,\r
+ activation, null\r
+ );\r
+ }\r
+ else\r
+ {\r
+ triggerActivator.reopen();\r
+ }\r
+\r
+ // fire BEFORE trigger\r
+ triggerActivator.notifyEvent(TriggerEvents.BEFORE_DELETE, \r
+ rowHolder.getResultSet(), \r
+ (CursorResultSet)null);\r
+ triggerActivator.cleanup();\r
+\r
+ }\r
+\r
+ }\r
+\r
+ //execute the after triggers set on the table.\r
+ void fireAfterTriggers() throws StandardException\r
+ {\r
+\r
+ // fire AFTER trigger\r
+ if (triggerActivator != null)\r
+ {\r
+ triggerActivator.reopen();\r
+ triggerActivator.notifyEvent(TriggerEvents.AFTER_DELETE, \r
+ rowHolder.getResultSet(),\r
+ (CursorResultSet)null);\r
+ triggerActivator.cleanup();\r
+ }\r
+ \r
+ }\r
+\r
+\r
+ //delete the rows that in case deferred case and\r
+ //during cascade delete (All deletes are deferred during cascade action)\r
+ void deleteDeferredRows() throws StandardException\r
+ {\r
+ \r
+ DataValueDescriptor rlColumn;\r
+ RowLocation baseRowLocation;\r
+ ExecRow deferredRLRow = null;\r
+\r
+ deferredBaseCC = tc.openCompiledConglomerate(false,\r
+ tc.OPENMODE_FORUPDATE|tc.OPENMODE_SECONDARY_LOCKED,\r
+ lockMode,\r
+ TransactionController.ISOLATION_SERIALIZABLE,\r
+ constants.heapSCOCI,\r
+ heapDCOCI);\r
+ \r
+ CursorResultSet rs = rowHolder.getResultSet();\r
+ try\r
+ {\r
+ /*\r
+ ** We need to do a fetch doing a partial row\r
+ ** read. We need to shift our 1-based bit\r
+ ** set to a zero based bit set like the store\r
+ ** expects.\r
+ */\r
+ FormatableBitSet readBitSet = RowUtil.shift(baseRowReadList, 1);\r
+\r
+ rs.open();\r
+ while ((deferredRLRow = rs.getNextRow()) != null)\r
+ {\r
+ rlColumn = deferredRLRow.getColumn(rlColumnNumber);\r
+ baseRowLocation = \r
+ (RowLocation) (rlColumn).getObject();\r
+ \r
+ /* Get the base row at the given RowLocation */\r
+ boolean row_exists = \r
+ deferredBaseCC.fetch(\r
+ baseRowLocation, deferredSparseRow.getRowArray(), \r
+ readBitSet);\r
+\r
+ // In case of cascade delete , things like before triggers can delete \r
+ // the rows before the dependent result get a chance to delete\r
+ if(cascadeDelete && !row_exists)\r
+ continue;\r
+\r
+ if (SanityManager.DEBUG)\r
+ {\r
+ if (!row_exists)\r
+ {\r
+ SanityManager.THROWASSERT("could not find row "+baseRowLocation);\r
+ }\r
+ }\r
+ \r
+ rc.deleteRow(deferredBaseRow, baseRowLocation);\r
+ source.markRowAsDeleted();\r
+ }\r
+ } finally\r
+ {\r
+ rs.close();\r
+ }\r
+ }\r
+\r
+\r
+ // make sure foreign key constraints are not violated\r
+ void runFkChecker(boolean restrictCheckOnly) throws StandardException\r
+ {\r
+\r
+ ExecRow deferredRLRow = null;\r
+ if (fkChecker != null)\r
+ {\r
+ /*\r
+ ** Second scan to make sure all the foreign key\r
+ ** constraints are ok. We have to do this after\r
+ ** we have completed the deletes in case of self referencing\r
+ ** constraints.\r
+ */\r
+ CursorResultSet rs = rowHolder.getResultSet();\r
+ try\r
+ {\r
+ rs.open();\r
+ while ((deferredRLRow = rs.getNextRow()) != null)\r
+ {\r
+ fkChecker.doPKCheck(deferredRLRow, restrictCheckOnly);\r
+ }\r
+ } finally\r
+ {\r
+ rs.close();\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * create a source for the dependent table\r
+ *\r
+ * <P>Delete Cascade ResultSet class will override this method.\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ NoPutResultSet createDependentSource(RowChanger rc)\r
+ throws StandardException\r
+ {\r
+ return null;\r
+ }\r
+\r
+\r
+ /**\r
+ * @see ResultSet#cleanUp\r
+ *\r
+ * @exception StandardException Thrown on error\r
+ */\r
+ public void cleanUp() throws StandardException\r
+ { \r
+ numOpens = 0;\r
+\r
+ /* Close down the source ResultSet tree */\r
+ if (source != null)\r
+ {\r
+ source.close();\r
+ // source is reused across executions\r
+ }\r
+ if (rc != null)\r
+ {\r
+ rc.close();\r
+ // rc is reused across executions\r
+ }\r
+\r
+ if (rowHolder != null)\r
+ {\r
+ rowHolder.close();\r
+ // rowHolder is reused across executions\r
+ }\r
+\r
+ if (fkChecker != null)\r
+ {\r
+ fkChecker.close();\r
+ // fkcheckers is reused across executions\r
+ }\r
+\r
+ if (deferredBaseCC != null)\r
+ deferredBaseCC.close();\r
+ deferredBaseCC = null;\r
+\r
+ if (rc != null) {\r
+ rc.close();\r
+ }\r
+ super.close();\r
+ }\r
+\r
+ public void finish() throws StandardException {\r
+ if (source != null)\r
+ source.finish();\r
+ super.finish();\r
+ }\r
+\r
+}\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r
+\r