KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > sql > execute > InternalTriggerExecutionContext


1 /*
2
3    Derby - Class org.apache.derby.impl.sql.execute.InternalTriggerExecutionContext
4
5    Licensed to the Apache Software Foundation (ASF) under one or more
6    contributor license agreements. See the NOTICE file distributed with
7    this work for additional information regarding copyright ownership.
8    The ASF licenses this file to you under the Apache License, Version 2.0
9    (the "License"); you may not use this file except in compliance with
10    the License. You may obtain a copy of the License at
11
12       http://www.apache.org/licenses/LICENSE-2.0
13
14    Unless required by applicable law or agreed to in writing, software
15    distributed under the License is distributed on an "AS IS" BASIS,
16    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17    See the License for the specific language governing permissions and
18    limitations under the License.
19
20  */

21
22 package org.apache.derby.impl.sql.execute;
23
24 import org.apache.derby.iapi.services.sanity.SanityManager;
25 import org.apache.derby.iapi.error.PublicAPI;
26 import org.apache.derby.iapi.db.TriggerExecutionContext;
27 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
28 import org.apache.derby.iapi.sql.ResultSet;
29 import org.apache.derby.iapi.sql.execute.CursorResultSet;
30 import org.apache.derby.iapi.sql.execute.ExecRow;
31 import org.apache.derby.iapi.types.DataValueDescriptor;
32 import org.apache.derby.iapi.sql.execute.ExecutionStmtValidator;
33 import org.apache.derby.iapi.sql.execute.ConstantAction;
34 import org.apache.derby.iapi.sql.dictionary.TriggerDescriptor;
35 import org.apache.derby.iapi.error.StandardException;
36 import org.apache.derby.iapi.services.i18n.MessageService;
37 import org.apache.derby.iapi.reference.SQLState;
38 import org.apache.derby.iapi.jdbc.ConnectionContext;
39 import org.apache.derby.catalog.UUID;
40 import java.util.Enumeration JavaDoc;
41 import java.util.Vector JavaDoc;
42 import java.util.Hashtable JavaDoc;
43 import java.sql.Connection JavaDoc;
44 import java.sql.SQLException JavaDoc;
45 import java.sql.Statement JavaDoc;
46 import org.apache.derby.iapi.error.ExceptionSeverity;
47 /**
48  * There is one of these beasts per INSERT/DELETE/UPDATE
49  * statement. It fulfills the contract for the externally
50  * visible trigger execution context and it validates
51  * that a statement that is about to be executed doesn't
52  * violate the restrictions placed upon what can be executed
53  * from a trigger.
54  * <p>
55  * Note that it is crucial that cleanup() is called once
56  * the DML has completed, cleanup() makes sure that users
57  * can't do something invalid on a tec reference that they
58  * were holding from when the trigger fired.
59  *
60  */

