1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 package net.sourceforge.schemaspy.model;
20
21 import java.sql.Connection;
22 import java.sql.DatabaseMetaData;
23 import java.sql.PreparedStatement;
24 import java.sql.ResultSet;
25 import java.sql.SQLException;
26 import java.text.SimpleDateFormat;
27 import java.util.ArrayList;
28 import java.util.Arrays;
29 import java.util.Collection;
30 import java.util.Date;
31 import java.util.HashMap;
32 import java.util.HashSet;
33 import java.util.Iterator;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.MissingResourceException;
37 import java.util.Properties;
38 import java.util.Set;
39 import java.util.StringTokenizer;
40 import java.util.logging.Level;
41 import java.util.logging.Logger;
42 import java.util.regex.Matcher;
43 import java.util.regex.Pattern;
44 import net.sourceforge.schemaspy.Config;
45 import net.sourceforge.schemaspy.model.xml.SchemaMeta;
46 import net.sourceforge.schemaspy.model.xml.TableMeta;
47 import net.sourceforge.schemaspy.util.CaseInsensitiveMap;
48
49 public class Database {
50 private final String databaseName;
51 private final String schema;
52 private String description;
53 private final Map<String, Table> tables = new CaseInsensitiveMap<Table>();
54 private final Map<String, View> views = new CaseInsensitiveMap<View>();
55 private final Map<String, Table> remoteTables = new CaseInsensitiveMap<Table>();
56 private final DatabaseMetaData meta;
57 private final Connection connection;
58 private final String connectTime = new SimpleDateFormat("EEE MMM dd HH:mm z yyyy").format(new Date());
59 private Set<String> sqlKeywords;
60 private Pattern invalidIdentifierPattern;
61 private final Logger logger = Logger.getLogger(getClass().getName());
62 private final boolean fineEnabled = logger.isLoggable(Level.FINE);
63
64 public Database(Config config, Connection connection, DatabaseMetaData meta, String name, String schema, Properties properties, SchemaMeta schemaMeta) throws SQLException, MissingResourceException {
65 this.connection = connection;
66 this.meta = meta;
67 databaseName = name;
68 this.schema = schema;
69 description = config.getDescription();
70
71 initTables(meta, properties, config);
72 if (config.isViewsEnabled())
73 initViews(meta, properties, config);
74
75 initCheckConstraints(properties);
76 initTableIds(properties);
77 initIndexIds(properties);
78 initTableComments(properties);
79 initTableColumnComments(properties);
80 initViewComments(properties);
81 initViewColumnComments(properties);
82
83 connectTables();
84 updateFromXmlMetadata(schemaMeta);
85 }
86
87 public String getName() {
88 return databaseName;
89 }
90
91 public String getSchema() {
92 return schema;
93 }
94
95
96
97
98
99
100 public String getDescription() {
101 return description;
102 }
103
104 public Collection<Table> getTables() {
105 return tables.values();
106 }
107
108
109
110
111
112
113 public Map<String, Table> getTablesByName() {
114 return tables;
115 }
116
117 public Collection<View> getViews() {
118 return views.values();
119 }
120
121 public Collection<Table> getRemoteTables() {
122 return remoteTables.values();
123 }
124
125 public Connection getConnection() {
126 return connection;
127 }
128
129 public DatabaseMetaData getMetaData() {
130 return meta;
131 }
132
133 public String getConnectTime() {
134 return connectTime;
135 }
136
137 public String getDatabaseProduct() {
138 try {
139 return meta.getDatabaseProductName() + " - " + meta.getDatabaseProductVersion();
140 } catch (SQLException exc) {
141 return "";
142 }
143 }
144
145
146
147
148 class NameValidator {
149 private final String clazz;
150 private final Pattern include;
151 private final Pattern exclude;
152 private final Set<String> validTypes;
153
154
155
156
157
158
159
160 NameValidator(String clazz, Pattern include, Pattern exclude, String[] validTypes) {
161 this.clazz = clazz;
162 this.include = include;
163 this.exclude = exclude;
164 this.validTypes = new HashSet<String>();
165 for (String type : validTypes)
166 {
167 this.validTypes.add(type.toUpperCase());
168 }
169 }
170
171
172
173
174
175
176
177
178 boolean isValid(String name, String type) {
179
180 if (!validTypes.contains(type.toUpperCase()))
181 return false;
182
183
184
185
186
187 if (name.indexOf("BIN$") == 0) {
188 if (fineEnabled) {
189 logger.fine("Excluding " + clazz + " " + name +
190 ": \"BIN$\" prefix implies a (deleted) table in the Oracle Recycle Bin ");
191 }
192 return false;
193 }
194
195 if (exclude.matcher(name).matches()) {
196 if (fineEnabled) {
197 logger.fine("Excluding " + clazz + " " + name +
198 ": matches exclusion pattern \"" + exclude + '"');
199 }
200 return false;
201 }
202
203 boolean valid = include.matcher(name).matches();
204 if (fineEnabled) {
205 if (valid) {
206 logger.fine("Including " + clazz + " " + name +
207 ": matches inclusion pattern \"" + include + '"');
208 } else {
209 logger.fine("Excluding " + clazz + " " + name +
210 ": doesn't match inclusion pattern \"" + include + '"');
211 }
212 }
213 return valid;
214 }
215 }
216
217
218
219
220
221
222
223
224
225 private void initTables(final DatabaseMetaData metadata, final Properties properties,
226 final Config config) throws SQLException {
227 final Pattern include = config.getTableInclusions();
228 final Pattern exclude = config.getTableExclusions();
229 final int maxThreads = config.getMaxDbThreads();
230
231 String[] types = getTypes("tableTypes", "TABLE", properties);
232 NameValidator validator = new NameValidator("table", include, exclude, types);
233 List<BasicTableMeta> entries = getBasicTableMeta(metadata, true, properties, types);
234
235 TableCreator creator;
236 if (maxThreads == 1) {
237 creator = new TableCreator();
238 } else {
239
240
241
242
243 creator = new ThreadedTableCreator(maxThreads);
244
245
246
247 while (!entries.isEmpty()) {
248 BasicTableMeta entry = entries.remove(0);
249
250 if (validator.isValid(entry.name, entry.type)) {
251 new TableCreator().create(entry, properties);
252 break;
253 }
254 }
255 }
256
257
258 for (BasicTableMeta entry : entries) {
259 if (validator.isValid(entry.name, entry.type)) {
260 creator.create(entry, properties);
261 }
262 }
263
264
265 creator.join();
266 }
267
268
269
270
271
272
273
274
275
276 private void initViews(DatabaseMetaData metadata, Properties properties,
277 Config config) throws SQLException {
278 Pattern includeTables = config.getTableInclusions();
279 Pattern excludeTables = config.getTableExclusions();
280 Pattern excludeColumns = config.getColumnExclusions();
281 Pattern excludeIndirectColumns = config.getIndirectColumnExclusions();
282
283 String[] types = getTypes("viewTypes", "VIEW", properties);
284 NameValidator validator = new NameValidator("view", includeTables, excludeTables, types);
285
286 for (BasicTableMeta entry : getBasicTableMeta(metadata, false, properties, types)) {
287 if (validator.isValid(entry.name, entry.type)) {
288 View view = new View(this, entry.schema, entry.name, entry.remarks,
289 entry.viewSql, properties,
290 excludeIndirectColumns, excludeColumns);
291 views.put(view.getName(), view);
292 if (logger.isLoggable(Level.FINE)) {
293 logger.fine("Found details of view " + view.getName());
294 } else {
295 System.out.print('.');
296 }
297 }
298 }
299 }
300
301
302
303
304 private class BasicTableMeta
305 {
306 @SuppressWarnings("hiding")
307 final String schema;
308 final String name;
309 final String type;
310 final String remarks;
311 final String viewSql;
312 final long numRows;
313
314
315
316
317
318
319
320
321
322 BasicTableMeta(String schema, String name, String type, String remarks, String text, long numRows)
323 {
324 this.schema = schema;
325 this.name = name;
326 this.type = type;
327 this.remarks = remarks;
328 viewSql = text;
329 this.numRows = numRows;
330 }
331 }
332
333
334
335
336
337
338
339
340
341
342 private List<BasicTableMeta> getBasicTableMeta(DatabaseMetaData metadata,
343 boolean forTables,
344 Properties properties,
345 String... types) throws SQLException {
346 String queryName = forTables ? "selectTablesSql" : "selectViewsSql";
347 String sql = properties.getProperty(queryName);
348 List<BasicTableMeta> basics = new ArrayList<BasicTableMeta>();
349 ResultSet rs = null;
350
351 if (sql != null) {
352 String clazz = forTables ? "table" : "view";
353 PreparedStatement stmt = null;
354
355 try {
356 stmt = prepareStatement(sql, null);
357 rs = stmt.executeQuery();
358
359 while (rs.next()) {
360 String name = rs.getString(clazz + "_name");
361 String sch = getOptionalString(rs, clazz + "_schema");
362 if (sch == null)
363 sch = schema;
364 String remarks = getOptionalString(rs, clazz + "_comment");
365 String text = forTables ? null : getOptionalString(rs, "view_definition");
366 String rows = forTables ? getOptionalString(rs, "table_rows") : null;
367 long numRows = rows == null ? -1 : Long.parseLong(rows);
368
369 basics.add(new BasicTableMeta(sch, name, clazz, remarks, text, numRows));
370 }
371 } catch (SQLException sqlException) {
372
373 System.out.flush();
374 System.err.println();
375 System.err.println("Failed to retrieve " + clazz + " names with custom SQL: " + sqlException);
376 System.err.println(sql);
377 } finally {
378 if (rs != null)
379 rs.close();
380 if (stmt != null)
381 stmt.close();
382 }
383 }
384
385 if (basics.isEmpty()) {
386 rs = metadata.getTables(null, schema, "%", types);
387
388 try {
389 while (rs.next()) {
390 String name = rs.getString("TABLE_NAME");
391 String type = rs.getString("TABLE_TYPE");
392 String schem = rs.getString("TABLE_SCHEM");
393 String remarks = getOptionalString(rs, "REMARKS");
394
395 basics.add(new BasicTableMeta(schem, name, type, remarks, null, -1));
396 }
397 } catch (SQLException exc) {
398 if (forTables)
399 throw exc;
400
401 System.out.flush();
402 System.err.println();
403 System.err.println("Ignoring view " + rs.getString("TABLE_NAME") + " due to exception:");
404 exc.printStackTrace();
405 System.err.println("Continuing analysis.");
406 } finally {
407 if (rs != null)
408 rs.close();
409 }
410 }
411
412 return basics;
413 }
414
415
416
417
418
419
420
421
422
423
424 private String[] getTypes(String propName, String defaultValue, Properties props) {
425 String value = props.getProperty(propName, defaultValue);
426 List<String> types = new ArrayList<String>();
427 for (String type : value.split(",")) {
428 type = type.trim();
429 if (type.length() > 0)
430 types.add(type);
431 }
432
433 return types.toArray(new String[types.size()]);
434 }
435
436
437
438
439
440
441 public String getOptionalString(ResultSet rs, String columnName)
442 {
443 try {
444 return rs.getString(columnName);
445 } catch (SQLException ignore) {
446 return null;
447 }
448 }
449
450 private void initCheckConstraints(Properties properties) throws SQLException {
451 String sql = properties.getProperty("selectCheckConstraintsSql");
452 if (sql != null) {
453 PreparedStatement stmt = null;
454 ResultSet rs = null;
455
456 try {
457 stmt = prepareStatement(sql, null);
458 rs = stmt.executeQuery();
459
460 while (rs.next()) {
461 String tableName = rs.getString("table_name");
462 Table table = tables.get(tableName);
463 if (table != null)
464 table.addCheckConstraint(rs.getString("constraint_name"), rs.getString("text"));
465 }
466 } catch (SQLException sqlException) {
467
468 System.err.println();
469 System.err.println("Failed to retrieve check constraints: " + sqlException);
470 System.err.println(sql);
471 } finally {
472 if (rs != null)
473 rs.close();
474 if (stmt != null)
475 stmt.close();
476 }
477 }
478 }
479
480 private void initTableIds(Properties properties) throws SQLException {
481 String sql = properties.getProperty("selectTableIdsSql");
482 if (sql != null) {
483 PreparedStatement stmt = null;
484 ResultSet rs = null;
485
486 try {
487 stmt = prepareStatement(sql, null);
488 rs = stmt.executeQuery();
489
490 while (rs.next()) {
491 String tableName = rs.getString("table_name");
492 Table table = tables.get(tableName);
493 if (table != null)
494 table.setId(rs.getObject("table_id"));
495 }
496 } catch (SQLException sqlException) {
497 System.err.println();
498 System.err.println(sql);
499 throw sqlException;
500 } finally {
501 if (rs != null)
502 rs.close();
503 if (stmt != null)
504 stmt.close();
505 }
506 }
507 }
508
509 private void initIndexIds(Properties properties) throws SQLException {
510 String sql = properties.getProperty("selectIndexIdsSql");
511 if (sql != null) {
512 PreparedStatement stmt = null;
513 ResultSet rs = null;
514
515 try {
516 stmt = prepareStatement(sql, null);
517 rs = stmt.executeQuery();
518
519 while (rs.next()) {
520 String tableName = rs.getString("table_name");
521 Table table = tables.get(tableName);
522 if (table != null) {
523 TableIndex index = table.getIndex(rs.getString("index_name"));
524 if (index != null)
525 index.setId(rs.getObject("index_id"));
526 }
527 }
528 } catch (SQLException sqlException) {
529 System.err.println();
530 System.err.println(sql);
531 throw sqlException;
532 } finally {
533 if (rs != null)
534 rs.close();
535 if (stmt != null)
536 stmt.close();
537 }
538 }
539 }
540
541
542
543
544
545
546
547
548
549 private void initTableComments(Properties properties) throws SQLException {
550 String sql = properties.getProperty("selectTableCommentsSql");
551 if (sql != null) {
552 PreparedStatement stmt = null;
553 ResultSet rs = null;
554
555 try {
556 stmt = prepareStatement(sql, null);
557 rs = stmt.executeQuery();
558
559 while (rs.next()) {
560 String tableName = rs.getString("table_name");
561 Table table = tables.get(tableName);
562 if (table == null)
563 table = views.get(tableName);
564
565 if (table != null)
566 table.setComments(rs.getString("comments"));
567 }
568 } catch (SQLException sqlException) {
569
570 System.err.println();
571 System.err.println("Failed to retrieve table/view comments: " + sqlException);
572 System.err.println(sql);
573 } finally {
574 if (rs != null)
575 rs.close();
576 if (stmt != null)
577 stmt.close();
578 }
579 }
580 }
581
582
583
584
585
586
587
588 private void initViewComments(Properties properties) throws SQLException {
589 String sql = properties.getProperty("selectViewCommentsSql");
590 if (sql != null) {
591 PreparedStatement stmt = null;
592 ResultSet rs = null;
593
594 try {
595 stmt = prepareStatement(sql, null);
596 rs = stmt.executeQuery();
597
598 while (rs.next()) {
599 String viewName = rs.getString("view_name");
600 if (viewName == null)
601 viewName = rs.getString("table_name");
602 Table view = views.get(viewName);
603
604 if (view != null)
605 view.setComments(rs.getString("comments"));
606 }
607 } catch (SQLException sqlException) {
608
609 System.err.println();
610 System.err.println("Failed to retrieve table/view comments: " + sqlException);
611 System.err.println(sql);
612 } finally {
613 if (rs != null)
614 rs.close();
615 if (stmt != null)
616 stmt.close();
617 }
618 }
619 }
620
621
622
623
624
625
626
627
628
629 private void initTableColumnComments(Properties properties) throws SQLException {
630 String sql = properties.getProperty("selectColumnCommentsSql");
631 if (sql != null) {
632 PreparedStatement stmt = null;
633 ResultSet rs = null;
634
635 try {
636 stmt = prepareStatement(sql, null);
637 rs = stmt.executeQuery();
638
639 while (rs.next()) {
640 String tableName = rs.getString("table_name");
641 Table table = tables.get(tableName);
642 if (table == null)
643 table = views.get(tableName);
644
645 if (table != null) {
646 TableColumn column = table.getColumn(rs.getString("column_name"));
647 if (column != null)
648 column.setComments(rs.getString("comments"));
649 }
650 }
651 } catch (SQLException sqlException) {
652
653 System.err.println();
654 System.err.println("Failed to retrieve column comments: " + sqlException);
655 System.err.println(sql);
656 } finally {
657 if (rs != null)
658 rs.close();
659 if (stmt != null)
660 stmt.close();
661 }
662 }
663 }
664
665
666
667
668
669
670
671 private void initViewColumnComments(Properties properties) throws SQLException {
672 String sql = properties.getProperty("selectViewColumnCommentsSql");
673 if (sql != null) {
674 PreparedStatement stmt = null;
675 ResultSet rs = null;
676
677 try {
678 stmt = prepareStatement(sql, null);
679 rs = stmt.executeQuery();
680
681 while (rs.next()) {
682 String viewName = rs.getString("view_name");
683 if (viewName == null)
684 viewName = rs.getString("table_name");
685 Table view = views.get(viewName);
686
687 if (view != null) {
688 TableColumn column = view.getColumn(rs.getString("column_name"));
689 if (column != null)
690 column.setComments(rs.getString("comments"));
691 }
692 }
693 } catch (SQLException sqlException) {
694
695 System.err.println();
696 System.err.println("Failed to retrieve view column comments: " + sqlException);
697 System.err.println(sql);
698 } finally {
699 if (rs != null)
700 rs.close();
701 if (stmt != null)
702 stmt.close();
703 }
704 }
705 }
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720 public PreparedStatement prepareStatement(String sql, String tableName) throws SQLException {
721 StringBuilder sqlBuf = new StringBuilder(sql);
722 List<String> sqlParams = getSqlParams(sqlBuf, tableName);
723 PreparedStatement stmt = getConnection().prepareStatement(sqlBuf.toString());
724
725 try {
726 for (int i = 0; i < sqlParams.size(); ++i) {
727 stmt.setString(i + 1, sqlParams.get(i).toString());
728 }
729 } catch (SQLException exc) {
730 stmt.close();
731 throw exc;
732 }
733
734 return stmt;
735 }
736
737 public Table addRemoteTable(String remoteSchema, String remoteTableName, String baseSchema, Properties properties, Pattern excludeIndirectColumns, Pattern excludeColumns) throws SQLException {
738 String fullName = remoteSchema + "." + remoteTableName;
739 Table remoteTable = remoteTables.get(fullName);
740 if (remoteTable == null) {
741 if (properties != null)
742 remoteTable = new RemoteTable(this, remoteSchema, remoteTableName, baseSchema, properties, excludeIndirectColumns, excludeColumns);
743 else
744 remoteTable = new ExplicitRemoteTable(this, remoteSchema, remoteTableName, baseSchema);
745
746 logger.fine("Adding remote table " + fullName);
747 remoteTable.connectForeignKeys(tables, excludeIndirectColumns, excludeColumns);
748 remoteTables.put(fullName, remoteTable);
749 }
750
751 return remoteTable;
752 }
753
754
755
756
757
758
759
760 public Set<String> getSqlKeywords() throws SQLException {
761 if (sqlKeywords == null) {
762
763 String[] sql92Keywords =
764 ("ADA" +
765 "| C | CATALOG_NAME | CHARACTER_SET_CATALOG | CHARACTER_SET_NAME" +
766 "| CHARACTER_SET_SCHEMA | CLASS_ORIGIN | COBOL | COLLATION_CATALOG" +
767 "| COLLATION_NAME | COLLATION_SCHEMA | COLUMN_NAME | COMMAND_FUNCTION | COMMITTED" +
768 "| CONDITION_NUMBER | CONNECTION_NAME | CONSTRAINT_CATALOG | CONSTRAINT_NAME" +
769 "| CONSTRAINT_SCHEMA | CURSOR_NAME" +
770 "| DATA | DATETIME_INTERVAL_CODE | DATETIME_INTERVAL_PRECISION | DYNAMIC_FUNCTION" +
771 "| FORTRAN" +
772 "| LENGTH" +
773 "| MESSAGE_LENGTH | MESSAGE_OCTET_LENGTH | MESSAGE_TEXT | MORE | MUMPS" +
774 "| NAME | NULLABLE | NUMBER" +
775 "| PASCAL | PLI" +
776 "| REPEATABLE | RETURNED_LENGTH | RETURNED_OCTET_LENGTH | RETURNED_SQLSTATE" +
777 "| ROW_COUNT" +
778 "| SCALE | SCHEMA_NAME | SERIALIZABLE | SERVER_NAME | SUBCLASS_ORIGIN" +
779 "| TABLE_NAME | TYPE" +
780 "| UNCOMMITTED | UNNAMED" +
781 "| ABSOLUTE | ACTION | ADD | ALL | ALLOCATE | ALTER | AND" +
782 "| ANY | ARE | AS | ASC" +
783 "| ASSERTION | AT | AUTHORIZATION | AVG" +
784 "| BEGIN | BETWEEN | BIT | BIT_LENGTH | BOTH | BY" +
785 "| CASCADE | CASCADED | CASE | CAST | CATALOG | CHAR | CHARACTER | CHAR_LENGTH" +
786 "| CHARACTER_LENGTH | CHECK | CLOSE | COALESCE | COLLATE | COLLATION" +
787 "| COLUMN | COMMIT | CONNECT | CONNECTION | CONSTRAINT" +
788 "| CONSTRAINTS | CONTINUE" +
789 "| CONVERT | CORRESPONDING | COUNT | CREATE | CROSS | CURRENT" +
790 "| CURRENT_DATE | CURRENT_TIME | CURRENT_TIMESTAMP | CURRENT_USER | CURSOR" +
791 "| DATE | DAY | DEALLOCATE | DEC | DECIMAL | DECLARE | DEFAULT | DEFERRABLE" +
792 "| DEFERRED | DELETE | DESC | DESCRIBE | DESCRIPTOR | DIAGNOSTICS" +
793 "| DISCONNECT | DISTINCT | DOMAIN | DOUBLE | DROP" +
794 "| ELSE | END | END-EXEC | ESCAPE | EXCEPT | EXCEPTION" +
795 "| EXEC | EXECUTE | EXISTS" +
796 "| EXTERNAL | EXTRACT" +
797 "| FALSE | FETCH | FIRST | FLOAT | FOR | FOREIGN | FOUND | FROM | FULL" +
798 "| GET | GLOBAL | GO | GOTO | GRANT | GROUP" +
799 "| HAVING | HOUR" +
800 "| IDENTITY | IMMEDIATE | IN | INDICATOR | INITIALLY | INNER | INPUT" +
801 "| INSENSITIVE | INSERT | INT | INTEGER | INTERSECT | INTERVAL | INTO | IS" +
802 "| ISOLATION" +
803 "| JOIN" +
804 "| KEY" +
805 "| LANGUAGE | LAST | LEADING | LEFT | LEVEL | LIKE | LOCAL | LOWER" +
806 "| MATCH | MAX | MIN | MINUTE | MODULE | MONTH" +
807 "| NAMES | NATIONAL | NATURAL | NCHAR | NEXT | NO | NOT | NULL" +
808 "| NULLIF | NUMERIC" +
809 "| OCTET_LENGTH | OF | ON | ONLY | OPEN | OPTION | OR" +
810 "| ORDER | OUTER" +
811 "| OUTPUT | OVERLAPS" +
812 "| PAD | PARTIAL | POSITION | PRECISION | PREPARE | PRESERVE | PRIMARY" +
813 "| PRIOR | PRIVILEGES | PROCEDURE | PUBLIC" +
814 "| READ | REAL | REFERENCES | RELATIVE | RESTRICT | REVOKE | RIGHT" +
815 "| ROLLBACK | ROWS" +
816 "| SCHEMA | SCROLL | SECOND | SECTION | SELECT | SESSION | SESSION_USER | SET" +
817 "| SIZE | SMALLINT | SOME | SPACE | SQL | SQLCODE | SQLERROR | SQLSTATE" +
818 "| SUBSTRING | SUM | SYSTEM_USER" +
819 "| TABLE | TEMPORARY | THEN | TIME | TIMESTAMP | TIMEZONE_HOUR | TIMEZONE_MINUTE" +
820 "| TO | TRAILING | TRANSACTION | TRANSLATE | TRANSLATION | TRIM | TRUE" +
821 "| UNION | UNIQUE | UNKNOWN | UPDATE | UPPER | USAGE | USER | USING" +
822 "| VALUE | VALUES | VARCHAR | VARYING | VIEW" +
823 "| WHEN | WHENEVER | WHERE | WITH | WORK | WRITE" +
824 "| YEAR" +
825 "| ZONE").split("|,\\s*");
826
827 String[] nonSql92Keywords = getMetaData().getSQLKeywords().toUpperCase().split(",\\s*");
828
829 sqlKeywords = new HashSet<String>();
830 sqlKeywords.addAll(Arrays.asList(sql92Keywords));
831 sqlKeywords.addAll(Arrays.asList(nonSql92Keywords));
832 }
833
834 return sqlKeywords;
835 }
836
837
838
839
840
841
842
843
844 public String getQuotedIdentifier(String id) throws SQLException {
845
846 Matcher matcher = getInvalidIdentifierPattern().matcher(id);
847
848 boolean quotesRequired = matcher.find() || getSqlKeywords().contains(id.toUpperCase());
849
850 if (quotesRequired) {
851
852 String quote = getMetaData().getIdentifierQuoteString().trim();
853 return quote + id + quote;
854 }
855
856
857 return id;
858 }
859
860
861
862
863
864
865 private Pattern getInvalidIdentifierPattern() throws SQLException {
866 if (invalidIdentifierPattern == null) {
867 String validChars = "a-zA-Z0-9_";
868 String reservedRegexChars = "-&^";
869 String extraValidChars = getMetaData().getExtraNameCharacters();
870 for (int i = 0; i < extraValidChars.length(); ++i) {
871 char ch = extraValidChars.charAt(i);
872 if (reservedRegexChars.indexOf(ch) >= 0)
873 validChars += "\\";
874 validChars += ch;
875 }
876
877 invalidIdentifierPattern = Pattern.compile("[^" + validChars + "]");
878 }
879
880 return invalidIdentifierPattern;
881 }
882
883
884
885
886
887
888
889
890
891
892
893 private List<String> getSqlParams(StringBuilder sql, String tableName) {
894 Map<String, String> namedParams = new HashMap<String, String>();
895 @SuppressWarnings("hiding")
896 String schema = getSchema();
897 if (schema == null)
898 schema = getName();
899 namedParams.put(":schema", schema);
900 namedParams.put(":owner", schema);
901 if (tableName != null) {
902 namedParams.put(":table", tableName);
903 namedParams.put(":view", tableName);
904 }
905
906 List<String> sqlParams = new ArrayList<String>();
907 int nextColon = sql.indexOf(":");
908 while (nextColon != -1) {
909 String paramName = new StringTokenizer(sql.substring(nextColon), " ,\"')").nextToken();
910 String paramValue = namedParams.get(paramName);
911 if (paramValue == null)
912 throw new InvalidConfigurationException("Unexpected named parameter '" + paramName + "' found in SQL '" + sql + "'");
913 sqlParams.add(paramValue);
914 sql.replace(nextColon, nextColon + paramName.length(), "?");
915 nextColon = sql.indexOf(":", nextColon);
916 }
917
918 return sqlParams;
919 }
920
921
922
923
924
925
926
927 private void updateFromXmlMetadata(SchemaMeta schemaMeta) throws SQLException {
928 if (schemaMeta != null) {
929 final Pattern excludeNone = Pattern.compile("[^.]");
930 final Properties noProps = new Properties();
931
932 description = schemaMeta.getComments();
933
934
935
936
937
938
939
940 for (TableMeta tableMeta : schemaMeta.getTables()) {
941 Table table;
942
943 if (tableMeta.getRemoteSchema() != null) {
944 table = remoteTables.get(tableMeta.getRemoteSchema() + '.' + tableMeta.getName());
945 if (table == null) {
946 table = addRemoteTable(tableMeta.getRemoteSchema(), tableMeta.getName(), getSchema(), null, excludeNone, excludeNone);
947 }
948 } else {
949 table = tables.get(tableMeta.getName());
950
951 if (table == null)
952 table = views.get(tableMeta.getName());
953
954 if (table == null) {
955 table = new Table(Database.this, getSchema(), tableMeta.getName(), null, noProps, excludeNone, excludeNone);
956 tables.put(table.getName(), table);
957 }
958 }
959
960 table.update(tableMeta);
961 }
962
963
964 for (TableMeta tableMeta : schemaMeta.getTables()) {
965 Table table;
966
967 if (tableMeta.getRemoteSchema() != null) {
968 table = remoteTables.get(tableMeta.getRemoteSchema() + '.' + tableMeta.getName());
969 } else {
970 table = tables.get(tableMeta.getName());
971 if (table == null)
972 table = views.get(tableMeta.getName());
973 }
974
975 table.connect(tableMeta, tables, remoteTables);
976 }
977 }
978 }
979
980 private void connectTables() throws SQLException {
981 Pattern excludeColumns = Config.getInstance().getColumnExclusions();
982 Pattern excludeIndirectColumns = Config.getInstance().getIndirectColumnExclusions();
983
984 for (Table table : tables.values()) {
985 table.connectForeignKeys(tables, excludeIndirectColumns, excludeColumns);
986 }
987 }
988
989
990
991
992 private class TableCreator {
993 private final Pattern excludeColumns = Config.getInstance().getColumnExclusions();
994 private final Pattern excludeIndirectColumns = Config.getInstance().getIndirectColumnExclusions();
995
996
997
998
999 void create(BasicTableMeta tableMeta, Properties properties) throws SQLException {
1000 createImpl(tableMeta, properties);
1001 }
1002
1003 protected void createImpl(BasicTableMeta tableMeta, Properties properties) throws SQLException {
1004 Table table = new Table(Database.this, tableMeta.schema, tableMeta.name, tableMeta.remarks, properties, excludeIndirectColumns, excludeColumns);
1005 if (tableMeta.numRows != -1) {
1006 table.setNumRows(tableMeta.numRows);
1007 }
1008
1009 synchronized (tables) {
1010 tables.put(table.getName(), table);
1011 }
1012
1013 if (logger.isLoggable(Level.FINE)) {
1014 logger.fine("Found details of table " + table.getName());
1015 } else {
1016 System.out.print('.');
1017 }
1018 }
1019
1020
1021
1022
1023
1024 void join() {
1025 }
1026 }
1027
1028
1029
1030
1031 private class ThreadedTableCreator extends TableCreator {
1032 private final Set<Thread> threads = new HashSet<Thread>();
1033 private final int maxThreads;
1034
1035 ThreadedTableCreator(int maxThreads) {
1036 this.maxThreads = maxThreads;
1037 }
1038
1039 @Override
1040 void create(final BasicTableMeta tableMeta, final Properties properties) throws SQLException {
1041 Thread runner = new Thread() {
1042 @Override
1043 public void run() {
1044 try {
1045 createImpl(tableMeta, properties);
1046 } catch (SQLException exc) {
1047 exc.printStackTrace();
1048 } finally {
1049 synchronized (threads) {
1050 threads.remove(this);
1051 threads.notify();
1052 }
1053 }
1054 }
1055 };
1056
1057 synchronized (threads) {
1058
1059 while (threads.size() >= maxThreads) {
1060 try {
1061 threads.wait();
1062 } catch (InterruptedException interrupted) {
1063 }
1064 }
1065
1066 threads.add(runner);
1067 }
1068
1069 runner.start();
1070 }
1071
1072
1073
1074
1075 @Override
1076 public void join() {
1077 while (true) {
1078 Thread thread;
1079
1080 synchronized (threads) {
1081 Iterator<Thread> iter = threads.iterator();
1082 if (!iter.hasNext())
1083 break;
1084
1085 thread = iter.next();
1086 }
1087
1088 try {
1089 thread.join();
1090 } catch (InterruptedException exc) {
1091 }
1092 }
1093 }
1094 }
1095 }