KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > derby > impl > sql > compile > CreateTriggerNode


1 /*
2
3    Derby - Class org.apache.derby.impl.sql.compile.CreateTriggerNode
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.compile;
23
24 import org.apache.derby.iapi.services.context.ContextManager;
25 import org.apache.derby.iapi.services.monitor.Monitor;
26 import org.apache.derby.iapi.services.sanity.SanityManager;
27
28 import org.apache.derby.iapi.error.StandardException;
29
30 import org.apache.derby.iapi.sql.compile.CompilerContext;
31 import org.apache.derby.iapi.sql.compile.Parser;
32
33 import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
34 import org.apache.derby.iapi.sql.dictionary.DataDescriptorGenerator;
35 import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext;
36 import org.apache.derby.iapi.sql.dictionary.DataDictionary;
37 import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
38 import org.apache.derby.iapi.sql.dictionary.SPSDescriptor;
39 import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
40 import org.apache.derby.iapi.sql.dictionary.TriggerDescriptor;
41
42 import org.apache.derby.iapi.sql.conn.Authorizer;
43 import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
44
45 import org.apache.derby.iapi.sql.depend.Dependent;
46
47 import org.apache.derby.iapi.reference.SQLState;
48
49 import org.apache.derby.iapi.sql.execute.ConstantAction;
50
51 import org.apache.derby.iapi.sql.ResultSet;
52
53 import org.apache.derby.iapi.types.DataTypeDescriptor;
54 import org.apache.derby.iapi.types.TypeId;
55
56 import org.apache.derby.catalog.UUID;
57 import java.sql.Timestamp JavaDoc;
58 import java.sql.Types JavaDoc;
59 import java.util.Enumeration JavaDoc;
60 import java.util.Hashtable JavaDoc;
61 import java.util.Vector JavaDoc;
62
63 /**
64  * A CreateTriggerNode is the root of a QueryTree
65  * that represents a CREATE TRIGGER
66  * statement.
67  *
68  * @author jamie
69  */

