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

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