001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.DeleteNode
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.compile;
023:
024: import org.apache.derby.iapi.services.context.ContextManager;
025:
026: import org.apache.derby.iapi.reference.SQLState;
027: import org.apache.derby.iapi.error.StandardException;
028:
029: import org.apache.derby.iapi.sql.conn.Authorizer;
030: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
031: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
032: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
033: import org.apache.derby.iapi.sql.dictionary.GenericDescriptorList;
034: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
035: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptorList;
036:
037: import org.apache.derby.iapi.sql.ResultSet;
038: import org.apache.derby.iapi.sql.StatementType;
039:
040: import org.apache.derby.iapi.sql.compile.CompilerContext;
041: import org.apache.derby.iapi.sql.compile.C_NodeTypes;
042: import org.apache.derby.iapi.reference.ClassName;
043:
044: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
045:
046: import org.apache.derby.iapi.sql.execute.CursorResultSet;
047: import org.apache.derby.iapi.sql.execute.ConstantAction;
048: import org.apache.derby.iapi.sql.execute.ExecPreparedStatement;
049: import org.apache.derby.iapi.sql.execute.ExecRow;
050:
051: import org.apache.derby.iapi.sql.Activation;
052:
053: import org.apache.derby.iapi.services.sanity.SanityManager;
054:
055: import org.apache.derby.iapi.services.compiler.MethodBuilder;
056:
057: import org.apache.derby.iapi.store.access.StaticCompiledOpenConglomInfo;
058: import org.apache.derby.iapi.store.access.TransactionController;
059:
060: import org.apache.derby.vti.DeferModification;
061:
062: import org.apache.derby.catalog.UUID;
063: import org.apache.derby.iapi.services.io.FormatableBitSet;
064:
065: import org.apache.derby.impl.sql.compile.ActivationClassBuilder;
066:
067: import org.apache.derby.impl.sql.execute.DeleteConstantAction;
068: import org.apache.derby.impl.sql.execute.FKInfo;
069:
070: import java.lang.reflect.Modifier;
071: import org.apache.derby.iapi.services.classfile.VMOpcode;
072: import org.apache.derby.iapi.services.io.FormatableProperties;
073: import java.util.Vector;
074: import java.util.Hashtable;
075: import java.util.Properties;
076: import org.apache.derby.iapi.sql.compile.NodeFactory;
077: import org.apache.derby.iapi.util.ReuseFactory;
078: import org.apache.derby.iapi.sql.depend.Dependent;
079: import org.apache.derby.iapi.sql.ResultDescription;
080: import org.apache.derby.iapi.services.compiler.LocalField;
081:
082: /**
083: * A DeleteNode represents a DELETE statement. It is the top-level node
084: * for the statement.
085: *
086: * For positioned delete, there may be no from table specified.
087: * The from table will be derived from the cursor specification of
088: * the named cursor.
089: *
090: * @author Jeff Lichtman
091: */
092:
093: public class DeleteNode extends DMLModStatementNode {
094: /* Column name for the RowLocation column in the ResultSet */
095: private static final String COLUMNNAME = "###RowLocationToDelete";
096:
097: /* Filled in by bind. */
098: protected boolean deferred;
099: protected ExecRow emptyHeapRow;
100: protected FromTable targetTable;
101: protected FKInfo fkInfo;
102: protected FormatableBitSet readColsBitSet;
103:
104: private ConstantAction[] dependentConstantActions;
105: private boolean cascadeDelete;
106: private QueryTreeNode[] dependentNodes;
107:
108: /**
109: * Initializer for a DeleteNode.
110: *
111: * @param targetTableName The name of the table to delete from
112: * @param queryExpression The query expression that will generate
113: * the rows to delete from the given table
114: */
115:
116: public void init(Object targetTableName, Object queryExpression) {
117: super .init(queryExpression);
118: this .targetTableName = (TableName) targetTableName;
119: }
120:
121: public String statementToString() {
122: return "DELETE";
123: }
124:
125: /**
126: * Bind this DeleteNode. This means looking up tables and columns and
127: * getting their types, and figuring out the result types of all
128: * expressions, as well as doing view resolution, permissions checking,
129: * etc.
130: * <p>
131: * If any indexes need to be updated, we add all the columns in the
132: * base table to the result column list, so that we can use the column
133: * values as look-up keys for the index rows to be deleted. Binding a
134: * delete will also massage the tree so that the ResultSetNode has
135: * column containing the RowLocation of the base row.
136: *
137: * @return The bound query tree
138: *
139: * @exception StandardException Thrown on error
140: */
141:
142: public QueryTreeNode bind() throws StandardException {
143: // We just need select privilege on the where clause tables
144: getCompilerContext()
145: .pushCurrentPrivType(Authorizer.SELECT_PRIV);
146: try {
147: FromList fromList = (FromList) getNodeFactory().getNode(
148: C_NodeTypes.FROM_LIST,
149: getNodeFactory().doJoinOrderOptimization(),
150: getContextManager());
151: ResultColumn rowLocationColumn = null;
152: CurrentRowLocationNode rowLocationNode;
153: TableName cursorTargetTableName = null;
154: CurrentOfNode currentOfNode = null;
155:
156: DataDictionary dataDictionary = getDataDictionary();
157: super .bindTables(dataDictionary);
158:
159: // wait to bind named target table until the underlying
160: // cursor is bound, so that we can get it from the
161: // cursor if this is a positioned delete.
162:
163: // for positioned delete, get the cursor's target table.
164: if (SanityManager.DEBUG)
165: SanityManager.ASSERT(resultSet != null
166: && resultSet instanceof SelectNode,
167: "Delete must have a select result set");
168:
169: SelectNode sel;
170: sel = (SelectNode) resultSet;
171: targetTable = (FromTable) sel.fromList.elementAt(0);
172: if (targetTable instanceof CurrentOfNode) {
173: currentOfNode = (CurrentOfNode) targetTable;
174:
175: cursorTargetTableName = currentOfNode
176: .getBaseCursorTargetTableName();
177: // instead of an assert, we might say the cursor is not updatable.
178: if (SanityManager.DEBUG)
179: SanityManager.ASSERT(cursorTargetTableName != null);
180: }
181:
182: if (targetTable instanceof FromVTI) {
183: targetVTI = (FromVTI) targetTable;
184: targetVTI.setTarget();
185: } else {
186: // positioned delete can leave off the target table.
187: // we get it from the cursor supplying the position.
188: if (targetTableName == null) {
189: // verify we have current of
190: if (SanityManager.DEBUG)
191: SanityManager
192: .ASSERT(cursorTargetTableName != null);
193:
194: targetTableName = cursorTargetTableName;
195: }
196: // for positioned delete, we need to verify that
197: // the named table is the same as the cursor's target (base table name).
198: else if (cursorTargetTableName != null) {
199: // this match requires that the named table in the delete
200: // be the same as a base name in the cursor.
201: if (!targetTableName.equals(cursorTargetTableName)) {
202: throw StandardException.newException(
203: SQLState.LANG_CURSOR_DELETE_MISMATCH,
204: targetTableName, currentOfNode
205: .getCursorName());
206: }
207: }
208: }
209:
210: // descriptor must exist, tables already bound.
211: verifyTargetTable();
212:
213: /* Generate a select list for the ResultSetNode - CurrentRowLocation(). */
214: if (SanityManager.DEBUG)
215: SanityManager
216: .ASSERT((resultSet.resultColumns == null),
217: "resultColumns is expected to be null until bind time");
218:
219: if (targetTable instanceof FromVTI) {
220: getResultColumnList();
221: resultColumnList = targetTable.getResultColumnsForList(
222: null, resultColumnList, null);
223:
224: /* Set the new result column list in the result set */
225: resultSet.setResultColumns(resultColumnList);
226: } else {
227: /*
228: ** Start off assuming no columns from the base table
229: ** are needed in the rcl.
230: */
231:
232: resultColumnList = new ResultColumnList();
233:
234: FromBaseTable fbt = getResultColumnList(resultColumnList);
235:
236: readColsBitSet = getReadMap(dataDictionary,
237: targetTableDescriptor);
238:
239: resultColumnList = fbt.addColsToList(resultColumnList,
240: readColsBitSet);
241:
242: /*
243: ** If all bits are set, then behave as if we chose all
244: ** in the first place
245: */
246: int i = 1;
247: int size = targetTableDescriptor.getMaxColumnID();
248: for (; i <= size; i++) {
249: if (!readColsBitSet.get(i)) {
250: break;
251: }
252: }
253:
254: if (i > size) {
255: readColsBitSet = null;
256: }
257:
258: /*
259: ** Construct an empty heap row for use in our constant action.
260: */
261: emptyHeapRow = targetTableDescriptor
262: .getEmptyExecRow(getContextManager());
263:
264: /* Generate the RowLocation column */
265: rowLocationNode = (CurrentRowLocationNode) getNodeFactory()
266: .getNode(C_NodeTypes.CURRENT_ROW_LOCATION_NODE,
267: getContextManager());
268: rowLocationColumn = (ResultColumn) getNodeFactory()
269: .getNode(C_NodeTypes.RESULT_COLUMN, COLUMNNAME,
270: rowLocationNode, getContextManager());
271: rowLocationColumn.markGenerated();
272:
273: /* Append to the ResultColumnList */
274: resultColumnList.addResultColumn(rowLocationColumn);
275:
276: /* Force the added columns to take on the table's correlation name, if any */
277: correlateAddedColumns(resultColumnList, targetTable);
278:
279: /* Set the new result column list in the result set */
280: resultSet.setResultColumns(resultColumnList);
281: }
282:
283: /* Bind the expressions before the ResultColumns are bound */
284: super .bindExpressions();
285:
286: /* Bind untyped nulls directly under the result columns */
287: resultSet.getResultColumns()
288: .bindUntypedNullsToResultColumns(resultColumnList);
289:
290: if (!(targetTable instanceof FromVTI)) {
291: /* Bind the new ResultColumn */
292: rowLocationColumn.bindResultColumnToExpression();
293:
294: bindConstraints(dataDictionary, getNodeFactory(),
295: targetTableDescriptor, null, resultColumnList,
296: (int[]) null, readColsBitSet, false, true); /* we alway include triggers in core language */
297:
298: /* If the target table is also a source table, then
299: * the delete will have to be in deferred mode
300: * For deletes, this means that the target table appears in a
301: * subquery. Also, self-referencing foreign key deletes
302: * are deferred. And triggers cause the delete to be deferred.
303: */
304: if (resultSet.subqueryReferencesTarget(
305: targetTableDescriptor.getName(), true)
306: || requiresDeferredProcessing()) {
307: deferred = true;
308: }
309: } else {
310: deferred = VTIDeferModPolicy.deferIt(
311: DeferModification.DELETE_STATEMENT, targetVTI,
312: null, sel.getWhereClause());
313: }
314: sel = null; // done with sel
315:
316: /* Verify that all underlying ResultSets reclaimed their FromList */
317: if (SanityManager.DEBUG) {
318: SanityManager
319: .ASSERT(
320: fromList.size() == 0,
321: "fromList.size() is expected to be 0, not "
322: + fromList.size()
323: + " on return from RS.bindExpressions()");
324: }
325:
326: //In case of cascade delete , create nodes for
327: //the ref action dependent tables and bind them.
328: if (fkTableNames != null) {
329: String currentTargetTableName = targetTableDescriptor
330: .getSchemaName()
331: + "." + targetTableDescriptor.getName();
332:
333: if (!isDependentTable) {
334: //graph node
335: graphHashTable = new Hashtable();
336: }
337:
338: /*Check whether the current tatget is already been explored.
339: *If we are seeing the same table name which we binded earlier
340: *means we have cyclic references.
341: */
342: if (!graphHashTable.containsKey(currentTargetTableName)) {
343: cascadeDelete = true;
344: int noDependents = fkTableNames.length;
345: dependentNodes = new QueryTreeNode[noDependents];
346: graphHashTable.put(currentTargetTableName,
347: new Integer(noDependents));
348: for (int i = 0; i < noDependents; i++) {
349: dependentNodes[i] = getDependentTableNode(
350: fkTableNames[i], fkRefActions[i],
351: fkColDescriptors[i]);
352: dependentNodes[i].bind();
353: }
354: }
355: } else {
356: //case where current dependent table does not have dependent tables
357: if (isDependentTable) {
358: String currentTargetTableName = targetTableDescriptor
359: .getSchemaName()
360: + "." + targetTableDescriptor.getName();
361: graphHashTable.put(currentTargetTableName,
362: new Integer(0));
363:
364: }
365: }
366: if (isPrivilegeCollectionRequired()) {
367: getCompilerContext().pushCurrentPrivType(getPrivType());
368: getCompilerContext().addRequiredTablePriv(
369: targetTableDescriptor);
370: getCompilerContext().popCurrentPrivType();
371: }
372: } finally {
373: getCompilerContext().popCurrentPrivType();
374: }
375: return this ;
376: } // end of bind
377:
378: int getPrivType() {
379: return Authorizer.DELETE_PRIV;
380: }
381:
382: /**
383: * Return true if the node references SESSION schema tables (temporary or permanent)
384: *
385: * @return true if references SESSION schema tables, else false
386: *
387: * @exception StandardException Thrown on error
388: */
389: public boolean referencesSessionSchema() throws StandardException {
390: //If delete table is on a SESSION schema table, then return true.
391: return resultSet.referencesSessionSchema();
392: }
393:
394: /**
395: * Compile constants that Execution will use
396: *
397: * @exception StandardException Thrown on failure
398: */
399: public ConstantAction makeConstantAction() throws StandardException {
400:
401: /* Different constant actions for base tables and updatable VTIs */
402: if (targetTableDescriptor != null) {
403: // Base table
404: int lockMode = resultSet.updateTargetLockMode();
405: long heapConglomId = targetTableDescriptor
406: .getHeapConglomerateId();
407: TransactionController tc = getLanguageConnectionContext()
408: .getTransactionCompile();
409: StaticCompiledOpenConglomInfo[] indexSCOCIs = new StaticCompiledOpenConglomInfo[indexConglomerateNumbers.length];
410:
411: for (int index = 0; index < indexSCOCIs.length; index++) {
412: indexSCOCIs[index] = tc
413: .getStaticCompiledConglomInfo(indexConglomerateNumbers[index]);
414: }
415:
416: /*
417: ** Do table locking if the table's lock granularity is
418: ** set to table.
419: */
420: if (targetTableDescriptor.getLockGranularity() == TableDescriptor.TABLE_LOCK_GRANULARITY) {
421: lockMode = TransactionController.MODE_TABLE;
422: }
423:
424: ResultDescription resultDescription = null;
425: if (isDependentTable) {
426: //triggers need the result description ,
427: //dependent tables don't have a source from generation time
428: //to get the result description
429: resultDescription = makeResultDescription();
430: }
431:
432: return getGenericConstantActionFactory()
433: .getDeleteConstantAction(
434: heapConglomId,
435: targetTableDescriptor.getTableType(),
436: tc
437: .getStaticCompiledConglomInfo(heapConglomId),
438: indicesToMaintain,
439: indexConglomerateNumbers,
440: indexSCOCIs,
441: emptyHeapRow,
442: deferred,
443: false,
444: targetTableDescriptor.getUUID(),
445: lockMode,
446: null,
447: null,
448: null,
449: 0,
450: null,
451: null,
452: resultDescription,
453: getFKInfo(),
454: getTriggerInfo(),
455: (readColsBitSet == null) ? (FormatableBitSet) null
456: : new FormatableBitSet(
457: readColsBitSet),
458: getReadColMap(targetTableDescriptor
459: .getNumberOfColumns(),
460: readColsBitSet),
461: resultColumnList
462: .getStreamStorableColIds(targetTableDescriptor
463: .getNumberOfColumns()),
464: (readColsBitSet == null) ? targetTableDescriptor
465: .getNumberOfColumns()
466: : readColsBitSet.getNumBitsSet(),
467: (UUID) null, resultSet.isOneRowResultSet(),
468: dependentConstantActions);
469: } else {
470: /* Return constant action for VTI
471: * NOTE: ConstantAction responsible for preserving instantiated
472: * VTIs for in-memory queries and for only preserving VTIs
473: * that implement Serializable for SPSs.
474: */
475: return getGenericConstantActionFactory()
476: .getUpdatableVTIConstantAction(
477: DeferModification.DELETE_STATEMENT,
478: deferred);
479: }
480: }
481:
482: /**
483: * Code generation for delete.
484: * The generated code will contain:
485: * o A static member for the (xxx)ResultSet with the RowLocations
486: * o The static member will be assigned the appropriate ResultSet within
487: * the nested calls to get the ResultSets. (The appropriate cast to the
488: * (xxx)ResultSet will be generated.)
489: * o The CurrentRowLocation() in SelectNode's select list will generate
490: * a new method for returning the RowLocation as well as a call to
491: * that method which will be stuffed in the call to the
492: * ProjectRestrictResultSet.
493: * o In case of referential actions, this function generate an
494: * array of resultsets on its dependent tables.
495: *
496: * @param acb The ActivationClassBuilder for the class being built
497: * @param mb The execute() method to be built
498: *
499: * @exception StandardException Thrown on error
500: */
501: public void generate(ActivationClassBuilder acb, MethodBuilder mb)
502: throws StandardException {
503:
504: //If the DML is on the temporary table, generate the code to mark temporary table as modified in the current UOW
505: generateCodeForTemporaryTable(acb, mb);
506:
507: /* generate the parameters */
508: if (!isDependentTable)
509: generateParameterValueSet(acb);
510:
511: acb.pushGetResultSetFactoryExpression(mb);
512: acb.newRowLocationScanResultSetName();
513: resultSet.generate(acb, mb); // arg 1
514:
515: String resultSetGetter;
516: int argCount;
517: String parentResultSetId;
518:
519: // Base table
520: if (targetTableDescriptor != null) {
521: /* Create the declaration for the scan ResultSet which generates the
522: * RowLocations to be deleted.
523: * Note that the field cannot be static because there
524: * can be multiple activations of the same activation class,
525: * and they can't share this field. Only exprN fields can
526: * be shared (or, more generally, read-only fields).
527: * RESOLVE - Need to deal with the type of the field.
528: */
529:
530: acb.newFieldDeclaration(Modifier.PRIVATE,
531: ClassName.CursorResultSet, acb
532: .getRowLocationScanResultSetName());
533:
534: if (cascadeDelete || isDependentTable) {
535: resultSetGetter = "getDeleteCascadeResultSet";
536: argCount = 4;
537: } else {
538: resultSetGetter = "getDeleteResultSet";
539: argCount = 1;
540: }
541:
542: } else {
543: argCount = 1;
544: resultSetGetter = "getDeleteVTIResultSet";
545: }
546:
547: if (isDependentTable) {
548: mb.push(acb.addItem(makeConstantAction()));
549:
550: } else {
551: if (cascadeDelete) {
552: mb.push(-1); //root table.
553: }
554: }
555:
556: String resultSetArrayType = ClassName.ResultSet + "[]";
557: if (cascadeDelete) {
558: parentResultSetId = targetTableDescriptor.getSchemaName()
559: + "." + targetTableDescriptor.getName();
560: // Generate the code to build the array
561: LocalField arrayField = acb.newFieldDeclaration(
562: Modifier.PRIVATE, resultSetArrayType);
563: mb.pushNewArray(ClassName.ResultSet, dependentNodes.length); // new ResultSet[size]
564: mb.setField(arrayField);
565: for (int index = 0; index < dependentNodes.length; index++) {
566:
567: dependentNodes[index].setRefActionInfo(
568: fkIndexConglomNumbers[index],
569: fkColArrays[index], parentResultSetId, true);
570: mb.getField(arrayField); // first arg (resultset array reference)
571: /*beetle:5360 : if too many statements are added to a method,
572: *size of method can hit 65k limit, which will
573: *lead to the class format errors at load time.
574: *To avoid this problem, when number of statements added
575: *to a method is > 2048, remaing statements are added to a new function
576: *and called from the function which created the function.
577: *See Beetle 5135 or 4293 for further details on this type of problem.
578: */
579: if (mb.statementNumHitLimit(10)) {
580: MethodBuilder dmb = acb.newGeneratedFun(
581: ClassName.ResultSet, Modifier.PRIVATE);
582: dependentNodes[index].generate(acb, dmb); //generates the resultset expression
583: dmb.methodReturn();
584: dmb.complete();
585: /* Generate the call to the new method */
586: mb.pushThis();
587: //second arg will be generated by this call
588: mb.callMethod(VMOpcode.INVOKEVIRTUAL,
589: (String) null, dmb.getName(),
590: ClassName.ResultSet, 0);
591: } else {
592: dependentNodes[index].generate(acb, mb); //generates the resultset expression
593: }
594:
595: mb.setArrayElement(index);
596: }
597: mb.getField(arrayField); // fourth argument - array reference
598: } else {
599: if (isDependentTable) {
600: mb.pushNull(resultSetArrayType); //No dependent tables for this table
601: }
602: }
603:
604: if (cascadeDelete || isDependentTable) {
605: parentResultSetId = targetTableDescriptor.getSchemaName()
606: + "." + targetTableDescriptor.getName();
607: mb.push(parentResultSetId);
608:
609: }
610: mb.callMethod(VMOpcode.INVOKEINTERFACE, (String) null,
611: resultSetGetter, ClassName.ResultSet, argCount);
612:
613: if (!isDependentTable && cascadeDelete) {
614: int numResultSets = acb.getRowCount();
615: if (numResultSets > 0) {
616: //generate activation.raParentResultSets = new NoPutResultSet[size]
617: MethodBuilder constructor = acb.getConstructor();
618: constructor.pushThis();
619: constructor.pushNewArray(ClassName.CursorResultSet,
620: numResultSets);
621: constructor.putField(ClassName.BaseActivation,
622: "raParentResultSets", ClassName.CursorResultSet
623: + "[]");
624: constructor.endStatement();
625: }
626: }
627: }
628:
629: /**
630: * Return the type of statement, something from
631: * StatementType.
632: *
633: * @return the type of statement
634: */
635: protected final int getStatementType() {
636: return StatementType.DELETE;
637: }
638:
639: /**
640: * Gets the map of all columns which must be read out of the base table.
641: * These are the columns needed to:
642: *
643: * o maintain indices
644: * o maintain foreign keys
645: *
646: * The returned map is a FormatableBitSet with 1 bit for each column in the
647: * table plus an extra, unsued 0-bit. If a 1-based column id must
648: * be read from the base table, then the corresponding 1-based bit
649: * is turned ON in the returned FormatableBitSet.
650: *
651: * @param dd the data dictionary to look in
652: * @param baseTable the base table descriptor
653: *
654: * @return a FormatableBitSet of columns to be read out of the base table
655: *
656: * @exception StandardException Thrown on error
657: */
658: public FormatableBitSet getReadMap(DataDictionary dd,
659: TableDescriptor baseTable) throws StandardException {
660: boolean[] needsDeferredProcessing = new boolean[1];
661: needsDeferredProcessing[0] = requiresDeferredProcessing();
662:
663: Vector conglomVector = new Vector();
664: relevantTriggers = new GenericDescriptorList();
665:
666: FormatableBitSet columnMap = DeleteNode.getDeleteReadMap(
667: baseTable, conglomVector, relevantTriggers,
668: needsDeferredProcessing);
669:
670: markAffectedIndexes(conglomVector);
671:
672: adjustDeferredFlag(needsDeferredProcessing[0]);
673:
674: return columnMap;
675: }
676:
677: /**
678: * In case of referential actions, we require to perform
679: * DML (UPDATE or DELETE) on the dependent tables.
680: * Following function returns the DML Node for the dependent table.
681: */
682: private QueryTreeNode getDependentTableNode(String tableName,
683: int refAction, ColumnDescriptorList cdl)
684: throws StandardException {
685: QueryTreeNode node = null;
686:
687: int index = tableName.indexOf('.');
688: String schemaName = tableName.substring(0, index);
689: String tName = tableName.substring(index + 1);
690: if (refAction == StatementType.RA_CASCADE) {
691: node = getEmptyDeleteNode(schemaName, tName);
692: ((DeleteNode) node).isDependentTable = true;
693: ((DeleteNode) node).graphHashTable = graphHashTable;
694: }
695:
696: if (refAction == StatementType.RA_SETNULL) {
697: node = getEmptyUpdateNode(schemaName, tName, cdl);
698: ((UpdateNode) node).isDependentTable = true;
699: ((UpdateNode) node).graphHashTable = graphHashTable;
700: }
701:
702: return node;
703: }
704:
705: private QueryTreeNode getEmptyDeleteNode(String schemaName,
706: String targetTableName) throws StandardException {
707:
708: ValueNode whereClause = null;
709: TableName tableName = null;
710: FromTable fromTable = null;
711: QueryTreeNode retval;
712: SelectNode resultSet;
713:
714: tableName = new TableName();
715: tableName.init(schemaName, targetTableName);
716:
717: NodeFactory nodeFactory = getNodeFactory();
718: FromList fromList = (FromList) nodeFactory.getNode(
719: C_NodeTypes.FROM_LIST, getContextManager());
720: fromTable = (FromTable) nodeFactory.getNode(
721: C_NodeTypes.FROM_BASE_TABLE, tableName, null,
722: ReuseFactory.getInteger(FromBaseTable.DELETE), null,
723: getContextManager());
724:
725: //we would like to use references index & table scan instead of
726: //what optimizer says for the dependent table scan.
727: Properties targetProperties = new FormatableProperties();
728: targetProperties.put("index", "null");
729: ((FromBaseTable) fromTable)
730: .setTableProperties(targetProperties);
731:
732: fromList.addFromTable(fromTable);
733: resultSet = (SelectNode) nodeFactory.getNode(
734: C_NodeTypes.SELECT_NODE, null, null, /* AGGREGATE list */
735: fromList, /* FROM list */
736: whereClause, /* WHERE clause */
737: null, /* GROUP BY list */
738: getContextManager());
739:
740: retval = (QueryTreeNode) nodeFactory.getNode(
741: C_NodeTypes.DELETE_NODE, tableName, resultSet,
742: getContextManager());
743:
744: return retval;
745: }
746:
747: private QueryTreeNode getEmptyUpdateNode(String schemaName,
748: String targetTableName, ColumnDescriptorList cdl)
749: throws StandardException {
750:
751: ValueNode whereClause = null;
752: TableName tableName = null;
753: FromTable fromTable = null;
754: QueryTreeNode retval;
755: SelectNode resultSet;
756:
757: tableName = new TableName();
758: tableName.init(schemaName, targetTableName);
759:
760: NodeFactory nodeFactory = getNodeFactory();
761: FromList fromList = (FromList) nodeFactory.getNode(
762: C_NodeTypes.FROM_LIST, getContextManager());
763: fromTable = (FromTable) nodeFactory.getNode(
764: C_NodeTypes.FROM_BASE_TABLE, tableName, null,
765: ReuseFactory.getInteger(FromBaseTable.DELETE), null,
766: getContextManager());
767:
768: //we would like to use references index & table scan instead of
769: //what optimizer says for the dependent table scan.
770: Properties targetProperties = new FormatableProperties();
771: targetProperties.put("index", "null");
772: ((FromBaseTable) fromTable)
773: .setTableProperties(targetProperties);
774:
775: fromList.addFromTable(fromTable);
776:
777: resultSet = (SelectNode) nodeFactory.getNode(
778: C_NodeTypes.SELECT_NODE, getSetClause(tableName, cdl),
779: null, /* AGGREGATE list */
780: fromList, /* FROM list */
781: whereClause, /* WHERE clause */
782: null, /* GROUP BY list */
783: getContextManager());
784:
785: retval = (QueryTreeNode) nodeFactory.getNode(
786: C_NodeTypes.UPDATE_NODE, tableName, resultSet,
787: getContextManager());
788:
789: return retval;
790: }
791:
792: private ResultColumnList getSetClause(TableName tabName,
793: ColumnDescriptorList cdl) throws StandardException {
794: ResultColumn resultColumn;
795: ValueNode valueNode;
796:
797: NodeFactory nodeFactory = getNodeFactory();
798: ResultColumnList columnList = (ResultColumnList) nodeFactory
799: .getNode(C_NodeTypes.RESULT_COLUMN_LIST,
800: getContextManager());
801:
802: valueNode = (ValueNode) nodeFactory.getNode(
803: C_NodeTypes.UNTYPED_NULL_CONSTANT_NODE,
804: getContextManager());
805: for (int index = 0; index < cdl.size(); index++) {
806: ColumnDescriptor cd = (ColumnDescriptor) cdl
807: .elementAt(index);
808: //only columns that are nullable need to be set to 'null' for ON
809: //DELETE SET NULL
810: if ((cd.getType()).isNullable()) {
811: resultColumn = (ResultColumn) nodeFactory.getNode(
812: C_NodeTypes.RESULT_COLUMN, cd, valueNode,
813: getContextManager());
814:
815: columnList.addResultColumn(resultColumn);
816: }
817: }
818: return columnList;
819: }
820:
821: public QueryTreeNode optimize() throws StandardException {
822: if (cascadeDelete) {
823: for (int index = 0; index < dependentNodes.length; index++) {
824: dependentNodes[index] = dependentNodes[index]
825: .optimize();
826: }
827: }
828:
829: return super .optimize();
830: }
831:
832: /**
833: * Builds a bitmap of all columns which should be read from the
834: * Store in order to satisfy an DELETE statement.
835: *
836: *
837: * 1) finds all indices on this table
838: * 2) adds the index columns to a bitmap of affected columns
839: * 3) adds the index descriptors to a list of conglomerate
840: * descriptors.
841: * 4) finds all DELETE triggers on the table
842: * 5) if there are any DELETE triggers, marks all columns in the bitmap
843: * 6) adds the triggers to an evolving list of triggers
844: *
845: * @param conglomVector OUT: vector of affected indices
846: * @param relevantTriggers IN/OUT. Passed in as an empty list. Filled in as we go.
847: * @param needsDeferredProcessing IN/OUT. true if the statement already needs
848: * deferred processing. set while evaluating this
849: * routine if a trigger requires
850: * deferred processing
851: *
852: * @return a FormatableBitSet of columns to be read out of the base table
853: *
854: * @exception StandardException Thrown on error
855: */
856: private static FormatableBitSet getDeleteReadMap(
857: TableDescriptor baseTable, Vector conglomVector,
858: GenericDescriptorList relevantTriggers,
859: boolean[] needsDeferredProcessing) throws StandardException {
860: int columnCount = baseTable.getMaxColumnID();
861: FormatableBitSet columnMap = new FormatableBitSet(
862: columnCount + 1);
863:
864: /*
865: ** Get a list of the indexes that need to be
866: ** updated. ColumnMap contains all indexed
867: ** columns where 1 or more columns in the index
868: ** are going to be modified.
869: **
870: ** Notice that we don't need to add constraint
871: ** columns. This is because we add all key constraints
872: ** (e.g. foreign keys) as a side effect of adding their
873: ** indexes above. And we don't need to deal with
874: ** check constraints on a delete.
875: **
876: ** Adding indexes also takes care of the replication
877: ** requirement of having the primary key.
878: */
879: DMLModStatementNode.getXAffectedIndexes(baseTable, null,
880: columnMap, conglomVector);
881:
882: /*
883: ** If we have any triggers, then get all the columns
884: ** because we don't know what the user will ultimately
885: ** reference.
886: */
887: baseTable.getAllRelevantTriggers(StatementType.DELETE,
888: (int[]) null, relevantTriggers);
889: if (relevantTriggers.size() > 0) {
890: needsDeferredProcessing[0] = true;
891: }
892:
893: if (relevantTriggers.size() > 0) {
894: for (int i = 1; i <= columnCount; i++) {
895: columnMap.set(i);
896: }
897: }
898:
899: return columnMap;
900: }
901:
902: /*
903: * Force column references (particularly those added by the compiler)
904: * to use the correlation name on the base table, if any.
905: */
906: private void correlateAddedColumns(ResultColumnList rcl,
907: FromTable fromTable) throws StandardException {
908: String correlationName = fromTable.getCorrelationName();
909:
910: if (correlationName == null) {
911: return;
912: }
913:
914: TableName correlationNameNode = makeTableName(null,
915: correlationName);
916: int count = rcl.size();
917:
918: for (int i = 0; i < count; i++) {
919: ResultColumn column = (ResultColumn) rcl.elementAt(i);
920:
921: ValueNode expression = column.getExpression();
922:
923: if ((expression != null)
924: && (expression instanceof ColumnReference)) {
925: ColumnReference reference = (ColumnReference) expression;
926:
927: reference.setTableNameNode(correlationNameNode);
928: }
929: }
930:
931: }
932:
933: }
|