You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TableInspector.java 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716
  1. /*
  2. * Copyright 2004-2011 H2 Group.
  3. * Copyright 2011 James Moger.
  4. * Copyright 2012 Frédéric Gaillard.
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. package com.iciql;
  19. import com.iciql.Iciql.IQColumn;
  20. import com.iciql.Iciql.IQIndex;
  21. import com.iciql.Iciql.IQIndexes;
  22. import com.iciql.Iciql.IQSchema;
  23. import com.iciql.Iciql.IQTable;
  24. import com.iciql.Iciql.IndexType;
  25. import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
  26. import com.iciql.TableDefinition.ConstraintUniqueDefinition;
  27. import com.iciql.TableDefinition.FieldDefinition;
  28. import com.iciql.TableDefinition.IndexDefinition;
  29. import com.iciql.util.StatementBuilder;
  30. import com.iciql.util.StringUtils;
  31. import com.iciql.util.Utils;
  32. import java.io.Serializable;
  33. import java.lang.reflect.Modifier;
  34. import java.sql.DatabaseMetaData;
  35. import java.sql.ResultSet;
  36. import java.sql.SQLException;
  37. import java.util.ArrayList;
  38. import java.util.Collections;
  39. import java.util.Date;
  40. import java.util.List;
  41. import java.util.Map;
  42. import java.util.Set;
  43. import static com.iciql.ValidationRemark.*;
  44. import static com.iciql.util.JdbcUtils.closeSilently;
  45. import static com.iciql.util.StringUtils.isNullOrEmpty;
  46. import static java.text.MessageFormat.format;
  47. /**
  48. * Class to inspect the contents of a particular table including its indexes.
  49. * This class does the bulk of the work in terms of model generation and model
  50. * validation.
  51. */
  52. public class TableInspector {
  53. private String schema;
  54. private String table;
  55. private Class<? extends java.util.Date> dateTimeClass;
  56. private List<String> primaryKeys = Utils.newArrayList();
  57. private Map<String, IndexInspector> indexes;
  58. private Map<String, ColumnInspector> columns;
  59. private final String eol = "\n";
  60. TableInspector(String schema, String table, Class<? extends java.util.Date> dateTimeClass) {
  61. this.schema = schema;
  62. this.table = table;
  63. this.dateTimeClass = dateTimeClass;
  64. }
  65. /**
  66. * Tests to see if this TableInspector represents schema.table.
  67. * <p>
  68. *
  69. * @param schema the schema name
  70. * @param table the table name
  71. * @return true if the table matches
  72. */
  73. boolean matches(String schema, String table) {
  74. if (isNullOrEmpty(schema)) {
  75. // table name matching
  76. return this.table.equalsIgnoreCase(table);
  77. } else if (isNullOrEmpty(table)) {
  78. // schema name matching
  79. return this.schema.equalsIgnoreCase(schema);
  80. } else {
  81. // exact table matching
  82. return this.schema.equalsIgnoreCase(schema) && this.table.equalsIgnoreCase(table);
  83. }
  84. }
  85. /**
  86. * Reads the DatabaseMetaData for the details of this table including
  87. * primary keys and indexes.
  88. *
  89. * @param metaData the database meta data
  90. */
  91. void read(DatabaseMetaData metaData) throws SQLException {
  92. ResultSet rs = null;
  93. // primary keys
  94. try {
  95. rs = metaData.getPrimaryKeys(null, schema, table);
  96. while (rs.next()) {
  97. String c = rs.getString("COLUMN_NAME");
  98. primaryKeys.add(c);
  99. }
  100. closeSilently(rs);
  101. // indexes
  102. rs = metaData.getIndexInfo(null, schema, table, false, true);
  103. indexes = Utils.newHashMap();
  104. while (rs.next()) {
  105. IndexInspector info = new IndexInspector(rs);
  106. if (info.type.equals(IndexType.UNIQUE)) {
  107. String name = info.name.toLowerCase();
  108. if (name.startsWith("primary") || name.startsWith("sys_idx_sys_pk")
  109. || name.startsWith("sql") || name.endsWith("_pkey")) {
  110. // skip primary key indexes
  111. continue;
  112. }
  113. }
  114. if (indexes.containsKey(info.name)) {
  115. indexes.get(info.name).addColumn(rs);
  116. } else {
  117. indexes.put(info.name, info);
  118. }
  119. }
  120. closeSilently(rs);
  121. // columns
  122. rs = metaData.getColumns(null, schema, table, null);
  123. columns = Utils.newHashMap();
  124. while (rs.next()) {
  125. ColumnInspector col = new ColumnInspector();
  126. col.name = rs.getString("COLUMN_NAME");
  127. col.type = rs.getString("TYPE_NAME");
  128. col.clazz = ModelUtils.getClassForSqlType(col.type, dateTimeClass);
  129. col.size = rs.getInt("COLUMN_SIZE");
  130. col.nullable = rs.getInt("NULLABLE") == DatabaseMetaData.columnNullable;
  131. try {
  132. Object autoIncrement = rs.getObject("IS_AUTOINCREMENT");
  133. if (autoIncrement instanceof Boolean) {
  134. col.isAutoIncrement = (Boolean) autoIncrement;
  135. } else if (autoIncrement instanceof String) {
  136. String val = autoIncrement.toString().toLowerCase();
  137. col.isAutoIncrement = val.equals("true") | val.equals("yes");
  138. } else if (autoIncrement instanceof Number) {
  139. Number n = (Number) autoIncrement;
  140. col.isAutoIncrement = n.intValue() > 0;
  141. }
  142. } catch (SQLException s) {
  143. // throw s;
  144. }
  145. if (primaryKeys.size() == 1) {
  146. if (col.name.equalsIgnoreCase(primaryKeys.get(0))) {
  147. col.isPrimaryKey = true;
  148. }
  149. }
  150. if (!col.isAutoIncrement) {
  151. col.defaultValue = rs.getString("COLUMN_DEF");
  152. }
  153. columns.put(col.name.toLowerCase(), col);
  154. }
  155. } finally {
  156. closeSilently(rs);
  157. }
  158. }
  159. /**
  160. * Generates a model (class definition) from this table. The model includes
  161. * indexes, primary keys, default values, lengths, and nullables.
  162. * information.
  163. * <p>
  164. * The caller may optionally set a destination package name, whether or not
  165. * to include the schema name (setting schema can be a problem when using
  166. * the model between databases), and if to automatically trim strings for
  167. * those that have a maximum length.
  168. * <p>
  169. *
  170. * @param packageName
  171. * @param annotateSchema
  172. * @param trimStrings
  173. * @return a complete model (class definition) for this table as a string
  174. */
  175. String generateModel(String packageName, boolean annotateSchema, boolean trimStrings) {
  176. // import statements
  177. Set<String> imports = Utils.newHashSet();
  178. imports.add(Serializable.class.getCanonicalName());
  179. imports.add(IQSchema.class.getCanonicalName());
  180. imports.add(IQTable.class.getCanonicalName());
  181. imports.add(IQIndexes.class.getCanonicalName());
  182. imports.add(IQIndex.class.getCanonicalName());
  183. imports.add(IQColumn.class.getCanonicalName());
  184. imports.add(IndexType.class.getCanonicalName());
  185. // fields
  186. StringBuilder fields = new StringBuilder();
  187. List<ColumnInspector> sortedColumns = Utils.newArrayList(columns.values());
  188. Collections.sort(sortedColumns);
  189. for (ColumnInspector col : sortedColumns) {
  190. fields.append(generateColumn(imports, col, trimStrings));
  191. }
  192. // build complete class definition
  193. StringBuilder model = new StringBuilder();
  194. if (!isNullOrEmpty(packageName)) {
  195. // package
  196. model.append("package " + packageName + ";");
  197. model.append(eol).append(eol);
  198. }
  199. // imports
  200. List<String> sortedImports = new ArrayList<String>(imports);
  201. Collections.sort(sortedImports);
  202. for (String imp : sortedImports) {
  203. model.append("import ").append(imp).append(';').append(eol);
  204. }
  205. model.append(eol);
  206. // @IQSchema
  207. if (annotateSchema && !isNullOrEmpty(schema)) {
  208. model.append('@').append(IQSchema.class.getSimpleName());
  209. model.append('(');
  210. AnnotationBuilder ap = new AnnotationBuilder();
  211. ap.addParameter(null, schema);
  212. model.append(ap);
  213. model.append(')').append(eol);
  214. }
  215. // @IQTable
  216. model.append('@').append(IQTable.class.getSimpleName());
  217. model.append('(');
  218. // IQTable annotation parameters
  219. AnnotationBuilder ap = new AnnotationBuilder();
  220. ap.addParameter("name", table);
  221. if (primaryKeys.size() > 1) {
  222. ap.addParameter("primaryKey", primaryKeys);
  223. }
  224. // finish @IQTable annotation
  225. model.append(ap);
  226. model.append(')').append(eol);
  227. // @IQIndexes
  228. // @IQIndex
  229. String indexAnnotations = generateIndexAnnotations();
  230. if (!StringUtils.isNullOrEmpty(indexAnnotations)) {
  231. model.append(indexAnnotations);
  232. }
  233. // class declaration
  234. String clazzName = ModelUtils.convertTableToClassName(table);
  235. model.append(format("public class {0} implements Serializable '{'", clazzName)).append(eol);
  236. model.append(eol);
  237. model.append("\tprivate static final long serialVersionUID = 1L;").append(eol);
  238. model.append(eol);
  239. // field declarations
  240. model.append(fields);
  241. // default constructor
  242. model.append("\t" + "public ").append(clazzName).append("() {").append(eol);
  243. model.append("\t}").append(eol);
  244. // end of class body
  245. model.append('}');
  246. model.trimToSize();
  247. return model.toString();
  248. }
  249. /**
  250. * Generates the specified index annotation.
  251. *
  252. * @param ap
  253. */
  254. String generateIndexAnnotations() {
  255. if (indexes == null || indexes.size() == 0) {
  256. // no matching indexes
  257. return null;
  258. }
  259. AnnotationBuilder ap = new AnnotationBuilder();
  260. if (indexes.size() == 1) {
  261. // single index
  262. IndexInspector index = indexes.values().toArray(new IndexInspector[1])[0];
  263. ap.append(generateIndexAnnotation(index));
  264. ap.append(eol);
  265. } else {
  266. // multiple indexes
  267. ap.append('@').append(IQIndexes.class.getSimpleName());
  268. ap.append("({");
  269. ap.resetCount();
  270. for (IndexInspector index : indexes.values()) {
  271. ap.appendExceptFirst(", ");
  272. ap.append(generateIndexAnnotation(index));
  273. }
  274. ap.append("})").append(eol);
  275. }
  276. return ap.toString();
  277. }
  278. private String generateIndexAnnotation(IndexInspector index) {
  279. AnnotationBuilder ap = new AnnotationBuilder();
  280. ap.append('@').append(IQIndex.class.getSimpleName());
  281. ap.append('(');
  282. ap.resetCount();
  283. if (!StringUtils.isNullOrEmpty(index.name)) {
  284. ap.addParameter("name", index.name);
  285. }
  286. if (!index.type.equals(IndexType.STANDARD)) {
  287. ap.addEnum("type", index.type);
  288. }
  289. if (ap.getCount() > 0) {
  290. // multiple fields specified
  291. ap.addParameter("value", index.columns);
  292. } else {
  293. // default value
  294. ap.addParameter(null, index.columns);
  295. }
  296. ap.append(')');
  297. return ap.toString();
  298. }
  299. private StatementBuilder generateColumn(Set<String> imports, ColumnInspector col, boolean trimStrings) {
  300. StatementBuilder sb = new StatementBuilder();
  301. Class<?> clazz = col.clazz;
  302. String column = ModelUtils.convertColumnToFieldName(col.name.toLowerCase());
  303. sb.append('\t');
  304. if (clazz == null) {
  305. // unsupported type
  306. clazz = Object.class;
  307. sb.append("// unsupported type " + col.type);
  308. } else {
  309. // Imports
  310. // don't import primitives, java.lang classes, or byte []
  311. if (clazz.getPackage() == null) {
  312. } else if (clazz.getPackage().getName().equals("java.lang")) {
  313. } else if (clazz.equals(byte[].class)) {
  314. } else {
  315. imports.add(clazz.getCanonicalName());
  316. }
  317. // @IQColumn
  318. sb.append('@').append(IQColumn.class.getSimpleName());
  319. // IQColumn annotation parameters
  320. AnnotationBuilder ap = new AnnotationBuilder();
  321. // IQColumn.name
  322. if (!col.name.equalsIgnoreCase(column)) {
  323. ap.addParameter("name", col.name);
  324. }
  325. // IQColumn.primaryKey
  326. // composite primary keys are annotated on the table
  327. if (col.isPrimaryKey && primaryKeys.size() == 1) {
  328. ap.addParameter("primaryKey=true");
  329. }
  330. // IQColumn.length
  331. if ((clazz == String.class) && (col.size > 0) && (col.size < Integer.MAX_VALUE)) {
  332. ap.addParameter("length", col.size);
  333. // IQColumn.trim
  334. if (trimStrings) {
  335. ap.addParameter("trim=true");
  336. }
  337. } else {
  338. // IQColumn.AutoIncrement
  339. if (col.isAutoIncrement) {
  340. ap.addParameter("autoIncrement=true");
  341. }
  342. }
  343. // IQColumn.nullable
  344. if (!col.nullable) {
  345. ap.addParameter("nullable=false");
  346. }
  347. // IQColumn.defaultValue
  348. if (!isNullOrEmpty(col.defaultValue)) {
  349. ap.addParameter("defaultValue=\"" + col.defaultValue + "\"");
  350. }
  351. // add leading and trailing ()
  352. if (ap.length() > 0) {
  353. ap.insert(0, '(');
  354. ap.append(')');
  355. }
  356. sb.append(ap);
  357. }
  358. sb.append(eol);
  359. // variable declaration
  360. sb.append("\t" + "public ");
  361. sb.append(clazz.getSimpleName());
  362. sb.append(' ');
  363. sb.append(column);
  364. sb.append(';');
  365. sb.append(eol).append(eol);
  366. return sb;
  367. }
  368. /**
  369. * Validates that a table definition (annotated, interface, or both) matches
  370. * the current state of the table and indexes in the database. Results are
  371. * returned as a list of validation remarks which includes recommendations,
  372. * warnings, and errors about the model. The caller may choose to have
  373. * validate throw an exception on any validation ERROR.
  374. *
  375. * @param def the table definition
  376. * @param throwError whether or not to throw an exception if an error was found
  377. * @return a list if validation remarks
  378. */
  379. <T> List<ValidationRemark> validate(TableDefinition<T> def, boolean throwError) {
  380. List<ValidationRemark> remarks = Utils.newArrayList();
  381. // model class definition validation
  382. if (!Modifier.isPublic(def.getModelClass().getModifiers())) {
  383. remarks.add(error(table, "SCHEMA",
  384. format("Class {0} MUST BE PUBLIC!", def.getModelClass().getCanonicalName())).throwError(
  385. throwError));
  386. }
  387. // Schema Validation
  388. if (!isNullOrEmpty(schema)) {
  389. if (isNullOrEmpty(def.schemaName)) {
  390. remarks.add(consider(table, "SCHEMA",
  391. format("@{0}(\"{1}\")", IQSchema.class.getSimpleName(), schema)));
  392. } else if (!schema.equalsIgnoreCase(def.schemaName)) {
  393. remarks.add(error(
  394. table,
  395. "SCHEMA",
  396. format("@{0}(\"{1}\") != {2}", IQSchema.class.getSimpleName(), def.schemaName, schema))
  397. .throwError(throwError));
  398. }
  399. }
  400. // index validation
  401. for (IndexInspector index : indexes.values()) {
  402. validate(remarks, def, index, throwError);
  403. }
  404. // field column validation
  405. for (FieldDefinition fieldDef : def.getFields()) {
  406. validate(remarks, fieldDef, throwError);
  407. }
  408. return remarks;
  409. }
  410. /**
  411. * Validates an inspected index from the database against the
  412. * IndexDefinition within the TableDefinition.
  413. */
  414. private <T> void validate(List<ValidationRemark> remarks, TableDefinition<T> def, IndexInspector index,
  415. boolean throwError) {
  416. List<IndexDefinition> defIndexes = def.getIndexes();
  417. if (defIndexes.size() > indexes.size()) {
  418. remarks.add(warn(table, IndexType.STANDARD.name(), "More model indexes than database indexes"));
  419. } else if (defIndexes.size() < indexes.size()) {
  420. remarks.add(warn(table, IndexType.STANDARD.name(), "Model class is missing indexes"));
  421. }
  422. // TODO complete index validation.
  423. // need to actually compare index types and columns within each index.
  424. // TODO add constraints validation
  425. List<ConstraintUniqueDefinition> defContraintsU = def.getContraintsUnique();
  426. List<ConstraintForeignKeyDefinition> defContraintsFK = def.getContraintsForeignKey();
  427. }
  428. /**
  429. * Validates a column against the model's field definition. Checks for
  430. * existence, supported type, type mapping, default value, defined lengths,
  431. * primary key, autoincrement.
  432. */
  433. private void validate(List<ValidationRemark> remarks, FieldDefinition fieldDef, boolean throwError) {
  434. // unknown field
  435. if (!columns.containsKey(fieldDef.columnName.toLowerCase())) {
  436. // unknown column mapping
  437. remarks.add(error(table, fieldDef, "Does not exist in database!").throwError(throwError));
  438. return;
  439. }
  440. ColumnInspector col = columns.get(fieldDef.columnName.toLowerCase());
  441. Class<?> fieldClass = fieldDef.field.getType();
  442. Class<?> jdbcClass = ModelUtils.getClassForSqlType(col.type, dateTimeClass);
  443. // supported type check
  444. // iciql maps to VARCHAR for unsupported types.
  445. if (fieldDef.dataType.equals("VARCHAR") && (fieldClass != String.class)) {
  446. remarks.add(error(table, fieldDef,
  447. "iciql does not currently implement support for " + fieldClass.getName()).throwError(
  448. throwError));
  449. }
  450. // number types
  451. if (!fieldClass.equals(jdbcClass)) {
  452. if (Number.class.isAssignableFrom(fieldClass)) {
  453. remarks.add(warn(
  454. table,
  455. col,
  456. format("Precision mismatch: ModelObject={0}, ColumnObject={1}",
  457. fieldClass.getSimpleName(), jdbcClass.getSimpleName())));
  458. } else {
  459. if (!Date.class.isAssignableFrom(jdbcClass)) {
  460. remarks.add(warn(
  461. table,
  462. col,
  463. format("Object Mismatch: ModelObject={0}, ColumnObject={1}",
  464. fieldClass.getSimpleName(), jdbcClass.getSimpleName())));
  465. }
  466. }
  467. }
  468. // string types
  469. if (fieldClass == String.class) {
  470. if ((fieldDef.length != col.size) && (col.size < Integer.MAX_VALUE)) {
  471. remarks.add(warn(
  472. table,
  473. col,
  474. format("{0}.length={1}, ColumnMaxLength={2}", IQColumn.class.getSimpleName(),
  475. fieldDef.length, col.size)));
  476. }
  477. if (fieldDef.length > 0 && !fieldDef.trim) {
  478. remarks.add(consider(table, col, format("{0}.trim=true will prevent IciqlExceptions on"
  479. + " INSERT or UPDATE, but will clip data!", IQColumn.class.getSimpleName())));
  480. }
  481. }
  482. // numeric autoIncrement
  483. if (fieldDef.isAutoIncrement != col.isAutoIncrement) {
  484. remarks.add(warn(
  485. table,
  486. col,
  487. format("{0}.autoIncrement={1}" + " while Column autoIncrement={2}",
  488. IQColumn.class.getSimpleName(), fieldDef.isAutoIncrement, col.isAutoIncrement)));
  489. }
  490. // default value
  491. if (!col.isAutoIncrement && !col.isPrimaryKey) {
  492. String defaultValue = null;
  493. if (fieldDef.defaultValue != null && fieldDef.defaultValue instanceof String) {
  494. defaultValue = fieldDef.defaultValue.toString();
  495. }
  496. // check Model.defaultValue format
  497. if (!ModelUtils.isProperlyFormattedDefaultValue(defaultValue)) {
  498. remarks.add(error(
  499. table,
  500. col,
  501. format("{0}.defaultValue=\"{1}\"" + " is improperly formatted!",
  502. IQColumn.class.getSimpleName(), defaultValue)).throwError(throwError));
  503. // next field
  504. return;
  505. }
  506. // compare Model.defaultValue to Column.defaultValue
  507. if (isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) {
  508. // Model.defaultValue is NULL, Column.defaultValue is NOT NULL
  509. remarks.add(warn(
  510. table,
  511. col,
  512. format("{0}.defaultValue=\"\"" + " while column default=\"{1}\"",
  513. IQColumn.class.getSimpleName(), col.defaultValue)));
  514. } else if (!isNullOrEmpty(defaultValue) && isNullOrEmpty(col.defaultValue)) {
  515. // Column.defaultValue is NULL, Model.defaultValue is NOT NULL
  516. remarks.add(warn(
  517. table,
  518. col,
  519. format("{0}.defaultValue=\"{1}\"" + " while column default=\"\"",
  520. IQColumn.class.getSimpleName(), defaultValue)));
  521. } else if (!isNullOrEmpty(defaultValue) && !isNullOrEmpty(col.defaultValue)) {
  522. if (!defaultValue.equals(col.defaultValue)) {
  523. // Model.defaultValue != Column.defaultValue
  524. remarks.add(warn(
  525. table,
  526. col,
  527. format("{0}.defaultValue=\"{1}\"" + " while column default=\"{2}\"",
  528. IQColumn.class.getSimpleName(), defaultValue, col.defaultValue)));
  529. }
  530. }
  531. // sanity check Model.defaultValue literal value
  532. if (!ModelUtils.isValidDefaultValue(fieldDef.field.getType(), defaultValue)) {
  533. remarks.add(error(
  534. table,
  535. col,
  536. format("{0}.defaultValue=\"{1}\" is invalid!", IQColumn.class.getSimpleName(),
  537. defaultValue)));
  538. }
  539. }
  540. }
  541. /**
  542. * Represents an index as it exists in the database.
  543. */
  544. private static class IndexInspector {
  545. String name;
  546. IndexType type;
  547. private List<String> columns = new ArrayList<String>();
  548. public IndexInspector(ResultSet rs) throws SQLException {
  549. name = rs.getString("INDEX_NAME");
  550. // determine index type
  551. boolean hash = rs.getInt("TYPE") == DatabaseMetaData.tableIndexHashed;
  552. boolean unique = !rs.getBoolean("NON_UNIQUE");
  553. if (!hash && !unique) {
  554. type = IndexType.STANDARD;
  555. } else if (hash && unique) {
  556. type = IndexType.UNIQUE_HASH;
  557. } else if (unique) {
  558. type = IndexType.UNIQUE;
  559. } else if (hash) {
  560. type = IndexType.HASH;
  561. }
  562. columns.add(rs.getString("COLUMN_NAME"));
  563. }
  564. public void addColumn(ResultSet rs) throws SQLException {
  565. columns.add(rs.getString("COLUMN_NAME"));
  566. }
  567. }
  568. /**
  569. * Represents a column as it exists in the database.
  570. */
  571. static class ColumnInspector implements Comparable<ColumnInspector> {
  572. String name;
  573. String type;
  574. int size;
  575. boolean nullable;
  576. Class<?> clazz;
  577. boolean isPrimaryKey;
  578. boolean isAutoIncrement;
  579. String defaultValue;
  580. public int compareTo(ColumnInspector o) {
  581. if (isPrimaryKey && o.isPrimaryKey) {
  582. // both primary sort by name
  583. return name.compareTo(o.name);
  584. } else if (isPrimaryKey && !o.isPrimaryKey) {
  585. // primary first
  586. return -1;
  587. } else if (!isPrimaryKey && o.isPrimaryKey) {
  588. // primary first
  589. return 1;
  590. } else {
  591. // neither primary, sort by name
  592. return name.compareTo(o.name);
  593. }
  594. }
  595. }
  596. /**
  597. * Convenience class based on StatementBuilder for creating the annotation
  598. * parameter list.
  599. */
  600. private static class AnnotationBuilder extends StatementBuilder {
  601. AnnotationBuilder() {
  602. super();
  603. }
  604. void addParameter(String parameter) {
  605. appendExceptFirst(", ");
  606. append(parameter);
  607. }
  608. <T> void addParameter(String parameter, T value) {
  609. appendExceptFirst(", ");
  610. if (!StringUtils.isNullOrEmpty(parameter)) {
  611. append(parameter);
  612. append('=');
  613. }
  614. if (value instanceof List) {
  615. append("{ ");
  616. List<?> list = (List<?>) value;
  617. StatementBuilder flat = new StatementBuilder();
  618. for (Object o : list) {
  619. flat.appendExceptFirst(", ");
  620. if (o instanceof String) {
  621. flat.append('\"');
  622. }
  623. // TODO escape string
  624. flat.append(o.toString().trim());
  625. if (o instanceof String) {
  626. flat.append('\"');
  627. }
  628. }
  629. append(flat);
  630. append(" }");
  631. } else {
  632. if (value instanceof String) {
  633. append('\"');
  634. }
  635. // TODO escape
  636. append(value.toString().trim());
  637. if (value instanceof String) {
  638. append('\"');
  639. }
  640. }
  641. }
  642. void addEnum(String parameter, Enum<?> value) {
  643. appendExceptFirst(", ");
  644. if (!StringUtils.isNullOrEmpty(parameter)) {
  645. append(parameter);
  646. append('=');
  647. }
  648. append(value.getClass().getSimpleName() + "." + value.name());
  649. }
  650. }
  651. }