| /* |
| |
| Derby - Class org.apache.derby.iapi.sql.dictionary.TriggerDescriptor |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.iapi.sql.dictionary; |
| |
| import org.apache.derby.iapi.services.io.ArrayUtil; |
| import org.apache.derby.iapi.services.io.Formatable; |
| import org.apache.derby.iapi.sql.depend.Dependent; |
| import org.apache.derby.iapi.sql.depend.Provider; |
| import org.apache.derby.iapi.error.StandardException; |
| import org.apache.derby.iapi.types.DataTypeUtilities; |
| import org.apache.derby.catalog.UUID; |
| import java.sql.Timestamp; |
| |
| import org.apache.derby.iapi.reference.SQLState; |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| import org.apache.derby.iapi.sql.StatementType; |
| import org.apache.derby.catalog.DependableFinder; |
| import org.apache.derby.catalog.Dependable; |
| import org.apache.derby.iapi.services.io.StoredFormatIds; |
| import org.apache.derby.iapi.sql.depend.DependencyManager; |
| import org.apache.derby.iapi.sql.conn.LanguageConnectionContext; |
| import org.apache.derby.iapi.store.access.TransactionController; |
| import org.apache.derby.iapi.services.context.Context; |
| import org.apache.derby.iapi.services.context.ContextService; |
| |
| import org.apache.derby.iapi.sql.compile.CompilerContext; |
| import org.apache.derby.iapi.sql.compile.Parser; |
| import org.apache.derby.iapi.sql.compile.Visitable; |
| |
| import java.io.ObjectOutput; |
| import java.io.ObjectInput; |
| import java.io.IOException; |
| import java.security.AccessController; |
| import java.security.PrivilegedAction; |
| |
| /** |
| * A trigger. |
| * <p> |
| * We are dependent on TableDescriptors, SPSDescriptors (for our |
| * WHEN clause and our action). Note that we don't strictly |
| * need to be dependent on out SPSes because we could just disallow |
| * anyone from dropping an sps of type 'T', but to keep dependencies |
| * uniform, we'll do be dependent. |
| * <p> |
| * We are a provider for DML (PreparedStatements or SPSes) |
| * |
| * The public methods for this class are: |
| * |
| * <ol> |
| * <li>getUUID |
| * <li>getName |
| * <li>getSchemaDescriptor |
| * <li> public boolean listensForEvent(int event); |
| * <li> public int getTriggerEventMask(); |
| * <li> public Timestamp getCreationTimestamp(); |
| * <li> public boolean isBeforeTrigger(); |
| * <li> public boolean isRowTrigger(); |
| * <li> public UUID getActionId(); |
| * <li> public SPSDescriptor getActionSPS(); |
| * <li> public UUID getWhenClauseId(); |
| * <li> public SPSDescriptor getWhenClauseSPS() |
| * <li> public String getWhenClauseText(); |
| * <li> public TableDescriptor getTableDescriptor() |
| * <li> public ReferencedColumns getReferencedColumnsDescriptor() |
| * <li> public int[] getReferencedCols(); |
| * <li> public int[] getReferencedColsInTriggerAction(); |
| * <li> public boolean enforced(); |
| * <li> public void setEnabled(); |
| * <li> public void setDisabled(); |
| * <li> public boolean needsToFire(int stmtType, int[] modifiedCols) |
| * <li> public String getTriggerDefinition(); |
| * <li> public boolean getReferencingOld(); |
| * <li> public boolean getReferencingNew(); |
| * <li> public String getOldReferencingName(); |
| * <li> public String getNewReferencingName(); |
| * </ol> |
| */ |
| public class TriggerDescriptor extends UniqueSQLObjectDescriptor |
| implements Provider, Dependent, Formatable |
| { |
| // field that we want users to be able to know about |
| public static final int SYSTRIGGERS_STATE_FIELD = 8; |
| |
| public static final int TRIGGER_EVENT_UPDATE = 1; |
| public static final int TRIGGER_EVENT_DELETE = 2; |
| public static final int TRIGGER_EVENT_INSERT = 4; |
| |
| |
| private UUID id; |
| private String name; |
| private String oldReferencingName; |
| private String newReferencingName; |
| private String triggerDefinition; |
| private SchemaDescriptor sd; |
| private int eventMask; |
| private boolean isBefore; |
| private boolean isRow; |
| private boolean referencingOld; |
| private boolean referencingNew; |
| private TableDescriptor td; |
| private UUID actionSPSId; |
| private SPSDescriptor actionSPS; |
| private UUID whenSPSId; |
| private SPSDescriptor whenSPS; |
| private boolean isEnabled; |
| private int[] referencedCols; |
| private int[] referencedColsInTriggerAction; |
| private Timestamp creationTimestamp; |
| private UUID triggerSchemaId; |
| private UUID triggerTableId; |
| private String whenClauseText; |
| |
| |
| /** |
| * Niladic constructor, for formatable |
| */ |
| public TriggerDescriptor() {} |
| |
| /** |
| * Constructor. Used when creating a trigger from SYS.SYSTRIGGERS |
| * |
| * @param dataDictionary the data dictionary |
| * @param sd the schema descriptor for this trigger |
| * @param id the trigger id |
| * @param name the trigger name |
| * @param eventMask TriggerDescriptor.TRIGGER_EVENT_XXXX |
| * @param isBefore is this a before (as opposed to after) trigger |
| * @param isRow is this a row trigger or statement trigger |
| * @param isEnabled is this trigger enabled or disabled |
| * @param td the table upon which this trigger is defined |
| * @param whenSPSId the sps id for the when clause (may be null) |
| * @param actionSPSId the spsid for the trigger action (may be null) |
| * @param creationTimestamp when was this trigger created? |
| * @param referencedCols what columns does this trigger reference (may be null) |
| * @param referencedColsInTriggerAction what columns does the trigger |
| * action reference through old/new transition variables |
| * (may be null) |
| * @param triggerDefinition The original user text of the trigger action |
| * @param referencingOld whether or not OLD appears in REFERENCING clause |
| * @param referencingNew whether or not NEW appears in REFERENCING clause |
| * @param oldReferencingName old referencing table name, if any, that appears in REFERCING clause |
| * @param newReferencingName new referencing table name, if any, that appears in REFERCING clause |
| * @param whenClauseText the SQL text of the WHEN clause, or {@code null} |
| * if there is no WHEN clause |
| */ |
| TriggerDescriptor |
| ( |
| DataDictionary dataDictionary, |
| SchemaDescriptor sd, |
| UUID id, |
| String name, |
| int eventMask, |
| boolean isBefore, |
| boolean isRow, |
| boolean isEnabled, |
| TableDescriptor td, |
| UUID whenSPSId, |
| UUID actionSPSId, |
| Timestamp creationTimestamp, |
| int[] referencedCols, |
| int[] referencedColsInTriggerAction, |
| String triggerDefinition, |
| boolean referencingOld, |
| boolean referencingNew, |
| String oldReferencingName, |
| String newReferencingName, |
| String whenClauseText |
| ) |
| { |
| super(dataDictionary); |
| this.id = id; |
| this.sd = sd; |
| this.name = name; |
| this.eventMask = eventMask; |
| this.isBefore = isBefore; |
| this.isRow = isRow; |
| this.td = td; |
| this.actionSPSId = actionSPSId; |
| this.whenSPSId = whenSPSId; |
| this.isEnabled = isEnabled; |
| this.referencedCols = referencedCols; |
| setReferencedColsInTriggerAction( referencedColsInTriggerAction ); |
| this.creationTimestamp = DataTypeUtilities.clone( creationTimestamp ); |
| this.triggerDefinition = triggerDefinition; |
| this.referencingOld = referencingOld; |
| this.referencingNew = referencingNew; |
| this.oldReferencingName = oldReferencingName; |
| this.newReferencingName = newReferencingName; |
| this.whenClauseText = whenClauseText; |
| triggerSchemaId = sd.getUUID(); |
| triggerTableId = td.getUUID(); |
| } |
| |
| |
| /** |
| * Get the trigger UUID |
| * |
| * @return the id |
| */ |
| public UUID getUUID() |
| { |
| return id; |
| } |
| |
| /** |
| * Get the trigger name |
| * |
| * @return the name |
| */ |
| public String getName() |
| { |
| return name; |
| } |
| |
| public UUID getTableId() { |
| return triggerTableId; |
| } |
| |
| /** |
| * Get the triggers schema descriptor |
| * |
| * @return the schema descriptor |
| * |
| * @exception StandardException on error |
| */ |
| public SchemaDescriptor getSchemaDescriptor() |
| throws StandardException |
| { |
| if (sd == null) |
| { |
| sd = getDataDictionary().getSchemaDescriptor(triggerSchemaId, null); |
| } |
| return sd; |
| } |
| |
| /** |
| * Indicate whether this trigger listens for this |
| * type of event. |
| * |
| * @param event TRIGGER_EVENT_XXXX |
| * |
| * @return true if it listens to the specified event. |
| */ |
| public boolean listensForEvent(int event) |
| { |
| return (event & eventMask) == event; |
| } |
| |
| |
| /** |
| * Get the trigger event mask. Currently, a trigger |
| * may only listen for a single event, though it may |
| * OR multiple events in the future. |
| * |
| * @return the trigger event mask |
| */ |
| public int getTriggerEventMask() |
| { |
| return eventMask; |
| } |
| |
| /** |
| * Get the time that this trigger was created. |
| * |
| * @return the time the trigger was created |
| */ |
| public Timestamp getCreationTimestamp() |
| { |
| return DataTypeUtilities.clone( creationTimestamp ); |
| } |
| |
| /** |
| * Is this a before trigger |
| * |
| * @return true if it is a before trigger |
| */ |
| public boolean isBeforeTrigger() |
| { |
| return isBefore; |
| } |
| |
| /** |
| * Is this a row trigger |
| * |
| * @return true if it is a before trigger |
| */ |
| public boolean isRowTrigger() |
| { |
| return isRow; |
| } |
| |
| |
| /** |
| * Get the trigger action sps UUID |
| * |
| * @return the uuid of the sps action |
| */ |
| public UUID getActionId() |
| { |
| return actionSPSId; |
| } |
| |
| /** |
| * Get the trigger action sps from SYSSTATEMENTS. If we find that |
| * the sps is invalid and the trigger is defined at row level and it |
| * has OLD/NEW transient variables through REFERENCES clause, then |
| * the sps from SYSSTATEMENTS may not be valid anymore. In such a |
| * case, we regenerate the trigger action sql and use that for the |
| * sps and update SYSSTATEMENTS using this new sps. This update of |
| * SYSSTATEMENTS was introduced with DERBY-4874 |
| * |
| * @param lcc The LanguageConnectionContext to use. |
| * @return the trigger action sps |
| * |
| * @exception StandardException on error |
| */ |
| public SPSDescriptor getActionSPS(LanguageConnectionContext lcc) |
| throws StandardException |
| { |
| return getSPS(lcc, false /* isWhenClause */); |
| } |
| |
| /** |
| * Get the SPS for the triggered SQL statement or the WHEN clause. |
| * |
| * @param lcc the LanguageConnectionContext to use |
| * @param isWhenClause {@code true} if the SPS for the WHEN clause is |
| * requested, {@code false} if it is the triggered SQL statement |
| * @return the requested SPS |
| * @throws StandardException if an error occurs |
| */ |
| private SPSDescriptor getSPS(LanguageConnectionContext lcc, |
| boolean isWhenClause) |
| throws StandardException |
| { |
| DataDictionary dd = getDataDictionary(); |
| SPSDescriptor sps = isWhenClause ? whenSPS : actionSPS; |
| UUID spsId = isWhenClause ? whenSPSId : actionSPSId; |
| String originalSQL = isWhenClause ? whenClauseText : triggerDefinition; |
| |
| if (sps == null) { |
| //bug 4821 - do the sysstatement look up in a nested readonly |
| //transaction rather than in the user transaction. Because of |
| //this, the nested compile transaction which is attempting to |
| //compile the trigger will not run into any locking issues with |
| //the user transaction for sysstatements. |
| lcc.beginNestedTransaction(true); |
| sps = dd.getSPSDescriptor(spsId); |
| lcc.commitNestedTransaction(); |
| } |
| |
| //We need to regenerate the trigger action sql if |
| //1)the trigger is found to be invalid, |
| //2)the trigger is defined at row level (that is the only kind of |
| // trigger which allows reference to individual columns from |
| // old/new row) |
| //3)the trigger action plan has columns that reference |
| // old/new row columns(if we are working with pre-10.9 db, |
| // meaning we are in soft-upgrade mode, then we won't have |
| // information about the actual trigger action columns since |
| // we didn't keep that info in those releases. For such dbs, |
| // we will just check if they are using REFERENCING OLD and/or |
| // NEW clause.) |
| //This code was added as part of DERBY-4874 where the Alter table |
| //had changed the length of a varchar column from varchar(30) to |
| //varchar(64) but the trigger action plan continued to use varchar(30). |
| //To fix varchar(30) in trigger action sql to varchar(64), we need |
| //to regenerate the trigger action sql. This new trigger action sql |
| //will then get updated into SYSSTATEMENTS table. |
| boolean in10_9_orHigherVersion = dd.checkVersion(DataDictionary.DD_VERSION_DERBY_10_9,null); |
| boolean usesReferencingClause = (in10_9_orHigherVersion) ? |
| referencedColsInTriggerAction != null : |
| (referencingOld || referencingNew); |
| |
| if ((!sps.isValid() || |
| (sps.getPreparedStatement() == null)) && |
| isRow && |
| usesReferencingClause) |
| { |
| CompilerContext newCC = lcc.pushCompilerContext( |
| dd.getSchemaDescriptor(sps.getCompSchemaId(), null)); |
| Parser pa = newCC.getParser(); |
| Visitable stmtnode = |
| isWhenClause ? pa.parseSearchCondition(originalSQL) |
| : pa.parseStatement(originalSQL); |
| lcc.popCompilerContext(newCC); |
| int[] cols; |
| cols = dd.examineTriggerNodeAndCols(stmtnode, |
| oldReferencingName, |
| newReferencingName, |
| originalSQL, |
| referencedCols, |
| referencedColsInTriggerAction, |
| 0, |
| td, |
| -1, |
| false, |
| null); |
| |
| String newText = dd.getTriggerActionString(stmtnode, |
| oldReferencingName, |
| newReferencingName, |
| originalSQL, |
| referencedCols, |
| referencedColsInTriggerAction, |
| 0, |
| td, |
| -1, |
| false, |
| null, |
| cols); |
| |
| if (isWhenClause) { |
| // The WHEN clause is not a full SQL statement, just a search |
| // condition, so we need to turn it into a statement in order |
| // to create an SPS. |
| newText = "VALUES " + newText; |
| } |
| |
| sps.setText(newText); |
| |
| //By this point, we are finished transforming the trigger action if |
| //it has any references to old/new transition variables. |
| } |
| |
| return sps; |
| } |
| |
| /** |
| * Get the trigger when clause sps UUID |
| * |
| * @return the uuid of the sps action |
| */ |
| public UUID getWhenClauseId() |
| { |
| return whenSPSId; |
| } |
| |
| /** |
| * Get the SQL text of the WHEN clause. |
| * @return SQL text for the WHEN clause, or {@code null} if there is |
| * no WHEN clause |
| */ |
| public String getWhenClauseText() { |
| return whenClauseText; |
| } |
| |
| /** |
| * Get the trigger when clause sps |
| * |
| * @param lcc the LanguageConnectionContext to use |
| * @return the sps of the when clause |
| * |
| * @exception StandardException on error |
| */ |
| public SPSDescriptor getWhenClauseSPS(LanguageConnectionContext lcc) |
| throws StandardException |
| { |
| if (whenSPSId == null) { |
| // This trigger doesn't have a WHEN clause. |
| return null; |
| } |
| |
| return getSPS(lcc, true /* isWhenClause */); |
| } |
| |
| /** |
| * Get the trigger table descriptor |
| * |
| * @return the table descripor upon which this trigger |
| * is declared |
| * |
| * @exception StandardException on error |
| */ |
| public TableDescriptor getTableDescriptor() |
| throws StandardException |
| { |
| if (td == null) |
| { |
| td = getDataDictionary().getTableDescriptor(triggerTableId); |
| } |
| return td; |
| } |
| |
| /** |
| * Get the referenced table descriptor for this trigger. |
| * |
| * @return the referenced table descriptor |
| * |
| * @exception StandardException on error |
| */ |
| // caller converts referencedCols to referencedColsDescriptor... |
| // public ReferencedColumns getReferencedColumnsDescriptor() |
| // throws StandardException |
| // { |
| // return (referencedCols == null) ? |
| // (ReferencedColumns)null : |
| // new ReferencedColumnsDescriptorImpl(referencedCols); |
| // } |
| |
| /** |
| * Get the referenced column array for this trigger, used in "alter table |
| * drop column", we get the handle and change it |
| * |
| * @return the referenced column array |
| */ |
| public int[] getReferencedCols() |
| { |
| return ArrayUtil.copy( referencedCols ); |
| } |
| |
| /** Update the array of referenced columns */ |
| public void setReferencedCols( int[] newCols ) |
| { |
| referencedCols = ArrayUtil.copy( newCols ); |
| } |
| |
| /** |
| * Get the referenced column array for the trigger action columns. |
| * |
| * @return the referenced column array |
| */ |
| public int[] getReferencedColsInTriggerAction() |
| { |
| return ArrayUtil.copy( referencedColsInTriggerAction ); |
| } |
| |
| /** Set the referenced column array for trigger actions */ |
| public void setReferencedColsInTriggerAction( int[] referencedColsInTriggerAction ) |
| { |
| this.referencedColsInTriggerAction = ArrayUtil.copy( referencedColsInTriggerAction ); |
| } |
| |
| /** |
| * Is this trigger enforced |
| * |
| * @return true if it is enforced |
| */ |
| public boolean isEnabled() |
| { |
| return isEnabled; |
| } |
| |
| /** |
| * Mark this trigger as enforced |
| * |
| */ |
| public void setEnabled() |
| { |
| isEnabled = true; |
| } |
| |
| /** |
| * Mark this trigger as disabled |
| * |
| */ |
| public void setDisabled() |
| { |
| isEnabled = false; |
| } |
| |
| /** |
| * Does this trigger need to fire on this type of |
| * DML? |
| * |
| * @param stmtType the type of DML |
| * (StatementType.INSERT|StatementType.UPDATE|StatementType.DELETE) |
| * @param modifiedCols the columns modified, or null for all |
| * |
| * @return true/false |
| * |
| * @exception StandardException on error |
| */ |
| public boolean needsToFire(int stmtType, int[] modifiedCols) |
| throws StandardException |
| { |
| |
| if (SanityManager.DEBUG) |
| { |
| if (!((stmtType == StatementType.INSERT) || |
| (stmtType == StatementType.BULK_INSERT_REPLACE) || |
| (stmtType == StatementType.UPDATE) || |
| (stmtType == StatementType.DELETE))) |
| { |
| SanityManager.THROWASSERT("invalid statement type "+stmtType); |
| } |
| } |
| |
| /* |
| ** If we are disabled, we never fire |
| */ |
| if (!isEnabled) |
| { |
| return false; |
| } |
| |
| if (stmtType == StatementType.INSERT) |
| { |
| return (eventMask & TRIGGER_EVENT_INSERT) == eventMask; |
| } |
| if (stmtType == StatementType.DELETE) |
| { |
| return (eventMask & TRIGGER_EVENT_DELETE) == eventMask; |
| } |
| |
| // this is a temporary restriction, but it may not be lifted |
| // anytime soon. |
| if (stmtType == StatementType.BULK_INSERT_REPLACE) |
| { |
| throw StandardException.newException(SQLState.LANG_NO_BULK_INSERT_REPLACE_WITH_TRIGGER, |
| getTableDescriptor().getQualifiedName(), name); |
| } |
| |
| // if update, only relevant if columns intersect |
| return ((eventMask & TRIGGER_EVENT_UPDATE) == eventMask) && |
| ConstraintDescriptor.doColumnsIntersect(modifiedCols, referencedCols); |
| } |
| |
| /** |
| * Get the original trigger definition. |
| * |
| * @return The trigger definition. |
| */ |
| public String getTriggerDefinition() |
| { |
| return triggerDefinition; |
| } |
| |
| /** |
| * Get whether or not OLD was replaced |
| * in the REFERENCING clause. |
| * |
| * @return Whether or not OLD was replaced |
| * in the REFERENCING clause. |
| */ |
| public boolean getReferencingOld() |
| { |
| return referencingOld; |
| } |
| |
| /** |
| * Get whether or not NEW was replaced |
| * in the REFERENCING clause. |
| * |
| * @return Whether or not NEW was replaced |
| * in the REFERENCING clause. |
| */ |
| public boolean getReferencingNew() |
| { |
| return referencingNew; |
| } |
| |
| /** |
| * Get the old Referencing name, if any, |
| * from the REFERENCING clause. |
| * |
| * @return The old Referencing name, if any, |
| * from the REFERENCING clause. |
| */ |
| public String getOldReferencingName() |
| { |
| return oldReferencingName; |
| } |
| |
| /** |
| * Get the new Referencing name, if any, |
| * from the REFERENCING clause. |
| * |
| * @return The new Referencing name, if any, |
| * from the REFERENCING clause. |
| */ |
| public String getNewReferencingName() |
| { |
| return newReferencingName; |
| } |
| |
| public String toString() |
| { |
| if (SanityManager.DEBUG) |
| { |
| return "TRIGGER: "+name; |
| } |
| else |
| { |
| return ""; |
| } |
| } |
| |
| //////////////////////////////////////////////////////////////////// |
| // |
| // PROVIDER INTERFACE |
| // |
| //////////////////////////////////////////////////////////////////// |
| |
| /** |
| * @return the stored form of this provider |
| * |
| * @see Dependable#getDependableFinder |
| */ |
| public DependableFinder getDependableFinder() |
| { |
| return getDependableFinder(StoredFormatIds.TRIGGER_DESCRIPTOR_FINDER_V01_ID); |
| } |
| |
| /** |
| * Return the name of this Provider. (Useful for errors.) |
| * |
| * @return String The name of this provider. |
| */ |
| public String getObjectName() |
| { |
| return name; |
| } |
| |
| /** |
| * Get the provider's UUID |
| * |
| * @return The provider's UUID |
| */ |
| public UUID getObjectID() |
| { |
| return id; |
| } |
| |
| /** |
| * Get the provider's type. |
| * |
| * @return char The provider's type. |
| */ |
| public String getClassType() |
| { |
| return Dependable.TRIGGER; |
| } |
| |
| ////////////////////////////////////////////////////// |
| // |
| // DEPENDENT INTERFACE |
| // |
| // Triggers are dependent on the underlying table, |
| // and their spses (for the trigger action and the WHEN |
| // clause). |
| // |
| ////////////////////////////////////////////////////// |
| /** |
| * Check that all of the dependent's dependencies are valid. |
| * |
| * @return true if the dependent is currently valid |
| */ |
| public synchronized boolean isValid() |
| { |
| return true; |
| } |
| |
| /** |
| * Prepare to mark the dependent as invalid (due to at least one of |
| * its dependencies being invalid). |
| * |
| * @param action The action causing the invalidation |
| * @param p the provider |
| * @param lcc the language connection context |
| * |
| * @exception StandardException thrown if unable to make it invalid |
| */ |
| public void prepareToInvalidate |
| ( |
| Provider p, |
| int action, |
| LanguageConnectionContext lcc |
| ) throws StandardException |
| { |
| switch (action) |
| { |
| /* |
| ** We are dependent on the underlying table, our SPSs, any tables |
| ** or other SQL objects that are referenced from the trigger, and |
| ** privileges on various objects. (we should be dropped before our |
| ** table is dropped. Also, we should be dropped before revoke |
| ** RESTRICT privilege is issued otherwise revoke RESTRICT will |
| ** throw an exception). |
| ** Currently, in Derby, an execute routine privilege can be revoked |
| ** only if there are no dependents on that privilege. When revoke |
| ** execute RESTRICT is exectued, all the dependents will receive |
| ** REVOKE_PRIVILEGE_RESTRICT and they should throw exception. |
| ** We handle this for TriggerDescriptor by throwning an exception |
| ** below. For all the other types of revoke privileges, for |
| ** instance, SELECT, UPDATE, DELETE, INSERT, REFERENCES, |
| ** TRIGGER, we don't do anything here and later in makeInvalid, we |
| ** make the TriggerDescriptor drop itself. |
| */ |
| case DependencyManager.DROP_TABLE: |
| case DependencyManager.DROP_SYNONYM: |
| case DependencyManager.DROP_SPS: |
| case DependencyManager.DROP_VIEW: |
| case DependencyManager.RENAME: |
| case DependencyManager.REVOKE_PRIVILEGE_RESTRICT: |
| case DependencyManager.DROP_METHOD_ALIAS: |
| DependencyManager dm = getDataDictionary().getDependencyManager(); |
| throw StandardException.newException(SQLState.LANG_PROVIDER_HAS_DEPENDENT_OBJECT, |
| dm.getActionString(action), |
| p.getObjectName(), "TRIGGER", name); |
| |
| /* |
| ** The trigger descriptor depends on the trigger table. |
| ** This means that we get called whenever anything happens |
| ** to the trigger table. There are so many cases where this |
| ** can happen that it doesn't make sense to have an assertion |
| ** here to check whether the action was expected (it makes |
| ** the code hard to maintain, and creates a big switch statement). |
| */ |
| default: |
| break; |
| } |
| } |
| |
| /** |
| * Mark the dependent as invalid (due to at least one of |
| * its dependencies being invalid). Always an error |
| * for a trigger -- should never have gotten here. |
| * |
| * @param lcc the language connection context |
| * @param action The action causing the invalidation |
| * |
| * @exception StandardException thrown if called in sanity mode |
| */ |
| public void makeInvalid(int action, LanguageConnectionContext lcc) throws StandardException |
| { |
| // No sanity check for valid action. Trigger descriptors depend on |
| // the trigger table, so there is a very large number of actions |
| // that we would have to check against. This is hard to maintain, |
| // so don't bother. |
| |
| switch (action) |
| { |
| // invalidate this trigger descriptor |
| case DependencyManager.USER_RECOMPILE_REQUEST: |
| DependencyManager dm = getDataDictionary().getDependencyManager(); |
| dm.invalidateFor(this, DependencyManager.PREPARED_STATEMENT_RELEASE, lcc); |
| break; |
| |
| // When REVOKE_PRIVILEGE gets sent (this happens for privilege |
| // types SELECT, UPDATE, DELETE, INSERT, REFERENCES, TRIGGER), we |
| // make the TriggerDescriptor drop itself. |
| // Ditto for revoking a role conferring a privilege. |
| case DependencyManager.REVOKE_PRIVILEGE: |
| case DependencyManager.REVOKE_ROLE: |
| drop(lcc); |
| |
| lcc.getLastActivation().addWarning( |
| StandardException.newWarning( |
| SQLState.LANG_TRIGGER_DROPPED, |
| this.getObjectName() )); |
| break; |
| |
| default: |
| break; |
| } |
| |
| } |
| |
| public void drop(LanguageConnectionContext lcc) throws StandardException |
| { |
| DataDictionary dd = getDataDictionary(); |
| DependencyManager dm = getDataDictionary().getDependencyManager(); |
| TransactionController tc = lcc.getTransactionExecute(); |
| dm.invalidateFor(this, DependencyManager.DROP_TRIGGER, lcc); |
| |
| // Drop the trigger |
| dd.dropTriggerDescriptor(this, tc); |
| |
| // Clear the dependencies for the trigger |
| dm.clearDependencies(lcc, this); |
| |
| // Drop the spses |
| SPSDescriptor spsd = dd.getSPSDescriptor(this.getActionId()); |
| |
| // there shouldn't be any dependencies, but in case |
| // there are, lets clear them |
| dm.invalidateFor(spsd, DependencyManager.DROP_TRIGGER, lcc); |
| dm.clearDependencies(lcc, spsd); |
| dd.dropSPSDescriptor(spsd, tc); |
| |
| if (getWhenClauseId() != null) |
| { |
| spsd = dd.getSPSDescriptor(getWhenClauseId()); |
| dm.invalidateFor(spsd, DependencyManager.DROP_TRIGGER, lcc); |
| dm.clearDependencies(lcc, spsd); |
| dd.dropSPSDescriptor(spsd, tc); |
| } |
| } |
| |
| |
| ////////////////////////////////////////////////////////////// |
| // |
| // FORMATABLE |
| // |
| ////////////////////////////////////////////////////////////// |
| |
| /** |
| * Read this object from a stream of stored objects. |
| * |
| * @param in read this. |
| * |
| * @exception IOException thrown on error |
| * @exception ClassNotFoundException thrown on error |
| */ |
| public void readExternal(ObjectInput in) |
| throws IOException, ClassNotFoundException |
| { |
| id = (UUID)in.readObject(); |
| name = (String)in.readObject(); |
| triggerSchemaId = (UUID)in.readObject(); |
| triggerTableId = (UUID)in.readObject(); |
| eventMask = in.readInt(); |
| isBefore = in.readBoolean(); |
| isRow = in.readBoolean(); |
| isEnabled = in.readBoolean(); |
| whenSPSId = (UUID)in.readObject(); |
| actionSPSId = (UUID)in.readObject(); |
| int length = in.readInt(); |
| if (length != 0) |
| { |
| referencedCols = new int[length]; |
| for (int i = 0; i < length; i++) |
| { |
| referencedCols[i] = in.readInt(); |
| } |
| } |
| length = in.readInt(); |
| if (length != 0) |
| { |
| referencedColsInTriggerAction = new int[length]; |
| for (int i = 0; i < length; i++) |
| { |
| referencedColsInTriggerAction[i] = in.readInt(); |
| } |
| } |
| triggerDefinition = (String)in.readObject(); |
| referencingOld = in.readBoolean(); |
| referencingNew = in.readBoolean(); |
| oldReferencingName = (String)in.readObject(); |
| newReferencingName = (String)in.readObject(); |
| whenClauseText = (String) in.readObject(); |
| } |
| |
| protected DataDictionary getDataDictionary() |
| { |
| /* |
| note: we need to do this since when this trigger is read back from |
| disk (when it is associated with a sps), the dataDictionary has not |
| been initialized and therefore can give a NullPointerException |
| */ |
| DataDictionary dd = super.getDataDictionary(); |
| if (dd == null) |
| { |
| LanguageConnectionContext lcc = (LanguageConnectionContext) |
| getContext(LanguageConnectionContext.CONTEXT_ID); |
| dd = lcc.getDataDictionary(); |
| setDataDictionary(dd); |
| } |
| return dd; |
| } |
| |
| /** |
| * Write this object to a stream of stored objects. |
| * |
| * @param out write bytes here. |
| * |
| * @exception IOException thrown on error |
| */ |
| public void writeExternal( ObjectOutput out ) |
| throws IOException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(triggerSchemaId != null, |
| "triggerSchemaId expected to be non-null"); |
| SanityManager.ASSERT(triggerTableId != null, |
| "triggerTableId expected to be non-null"); |
| } |
| out.writeObject(id); |
| out.writeObject(name); |
| out.writeObject(triggerSchemaId); |
| out.writeObject(triggerTableId); |
| out.writeInt(eventMask); |
| out.writeBoolean(isBefore); |
| out.writeBoolean(isRow); |
| out.writeBoolean(isEnabled); |
| out.writeObject(whenSPSId); |
| out.writeObject(actionSPSId); |
| if (referencedCols == null) |
| { |
| out.writeInt(0); |
| } |
| else |
| { |
| out.writeInt(referencedCols.length); |
| for (int i = 0; i < referencedCols.length; i++) |
| { |
| out.writeInt(referencedCols[i]); |
| } |
| } |
| if (referencedColsInTriggerAction == null) |
| { |
| out.writeInt(0); |
| } |
| else |
| { |
| out.writeInt(referencedColsInTriggerAction.length); |
| for (int i = 0; i < referencedColsInTriggerAction.length; i++) |
| { |
| out.writeInt(referencedColsInTriggerAction[i]); |
| } |
| } |
| out.writeObject(triggerDefinition); |
| out.writeBoolean(referencingOld); |
| out.writeBoolean(referencingNew); |
| out.writeObject(oldReferencingName); |
| out.writeObject(newReferencingName); |
| out.writeObject(whenClauseText); |
| } |
| |
| /** |
| * Get the formatID which corresponds to this class. |
| * |
| * @return the formatID of this class |
| */ |
| public int getTypeFormatId() { |
| return StoredFormatIds.TRIGGER_DESCRIPTOR_V01_ID; |
| } |
| |
| /** @see TupleDescriptor#getDescriptorType */ |
| public String getDescriptorType() |
| { |
| return "Trigger"; |
| } |
| |
| /** @see TupleDescriptor#getDescriptorName */ |
| public String getDescriptorName() { return name; } |
| |
| |
| /** |
| * Privileged lookup of a Context. Must be private so that user code |
| * can't call this entry point. |
| */ |
| private static Context getContext( final String contextID ) |
| { |
| if ( System.getSecurityManager() == null ) |
| { |
| return ContextService.getContext( contextID ); |
| } |
| else |
| { |
| return AccessController.doPrivileged |
| ( |
| new PrivilegedAction<Context>() |
| { |
| public Context run() |
| { |
| return ContextService.getContext( contextID ); |
| } |
| } |
| ); |
| } |
| } |
| |
| } |
| |