001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.DeleteCascadeResultSet
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.execute;
023:
024: import org.apache.derby.iapi.services.sanity.SanityManager;
025: import org.apache.derby.iapi.error.StandardException;
026: import org.apache.derby.iapi.sql.execute.ConstantAction;
027: import org.apache.derby.iapi.sql.execute.CursorResultSet;
028: import org.apache.derby.iapi.sql.execute.RowChanger;
029: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
030: import org.apache.derby.iapi.sql.Activation;
031: import org.apache.derby.iapi.sql.ResultDescription;
032: import org.apache.derby.iapi.types.DataValueDescriptor;
033: import org.apache.derby.iapi.sql.ResultSet;
034: import org.apache.derby.iapi.store.access.ConglomerateController;
035: import org.apache.derby.iapi.store.access.TransactionController;
036: import org.apache.derby.iapi.sql.execute.ExecRow;
037: import org.apache.derby.iapi.sql.execute.TemporaryRowHolder;
038:
039: import org.apache.derby.iapi.reference.SQLState;
040:
041: import java.util.Vector;
042: import java.util.Hashtable;
043: import java.util.Enumeration;
044:
045: /**
046: * Delete the rows from the specified base table and executes delete/update
047: * on dependent tables depending on the referential actions specified.
048: * Note:(beetle:5197) Dependent Resultsets of DeleteCascade Resultset can in
049: * any one of the multiple resultsets generated for the same table because of
050: * multiple foreign key relationship to the same table. At the bind time ,
051: * dependents are binded only once per table.
052: * We can not depend on mainNodeTable Flag to fire actions on dependents,
053: * it should be done based on whether the resultset has dependent resultsets or not.
054: *
055: */
056: public class DeleteCascadeResultSet extends DeleteResultSet {
057:
058: public ResultSet[] dependentResultSets;
059: private int noDependents = 0;
060: private CursorResultSet parentSource;
061: private FKInfo parentFKInfo;
062: private long fkIndexConglomNumber;
063: private String resultSetId;
064: private boolean mainNodeForTable = true;
065: private boolean affectedRows = false;
066: private int tempRowHolderId; //this result sets temporary row holder id
067:
068: /*
069: * class interface
070: * @exception StandardException Thrown on error
071: */
072: public DeleteCascadeResultSet(NoPutResultSet source,
073: Activation activation, int constantActionItem,
074: ResultSet[] dependentResultSets, String resultSetId)
075: throws StandardException {
076:
077: super (source, ((constantActionItem == -1) ? activation
078: .getConstantAction() : (ConstantAction) activation
079: .getPreparedStatement().getSavedObject(
080: constantActionItem)), activation);
081:
082: ConstantAction passedInConstantAction;
083: if (constantActionItem == -1)
084: passedInConstantAction = activation.getConstantAction(); //root table
085: else {
086: passedInConstantAction = (ConstantAction) activation
087: .getPreparedStatement().getSavedObject(
088: constantActionItem);
089: resultDescription = constants.resultDescription;
090: }
091: cascadeDelete = true;
092: this .resultSetId = resultSetId;
093:
094: if (dependentResultSets != null) {
095: noDependents = dependentResultSets.length;
096: this .dependentResultSets = dependentResultSets;
097: }
098:
099: }
100:
101: /**
102: @exception StandardException Standard Cloudscape error policy
103: */
104: public void open() throws StandardException {
105:
106: try {
107: setup();
108: if (isMultipleDeletePathsExist()) {
109: setRowHoldersTypeToUniqueStream();
110: //collect until there are no more rows to found
111: while (collectAffectedRows(false))
112: ;
113: } else {
114: collectAffectedRows(false);
115: }
116: if (!affectedRows) {
117: activation.addWarning(StandardException
118: .newWarning(SQLState.LANG_NO_ROW_FOUND));
119: }
120:
121: runFkChecker(true); //check for only RESTRICT referential action rule violations
122: Hashtable mntHashTable = new Hashtable(); //Hash Table to identify mutiple node for same table cases.
123: mergeRowHolders(mntHashTable);
124: fireBeforeTriggers(mntHashTable);
125: deleteDeferredRows();
126: runFkChecker(false); //check for all constraint violations
127: rowChangerFinish();
128: fireAfterTriggers();
129: cleanUp();
130: } finally {
131: //clear the parent result sets hash table
132: activation.clearParentResultSets();
133: }
134:
135: endTime = getCurrentTimeMillis();
136:
137: }
138:
139: /**
140: *Gathers the rows that needs to be deleted/updated
141: *and creates a temporary resulsets that will be passed
142: *as source to its dependent result sets.
143: */
144: void setup() throws StandardException {
145:
146: /* Cache query plan text for source, before it gets blown away */
147: if (lcc.getRunTimeStatisticsMode()) {
148: /* savedSource nulled after run time statistics generation */
149: savedSource = source;
150: }
151:
152: super .setup();
153: activation.setParentResultSet(rowHolder, resultSetId);
154: Vector sVector = (Vector) activation
155: .getParentResultSet(resultSetId);
156: tempRowHolderId = sVector.size() - 1;
157: for (int i = 0; i < noDependents; i++) {
158: if (dependentResultSets[i] instanceof UpdateResultSet) {
159: ((UpdateResultSet) dependentResultSets[i]).setup();
160: } else {
161: ((DeleteCascadeResultSet) dependentResultSets[i])
162: .setup();
163: }
164: }
165:
166: }
167:
168: boolean collectAffectedRows(boolean rowsFound)
169: throws StandardException {
170: if (super .collectAffectedRows()) {
171: affectedRows = true;
172: rowsFound = true;
173: }
174:
175: for (int i = 0; i < noDependents; i++) {
176: if (dependentResultSets[i] instanceof UpdateResultSet) {
177: if (((UpdateResultSet) dependentResultSets[i])
178: .collectAffectedRows())
179: rowsFound = true;
180: } else {
181: if (((DeleteCascadeResultSet) dependentResultSets[i])
182: .collectAffectedRows(rowsFound))
183: rowsFound = true;
184: }
185: }
186:
187: return rowsFound;
188: }
189:
190: void fireBeforeTriggers(Hashtable msht) throws StandardException {
191: if (!mainNodeForTable) {
192: /*to handle case where no table node had qualified rows, in which case no node for
193: * the table get marked as mainNodeFor table , one way to identify
194: * such case is to look at the mutinode hash table and see if the result id exist ,
195: *if it does not means none of the table nodes resulsets got marked
196: * as main node for table. If that is the case we mark this
197: * resultset as mainNodeTable and put entry in the hash table.
198: */
199: if (!msht.containsKey(resultSetId)) {
200: mainNodeForTable = true;
201: msht.put(resultSetId, resultSetId);
202: }
203: }
204:
205: //execute the before triggers on the dependents
206: //Defect 5743: Before enabling BEFORE triggers, check DB2 behavior.
207: for (int i = 0; i < noDependents; i++) {
208: if (dependentResultSets[i] instanceof UpdateResultSet) {
209: ((UpdateResultSet) dependentResultSets[i])
210: .fireBeforeTriggers();
211: } else {
212: ((DeleteCascadeResultSet) dependentResultSets[i])
213: .fireBeforeTriggers(msht);
214: }
215: }
216:
217: //If there is more than one node for the same table
218: //only one node fires the triggers
219: if (mainNodeForTable && constants.deferred)
220: super .fireBeforeTriggers();
221: }
222:
223: void fireAfterTriggers() throws StandardException {
224: //fire the After Triggers on the dependent tables, if any rows changed
225: for (int i = 0; i < noDependents && affectedRows; i++) {
226: if (dependentResultSets[i] instanceof UpdateResultSet) {
227: ((UpdateResultSet) dependentResultSets[i])
228: .fireAfterTriggers();
229: } else {
230:
231: ((DeleteCascadeResultSet) dependentResultSets[i])
232: .fireAfterTriggers();
233: }
234: }
235:
236: //If there is more than one node for the same table
237: //, we let only one node fire the triggers.
238: if (mainNodeForTable && constants.deferred)
239: super .fireAfterTriggers();
240: }
241:
242: void deleteDeferredRows() throws StandardException {
243:
244: //delete the rows in the dependents tables
245: for (int i = 0; i < noDependents; i++) {
246: if (dependentResultSets[i] instanceof UpdateResultSet) {
247: ((UpdateResultSet) dependentResultSets[i])
248: .updateDeferredRows();
249: } else {
250: ((DeleteCascadeResultSet) dependentResultSets[i])
251: .deleteDeferredRows();
252: }
253: }
254:
255: //If there is more than one node for the same table
256: //only one node deletes all the rows.
257: if (mainNodeForTable)
258: super .deleteDeferredRows();
259: }
260:
261: void runFkChecker(boolean restrictCheckOnly)
262: throws StandardException {
263:
264: //run the Foreign key or primary key Checker on the dependent tables
265: for (int i = 0; i < noDependents; i++) {
266: if (dependentResultSets[i] instanceof UpdateResultSet) {
267: ((UpdateResultSet) dependentResultSets[i])
268: .runChecker(restrictCheckOnly);
269: } else {
270: ((DeleteCascadeResultSet) dependentResultSets[i])
271: .runFkChecker(restrictCheckOnly);
272: }
273: }
274:
275: //If there is more than one node for the same table
276: //only one node does all foreign key checks.
277: if (mainNodeForTable)
278: super .runFkChecker(restrictCheckOnly);
279: }
280:
281: public void cleanUp() throws StandardException {
282:
283: super .cleanUp();
284: for (int i = 0; i < noDependents; i++) {
285: if (dependentResultSets[i] instanceof UpdateResultSet) {
286: ((UpdateResultSet) dependentResultSets[i]).cleanUp();
287: } else {
288: ((DeleteCascadeResultSet) dependentResultSets[i])
289: .cleanUp();
290: }
291: }
292:
293: endTime = getCurrentTimeMillis();
294: }
295:
296: private void rowChangerFinish() throws StandardException {
297:
298: rc.finish();
299: for (int i = 0; i < noDependents; i++) {
300: if (dependentResultSets[i] instanceof UpdateResultSet) {
301: ((UpdateResultSet) dependentResultSets[i])
302: .rowChangerFinish();
303: } else {
304: ((DeleteCascadeResultSet) dependentResultSets[i])
305: .rowChangerFinish();
306: }
307: }
308: }
309:
310: //if there is more than one node for the same table, copy the rows
311: // into one node , so that we don't fire trigger more than once.
312: private void mergeRowHolders(Hashtable msht)
313: throws StandardException {
314: if (msht.containsKey(resultSetId) || rowCount == 0) {
315: //there is already another resultset node that is marked as main
316: //node for this table or this resultset has no rows qualified.
317: //when none of the resultset nodes for the table has any rows then
318: //we mark them as one them as main node in fireBeforeTriggers().
319: mainNodeForTable = false;
320: } else {
321: mergeResultSets();
322: mainNodeForTable = true;
323: msht.put(resultSetId, resultSetId);
324: }
325:
326: for (int i = 0; i < noDependents; i++) {
327: if (dependentResultSets[i] instanceof UpdateResultSet) {
328: return;
329: } else {
330: ((DeleteCascadeResultSet) dependentResultSets[i])
331: .mergeRowHolders(msht);
332: }
333: }
334: }
335:
336: private void mergeResultSets() throws StandardException {
337: Vector sVector = (Vector) activation
338: .getParentResultSet(resultSetId);
339: int size = sVector.size();
340: // if there is more than one source, we need to merge them into onc
341: // temporary result set.
342: if (size > 1) {
343: ExecRow row = null;
344: int rowHolderId = 0;
345: //copy all the vallues in the result set to the current resultset row holder
346: while (rowHolderId < size) {
347: if (rowHolderId == tempRowHolderId) {
348: //skipping the row holder that we are copying the rows into.
349: rowHolderId++;
350: continue;
351: }
352: TemporaryRowHolder currentRowHolder = (TemporaryRowHolder) sVector
353: .elementAt(rowHolderId);
354: CursorResultSet rs = currentRowHolder.getResultSet();
355: rs.open();
356: while ((row = rs.getNextRow()) != null) {
357: rowHolder.insert(row);
358: }
359: rs.close();
360: rowHolderId++;
361: }
362:
363: }
364: }
365:
366: public void finish() throws StandardException {
367: super .finish();
368:
369: //clear the parent result sets hash table
370: //This is necessary in case if we hit any error conditions
371: activation.clearParentResultSets();
372: }
373:
374: /* check whether we have mutiple path delete scenario, if
375: ** find any retun true. Multiple delete paths exist if we find more than
376: ** one parent source resultset for a table involved in the delete cascade
377: **/
378: private boolean isMultipleDeletePathsExist() {
379: Hashtable parentResultSets = activation.getParentResultSets();
380: for (Enumeration e = parentResultSets.keys(); e
381: .hasMoreElements();) {
382: String rsId = (String) e.nextElement();
383: Vector sVector = (Vector) activation
384: .getParentResultSet(rsId);
385: int size = sVector.size();
386: if (size > 1) {
387: return true;
388: }
389: }
390: return false;
391: }
392:
393: /*
394: **Incases where we have multiple paths we could get the same
395: **rows to be deleted mutiple time and also in case of cycles
396: **there might be new rows getting added to the row holders through
397: **multiple iterations. To handle these case we set the temporary row holders
398: ** to be 'uniqStream' type.
399: **/
400: private void setRowHoldersTypeToUniqueStream() {
401: Hashtable parentResultSets = activation.getParentResultSets();
402: for (Enumeration e = parentResultSets.keys(); e
403: .hasMoreElements();) {
404: String rsId = (String) e.nextElement();
405: Vector sVector = (Vector) activation
406: .getParentResultSet(rsId);
407: int size = sVector.size();
408: int rowHolderId = 0;
409: while (rowHolderId < size) {
410: TemporaryRowHolder currentRowHolder = (TemporaryRowHolder) sVector
411: .elementAt(rowHolderId);
412: currentRowHolder.setRowHolderTypeToUniqueStream();
413: rowHolderId++;
414: }
415: }
416: }
417:
418: }
|