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