001: /*
002:
003: Derby - Class org.apache.derby.iapi.sql.dictionary.DDUtils
004:
005: Licensed to the Apache Software Foundation (ASF) under one or more
006: contributor license agreements. See the NOTICE file distributed with
007: this work for additional information regarding copyright ownership.
008: The ASF licenses this file to you under the Apache License, Version 2.0
009: (the "License"); you may not use this file except in compliance with
010: the License. You may obtain a copy of the License at
011:
012: http://www.apache.org/licenses/LICENSE-2.0
013:
014: Unless required by applicable law or agreed to in writing, software
015: distributed under the License is distributed on an "AS IS" BASIS,
016: WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: See the License for the specific language governing permissions and
018: limitations under the License.
019:
020: */
021:
022: package org.apache.derby.iapi.sql.dictionary;
023:
024: import org.apache.derby.iapi.error.StandardException;
025:
026: import org.apache.derby.iapi.reference.SQLState;
027: import org.apache.derby.iapi.sql.StatementType;
028: import java.util.Hashtable;
029: import org.apache.derby.iapi.services.sanity.SanityManager;
030: import org.apache.derby.iapi.services.i18n.MessageService;
031: import java.util.Enumeration;
032:
033: /**
034: * Static Data dictionary utilities.
035: *
036: * @version 0.1
037: * @author Rick Hillegas
038: */
039:
040: public class DDUtils {
041:
042: /*
043: ** For a foreign key, this is used to locate the referenced
044: ** key using the ConstraintInfo. If it doesn't find the
045: ** correct constraint it will throw an error.
046: */
047: public static ReferencedKeyConstraintDescriptor locateReferencedConstraint(
048: DataDictionary dd, TableDescriptor td,
049: String myConstraintName, // for error messages
050: String[] myColumnNames, ConsInfo otherConstraintInfo)
051: throws StandardException {
052: TableDescriptor refTd = otherConstraintInfo
053: .getReferencedTableDescriptor(dd);
054: if (refTd == null) {
055: throw StandardException.newException(
056: SQLState.LANG_INVALID_FK_NO_REF_TAB,
057: myConstraintName, otherConstraintInfo
058: .getReferencedTableName());
059: }
060:
061: ReferencedKeyConstraintDescriptor refCd = null;
062:
063: /*
064: ** There were no column names specified, just find
065: ** the primary key on the table in question
066: */
067: String[] refColumnNames = otherConstraintInfo
068: .getReferencedColumnNames();
069: if (refColumnNames == null || refColumnNames.length == 0) {
070: refCd = refTd.getPrimaryKey();
071: if (refCd == null) {
072: throw StandardException.newException(
073: SQLState.LANG_INVALID_FK_NO_PK,
074: myConstraintName, refTd.getQualifiedName());
075: }
076:
077: ColumnDescriptorList cdl = getColumnDescriptors(dd, td,
078: myColumnNames);
079:
080: /*
081: ** Check the column list length to give a more informative
082: ** error in case they aren't the same.
083: */
084: if (cdl.size() != refCd.getColumnDescriptors().size()) {
085: throw StandardException.newException(
086: SQLState.LANG_INVALID_FK_DIFFERENT_COL_COUNT,
087: myConstraintName, String.valueOf(cdl.size()),
088: String.valueOf(refCd.getColumnDescriptors()
089: .size()));
090: }
091:
092: /*
093: ** Make sure all types are the same.
094: */
095: if (!refCd.areColumnsComparable(cdl)) {
096: throw StandardException
097: .newException(
098: SQLState.LANG_INVALID_FK_COL_TYPES_DO_NOT_MATCH,
099: myConstraintName);
100: }
101:
102: return refCd;
103: }
104:
105: /*
106: ** Check the referenced columns vs. each unique or primary key to
107: ** see if they match the foreign key.
108: */
109: else {
110: ConstraintDescriptor cd;
111:
112: ColumnDescriptorList colDl = getColumnDescriptors(dd, td,
113: myColumnNames);
114: ConstraintDescriptorList refCDL = dd
115: .getConstraintDescriptors(refTd);
116:
117: int refCDLSize = refCDL.size();
118: for (int index = 0; index < refCDLSize; index++) {
119: cd = refCDL.elementAt(index);
120:
121: /*
122: ** Matches if it is not a check or fk, and
123: ** all the types line up.
124: */
125: if ((cd instanceof ReferencedKeyConstraintDescriptor)
126: && cd.areColumnsComparable(colDl)
127: && columnNamesMatch(refColumnNames, cd
128: .getColumnDescriptors())) {
129: return (ReferencedKeyConstraintDescriptor) cd;
130: }
131: }
132:
133: /*
134: ** If we got here, we didn't find anything
135: */
136: throw StandardException.newException(
137: SQLState.LANG_INVALID_FK_NO_REF_KEY,
138: myConstraintName, refTd.getQualifiedName());
139: }
140: }
141:
142: public static ColumnDescriptorList getColumnDescriptors(
143: DataDictionary dd, TableDescriptor td, String[] columnNames)
144: throws StandardException {
145: ColumnDescriptorList cdl = new ColumnDescriptorList();
146: for (int colCtr = 0; colCtr < columnNames.length; colCtr++) {
147: ColumnDescriptor cd = td
148: .getColumnDescriptor(columnNames[colCtr]);
149: cdl.add(td.getUUID(), cd);
150: }
151: return cdl;
152: }
153:
154: public static boolean columnNamesMatch(String[] columnNames,
155: ColumnDescriptorList cdl) throws StandardException {
156: if (columnNames.length != cdl.size()) {
157: return false;
158: }
159:
160: String name;
161: for (int index = 0; index < columnNames.length; index++) {
162: name = ((ColumnDescriptor) cdl.elementAt(index))
163: .getColumnName();
164: if (!name.equals(columnNames[index])) {
165: return false;
166: }
167: }
168:
169: return true;
170: }
171:
172: /*
173: **checks whether the foreign key relation ships referential action
174: **is violating the restrictions we have in the current system.
175: **/
176: public static void validateReferentialActions(DataDictionary dd,
177: TableDescriptor td, String myConstraintName, // for error messages
178: ConsInfo otherConstraintInfo, String[] columnNames)
179: throws StandardException {
180:
181: int refAction = otherConstraintInfo
182: .getReferentialActionDeleteRule();
183:
184: //Do not allow ON DELETE SET NULL as a referential action
185: //if none of the foreign key columns are nullable.
186: if (refAction == StatementType.RA_SETNULL) {
187: boolean foundNullableColumn = false;
188: //check if we have a nullable foreign key column
189: for (int colCtr = 0; colCtr < columnNames.length; colCtr++) {
190: ColumnDescriptor cd = td
191: .getColumnDescriptor(columnNames[colCtr]);
192: if ((cd.getType().isNullable())) {
193: foundNullableColumn = true;
194: break;
195: }
196: }
197:
198: if (!foundNullableColumn) {
199: throw StandardException.newException(
200: SQLState.LANG_INVALID_FK_COL_FOR_SETNULL,
201: myConstraintName);
202: }
203: }
204:
205: //check whether the foreign key relation ships referential action
206: //is not violating the restrictions we have in the current system.
207: TableDescriptor refTd = otherConstraintInfo
208: .getReferencedTableDescriptor(dd);
209: Hashtable deleteConnHashtable = new Hashtable();
210: //find whether the foreign key is self referencing.
211: boolean isSelfReferencingFk = (refTd.getUUID().equals(td
212: .getUUID()));
213: String refTableName = refTd.getSchemaName() + "."
214: + refTd.getName();
215: //look for the other foreign key constraints on this table first
216: int currentSelfRefValue = getCurrentDeleteConnections(dd, td,
217: -1, deleteConnHashtable, false, true);
218: validateDeleteConnection(dd, td, refTd, refAction,
219: deleteConnHashtable, (Hashtable) deleteConnHashtable
220: .clone(), true, myConstraintName, false,
221: new StringBuffer(0), refTableName, isSelfReferencingFk,
222: currentSelfRefValue);
223:
224: //if it not a selfreferencing key check for violation of exiting connections.
225: if (!isSelfReferencingFk) {
226: checkForAnyExistingDeleteConnectionViolations(dd, td,
227: refAction, deleteConnHashtable, myConstraintName);
228: }
229: }
230:
231: /*
232: ** Finds the existing delete connection for the table and the referential
233: ** actions that will occur and stores the information in the hash table.
234: ** HashTable (key , value) = ( table name that this table is delete
235: ** connected to, referential action that will occur if there is a delete on
236: ** the table this table connected to[CASACDE, SETNULL , RESTRICT ...etc).)
237: **/
238:
239: private static int getCurrentDeleteConnections(DataDictionary dd,
240: TableDescriptor td, int refActionType, Hashtable dch,
241: boolean prevNotCascade, boolean findSelfRef)
242: throws StandardException {
243:
244: int selfRefValue = -1; //store the self reference referential action
245:
246: //make sure we get any foreign key constraints added earlier in the same statement.
247: td.emptyConstraintDescriptorList();
248: ConstraintDescriptorList cdl = dd.getConstraintDescriptors(td);
249: int cdlSize = cdl.size();
250:
251: boolean passedInPrevNotCascade = prevNotCascade;
252: for (int index = 0; index < cdlSize; index++) {
253: ConstraintDescriptor cd = cdl.elementAt(index);
254:
255: //look for foreign keys
256: if ((cd instanceof ForeignKeyConstraintDescriptor)) {
257: ForeignKeyConstraintDescriptor fkcd = (ForeignKeyConstraintDescriptor) cd;
258: String constraintName = fkcd.getConstraintName();
259: int raDeleteRule = fkcd.getRaDeleteRule();
260: int raUpdateRule = fkcd.getRaUpdateRule();
261:
262: if (findSelfRef && fkcd.isSelfReferencingFK()) {
263: //All self references will have same referential actions type
264: selfRefValue = raDeleteRule;
265: findSelfRef = false;
266: }
267:
268: ReferencedKeyConstraintDescriptor refcd = fkcd
269: .getReferencedConstraint();
270: TableDescriptor refTd = refcd.getTableDescriptor();
271: int childRefAction = refActionType == -1 ? raDeleteRule
272: : refActionType;
273:
274: String refTableName = refTd.getSchemaName() + "."
275: + refTd.getName();
276: //check with the existing references.
277: Integer rAction = ((Integer) dch.get(refTableName));
278: if (rAction != null) // we already looked at this table
279: {
280: prevNotCascade = passedInPrevNotCascade;
281: continue;
282: }
283:
284: //if we are not cascading, check whether the link before
285: //this was cascade or not. If we travel through two NON CASCADE ACTION
286: //links then the delete connection is broken(only a delete can have further
287: // referential effects)
288: if (raDeleteRule != StatementType.RA_CASCADE) {
289: if (prevNotCascade) {
290: prevNotCascade = passedInPrevNotCascade;
291: continue;
292: } else
293: prevNotCascade = true;
294: }
295:
296: //store the delete connection info in the hash table,
297: //note that the referential action value is not what is
298: //not specified on the current link. It is actually the
299: //value of what happens to the table whose delete
300: // connections we are finding.
301: dch.put(refTableName, (new Integer(childRefAction)));
302:
303: //find the next delete conectiions on this path for non
304: //self referencig delete connections.
305: if (!fkcd.isSelfReferencingFK())
306: getCurrentDeleteConnections(dd, refTd,
307: childRefAction, dch, true, false);
308: prevNotCascade = passedInPrevNotCascade;
309: }
310: }
311:
312: return selfRefValue;
313: }
314:
315: /*
316: ** Following function validates whether the new foreign key relation ship
317: ** violates any restriction on the referential actions. Current refAction
318: ** implementation does not allow cases where we can possible land up
319: ** having multiple action for the same row in a table, this happens becase
320: ** user can possibly define differential action through multiple paths.
321: ** Following function throws error while creating foreign keys if the new
322: ** releations ship leads to any such conditions.
323: ** NOTE : SQL99 standard also does not cleary says what we are suppose to do
324: ** in these non determenistic cases.
325: ** Our implementation just follows what is did in DB2 and throws error
326: ** messaged similar to DB2 (sql0632N, sql0633N, sql0634N)
327: */
328:
329: private static void validateDeleteConnection(
330: DataDictionary dd,
331: TableDescriptor actualTd, // the table we are adding the foriegn key.
332: TableDescriptor refTd,
333: int refActionType,
334: Hashtable dch,
335: Hashtable ech, //existing delete connections
336: boolean checkImmediateRefTable, String myConstraintName,
337: boolean prevNotCascade, StringBuffer cycleString,
338: String currentRefTableName, //the name of the table we are referring too.
339: boolean isSelfReferencingFk, int currentSelfRefValue)
340: throws StandardException {
341:
342: Integer rAction;
343:
344: String refTableName = refTd.getSchemaName() + "."
345: + refTd.getName();
346:
347: /*
348: ** Validate the new referentail action value with respect to the
349: ** already existing connections to this table we gathered from
350: ** the getCurrentDeleteConnections() call.
351: */
352:
353: if (checkImmediateRefTable) {
354: rAction = ((Integer) dch.get(refTableName));
355:
356: // check possible invalide cases incase of self referencing foreign key
357: if (isSelfReferencingFk) {
358: //All the relation ship referring to a table should have the
359: //same refaction except incase of SET NULL. In this case
360: //it is the same table , so we have to check with existing self
361: //referencing actions.
362: if (currentSelfRefValue != -1) {
363: if (currentSelfRefValue != refActionType) {
364: //If there is a SET NULL relation ship we can not have any
365: // other relation ship with it.
366: if (currentSelfRefValue == StatementType.RA_SETNULL)
367: throw generateError(
368: SQLState.LANG_CANT_BE_DEPENDENT_ESELF,
369: myConstraintName,
370: currentRefTableName);
371: else {
372: /*
373: ** case where we can cleary say what the
374: ** referential actions should be. Like,
375: ** if there is NO ACTION relationsip
376: **already, new relation ship also shold be NO ACTION.
377: */
378: throw generateError(
379: SQLState.LANG_DELETE_RULE_MUSTBE_ESELF,
380: myConstraintName,
381: currentSelfRefValue);
382: }
383: } else {
384: //more than one ON DELET SET NULL to the same table is not allowed
385: if (currentSelfRefValue == StatementType.RA_SETNULL
386: && refActionType == StatementType.RA_SETNULL) {
387: throw generateError(
388: SQLState.LANG_CANT_BE_DEPENDENT_ESELF,
389: myConstraintName,
390: currentRefTableName);
391: }
392: }
393: }
394:
395: /*
396: ** If the new releation ship is self referencing and if
397: ** the current existing relation ship to other tables is
398: ** CASCADE type them new self reference should be of type
399: ** CASCADE, otherwise we should throw error.
400: */
401:
402: if (isSelfReferencingFk
403: && dch.contains(new Integer(
404: StatementType.RA_CASCADE))
405: && refActionType != StatementType.RA_CASCADE) {
406: throw generateError(
407: SQLState.LANG_DELETE_RULE_MUSTBE_ECASCADE,
408: myConstraintName, StatementType.RA_CASCADE);
409: }
410:
411: //end of possible error case scenarios for self reference key additions
412: return;
413: }
414:
415: //cases where the new reference is referring to another table
416:
417: //check whether it matched with existing self references.
418: // If A self-referencing constraint exists with a delete rule of
419: // SET NULL, NO ACTION or RESTRICT. We can not add CASCADE
420: // relationship with another table.
421:
422: if (currentSelfRefValue != -1) {
423: if (refActionType == StatementType.RA_CASCADE
424: && currentSelfRefValue != StatementType.RA_CASCADE) {
425: throw generateError(
426: SQLState.LANG_DELETE_RULE_CANT_BE_CASCADE_ESELF,
427: myConstraintName);
428:
429: }
430:
431: }
432:
433: //check for the cases with existing relationships to the
434: //referenced table
435: if (rAction != null) {
436: checkForMultiplePathInvalidCases(rAction.intValue(),
437: refActionType, myConstraintName,
438: currentRefTableName);
439: }
440:
441: //mark the current connect to the reference table to identify the cycle.
442: if (refActionType != StatementType.RA_CASCADE) {
443: prevNotCascade = true;
444: }
445:
446: /*
447: ** cycle string is used to keep track of the referential actions of
448: ** the nodes we visited, this is required to make sure that in case
449: ** of cycles , all the nodes in the cycle have same type of
450: ** referential action.
451: **/
452: cycleString = cycleString.append(refActionType);
453: }
454:
455: boolean passedInPrevNotCascade = prevNotCascade;
456:
457: //delete connection is broken for if we see ON DELET SET NULL link
458: // one level deeper than the table we are adding the foreing key
459: //Where as to check for cycles we need to go for more level also;
460: // To check cases like CASCADE CASCADE SET NULL cycle is not valid.
461: //Following variable is used make the distinction.
462: boolean multiPathCheck = true;
463:
464: // check for cases where the new connection we are forming to the
465: // reference table could create invalid any cycles or mutiple paths
466: // with the delete-connections the referencing table might have already.
467: ConstraintDescriptorList refCDL = dd
468: .getConstraintDescriptors(refTd);
469: int refCDLSize = refCDL.size();
470: for (int index = 0; index < refCDLSize; index++) {
471: ConstraintDescriptor cd = refCDL.elementAt(index);
472:
473: if ((cd instanceof ForeignKeyConstraintDescriptor)) {
474: ForeignKeyConstraintDescriptor fkcd = (ForeignKeyConstraintDescriptor) cd;
475: String constraintName = fkcd.getConstraintName();
476: int raDeleteRule = fkcd.getRaDeleteRule();
477: int raUpdateRule = fkcd.getRaUpdateRule();
478:
479: ReferencedKeyConstraintDescriptor refcd = fkcd
480: .getReferencedConstraint();
481: TableDescriptor nextRefTd = refcd.getTableDescriptor();
482:
483: //if we are not cascading, check whether the link before
484: //this was cascade or not. If we travel through two NON CASCADE ACTION
485: //links then the delete connection is broken(only a delete can have further
486: //referential effects)
487: if (raDeleteRule != StatementType.RA_CASCADE) {
488: if (prevNotCascade) {
489: prevNotCascade = passedInPrevNotCascade;
490: continue;
491: } else {
492: prevNotCascade = true;
493: multiPathCheck = false;
494: }
495:
496: }
497:
498: //check whether the current link is a self referencing one
499: boolean isSelfRefLink = fkcd.isSelfReferencingFK();
500:
501: //check for this is non self referencing cycles case
502: //In cases of cycle, whole cycle should have the same refAction
503: // value. Other wise we should throw an exception
504: cycleString = cycleString.append(raDeleteRule);
505: boolean isFormingCycle = (nextRefTd.getUUID()
506: .equals(actualTd.getUUID()));
507: if (isFormingCycle) {
508: //make sure that all the nodes in the cycle have the same
509: //referential action value, otherwise we should throw an error.
510: for (int i = 0; i < cycleString.length(); i++) {
511: int otherRefAction = Character
512: .getNumericValue(cycleString.charAt(i));
513: if (otherRefAction != refActionType) {
514: //cases where one of the existing relation ships in
515: //the cycle is not cascade , so we can not have
516: // cascade relation ship.
517: if (otherRefAction != StatementType.RA_CASCADE) {
518: throw generateError(
519: SQLState.LANG_DELETE_RULE_CANT_BE_CASCADE_ECYCLE,
520: myConstraintName);
521: } else {
522: //possibly all the other nodes in the cycle has
523: //cascade relationsship , we can not add a non
524: //cascade relation ship.
525: throw generateError(
526: SQLState.LANG_CANT_BE_DEPENDENT_ECYCLE,
527: myConstraintName,
528: currentRefTableName);
529: }
530: }
531: }
532: }
533:
534: String nextRefTableName = nextRefTd.getSchemaName()
535: + "." + nextRefTd.getName();
536: rAction = ((Integer) ech.get(nextRefTableName));
537: if (rAction != null) {
538: /*
539: ** If the table name has entry in the hash table means, there
540: ** is already a path to this table exists from the table
541: ** the new foreign key relation ship is being formed.
542: ** Note: refValue in the hash table is how the table we are
543: ** adding the new relationsship is going to affected not
544: ** current path refvalue.
545: **/
546: if (!isSelfRefLink && multiPathCheck)
547: checkForMultiplePathInvalidCases(rAction
548: .intValue(), refActionType,
549: myConstraintName, currentRefTableName);
550:
551: } else {
552: rAction = ((Integer) dch.get(nextRefTableName));
553: if (rAction == null) {
554: if (multiPathCheck)
555: dch.put(nextRefTableName, (new Integer(
556: refActionType)));
557: if (!isSelfRefLink) {
558: validateDeleteConnection(dd, actualTd,
559: nextRefTd, refActionType, dch, ech,
560: false, myConstraintName,
561: prevNotCascade, cycleString,
562: currentRefTableName,
563: isSelfReferencingFk,
564: currentSelfRefValue);
565: }
566: }
567: }
568: prevNotCascade = passedInPrevNotCascade;
569: //removes the char added for the current call
570: cycleString.setLength(cycleString.length() - 1);
571:
572: }
573: }
574: }
575:
576: /*
577: **Check whether the mulitple path case is valid or not following
578: ** cases are invalid:
579: ** case 1: The relationship causes the table to be delete-connected to
580: ** the indicated table through multiple relationships and the
581: ** delete rule of the existing relationship is SET NULL.
582: ** case 2: The relationship would cause the table to be
583: ** delete-connected to the same table through multiple
584: ** relationships and such relationships must have the same
585: ** delete rule (NO ACTION, RESTRICT or CASCADE).
586: ** case 3: The relationship would cause another table to be
587: ** delete-connected to the same table through multiple paths
588: ** with different delete rules or with delete rule equal to SET NULL.
589: **/
590:
591: private static void checkForMultiplePathInvalidCases(
592: int currentRefAction, int refActionType,
593: String myConstraintName, String currentRefTableName)
594: throws StandardException {
595:
596: //All the relation ship referring to a table should have the
597: //same refaction except incase of SET NULL
598: if (currentRefAction != refActionType) {
599:
600: //If there is a SET NULL relation ship we can not have any
601: // other relation ship with it.
602: if (currentRefAction == StatementType.RA_SETNULL)
603: throw generateError(
604: SQLState.LANG_CANT_BE_DEPENDENT_MPATH,
605: myConstraintName, currentRefTableName);
606: else
607: //This error say what the delete rule must be for the
608: // foreign key be valid
609: throw generateError(
610: SQLState.LANG_DELETE_RULE_MUSTBE_MPATH,
611: myConstraintName, currentRefAction);
612:
613: } else {
614: //more than one ON DELET SET NULL to the same table is not allowed
615: if (currentRefAction == StatementType.RA_SETNULL
616: && refActionType == StatementType.RA_SETNULL) {
617: throw generateError(
618: SQLState.LANG_CANT_BE_DEPENDENT_MPATH,
619: myConstraintName, currentRefTableName);
620: }
621: }
622: }
623:
624: /*
625: ** Check whether the delete rule of FOREIGN KEY must not be CASCADE because
626: ** the new relationship would cause another table to be delete-connected to
627: ** the same table through multiple paths with different delete rules or with
628: ** delete rule equal to SET NULL.
629: **
630: ** For example :
631: ** t1
632: ** CASCADE / \ CASCADE
633: ** / \
634: ** t2 t3
635: ** \ /
636: ** SET NULL \ / CASCADE (Can we add this one ? NO)
637: ** \ /
638: \t4/
639: **
640: ** existing links:
641: ** t2 references t1 ON DELETE CASCADE (fkey1)
642: ** t3 references t1 ON DELETE CASCADE (fkey2)
643: ** t2 reference t4 ON DELETE SET NULL (fkey3)
644: ** Now if if try to add a new link i.e
645: ** t4 references t3 ON DELETE SET NULL (fkey4)
646: ** Say if we add it, then if we execute 'delete from t1'
647: ** Because of referential actions , we will try to delete a row through
648: ** one path and tries to update through another path.
649: ** Nothing in standard that say whether we are suppose to delete the row
650: ** or update the row. DB2UDB raises error when we try to create the
651: ** foreign key fkey4, cloudscape also does the same.
652: **
653: ** How we catch the error case ?
654: ** Point to note here is the table(t4) we are adding the foreign key does
655: ** not have a problem in this scenarion because we are adding a
656: ** a CASACDE link , some other table(t2) that is referring
657: ** can get multiple referential action paths. We can not
658: ** this error case for self referencing links.
659: ** Algorithm:
660: ** -Gather the foreign keys that are
661: ** referring(ReferencedKeyConstraintDescriptor) to the table we are adding
662: ** foreign key, in our example case we get (fkey3 - table t2 -t4 link)
663: ** for each ReferencedKeyConstraintDescriptor
664: ** {
665: ** 1)find the delete connections of the referring table.
666: ** [getCurrentDeleteConnections() will return this hash table]
667: ** 2) we already have collected the Delete connections
668: ** in validDeleteConnections() for the actual table we are adding the
669: ** foreign key.
670: ** 3) Now check whether the referring table is also
671: ** referring any table that the table we are adding
672: ** foreign key has delete connection.
673: **
674: ** for each table referring table delete connection hash table
675: ** {
676: ** if it is there in the actual table delete connection hash table
677: ** {
678: ** //In our example case we find t1 in both the hash tables.
679: ** make sure we are having valid referential action
680: ** from the existing path and the new path we got from
681: ** new foreign key relation ship.
682: ** //In our example case t2 has CASCADE relations with t1
683: ** //Because of new foreign key added we also get
684: ** //SET NULL relation ship with t1. This is not valid
685: ** //so we should throw error.
686: ** }
687: ** }
688: ** }
689: **/
690:
691: private static void checkForAnyExistingDeleteConnectionViolations(
692: DataDictionary dd, TableDescriptor td, int refActionType,
693: Hashtable newDconnHashTable, String myConstraintName)
694: throws StandardException {
695:
696: //We need to check for the condition in this function only when we are
697: //adding ref action of type CASCADE
698: if (refActionType != StatementType.RA_CASCADE)
699: return;
700:
701: //find the tables that are referring to the table we
702: //are adding the foreign key and check whether we violate their existing rules.
703: String addTableName = td.getSchemaName() + "." + td.getName();
704: ;
705: ConstraintDescriptorList refCDL = dd
706: .getConstraintDescriptors(td);
707: int refCDLSize = refCDL.size();
708: for (int index = 0; index < refCDLSize; index++) {
709: ConstraintDescriptor cd = refCDL.elementAt(index);
710:
711: if ((cd instanceof ReferencedKeyConstraintDescriptor)) {
712: ConstraintDescriptorList fkcdl = dd
713: .getActiveConstraintDescriptors(((ReferencedKeyConstraintDescriptor) cd)
714: .getForeignKeyConstraints(ConstraintDescriptor.ALL));
715:
716: int size = fkcdl.size();
717: if (size == 0) {
718: continue;
719: }
720:
721: //Note: More than one table can refer to the same
722: //ReferencedKeyConstraintDescriptor, so we need to find all the tables.
723: Hashtable dConnHashtable = new Hashtable();
724: for (int inner = 0; inner < size; inner++) {
725: ForeignKeyConstraintDescriptor fkcd = (ForeignKeyConstraintDescriptor) fkcdl
726: .elementAt(inner);
727: TableDescriptor fktd = fkcd.getTableDescriptor();
728: //Delete rule that we have to the table we are adding the
729: // foreign key relation shop
730: int raDeleteRuleToAddTable = fkcd.getRaDeleteRule();
731:
732: //This check should not be done on self referencing references.
733: if (!fkcd.isSelfReferencingFK()) {
734:
735: //gather the delete connections of the table that is
736: //referring to the table we are adding foreign key relation ship
737:
738: getCurrentDeleteConnections(dd, fktd, -1,
739: dConnHashtable, false, true);
740:
741: /*
742: **Find out if we introduced more than one delete connection
743: **paths to the table that are referring the table we adding
744: **the foreign key relatiosn ship.
745: **If we have multiple paths they should have the same type
746: **referential action and only one SET NULL path.
747: **/
748:
749: for (Enumeration e = dConnHashtable.keys(); e
750: .hasMoreElements();) {
751: String tName = (String) e.nextElement();
752: //we should not check for the table name to which we are
753: //adding the foreign key relation ship.
754: if (!tName.equals(addTableName)) {
755: if (newDconnHashTable
756: .containsKey(tName)) {
757: int currentDeleteRule = ((Integer) dConnHashtable
758: .get(tName)).intValue();
759: if ((currentDeleteRule == StatementType.RA_SETNULL && raDeleteRuleToAddTable == StatementType.RA_SETNULL)
760: || currentDeleteRule != raDeleteRuleToAddTable) {
761: throw generateError(
762: SQLState.LANG_DELETE_RULE_CANT_BE_CASCADE_MPATH,
763: myConstraintName);
764: }
765: }
766: }
767: }
768: }
769: //same hash table can be used for the other referring tables
770: //so clear the hash table.
771: dConnHashtable.clear();
772: }
773: }
774: }
775: }
776:
777: private static StandardException generateError(String messageId,
778: String myConstraintName) {
779: String message = MessageService.getTextMessage(messageId);
780: return StandardException.newException(
781: SQLState.LANG_DELETE_RULE_VIOLATION, myConstraintName,
782: message);
783: }
784:
785: private static StandardException generateError(String messageId,
786: String myConstraintName, int raRule) {
787: String raRuleStringId;
788: switch (raRule) {
789: case StatementType.RA_CASCADE:
790: raRuleStringId = SQLState.LANG_DELETE_RULE_CASCADE;
791: break;
792: case StatementType.RA_RESTRICT:
793: raRuleStringId = SQLState.LANG_DELETE_RULE_RESTRICT;
794: break;
795: case StatementType.RA_NOACTION:
796: raRuleStringId = SQLState.LANG_DELETE_RULE_NOACTION;
797: break;
798: case StatementType.RA_SETNULL:
799: raRuleStringId = SQLState.LANG_DELETE_RULE_SETNULL;
800: break;
801: case StatementType.RA_SETDEFAULT:
802: raRuleStringId = SQLState.LANG_DELETE_RULE_SETDEFAULT;
803: break;
804: default:
805: raRuleStringId = SQLState.LANG_DELETE_RULE_NOACTION; // NO ACTION (default value)
806: }
807:
808: String raRuleMessageString = MessageService
809: .getTextMessage(raRuleStringId);
810: String message = MessageService.getTextMessage(messageId,
811: raRuleMessageString);
812: return StandardException.newException(
813: SQLState.LANG_DELETE_RULE_VIOLATION, myConstraintName,
814: message);
815: }
816:
817: private static StandardException generateError(String messageId,
818: String myConstraintName, String refTableName) {
819:
820: String message = MessageService.getTextMessage(messageId,
821: refTableName);
822: return StandardException.newException(
823: SQLState.LANG_DELETE_RULE_VIOLATION, myConstraintName,
824: message);
825: }
826:
827: }
|