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