70
71 public class CreateTriggerNode extends DDLStatementNode
72 {
73     private TableName triggerName;
74     private TableName tableName;
75     private int triggerEventMask;
76     private ResultColumnList triggerCols;
77     private boolean isBefore;
78     private boolean isRow;
79     private boolean isEnabled;
80     private Vector JavaDoc refClause;
81     private QueryTreeNode whenClause;
82     private String JavaDoc whenText;
83     private int whenOffset;
84     private StatementNode actionNode;
85     private String JavaDoc actionText;
86     private String JavaDoc originalActionText; // text w/o trim of spaces
87
private int actionOffset;
88
89     private SchemaDescriptor triggerSchemaDescriptor;
90     private SchemaDescriptor compSchemaDescriptor;
91     private int[] referencedColInts;
92     private TableDescriptor triggerTableDescriptor;
93     private UUID actionCompSchemaId;
94
95     /*
96     ** Names of old and new table. By default we have
97     ** OLD/old and NEW/new. The casing is dependent on
98     ** the language connection context casing as the rest
99     ** of other code. Therefore we will set the value of the
100     ** String at the init() time.
101     ** However, if there is a referencing clause
102     ** we will reset these values to be whatever the user
103     ** wants.
104     */

105     private String JavaDoc oldTableName;
106     private String JavaDoc newTableName;
107
108     private boolean oldTableInReferencingClause;
109     private boolean newTableInReferencingClause;
110
111
112     /**
113      * Initializer for a CreateTriggerNode
114      *
115      * @param triggerName name of the trigger
116      * @param tableName name of the table which the trigger is declared upon
117      * @param triggerEventMask TriggerDescriptor.TRIGGER_EVENT_XXX
118      * @param triggerCols columns trigger is to fire upon. Valid
119      * for UPDATE case only.
120      * @param isBefore is before trigger (false for after)
121      * @param isRow true for row trigger, false for statement
122      * @param isEnabled true if enabled
123      * @param refClause the referencing clause
124      * @param whenClause the WHEN clause tree
125      * @param whenText the text of the WHEN clause
126      * @param whenOffset offset of start of WHEN clause
127      * @param actionNode the trigger action tree
128      * @param actionText the text of the trigger action
129      * @param actionOffset offset of start of action clause
130      *
131      * @exception StandardException Thrown on error
132      */

133     public void init
134     (
135         Object JavaDoc triggerName,
136         Object JavaDoc tableName,
137         Object JavaDoc triggerEventMask,
138         Object JavaDoc triggerCols,
139         Object JavaDoc isBefore,
140         Object JavaDoc isRow,
141         Object JavaDoc isEnabled,
142         Object JavaDoc refClause,
143         Object JavaDoc whenClause,
144         Object JavaDoc whenText,
145         Object JavaDoc whenOffset,
146         Object JavaDoc actionNode,
147         Object JavaDoc actionText,
148         Object JavaDoc actionOffset
149     ) throws StandardException
150     {
151         initAndCheck(triggerName);
152         this.triggerName = (TableName) triggerName;
153         this.tableName = (TableName) tableName;
154         this.triggerEventMask = ((Integer JavaDoc) triggerEventMask).intValue();
155         this.triggerCols = (ResultColumnList) triggerCols;
156         this.isBefore = ((Boolean JavaDoc) isBefore).booleanValue();
157         this.isRow = ((Boolean JavaDoc) isRow).booleanValue();
158         this.isEnabled = ((Boolean JavaDoc) isEnabled).booleanValue();
159         this.refClause = (Vector JavaDoc) refClause;
160         this.whenClause = (QueryTreeNode) whenClause;
161         this.whenText = (whenText == null) ? null : ((String JavaDoc) whenText).trim();
162         this.whenOffset = ((Integer JavaDoc) whenOffset).intValue();
163         this.actionNode = (StatementNode) actionNode;
164         this.originalActionText = (String JavaDoc) actionText;
165         this.actionText =
166                     (actionText == null) ? null : ((String JavaDoc) actionText).trim();
167         this.actionOffset = ((Integer JavaDoc) actionOffset).intValue();
168
169         implicitCreateSchema = true;
170     }
171
172     public String JavaDoc statementToString()
173     {
174         return "CREATE TRIGGER";
175     }
176
177     /**
178      * Prints the sub-nodes of this object. See QueryTreeNode.java for
179      * how tree printing is supposed to work.
180      *
181      * @param depth The depth of this node in the tree
182      */

183
184     public void printSubNodes(int depth)
185     {
186         if (SanityManager.DEBUG)
187         {
188             super.printSubNodes(depth);
189
190             if (triggerCols != null)
191             {
192                 printLabel(depth, "triggerColumns: ");
193                 triggerCols.treePrint(depth + 1);
194             }
195             if (whenClause != null)
196             {
197                 printLabel(depth, "whenClause: ");
198                 whenClause.treePrint(depth + 1);
199             }
200             if (actionNode != null)
201             {
202                 printLabel(depth, "actionNode: ");
203                 actionNode.treePrint(depth + 1);
204             }
205         }
206     }
207
208
209     // accessors
210

211
212     // We inherit the generate() method from DDLStatementNode.
213

214     /**
215      * Bind this CreateTriggerNode. This means doing any static error
216      * checking that can be done before actually creating the table.
217      *
218      * @return The bound query tree
219      *
220      * @exception StandardException Thrown on error
221      */

222     public QueryTreeNode bind() throws StandardException
223     {
224         CompilerContext compilerContext = getCompilerContext();
225         DataDictionary dd = getDataDictionary();
226         /*
227         ** Grab the current schema. We will use that for
228         ** sps compilation
229         */

230         LanguageConnectionContext lcc = getLanguageConnectionContext();
231         compSchemaDescriptor = lcc.getDefaultSchema();
232
233         /*
234         ** Get and check the schema descriptor for this
235         ** trigger. This check will throw the proper exception
236         ** if someone tries to create a trigger in the SYS
237         ** schema.
238         */

239         triggerSchemaDescriptor = getSchemaDescriptor();
240
241         /*
242         ** Get the trigger table.
243         */

244         triggerTableDescriptor = getTableDescriptor(tableName);
245
246         //throw an exception if user is attempting to create a trigger on a temporary table
247
if (isSessionSchema(triggerTableDescriptor.getSchemaDescriptor()))
248         {
249                 throw StandardException.newException(SQLState.LANG_OPERATION_NOT_ALLOWED_ON_SESSION_SCHEMA_TABLES);
250         }
251         if (isPrivilegeCollectionRequired())
252         {
253             compilerContext.pushCurrentPrivType(Authorizer.TRIGGER_PRIV);
254             compilerContext.addRequiredTablePriv(triggerTableDescriptor);
255             compilerContext.popCurrentPrivType();
256         }
257
258         /*
259         ** Regenerates the actionText and actionNode if necessary.
260         */

261         boolean needInternalSQL = bindReferencesClause(dd);
262
263         lcc.pushTriggerTable(triggerTableDescriptor);
264         try
265         {
266             /*
267             ** Bind the trigger action and the trigger
268             ** when clause to make sure that they are
269             ** ok. Note that we have already substituted
270             ** in various replacements for OLD/NEW transition
271             ** tables/variables and reparsed if necessary.
272             */

273             if (needInternalSQL)
274                 compilerContext.setReliability(CompilerContext.INTERNAL_SQL_LEGAL);
275             
276             // For before triggers, the action statement cannot contain calls
277
// to procedures that modify SQL data. If the action statement
278
// contains a procedure call, this reliability will be used during
279
// bind of the call statement node.
280
if(isBefore)
281                 compilerContext.setReliability(CompilerContext.MODIFIES_SQL_DATA_PROCEDURE_ILLEGAL);
282                     
283             actionNode.bind();
284
285             if (whenClause != null)
286             {
287                 whenClause.bind();
288             }
289         }
290         finally
291         {
292             lcc.popTriggerTable(triggerTableDescriptor);
293         }
294
295         /*
296         ** Statement is dependent on the TableDescriptor
297         */

298         compilerContext.createDependency(triggerTableDescriptor);
299
300         /*
301         ** If there is a list of columns, then no duplicate columns,
302         ** and all columns must be found.
303         */

304         if (triggerCols != null && triggerCols.size() != 0)
305         {
306             referencedColInts = new int[triggerCols.size()];
307             Hashtable JavaDoc columnNames = new Hashtable JavaDoc();
308             int tcSize = triggerCols.size();
309             for (int i = 0; i < tcSize; i++)
310             {
311                 ResultColumn rc = (ResultColumn) triggerCols.elementAt(i);
312                 if (columnNames.put(rc.getName(), rc) != null)
313                 {
314                     throw StandardException.newException(SQLState.LANG_DUPLICATE_COLUMN_IN_TRIGGER_UPDATE,
315                                             rc.getName(),
316                                             triggerName);
317                 }
318
319                 ColumnDescriptor cd = triggerTableDescriptor.getColumnDescriptor(rc.getName());
320                 if (cd == null)
321                 {
322                     throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE,
323                                                                 rc.getName(),
324                                                                 tableName);
325                 }
326
327                 referencedColInts[i] = cd.getPosition();
328             }
329  
330             // sort the list
331
java.util.Arrays.sort(referencedColInts);
332         }
333
334         //If attempting to reference a SESSION schema table (temporary or permanent) in the trigger action, throw an exception
335
if (actionNode.referencesSessionSchema())
336             throw StandardException.newException(SQLState.LANG_OPERATION_NOT_ALLOWED_ON_SESSION_SCHEMA_TABLES);
337
338         return this;
339     }
340
341     /**
342      * Return true if the node references SESSION schema tables (temporary or permanent)
343      *
344      * @return true if references SESSION schema tables, else false
345      *
346      * @exception StandardException Thrown on error
347      */

