001: /*
002:
003: Derby - Class org.apache.derby.impl.sql.compile.CreateTriggerNode
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.impl.sql.compile;
023:
024: import org.apache.derby.iapi.services.context.ContextManager;
025: import org.apache.derby.iapi.services.monitor.Monitor;
026: import org.apache.derby.iapi.services.sanity.SanityManager;
027:
028: import org.apache.derby.iapi.error.StandardException;
029:
030: import org.apache.derby.iapi.sql.compile.CompilerContext;
031: import org.apache.derby.iapi.sql.compile.Parser;
032:
033: import org.apache.derby.iapi.sql.dictionary.ColumnDescriptor;
034: import org.apache.derby.iapi.sql.dictionary.DataDescriptorGenerator;
035: import org.apache.derby.iapi.sql.dictionary.DataDictionaryContext;
036: import org.apache.derby.iapi.sql.dictionary.DataDictionary;
037: import org.apache.derby.iapi.sql.dictionary.SchemaDescriptor;
038: import org.apache.derby.iapi.sql.dictionary.SPSDescriptor;
039: import org.apache.derby.iapi.sql.dictionary.TableDescriptor;
040: import org.apache.derby.iapi.sql.dictionary.TriggerDescriptor;
041:
042: import org.apache.derby.iapi.sql.conn.Authorizer;
043: import org.apache.derby.iapi.sql.conn.LanguageConnectionContext;
044:
045: import org.apache.derby.iapi.sql.depend.Dependent;
046:
047: import org.apache.derby.iapi.reference.SQLState;
048:
049: import org.apache.derby.iapi.sql.execute.ConstantAction;
050:
051: import org.apache.derby.iapi.sql.ResultSet;
052:
053: import org.apache.derby.iapi.types.DataTypeDescriptor;
054: import org.apache.derby.iapi.types.TypeId;
055:
056: import org.apache.derby.catalog.UUID;
057: import java.sql.Timestamp;
058: import java.sql.Types;
059: import java.util.Enumeration;
060: import java.util.Hashtable;
061: import java.util.Vector;
062:
063: /**
064: * A CreateTriggerNode is the root of a QueryTree
065: * that represents a CREATE TRIGGER
066: * statement.
067: *
068: * @author jamie
069: */
070:
071: public class CreateTriggerNode extends DDLStatementNode {
072: private TableName triggerName;
073: private TableName tableName;
074: private int triggerEventMask;
075: private ResultColumnList triggerCols;
076: private boolean isBefore;
077: private boolean isRow;
078: private boolean isEnabled;
079: private Vector refClause;
080: private QueryTreeNode whenClause;
081: private String whenText;
082: private int whenOffset;
083: private StatementNode actionNode;
084: private String actionText;
085: private String originalActionText; // text w/o trim of spaces
086: private int actionOffset;
087:
088: private SchemaDescriptor triggerSchemaDescriptor;
089: private SchemaDescriptor compSchemaDescriptor;
090: private int[] referencedColInts;
091: private TableDescriptor triggerTableDescriptor;
092: private UUID actionCompSchemaId;
093:
094: /*
095: ** Names of old and new table. By default we have
096: ** OLD/old and NEW/new. The casing is dependent on
097: ** the language connection context casing as the rest
098: ** of other code. Therefore we will set the value of the
099: ** String at the init() time.
100: ** However, if there is a referencing clause
101: ** we will reset these values to be whatever the user
102: ** wants.
103: */
104: private String oldTableName;
105: private String newTableName;
106:
107: private boolean oldTableInReferencingClause;
108: private boolean newTableInReferencingClause;
109:
110: /**
111: * Initializer for a CreateTriggerNode
112: *
113: * @param triggerName name of the trigger
114: * @param tableName name of the table which the trigger is declared upon
115: * @param triggerEventMask TriggerDescriptor.TRIGGER_EVENT_XXX
116: * @param triggerCols columns trigger is to fire upon. Valid
117: * for UPDATE case only.
118: * @param isBefore is before trigger (false for after)
119: * @param isRow true for row trigger, false for statement
120: * @param isEnabled true if enabled
121: * @param refClause the referencing clause
122: * @param whenClause the WHEN clause tree
123: * @param whenText the text of the WHEN clause
124: * @param whenOffset offset of start of WHEN clause
125: * @param actionNode the trigger action tree
126: * @param actionText the text of the trigger action
127: * @param actionOffset offset of start of action clause
128: *
129: * @exception StandardException Thrown on error
130: */
131: public void init(Object triggerName, Object tableName,
132: Object triggerEventMask, Object triggerCols,
133: Object isBefore, Object isRow, Object isEnabled,
134: Object refClause, Object whenClause, Object whenText,
135: Object whenOffset, Object actionNode, Object actionText,
136: Object actionOffset) throws StandardException {
137: initAndCheck(triggerName);
138: this .triggerName = (TableName) triggerName;
139: this .tableName = (TableName) tableName;
140: this .triggerEventMask = ((Integer) triggerEventMask).intValue();
141: this .triggerCols = (ResultColumnList) triggerCols;
142: this .isBefore = ((Boolean) isBefore).booleanValue();
143: this .isRow = ((Boolean) isRow).booleanValue();
144: this .isEnabled = ((Boolean) isEnabled).booleanValue();
145: this .refClause = (Vector) refClause;
146: this .whenClause = (QueryTreeNode) whenClause;
147: this .whenText = (whenText == null) ? null : ((String) whenText)
148: .trim();
149: this .whenOffset = ((Integer) whenOffset).intValue();
150: this .actionNode = (StatementNode) actionNode;
151: this .originalActionText = (String) actionText;
152: this .actionText = (actionText == null) ? null
153: : ((String) actionText).trim();
154: this .actionOffset = ((Integer) actionOffset).intValue();
155:
156: implicitCreateSchema = true;
157: }
158:
159: public String statementToString() {
160: return "CREATE TRIGGER";
161: }
162:
163: /**
164: * Prints the sub-nodes of this object. See QueryTreeNode.java for
165: * how tree printing is supposed to work.
166: *
167: * @param depth The depth of this node in the tree
168: */
169:
170: public void printSubNodes(int depth) {
171: if (SanityManager.DEBUG) {
172: super .printSubNodes(depth);
173:
174: if (triggerCols != null) {
175: printLabel(depth, "triggerColumns: ");
176: triggerCols.treePrint(depth + 1);
177: }
178: if (whenClause != null) {
179: printLabel(depth, "whenClause: ");
180: whenClause.treePrint(depth + 1);
181: }
182: if (actionNode != null) {
183: printLabel(depth, "actionNode: ");
184: actionNode.treePrint(depth + 1);
185: }
186: }
187: }
188:
189: // accessors
190:
191: // We inherit the generate() method from DDLStatementNode.
192:
193: /**
194: * Bind this CreateTriggerNode. This means doing any static error
195: * checking that can be done before actually creating the table.
196: *
197: * @return The bound query tree
198: *
199: * @exception StandardException Thrown on error
200: */
201: public QueryTreeNode bind() throws StandardException {
202: CompilerContext compilerContext = getCompilerContext();
203: DataDictionary dd = getDataDictionary();
204: /*
205: ** Grab the current schema. We will use that for
206: ** sps compilation
207: */
208: LanguageConnectionContext lcc = getLanguageConnectionContext();
209: compSchemaDescriptor = lcc.getDefaultSchema();
210:
211: /*
212: ** Get and check the schema descriptor for this
213: ** trigger. This check will throw the proper exception
214: ** if someone tries to create a trigger in the SYS
215: ** schema.
216: */
217: triggerSchemaDescriptor = getSchemaDescriptor();
218:
219: /*
220: ** Get the trigger table.
221: */
222: triggerTableDescriptor = getTableDescriptor(tableName);
223:
224: //throw an exception if user is attempting to create a trigger on a temporary table
225: if (isSessionSchema(triggerTableDescriptor
226: .getSchemaDescriptor())) {
227: throw StandardException
228: .newException(SQLState.LANG_OPERATION_NOT_ALLOWED_ON_SESSION_SCHEMA_TABLES);
229: }
230: if (isPrivilegeCollectionRequired()) {
231: compilerContext
232: .pushCurrentPrivType(Authorizer.TRIGGER_PRIV);
233: compilerContext
234: .addRequiredTablePriv(triggerTableDescriptor);
235: compilerContext.popCurrentPrivType();
236: }
237:
238: /*
239: ** Regenerates the actionText and actionNode if necessary.
240: */
241: boolean needInternalSQL = bindReferencesClause(dd);
242:
243: lcc.pushTriggerTable(triggerTableDescriptor);
244: try {
245: /*
246: ** Bind the trigger action and the trigger
247: ** when clause to make sure that they are
248: ** ok. Note that we have already substituted
249: ** in various replacements for OLD/NEW transition
250: ** tables/variables and reparsed if necessary.
251: */
252: if (needInternalSQL)
253: compilerContext
254: .setReliability(CompilerContext.INTERNAL_SQL_LEGAL);
255:
256: // For before triggers, the action statement cannot contain calls
257: // to procedures that modify SQL data. If the action statement
258: // contains a procedure call, this reliability will be used during
259: // bind of the call statement node.
260: if (isBefore)
261: compilerContext
262: .setReliability(CompilerContext.MODIFIES_SQL_DATA_PROCEDURE_ILLEGAL);
263:
264: actionNode.bind();
265:
266: if (whenClause != null) {
267: whenClause.bind();
268: }
269: } finally {
270: lcc.popTriggerTable(triggerTableDescriptor);
271: }
272:
273: /*
274: ** Statement is dependent on the TableDescriptor
275: */
276: compilerContext.createDependency(triggerTableDescriptor);
277:
278: /*
279: ** If there is a list of columns, then no duplicate columns,
280: ** and all columns must be found.
281: */
282: if (triggerCols != null && triggerCols.size() != 0) {
283: referencedColInts = new int[triggerCols.size()];
284: Hashtable columnNames = new Hashtable();
285: int tcSize = triggerCols.size();
286: for (int i = 0; i < tcSize; i++) {
287: ResultColumn rc = (ResultColumn) triggerCols
288: .elementAt(i);
289: if (columnNames.put(rc.getName(), rc) != null) {
290: throw StandardException
291: .newException(
292: SQLState.LANG_DUPLICATE_COLUMN_IN_TRIGGER_UPDATE,
293: rc.getName(), triggerName);
294: }
295:
296: ColumnDescriptor cd = triggerTableDescriptor
297: .getColumnDescriptor(rc.getName());
298: if (cd == null) {
299: throw StandardException.newException(
300: SQLState.LANG_COLUMN_NOT_FOUND_IN_TABLE, rc
301: .getName(), tableName);
302: }
303:
304: referencedColInts[i] = cd.getPosition();
305: }
306:
307: // sort the list
308: java.util.Arrays.sort(referencedColInts);
309: }
310:
311: //If attempting to reference a SESSION schema table (temporary or permanent) in the trigger action, throw an exception
312: if (actionNode.referencesSessionSchema())
313: throw StandardException
314: .newException(SQLState.LANG_OPERATION_NOT_ALLOWED_ON_SESSION_SCHEMA_TABLES);
315:
316: return this ;
317: }
318:
319: /**
320: * Return true if the node references SESSION schema tables (temporary or permanent)
321: *
322: * @return true if references SESSION schema tables, else false
323: *
324: * @exception StandardException Thrown on error
325: */
326: public boolean referencesSessionSchema() throws StandardException {
327: //If create trigger is part of create statement and the trigger is defined on or it references SESSION schema tables,
328: //it will get caught in the bind phase of trigger and exception will be thrown by the trigger bind.
329: return (isSessionSchema(triggerTableDescriptor.getSchemaName()) || actionNode
330: .referencesSessionSchema());
331: }
332:
333: /*
334: ** BIND OLD/NEW TRANSITION TABLES/VARIABLES
335: **
336: ** 1) validate the referencing clause (if any)
337: **
338: ** 2) convert trigger action text. e.g.
339: ** DELETE FROM t WHERE c = old.c
340: ** turns into
341: ** DELETE FROM t WHERE c = org.apache.derby.iapi.db.Factory::
342: ** getTriggerExecutionContext().getOldRow().getInt('C');
343: ** or
344: ** DELETE FROM t WHERE c in (SELECT c FROM OLD)
345: ** turns into
346: ** DELETE FROM t WHERE c in (SELECT c FROM new TriggerOldTransitionTable OLD)
347: **
348: ** 3) check all column references against new/old transition
349: ** variables (since they are no longer 'normal' column references
350: ** that will be checked during bind)
351: **
352: ** 4) reparse the new action text
353: **
354: ** You might be wondering why we regenerate the text and reparse
355: ** instead of just reworking the tree to have the nodes we want.
356: ** Well, the primary reason is that if we screwed with the tree,
357: ** then we would have a major headache if this trigger action
358: ** was ever recompiled -- spses don't really know that they are
359: ** triggers so it would be quite arduous to figure out that an
360: ** sps is a trigger and munge up its query tree after figuring
361: ** out what its OLD/NEW tables are, etc. Also, it is just plain
362: ** easier to just generate the sql and rebind.
363: */
364: private boolean bindReferencesClause(DataDictionary dd)
365: throws StandardException {
366: validateReferencesClause(dd);
367:
368: StringBuffer newText = new StringBuffer();
369: boolean regenNode = false;
370: int start = 0;
371: if (isRow) {
372: /*
373: ** For a row trigger, we find all column references. If
374: ** they are referencing NEW or OLD we turn them into
375: ** getTriggerExecutionContext().getOldRow().getInt('C');
376: */
377: CollectNodesVisitor visitor = new CollectNodesVisitor(
378: ColumnReference.class);
379: actionNode.accept(visitor);
380: Vector refs = visitor.getList();
381: /* we need to sort on position in string, beetle 4324
382: */
383: QueryTreeNode[] cols = sortRefs(refs, true);
384:
385: for (int i = 0; i < cols.length; i++) {
386: ColumnReference ref = (ColumnReference) cols[i];
387:
388: /*
389: ** Only occurrences of those OLD/NEW transition tables/variables
390: ** are of interest here. There may be intermediate nodes in the
391: ** parse tree that have its own RCL which contains copy of
392: ** column references(CR) from other nodes. e.g.:
393: **
394: ** CREATE TRIGGER tt
395: ** AFTER INSERT ON x
396: ** REFERENCING NEW AS n
397: ** FOR EACH ROW
398: ** INSERT INTO y VALUES (n.i), (999), (333);
399: **
400: ** The above trigger action will result in InsertNode that
401: ** contains a UnionNode of RowResultSetNodes. The UnionNode
402: ** will have a copy of the CRs from its left child and those CRs
403: ** will not have its beginOffset set which indicates they are
404: ** not relevant for the conversion processing here, so we can
405: ** safely skip them.
406: */
407: if (ref.getBeginOffset() == -1) {
408: continue;
409: }
410:
411: TableName tableName = ref.getTableNameNode();
412: if ((tableName == null)
413: || ((oldTableName == null || !oldTableName
414: .equals(tableName.getTableName())) && (newTableName == null || !newTableName
415: .equals(tableName.getTableName())))) {
416: continue;
417: }
418:
419: int tokBeginOffset = tableName.getBeginOffset();
420: int tokEndOffset = tableName.getEndOffset();
421: if (tokBeginOffset == -1) {
422: continue;
423: }
424:
425: regenNode = true;
426: checkInvalidTriggerReference(tableName.getTableName());
427: String colName = ref.getColumnName();
428: int columnLength = ref.getEndOffset()
429: - ref.getBeginOffset() + 1;
430:
431: newText.append(originalActionText.substring(start,
432: tokBeginOffset - actionOffset));
433: newText.append(genColumnReferenceSQL(dd, colName,
434: tableName.getTableName(), tableName
435: .getTableName().equals(oldTableName)));
436: start = tokEndOffset - actionOffset + columnLength + 2;
437: }
438: } else {
439: /*
440: ** For a statement trigger, we find all FromBaseTable nodes. If
441: ** the from table is NEW or OLD (or user designated alternates
442: ** REFERENCING), we turn them into a trigger table VTI.
443: */
444: CollectNodesVisitor visitor = new CollectNodesVisitor(
445: FromBaseTable.class);
446: actionNode.accept(visitor);
447: Vector refs = visitor.getList();
448: QueryTreeNode[] tabs = sortRefs(refs, false);
449: for (int i = 0; i < tabs.length; i++) {
450: FromBaseTable fromTable = (FromBaseTable) tabs[i];
451: String refTableName = fromTable.getTableName()
452: .getTableName();
453: String baseTableName = fromTable.getBaseTableName();
454: if ((baseTableName == null)
455: || ((oldTableName == null || !oldTableName
456: .equals(baseTableName)) && (newTableName == null || !newTableName
457: .equals(baseTableName)))) {
458: continue;
459: }
460: int tokBeginOffset = fromTable.getTableNameField()
461: .getBeginOffset();
462: int tokEndOffset = fromTable.getTableNameField()
463: .getEndOffset();
464: if (tokBeginOffset == -1) {
465: continue;
466: }
467:
468: checkInvalidTriggerReference(baseTableName);
469:
470: regenNode = true;
471: newText.append(originalActionText.substring(start,
472: tokBeginOffset - actionOffset));
473: newText
474: .append(baseTableName.equals(oldTableName) ? "new org.apache.derby.catalog.TriggerOldTransitionRows() "
475: : "new org.apache.derby.catalog.TriggerNewTransitionRows() ");
476: /*
477: ** If the user supplied a correlation, then just
478: ** pick it up automatically; otherwise, supply
479: ** the default.
480: */
481: if (refTableName.equals(baseTableName)) {
482: newText.append(baseTableName).append(" ");
483: }
484: start = tokEndOffset - actionOffset + 1;
485: }
486: }
487:
488: /*
489: ** Parse the new action text with the substitutions.
490: ** Also, we reset the actionText to this new value. This
491: ** is what we are going to stick in the system tables.
492: */
493: if (regenNode) {
494: if (start < originalActionText.length()) {
495: newText.append(originalActionText.substring(start));
496: }
497: actionText = newText.toString();
498: actionNode = (StatementNode) reparseTriggerText();
499: }
500:
501: return regenNode;
502: }
503:
504: /*
505: ** Sort the refs into array.
506: */
507: private QueryTreeNode[] sortRefs(Vector refs, boolean isRow) {
508: int size = refs.size();
509: QueryTreeNode[] sorted = new QueryTreeNode[size];
510: int i = 0;
511: for (Enumeration e = refs.elements(); e.hasMoreElements();) {
512: if (isRow)
513: sorted[i++] = (ColumnReference) e.nextElement();
514: else
515: sorted[i++] = (FromBaseTable) e.nextElement();
516: }
517:
518: /* bubble sort
519: */
520: QueryTreeNode temp;
521: for (i = 0; i < size - 1; i++) {
522: temp = null;
523: for (int j = 0; j < size - i - 1; j++) {
524: if ((isRow && sorted[j].getBeginOffset() > sorted[j + 1]
525: .getBeginOffset())
526: || (!isRow && ((FromBaseTable) sorted[j])
527: .getTableNameField().getBeginOffset() > ((FromBaseTable) sorted[j + 1])
528: .getTableNameField().getBeginOffset())) {
529: temp = sorted[j];
530: sorted[j] = sorted[j + 1];
531: sorted[j + 1] = temp;
532: }
533: }
534: if (temp == null) // sorted
535: break;
536: }
537:
538: return sorted;
539: }
540:
541: /*
542: ** Parse the text and return the tree.
543: */
544: private QueryTreeNode reparseTriggerText() throws StandardException {
545: /*
546: ** Get a new compiler context, so the parsing of the text
547: ** doesn't mess up anything in the current context
548: */
549: LanguageConnectionContext lcc = getLanguageConnectionContext();
550: CompilerContext newCC = lcc.pushCompilerContext();
551: newCC.setReliability(CompilerContext.INTERNAL_SQL_LEGAL);
552:
553: try {
554: return QueryTreeNode.parseQueryText(newCC, actionText,
555: (Object[]) null, lcc);
556: }
557:
558: finally {
559: lcc.popCompilerContext(newCC);
560: }
561: }
562:
563: /*
564: ** Make sure the given column name is found in the trigger
565: ** target table. Generate the appropriate SQL to get it.
566: **
567: ** @return a string that is used to get the column using
568: ** getObject() on the desired result set and CAST it back
569: ** to the proper type in the SQL domain.
570: **
571: ** @exception StandardException on invalid column name
572: */
573: private String genColumnReferenceSQL(DataDictionary dd,
574: String colName, String tabName, boolean isOldTable)
575: throws StandardException {
576: ColumnDescriptor colDesc = null;
577: if ((colDesc = triggerTableDescriptor
578: .getColumnDescriptor(colName)) == null) {
579: throw StandardException.newException(
580: SQLState.LANG_COLUMN_NOT_FOUND, tabName + "."
581: + colName);
582: }
583:
584: /*
585: ** Generate something like this:
586: **
587: ** cast (org.apache.derby.iapi.db.Factory::
588: ** getTriggerExecutionContext().getNewRow().
589: ** getObject(<colPosition>) AS DECIMAL(6,2))
590: **
591: ** Column position is used to avoid the wrong column being
592: ** selected problem (DERBY-1258) caused by the case insensitive
593: ** JDBC rules for fetching a column by name.
594: **
595: ** The cast back to the SQL Domain may seem redundant
596: ** but we need it to make the column reference appear
597: ** EXACTLY like a regular column reference, so we need
598: ** the object in the SQL Domain and we need to have the
599: ** type information. Thus a user should be able to do
600: ** something like
601: **
602: ** CREATE TRIGGER ... INSERT INTO T length(Column), ...
603: */
604: StringBuffer methodCall = new StringBuffer();
605: methodCall
606: .append("CAST (org.apache.derby.iapi.db.Factory::getTriggerExecutionContext().");
607: methodCall.append(isOldTable ? "getOldRow()" : "getNewRow()");
608: methodCall.append(".getObject(");
609: methodCall.append(colDesc.getPosition());
610: methodCall.append(") AS ");
611: DataTypeDescriptor dts = colDesc.getType();
612: TypeId typeId = dts.getTypeId();
613:
614: /*
615: ** getSQLString() returns <typeName>
616: ** for user types, so call getSQLTypeName in that
617: ** case.
618: */
619: methodCall.append((typeId.userType() ? typeId.getSQLTypeName()
620: : dts.getSQLstring()));
621:
622: methodCall.append(") ");
623:
624: return methodCall.toString();
625: }
626:
627: /*
628: ** Check for illegal combinations here: insert & old or
629: ** delete and new
630: */
631: private void checkInvalidTriggerReference(String tableName)
632: throws StandardException {
633: if (tableName.equals(oldTableName)
634: && (triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_INSERT) == TriggerDescriptor.TRIGGER_EVENT_INSERT) {
635: throw StandardException.newException(
636: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "INSERT",
637: "new");
638: } else if (tableName.equals(newTableName)
639: && (triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_DELETE) == TriggerDescriptor.TRIGGER_EVENT_DELETE) {
640: throw StandardException.newException(
641: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "DELETE",
642: "old");
643: }
644: }
645:
646: /*
647: ** Make sure that the referencing clause is legitimate.
648: ** While we are at it we set the new/oldTableName to
649: ** be whatever the user wants.
650: */
651: private void validateReferencesClause(DataDictionary dd)
652: throws StandardException {
653: if ((refClause == null) || (refClause.size() == 0)) {
654: return;
655: }
656:
657: for (Enumeration e = refClause.elements(); e.hasMoreElements();) {
658: TriggerReferencingStruct trn = (TriggerReferencingStruct) e
659: .nextElement();
660:
661: /*
662: ** 1) Make sure that we don't try to refer
663: ** to a table for a row trigger or a row for
664: ** a table trigger.
665: */
666: if (isRow && !trn.isRow) {
667: throw StandardException.newException(
668: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH, "ROW",
669: "row");
670: } else if (!isRow && trn.isRow) {
671: throw StandardException.newException(
672: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH,
673: "STATEMENT", "table");
674: }
675:
676: /*
677: ** 2) Make sure we have no dups
678: */
679: if (trn.isNew) {
680:
681: if (newTableInReferencingClause) {
682: throw StandardException
683: .newException(SQLState.LANG_TRIGGER_BAD_REF_CLAUSE_DUPS);
684: }
685:
686: /*
687: ** 3a) No NEW reference in delete trigger
688: */
689: if ((triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_DELETE) == TriggerDescriptor.TRIGGER_EVENT_DELETE) {
690: throw StandardException.newException(
691: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH,
692: "DELETE", "old");
693: }
694: newTableName = trn.identifier;
695: newTableInReferencingClause = true;
696: } else {
697: if (oldTableInReferencingClause) {
698: throw StandardException
699: .newException(SQLState.LANG_TRIGGER_BAD_REF_CLAUSE_DUPS);
700: }
701: /*
702: ** 3b) No OLD reference in insert trigger
703: */
704: if ((triggerEventMask & TriggerDescriptor.TRIGGER_EVENT_INSERT) == TriggerDescriptor.TRIGGER_EVENT_INSERT) {
705: throw StandardException.newException(
706: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH,
707: "INSERT", "new");
708: }
709: oldTableName = trn.identifier;
710: oldTableInReferencingClause = true;
711: }
712:
713: /*
714: ** 4) Additional restriction on BEFORE triggers
715: */
716: if (this .isBefore && !trn.isRow) {
717: // OLD_TABLE and NEW_TABLE not allowed for BEFORE triggers.
718: throw StandardException.newException(
719: SQLState.LANG_TRIGGER_BAD_REF_MISMATCH,
720: "BEFORE", "row");
721: }
722:
723: }
724:
725: }
726:
727: /**
728: * Create the Constant information that will drive the guts of Execution.
729: *
730: * @exception StandardException Thrown on failure
731: */
732: public ConstantAction makeConstantAction() throws StandardException {
733: String oldReferencingName = (oldTableInReferencingClause) ? oldTableName
734: : null;
735: String newReferencingName = (newTableInReferencingClause) ? newTableName
736: : null;
737:
738: return getGenericConstantActionFactory()
739: .getCreateTriggerConstantAction(
740: triggerSchemaDescriptor.getSchemaName(),
741: getRelativeName(),
742: triggerEventMask,
743: isBefore,
744: isRow,
745: isEnabled,
746: triggerTableDescriptor,
747: (UUID) null, // when SPSID
748: whenText,
749: (UUID) null, // action SPSid
750: actionText,
751: (actionCompSchemaId == null) ? compSchemaDescriptor
752: .getUUID()
753: : actionCompSchemaId,
754: (Timestamp) null, // creation time
755: referencedColInts, originalActionText,
756: oldTableInReferencingClause,
757: newTableInReferencingClause,
758: oldReferencingName, newReferencingName);
759: }
760:
761: /**
762: * Convert this object to a String. See comments in QueryTreeNode.java
763: * for how this should be done for tree printing.
764: *
765: * @return This object as a String
766: */
767: public String toString() {
768: if (SanityManager.DEBUG) {
769: String refString = "null";
770: if (refClause != null) {
771: StringBuffer buf = new StringBuffer();
772: for (Enumeration e = refClause.elements(); e
773: .hasMoreElements();) {
774: buf.append("\t");
775: TriggerReferencingStruct trn = (TriggerReferencingStruct) e
776: .nextElement();
777: buf.append(trn.toString());
778: buf.append("\n");
779: }
780: refString = buf.toString();
781: }
782:
783: return super .toString() + "tableName: " + tableName
784: + "\ntriggerEventMask: " + triggerEventMask
785: + "\nisBefore: " + isBefore + "\nisRow: " + isRow
786: + "\nisEnabled: " + isEnabled + "\nwhenText: "
787: + whenText + "\nrefClause: " + refString
788: + "\nactionText: " + actionText + "\n";
789: } else {
790: return "";
791: }
792: }
793:
794: }
|