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.

TableDefinition.java 36KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227
  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 java.lang.reflect.Field;
  20. import java.sql.PreparedStatement;
  21. import java.sql.ResultSet;
  22. import java.sql.SQLException;
  23. import java.util.ArrayList;
  24. import java.util.Arrays;
  25. import java.util.IdentityHashMap;
  26. import java.util.LinkedHashSet;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Set;
  30. import com.iciql.Iciql.ConstraintDeferrabilityType;
  31. import com.iciql.Iciql.ConstraintDeleteType;
  32. import com.iciql.Iciql.ConstraintUpdateType;
  33. import com.iciql.Iciql.DataTypeAdapter;
  34. import com.iciql.Iciql.EnumId;
  35. import com.iciql.Iciql.EnumType;
  36. import com.iciql.Iciql.IQColumn;
  37. import com.iciql.Iciql.IQConstraint;
  38. import com.iciql.Iciql.IQContraintForeignKey;
  39. import com.iciql.Iciql.IQContraintUnique;
  40. import com.iciql.Iciql.IQContraintsForeignKey;
  41. import com.iciql.Iciql.IQContraintsUnique;
  42. import com.iciql.Iciql.IQIgnore;
  43. import com.iciql.Iciql.IQIndex;
  44. import com.iciql.Iciql.IQIndexes;
  45. import com.iciql.Iciql.IQSchema;
  46. import com.iciql.Iciql.IQTable;
  47. import com.iciql.Iciql.IQVersion;
  48. import com.iciql.Iciql.IQView;
  49. import com.iciql.Iciql.IndexType;
  50. import com.iciql.util.IciqlLogger;
  51. import com.iciql.util.StatementBuilder;
  52. import com.iciql.util.StringUtils;
  53. import com.iciql.util.Utils;
  54. /**
  55. * A table definition contains the index definitions of a table, the field
  56. * definitions, the table name, and other meta data.
  57. *
  58. * @param <T>
  59. * the table type
  60. */
  61. public class TableDefinition<T> {
  62. /**
  63. * The meta data of an index.
  64. */
  65. public static class IndexDefinition {
  66. public IndexType type;
  67. public String indexName;
  68. public List<String> columnNames;
  69. }
  70. /**
  71. * The meta data of a constraint on foreign key.
  72. */
  73. public static class ConstraintForeignKeyDefinition {
  74. public String constraintName;
  75. public List<String> foreignColumns;
  76. public String referenceTable;
  77. public List<String> referenceColumns;
  78. public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET;
  79. public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET;
  80. public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET;
  81. }
  82. /**
  83. * The meta data of a unique constraint.
  84. */
  85. public static class ConstraintUniqueDefinition {
  86. public String constraintName;
  87. public List<String> uniqueColumns;
  88. }
  89. /**
  90. * The meta data of a field.
  91. */
  92. static class FieldDefinition {
  93. String columnName;
  94. Field field;
  95. String dataType;
  96. int length;
  97. int scale;
  98. boolean isPrimaryKey;
  99. boolean isAutoIncrement;
  100. boolean trim;
  101. boolean nullable;
  102. String defaultValue;
  103. EnumType enumType;
  104. Class<?> enumTypeClass;
  105. boolean isPrimitive;
  106. String constraint;
  107. Class<? extends DataTypeAdapter<?>> typeAdapter;
  108. Object getValue(Object obj) {
  109. try {
  110. return field.get(obj);
  111. } catch (Exception e) {
  112. throw new IciqlException(e);
  113. }
  114. }
  115. private Object initWithNewObject(Object obj) {
  116. Object o = Utils.newObject(field.getType());
  117. setValue(obj, o);
  118. return o;
  119. }
  120. private void setValue(Object obj, Object o) {
  121. try {
  122. if (!field.isAccessible()) {
  123. field.setAccessible(true);
  124. }
  125. if (field.getType().isPrimitive() && o == null) {
  126. // do not attempt to set a primitive to null
  127. return;
  128. }
  129. field.set(obj, o);
  130. } catch (IciqlException e) {
  131. throw e;
  132. } catch (Exception e) {
  133. throw new IciqlException(e);
  134. }
  135. }
  136. @Override
  137. public int hashCode() {
  138. return columnName.hashCode();
  139. }
  140. @Override
  141. public boolean equals(Object o) {
  142. if (o instanceof FieldDefinition) {
  143. return o.hashCode() == hashCode();
  144. }
  145. return false;
  146. }
  147. }
  148. public ArrayList<FieldDefinition> fields = Utils.newArrayList();
  149. String schemaName;
  150. String tableName;
  151. String viewTableName;
  152. int tableVersion;
  153. List<String> primaryKeyColumnNames;
  154. boolean memoryTable;
  155. boolean multiplePrimitiveBools;
  156. private boolean createIfRequired = true;
  157. private Class<T> clazz;
  158. private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();
  159. private ArrayList<IndexDefinition> indexes = Utils.newArrayList();
  160. ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList();
  161. ArrayList<ConstraintUniqueDefinition> constraintsUnique = Utils.newArrayList();
  162. TableDefinition(Class<T> clazz) {
  163. this.clazz = clazz;
  164. schemaName = null;
  165. tableName = clazz.getSimpleName();
  166. }
  167. Class<T> getModelClass() {
  168. return clazz;
  169. }
  170. List<FieldDefinition> getFields() {
  171. return fields;
  172. }
  173. void defineSchemaName(String schemaName) {
  174. this.schemaName = schemaName;
  175. }
  176. void defineTableName(String tableName) {
  177. this.tableName = tableName;
  178. }
  179. void defineViewTableName(String viewTableName) {
  180. this.viewTableName = viewTableName;
  181. }
  182. void defineMemoryTable() {
  183. this.memoryTable = true;
  184. }
  185. void defineSkipCreate() {
  186. this.createIfRequired = false;
  187. }
  188. /**
  189. * Define a primary key by the specified model fields.
  190. *
  191. * @param modelFields
  192. * the ordered list of model fields
  193. */
  194. void definePrimaryKey(Object[] modelFields) {
  195. List<String> columnNames = mapColumnNames(modelFields);
  196. setPrimaryKey(columnNames);
  197. }
  198. /**
  199. * Define a primary key by the specified column names.
  200. *
  201. * @param columnNames
  202. * the ordered list of column names
  203. */
  204. private void setPrimaryKey(List<String> columnNames) {
  205. primaryKeyColumnNames = Utils.newArrayList(columnNames);
  206. List<String> pkNames = Utils.newArrayList();
  207. for (String name : columnNames) {
  208. pkNames.add(name.toLowerCase());
  209. }
  210. // set isPrimaryKey flag for all field definitions
  211. for (FieldDefinition fieldDefinition : fieldMap.values()) {
  212. fieldDefinition.isPrimaryKey = pkNames.contains(fieldDefinition.columnName.toLowerCase());
  213. }
  214. }
  215. private <A> String getColumnName(A fieldObject) {
  216. FieldDefinition def = fieldMap.get(fieldObject);
  217. return def == null ? null : def.columnName;
  218. }
  219. private ArrayList<String> mapColumnNames(Object[] columns) {
  220. ArrayList<String> columnNames = Utils.newArrayList();
  221. for (Object column : columns) {
  222. columnNames.add(getColumnName(column));
  223. }
  224. return columnNames;
  225. }
  226. /**
  227. * Defines an index with the specified model fields.
  228. *
  229. * @param name
  230. * the index name (optional)
  231. * @param type
  232. * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
  233. * @param modelFields
  234. * the ordered list of model fields
  235. */
  236. void defineIndex(String name, IndexType type, Object[] modelFields) {
  237. List<String> columnNames = mapColumnNames(modelFields);
  238. addIndex(name, type, columnNames);
  239. }
  240. /**
  241. * Defines an index with the specified column names.
  242. *
  243. * @param type
  244. * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
  245. * @param columnNames
  246. * the ordered list of column names
  247. */
  248. private void addIndex(String name, IndexType type, List<String> columnNames) {
  249. IndexDefinition index = new IndexDefinition();
  250. if (StringUtils.isNullOrEmpty(name)) {
  251. index.indexName = tableName + "_idx_" + indexes.size();
  252. } else {
  253. index.indexName = name;
  254. }
  255. index.columnNames = Utils.newArrayList(columnNames);
  256. index.type = type;
  257. indexes.add(index);
  258. }
  259. /**
  260. * Defines an unique constraint with the specified model fields.
  261. *
  262. * @param name
  263. * the constraint name (optional)
  264. * @param modelFields
  265. * the ordered list of model fields
  266. */
  267. void defineConstraintUnique(String name, Object[] modelFields) {
  268. List<String> columnNames = mapColumnNames(modelFields);
  269. addConstraintUnique(name, columnNames);
  270. }
  271. /**
  272. * Defines an unique constraint.
  273. *
  274. * @param name
  275. * @param columnNames
  276. */
  277. private void addConstraintUnique(String name, List<String> columnNames) {
  278. ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();
  279. if (StringUtils.isNullOrEmpty(name)) {
  280. constraint.constraintName = tableName + "_unique_" + constraintsUnique.size();
  281. } else {
  282. constraint.constraintName = name;
  283. }
  284. constraint.uniqueColumns = Utils.newArrayList(columnNames);
  285. constraintsUnique.add(constraint);
  286. }
  287. /**
  288. * Defines a foreign key constraint with the specified model fields.
  289. *
  290. * @param name
  291. * the constraint name (optional)
  292. * @param modelFields
  293. * the ordered list of model fields
  294. */
  295. void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields,
  296. ConstraintDeleteType deleteType, ConstraintUpdateType updateType,
  297. ConstraintDeferrabilityType deferrabilityType) {
  298. List<String> columnNames = mapColumnNames(modelFields);
  299. List<String> referenceColumnNames = mapColumnNames(refModelFields);
  300. addConstraintForeignKey(name, columnNames, refTableName, referenceColumnNames,
  301. deleteType, updateType, deferrabilityType);
  302. }
  303. void defineColumnName(Object column, String columnName) {
  304. FieldDefinition def = fieldMap.get(column);
  305. if (def != null) {
  306. def.columnName = columnName;
  307. }
  308. }
  309. void defineAutoIncrement(Object column) {
  310. FieldDefinition def = fieldMap.get(column);
  311. if (def != null) {
  312. def.isAutoIncrement = true;
  313. }
  314. }
  315. void defineLength(Object column, int length) {
  316. FieldDefinition def = fieldMap.get(column);
  317. if (def != null) {
  318. def.length = length;
  319. }
  320. }
  321. void defineScale(Object column, int scale) {
  322. FieldDefinition def = fieldMap.get(column);
  323. if (def != null) {
  324. def.scale = scale;
  325. }
  326. }
  327. void defineTrim(Object column) {
  328. FieldDefinition def = fieldMap.get(column);
  329. if (def != null) {
  330. def.trim = true;
  331. }
  332. }
  333. void defineNullable(Object column, boolean isNullable) {
  334. FieldDefinition def = fieldMap.get(column);
  335. if (def != null) {
  336. def.nullable = isNullable;
  337. }
  338. }
  339. void defineDefaultValue(Object column, String defaultValue) {
  340. FieldDefinition def = fieldMap.get(column);
  341. if (def != null) {
  342. def.defaultValue = defaultValue;
  343. }
  344. }
  345. void defineConstraint(Object column, String constraint) {
  346. FieldDefinition def = fieldMap.get(column);
  347. if (def != null) {
  348. def.constraint = constraint;
  349. }
  350. }
  351. void defineTypeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) {
  352. FieldDefinition def = fieldMap.get(column);
  353. if (def != null) {
  354. def.typeAdapter = typeAdapter;
  355. }
  356. }
  357. void mapFields(Db db) {
  358. boolean byAnnotationsOnly = false;
  359. boolean inheritColumns = false;
  360. if (clazz.isAnnotationPresent(IQTable.class)) {
  361. IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
  362. byAnnotationsOnly = tableAnnotation.annotationsOnly();
  363. inheritColumns = tableAnnotation.inheritColumns();
  364. }
  365. if (clazz.isAnnotationPresent(IQView.class)) {
  366. IQView viewAnnotation = clazz.getAnnotation(IQView.class);
  367. byAnnotationsOnly = viewAnnotation.annotationsOnly();
  368. inheritColumns = viewAnnotation.inheritColumns();
  369. }
  370. List<Field> classFields = classFields(inheritColumns);
  371. Set<FieldDefinition> uniqueFields = new LinkedHashSet<FieldDefinition>();
  372. T defaultObject = Db.instance(clazz);
  373. for (Field f : classFields) {
  374. // check if we should skip this field
  375. if (f.isAnnotationPresent(IQIgnore.class)) {
  376. continue;
  377. }
  378. // default to field name
  379. String columnName = f.getName();
  380. boolean isAutoIncrement = false;
  381. boolean isPrimaryKey = false;
  382. int length = 0;
  383. int scale = 0;
  384. boolean trim = false;
  385. boolean nullable = !f.getType().isPrimitive();
  386. String defaultValue = "";
  387. String constraint = "";
  388. String dataType = null;
  389. Class<? extends DataTypeAdapter<?>> typeAdapter = null;
  390. // configure Java -> SQL enum mapping
  391. EnumType enumType = Utils.getEnumType(f);
  392. Class<?> enumTypeClass = Utils.getEnumTypeClass(f);
  393. // try using default object
  394. try {
  395. f.setAccessible(true);
  396. Object value = f.get(defaultObject);
  397. if (value != null) {
  398. if (value.getClass().isEnum()) {
  399. // enum default, convert to target type
  400. Enum<?> anEnum = (Enum<?>) value;
  401. Object o = Utils.convertEnum(anEnum, enumType);
  402. defaultValue = ModelUtils.formatDefaultValue(o);
  403. } else {
  404. // object default
  405. defaultValue = ModelUtils.formatDefaultValue(value);
  406. }
  407. }
  408. } catch (IllegalAccessException e) {
  409. throw new IciqlException(e, "failed to get default object for {0}", columnName);
  410. }
  411. // identify the type adapter
  412. typeAdapter = Utils.getDataTypeAdapter(f.getAnnotations());
  413. if (typeAdapter == null) {
  414. typeAdapter = Utils.getDataTypeAdapter(f.getType().getAnnotations());
  415. }
  416. if (typeAdapter != null) {
  417. DataTypeAdapter<?> dtt = db.getDialect().getAdapter(typeAdapter);
  418. dataType = dtt.getDataType();
  419. }
  420. boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);
  421. if (hasAnnotation) {
  422. IQColumn col = f.getAnnotation(IQColumn.class);
  423. if (!StringUtils.isNullOrEmpty(col.name())) {
  424. columnName = col.name();
  425. }
  426. isAutoIncrement = col.autoIncrement();
  427. isPrimaryKey = col.primaryKey();
  428. length = col.length();
  429. scale = col.scale();
  430. trim = col.trim();
  431. nullable = col.nullable();
  432. // annotation overrides
  433. if (!StringUtils.isNullOrEmpty(col.defaultValue())) {
  434. defaultValue = col.defaultValue();
  435. }
  436. }
  437. boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class);
  438. if (hasConstraint) {
  439. IQConstraint con = f.getAnnotation(IQConstraint.class);
  440. // annotation overrides
  441. if (!StringUtils.isNullOrEmpty(con.value())) {
  442. constraint = con.value();
  443. }
  444. }
  445. boolean reflectiveMatch = !byAnnotationsOnly;
  446. if (reflectiveMatch || hasAnnotation || hasConstraint) {
  447. FieldDefinition fieldDef = new FieldDefinition();
  448. fieldDef.isPrimitive = f.getType().isPrimitive();
  449. fieldDef.field = f;
  450. fieldDef.columnName = columnName;
  451. fieldDef.isAutoIncrement = isAutoIncrement;
  452. fieldDef.isPrimaryKey = isPrimaryKey;
  453. fieldDef.length = length;
  454. fieldDef.scale = scale;
  455. fieldDef.trim = trim;
  456. fieldDef.nullable = nullable;
  457. fieldDef.defaultValue = defaultValue;
  458. fieldDef.enumType = enumType;
  459. fieldDef.enumTypeClass = enumTypeClass;
  460. fieldDef.dataType = StringUtils.isNullOrEmpty(dataType) ? ModelUtils.getDataType(fieldDef) : dataType;
  461. fieldDef.typeAdapter = typeAdapter;
  462. fieldDef.constraint = constraint;
  463. uniqueFields.add(fieldDef);
  464. }
  465. }
  466. fields.addAll(uniqueFields);
  467. List<String> primaryKey = Utils.newArrayList();
  468. int primitiveBoolean = 0;
  469. for (FieldDefinition fieldDef : fields) {
  470. if (fieldDef.isPrimaryKey) {
  471. primaryKey.add(fieldDef.columnName);
  472. }
  473. if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) {
  474. primitiveBoolean++;
  475. }
  476. }
  477. if (primitiveBoolean > 1) {
  478. multiplePrimitiveBools = true;
  479. IciqlLogger
  480. .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!");
  481. }
  482. if (primaryKey.size() > 0) {
  483. setPrimaryKey(primaryKey);
  484. }
  485. }
  486. private List<Field> classFields(boolean inheritColumns) {
  487. List<Field> classFields = Utils.newArrayList();
  488. classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
  489. Class<?> superClass = clazz;
  490. while (inheritColumns) {
  491. superClass = superClass.getSuperclass();
  492. classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));
  493. if (superClass.isAnnotationPresent(IQView.class)) {
  494. IQView superView = superClass.getAnnotation(IQView.class);
  495. inheritColumns = superView.inheritColumns();
  496. } else if (superClass.isAnnotationPresent(IQTable.class)) {
  497. IQTable superTable = superClass.getAnnotation(IQTable.class);
  498. inheritColumns = superTable.inheritColumns();
  499. } else {
  500. inheritColumns = false;
  501. }
  502. }
  503. return classFields;
  504. }
  505. void checkMultipleBooleans() {
  506. if (multiplePrimitiveBools) {
  507. throw new IciqlException(
  508. "Can not explicitly reference a primitive boolean if there are multiple boolean fields in your model class!");
  509. }
  510. }
  511. void checkMultipleEnums(Object o) {
  512. if (o == null) {
  513. return;
  514. }
  515. Class<?> clazz = o.getClass();
  516. if (!clazz.isEnum()) {
  517. return;
  518. }
  519. int fieldCount = 0;
  520. for (FieldDefinition fieldDef : fields) {
  521. Class<?> targetType = fieldDef.field.getType();
  522. if (clazz.equals(targetType)) {
  523. fieldCount++;
  524. }
  525. }
  526. if (fieldCount > 1) {
  527. throw new IciqlException(
  528. "Can not explicitly reference {0} because there are {1} {0} fields in your model class!",
  529. clazz.getSimpleName(), fieldCount);
  530. }
  531. }
  532. /**
  533. * Optionally truncates strings to the maximum length and converts
  534. * java.lang.Enum types to Strings or Integers.
  535. */
  536. Object getValue(Object obj, FieldDefinition field) {
  537. Object value = field.getValue(obj);
  538. if (value == null) {
  539. return value;
  540. }
  541. if (field.enumType != null) {
  542. // convert enumeration to INT or STRING
  543. Enum<?> iqenum = (Enum<?>) value;
  544. switch (field.enumType) {
  545. case NAME:
  546. if (field.trim && field.length > 0) {
  547. if (iqenum.name().length() > field.length) {
  548. return iqenum.name().substring(0, field.length);
  549. }
  550. }
  551. return iqenum.name();
  552. case ORDINAL:
  553. return iqenum.ordinal();
  554. case ENUMID:
  555. if (!EnumId.class.isAssignableFrom(value.getClass())) {
  556. throw new IciqlException(field.field.getName() + " does not implement EnumId!");
  557. }
  558. EnumId<?> enumid = (EnumId<?>) value;
  559. return enumid.enumId();
  560. }
  561. }
  562. if (field.trim && field.length > 0) {
  563. if (value instanceof String) {
  564. // clip strings
  565. String s = (String) value;
  566. if (s.length() > field.length) {
  567. return s.substring(0, field.length);
  568. }
  569. return s;
  570. }
  571. return value;
  572. }
  573. // return the value unchanged
  574. return value;
  575. }
  576. PreparedStatement createInsertStatement(Db db, Object obj, boolean returnKey) {
  577. SQLStatement stat = new SQLStatement(db);
  578. StatementBuilder buff = new StatementBuilder("INSERT INTO ");
  579. buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
  580. for (FieldDefinition field : fields) {
  581. if (skipInsertField(field, obj)) {
  582. continue;
  583. }
  584. buff.appendExceptFirst(", ");
  585. buff.append(db.getDialect().prepareColumnName(field.columnName));
  586. }
  587. buff.append(") VALUES(");
  588. buff.resetCount();
  589. for (FieldDefinition field : fields) {
  590. if (skipInsertField(field, obj)) {
  591. continue;
  592. }
  593. buff.appendExceptFirst(", ");
  594. buff.append('?');
  595. Object value = getValue(obj, field);
  596. if (value == null && !field.nullable) {
  597. // try to interpret and instantiate a default value
  598. value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
  599. }
  600. Object parameter = db.getDialect().serialize(value, field.typeAdapter);
  601. stat.addParameter(parameter);
  602. }
  603. buff.append(')');
  604. stat.setSQL(buff.toString());
  605. IciqlLogger.insert(stat.getSQL());
  606. return stat.prepare(returnKey);
  607. }
  608. long insert(Db db, Object obj, boolean returnKey) {
  609. if (!StringUtils.isNullOrEmpty(viewTableName)) {
  610. throw new IciqlException("Iciql does not support inserting rows into views!");
  611. }
  612. SQLStatement stat = new SQLStatement(db);
  613. StatementBuilder buff = new StatementBuilder("INSERT INTO ");
  614. buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
  615. for (FieldDefinition field : fields) {
  616. if (skipInsertField(field, obj)) {
  617. continue;
  618. }
  619. buff.appendExceptFirst(", ");
  620. buff.append(db.getDialect().prepareColumnName(field.columnName));
  621. }
  622. buff.append(") VALUES(");
  623. buff.resetCount();
  624. for (FieldDefinition field : fields) {
  625. if (skipInsertField(field, obj)) {
  626. continue;
  627. }
  628. buff.appendExceptFirst(", ");
  629. buff.append('?');
  630. Object value = getValue(obj, field);
  631. if (value == null && !field.nullable) {
  632. // try to interpret and instantiate a default value
  633. value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
  634. }
  635. Object parameter = db.getDialect().serialize(value, field.typeAdapter);
  636. stat.addParameter(parameter);
  637. }
  638. buff.append(')');
  639. stat.setSQL(buff.toString());
  640. IciqlLogger.insert(stat.getSQL());
  641. if (returnKey) {
  642. return stat.executeInsert();
  643. }
  644. return stat.executeUpdate();
  645. }
  646. private boolean skipInsertField(FieldDefinition field, Object obj) {
  647. if (field.isAutoIncrement) {
  648. Object value = getValue(obj, field);
  649. if (field.isPrimitive) {
  650. // skip uninitialized primitive autoincrement values
  651. if (value.toString().equals("0")) {
  652. return true;
  653. }
  654. } else if (value == null) {
  655. // skip null object autoincrement values
  656. return true;
  657. }
  658. } else {
  659. // conditionally skip insert of null
  660. Object value = getValue(obj, field);
  661. if (value == null) {
  662. if (field.nullable) {
  663. // skip null assignment, field is nullable
  664. return true;
  665. } else if (StringUtils.isNullOrEmpty(field.defaultValue)) {
  666. IciqlLogger.warn("no default value, skipping null insert assignment for {0}.{1}",
  667. tableName, field.columnName);
  668. return true;
  669. }
  670. }
  671. }
  672. return false;
  673. }
  674. int merge(Db db, Object obj) {
  675. if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
  676. throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()
  677. + " - no update possible");
  678. }
  679. SQLStatement stat = new SQLStatement(db);
  680. db.getDialect().prepareMerge(stat, schemaName, tableName, this, obj);
  681. IciqlLogger.merge(stat.getSQL());
  682. return stat.executeUpdate();
  683. }
  684. int update(Db db, Object obj) {
  685. if (!StringUtils.isNullOrEmpty(viewTableName)) {
  686. throw new IciqlException("Iciql does not support updating rows in views!");
  687. }
  688. if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
  689. throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()
  690. + " - no update possible");
  691. }
  692. SQLStatement stat = new SQLStatement(db);
  693. StatementBuilder buff = new StatementBuilder("UPDATE ");
  694. buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET ");
  695. buff.resetCount();
  696. for (FieldDefinition field : fields) {
  697. if (!field.isPrimaryKey) {
  698. Object value = getValue(obj, field);
  699. if (value == null && !field.nullable) {
  700. // try to interpret and instantiate a default value
  701. value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
  702. }
  703. buff.appendExceptFirst(", ");
  704. buff.append(db.getDialect().prepareColumnName(field.columnName));
  705. buff.append(" = ?");
  706. Object parameter = db.getDialect().serialize(value, field.typeAdapter);
  707. stat.addParameter(parameter);
  708. }
  709. }
  710. Object alias = Utils.newObject(obj.getClass());
  711. Query<Object> query = Query.from(db, alias);
  712. boolean firstCondition = true;
  713. for (FieldDefinition field : fields) {
  714. if (field.isPrimaryKey) {
  715. Object fieldAlias = field.getValue(alias);
  716. Object value = field.getValue(obj);
  717. if (field.isPrimitive) {
  718. fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);
  719. }
  720. if (!firstCondition) {
  721. query.addConditionToken(ConditionAndOr.AND);
  722. }
  723. firstCondition = false;
  724. query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));
  725. }
  726. }
  727. stat.setSQL(buff.toString());
  728. query.appendWhere(stat);
  729. IciqlLogger.update(stat.getSQL());
  730. return stat.executeUpdate();
  731. }
  732. int delete(Db db, Object obj) {
  733. if (!StringUtils.isNullOrEmpty(viewTableName)) {
  734. throw new IciqlException("Iciql does not support deleting rows from views!");
  735. }
  736. if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
  737. throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()
  738. + " - no update possible");
  739. }
  740. SQLStatement stat = new SQLStatement(db);
  741. StatementBuilder buff = new StatementBuilder("DELETE FROM ");
  742. buff.append(db.getDialect().prepareTableName(schemaName, tableName));
  743. buff.resetCount();
  744. Object alias = Utils.newObject(obj.getClass());
  745. Query<Object> query = Query.from(db, alias);
  746. boolean firstCondition = true;
  747. for (FieldDefinition field : fields) {
  748. if (field.isPrimaryKey) {
  749. Object fieldAlias = field.getValue(alias);
  750. Object value = field.getValue(obj);
  751. if (field.isPrimitive) {
  752. fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);
  753. }
  754. if (!firstCondition) {
  755. query.addConditionToken(ConditionAndOr.AND);
  756. }
  757. firstCondition = false;
  758. query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));
  759. }
  760. }
  761. stat.setSQL(buff.toString());
  762. query.appendWhere(stat);
  763. IciqlLogger.delete(stat.getSQL());
  764. return stat.executeUpdate();
  765. }
  766. TableDefinition<T> createIfRequired(Db db) {
  767. // globally enable/disable check of create if required
  768. if (db.getSkipCreate()) {
  769. return this;
  770. }
  771. if (!createIfRequired) {
  772. // skip table and index creation
  773. // but still check for upgrades
  774. db.upgradeTable(this);
  775. return this;
  776. }
  777. if (db.hasCreated(clazz)) {
  778. return this;
  779. }
  780. SQLStatement stat = new SQLStatement(db);
  781. if (StringUtils.isNullOrEmpty(viewTableName)) {
  782. db.getDialect().prepareCreateTable(stat, this);
  783. } else {
  784. db.getDialect().prepareCreateView(stat, this);
  785. }
  786. IciqlLogger.create(stat.getSQL());
  787. try {
  788. stat.executeUpdate();
  789. } catch (IciqlException e) {
  790. if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {
  791. throw e;
  792. }
  793. }
  794. // create indexes
  795. for (IndexDefinition index : indexes) {
  796. stat = new SQLStatement(db);
  797. db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index);
  798. IciqlLogger.create(stat.getSQL());
  799. try {
  800. stat.executeUpdate();
  801. } catch (IciqlException e) {
  802. if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS
  803. && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {
  804. throw e;
  805. }
  806. }
  807. }
  808. // tables are created using IF NOT EXISTS
  809. // but we may still need to upgrade
  810. db.upgradeTable(this);
  811. return this;
  812. }
  813. void mapObject(Object obj) {
  814. fieldMap.clear();
  815. initObject(obj, fieldMap);
  816. if (clazz.isAnnotationPresent(IQSchema.class)) {
  817. IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class);
  818. // setup schema name mapping, if properly annotated
  819. if (!StringUtils.isNullOrEmpty(schemaAnnotation.value())) {
  820. schemaName = schemaAnnotation.value();
  821. }
  822. }
  823. if (clazz.isAnnotationPresent(IQTable.class)) {
  824. IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
  825. // setup table name mapping, if properly annotated
  826. if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) {
  827. tableName = tableAnnotation.name();
  828. }
  829. // allow control over createTableIfRequired()
  830. createIfRequired = tableAnnotation.create();
  831. // model version
  832. if (clazz.isAnnotationPresent(IQVersion.class)) {
  833. IQVersion versionAnnotation = clazz.getAnnotation(IQVersion.class);
  834. if (versionAnnotation.value() > 0) {
  835. tableVersion = versionAnnotation.value();
  836. }
  837. }
  838. // setup the primary index, if properly annotated
  839. if (tableAnnotation.primaryKey().length > 0) {
  840. List<String> primaryKey = Utils.newArrayList();
  841. primaryKey.addAll(Arrays.asList(tableAnnotation.primaryKey()));
  842. setPrimaryKey(primaryKey);
  843. }
  844. }
  845. if (clazz.isAnnotationPresent(IQView.class)) {
  846. IQView viewAnnotation = clazz.getAnnotation(IQView.class);
  847. // setup view name mapping, if properly annotated
  848. // set this as the table name so it fits in seemlessly with iciql
  849. if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) {
  850. tableName = viewAnnotation.name();
  851. } else {
  852. tableName = clazz.getSimpleName();
  853. }
  854. // setup source table name mapping, if properly annotated
  855. if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) {
  856. viewTableName = viewAnnotation.tableName();
  857. } else {
  858. // check for IQTable annotation on super class
  859. Class<?> superClass = clazz.getSuperclass();
  860. if (superClass.isAnnotationPresent(IQTable.class)) {
  861. IQTable table = superClass.getAnnotation(IQTable.class);
  862. if (StringUtils.isNullOrEmpty(table.name())) {
  863. // super.SimpleClassName
  864. viewTableName = superClass.getSimpleName();
  865. } else {
  866. // super.IQTable.name()
  867. viewTableName = table.name();
  868. }
  869. } else if (superClass.isAnnotationPresent(IQView.class)) {
  870. // super class is a view
  871. IQView parentView = superClass.getAnnotation(IQView.class);
  872. if (StringUtils.isNullOrEmpty(parentView.tableName())) {
  873. // parent view does not define a tableName, must be inherited
  874. Class<?> superParent = superClass.getSuperclass();
  875. if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) {
  876. IQTable superParentTable = superParent.getAnnotation(IQTable.class);
  877. if (StringUtils.isNullOrEmpty(superParentTable.name())) {
  878. // super.super.SimpleClassName
  879. viewTableName = superParent.getSimpleName();
  880. } else {
  881. // super.super.IQTable.name()
  882. viewTableName = superParentTable.name();
  883. }
  884. }
  885. } else {
  886. // super.IQView.tableName()
  887. viewTableName = parentView.tableName();
  888. }
  889. }
  890. if (StringUtils.isNullOrEmpty(viewTableName)) {
  891. // still missing view table name
  892. throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName);
  893. }
  894. }
  895. // allow control over createTableIfRequired()
  896. createIfRequired = viewAnnotation.create();
  897. }
  898. if (clazz.isAnnotationPresent(IQIndex.class)) {
  899. // single table index
  900. IQIndex index = clazz.getAnnotation(IQIndex.class);
  901. addIndex(index);
  902. }
  903. if (clazz.isAnnotationPresent(IQIndexes.class)) {
  904. // multiple table indexes
  905. IQIndexes indexes = clazz.getAnnotation(IQIndexes.class);
  906. for (IQIndex index : indexes.value()) {
  907. addIndex(index);
  908. }
  909. }
  910. if (clazz.isAnnotationPresent(IQContraintUnique.class)) {
  911. // single table unique constraint
  912. IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class);
  913. addConstraintUnique(constraint);
  914. }
  915. if (clazz.isAnnotationPresent(IQContraintsUnique.class)) {
  916. // multiple table unique constraints
  917. IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class);
  918. for (IQContraintUnique constraint : constraints.value()) {
  919. addConstraintUnique(constraint);
  920. }
  921. }
  922. if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) {
  923. // single table constraint
  924. IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class);
  925. addConstraintForeignKey(constraint);
  926. }
  927. if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) {
  928. // multiple table constraints
  929. IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class);
  930. for (IQContraintForeignKey constraint : constraints.value()) {
  931. addConstraintForeignKey(constraint);
  932. }
  933. }
  934. }
  935. private void addConstraintForeignKey(IQContraintForeignKey constraint) {
  936. List<String> foreignColumns = Arrays.asList(constraint.foreignColumns());
  937. List<String> referenceColumns = Arrays.asList(constraint.referenceColumns());
  938. addConstraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType());
  939. }
  940. private void addConstraintUnique(IQContraintUnique constraint) {
  941. List<String> uniqueColumns = Arrays.asList(constraint.uniqueColumns());
  942. addConstraintUnique(constraint.name(), uniqueColumns);
  943. }
  944. /**
  945. * Defines a foreign key constraint with the specified parameters.
  946. *
  947. * @param name
  948. * name of the constraint
  949. * @param foreignColumns
  950. * list of columns declared as foreign
  951. * @param referenceName
  952. * reference table name
  953. * @param referenceColumns
  954. * list of columns used in reference table
  955. * @param deleteType
  956. * action on delete
  957. * @param updateType
  958. * action on update
  959. * @param deferrabilityType
  960. * deferrability mode
  961. */
  962. private void addConstraintForeignKey(String name,
  963. List<String> foreignColumns, String referenceName,
  964. List<String> referenceColumns, ConstraintDeleteType deleteType,
  965. ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {
  966. ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();
  967. if (StringUtils.isNullOrEmpty(name)) {
  968. constraint.constraintName = tableName + "_fkey_" + constraintsForeignKey.size();
  969. } else {
  970. constraint.constraintName = name;
  971. }
  972. constraint.foreignColumns = Utils.newArrayList(foreignColumns);
  973. constraint.referenceColumns = Utils.newArrayList(referenceColumns);
  974. constraint.referenceTable = referenceName;
  975. constraint.deleteType = deleteType;
  976. constraint.updateType = updateType;
  977. constraint.deferrabilityType = deferrabilityType;
  978. constraintsForeignKey.add(constraint);
  979. }
  980. private void addIndex(IQIndex index) {
  981. List<String> columns = Arrays.asList(index.value());
  982. addIndex(index.name(), index.type(), columns);
  983. }
  984. List<IndexDefinition> getIndexes() {
  985. return indexes;
  986. }
  987. List<ConstraintUniqueDefinition> getContraintsUnique() {
  988. return constraintsUnique;
  989. }
  990. List<ConstraintForeignKeyDefinition> getContraintsForeignKey() {
  991. return constraintsForeignKey;
  992. }
  993. private void initObject(Object obj, Map<Object, FieldDefinition> map) {
  994. for (FieldDefinition def : fields) {
  995. Object newValue = def.initWithNewObject(obj);
  996. map.put(newValue, def);
  997. }
  998. }
  999. void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map, boolean reuse) {
  1000. for (FieldDefinition def : fields) {
  1001. Object value;
  1002. if (!reuse) {
  1003. value = def.initWithNewObject(obj);
  1004. } else {
  1005. value = def.getValue(obj);
  1006. }
  1007. SelectColumn<T> column = new SelectColumn<T>(table, def);
  1008. map.put(value, column);
  1009. }
  1010. }
  1011. /**
  1012. * Most queries executed by iciql have named select lists (select alpha,
  1013. * beta where...) but sometimes a wildcard select is executed (select *).
  1014. * When a wildcard query is executed on a table that has more columns than
  1015. * are mapped in your model object, this creates a column mapping issue.
  1016. * JaQu assumed that you can always use the integer index of the
  1017. * reflectively mapped field definition to determine position in the result
  1018. * set.
  1019. *
  1020. * This is not always true.
  1021. *
  1022. * iciql identifies when a select * query is executed and maps column names
  1023. * to a column index from the result set. If the select statement is
  1024. * explicit, then the standard assumed column index is used instead.
  1025. *
  1026. * @param rs
  1027. * @return
  1028. */
  1029. int[] mapColumns(boolean wildcardSelect, ResultSet rs) {
  1030. int[] columns = new int[fields.size()];
  1031. for (int i = 0; i < fields.size(); i++) {
  1032. try {
  1033. FieldDefinition def = fields.get(i);
  1034. int columnIndex;
  1035. if (wildcardSelect) {
  1036. // select *
  1037. // create column index by field name
  1038. columnIndex = rs.findColumn(def.columnName);
  1039. } else {
  1040. // select alpha, beta, gamma, etc
  1041. // explicit select order
  1042. columnIndex = i + 1;
  1043. }
  1044. columns[i] = columnIndex;
  1045. } catch (SQLException s) {
  1046. throw new IciqlException(s);
  1047. }
  1048. }
  1049. return columns;
  1050. }
  1051. void readRow(SQLDialect dialect, Object item, ResultSet rs, int[] columns) {
  1052. for (int i = 0; i < fields.size(); i++) {
  1053. FieldDefinition def = fields.get(i);
  1054. Class<?> targetType = def.field.getType();
  1055. Object o;
  1056. if (targetType.isEnum()) {
  1057. Object obj;
  1058. try {
  1059. obj = rs.getObject(columns[i]);
  1060. } catch (SQLException e) {
  1061. throw new IciqlException(e);
  1062. }
  1063. o = Utils.convertEnum(obj, targetType, def.enumType);
  1064. } else {
  1065. o = dialect.deserialize(rs, columns[i], targetType, def.typeAdapter);
  1066. }
  1067. def.setValue(item, o);
  1068. }
  1069. }
  1070. void appendSelectList(SQLStatement stat) {
  1071. for (int i = 0; i < fields.size(); i++) {
  1072. if (i > 0) {
  1073. stat.appendSQL(", ");
  1074. }
  1075. FieldDefinition def = fields.get(i);
  1076. stat.appendColumn(def.columnName);
  1077. }
  1078. }
  1079. <Y, X> void appendSelectList(SQLStatement stat, Query<Y> query, X x) {
  1080. // select t0.col1, t0.col2, t0.col3...
  1081. // select table1.col1, table1.col2, table1.col3...
  1082. String selectDot = "";
  1083. SelectTable<?> sel = query.getSelectTable(x);
  1084. if (sel != null) {
  1085. if (query.isJoin()) {
  1086. selectDot = sel.getAs() + ".";
  1087. } else {
  1088. String sn = sel.getAliasDefinition().schemaName;
  1089. String tn = sel.getAliasDefinition().tableName;
  1090. selectDot = query.getDb().getDialect().prepareTableName(sn, tn) + ".";
  1091. }
  1092. }
  1093. for (int i = 0; i < fields.size(); i++) {
  1094. if (i > 0) {
  1095. stat.appendSQL(", ");
  1096. }
  1097. stat.appendSQL(selectDot);
  1098. FieldDefinition def = fields.get(i);
  1099. if (def.isPrimitive) {
  1100. Object obj = def.getValue(x);
  1101. Object alias = query.getPrimitiveAliasByValue(obj);
  1102. query.appendSQL(stat, x, alias);
  1103. } else {
  1104. Object obj = def.getValue(x);
  1105. query.appendSQL(stat, x, obj);
  1106. }
  1107. }
  1108. }
  1109. }