348     public boolean referencesSessionSchema()
349         throws StandardException
350     {
351         //If create trigger is part of create statement and the trigger is defined on or it references SESSION schema tables,
352
//it will get caught in the bind phase of trigger and exception will be thrown by the trigger bind.
353
return (isSessionSchema(triggerTableDescriptor.getSchemaName()) || actionNode.referencesSessionSchema());
354     }
355
356     /*
357     ** BIND OLD/NEW TRANSITION TABLES/VARIABLES
358     **
359     ** 1) validate the referencing clause (if any)
360     **
361     ** 2) convert trigger action text. e.g.
362     ** DELETE FROM t WHERE c = old.c
363     ** turns into
364     ** DELETE FROM t WHERE c = org.apache.derby.iapi.db.Factory::
365     ** getTriggerExecutionContext().getOldRow().getInt('C');
366     ** or
367     ** DELETE FROM t WHERE c in (SELECT c FROM OLD)
368     ** turns into
369     ** DELETE FROM t WHERE c in (SELECT c FROM new TriggerOldTransitionTable OLD)
370     **
371     ** 3) check all column references against new/old transition
372     ** variables (since they are no longer 'normal' column references
373     ** that will be checked during bind)
374     **
375     ** 4) reparse the new action text
376     **
377     ** You might be wondering why we regenerate the text and reparse
378     ** instead of just reworking the tree to have the nodes we want.
379     ** Well, the primary reason is that if we screwed with the tree,
380     ** then we would have a major headache if this trigger action
381     ** was ever recompiled -- spses don't really know that they are
382     ** triggers so it would be quite arduous to figure out that an
383     ** sps is a trigger and munge up its query tree after figuring
384     ** out what its OLD/NEW tables are, etc. Also, it is just plain
385     ** easier to just generate the sql and rebind.
386     */

