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