61 public class InternalTriggerExecutionContext implements TriggerExecutionContext, ExecutionStmtValidator
62 {
63     /*
64     ** Immutable
65     */

66     protected int[] changedColIds;
67     protected String JavaDoc[] changedColNames;
68     protected int dmlType;
69     protected String JavaDoc statementText;
70     protected ConnectionContext cc;
71     protected UUID targetTableId;
72     protected String JavaDoc targetTableName;
73     protected LanguageConnectionContext lcc;
74
75     /*
76     ** Mutable
77     */

78     protected CursorResultSet beforeResultSet;
79     protected CursorResultSet afterResultSet;
80
81     /**
82      * used exclusively for InsertResultSets which have autoincrement
83      * columns.
84      */

85     protected ExecRow afterRow;
86                                 
87     protected boolean cleanupCalled;
88     protected TriggerEvent event;
89     protected TriggerDescriptor triggerd;
90
91     /*
92     ** Used to track all the result sets we have given out to
93     ** users. When the trigger context is no longer valid,
94     ** we close all the result sets that may be in the user
95     ** space because they can no longer provide meaningful
96     ** results.
97     */

98     private Vector JavaDoc resultSetVector;
99
100     /**
101      * aiCounters is a vector of AutoincrementCounters used to keep state which
102      * might be used by the trigger. This is only used by Insert triggers--
103      * Delete and Update triggers do not use this variable.
104      *
105      * @see AutoincrementCounter
106      *
107      */

108     private Vector JavaDoc aiCounters;
109     
110     /**
111      * aiHT is a hashtable of autincrement <key, value> pairs. This is used for
112      * ai values generated by the trigger.
113      */

114     private Hashtable JavaDoc aiHT;
115
116     /**
117      * Build me a big old nasty trigger execution context.
118      * Damnit.
119      * <p>
120      * About the only thing of real interest to outside observers
121      * is that it pushes itself as the trigger execution context
122      * in the lcc. Be sure to call <i>cleanup()</i> when you
123      * are done, or you will be flogged like the reprobate that
124      * you are.
125      *
126      * @param lcc the lcc
127      * @param statementText the text of the statement that caused the
128      * trigger to fire. may be null if we are replicating
129      * @param changedColIds the list of columns that changed. Null
130      * for all columns or INSERT/DELETE.
131      * @param changedColNames the names that correspond to changedColIds
132      * @param targetTableId the UUID of the table upon which the trigger
133      * fired
134      * @param targetTableName the name of the table upon which the trigger
135      * fired
136      * @param aiCounters A vector of AutoincrementCounters to keep state
137      * of the ai columns in this insert trigger.a
138      *
139      * @exception StandardException on error
140      */

141     public InternalTriggerExecutionContext
142     (
143         LanguageConnectionContext lcc,
144         ConnectionContext cc,
145         String JavaDoc statementText,
146         int dmlType,
147         int[] changedColIds,
148         String JavaDoc[] changedColNames,
149         UUID targetTableId,
150         String JavaDoc targetTableName,
151         Vector JavaDoc aiCounters
152     ) throws StandardException
153     {
154         this.dmlType = dmlType;
155         this.changedColIds = changedColIds;
156         this.changedColNames = changedColNames;
157         this.statementText = statementText;
158         this.cc = cc;
159         this.lcc = lcc;
160         this.targetTableId = targetTableId;
161         this.targetTableName = targetTableName;
162         this.resultSetVector = new Vector JavaDoc();
163         this.aiCounters = aiCounters;
164
165         if (SanityManager.DEBUG)
166         {
167             if ((changedColIds == null) != (changedColNames == null))
168             {
169                 SanityManager.THROWASSERT("bad changed cols, "+
170                     "(changedColsIds == null) = "+(changedColIds == null)+
171                     " (changedColsNames == null) = "+(changedColNames == null));
172             }
173             if (changedColIds != null)
174             {
175                 SanityManager.ASSERT(changedColIds.length == changedColNames.length,
176                     "different number of changed col ids vs names");
177             }
178         }
179
180         lcc.pushTriggerExecutionContext(this);
181     }
182
183     void setBeforeResultSet(CursorResultSet rs)
184     {
185         beforeResultSet = rs;
186     }
187
188     void setAfterResultSet(CursorResultSet rs)
189          throws StandardException
190     {
191         afterResultSet = rs;
192         
193         if (aiCounters != null)
194         {
195             if (triggerd.isRowTrigger())
196             {
197                 // An after row trigger needs to see the "first" row inserted
198
rs.open();
199                 afterRow = rs.getNextRow();
200                 rs.close();
201             }
202             else
203             {
204                 // after statement trigger needs to look at the last value.
205
if (!triggerd.isBeforeTrigger())
206                     resetAICounters(false);
207             }
208         }
209     }
210
211     void setCurrentTriggerEvent(TriggerEvent event)
212     {
213         this.event = event;
214     }
215
216     void clearCurrentTriggerEvent()
217     {
218         event = null;
219     }
220
221     void setTrigger(TriggerDescriptor triggerd)
222     {
223         this.triggerd = triggerd;
224     }
225     
226     void clearTrigger() throws StandardException
227     {
228         event = null;
229         triggerd = null;
230         if (afterResultSet != null)
231         {
232             afterResultSet.close();
233             afterResultSet = null;
234         }
235         if (beforeResultSet != null)
236         {
237             beforeResultSet.close();
238             beforeResultSet = null;
239         }
240     }
241
242     /**
243      * Cleanup the trigger execution context. <B>MUST</B>
244      * be called when the caller is done with the trigger
245      * execution context.
246      * <p>
247      * We go to somewhat exaggerated lengths to free up
248      * all our resources here because a user may hold on
249      * to a TEC after it is valid, so we clean everything
250      * up to be on the safe side.
251      *
252      * @exception StandardException on unexpected error
253      */

254     protected void cleanup()
255         throws StandardException
256     {
257         lcc.popTriggerExecutionContext(this);
258
259         /*
260         ** Explicitly close all result sets that we have
261         ** given out to the user.
262         */

263         for (Enumeration JavaDoc e = resultSetVector.elements();
264              e.hasMoreElements(); )
265         {
266             java.sql.ResultSet JavaDoc rs = (java.sql.ResultSet JavaDoc)e.nextElement();
267             try
268             {
269                 rs.close();
270             } catch (SQLException JavaDoc se) {}
271         }
272         resultSetVector = null;
273     
274         /*
275         ** We should have already closed our underlying
276         ** ExecResultSets by closing the jdbc result sets,
277         ** but in case we got an error that we caught and
278         ** ignored, explicitly close them.
279         */

280         if (afterResultSet != null)
281         {
282             afterResultSet.close();
283             afterResultSet = null;
284         }
285         if (beforeResultSet != null)
286         {
287             beforeResultSet.close();
288             beforeResultSet = null;
289         }
290
291         lcc = null;
292         cleanupCalled = true;
293     }
294
295     /**
296      * Make sure that the user isn't trying to get a result
297      * set after we have cleaned up.
298      */

299     private void ensureProperContext() throws SQLException JavaDoc
300     {
301         if (cleanupCalled)
302         {
303             throw new SQLException JavaDoc(
304                 MessageService.getTextMessage(
305                                     SQLState.LANG_STATEMENT_CLOSED_NO_REASON),
306                                     "XCL31",
307                                     ExceptionSeverity.STATEMENT_SEVERITY
308                                     );
309         }
310     }
311
312     /////////////////////////////////////////////////////////
313
//
314
// ExecutionStmtValidator
315
//
316
/////////////////////////////////////////////////////////
317
/**
318      * Make sure that whatever statement is about to be executed
319      * is ok from the context of this trigger.
320      * <p>
321      * Note that we are sub classed in replication for checks
322      * for replication specific language.
323      *
324      * @param constantAction the constant action of the action
325      * that we are to validate
326      *
327      * @exception StandardException on error
328      */

329     public void validateStatement(ConstantAction constantAction) throws StandardException
330     {
331
332         // DDL statements are not allowed in triggers. Direct use of DDL
333
// statements in a trigger's action statement is disallowed by the
334
// parser. However, this runtime check is needed to prevent execution
335
// of DDL statements by procedures within a trigger context.
336
if (constantAction instanceof DDLConstantAction) {
337             throw StandardException.newException(SQLState.LANG_NO_DDL_IN_TRIGGER, triggerd.getName(), constantAction.toString());
338         }
339         
340         // No INSERT/UPDATE/DELETE for a before trigger. There is no need to
341
// check this here because parser does not allow these DML statements
342
// in a trigger's action statement in a before trigger. Parser also
343
// disallows creation of before triggers calling procedures that modify
344
// SQL data.
345

346     }
347
348     /////////////////////////////////////////////////////////
349
//
350
// TriggerExectionContext
351
//
352
/////////////////////////////////////////////////////////
353

354     /**
355      * Get the target table name upon which the
356      * trigger event is declared.
357      *
358      * @return the target table
359      */

360     public String JavaDoc getTargetTableName()
361     {
362         return targetTableName;
363     }
364
365     /**
366      * Get the target table UUID upon which the
367      * trigger event is declared.
368      *
369      * @return the uuid of the target table
370      */

371     public UUID getTargetTableId()
372     {
373         return targetTableId;
374     }
375
376     /**
377      * Get the type for the event that caused the
378      * trigger to fire.
379      *
380      * @return the event type (e.g. UPDATE_EVENT)
381      */

382     public int getEventType()
383     {
384         return dmlType;
385     }
386
387     /**
388      * Get the text of the statement that caused the
389      * trigger to fire.
390      *
391      * @return the statement text
392      */

393     public String JavaDoc getEventStatementText()
394     {
395         return statementText;
396     }
397
398     /**
399      * Get the columns that have been modified by the statement
400      * that caused this trigger to fire. If all columns are
401      * modified, will return null (e.g. for INSERT or DELETE will
402      * return null).
403      *
404      * @return an array of Strings
405      */

406     public String JavaDoc[] getModifiedColumns()
407     {
408         return changedColNames;
409     }
410
411     /**
412      * Find out of a column was changed, by column name
413      *
414      * @param columnName the column to check
415      *
416      * @return true if the column was modified by this statement.
417      * Note that this will always return true for INSERT
418      * and DELETE regardless of the column name passed in.
419      */

420     public boolean wasColumnModified(String JavaDoc columnName)
421     {
422         if (changedColNames == null)
423         {
424             return true;
425         }
426
427         for (int i = 0; i < changedColNames.length; i++)
428         {
429             if (changedColNames[i].equals(columnName))
430             {
431                 return true;
432             }
433         }
434         return false;
435     }
436
437     /**
438      * Find out of a column was changed, by column number
439      *
440      * @param columnNumber the column to check
441      *
442      * @return true if the column was modified by this statement.
443      * Note that this will always return true for INSERT
444      * and DELETE regardless of the column name passed in.
445      */

446     public boolean wasColumnModified(int columnNumber)
447     {
448         if (changedColIds == null)
449         {
450             return true;
451         }
452
453         for (int i = 0; i < changedColNames.length; i++)
454         {
455             if (changedColIds[i] == columnNumber)
456             {
457                 return true;
458             }
459         }
460         return false;
461     }
462
463     /**
464      * Returns a result set row the old images of the changed rows.
465      * For a row trigger, the result set will have a single row. For
466      * a statement trigger, this result set has every row that has
467      * changed or will change. If a statement trigger does not affect
468      * a row, then the result set will be empty (i.e. ResultSet.next()
469      * will return false).
470      *
471      * @return the ResultSet containing before images of the rows
472      * changed by the triggering event.
473      *
474      * @exception SQLException if called after the triggering event has
475      * completed
476      */

477     public java.sql.ResultSet JavaDoc getOldRowSet() throws SQLException JavaDoc
478     {
479         ensureProperContext();
480         if (beforeResultSet == null)
481         {
482             return null;
483         }
484
485         try
486         {
487             CursorResultSet brs = beforeResultSet;
488             /* We should really shallow clone the result set, because it could be used
489              * at multiple places independently in trigger action. This is a bug found
490              * during the fix of beetle 4373.
491              */

492             if (brs instanceof TemporaryRowHolderResultSet)
493                 brs = (CursorResultSet) ((TemporaryRowHolderResultSet) brs).clone();
494             else if (brs instanceof TableScanResultSet)
495                 brs = (CursorResultSet) ((TableScanResultSet) brs).clone();
496             brs.open();
497             java.sql.ResultSet JavaDoc rs = cc.getResultSet(brs);
498             resultSetVector.addElement(rs);
499             return rs;
500         } catch (StandardException se)
501         {
502             throw PublicAPI.wrapStandardException(se);
503         }
504     }
505
506     /**
507      * Returns a result set row the new images of the changed rows.
508      * For a row trigger, the result set will have a single row. For
509      * a statement trigger, this result set has every row that has
510      * changed or will change. If a statement trigger does not affect
511      * a row, then the result set will be empty (i.e. ResultSet.next()
512      * will return false).
513      *
514      * @return the ResultSet containing after images of the rows
515      * changed by the triggering event.
516      *
517      * @exception SQLException if called after the triggering event has
518      * completed
519      */

520     public java.sql.ResultSet JavaDoc getNewRowSet() throws SQLException JavaDoc
521     {
522         ensureProperContext();
523
524         if (afterResultSet == null)
525         {
526             return null;
527         }
528         try
529         {
530             /* We should really shallow clone the result set, because it could be used
531              * at multiple places independently in trigger action. This is a bug found
532              * during the fix of beetle 4373.
533              */

534             CursorResultSet ars = afterResultSet;
535             if (ars instanceof TemporaryRowHolderResultSet)
536                 ars = (CursorResultSet) ((TemporaryRowHolderResultSet) ars).clone();
537             else if (ars instanceof TableScanResultSet)
538                 ars = (CursorResultSet) ((TableScanResultSet) ars).clone();
539             ars.open();
540             java.sql.ResultSet JavaDoc rs = cc.getResultSet(ars);
541             resultSetVector.addElement(rs);
542             return rs;
543         } catch (StandardException se)
544         {
545             throw PublicAPI.wrapStandardException(se);
546         }
547     }
548
549     /**
550      * Like getBeforeResultSet(), but returns a result set positioned
551      * on the first row of the before result set. Used as a convenience
552      * to get a column for a row trigger. Equivalent to getBeforeResultSet()
553      * followed by next().
554      *
555      * @return the ResultSet positioned on the old row image.
556      *
557      * @exception SQLException if called after the triggering event has
558      * completed
559      */

560     public java.sql.ResultSet JavaDoc getOldRow() throws SQLException JavaDoc
561     {
562         java.sql.ResultSet JavaDoc rs = getOldRowSet();
563         if (rs != null)
564             rs.next();
565         
566         return rs;
567     }
568
569     /**
570      * Like getAfterResultSet(), but returns a result set positioned
571      * on the first row of the before result set. Used as a convenience
572      * to get a column for a row trigger. Equivalent to getAfterResultSet()
573      * followed by next().
574      *
575      * @return the ResultSet positioned on the new row image.
576      *
577      * @exception SQLException if called after the triggering event has
578      * completed
579      */

580     public java.sql.ResultSet JavaDoc getNewRow() throws SQLException JavaDoc
581     {
582         java.sql.ResultSet JavaDoc rs = getNewRowSet();
583         if (rs != null)
584             rs.next();
585         return rs;
586     }
587     
588     public Long JavaDoc getAutoincrementValue(String JavaDoc identity)
589     {
590         // first search the hashtable-- this represents the ai values generated
591
// by this trigger.
592
if (aiHT != null)
593             {
594                 Long JavaDoc value = (Long JavaDoc)aiHT.get(identity);
595                 if (value != null)
596                     return value;
597             }
598         
599         
600         // If we didn't find it in the hashtable search in the counters which
601
// represent values inherited by trigger from insert statements.
602
if (aiCounters != null)
603         {
604             for (int i = 0; i < aiCounters.size(); i++)
605             {
606                 AutoincrementCounter aic =
607                     (AutoincrementCounter)aiCounters.elementAt(i);
608
609                 // System.out.println("in itec:getaivalue " + aic);
610
if (identity.equals(aic.getIdentity()))
611                 {
612                     // System.out.println("in itec:getvalue--returning " + aic.getCurrentValue());
613
return aic.getCurrentValue();
614                 }
615             }
616         }
617         
618         // didn't find it-- return NULL.
619
return null;
620     }
621     /**
622      * Copy a hashtable of autoincrement values into the trigger
623      * execution context hashtable of autoincrement values.
624      */

625     public void copyHashtableToAIHT(Hashtable JavaDoc from)
626     {
627         if (from == null)
628             return;
629         if (aiHT == null)
630             aiHT = new Hashtable JavaDoc();
631         for (Enumeration JavaDoc e = from.keys(); e.hasMoreElements(); )
632         {
633             Object JavaDoc key = e.nextElement();
634             Object JavaDoc value = from.get(key);
635             aiHT.put(key, value);
636             // System.out.println(" in itec:chte-- " + key + " " + value);
637
}
638     }
639         
640     /**
641      * Reset Autoincrement counters to the beginning or the end.
642      *
643      * @param begin if True, reset the AutoincremnetCounter to the
644      * beginning-- used to reset the counters for the
645      * next trigger. If false, reset it to the end--
646      * this sets up the counter appropriately for a
647      * AFTER STATEMENT trigger.
648      */

649     public void resetAICounters(boolean begin)
650     {
651         if (aiCounters == null)
652             return;
653
654         afterRow = null;
655
656         int size = aiCounters.size();
657         for (int i = 0; i < size; i++)
658         {
659             AutoincrementCounter aic =
660                 (AutoincrementCounter)aiCounters.elementAt(i);
661             aic.reset(begin);
662         }
663     }
664     
665     /**
666      * Update Autoincrement Counters from the last row inserted.
667      *
668      */

669     public void updateAICounters() throws StandardException
670     {
671         if (aiCounters == null)
672             return;
673
674         int size = aiCounters.size();
675         for (int i = 0; i < size; i++)
676         {
677             AutoincrementCounter aic =
678                 (AutoincrementCounter)aiCounters.elementAt(i);
679             DataValueDescriptor dvd = afterRow.getColumn(aic.getColumnPosition());
680             aic.update(dvd.getLong());
681         }
682     }
683
684
685     public String JavaDoc toString() {
686         return triggerd.getName();
687     }
688
689
690 }
691
Popular Tags