View Javadoc
1   /*
2    * This file is a part of the SchemaSpy project (http://schemaspy.sourceforge.net).
3    * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2009, 2010 John Currier
4    *
5    * SchemaSpy is free software; you can redistribute it and/or
6    * modify it under the terms of the GNU Lesser General Public
7    * License as published by the Free Software Foundation; either
8    * version 2.1 of the License, or (at your option) any later version.
9    *
10   * SchemaSpy is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13   * Lesser General Public License for more details.
14   *
15   * You should have received a copy of the GNU Lesser General Public
16   * License along with this library; if not, write to the Free Software
17   * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
18   */
19  package net.sourceforge.schemaspy.model;
20  
21  import static java.sql.DatabaseMetaData.importedKeyCascade;
22  import static java.sql.DatabaseMetaData.importedKeyNoAction;
23  import static java.sql.DatabaseMetaData.importedKeyRestrict;
24  import static java.sql.DatabaseMetaData.importedKeySetNull;
25  
26  import java.util.ArrayList;
27  import java.util.Collections;
28  import java.util.List;
29  import java.util.logging.Level;
30  import java.util.logging.Logger;
31  
32  /**
33   * Represents a <a href='http://en.wikipedia.org/wiki/Foreign_key'>
34   * Foreign Key Constraint</a> that "ties" a child table to a parent table
35   * via foreign and primary keys.
36   */
37  public class ForeignKeyConstraint implements Comparable<ForeignKeyConstraint> {
38      private final String name;
39      private Table parentTable;
40      private final List<TableColumn> parentColumns = new ArrayList<TableColumn>();
41      private final Table childTable;
42      private final List<TableColumn> childColumns = new ArrayList<TableColumn>();
43      private final int deleteRule;
44      private final int updateRule;
45      private final static Logger logger = Logger.getLogger(ForeignKeyConstraint.class.getName());
46      private final static boolean finerEnabled = logger.isLoggable(Level.FINER);
47  
48      /**
49       * Construct a foreign key for the specified child table.
50       * Relationship details will be added later.
51       *
52       * @param child
53       * @param name
54       * @param deleteRule
55       */
56      ForeignKeyConstraint(Table child, String name, int updateRule, int deleteRule) {
57          this.name = name; // implied constraints will have a null name and override getName()
58          if (finerEnabled)
59              logger.finer("Adding foreign key constraint '" + getName() + "' to " + child);
60          childTable = child;
61          this.deleteRule = deleteRule;
62          this.updateRule = updateRule;
63      }
64  
65      /**
66       * This constructor is intended for use <b>after</b> all of the tables have been
67       * found in the system.  One impact of using this constructor is that it will
68       * "glue" the two tables together through their columns.
69       *
70       * @param parentColumn
71       * @param childColumn
72       */
73      public ForeignKeyConstraint(TableColumn parentColumn, TableColumn childColumn,
74                                  int updateRule, int deleteRule) {
75          this(childColumn.getTable(), null, updateRule, deleteRule);
76  
77          addChildColumn(childColumn);
78          addParentColumn(parentColumn);
79  
80          childColumn.addParent(parentColumn, this);
81          parentColumn.addChild(childColumn, this);
82      }
83  
84      /**
85       * Same as {@link #ForeignKeyConstraint(TableColumn, TableColumn, int, int)},
86       * but defaults updateRule and deleteRule to
87       * {@link java.sql.DatabaseMetaData#importedKeyNoAction}.
88       *
89       * @param parentColumn
90       * @param childColumn
91       */
92      public ForeignKeyConstraint(TableColumn parentColumn, TableColumn childColumn) {
93          this(parentColumn, childColumn, importedKeyNoAction, importedKeyNoAction);
94      }
95  
96      /**
97       * Add a "parent" side to the constraint.
98       *
99       * @param column
100      */
101     void addParentColumn(TableColumn column) {
102         if (column != null) {
103             parentColumns.add(column);
104             parentTable = column.getTable();
105         }
106     }
107 
108     /**
109      * Add a "child" side to the constraint.
110      *
111      * @param column
112      */
113     void addChildColumn(TableColumn column) {
114         if (column != null) {
115             childColumns.add(column);
116         }
117     }
118 
119     /**
120      * Returns the name of the constraint
121      *
122      * @return
123      */
124     public String getName() {
125         return name;
126     }
127 
128     /**
129      * Returns the parent table (the table that contains the referenced primary key
130      * column).
131      *
132      * @return
133      */
134     public Table getParentTable() {
135         return parentTable;
136     }
137 
138     /**
139      * Returns all of the primary key columns that are referenced by this constraint.
140      *
141      * @return
142      */
143     public List<TableColumn> getParentColumns() {
144         return Collections.unmodifiableList(parentColumns);
145     }
146 
147     /**
148      * Returns the table on the "child" end of the relationship (contains the foreign
149      * key that references the parent table's primary key).
150      *
151      * @return
152      */
153     public Table getChildTable() {
154         return childTable;
155     }
156 
157     /**
158      * Returns all of the foreign key columns that are referenced by this constraint.
159      *
160      * @return
161      */
162     public List<TableColumn> getChildColumns() {
163         return Collections.unmodifiableList(childColumns);
164     }
165 
166     /**
167      * Returns the delete rule for this constraint.
168      *
169      * @see {@link java.sql.DatabaseMetaData#importedKeyCascade}
170      */
171     public int getDeleteRule() {
172         return deleteRule;
173     }
174 
175     /**
176      * Returns <code>true</code> if this constraint should
177      * <a href='http://en.wikipedia.org/wiki/Cascade_delete'>cascade deletions</code>.
178      *
179      * @return
180      */
181     public boolean isCascadeOnDelete() {
182         return getDeleteRule() == importedKeyCascade;
183     }
184 
185     /**
186      * Returns <code>true</code> if the constraint prevents the parent table
187      * from being deleted if child tables exist.
188      *
189      * @return
190      */
191     public boolean isRestrictDelete() {
192         return getDeleteRule() == importedKeyNoAction || getDeleteRule() == importedKeyRestrict;
193     }
194 
195     /**
196      * Returns <code>true</code> if the constraint indicates that the foreign key
197      * will be set to <code>null</code> when the parent key is deleted.
198      *
199      * @return
200      */
201     public boolean isNullOnDelete() {
202         return getDeleteRule() == importedKeySetNull;
203     }
204 
205     public String getDeleteRuleName() {
206         switch (getDeleteRule()) {
207             case importedKeyCascade:
208                 return "Cascade on delete";
209 
210             case importedKeyRestrict:
211             case importedKeyNoAction:
212                 return "Restrict delete";
213 
214             case importedKeySetNull:
215                 return "Null on delete";
216 
217             default:
218                 return "";
219         }
220     }
221 
222     public String getDeleteRuleDescription() {
223         switch (getDeleteRule()) {
224             case importedKeyCascade:
225                 return "Cascade on delete:\n Deletion of parent deletes child";
226 
227             case importedKeyRestrict:
228             case importedKeyNoAction:
229                 return "Restrict delete:\n Parent cannot be deleted if children exist";
230 
231             case importedKeySetNull:
232                 return "Null on delete:\n Foreign key to parent set to NULL when parent deleted";
233 
234             default:
235                 return "";
236         }
237     }
238 
239     public String getDeleteRuleAlias() {
240         switch (getDeleteRule()) {
241             case importedKeyCascade:
242                 return "C";
243 
244             case importedKeyRestrict:
245             case importedKeyNoAction:
246                 return "R";
247 
248             case importedKeySetNull:
249                 return "N";
250 
251             default:
252                 return "";
253         }
254     }
255 
256     /**
257      * Returns the update rule for this constraint.
258      *
259      * @see {@link java.sql.DatabaseMetaData#importedKeyCascade}
260      */
261     public int getUpdateRule() {
262         return updateRule;
263     }
264 
265     /**
266      * Returns <code>true</code> if this is an implied constraint or
267      * <code>false</code> if it is "real".
268      *
269      * Subclasses that implement implied constraints should override this method.
270      *
271      * @return
272      */
273     public boolean isImplied() {
274         return false;
275     }
276 
277     /**
278      * We have several types of constraints.
279      * This returns <code>true</code> if the constraint came from the database
280      * metadata and not inferred by something else.
281      * This is different than {@link #isImplied()} in that implied relationships
282      * are a specific type of non-real relationships.
283      *
284      * @return
285      */
286     public boolean isReal() {
287         return getClass() == ForeignKeyConstraint.class;
288     }
289 
290     /**
291      * Custom comparison method to deal with foreign key names that aren't
292      * unique across all schemas being evaluated
293      *
294      * @param other ForeignKeyConstraint
295      *
296      * @return
297      */
298     public int compareTo(ForeignKeyConstraint other) {
299         if (other == this)
300             return 0;
301 
302         int rc = getName().compareToIgnoreCase(other.getName());
303         if (rc == 0) {
304             // should only get here if we're dealing with cross-schema references (rare)
305             String ours = getChildColumns().get(0).getTable().getSchema();
306             String theirs = other.getChildColumns().get(0).getTable().getSchema();
307             if (ours != null && theirs != null)
308                 rc = ours.compareToIgnoreCase(theirs);
309             else if (ours == null)
310                 rc = -1;
311             else
312                 rc = 1;
313         }
314 
315         return rc;
316     }
317 
318     /**
319      * Static method that returns a string representation of the specified
320      * list of {@link TableColumn columns}.
321      *
322      * @param columns
323      * @return
324      */
325     public static String toString(List<TableColumn> columns) {
326         if (columns.size() == 1)
327             return columns.iterator().next().toString();
328         return columns.toString();
329     }
330 
331     /**
332      * Returns a string representation of this foreign key constraint.
333      *
334      * @return
335      */
336     @Override
337     public String toString() {
338         StringBuilder buf = new StringBuilder();
339         buf.append(childTable.getName());
340         buf.append('.');
341         buf.append(toString(childColumns));
342         buf.append(" refs ");
343         buf.append(parentTable.getName());
344         buf.append('.');
345         buf.append(toString(parentColumns));
346         if (parentTable.isRemote()) {
347             buf.append(" in ");
348             buf.append(parentTable.getSchema());
349         }
350         if (name != null) {
351             buf.append(" via ");
352             buf.append(name);
353         }
354 
355         return buf.toString();
356     }
357 }