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 43KB

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