387     private boolean bindReferencesClause(DataDictionary dd) throws StandardException
388     {
389         validateReferencesClause(dd);
390
391         StringBuffer JavaDoc newText = new StringBuffer JavaDoc();
392         boolean regenNode = false;
393         int start = 0;
394         if (isRow)
395         {
396             /*
397             ** For a row trigger, we find all column references. If
398             ** they are referencing NEW or OLD we turn them into
399             ** getTriggerExecutionContext().getOldRow().getInt('C');
400             */

401             CollectNodesVisitor visitor = new CollectNodesVisitor(ColumnReference.class);
402             actionNode.accept(visitor);
403             Vector JavaDoc refs = visitor.getList();
404             /* we need to sort on position in string, beetle 4324
405              */

406             QueryTreeNode[] cols = sortRefs(refs, true);
407
408             for (int i = 0; i < cols.length; i++)
409             {
410                 ColumnReference ref = (ColumnReference) cols[i];
411                 
412                 /*
413                 ** Only occurrences of those OLD/NEW transition tables/variables
414                 ** are of interest here. There may be intermediate nodes in the
415                 ** parse tree that have its own RCL which contains copy of
416                 ** column references(CR) from other nodes. e.g.:
417                 **
418                 ** CREATE TRIGGER tt
419                 ** AFTER INSERT ON x
420                 ** REFERENCING NEW AS n
421                 ** FOR EACH ROW
422                 ** INSERT INTO y VALUES (n.i), (999), (333);
423                 **
424                 ** The above trigger action will result in InsertNode that
425                 ** contains a UnionNode of RowResultSetNodes. The UnionNode
426                 ** will have a copy of the CRs from its left child and those CRs
427                 ** will not have its beginOffset set which indicates they are
428                 ** not relevant for the conversion processing here, so we can
429                 ** safely skip them.
430                 */

431                 if (ref.getBeginOffset() == -1)
432                 {
433                     continue;
434                 }
435                 
436                 TableName tableName = ref.getTableNameNode();
437                 if ((tableName == null) ||
438                     ((oldTableName == null || !oldTableName.equals(tableName.getTableName())) &&
439                     (newTableName == null || !newTableName.equals(tableName.getTableName()))))
440                 {
441                     continue;
442                 }
443                     
444                 int tokBeginOffset = tableName.getBeginOffset();
445                 int tokEndOffset = tableName.getEndOffset();
446                 if (tokBeginOffset == -1)
447                 {
448                     continue;
449                 }
450
451                 regenNode = true;
452                 checkInvalidTriggerReference(tableName.getTableName());
453                 String JavaDoc colName = ref.getColumnName();
454                 int columnLength = ref.getEndOffset() - ref.getBeginOffset() + 1;
455
456                 newText.append(originalActionText.substring(start, tokBeginOffset-actionOffset));
457                 newText.append(genColumnReferenceSQL(dd, colName, tableName.getTableName(), tableName.getTableName().equals(oldTableName)));
458                 start = tokEndOffset- actionOffset + columnLength + 2;
459             }
460         }
461         else
462         {
463             /*
464             ** For a statement trigger, we find all FromBaseTable nodes. If
465             ** the from table is NEW or OLD (or user designated alternates
466             ** REFERENCING), we turn them into a trigger table VTI.
467             */

468             CollectNodesVisitor visitor = new CollectNodesVisitor(FromBaseTable.class);
469             actionNode.accept(visitor);
470             Vector JavaDoc refs = visitor.getList();
471             QueryTreeNode[] tabs = sortRefs(refs, false);
472             for (int i = 0; i < tabs.length; i++)
473             {
474                 FromBaseTable fromTable = (FromBaseTable) tabs[i];
475                 String JavaDoc refTableName = fromTable.getTableName().getTableName();
476                 String JavaDoc baseTableName = fromTable.getBaseTableName();
477                 if ((baseTableName == null) ||
478                     ((oldTableName == null || !oldTableName.equals(baseTableName)) &&
479                     (newTableName == null || !newTableName.equals(baseTableName))))
480                 {
481                     continue;
482                 }
483                 int tokBeginOffset = fromTable.getTableNameField().getBeginOffset();
484                 int tokEndOffset = fromTable.getTableNameField().getEndOffset();
485                 if (tokBeginOffset == -1)
486                 {
487                     continue;
488                 }
489
490                 checkInvalidTriggerReference(baseTableName);
491
492                 regenNode = true;
493                 newText.append(originalActionText.substring(start, tokBeginOffset-actionOffset));
494                 newText.append(baseTableName.equals(oldTableName) ?
495                                 "new org.apache.derby.catalog.TriggerOldTransitionRows() " :
496                                 "new org.apache.derby.catalog.TriggerNewTransitionRows() ");
497                 /*
498                 ** If the user supplied a correlation, then just
499                 ** pick it up automatically; otherwise, supply
500                 ** the default.
501                 */

502                 if (refTableName.equals(baseTableName))
503                 {
504                     newText.append(baseTableName).append(" ");
505                 }
506                 start=tokEndOffset-actionOffset+1;
507             }
508         }
509
510         /*
511         ** Parse the new action text with the substitutions.
512         ** Also, we reset the actionText to this new value. This
513         ** is what we are going to stick in the system tables.
514         */

515         if (regenNode)
516         {
517             if (start < originalActionText.length())
518             {
519                 newText.append(originalActionText.substring(start));
520             }
521             actionText = newText.toString();
522             actionNode = (StatementNode)reparseTriggerText();
523         }
524
525         return regenNode;
526     }
527
528     /*
529     ** Sort the refs into array.
530     */

