001: /*
002:
003: Derby - Class org.apache.derby.iapi.sql.dictionary.TriggerDescriptor
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.iapi.sql.dictionary;
023:
024: import org.apache.derby.iapi.services.io.Formatable;
025: import org.apache.derby.iapi.sql.depend.Dependent;
026: import org.apache.derby.iapi.sql.depend.Provider;
027: import org.apache.derby.iapi.error.StandardException;
028: import org.apache.derby.catalog.UUID;
029: import java.sql.Timestamp;
030:
031: import org.apache.derby.iapi.reference.SQLState;
032: import org.apache.derby.iapi.services.sanity.SanityManager;
033: import org.apache.derby.iapi.sql.StatementType;
034: import org.apache.derby.catalog.DependableFinder;
035: import org.apache.derby.catalog.Dependable;
036: import org.apache.derby.iapi.services.io.StoredFormatIds;
037: import org.apache.derby.iapi.sql.depend.DependencyManager;
038: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
039: import org.apache.derby.iapi.services.context.ContextService;
040:
041: import org.apache.derby.impl.sql.execute.DropTriggerConstantAction;
042:
043: import java.io.ObjectOutput;
044: import java.io.ObjectInput;
045: import java.io.IOException;
046:
047: /**
048: * A trigger.
049: * <p>
050: * We are dependent on TableDescriptors, SPSDescriptors (for our
051: * WHEN clause and our action). Note that we don't strictly
052: * need to be dependent on out SPSes because we could just disallow
053: * anyone from dropping an sps of type 'T', but to keep dependencies
054: * uniform, we'll do be dependent.
055: * <p>
056: * We are a provider for DML (PreparedStatements or SPSes)
057: *
058: * The public methods for this class are:
059: *
060: * <ol>
061: * <li>getUUID
062: * <li>getName
063: * <li>getSchemaDescriptor
064: * <li> public boolean listensForEvent(int event);
065: * <li> public int getTriggerEventMask();
066: * <li> public Timestamp getCreationTimestamp();
067: * <li> public boolean isBeforeTrigger();
068: * <li> public boolean isRowTrigger();
069: * <li> public UUID getActionId();
070: * <li> public SPSDescriptor getActionSPS();
071: * <li> public UUID getWhenClauseId();
072: * <li> public SPSDescriptor getWhenClauseSPS()
073: * <li> public TableDescriptor getTableDescriptor()
074: * <li> public ReferencedColumns getReferencedColumnsDescriptor()
075: * <li> public int[] getReferencedCols();
076: * <li> public boolean isEnabled();
077: * <li> public void setEnabled();
078: * <li> public void setDisabled();
079: * <li> public boolean needsToFire(int stmtType, int[] modifiedCols)
080: * <li> public String getTriggerDefinition();
081: * <li> public boolean getReferencingOld();
082: * <li> public boolean getReferencingNew();
083: * <li> public String getOldReferencingName();
084: * <li> public String getNewReferencingName();
085: * </ol>
086: * @author Jamie
087: */
088: public class TriggerDescriptor extends TupleDescriptor implements
089: UniqueSQLObjectDescriptor, Provider, Dependent, Formatable {
090: // field that we want users to be able to know about
091: public static final int SYSTRIGGERS_STATE_FIELD = 8;
092:
093: public static final int TRIGGER_EVENT_UPDATE = 1;
094: public static final int TRIGGER_EVENT_DELETE = 2;
095: public static final int TRIGGER_EVENT_INSERT = 4;
096:
097: private UUID id;
098: private String name;
099: private String oldReferencingName;
100: private String newReferencingName;
101: private String triggerDefinition;
102: private SchemaDescriptor sd;
103: private int eventMask;
104: private boolean isBefore;
105: private boolean isRow;
106: private boolean referencingOld;
107: private boolean referencingNew;
108: private TableDescriptor td;
109: private UUID actionSPSId;
110: private SPSDescriptor actionSPS;
111: private UUID whenSPSId;
112: private SPSDescriptor whenSPS;
113: private boolean isEnabled;
114: private int[] referencedCols;
115: private Timestamp creationTimestamp;
116: private UUID triggerSchemaId;
117: private UUID triggerTableId;
118:
119: /**
120: * Niladic constructor, for formatable
121: */
122: public TriggerDescriptor() {
123: }
124:
125: /**
126: * Constructor. Used when creating a trigger from SYS.SYSTRIGGERS
127: *
128: * @param dataDictionary the data dictionary
129: * @param sd the schema descriptor for this trigger
130: * @param id the trigger id
131: * @param name the trigger name
132: * @param eventMask TriggerDescriptor.TRIGGER_EVENT_XXXX
133: * @param isBefore is this a before (as opposed to after) trigger
134: * @param isRow is this a row trigger or statement trigger
135: * @param isEnabled is this trigger enabled or disabled
136: * @param td the table upon which this trigger is defined
137: * @param whenSPSId the sps id for the when clause (may be null)
138: * @param actionSPSId the spsid for the trigger action (may be null)
139: * @param creationTimestamp when was this trigger created?
140: * @param referencedCols what columns does this trigger reference (may be null)
141: * @param triggerDefinition The original user text of the trigger action
142: * @param referencingOld whether or not OLD appears in REFERENCING clause
143: * @param referencingNew whether or not NEW appears in REFERENCING clause
144: * @param oldReferencingName old referencing table name, if any, that appears in REFERCING clause
145: * @param newReferencingName new referencing table name, if any, that appears in REFERCING clause
146: */
147: public TriggerDescriptor(DataDictionary dataDictionary,
148: SchemaDescriptor sd, UUID id, String name, int eventMask,
149: boolean isBefore, boolean isRow, boolean isEnabled,
150: TableDescriptor td, UUID whenSPSId, UUID actionSPSId,
151: Timestamp creationTimestamp, int[] referencedCols,
152: String triggerDefinition, boolean referencingOld,
153: boolean referencingNew, String oldReferencingName,
154: String newReferencingName) {
155: super (dataDictionary);
156: this .id = id;
157: this .sd = sd;
158: this .name = name;
159: this .eventMask = eventMask;
160: this .isBefore = isBefore;
161: this .isRow = isRow;
162: this .td = td;
163: this .actionSPSId = actionSPSId;
164: this .whenSPSId = whenSPSId;
165: this .isEnabled = isEnabled;
166: this .referencedCols = referencedCols;
167: this .creationTimestamp = creationTimestamp;
168: this .triggerDefinition = triggerDefinition;
169: this .referencingOld = referencingOld;
170: this .referencingNew = referencingNew;
171: this .oldReferencingName = oldReferencingName;
172: this .newReferencingName = newReferencingName;
173: triggerSchemaId = sd.getUUID();
174: triggerTableId = td.getUUID();
175: }
176:
177: /**
178: * Get the trigger UUID
179: *
180: * @return the id
181: */
182: public UUID getUUID() {
183: return id;
184: }
185:
186: /**
187: * Get the trigger name
188: *
189: * @return the name
190: */
191: public String getName() {
192: return name;
193: }
194:
195: public UUID getTableId() {
196: return triggerTableId;
197: }
198:
199: /**
200: * Get the triggers schema descriptor
201: *
202: * @return the schema descriptor
203: *
204: * @exception StandardException on error
205: */
206: public SchemaDescriptor getSchemaDescriptor()
207: throws StandardException {
208: if (sd == null) {
209: sd = getDataDictionary().getSchemaDescriptor(
210: triggerSchemaId, null);
211: }
212: return sd;
213: }
214:
215: /**
216: * Indicate whether this trigger listens for this
217: * type of event.
218: *
219: * @param event TRIGGER_EVENT_XXXX
220: *
221: * @return true if it listens to the specified event.
222: */
223: public boolean listensForEvent(int event) {
224: return (event & eventMask) == event;
225: }
226:
227: /**
228: * Get the trigger event mask. Currently, a trigger
229: * may only listen for a single event, though it may
230: * OR multiple events in the future.
231: *
232: * @return the trigger event mask
233: */
234: public int getTriggerEventMask() {
235: return eventMask;
236: }
237:
238: /**
239: * Get the time that this trigger was created.
240: *
241: * @return the time the trigger was created
242: */
243: public Timestamp getCreationTimestamp() {
244: return creationTimestamp;
245: }
246:
247: /**
248: * Is this a before trigger
249: *
250: * @return true if it is a before trigger
251: */
252: public boolean isBeforeTrigger() {
253: return isBefore;
254: }
255:
256: /**
257: * Is this a row trigger
258: *
259: * @return true if it is a before trigger
260: */
261: public boolean isRowTrigger() {
262: return isRow;
263: }
264:
265: /**
266: * Get the trigger action sps UUID
267: *
268: * @return the uuid of the sps action
269: */
270: public UUID getActionId() {
271: return actionSPSId;
272: }
273:
274: /**
275: * Get the trigger action sps
276: *
277: * @return the trigger action sps
278: *
279: * @exception StandardException on error
280: */
281: public SPSDescriptor getActionSPS(LanguageConnectionContext lcc)
282: throws StandardException {
283: if (actionSPS == null) {
284: //bug 4821 - do the sysstatement look up in a nested readonly
285: //transaction rather than in the user transaction. Because of
286: //this, the nested compile transaction which is attempting to
287: //compile the trigger will not run into any locking issues with
288: //the user transaction for sysstatements.
289: lcc.beginNestedTransaction(true);
290: actionSPS = getDataDictionary().getSPSDescriptor(
291: actionSPSId);
292: lcc.commitNestedTransaction();
293: }
294: return actionSPS;
295: }
296:
297: /**
298: * Get the trigger when clause sps UUID
299: *
300: * @return the uuid of the sps action
301: */
302: public UUID getWhenClauseId() {
303: return whenSPSId;
304: }
305:
306: /**
307: * Get the trigger when clause sps
308: *
309: * @return the sps of the when clause
310: *
311: * @exception StandardException on error
312: */
313: public SPSDescriptor getWhenClauseSPS() throws StandardException {
314: if (whenSPS == null) {
315: whenSPS = getDataDictionary().getSPSDescriptor(whenSPSId);
316: }
317: return whenSPS;
318: }
319:
320: /**
321: * Get the trigger table descriptor
322: *
323: * @return the table descripor upon which this trigger
324: * is declared
325: *
326: * @exception StandardException on error
327: */
328: public TableDescriptor getTableDescriptor()
329: throws StandardException {
330: if (td == null) {
331: td = getDataDictionary().getTableDescriptor(triggerTableId);
332: }
333: return td;
334: }
335:
336: /**
337: * Get the referenced table descriptor for this trigger.
338: *
339: * @return the referenced table descriptor
340: *
341: * @exception StandardException on error
342: */
343: // caller converts referencedCols to referencedColsDescriptor...
344: // public ReferencedColumns getReferencedColumnsDescriptor()
345: // throws StandardException
346: // {
347: // return (referencedCols == null) ?
348: // (ReferencedColumns)null :
349: // new ReferencedColumnsDescriptorImpl(referencedCols);
350: // }
351: /**
352: * Get the referenced column array for this trigger, used in "alter table
353: * drop column", we get the handle and change it
354: *
355: * @return the referenced column array
356: */
357: public int[] getReferencedCols() {
358: return referencedCols;
359: }
360:
361: /**
362: * Is this trigger enabled
363: *
364: * @return true if it is enabled
365: */
366: public boolean isEnabled() {
367: return isEnabled;
368: }
369:
370: /**
371: * Mark this trigger as enabled
372: *
373: */
374: public void setEnabled() {
375: isEnabled = true;
376: }
377:
378: /**
379: * Mark this trigger as disabled
380: *
381: */
382: public void setDisabled() {
383: isEnabled = false;
384: }
385:
386: /**
387: * Does this trigger need to fire on this type of
388: * DML?
389: *
390: * @param stmtType the type of DML
391: * (StatementType.INSERT|StatementType.UPDATE|StatementType.DELETE)
392: * @param modifiedCols the columns modified, or null for all
393: *
394: * @return true/false
395: *
396: * @exception StandardException on error
397: */
398: public boolean needsToFire(int stmtType, int[] modifiedCols)
399: throws StandardException {
400:
401: if (SanityManager.DEBUG) {
402: if (!((stmtType == StatementType.INSERT)
403: || (stmtType == StatementType.BULK_INSERT_REPLACE)
404: || (stmtType == StatementType.UPDATE) || (stmtType == StatementType.DELETE))) {
405: SanityManager.THROWASSERT("invalid statement type "
406: + stmtType);
407: }
408: }
409:
410: /*
411: ** If we are disabled, we never fire
412: */
413: if (!isEnabled) {
414: return false;
415: }
416:
417: if (stmtType == StatementType.INSERT) {
418: return (eventMask & TRIGGER_EVENT_INSERT) == eventMask;
419: }
420: if (stmtType == StatementType.DELETE) {
421: return (eventMask & TRIGGER_EVENT_DELETE) == eventMask;
422: }
423:
424: // this is a temporary restriction, but it may not be lifted
425: // anytime soon.
426: if (stmtType == StatementType.BULK_INSERT_REPLACE) {
427: throw StandardException.newException(
428: SQLState.LANG_NO_BULK_INSERT_REPLACE_WITH_TRIGGER,
429: getTableDescriptor().getQualifiedName(), name);
430: }
431:
432: // if update, only relevant if columns intersect
433: return ((eventMask & TRIGGER_EVENT_UPDATE) == eventMask)
434: && ConstraintDescriptor.doColumnsIntersect(
435: modifiedCols, referencedCols);
436: }
437:
438: /**
439: * Get the original trigger definition.
440: *
441: * @return The trigger definition.
442: */
443: public String getTriggerDefinition() {
444: return triggerDefinition;
445: }
446:
447: /**
448: * Get whether or not OLD was replaced
449: * in the REFERENCING clause.
450: *
451: * @return Whether or not OLD was replaced
452: * in the REFERENCING clause.
453: */
454: public boolean getReferencingOld() {
455: return referencingOld;
456: }
457:
458: /**
459: * Get whether or not NEW was replaced
460: * in the REFERENCING clause.
461: *
462: * @return Whether or not NEW was replaced
463: * in the REFERENCING clause.
464: */
465: public boolean getReferencingNew() {
466: return referencingNew;
467: }
468:
469: /**
470: * Get the old Referencing name, if any,
471: * from the REFERENCING clause.
472: *
473: * @return The old Referencing name, if any,
474: * from the REFERENCING clause.
475: */
476: public String getOldReferencingName() {
477: return oldReferencingName;
478: }
479:
480: /**
481: * Get the new Referencing name, if any,
482: * from the REFERENCING clause.
483: *
484: * @return The new Referencing name, if any,
485: * from the REFERENCING clause.
486: */
487: public String getNewReferencingName() {
488: return newReferencingName;
489: }
490:
491: public String toString() {
492: if (SanityManager.DEBUG) {
493: return "TRIGGER: " + name;
494: } else {
495: return "";
496: }
497: }
498:
499: ////////////////////////////////////////////////////////////////////
500: //
501: // PROVIDER INTERFACE
502: //
503: ////////////////////////////////////////////////////////////////////
504:
505: /**
506: * @return the stored form of this provider
507: *
508: * @see Dependable#getDependableFinder
509: */
510: public DependableFinder getDependableFinder() {
511: return getDependableFinder(StoredFormatIds.TRIGGER_DESCRIPTOR_FINDER_V01_ID);
512: }
513:
514: /**
515: * Return the name of this Provider. (Useful for errors.)
516: *
517: * @return String The name of this provider.
518: */
519: public String getObjectName() {
520: return name;
521: }
522:
523: /**
524: * Get the provider's UUID
525: *
526: * @return The provider's UUID
527: */
528: public UUID getObjectID() {
529: return id;
530: }
531:
532: /**
533: * Get the provider's type.
534: *
535: * @return char The provider's type.
536: */
537: public String getClassType() {
538: return Dependable.TRIGGER;
539: }
540:
541: //////////////////////////////////////////////////////
542: //
543: // DEPENDENT INTERFACE
544: //
545: // Triggers are dependent on the underlying table,
546: // and their spses (for the trigger action and the WHEN
547: // clause).
548: //
549: //////////////////////////////////////////////////////
550: /**
551: * Check that all of the dependent's dependencies are valid.
552: *
553: * @return true if the dependent is currently valid
554: */
555: public synchronized boolean isValid() {
556: return true;
557: }
558:
559: /**
560: * Prepare to mark the dependent as invalid (due to at least one of
561: * its dependencies being invalid).
562: *
563: * @param action The action causing the invalidation
564: * @param p the provider
565: * @param lcc the language connection context
566: *
567: * @exception StandardException thrown if unable to make it invalid
568: */
569: public void prepareToInvalidate(Provider p, int action,
570: LanguageConnectionContext lcc) throws StandardException {
571: switch (action) {
572: /*
573: ** We are only dependent on the underlying table, and our spses and
574: ** privileges on various objects. (we should be dropped before our
575: ** table is dropped. Also, we should be dropped before revoke
576: ** RESTRICT privilege is issued otherwise revoke RESTRICT will
577: ** throw an exception).
578: ** Currently, in Derby, an execute routine privilege can be revoked
579: ** only if there are no dependents on that privilege. When revoke
580: ** execute RESTRICT is exectued, all the dependents will receive
581: ** REVOKE_PRIVILEGE_RESTRICT and they should throw exception.
582: ** We handle this for TriggerDescriptor by throwning an exception
583: ** below. For all the other types of revoke privileges, for
584: ** instance, SELECT, UPDATE, DELETE, INSERT, REFERENCES,
585: ** TRIGGER, we don't do anything here and later in makeInvalid, we
586: ** make the TriggerDescriptor drop itself.
587: */
588: case DependencyManager.DROP_TABLE:
589: case DependencyManager.DROP_SYNONYM:
590: case DependencyManager.DROP_SPS:
591: case DependencyManager.RENAME:
592: case DependencyManager.REVOKE_PRIVILEGE_RESTRICT:
593: DependencyManager dm = getDataDictionary()
594: .getDependencyManager();
595: throw StandardException.newException(
596: SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT, dm
597: .getActionString(action),
598: p.getObjectName(), "TRIGGER", name);
599:
600: /*
601: ** The trigger descriptor depends on the trigger table.
602: ** This means that we get called whenever anything happens
603: ** to the trigger table. There are so many cases where this
604: ** can happen that it doesn't make sense to have an assertion
605: ** here to check whether the action was expected (it makes
606: ** the code hard to maintain, and creates a big switch statement).
607: */
608: default:
609: break;
610: }
611: }
612:
613: /**
614: * Mark the dependent as invalid (due to at least one of
615: * its dependencies being invalid). Always an error
616: * for a trigger -- should never have gotten here.
617: *
618: * @param lcc the language connection context
619: * @param action The action causing the invalidation
620: *
621: * @exception StandardException thrown if called in sanity mode
622: */
623: public void makeInvalid(int action, LanguageConnectionContext lcc)
624: throws StandardException {
625: // No sanity check for valid action. Trigger descriptors depend on
626: // the trigger table, so there is a very large number of actions
627: // that we would have to check against. This is hard to maintain,
628: // so don't bother.
629:
630: switch (action) {
631: // invalidate this trigger descriptor
632: case DependencyManager.USER_RECOMPILE_REQUEST:
633: DependencyManager dm = getDataDictionary()
634: .getDependencyManager();
635: dm.invalidateFor(this ,
636: DependencyManager.PREPARED_STATEMENT_RELEASE, lcc);
637: break;
638:
639: // When REVOKE_PRIVILEGE gets sent (this happens for privilege
640: // types SELECT, UPDATE, DELETE, INSERT, REFERENCES, TRIGGER), we
641: // make the TriggerDescriptor drop itself.
642: case DependencyManager.REVOKE_PRIVILEGE:
643: DropTriggerConstantAction.dropTriggerDescriptor(lcc,
644: getDataDictionary().getDependencyManager(),
645: getDataDictionary(), lcc.getTransactionExecute(),
646: this , null);
647: break;
648:
649: default:
650: break;
651: }
652:
653: }
654:
655: /**
656: * Attempt to revalidate the dependent. Meaningless
657: * for a trigger.
658: *
659: * @param lcc the language connection context
660: */
661: public void makeValid(LanguageConnectionContext lcc) {
662: }
663:
664: //////////////////////////////////////////////////////////////
665: //
666: // FORMATABLE
667: //
668: //////////////////////////////////////////////////////////////
669:
670: /**
671: * Read this object from a stream of stored objects.
672: *
673: * @param in read this.
674: *
675: * @exception IOException thrown on error
676: * @exception ClassNotFoundException thrown on error
677: */
678: public void readExternal(ObjectInput in) throws IOException,
679: ClassNotFoundException {
680: id = (UUID) in.readObject();
681: name = (String) in.readObject();
682: triggerSchemaId = (UUID) in.readObject();
683: triggerTableId = (UUID) in.readObject();
684: eventMask = in.readInt();
685: isBefore = in.readBoolean();
686: isRow = in.readBoolean();
687: isEnabled = in.readBoolean();
688: whenSPSId = (UUID) in.readObject();
689: actionSPSId = (UUID) in.readObject();
690: int length = in.readInt();
691: if (length != 0) {
692: referencedCols = new int[length];
693: for (int i = 0; i < length; i++) {
694: referencedCols[i] = in.readInt();
695: }
696: }
697: triggerDefinition = (String) in.readObject();
698: referencingOld = in.readBoolean();
699: referencingNew = in.readBoolean();
700: oldReferencingName = (String) in.readObject();
701: newReferencingName = (String) in.readObject();
702:
703: }
704:
705: protected DataDictionary getDataDictionary()
706: throws StandardException {
707: /*
708: note: we need to do this since when this trigger is read back from
709: disk (when it is associated with a sps), the dataDictionary has not
710: been initialized and therefore can give a NullPointerException
711: */
712: DataDictionary dd = super .getDataDictionary();
713: if (dd == null) {
714: LanguageConnectionContext lcc = (LanguageConnectionContext) ContextService
715: .getContext(LanguageConnectionContext.CONTEXT_ID);
716: dd = lcc.getDataDictionary();
717: setDataDictionary(dd);
718: }
719: return dd;
720: }
721:
722: /**
723: * Write this object to a stream of stored objects.
724: *
725: * @param out write bytes here.
726: *
727: * @exception IOException thrown on error
728: */
729: public void writeExternal(ObjectOutput out) throws IOException {
730: if (SanityManager.DEBUG) {
731: SanityManager.ASSERT(triggerSchemaId != null,
732: "triggerSchemaId expected to be non-null");
733: SanityManager.ASSERT(triggerTableId != null,
734: "triggerTableId expected to be non-null");
735: }
736: out.writeObject(id);
737: out.writeObject(name);
738: out.writeObject(triggerSchemaId);
739: out.writeObject(triggerTableId);
740: out.writeInt(eventMask);
741: out.writeBoolean(isBefore);
742: out.writeBoolean(isRow);
743: out.writeBoolean(isEnabled);
744: out.writeObject(whenSPSId);
745: out.writeObject(actionSPSId);
746: if (referencedCols == null) {
747: out.writeInt(0);
748: } else {
749: out.writeInt(referencedCols.length);
750: for (int i = 0; i < referencedCols.length; i++) {
751: out.writeInt(referencedCols[i]);
752: }
753: }
754: out.writeObject(triggerDefinition);
755: out.writeBoolean(referencingOld);
756: out.writeBoolean(referencingNew);
757: out.writeObject(oldReferencingName);
758: out.writeObject(newReferencingName);
759: }
760:
761: /**
762: * Get the formatID which corresponds to this class.
763: *
764: * @return the formatID of this class
765: */
766: public int getTypeFormatId() {
767: return StoredFormatIds.TRIGGER_DESCRIPTOR_V01_ID;
768: }
769:
770: /** @see TupleDescriptor#getDescriptorType */
771: public String getDescriptorType() {
772: return "Trigger";
773: }
774:
775: /** @see TupleDescriptor#getDescriptorName */
776: public String getDescriptorName() {
777: return name;
778: }
779:
780: }
|