View Javadoc
1   /*
2    *    Copyright 2006-2023 the original author or authors.
3    *
4    *    Licensed under the Apache License, Version 2.0 (the "License");
5    *    you may not use this file except in compliance with the License.
6    *    You may obtain a copy of the License at
7    *
8    *       https://www.apache.org/licenses/LICENSE-2.0
9    *
10   *    Unless required by applicable law or agreed to in writing, software
11   *    distributed under the License is distributed on an "AS IS" BASIS,
12   *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   *    See the License for the specific language governing permissions and
14   *    limitations under the License.
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")); //$NON-NLS-1$
90              return;
91          }
92  
93          try {
94              // keep primary columns in key sequence order
95              Map<Short, String> keyColumns = new TreeMap<>();
96              while (rs.next()) {
97                  String columnName = rs.getString("COLUMN_NAME"); //$NON-NLS-1$
98                  short keySeq = rs.getShort("KEY_SEQ"); //$NON-NLS-1$
99                  keyColumns.put(keySeq, columnName);
100             }
101 
102             for (String columnName : keyColumns.values()) {
103                 introspectedTable.addPrimaryKeyColumn(columnName);
104             }
105         } catch (SQLException e) {
106             // ignore the primary key if there's any error
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                 // ignore
118             }
119         }
120     }
121 
122     private void reportIntrospectionWarnings(
123             IntrospectedTable introspectedTable,
124             TableConfiguration tableConfiguration, FullyQualifiedTable table) {
125         // make sure that every column listed in column overrides
126         // actually exists in the table
127         for (ColumnOverride columnOverride : tableConfiguration
128                 .getColumnOverrides()) {
129             if (!introspectedTable.getColumn(columnOverride.getColumnName()).isPresent()) {
130                 warnings.add(getString("Warning.3", //$NON-NLS-1$
131                         columnOverride.getColumnName(), table.toString()));
132             }
133         }
134 
135         // make sure that every column listed in ignored columns
136         // actually exists in the table
137         for (String string : tableConfiguration.getIgnoredColumnsInError()) {
138             warnings.add(getString("Warning.4", //$NON-NLS-1$
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", //$NON-NLS-1$
146                             generatedKey.getColumn(), table.toString()));
147                 } else {
148                     warnings.add(getString("Warning.6", //$NON-NLS-1$
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", //$NON-NLS-1$
157                         ic.getActualColumnName(), table.toString()));
158             }
159         }
160     }
161 
162     /**
163      * Returns a List of IntrospectedTable elements that matches the specified table configuration.
164      *
165      * @param tc
166      *            the table configuration
167      * @return a list of introspected tables
168      * @throws SQLException
169      *             if any errors in introspection
170      */
171     public List<IntrospectedTable> introspectTables(TableConfiguration tc)
172             throws SQLException {
173 
174         // get the raw columns from the DB
175         Map<ActualTableName, List<IntrospectedColumn>> columns = getColumns(tc);
176 
177         if (columns.isEmpty()) {
178             warnings.add(getString("Warning.19", tc.getCatalog(), //$NON-NLS-1$
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         // now introspectedTables has all the columns from all the
192         // tables in the configuration. Do some validation...
193 
194         Iterator<IntrospectedTable> iter = introspectedTables.iterator();
195         while (iter.hasNext()) {
196             IntrospectedTable introspectedTable = iter.next();
197 
198             if (!introspectedTable.hasAnyColumns()) {
199                 // add warning that the table has no columns, remove from the
200                 // list
201                 String warning = getString(
202                                 "Warning.1", introspectedTable.getFullyQualifiedTable().toString()); //$NON-NLS-1$
203                 warnings.add(warning);
204                 iter.remove();
205             } else if (!introspectedTable.hasPrimaryKeyColumns()
206                     && !introspectedTable.hasBaseColumns()) {
207                 // add warning that the table has only BLOB columns, remove from
208                 // the list
209                 String warning = getString(
210                                 "Warning.18", introspectedTable.getFullyQualifiedTable().toString()); //$NON-NLS-1$
211                 warnings.add(warning);
212                 iter.remove();
213             } else {
214                 // now make sure that all columns called out in the
215                 // configuration
216                 // actually exist
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", //$NON-NLS-1$
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; //$NON-NLS-1$
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                     // type cannot be resolved. Check for ignored or overridden
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                     // if the type is not supported, then we'll report a warning
310                     if (warn) {
311                         introspectedColumn
312                                 .setFullyQualifiedJavaType(FullyQualifiedJavaType
313                                         .getObjectInstance());
314                         introspectedColumn.setJdbcTypeName("OTHER"); //$NON-NLS-1$
315 
316                         String warning = getString("Warning.14", //$NON-NLS-1$
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", //$NON-NLS-1$
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)); //$NON-NLS-1$
469         }
470 
471         ResultSet rs = databaseMetaData.getColumns(localCatalog, localSchema,
472                 localTableName, "%"); //$NON-NLS-1$
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))) { //$NON-NLS-1$
480                 supportsIsAutoIncrement = true;
481             }
482             if ("IS_GENERATEDCOLUMN".equals(rsmd.getColumnName(i))) { //$NON-NLS-1$
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")); //$NON-NLS-1$
493             introspectedColumn.setActualTypeName(rs.getString("TYPE_NAME")); //$NON-NLS-1$
494             introspectedColumn.setLength(rs.getInt("COLUMN_SIZE")); //$NON-NLS-1$
495             introspectedColumn.setActualColumnName(rs.getString("COLUMN_NAME")); //$NON-NLS-1$
496             introspectedColumn
497                     .setNullable(rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable); //$NON-NLS-1$
498             introspectedColumn.setScale(rs.getInt("DECIMAL_DIGITS")); //$NON-NLS-1$
499             introspectedColumn.setRemarks(rs.getString("REMARKS")); //$NON-NLS-1$
500             introspectedColumn.setDefaultValue(rs.getString("COLUMN_DEF")); //$NON-NLS-1$
501 
502             if (supportsIsAutoIncrement) {
503                 introspectedColumn.setAutoIncrement(
504                         "YES".equals(rs.getString("IS_AUTOINCREMENT"))); //$NON-NLS-1$ //$NON-NLS-2$
505             }
506 
507             if (supportsIsGeneratedColumn) {
508                 introspectedColumn.setGeneratedColumn(
509                         "YES".equals(rs.getString("IS_GENERATEDCOLUMN"))); //$NON-NLS-1$ //$NON-NLS-2$
510             }
511 
512             ActualTableName atn = new ActualTableName(
513                     rs.getString("TABLE_CAT"), //$NON-NLS-1$
514                     rs.getString("TABLE_SCHEM"), //$NON-NLS-1$
515                     rs.getString("TABLE_NAME")); //$NON-NLS-1$
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", //$NON-NLS-1$
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             // issue a warning if there is more than one table and
536             // no wildcards were used
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", //$NON-NLS-1$
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); //$NON-NLS-1$
560         StringBuilder sb = new StringBuilder();
561         while (st.hasMoreTokens()) {
562             String token = st.nextToken();
563             if (token.equals("_") //$NON-NLS-1$
564                     || token.equals("%")) { //$NON-NLS-1$
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             // we only use the returned catalog and schema if something was
587             // actually
588             // specified on the table configuration. If something was returned
589             // from the DB for these fields, but nothing was specified on the
590             // table
591             // configuration, then some sort of DB default is being returned
592             // and we don't want that in our SQL
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      * Calls database metadata to retrieve extra information about the table
626      * such as remarks associated with the table and the type.
627      *
628      * <p>If there is any error, we just add a warning and continue.
629      *
630      * @param introspectedTable the introspected table to enhance
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"); //$NON-NLS-1$
640                 String tableType = rs.getString("TABLE_TYPE"); //$NON-NLS-1$
641                 introspectedTable.setRemarks(remarks);
642                 introspectedTable.setTableType(tableType);
643             }
644             closeResultSet(rs);
645         } catch (SQLException e) {
646             warnings.add(getString("Warning.27", e.getMessage())); //$NON-NLS-1$
647         }
648     }
649 }