531     private QueryTreeNode[] sortRefs(Vector JavaDoc refs, boolean isRow)
532     {
533         int size = refs.size();
534         QueryTreeNode[] sorted = new QueryTreeNode[size];
535         int i = 0;
536         for (Enumeration JavaDoc e = refs.elements(); e.hasMoreElements(); )
537         {
538             if (isRow)
539                 sorted[i++] = (ColumnReference)e.nextElement();
540             else
541                 sorted[i++] = (FromBaseTable)e.nextElement();
542         }
543
544         /* bubble sort
545          */

546         QueryTreeNode temp;
547         for (i = 0; i < size - 1; i++)
548         {
549             temp = null;
550             for (int j = 0; j < size - i - 1; j++)
551             {
552                 if ((isRow &&
553                      sorted[j].getBeginOffset() >
554                      sorted[j+1].getBeginOffset()
555                     ) ||
556                     (!isRow &&
557                      ((FromBaseTable) sorted[j]).getTableNameField().getBeginOffset() >
558                      ((FromBaseTable) sorted[j+1]).getTableNameField().getBeginOffset()
559                     ))
560                 {
561                     temp = sorted[j];
562                     sorted[j] = sorted[j+1];
563                     sorted[j+1] = temp;
564                 }
565             }
566             if (temp == null) // sorted
567
break;
568         }
569
570         return sorted;
571     }
572
573     /*
574     ** Parse the text and return the tree.
575     */

