001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.DeleteResultSet
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.monitor.Monitor;
025: import org.apache.derby.iapi.services.sanity.SanityManager;
026: import org.apache.derby.iapi.services.stream.HeaderPrintWriter;
027: import org.apache.derby.iapi.services.stream.InfoStreams;
028: import org.apache.derby.iapi.error.StandardException;
029: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
030: import org.apache.derby.iapi.sql.dictionary.IndexRowGenerator;
031: import org.apache.derby.iapi.sql.execute.ConstantAction;
032: import org.apache.derby.iapi.sql.execute.CursorResultSet;
033: import org.apache.derby.iapi.sql.execute.ExecRow;
034: import org.apache.derby.iapi.sql.execute.ExecutionContext;
035: import org.apache.derby.iapi.sql.execute.RowChanger;
036: import org.apache.derby.iapi.sql.execute.NoPutResultSet;
037: import org.apache.derby.iapi.sql.Activation;
038: import org.apache.derby.iapi.sql.ResultDescription;
039: import org.apache.derby.iapi.sql.ResultSet;
040: import org.apache.derby.iapi.types.DataValueDescriptor;
041:
042: import org.apache.derby.iapi.store.access.ConglomerateController;
043: import org.apache.derby.iapi.store.access.DynamicCompiledOpenConglomInfo;
044: import org.apache.derby.iapi.store.access.ScanController;
045: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
046: import org.apache.derby.iapi.store.access.TransactionController;
047:
048: import org.apache.derby.iapi.types.RowLocation;
049:
050: import org.apache.derby.iapi.reference.SQLState;
051:
052: import org.apache.derby.iapi.db.TriggerExecutionContext;
053: import org.apache.derby.iapi.services.io.FormatableBitSet;
054: import java.util.Properties;
055:
056: import org.apache.derby.iapi.types.RowLocation;
057: import org.apache.derby.iapi.sql.execute.ExecIndexRow;
058:
059: /**
060: * Delete the rows from the specified
061: * base table. This will cause constraints to be checked
062: * and triggers to be executed based on the c's and t's
063: * compiled into the insert plan.
064: */
065: class DeleteResultSet extends DMLWriteResultSet {
066: private TransactionController tc;
067: DeleteConstantAction constants;
068: protected ResultDescription resultDescription;
069: protected NoPutResultSet source;
070: NoPutResultSet savedSource;
071: int numIndexes;
072: protected RowChanger rc;
073: private ExecRow row;
074:
075: protected ConglomerateController deferredBaseCC;
076:
077: protected TemporaryRowHolderImpl rowHolder;
078:
079: private int numOpens; // number of opens w/o a close
080: private boolean firstExecute;
081:
082: // cached across opens()s
083: private FormatableBitSet baseRowReadList;
084: private int rlColumnNumber;
085: protected FKInfo[] fkInfoArray;
086: private TriggerInfo triggerInfo;
087: private RISetChecker fkChecker;
088: private TriggerEventActivator triggerActivator;
089: private boolean noTriggersOrFks;
090:
091: ExecRow deferredSparseRow;
092: ExecRow deferredBaseRow;
093: int lockMode;
094: protected boolean cascadeDelete;
095: ExecRow deferredRLRow = null;
096: int numberOfBaseColumns = 0;
097:
098: /**
099: * Returns the description of the deleted rows.
100: * REVISIT: Do we want this to return NULL instead?
101: */
102: public ResultDescription getResultDescription() {
103: return resultDescription;
104: }
105:
106: /*
107: * class interface
108: *
109: */
110: DeleteResultSet(NoPutResultSet source, Activation activation)
111: throws StandardException {
112: this (source, activation.getConstantAction(), activation);
113: }
114:
115: /**
116: * REMIND: At present this takes just the conglomerate id
117: * of the table. We can expect this to expand to include
118: * passing information about triggers, constraints, and
119: * any additional conglomerates on the underlying table
120: * for access methods.
121: *
122: * @exception StandardException Thrown on error
123: */
124: DeleteResultSet(NoPutResultSet source,
125: ConstantAction passedInConstantAction, Activation activation)
126: throws StandardException {
127: super (activation, passedInConstantAction);
128: this .source = source;
129:
130: tc = activation.getTransactionController();
131: constants = (DeleteConstantAction) constantAction;
132: fkInfoArray = constants.getFKInfo(lcc.getExecutionContext());
133: triggerInfo = constants.getTriggerInfo(lcc
134: .getExecutionContext());
135: noTriggersOrFks = ((fkInfoArray == null) && (triggerInfo == null));
136: baseRowReadList = constants.getBaseRowReadList();
137: if (source != null)
138: resultDescription = source.getResultDescription();
139: else
140: resultDescription = constants.resultDescription;
141:
142: }
143:
144: /**
145: @exception StandardException Standard Cloudscape error policy
146: */
147: public void open() throws StandardException {
148:
149: setup();
150: boolean rowsFound = collectAffectedRows(); //this call also deletes rows , if not deferred
151: if (!rowsFound) {
152: activation.addWarning(StandardException
153: .newWarning(SQLState.LANG_NO_ROW_FOUND));
154: }
155:
156: /*
157: ** If the delete is deferred, scan the temporary conglomerate to
158: ** get the RowLocations of the rows to be deleted. Re-fetch the
159: ** rows and delete them using the RowChanger.
160: */
161: if (constants.deferred) {
162: runFkChecker(true); //check for only RESTRICT referential action rule violations
163: fireBeforeTriggers();
164: deleteDeferredRows();
165: runFkChecker(false); //check for all constraint violations
166: // apply
167: rc.finish();
168: fireAfterTriggers();
169: }
170:
171: /* Cache query plan text for source, before it gets blown away */
172: if (lcc.getRunTimeStatisticsMode()) {
173: /* savedSource nulled after run time statistics generation */
174: savedSource = source;
175: }
176:
177: cleanUp();
178: endTime = getCurrentTimeMillis();
179:
180: }
181:
182: //this routine open the source and find the dependent rows
183: void setup() throws StandardException {
184:
185: // Remember if this is the 1st execution
186: firstExecute = (rc == null);
187:
188: try {
189:
190: //open the source for the parent tables
191: if (numOpens++ == 0) {
192: source.openCore();
193: } else {
194: source.reopenCore();
195: }
196: } catch (StandardException se) {
197: activation.checkStatementValidity();
198: throw se;
199:
200: }
201:
202: activation.checkStatementValidity();
203:
204: /* Get or re-use the row changer.
205: * NOTE: We need to set ourself as the top result set
206: * if this is not the 1st execution. (Done in constructor
207: * for 1st execution.)
208: */
209: if (firstExecute) {
210: rc = lcc.getLanguageConnectionFactory()
211: .getExecutionFactory().getRowChanger(
212: constants.conglomId, constants.heapSCOCI,
213: heapDCOCI, constants.irgs,
214: constants.indexCIDS, constants.indexSCOCIs,
215: indexDCOCIs, constants.numColumns, tc,
216: (int[]) null, baseRowReadList,
217: constants.getBaseRowReadMap(),
218: constants.getStreamStorableHeapColIds(),
219: activation);
220: } else {
221: lcc.getStatementContext().setTopResultSet(this ,
222: subqueryTrackingArray);
223: }
224: /* decode the lock mode for the execution isolation level */
225: lockMode = UpdateResultSet.decodeLockMode(lcc,
226: constants.lockMode);
227:
228: /* Open the RowChanger before the source ResultSet so that
229: * the store will see the RowChanger's lock as a covering lock
230: * if it is a table lock.
231: */
232: rc.open(lockMode);
233:
234: /* The source does not know whether or not we are doing a
235: * deferred mode delete. If we are, then we must clear the
236: * index scan info from the activation so that the row changer
237: * does not re-use that information (which won't be valid for
238: * a deferred mode delete).
239: */
240: if (constants.deferred || cascadeDelete) {
241: activation.clearIndexScanInfo();
242: }
243:
244: rowCount = 0;
245: if (!cascadeDelete)
246: row = getNextRowCore(source);
247:
248: /*
249: ** We need the number of columns even if there are
250: ** no rows. Note that source.ressultDescription() may
251: ** be null on a rep target doing a refresh.
252: */
253: if (resultDescription == null) {
254: if (SanityManager.DEBUG) {
255: /*
256: ** We NEED a result description when we are going to
257: ** to have to kick off a trigger. In a replicated environment
258: ** we don't get a result description when we are replaying
259: ** source xacts on the target, but we shouldn't be firing
260: ** a trigger in that case anyway.
261: */
262: SanityManager
263: .ASSERT(triggerInfo == null,
264: "result description is needed to supply to trigger result sets");
265: }
266: numberOfBaseColumns = (row == null) ? 0 : row.nColumns();
267: } else {
268: numberOfBaseColumns = resultDescription.getColumnCount();
269: }
270:
271: numIndexes = constants.irgs.length;
272:
273: if (constants.deferred || cascadeDelete) {
274: Properties properties = new Properties();
275:
276: // Get the properties on the old heap
277: rc.getHeapConglomerateController()
278: .getInternalTablePropertySet(properties);
279:
280: /*
281: ** If deferred and fk or trigger, we are going to grab
282: ** the entire row.
283: **
284: ** If we are deferred w/o a fk, then we only
285: ** save the row location.
286: */
287: deferredRLRow = RowUtil.getEmptyValueRow(1, lcc);
288: rlColumnNumber = noTriggersOrFks ? 1 : numberOfBaseColumns;
289: if (cascadeDelete) {
290: rowHolder = new TemporaryRowHolderImpl(
291: activation,
292: properties,
293: (resultDescription != null) ? resultDescription
294: .truncateColumns(rlColumnNumber) : null,
295: false);
296:
297: } else {
298:
299: rowHolder = new TemporaryRowHolderImpl(activation,
300: properties,
301: (resultDescription != null) ? resultDescription
302: .truncateColumns(rlColumnNumber) : null);
303:
304: }
305:
306: rc.setRowHolder(rowHolder);
307: }
308:
309: if (fkInfoArray != null) {
310: if (fkChecker == null) {
311: fkChecker = new RISetChecker(tc, fkInfoArray);
312: } else {
313: fkChecker.reopen();
314: }
315: }
316: }
317:
318: boolean collectAffectedRows() throws StandardException {
319:
320: DataValueDescriptor rlColumn;
321: RowLocation baseRowLocation;
322: boolean rowsFound = false;
323:
324: if (cascadeDelete)
325: row = getNextRowCore(source);
326:
327: while (row != null) {
328: /* By convention, the last column for a delete contains a SQLRef
329: * containing the RowLocation of the row to be deleted. If we're
330: * doing a deferred delete, store the RowLocations in the
331: * temporary conglomerate. If we're not doing a deferred delete,
332: * just delete the rows immediately.
333: */
334:
335: rowsFound = true;
336:
337: rlColumn = row.getColumn(row.nColumns());
338:
339: if (constants.deferred || cascadeDelete) {
340:
341: /*
342: ** If we are deferred because of a trigger or foreign
343: ** key, we need to save off the entire row. Otherwise,
344: ** we just save the RID.
345: */
346: if (noTriggersOrFks) {
347: deferredRLRow.setColumn(1, rlColumn);
348: rowHolder.insert(deferredRLRow);
349: } else {
350: rowHolder.insert(row);
351: }
352:
353: /*
354: ** If we haven't already, lets get a template to
355: ** use as a template for our rescan of the base table.
356: ** Do this now while we have a real row to use
357: ** as a copy.
358: **
359: ** There is one less column in the base row than
360: ** there is in source row, because the base row
361: ** doesn't contain the row location.
362: */
363: if (deferredBaseRow == null) {
364: deferredBaseRow = RowUtil.getEmptyValueRow(
365: numberOfBaseColumns - 1, lcc);
366:
367: RowUtil.copyCloneColumns(deferredBaseRow, row,
368: numberOfBaseColumns - 1);
369: deferredSparseRow = makeDeferredSparseRow(
370: deferredBaseRow, baseRowReadList, lcc);
371: }
372: } else {
373: if (fkChecker != null) {
374: fkChecker.doPKCheck(row, false);
375: }
376:
377: baseRowLocation = (RowLocation) (rlColumn).getObject();
378:
379: if (SanityManager.DEBUG) {
380: SanityManager.ASSERT(baseRowLocation != null,
381: "baseRowLocation is null");
382: }
383:
384: rc.deleteRow(row, baseRowLocation);
385: source.markRowAsDeleted();
386: }
387:
388: rowCount++;
389:
390: // No need to do a next on a single row source
391: if (constants.singleRowSource) {
392: row = null;
393: } else {
394: row = getNextRowCore(source);
395: }
396: }
397:
398: return rowsFound;
399: }
400:
401: // execute the before triggers set on the table
402: void fireBeforeTriggers() throws StandardException {
403:
404: if (triggerInfo != null) {
405: if (triggerActivator == null) {
406: triggerActivator = new TriggerEventActivator(lcc, tc,
407: constants.targetUUID, triggerInfo,
408: TriggerExecutionContext.DELETE_EVENT,
409: activation, null);
410: } else {
411: triggerActivator.reopen();
412: }
413:
414: // fire BEFORE trigger
415: triggerActivator.notifyEvent(TriggerEvents.BEFORE_DELETE,
416: rowHolder.getResultSet(), (CursorResultSet) null);
417: triggerActivator.cleanup();
418:
419: }
420:
421: }
422:
423: //execute the after triggers set on the table.
424: void fireAfterTriggers() throws StandardException {
425:
426: // fire AFTER trigger
427: if (triggerActivator != null) {
428: triggerActivator.reopen();
429: triggerActivator.notifyEvent(TriggerEvents.AFTER_DELETE,
430: rowHolder.getResultSet(), (CursorResultSet) null);
431: triggerActivator.cleanup();
432: }
433:
434: }
435:
436: //delete the rows that in case deferred case and
437: //during cascade delete (All deletes are deferred during cascade action)
438: void deleteDeferredRows() throws StandardException {
439:
440: DataValueDescriptor rlColumn;
441: RowLocation baseRowLocation;
442: ExecRow deferredRLRow = null;
443:
444: deferredBaseCC = tc.openCompiledConglomerate(false,
445: tc.OPENMODE_FORUPDATE | tc.OPENMODE_SECONDARY_LOCKED,
446: lockMode, TransactionController.ISOLATION_SERIALIZABLE,
447: constants.heapSCOCI, heapDCOCI);
448:
449: CursorResultSet rs = rowHolder.getResultSet();
450: try {
451: /*
452: ** We need to do a fetch doing a partial row
453: ** read. We need to shift our 1-based bit
454: ** set to a zero based bit set like the store
455: ** expects.
456: */
457: FormatableBitSet readBitSet = RowUtil.shift(
458: baseRowReadList, 1);
459:
460: rs.open();
461: while ((deferredRLRow = rs.getNextRow()) != null) {
462: rlColumn = deferredRLRow.getColumn(rlColumnNumber);
463: baseRowLocation = (RowLocation) (rlColumn).getObject();
464:
465: /* Get the base row at the given RowLocation */
466: boolean row_exists = deferredBaseCC.fetch(
467: baseRowLocation, deferredSparseRow
468: .getRowArray(), readBitSet);
469:
470: // In case of cascade delete , things like before triggers can delete
471: // the rows before the dependent result get a chance to delete
472: if (cascadeDelete && !row_exists)
473: continue;
474:
475: if (SanityManager.DEBUG) {
476: if (!row_exists) {
477: SanityManager.THROWASSERT("could not find row "
478: + baseRowLocation);
479: }
480: }
481:
482: rc.deleteRow(deferredBaseRow, baseRowLocation);
483: source.markRowAsDeleted();
484: }
485: } finally {
486: rs.close();
487: }
488: }
489:
490: // make sure foreign key constraints are not violated
491: void runFkChecker(boolean restrictCheckOnly)
492: throws StandardException {
493:
494: ExecRow deferredRLRow = null;
495: if (fkChecker != null) {
496: /*
497: ** Second scan to make sure all the foreign key
498: ** constraints are ok. We have to do this after
499: ** we have completed the deletes in case of self referencing
500: ** constraints.
501: */
502: CursorResultSet rs = rowHolder.getResultSet();
503: try {
504: rs.open();
505: while ((deferredRLRow = rs.getNextRow()) != null) {
506: fkChecker.doPKCheck(deferredRLRow,
507: restrictCheckOnly);
508: }
509: } finally {
510: rs.close();
511: }
512: }
513: }
514:
515: /**
516: * create a source for the dependent table
517: *
518: * <P>Delete Cascade ResultSet class will override this method.
519: *
520: * @exception StandardException Thrown on error
521: */
522: NoPutResultSet createDependentSource(RowChanger rc)
523: throws StandardException {
524: return null;
525: }
526:
527: /**
528: * @see ResultSet#cleanUp
529: *
530: * @exception StandardException Thrown on error
531: */
532: public void cleanUp() throws StandardException {
533: numOpens = 0;
534:
535: /* Close down the source ResultSet tree */
536: if (source != null) {
537: source.close();
538: // source is reused across executions
539: }
540: if (rc != null) {
541: rc.close();
542: // rc is reused across executions
543: }
544:
545: if (rowHolder != null) {
546: rowHolder.close();
547: // rowHolder is reused across executions
548: }
549:
550: if (fkChecker != null) {
551: fkChecker.close();
552: // fkcheckers is reused across executions
553: }
554:
555: if (deferredBaseCC != null)
556: deferredBaseCC.close();
557: deferredBaseCC = null;
558:
559: super .close();
560: }
561:
562: public void finish() throws StandardException {
563: if (source != null)
564: source.finish();
565: super.finish();
566: }
567:
568: }
|