1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package org.mybatis.generator.internal.db;
17
18 import static org.mybatis.generator.internal.util.JavaBeansUtil.getCamelCaseString;
19 import static org.mybatis.generator.internal.util.JavaBeansUtil.getValidPropertyName;
20 import static org.mybatis.generator.internal.util.StringUtility.composeFullyQualifiedTableName;
21 import static org.mybatis.generator.internal.util.StringUtility.isTrue;
22 import static org.mybatis.generator.internal.util.StringUtility.stringContainsSQLWildcard;
23 import static org.mybatis.generator.internal.util.StringUtility.stringContainsSpace;
24 import static org.mybatis.generator.internal.util.StringUtility.stringHasValue;
25 import static org.mybatis.generator.internal.util.messages.Messages.getString;
26
27 import java.sql.DatabaseMetaData;
28 import java.sql.ResultSet;
29 import java.sql.SQLException;
30 import java.util.ArrayList;
31 import java.util.HashMap;
32 import java.util.Iterator;
33 import java.util.List;
34 import java.util.Map;
35 import java.util.StringTokenizer;
36 import java.util.TreeMap;
37 import java.util.regex.Matcher;
38 import java.util.regex.Pattern;
39
40 import org.mybatis.generator.api.FullyQualifiedTable;
41 import org.mybatis.generator.api.IntrospectedColumn;
42 import org.mybatis.generator.api.IntrospectedTable;
43 import org.mybatis.generator.api.JavaTypeResolver;
44 import org.mybatis.generator.api.dom.java.FullyQualifiedJavaType;
45 import org.mybatis.generator.api.dom.java.JavaReservedWords;
46 import org.mybatis.generator.config.ColumnOverride;
47 import org.mybatis.generator.config.Context;
48 import org.mybatis.generator.config.GeneratedKey;
49 import org.mybatis.generator.config.PropertyRegistry;
50 import org.mybatis.generator.config.TableConfiguration;
51 import org.mybatis.generator.internal.ObjectFactory;
52 import org.mybatis.generator.logging.Log;
53 import org.mybatis.generator.logging.LogFactory;
54
55
56
57
58
59
60 public class DatabaseIntrospector {
61
62
63 private DatabaseMetaData databaseMetaData;
64
65
66 private JavaTypeResolver javaTypeResolver;
67
68
69 private List<String> warnings;
70
71
72 private Context context;
73
74
75 private Log logger;
76
77
78
79
80
81
82
83
84
85
86
87
88
89 public DatabaseIntrospector(Context context,
90 DatabaseMetaData databaseMetaData,
91 JavaTypeResolver javaTypeResolver, List<String> warnings) {
92 super();
93 this.context = context;
94 this.databaseMetaData = databaseMetaData;
95 this.javaTypeResolver = javaTypeResolver;
96 this.warnings = warnings;
97 logger = LogFactory.getLog(getClass());
98 }
99
100
101
102
103
104
105
106
107
108 private void calculatePrimaryKey(FullyQualifiedTable table,
109 IntrospectedTable introspectedTable) {
110 ResultSet rs = null;
111
112 try {
113 rs = databaseMetaData.getPrimaryKeys(
114 table.getIntrospectedCatalog(), table
115 .getIntrospectedSchema(), table
116 .getIntrospectedTableName());
117 } catch (SQLException e) {
118 closeResultSet(rs);
119 warnings.add(getString("Warning.15"));
120 return;
121 }
122
123 try {
124
125 Map<Short, String> keyColumns = new TreeMap<Short, String>();
126 while (rs.next()) {
127 String columnName = rs.getString("COLUMN_NAME");
128 short keySeq = rs.getShort("KEY_SEQ");
129 keyColumns.put(keySeq, columnName);
130 }
131
132 for (String columnName : keyColumns.values()) {
133 introspectedTable.addPrimaryKeyColumn(columnName);
134 }
135 } catch (SQLException e) {
136
137 } finally {
138 closeResultSet(rs);
139 }
140 }
141
142
143
144
145
146
147
148 private void closeResultSet(ResultSet rs) {
149 if (rs != null) {
150 try {
151 rs.close();
152 } catch (SQLException e) {
153
154 }
155 }
156 }
157
158
159
160
161
162
163
164
165
166
167
168 private void reportIntrospectionWarnings(
169 IntrospectedTable introspectedTable,
170 TableConfiguration tableConfiguration, FullyQualifiedTable table) {
171
172
173 for (ColumnOverride columnOverride : tableConfiguration
174 .getColumnOverrides()) {
175 if (introspectedTable.getColumn(columnOverride.getColumnName()) == null) {
176 warnings.add(getString("Warning.3",
177 columnOverride.getColumnName(), table.toString()));
178 }
179 }
180
181
182
183 for (String string : tableConfiguration.getIgnoredColumnsInError()) {
184 warnings.add(getString("Warning.4",
185 string, table.toString()));
186 }
187
188 GeneratedKey generatedKey = tableConfiguration.getGeneratedKey();
189 if (generatedKey != null
190 && introspectedTable.getColumn(generatedKey.getColumn()) == null) {
191 if (generatedKey.isIdentity()) {
192 warnings.add(getString("Warning.5",
193 generatedKey.getColumn(), table.toString()));
194 } else {
195 warnings.add(getString("Warning.6",
196 generatedKey.getColumn(), table.toString()));
197 }
198 }
199
200 for (IntrospectedColumn ic : introspectedTable.getAllColumns()) {
201 if (JavaReservedWords.containsWord(ic.getJavaProperty())) {
202 warnings.add(getString("Warning.26",
203 ic.getActualColumnName(), table.toString()));
204 }
205 }
206 }
207
208
209
210
211
212
213
214
215
216
217 public List<IntrospectedTable> introspectTables(TableConfiguration tc)
218 throws SQLException {
219
220
221 Map<ActualTableName, List<IntrospectedColumn>> columns = getColumns(tc);
222
223 if (columns.isEmpty()) {
224 warnings.add(getString("Warning.19", tc.getCatalog(),
225 tc.getSchema(), tc.getTableName()));
226 return null;
227 }
228
229 removeIgnoredColumns(tc, columns);
230 calculateExtraColumnInformation(tc, columns);
231 applyColumnOverrides(tc, columns);
232 calculateIdentityColumns(tc, columns);
233
234 List<IntrospectedTable> introspectedTables = calculateIntrospectedTables(
235 tc, columns);
236
237
238
239
240 Iterator<IntrospectedTable> iter = introspectedTables.iterator();
241 while (iter.hasNext()) {
242 IntrospectedTable introspectedTable = iter.next();
243
244 if (!introspectedTable.hasAnyColumns()) {
245
246
247 String warning = getString(
248 "Warning.1", introspectedTable.getFullyQualifiedTable().toString());
249 warnings.add(warning);
250 iter.remove();
251 } else if (!introspectedTable.hasPrimaryKeyColumns()
252 && !introspectedTable.hasBaseColumns()) {
253
254
255 String warning = getString(
256 "Warning.18", introspectedTable.getFullyQualifiedTable().toString());
257 warnings.add(warning);
258 iter.remove();
259 } else {
260
261
262
263 reportIntrospectionWarnings(introspectedTable, tc,
264 introspectedTable.getFullyQualifiedTable());
265 }
266 }
267
268 return introspectedTables;
269 }
270
271
272
273
274
275
276
277
278
279 private void removeIgnoredColumns(TableConfiguration tc,
280 Map<ActualTableName, List<IntrospectedColumn>> columns) {
281 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
282 .entrySet()) {
283 Iterator<IntrospectedColumn> tableColumns = entry.getValue()
284 .iterator();
285 while (tableColumns.hasNext()) {
286 IntrospectedColumn introspectedColumn = tableColumns.next();
287 if (tc
288 .isColumnIgnored(introspectedColumn
289 .getActualColumnName())) {
290 tableColumns.remove();
291 if (logger.isDebugEnabled()) {
292 logger.debug(getString("Tracing.3",
293 introspectedColumn.getActualColumnName(), entry
294 .getKey().toString()));
295 }
296 }
297 }
298 }
299 }
300
301
302
303
304
305
306
307
308
309 private void calculateExtraColumnInformation(TableConfiguration tc,
310 Map<ActualTableName, List<IntrospectedColumn>> columns) {
311 StringBuilder sb = new StringBuilder();
312 Pattern pattern = null;
313 String replaceString = null;
314 if (tc.getColumnRenamingRule() != null) {
315 pattern = Pattern.compile(tc.getColumnRenamingRule()
316 .getSearchString());
317 replaceString = tc.getColumnRenamingRule().getReplaceString();
318 replaceString = replaceString == null ? "" : replaceString;
319 }
320
321 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
322 .entrySet()) {
323 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
324 String calculatedColumnName;
325 if (pattern == null) {
326 calculatedColumnName = introspectedColumn
327 .getActualColumnName();
328 } else {
329 Matcher matcher = pattern.matcher(introspectedColumn
330 .getActualColumnName());
331 calculatedColumnName = matcher.replaceAll(replaceString);
332 }
333
334 if (isTrue(tc
335 .getProperty(PropertyRegistry.TABLE_USE_ACTUAL_COLUMN_NAMES))) {
336 introspectedColumn.setJavaProperty(
337 getValidPropertyName(calculatedColumnName));
338 } else if (isTrue(tc
339 .getProperty(PropertyRegistry.TABLE_USE_COMPOUND_PROPERTY_NAMES))) {
340 sb.setLength(0);
341 sb.append(calculatedColumnName);
342 sb.append('_');
343 sb.append(getCamelCaseString(
344 introspectedColumn.getRemarks(), true));
345 introspectedColumn.setJavaProperty(
346 getValidPropertyName(sb.toString()));
347 } else {
348 introspectedColumn.setJavaProperty(
349 getCamelCaseString(calculatedColumnName, false));
350 }
351
352 FullyQualifiedJavaType fullyQualifiedJavaType = javaTypeResolver
353 .calculateJavaType(introspectedColumn);
354
355 if (fullyQualifiedJavaType != null) {
356 introspectedColumn
357 .setFullyQualifiedJavaType(fullyQualifiedJavaType);
358 introspectedColumn.setJdbcTypeName(javaTypeResolver
359 .calculateJdbcTypeName(introspectedColumn));
360 } else {
361
362 boolean warn = true;
363 if (tc.isColumnIgnored(introspectedColumn
364 .getActualColumnName())) {
365 warn = false;
366 }
367
368 ColumnOverride co = tc.getColumnOverride(introspectedColumn
369 .getActualColumnName());
370 if (co != null
371 && stringHasValue(co.getJavaType())
372 && stringHasValue(co.getJavaType())) {
373 warn = false;
374 }
375
376
377 if (warn) {
378 introspectedColumn
379 .setFullyQualifiedJavaType(FullyQualifiedJavaType
380 .getObjectInstance());
381 introspectedColumn.setJdbcTypeName("OTHER");
382
383 String warning = getString("Warning.14",
384 Integer.toString(introspectedColumn.getJdbcType()),
385 entry.getKey().toString(),
386 introspectedColumn.getActualColumnName());
387
388 warnings.add(warning);
389 }
390 }
391
392 if (context.autoDelimitKeywords()
393 && SqlReservedWords.containsWord(introspectedColumn
394 .getActualColumnName())) {
395 introspectedColumn.setColumnNameDelimited(true);
396 }
397
398 if (tc.isAllColumnDelimitingEnabled()) {
399 introspectedColumn.setColumnNameDelimited(true);
400 }
401 }
402 }
403 }
404
405
406
407
408
409
410
411
412
413 private void calculateIdentityColumns(TableConfiguration tc,
414 Map<ActualTableName, List<IntrospectedColumn>> columns) {
415 GeneratedKey gk = tc.getGeneratedKey();
416 if (gk == null) {
417
418 return;
419 }
420
421 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
422 .entrySet()) {
423 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
424 if (isMatchedColumn(introspectedColumn, gk)) {
425 if (gk.isIdentity() || gk.isJdbcStandard()) {
426 introspectedColumn.setIdentity(true);
427 introspectedColumn.setSequenceColumn(false);
428 } else {
429 introspectedColumn.setIdentity(false);
430 introspectedColumn.setSequenceColumn(true);
431 }
432 }
433 }
434 }
435 }
436
437
438
439
440
441
442
443
444
445
446 private boolean isMatchedColumn(IntrospectedColumn introspectedColumn, GeneratedKey gk) {
447 if (introspectedColumn.isColumnNameDelimited()) {
448 return introspectedColumn.getActualColumnName().equals(gk.getColumn());
449 } else {
450 return introspectedColumn.getActualColumnName().equalsIgnoreCase(gk.getColumn());
451 }
452 }
453
454
455
456
457
458
459
460
461
462 private void applyColumnOverrides(TableConfiguration tc,
463 Map<ActualTableName, List<IntrospectedColumn>> columns) {
464 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
465 .entrySet()) {
466 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
467 ColumnOverride columnOverride = tc
468 .getColumnOverride(introspectedColumn
469 .getActualColumnName());
470
471 if (columnOverride != null) {
472 if (logger.isDebugEnabled()) {
473 logger.debug(getString("Tracing.4",
474 introspectedColumn.getActualColumnName(), entry
475 .getKey().toString()));
476 }
477
478 if (stringHasValue(columnOverride
479 .getJavaProperty())) {
480 introspectedColumn.setJavaProperty(columnOverride
481 .getJavaProperty());
482 }
483
484 if (stringHasValue(columnOverride
485 .getJavaType())) {
486 introspectedColumn
487 .setFullyQualifiedJavaType(new FullyQualifiedJavaType(
488 columnOverride.getJavaType()));
489 }
490
491 if (stringHasValue(columnOverride
492 .getJdbcType())) {
493 introspectedColumn.setJdbcTypeName(columnOverride
494 .getJdbcType());
495 }
496
497 if (stringHasValue(columnOverride
498 .getTypeHandler())) {
499 introspectedColumn.setTypeHandler(columnOverride
500 .getTypeHandler());
501 }
502
503 if (columnOverride.isColumnNameDelimited()) {
504 introspectedColumn.setColumnNameDelimited(true);
505 }
506
507 introspectedColumn.setProperties(columnOverride
508 .getProperties());
509 }
510 }
511 }
512 }
513
514
515
516
517
518
519
520
521
522
523
524 private Map<ActualTableName, List<IntrospectedColumn>> getColumns(
525 TableConfiguration tc) throws SQLException {
526 String localCatalog;
527 String localSchema;
528 String localTableName;
529
530 boolean delimitIdentifiers = tc.isDelimitIdentifiers()
531 || stringContainsSpace(tc.getCatalog())
532 || stringContainsSpace(tc.getSchema())
533 || stringContainsSpace(tc.getTableName());
534
535 if (delimitIdentifiers) {
536 localCatalog = tc.getCatalog();
537 localSchema = tc.getSchema();
538 localTableName = tc.getTableName();
539 } else if (databaseMetaData.storesLowerCaseIdentifiers()) {
540 localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
541 .toLowerCase();
542 localSchema = tc.getSchema() == null ? null : tc.getSchema()
543 .toLowerCase();
544 localTableName = tc.getTableName() == null ? null : tc
545 .getTableName().toLowerCase();
546 } else if (databaseMetaData.storesUpperCaseIdentifiers()) {
547 localCatalog = tc.getCatalog() == null ? null : tc.getCatalog()
548 .toUpperCase();
549 localSchema = tc.getSchema() == null ? null : tc.getSchema()
550 .toUpperCase();
551 localTableName = tc.getTableName() == null ? null : tc
552 .getTableName().toUpperCase();
553 } else {
554 localCatalog = tc.getCatalog();
555 localSchema = tc.getSchema();
556 localTableName = tc.getTableName();
557 }
558
559 if (tc.isWildcardEscapingEnabled()) {
560 String escapeString = databaseMetaData.getSearchStringEscape();
561
562 StringBuilder sb = new StringBuilder();
563 StringTokenizer st;
564 if (localSchema != null) {
565 st = new StringTokenizer(localSchema, "_%", true);
566 while (st.hasMoreTokens()) {
567 String token = st.nextToken();
568 if (token.equals("_")
569 || token.equals("%")) {
570 sb.append(escapeString);
571 }
572 sb.append(token);
573 }
574 localSchema = sb.toString();
575 }
576
577 sb.setLength(0);
578 st = new StringTokenizer(localTableName, "_%", true);
579 while (st.hasMoreTokens()) {
580 String token = st.nextToken();
581 if (token.equals("_")
582 || token.equals("%")) {
583 sb.append(escapeString);
584 }
585 sb.append(token);
586 }
587 localTableName = sb.toString();
588 }
589
590 Map<ActualTableName, List<IntrospectedColumn>> answer = new HashMap<ActualTableName, List<IntrospectedColumn>>();
591
592 if (logger.isDebugEnabled()) {
593 String fullTableName = composeFullyQualifiedTableName(localCatalog, localSchema,
594 localTableName, '.');
595 logger.debug(getString("Tracing.1", fullTableName));
596 }
597
598 ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema,
599 localTableName, null);
600
601 while (rs.next()) {
602 IntrospectedColumn introspectedColumn = ObjectFactory
603 .createIntrospectedColumn(context);
604
605 introspectedColumn.setTableAlias(tc.getAlias());
606 introspectedColumn.setJdbcType(rs.getInt("DATA_TYPE"));
607 introspectedColumn.setLength(rs.getInt("COLUMN_SIZE"));
608 introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME"));
609 introspectedColumn
610 .setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
611 introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS"));
612 introspectedColumn.setRemarks(rs.getString("REMARKS"));
613 introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF"));
614
615 ActualTableName atn = new ActualTableName(
616 rs.getString("TABLE_CAT"),
617 rs.getString("TABLE_SCHEM"),
618 rs.getString("TABLE_NAME"));
619
620 List<IntrospectedColumn> columns = answer.get(atn);
621 if (columns == null) {
622 columns = new ArrayList<IntrospectedColumn>();
623 answer.put(atn, columns);
624 }
625
626 columns.add(introspectedColumn);
627
628 if (logger.isDebugEnabled()) {
629 logger.debug(getString(
630 "Tracing.2",
631 introspectedColumn.getActualColumnName(), Integer
632 .toString(introspectedColumn.getJdbcType()),
633 atn.toString()));
634 }
635 }
636
637 closeResultSet(rs);
638
639 if (answer.size() > 1
640 && !stringContainsSQLWildcard(localSchema)
641 && !stringContainsSQLWildcard(localTableName)) {
642
643
644 ActualTableName inputAtn = new ActualTableName(tc.getCatalog(), tc
645 .getSchema(), tc.getTableName());
646
647 StringBuilder sb = new StringBuilder();
648 boolean comma = false;
649 for (ActualTableName atn : answer.keySet()) {
650 if (comma) {
651 sb.append(',');
652 } else {
653 comma = true;
654 }
655 sb.append(atn.toString());
656 }
657
658 warnings.add(getString("Warning.25",
659 inputAtn.toString(), sb.toString()));
660 }
661
662 return answer;
663 }
664
665
666
667
668
669
670
671
672
673
674 private List<IntrospectedTable> calculateIntrospectedTables(
675 TableConfiguration tc,
676 Map<ActualTableName, List<IntrospectedColumn>> columns) {
677 boolean delimitIdentifiers = tc.isDelimitIdentifiers()
678 || stringContainsSpace(tc.getCatalog())
679 || stringContainsSpace(tc.getSchema())
680 || stringContainsSpace(tc.getTableName());
681
682 List<IntrospectedTable> answer = new ArrayList<IntrospectedTable>();
683
684 for (Map.Entry<ActualTableName, List<IntrospectedColumn>> entry : columns
685 .entrySet()) {
686 ActualTableName atn = entry.getKey();
687
688
689
690
691
692
693
694
695 FullyQualifiedTable table = new FullyQualifiedTable(
696 stringHasValue(tc.getCatalog()) ? atn
697 .getCatalog() : null,
698 stringHasValue(tc.getSchema()) ? atn
699 .getSchema() : null,
700 atn.getTableName(),
701 tc.getDomainObjectName(),
702 tc.getAlias(),
703 isTrue(tc.getProperty(PropertyRegistry.TABLE_IGNORE_QUALIFIERS_AT_RUNTIME)),
704 tc.getProperty(PropertyRegistry.TABLE_RUNTIME_CATALOG),
705 tc.getProperty(PropertyRegistry.TABLE_RUNTIME_SCHEMA),
706 tc.getProperty(PropertyRegistry.TABLE_RUNTIME_TABLE_NAME),
707 delimitIdentifiers, context);
708
709 IntrospectedTable introspectedTable = ObjectFactory
710 .createIntrospectedTable(tc, table, context);
711
712 for (IntrospectedColumn introspectedColumn : entry.getValue()) {
713 introspectedTable.addColumn(introspectedColumn);
714 }
715
716 calculatePrimaryKey(table, introspectedTable);
717
718 enhanceIntrospectedTable(introspectedTable);
719
720 answer.add(introspectedTable);
721 }
722
723 return answer;
724 }
725
726
727
728
729
730
731
732
733
734 private void enhanceIntrospectedTable(IntrospectedTable introspectedTable) {
735 try {
736 FullyQualifiedTable fqt = introspectedTable.getFullyQualifiedTable();
737
738 ResultSet rs = databaseMetaData.getTables(fqt.getIntrospectedCatalog(), fqt.getIntrospectedSchema(),
739 fqt.getIntrospectedTableName(), null);
740 if (rs.next()) {
741 String remarks = rs.getString("REMARKS");
742 String tableType = rs.getString("TABLE_TYPE");
743 introspectedTable.setRemarks(remarks);
744 introspectedTable.setTableType(tableType);
745 }
746 closeResultSet(rs);
747 } catch (SQLException e) {
748 warnings.add(getString("Warning.27", e.getMessage()));
749 }
750 }
751 }