576     private QueryTreeNode reparseTriggerText() throws StandardException
577     {
578         /*
579         ** Get a new compiler context, so the parsing of the text
580         ** doesn't mess up anything in the current context
581         */

582         LanguageConnectionContext lcc = getLanguageConnectionContext();
583         CompilerContext newCC = lcc.pushCompilerContext();
584         newCC.setReliability(CompilerContext.INTERNAL_SQL_LEGAL);
585
586         try
587         {
588             return QueryTreeNode.parseQueryText(newCC, actionText, (Object JavaDoc[])null, lcc);
589         }
590
591         finally
592         {
593             lcc.popCompilerContext(newCC);
594         }
595     }
596     /*
597     ** Make sure the given column name is found in the trigger
598     ** target table. Generate the appropriate SQL to get it.
599     **
600     ** @return a string that is used to get the column using
601     ** getObject() on the desired result set and CAST it back
602     ** to the proper type in the SQL domain.
603     **
604     ** @exception StandardException on invalid column name
605     */

606     private String JavaDoc genColumnReferenceSQL
607     (
608         DataDictionary dd,
609         String JavaDoc colName,
610         String JavaDoc tabName,
611         boolean isOldTable
612     ) throws StandardException
613     {
614         ColumnDescriptor colDesc = null;
615         if ((colDesc = triggerTableDescriptor.getColumnDescriptor(colName)) == null)
616         {
617             throw StandardException.newException(SQLState.LANG_COLUMN_NOT_FOUND, tabName+"."+colName);
618         }
619
620         /*
621         ** Generate something like this:
622         **
623         ** cast (org.apache.derby.iapi.db.Factory::
624         ** getTriggerExecutionContext().getNewRow().
625         ** getObject(<colPosition>) AS DECIMAL(6,2))
626         **
627         ** Column position is used to avoid the wrong column being
628         ** selected problem (DERBY-1258) caused by the case insensitive
629         ** JDBC rules for fetching a column by name.
630         **
631         ** The cast back to the SQL Domain may seem redundant
632         ** but we need it to make the column reference appear
633         ** EXACTLY like a regular column reference, so we need
634         ** the object in the SQL Domain and we need to have the
635         ** type information. Thus a user should be able to do
636         ** something like
637         **
638         ** CREATE TRIGGER ... INSERT INTO T length(Column), ...
639         */

640         StringBuffer JavaDoc methodCall = new StringBuffer JavaDoc();
641         methodCall.append("CAST (org.apache.derby.iapi.db.Factory::getTriggerExecutionContext().");
642         methodCall.append(isOldTable ? "getOldRow()" : "getNewRow()");
643         methodCall.append(".getObject(");
644         methodCall.append(colDesc.getPosition());
645         methodCall.append(") AS ");
646         DataTypeDescriptor dts = colDesc.getType();
647         TypeId typeId = dts.getTypeId();
648
649         /*
650         ** getSQLString() returns <typeName>
651         ** for user types, so call getSQLTypeName in that
652         ** case.
653         */

654         methodCall.append(
655           (typeId.userType() ? typeId.getSQLTypeName() : dts.getSQLstring()));
656         
657         methodCall.append(") ");
658
659         return methodCall.toString();
660     }
661
662     /*
663     ** Check for illegal combinations here: insert & old or
664     ** delete and new
665     */

666     private void checkInvalidTriggerReference(String JavaDoc tableName) throws StandardException
667     {
668         if (tableName.equals(oldTableName) &&
669             (triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_INSERT) == TriggerDescriptor.TRIGGER_EVENT_INSERT)
670         {
671             throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "INSERT", "new");
672         }
673         else if (tableName.equals(newTableName) &&
674             (triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_DELETE) == TriggerDescriptor.TRIGGER_EVENT_DELETE)
675         {
676             throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "DELETE", "old");
677         }
678     }
679
680     /*
681     ** Make sure that the referencing clause is legitimate.
682     ** While we are at it we set the new/oldTableName to
683     ** be whatever the user wants.
684     */

685     private void validateReferencesClause(DataDictionary dd) throws StandardException
686     {
687         if ((refClause == null) || (refClause.size() == 0))
688         {
689             return;
690         }
691
692         for (Enumeration JavaDoc e = refClause.elements(); e.hasMoreElements(); )
693         {
694             TriggerReferencingStruct trn = (TriggerReferencingStruct)e.nextElement();
695
696             /*
697             ** 1) Make sure that we don't try to refer
698             ** to a table for a row trigger or a row for
699             ** a table trigger.
700             */

701             if (isRow && !trn.isRow)
702             {
703                 throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "ROW", "row");
704             }
705             else if (!isRow && trn.isRow)
706             {
707                 throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "STATEMENT", "table");
708             }
709
710             /*
711             ** 2) Make sure we have no dups
712             */

