001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.execute.InternalTriggerExecutionContext
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.PublicAPI;
026: import org.apache.derby.iapi.db.TriggerExecutionContext;
027: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
028: import org.apache.derby.iapi.sql.ResultSet;
029: import org.apache.derby.iapi.sql.execute.CursorResultSet;
030: import org.apache.derby.iapi.sql.execute.ExecRow;
031: import org.apache.derby.iapi.types.DataValueDescriptor;
032: import org.apache.derby.iapi.sql.execute.ExecutionStmtValidator;
033: import org.apache.derby.iapi.sql.execute.ConstantAction;
034: import org.apache.derby.iapi.sql.dictionary.TriggerDescriptor;
035: import org.apache.derby.iapi.error.StandardException;
036: import org.apache.derby.iapi.services.i18n.MessageService;
037: import org.apache.derby.iapi.reference.SQLState;
038: import org.apache.derby.iapi.jdbc.ConnectionContext;
039: import org.apache.derby.catalog.UUID;
040: import java.util.Enumeration;
041: import java.util.Vector;
042: import java.util.Hashtable;
043: import java.sql.Connection;
044: import java.sql.SQLException;
045: import java.sql.Statement;
046: import org.apache.derby.iapi.error.ExceptionSeverity;
047:
048: /**
049: * There is one of these beasts per INSERT/DELETE/UPDATE
050: * statement. It fulfills the contract for the externally
051: * visible trigger execution context and it validates
052: * that a statement that is about to be executed doesn't
053: * violate the restrictions placed upon what can be executed
054: * from a trigger.
055: * <p>
056: * Note that it is crucial that cleanup() is called once
057: * the DML has completed, cleanup() makes sure that users
058: * can't do something invalid on a tec reference that they
059: * were holding from when the trigger fired.
060: *
061: */
062: public class InternalTriggerExecutionContext implements
063: TriggerExecutionContext, ExecutionStmtValidator {
064: /*
065: ** Immutable
066: */
067: protected int[] changedColIds;
068: protected String[] changedColNames;
069: protected int dmlType;
070: protected String statementText;
071: protected ConnectionContext cc;
072: protected UUID targetTableId;
073: protected String targetTableName;
074: protected LanguageConnectionContext lcc;
075:
076: /*
077: ** Mutable
078: */
079: protected CursorResultSet beforeResultSet;
080: protected CursorResultSet afterResultSet;
081:
082: /**
083: * used exclusively for InsertResultSets which have autoincrement
084: * columns.
085: */
086: protected ExecRow afterRow;
087:
088: protected boolean cleanupCalled;
089: protected TriggerEvent event;
090: protected TriggerDescriptor triggerd;
091:
092: /*
093: ** Used to track all the result sets we have given out to
094: ** users. When the trigger context is no longer valid,
095: ** we close all the result sets that may be in the user
096: ** space because they can no longer provide meaningful
097: ** results.
098: */
099: private Vector resultSetVector;
100:
101: /**
102: * aiCounters is a vector of AutoincrementCounters used to keep state which
103: * might be used by the trigger. This is only used by Insert triggers--
104: * Delete and Update triggers do not use this variable.
105: *
106: * @see AutoincrementCounter
107: *
108: */
109: private Vector aiCounters;
110:
111: /**
112: * aiHT is a hashtable of autincrement <key, value> pairs. This is used for
113: * ai values generated by the trigger.
114: */
115: private Hashtable aiHT;
116:
117: /**
118: * Build me a big old nasty trigger execution context.
119: * Damnit.
120: * <p>
121: * About the only thing of real interest to outside observers
122: * is that it pushes itself as the trigger execution context
123: * in the lcc. Be sure to call <i>cleanup()</i> when you
124: * are done, or you will be flogged like the reprobate that
125: * you are.
126: *
127: * @param lcc the lcc
128: * @param statementText the text of the statement that caused the
129: * trigger to fire. may be null if we are replicating
130: * @param changedColIds the list of columns that changed. Null
131: * for all columns or INSERT/DELETE.
132: * @param changedColNames the names that correspond to changedColIds
133: * @param targetTableId the UUID of the table upon which the trigger
134: * fired
135: * @param targetTableName the name of the table upon which the trigger
136: * fired
137: * @param aiCounters A vector of AutoincrementCounters to keep state
138: * of the ai columns in this insert trigger.a
139: *
140: * @exception StandardException on error
141: */
142: public InternalTriggerExecutionContext(
143: LanguageConnectionContext lcc, ConnectionContext cc,
144: String statementText, int dmlType, int[] changedColIds,
145: String[] changedColNames, UUID targetTableId,
146: String targetTableName, Vector aiCounters)
147: throws StandardException {
148: this .dmlType = dmlType;
149: this .changedColIds = changedColIds;
150: this .changedColNames = changedColNames;
151: this .statementText = statementText;
152: this .cc = cc;
153: this .lcc = lcc;
154: this .targetTableId = targetTableId;
155: this .targetTableName = targetTableName;
156: this .resultSetVector = new Vector();
157: this .aiCounters = aiCounters;
158:
159: if (SanityManager.DEBUG) {
160: if ((changedColIds == null) != (changedColNames == null)) {
161: SanityManager.THROWASSERT("bad changed cols, "
162: + "(changedColsIds == null) = "
163: + (changedColIds == null)
164: + " (changedColsNames == null) = "
165: + (changedColNames == null));
166: }
167: if (changedColIds != null) {
168: SanityManager.ASSERT(
169: changedColIds.length == changedColNames.length,
170: "different number of changed col ids vs names");
171: }
172: }
173:
174: lcc.pushTriggerExecutionContext(this );
175: }
176:
177: void setBeforeResultSet(CursorResultSet rs) {
178: beforeResultSet = rs;
179: }
180:
181: void setAfterResultSet(CursorResultSet rs) throws StandardException {
182: afterResultSet = rs;
183:
184: if (aiCounters != null) {
185: if (triggerd.isRowTrigger()) {
186: // An after row trigger needs to see the "first" row inserted
187: rs.open();
188: afterRow = rs.getNextRow();
189: rs.close();
190: } else {
191: // after statement trigger needs to look at the last value.
192: if (!triggerd.isBeforeTrigger())
193: resetAICounters(false);
194: }
195: }
196: }
197:
198: void setCurrentTriggerEvent(TriggerEvent event) {
199: this .event = event;
200: }
201:
202: void clearCurrentTriggerEvent() {
203: event = null;
204: }
205:
206: void setTrigger(TriggerDescriptor triggerd) {
207: this .triggerd = triggerd;
208: }
209:
210: void clearTrigger() throws StandardException {
211: event = null;
212: triggerd = null;
213: if (afterResultSet != null) {
214: afterResultSet.close();
215: afterResultSet = null;
216: }
217: if (beforeResultSet != null) {
218: beforeResultSet.close();
219: beforeResultSet = null;
220: }
221: }
222:
223: /**
224: * Cleanup the trigger execution context. <B>MUST</B>
225: * be called when the caller is done with the trigger
226: * execution context.
227: * <p>
228: * We go to somewhat exaggerated lengths to free up
229: * all our resources here because a user may hold on
230: * to a TEC after it is valid, so we clean everything
231: * up to be on the safe side.
232: *
233: * @exception StandardException on unexpected error
234: */
235: protected void cleanup() throws StandardException {
236: lcc.popTriggerExecutionContext(this );
237:
238: /*
239: ** Explicitly close all result sets that we have
240: ** given out to the user.
241: */
242: for (Enumeration e = resultSetVector.elements(); e
243: .hasMoreElements();) {
244: java.sql.ResultSet rs = (java.sql.ResultSet) e
245: .nextElement();
246: try {
247: rs.close();
248: } catch (SQLException se) {
249: }
250: }
251: resultSetVector = null;
252:
253: /*
254: ** We should have already closed our underlying
255: ** ExecResultSets by closing the jdbc result sets,
256: ** but in case we got an error that we caught and
257: ** ignored, explicitly close them.
258: */
259: if (afterResultSet != null) {
260: afterResultSet.close();
261: afterResultSet = null;
262: }
263: if (beforeResultSet != null) {
264: beforeResultSet.close();
265: beforeResultSet = null;
266: }
267:
268: lcc = null;
269: cleanupCalled = true;
270: }
271:
272: /**
273: * Make sure that the user isn't trying to get a result
274: * set after we have cleaned up.
275: */
276: private void ensureProperContext() throws SQLException {
277: if (cleanupCalled) {
278: throw new SQLException(
279: MessageService
280: .getTextMessage(SQLState.LANG_STATEMENT_CLOSED_NO_REASON),
281: "XCL31", ExceptionSeverity.STATEMENT_SEVERITY);
282: }
283: }
284:
285: /////////////////////////////////////////////////////////
286: //
287: // ExecutionStmtValidator
288: //
289: /////////////////////////////////////////////////////////
290: /**
291: * Make sure that whatever statement is about to be executed
292: * is ok from the context of this trigger.
293: * <p>
294: * Note that we are sub classed in replication for checks
295: * for replication specific language.
296: *
297: * @param constantAction the constant action of the action
298: * that we are to validate
299: *
300: * @exception StandardException on error
301: */
302: public void validateStatement(ConstantAction constantAction)
303: throws StandardException {
304:
305: // DDL statements are not allowed in triggers. Direct use of DDL
306: // statements in a trigger's action statement is disallowed by the
307: // parser. However, this runtime check is needed to prevent execution
308: // of DDL statements by procedures within a trigger context.
309: if (constantAction instanceof DDLConstantAction) {
310: throw StandardException.newException(
311: SQLState.LANG_NO_DDL_IN_TRIGGER,
312: triggerd.getName(), constantAction.toString());
313: }
314:
315: // No INSERT/UPDATE/DELETE for a before trigger. There is no need to
316: // check this here because parser does not allow these DML statements
317: // in a trigger's action statement in a before trigger. Parser also
318: // disallows creation of before triggers calling procedures that modify
319: // SQL data.
320:
321: }
322:
323: /////////////////////////////////////////////////////////
324: //
325: // TriggerExectionContext
326: //
327: /////////////////////////////////////////////////////////
328:
329: /**
330: * Get the target table name upon which the
331: * trigger event is declared.
332: *
333: * @return the target table
334: */
335: public String getTargetTableName() {
336: return targetTableName;
337: }
338:
339: /**
340: * Get the target table UUID upon which the
341: * trigger event is declared.
342: *
343: * @return the uuid of the target table
344: */
345: public UUID getTargetTableId() {
346: return targetTableId;
347: }
348:
349: /**
350: * Get the type for the event that caused the
351: * trigger to fire.
352: *
353: * @return the event type (e.g. UPDATE_EVENT)
354: */
355: public int getEventType() {
356: return dmlType;
357: }
358:
359: /**
360: * Get the text of the statement that caused the
361: * trigger to fire.
362: *
363: * @return the statement text
364: */
365: public String getEventStatementText() {
366: return statementText;
367: }
368:
369: /**
370: * Get the columns that have been modified by the statement
371: * that caused this trigger to fire. If all columns are
372: * modified, will return null (e.g. for INSERT or DELETE will
373: * return null).
374: *
375: * @return an array of Strings
376: */
377: public String[] getModifiedColumns() {
378: return changedColNames;
379: }
380:
381: /**
382: * Find out of a column was changed, by column name
383: *
384: * @param columnName the column to check
385: *
386: * @return true if the column was modified by this statement.
387: * Note that this will always return true for INSERT
388: * and DELETE regardless of the column name passed in.
389: */
390: public boolean wasColumnModified(String columnName) {
391: if (changedColNames == null) {
392: return true;
393: }
394:
395: for (int i = 0; i < changedColNames.length; i++) {
396: if (changedColNames[i].equals(columnName)) {
397: return true;
398: }
399: }
400: return false;
401: }
402:
403: /**
404: * Find out of a column was changed, by column number
405: *
406: * @param columnNumber the column to check
407: *
408: * @return true if the column was modified by this statement.
409: * Note that this will always return true for INSERT
410: * and DELETE regardless of the column name passed in.
411: */
412: public boolean wasColumnModified(int columnNumber) {
413: if (changedColIds == null) {
414: return true;
415: }
416:
417: for (int i = 0; i < changedColNames.length; i++) {
418: if (changedColIds[i] == columnNumber) {
419: return true;
420: }
421: }
422: return false;
423: }
424:
425: /**
426: * Returns a result set row the old images of the changed rows.
427: * For a row trigger, the result set will have a single row. For
428: * a statement trigger, this result set has every row that has
429: * changed or will change. If a statement trigger does not affect
430: * a row, then the result set will be empty (i.e. ResultSet.next()
431: * will return false).
432: *
433: * @return the ResultSet containing before images of the rows
434: * changed by the triggering event.
435: *
436: * @exception SQLException if called after the triggering event has
437: * completed
438: */
439: public java.sql.ResultSet getOldRowSet() throws SQLException {
440: ensureProperContext();
441: if (beforeResultSet == null) {
442: return null;
443: }
444:
445: try {
446: CursorResultSet brs = beforeResultSet;
447: /* We should really shallow clone the result set, because it could be used
448: * at multiple places independently in trigger action. This is a bug found
449: * during the fix of beetle 4373.
450: */
451: if (brs instanceof TemporaryRowHolderResultSet)
452: brs = (CursorResultSet) ((TemporaryRowHolderResultSet) brs)
453: .clone();
454: else if (brs instanceof TableScanResultSet)
455: brs = (CursorResultSet) ((TableScanResultSet) brs)
456: .clone();
457: brs.open();
458: java.sql.ResultSet rs = cc.getResultSet(brs);
459: resultSetVector.addElement(rs);
460: return rs;
461: } catch (StandardException se) {
462: throw PublicAPI.wrapStandardException(se);
463: }
464: }
465:
466: /**
467: * Returns a result set row the new images of the changed rows.
468: * For a row trigger, the result set will have a single row. For
469: * a statement trigger, this result set has every row that has
470: * changed or will change. If a statement trigger does not affect
471: * a row, then the result set will be empty (i.e. ResultSet.next()
472: * will return false).
473: *
474: * @return the ResultSet containing after images of the rows
475: * changed by the triggering event.
476: *
477: * @exception SQLException if called after the triggering event has
478: * completed
479: */
480: public java.sql.ResultSet getNewRowSet() throws SQLException {
481: ensureProperContext();
482:
483: if (afterResultSet == null) {
484: return null;
485: }
486: try {
487: /* We should really shallow clone the result set, because it could be used
488: * at multiple places independently in trigger action. This is a bug found
489: * during the fix of beetle 4373.
490: */
491: CursorResultSet ars = afterResultSet;
492: if (ars instanceof TemporaryRowHolderResultSet)
493: ars = (CursorResultSet) ((TemporaryRowHolderResultSet) ars)
494: .clone();
495: else if (ars instanceof TableScanResultSet)
496: ars = (CursorResultSet) ((TableScanResultSet) ars)
497: .clone();
498: ars.open();
499: java.sql.ResultSet rs = cc.getResultSet(ars);
500: resultSetVector.addElement(rs);
501: return rs;
502: } catch (StandardException se) {
503: throw PublicAPI.wrapStandardException(se);
504: }
505: }
506:
507: /**
508: * Like getBeforeResultSet(), but returns a result set positioned
509: * on the first row of the before result set. Used as a convenience
510: * to get a column for a row trigger. Equivalent to getBeforeResultSet()
511: * followed by next().
512: *
513: * @return the ResultSet positioned on the old row image.
514: *
515: * @exception SQLException if called after the triggering event has
516: * completed
517: */
518: public java.sql.ResultSet getOldRow() throws SQLException {
519: java.sql.ResultSet rs = getOldRowSet();
520: if (rs != null)
521: rs.next();
522:
523: return rs;
524: }
525:
526: /**
527: * Like getAfterResultSet(), but returns a result set positioned
528: * on the first row of the before result set. Used as a convenience
529: * to get a column for a row trigger. Equivalent to getAfterResultSet()
530: * followed by next().
531: *
532: * @return the ResultSet positioned on the new row image.
533: *
534: * @exception SQLException if called after the triggering event has
535: * completed
536: */
537: public java.sql.ResultSet getNewRow() throws SQLException {
538: java.sql.ResultSet rs = getNewRowSet();
539: if (rs != null)
540: rs.next();
541: return rs;
542: }
543:
544: public Long getAutoincrementValue(String identity) {
545: // first search the hashtable-- this represents the ai values generated
546: // by this trigger.
547: if (aiHT != null) {
548: Long value = (Long) aiHT.get(identity);
549: if (value != null)
550: return value;
551: }
552:
553: // If we didn't find it in the hashtable search in the counters which
554: // represent values inherited by trigger from insert statements.
555: if (aiCounters != null) {
556: for (int i = 0; i < aiCounters.size(); i++) {
557: AutoincrementCounter aic = (AutoincrementCounter) aiCounters
558: .elementAt(i);
559:
560: // System.out.println("in itec:getaivalue " + aic);
561: if (identity.equals(aic.getIdentity())) {
562: // System.out.println("in itec:getvalue--returning " + aic.getCurrentValue());
563: return aic.getCurrentValue();
564: }
565: }
566: }
567:
568: // didn't find it-- return NULL.
569: return null;
570: }
571:
572: /**
573: * Copy a hashtable of autoincrement values into the trigger
574: * execution context hashtable of autoincrement values.
575: */
576: public void copyHashtableToAIHT(Hashtable from) {
577: if (from == null)
578: return;
579: if (aiHT == null)
580: aiHT = new Hashtable();
581: for (Enumeration e = from.keys(); e.hasMoreElements();) {
582: Object key = e.nextElement();
583: Object value = from.get(key);
584: aiHT.put(key, value);
585: // System.out.println(" in itec:chte-- " + key + " " + value);
586: }
587: }
588:
589: /**
590: * Reset Autoincrement counters to the beginning or the end.
591: *
592: * @param begin if True, reset the AutoincremnetCounter to the
593: * beginning-- used to reset the counters for the
594: * next trigger. If false, reset it to the end--
595: * this sets up the counter appropriately for a
596: * AFTER STATEMENT trigger.
597: */
598: public void resetAICounters(boolean begin) {
599: if (aiCounters == null)
600: return;
601:
602: afterRow = null;
603:
604: int size = aiCounters.size();
605: for (int i = 0; i < size; i++) {
606: AutoincrementCounter aic = (AutoincrementCounter) aiCounters
607: .elementAt(i);
608: aic.reset(begin);
609: }
610: }
611:
612: /**
613: * Update Autoincrement Counters from the last row inserted.
614: *
615: */
616: public void updateAICounters() throws StandardException {
617: if (aiCounters == null)
618: return;
619:
620: int size = aiCounters.size();
621: for (int i = 0; i < size; i++) {
622: AutoincrementCounter aic = (AutoincrementCounter) aiCounters
623: .elementAt(i);
624: DataValueDescriptor dvd = afterRow.getColumn(aic
625: .getColumnPosition());
626: aic.update(dvd.getLong());
627: }
628: }
629:
630: public String toString() {
631: return triggerd.getName();
632: }
633:
634: }
|