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

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