1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.schemaspy;
20
21 import java.sql.DatabaseMetaData;
22 import java.sql.ResultSet;
23 import java.sql.ResultSetMetaData;
24 import java.sql.SQLException;
25 import java.util.ArrayList;
26 import java.util.Collection;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.HashMap;
30 import java.util.Iterator;
31 import java.util.List;
32 import java.util.Map;
33 import java.util.Set;
34 import java.util.TreeMap;
35 import java.util.TreeSet;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38 import java.util.regex.Pattern;
39 import net.sourceforge.schemaspy.model.ForeignKeyConstraint;
40 import net.sourceforge.schemaspy.model.ImpliedForeignKeyConstraint;
41 import net.sourceforge.schemaspy.model.RailsForeignKeyConstraint;
42 import net.sourceforge.schemaspy.model.Table;
43 import net.sourceforge.schemaspy.model.TableColumn;
44 import net.sourceforge.schemaspy.model.TableIndex;
45 import net.sourceforge.schemaspy.util.Inflection;
46
47 public class DbAnalyzer {
48 public static List<ImpliedForeignKeyConstraint> getImpliedConstraints(Collection<Table> tables) {
49 List<TableColumn> columnsWithoutParents = new ArrayList<TableColumn>();
50 Map<TableColumn, Table> allPrimaries = new TreeMap<TableColumn, Table>(new Comparator<TableColumn>() {
51 public int compare(TableColumn column1, TableColumn column2) {
52 int rc = column1.getName().compareToIgnoreCase(column2.getName());
53 if (rc == 0)
54 rc = column1.getType().compareToIgnoreCase(column2.getType());
55 if (rc == 0)
56 rc = column1.getLength() - column2.getLength();
57 return rc;
58 }
59 });
60
61 int duplicatePrimaries = 0;
62
63
64 for (Table table : tables) {
65 List<TableColumn> tablePrimaries = table.getPrimaryColumns();
66 if (tablePrimaries.size() == 1) {
67 for (TableColumn primary : tablePrimaries) {
68 if (primary.allowsImpliedChildren() &&
69 allPrimaries.put(primary, table) != null)
70 ++duplicatePrimaries;
71 }
72 }
73
74 for (TableColumn column : table.getColumns()) {
75 if (!column.isForeignKey() && column.allowsImpliedParents())
76 columnsWithoutParents.add(column);
77 }
78 }
79
80
81
82
83 if (duplicatePrimaries > allPrimaries.size())
84 return new ArrayList<ImpliedForeignKeyConstraint>();
85
86 sortColumnsByTable(columnsWithoutParents);
87
88 List<ImpliedForeignKeyConstraint> impliedConstraints = new ArrayList<ImpliedForeignKeyConstraint>();
89 for (TableColumn childColumn : columnsWithoutParents) {
90 Table primaryTable = allPrimaries.get(childColumn);
91 if (primaryTable != null && primaryTable != childColumn.getTable()) {
92 TableColumn parentColumn = primaryTable.getColumn(childColumn.getName());
93
94
95 if (parentColumn.getParentConstraint(childColumn) == null) {
96
97
98 impliedConstraints.add(new ImpliedForeignKeyConstraint(parentColumn, childColumn));
99 }
100 }
101 }
102
103 return impliedConstraints;
104 }
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122 public static List<RailsForeignKeyConstraint> getRailsConstraints(Map<String, Table> tables) {
123 List<RailsForeignKeyConstraint> railsConstraints = new ArrayList<RailsForeignKeyConstraint>(tables.size());
124
125
126
127 for (Table table : tables.values()) {
128 for (TableColumn column : table.getColumns()) {
129 String columnName = column.getName().toLowerCase();
130 if (!column.isForeignKey() && column.allowsImpliedParents() && columnName.endsWith("_id")) {
131 String singular = columnName.substring(0, columnName.length() - 3);
132 String primaryTableName = Inflection.pluralize(singular);
133 Table primaryTable = tables.get(primaryTableName);
134 if (primaryTable != null) {
135 TableColumn primaryColumn = primaryTable.getColumn("ID");
136 if (primaryColumn != null) {
137 railsConstraints.add(new RailsForeignKeyConstraint(primaryColumn, column));
138 }
139 }
140 }
141 }
142 }
143
144 return railsConstraints;
145 }
146
147
148
149
150
151
152
153
154 public static List<ForeignKeyConstraint> getForeignKeyConstraints(Collection<Table> tables) {
155 List<ForeignKeyConstraint> constraints = new ArrayList<ForeignKeyConstraint>();
156
157 for (Table table : tables) {
158 constraints.addAll(table.getForeignKeys());
159 }
160
161 return constraints;
162 }
163
164 public static List<Table> getOrphans(Collection<Table> tables) {
165 List<Table> orphans = new ArrayList<Table>();
166
167 for (Table table : tables) {
168 if (table.isOrphan(false)) {
169 orphans.add(table);
170 }
171 }
172
173 return sortTablesByName(orphans);
174 }
175
176
177
178
179
180 public static List<TableColumn> getMustBeUniqueNullableColumns(Collection<Table> tables) {
181 List<TableColumn> uniqueNullables = new ArrayList<TableColumn>();
182
183 for (Table table : tables) {
184 for (TableIndex index : table.getIndexes()) {
185 if (index.isUniqueNullable()) {
186 uniqueNullables.addAll(index.getColumns());
187 }
188 }
189 }
190
191 return sortColumnsByTable(uniqueNullables);
192 }
193
194
195
196
197 public static List<Table> getTablesWithoutIndexes(Collection<Table> tables) {
198 List<Table> withoutIndexes = new ArrayList<Table>();
199
200 for (Table table : tables) {
201 if (!table.isView() && table.getIndexes().size() == 0)
202 withoutIndexes.add(table);
203 }
204
205 return sortTablesByName(withoutIndexes);
206 }
207
208 public static List<Table> getTablesWithIncrementingColumnNames(Collection<Table> tables) {
209 List<Table> denormalizedTables = new ArrayList<Table>();
210
211 for (Table table : tables) {
212 Map<String, Long> columnPrefixes = new HashMap<String, Long>();
213
214 for (TableColumn column : table.getColumns()) {
215
216
217
218 String columnName = column.getName();
219 String numbers = null;
220 for (int i = columnName.length() - 1; i > 0; --i) {
221 if (Character.isDigit(columnName.charAt(i))) {
222 numbers = String.valueOf(columnName.charAt(i)) + (numbers == null ? "" : numbers);
223 } else {
224 break;
225 }
226 }
227
228
229
230 if (numbers == null) {
231 numbers = "1";
232 columnName = columnName + numbers;
233 }
234
235
236
237 String prefix = columnName.substring(0, columnName.length() - numbers.length());
238 long numeric = Long.parseLong(numbers);
239 Long existing = columnPrefixes.get(prefix);
240 if (existing != null && Math.abs(existing.longValue() - numeric) == 1) {
241
242 denormalizedTables.add(table);
243 break;
244 }
245 columnPrefixes.put(prefix, new Long(numeric));
246 }
247 }
248
249 return sortTablesByName(denormalizedTables);
250 }
251
252 public static List<Table> getTablesWithOneColumn(Collection<Table> tables) {
253 List<Table> singleColumnTables = new ArrayList<Table>();
254
255 for (Table table : tables) {
256 if (table.getColumns().size() == 1)
257 singleColumnTables.add(table);
258 }
259
260 return sortTablesByName(singleColumnTables);
261 }
262
263 public static List<Table> sortTablesByName(List<Table> tables) {
264 Collections.sort(tables, new Comparator<Table>() {
265 public int compare(Table table1, Table table2) {
266 return table1.compareTo(table2);
267 }
268 });
269
270 return tables;
271 }
272
273 public static List<TableColumn> sortColumnsByTable(List<TableColumn> columns) {
274 Collections.sort(columns, new Comparator<TableColumn>() {
275 public int compare(TableColumn column1, TableColumn column2) {
276 int rc = column1.getTable().compareTo(column2.getTable());
277 if (rc == 0)
278 rc = column1.getName().compareToIgnoreCase(column2.getName());
279 return rc;
280 }
281 });
282
283 return columns;
284 }
285
286
287
288
289
290
291
292
293 public static List<TableColumn> getDefaultNullStringColumns(Collection<Table> tables) {
294 List<TableColumn> defaultNullStringColumns = new ArrayList<TableColumn>();
295
296 for (Table table : tables) {
297 for (TableColumn column : table.getColumns()) {
298 Object defaultValue = column.getDefaultValue();
299 if (defaultValue != null && defaultValue instanceof String) {
300 String defaultString = defaultValue.toString();
301 if (defaultString.trim().equalsIgnoreCase("null")) {
302 defaultNullStringColumns.add(column);
303 }
304 }
305 }
306 }
307
308 return sortColumnsByTable(defaultNullStringColumns);
309 }
310
311
312
313
314
315
316 public static List<String> getSchemas(DatabaseMetaData meta) throws SQLException {
317 List<String> schemas = new ArrayList<String>();
318
319 ResultSet rs = meta.getSchemas();
320 while (rs.next()) {
321 schemas.add(rs.getString("TABLE_SCHEM"));
322 }
323 rs.close();
324
325 return schemas;
326 }
327
328
329
330
331
332
333 public static List<String> getPopulatedSchemas(DatabaseMetaData meta) throws SQLException {
334 return getPopulatedSchemas(meta, ".*");
335 }
336
337
338
339
340
341
342
343 public static List<String> getPopulatedSchemas(DatabaseMetaData meta, String schemaSpec) throws SQLException {
344 Set<String> schemas = new TreeSet<String>();
345 Pattern schemaRegex = Pattern.compile(schemaSpec);
346 Logger logger = Logger.getLogger(DbAnalyzer.class.getName());
347 boolean logging = logger.isLoggable(Level.FINE);
348
349 Iterator<String> iter = getSchemas(meta).iterator();
350 while (iter.hasNext()) {
351 String schema = iter.next().toString();
352 if (schemaRegex.matcher(schema).matches()) {
353 ResultSet rs = null;
354 try {
355 rs = meta.getTables(null, schema, "%", null);
356 if (rs.next()) {
357 if (logging)
358 logger.fine("Including schema " + schema +
359 ": matches + \"" + schemaRegex + "\" and contains tables");
360 schemas.add(schema);
361 } else {
362 if (logging)
363 logger.fine("Excluding schema " + schema +
364 ": matches \"" + schemaRegex + "\" but contains no tables");
365 }
366 } catch (SQLException ignore) {
367 } finally {
368 if (rs != null)
369 rs.close();
370 }
371 } else {
372 if (logging)
373 logger.fine("Excluding schema " + schema +
374 ": doesn't match \"" + schemaRegex + '"');
375 }
376 }
377
378 return new ArrayList<String>(schemas);
379 }
380
381
382
383
384
385
386 public static void dumpResultSetRow(ResultSet rs, String description) throws SQLException {
387 ResultSetMetaData meta = rs.getMetaData();
388 int numColumns = meta.getColumnCount();
389 System.out.println(numColumns + " columns of " + description + ":");
390 for (int i = 1; i <= numColumns; ++i) {
391 System.out.print(meta.getColumnLabel(i));
392 System.out.print(": ");
393 System.out.print(String.valueOf(rs.getString(i)));
394 System.out.print("\t");
395 }
396 System.out.println();
397 }
398 }