713             if (trn.isNew)
714             {
715
716                 if (newTableInReferencingClause)
717                 {
718                     throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_CLAUSE_DUPS);
719                 }
720
721                 /*
722                 ** 3a) No NEW reference in delete trigger
723                 */

724                 if ((triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_DELETE) == TriggerDescriptor.TRIGGER_EVENT_DELETE)
725                 {
726                     throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "DELETE", "old");
727                 }
728                 newTableName = trn.identifier;
729                 newTableInReferencingClause = true;
730             }
731             else
732             {
733                 if (oldTableInReferencingClause)
734                 {
735                     throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_CLAUSE_DUPS);
736                 }
737                 /*
738                 ** 3b) No OLD reference in insert trigger
739                 */

740                 if ((triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_INSERT) == TriggerDescriptor.TRIGGER_EVENT_INSERT)
741                 {
742                     throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "INSERT", "new");
743                 }
744                 oldTableName = trn.identifier;
745                 oldTableInReferencingClause = true;
746             }
747
748             /*
749             ** 4) Additional restriction on BEFORE triggers
750             */

751             if (this.isBefore && !trn.isRow) {
752             // OLD_TABLE and NEW_TABLE not allowed for BEFORE triggers.
753
throw StandardException.newException(SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "BEFORE", "row");
754             }
755
756         }
757
758     }
759
760
761     /**
762      * Create the Constant information that will drive the guts of Execution.
763      *
764      * @exception StandardException Thrown on failure
765      */

766     public ConstantAction makeConstantAction() throws StandardException
767     {
768         String JavaDoc oldReferencingName = (oldTableInReferencingClause) ? oldTableName : null;
769         String JavaDoc newReferencingName = (newTableInReferencingClause) ? newTableName : null;
770
771         return getGenericConstantActionFactory().getCreateTriggerConstantAction(
772                                             triggerSchemaDescriptor.getSchemaName(),
773                                             getRelativeName(),
774                                             triggerEventMask,
775                                             isBefore,
776                                             isRow,
777                                             isEnabled,
778                                             triggerTableDescriptor,
779                                             (UUID)null, // when SPSID
780
whenText,
781                                             (UUID)null, // action SPSid
782
actionText,
783                                             (actionCompSchemaId == null) ?
784                                                 compSchemaDescriptor.getUUID() :
785                                                 actionCompSchemaId,
786                                             (Timestamp JavaDoc)null, // creation time
787
referencedColInts,
788                                             originalActionText,
789                                             oldTableInReferencingClause,
790                                             newTableInReferencingClause,
791                                             oldReferencingName,
792                                             newReferencingName
793                                             );
794     }
795
796
797     /**
798      * Convert this object to a String. See comments in QueryTreeNode.java
799      * for how this should be done for tree printing.
800      *
801      * @return This object as a String
802      */

803     public String JavaDoc toString()
804     {
805         if (SanityManager.DEBUG)
806         {
807             String JavaDoc refString = "null";
808             if (refClause != null)
809             {
810                 StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
811                 for (Enumeration JavaDoc e = refClause.elements(); e.hasMoreElements(); )
812                 {
813                     buf.append("\t");
814                     TriggerReferencingStruct trn =
815                             (TriggerReferencingStruct)e.nextElement();
816                     buf.append(trn.toString());
817                     buf.append("\n");
818                 }
819                 refString = buf.toString();
820             }
821
822             return super.toString() +
823                 "tableName: "+tableName+
824                 "\ntriggerEventMask: "+triggerEventMask+
825                 "\nisBefore: "+isBefore+
826                 "\nisRow: "+isRow+
827                 "\nisEnabled: "+isEnabled+
828                 "\nwhenText: "+whenText+
829                 "\nrefClause: "+refString+
830                 "\nactionText: "+actionText+
831                 "\n";
832         }
833         else
834         {
835             return "";
836         }
837     }
838
839 }
840
Popular Tags