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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. /*
  2. * Copyright 2004-2011 H2 Group.
  3. * Copyright 2011 James Moger.
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. */
  17. package com.iciql;
  18. import java.lang.reflect.Field;
  19. import java.lang.reflect.Modifier;
  20. import java.sql.ResultSet;
  21. import java.sql.SQLException;
  22. import java.util.ArrayList;
  23. import java.util.Arrays;
  24. import java.util.IdentityHashMap;
  25. import java.util.List;
  26. import java.util.Map;
  27. import com.iciql.Iciql.IndexType;
  28. import com.iciql.Iciql.IQColumn;
  29. import com.iciql.Iciql.IQIndex;
  30. import com.iciql.Iciql.IQSchema;
  31. import com.iciql.Iciql.IQTable;
  32. import com.iciql.util.StatementBuilder;
  33. import com.iciql.util.StatementLogger;
  34. import com.iciql.util.StringUtils;
  35. import com.iciql.util.Utils;
  36. /**
  37. * A table definition contains the index definitions of a table, the field
  38. * definitions, the table name, and other meta data.
  39. *
  40. * @param <T>
  41. * the table type
  42. */
  43. class TableDefinition<T> {
  44. /**
  45. * The meta data of an index.
  46. */
  47. static class IndexDefinition {
  48. IndexType type;
  49. String indexName;
  50. List<String> columnNames;
  51. }
  52. /**
  53. * The meta data of a field.
  54. */
  55. static class FieldDefinition {
  56. String columnName;
  57. Field field;
  58. String dataType;
  59. int maxLength;
  60. boolean isPrimaryKey;
  61. boolean isAutoIncrement;
  62. boolean trimString;
  63. boolean allowNull;
  64. String defaultValue;
  65. Object getValue(Object obj) {
  66. try {
  67. return field.get(obj);
  68. } catch (Exception e) {
  69. throw new IciqlException(e);
  70. }
  71. }
  72. Object initWithNewObject(Object obj) {
  73. Object o = Utils.newObject(field.getType());
  74. setValue(obj, o);
  75. return o;
  76. }
  77. void setValue(Object obj, Object o) {
  78. try {
  79. if (!field.isAccessible()) {
  80. field.setAccessible(true);
  81. }
  82. o = Utils.convert(o, field.getType());
  83. field.set(obj, o);
  84. } catch (Exception e) {
  85. throw new IciqlException(e);
  86. }
  87. }
  88. Object read(ResultSet rs, int columnIndex) {
  89. try {
  90. return rs.getObject(columnIndex);
  91. } catch (SQLException e) {
  92. throw new IciqlException(e);
  93. }
  94. }
  95. }
  96. String schemaName;
  97. String tableName;
  98. int tableVersion;
  99. private boolean createTableIfRequired = true;
  100. private Class<T> clazz;
  101. private ArrayList<FieldDefinition> fields = Utils.newArrayList();
  102. private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();
  103. private List<String> primaryKeyColumnNames;
  104. private ArrayList<IndexDefinition> indexes = Utils.newArrayList();
  105. private boolean memoryTable;
  106. TableDefinition(Class<T> clazz) {
  107. this.clazz = clazz;
  108. schemaName = null;
  109. tableName = clazz.getSimpleName();
  110. }
  111. Class<T> getModelClass() {
  112. return clazz;
  113. }
  114. List<FieldDefinition> getFields() {
  115. return fields;
  116. }
  117. FieldDefinition getField(String name) {
  118. for (FieldDefinition field:fields) {
  119. if (field.columnName.equalsIgnoreCase(name)) {
  120. return field;
  121. }
  122. }
  123. return null;
  124. }
  125. void setSchemaName(String schemaName) {
  126. this.schemaName = schemaName;
  127. }
  128. void setTableName(String tableName) {
  129. this.tableName = tableName;
  130. }
  131. /**
  132. * Define a primary key by the specified model fields.
  133. *
  134. * @param modelFields
  135. * the ordered list of model fields
  136. */
  137. void setPrimaryKey(Object[] modelFields) {
  138. List<String> columnNames = mapColumnNames(modelFields);
  139. setPrimaryKey(columnNames);
  140. }
  141. /**
  142. * Define a primary key by the specified column names.
  143. *
  144. * @param columnNames
  145. * the ordered list of column names
  146. */
  147. void setPrimaryKey(List<String> columnNames) {
  148. primaryKeyColumnNames = Utils.newArrayList(columnNames);
  149. // set isPrimaryKey flag for all field definitions
  150. for (FieldDefinition fieldDefinition : fieldMap.values()) {
  151. fieldDefinition.isPrimaryKey = this.primaryKeyColumnNames.contains(fieldDefinition.columnName);
  152. }
  153. }
  154. <A> String getColumnName(A fieldObject) {
  155. FieldDefinition def = fieldMap.get(fieldObject);
  156. return def == null ? null : def.columnName;
  157. }
  158. private ArrayList<String> mapColumnNames(Object[] columns) {
  159. ArrayList<String> columnNames = Utils.newArrayList();
  160. for (Object column : columns) {
  161. columnNames.add(getColumnName(column));
  162. }
  163. return columnNames;
  164. }
  165. /**
  166. * Defines an index with the specified model fields.
  167. *
  168. * @param type
  169. * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
  170. * @param modelFields
  171. * the ordered list of model fields
  172. */
  173. void addIndex(IndexType type, Object[] modelFields) {
  174. List<String> columnNames = mapColumnNames(modelFields);
  175. addIndex(type, columnNames);
  176. }
  177. /**
  178. * Defines an index with the specified column names.
  179. *
  180. * @param type
  181. * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
  182. * @param columnNames
  183. * the ordered list of column names
  184. */
  185. void addIndex(IndexType type, List<String> columnNames) {
  186. IndexDefinition index = new IndexDefinition();
  187. index.indexName = tableName + "_" + indexes.size();
  188. index.columnNames = Utils.newArrayList(columnNames);
  189. index.type = type;
  190. indexes.add(index);
  191. }
  192. public void setColumnName(Object column, String columnName) {
  193. FieldDefinition def = fieldMap.get(column);
  194. if (def != null) {
  195. def.columnName = columnName;
  196. }
  197. }
  198. public void setMaxLength(Object column, int maxLength) {
  199. FieldDefinition def = fieldMap.get(column);
  200. if (def != null) {
  201. def.maxLength = maxLength;
  202. }
  203. }
  204. void mapFields() {
  205. boolean byAnnotationsOnly = false;
  206. boolean inheritColumns = false;
  207. boolean strictTypeMapping = false;
  208. if (clazz.isAnnotationPresent(IQTable.class)) {
  209. IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
  210. byAnnotationsOnly = tableAnnotation.annotationsOnly();
  211. inheritColumns = tableAnnotation.inheritColumns();
  212. strictTypeMapping = tableAnnotation.strictTypeMapping();
  213. }
  214. List<Field> classFields = Utils.newArrayList();
  215. classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
  216. if (inheritColumns) {
  217. Class<?> superClass = clazz.getSuperclass();
  218. classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));
  219. }
  220. for (Field f : classFields) {
  221. // default to field name
  222. String columnName = f.getName();
  223. boolean isAutoIncrement = false;
  224. boolean isPrimaryKey = false;
  225. int maxLength = 0;
  226. boolean trimString = false;
  227. boolean allowNull = true;
  228. String defaultValue = "";
  229. boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);
  230. if (hasAnnotation) {
  231. IQColumn col = f.getAnnotation(IQColumn.class);
  232. if (!StringUtils.isNullOrEmpty(col.name())) {
  233. columnName = col.name();
  234. }
  235. isAutoIncrement = col.autoIncrement();
  236. isPrimaryKey = col.primaryKey();
  237. maxLength = col.maxLength();
  238. trimString = col.trimString();
  239. allowNull = col.allowNull();
  240. defaultValue = col.defaultValue();
  241. }
  242. boolean isPublic = Modifier.isPublic(f.getModifiers());
  243. boolean reflectiveMatch = isPublic && !byAnnotationsOnly;
  244. if (reflectiveMatch || hasAnnotation) {
  245. FieldDefinition fieldDef = new FieldDefinition();
  246. fieldDef.field = f;
  247. fieldDef.columnName = columnName;
  248. fieldDef.isAutoIncrement = isAutoIncrement;
  249. fieldDef.isPrimaryKey = isPrimaryKey;
  250. fieldDef.maxLength = maxLength;
  251. fieldDef.trimString = trimString;
  252. fieldDef.allowNull = allowNull;
  253. fieldDef.defaultValue = defaultValue;
  254. fieldDef.dataType = ModelUtils.getDataType(fieldDef, strictTypeMapping);
  255. fields.add(fieldDef);
  256. }
  257. }
  258. List<String> primaryKey = Utils.newArrayList();
  259. for (FieldDefinition fieldDef : fields) {
  260. if (fieldDef.isPrimaryKey) {
  261. primaryKey.add(fieldDef.columnName);
  262. }
  263. }
  264. if (primaryKey.size() > 0) {
  265. setPrimaryKey(primaryKey);
  266. }
  267. }
  268. /**
  269. * Optionally truncates strings to the maximum length
  270. */
  271. private Object getValue(Object obj, FieldDefinition field) {
  272. Object value = field.getValue(obj);
  273. if (field.trimString && field.maxLength > 0) {
  274. if (value instanceof String) {
  275. // clip strings
  276. String s = (String) value;
  277. if (s.length() > field.maxLength) {
  278. return s.substring(0, field.maxLength);
  279. }
  280. return s;
  281. }
  282. return value;
  283. }
  284. // standard behavior
  285. return value;
  286. }
  287. long insert(Db db, Object obj, boolean returnKey) {
  288. SQLStatement stat = new SQLStatement(db);
  289. StatementBuilder buff = new StatementBuilder("INSERT INTO ");
  290. buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
  291. for (FieldDefinition field : fields) {
  292. buff.appendExceptFirst(", ");
  293. buff.append(db.getDialect().prepareColumnName(field.columnName));
  294. }
  295. buff.append(") VALUES(");
  296. buff.resetCount();
  297. for (FieldDefinition field : fields) {
  298. buff.appendExceptFirst(", ");
  299. buff.append('?');
  300. Object value = getValue(obj, field);
  301. stat.addParameter(value);
  302. }
  303. buff.append(')');
  304. stat.setSQL(buff.toString());
  305. StatementLogger.insert(stat.getSQL());
  306. if (returnKey) {
  307. return stat.executeInsert();
  308. }
  309. return stat.executeUpdate();
  310. }
  311. void merge(Db db, Object obj) {
  312. if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
  313. throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
  314. + " - no update possible");
  315. }
  316. SQLStatement stat = new SQLStatement(db);
  317. StatementBuilder buff = new StatementBuilder("MERGE INTO ");
  318. buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" (");
  319. buff.resetCount();
  320. for (FieldDefinition field : fields) {
  321. buff.appendExceptFirst(", ");
  322. buff.append(db.getDialect().prepareColumnName(field.columnName));
  323. }
  324. buff.append(") KEY(");
  325. buff.resetCount();
  326. for (FieldDefinition field : fields) {
  327. if (field.isPrimaryKey) {
  328. buff.appendExceptFirst(", ");
  329. buff.append(db.getDialect().prepareColumnName(field.columnName));
  330. }
  331. }
  332. buff.append(") ");
  333. buff.resetCount();
  334. buff.append("VALUES (");
  335. for (FieldDefinition field : fields) {
  336. buff.appendExceptFirst(", ");
  337. buff.append('?');
  338. Object value = getValue(obj, field);
  339. stat.addParameter(value);
  340. }
  341. buff.append(')');
  342. stat.setSQL(buff.toString());
  343. StatementLogger.merge(stat.getSQL());
  344. stat.executeUpdate();
  345. }
  346. void update(Db db, Object obj) {
  347. if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
  348. throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
  349. + " - no update possible");
  350. }
  351. SQLStatement stat = new SQLStatement(db);
  352. StatementBuilder buff = new StatementBuilder("UPDATE ");
  353. buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET ");
  354. buff.resetCount();
  355. for (FieldDefinition field : fields) {
  356. if (!field.isPrimaryKey) {
  357. buff.appendExceptFirst(", ");
  358. buff.append(db.getDialect().prepareColumnName(field.columnName));
  359. buff.append(" = ?");
  360. Object value = getValue(obj, field);
  361. stat.addParameter(value);
  362. }
  363. }
  364. Object alias = Utils.newObject(obj.getClass());
  365. Query<Object> query = Query.from(db, alias);
  366. boolean firstCondition = true;
  367. for (FieldDefinition field : fields) {
  368. if (field.isPrimaryKey) {
  369. Object aliasValue = field.getValue(alias);
  370. Object value = field.getValue(obj);
  371. if (!firstCondition) {
  372. query.addConditionToken(ConditionAndOr.AND);
  373. }
  374. firstCondition = false;
  375. query.addConditionToken(new Condition<Object>(aliasValue, value, CompareType.EQUAL));
  376. }
  377. }
  378. stat.setSQL(buff.toString());
  379. query.appendWhere(stat);
  380. StatementLogger.update(stat.getSQL());
  381. stat.executeUpdate();
  382. }
  383. void delete(Db db, Object obj) {
  384. if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
  385. throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
  386. + " - no update possible");
  387. }
  388. SQLStatement stat = new SQLStatement(db);
  389. StatementBuilder buff = new StatementBuilder("DELETE FROM ");
  390. buff.append(db.getDialect().prepareTableName(schemaName, tableName));
  391. buff.resetCount();
  392. Object alias = Utils.newObject(obj.getClass());
  393. Query<Object> query = Query.from(db, alias);
  394. boolean firstCondition = true;
  395. for (FieldDefinition field : fields) {
  396. if (field.isPrimaryKey) {
  397. Object aliasValue = field.getValue(alias);
  398. Object value = field.getValue(obj);
  399. if (!firstCondition) {
  400. query.addConditionToken(ConditionAndOr.AND);
  401. }
  402. firstCondition = false;
  403. query.addConditionToken(new Condition<Object>(aliasValue, value, CompareType.EQUAL));
  404. }
  405. }
  406. stat.setSQL(buff.toString());
  407. query.appendWhere(stat);
  408. StatementLogger.delete(stat.getSQL());
  409. stat.executeUpdate();
  410. }
  411. TableDefinition<T> createTableIfRequired(Db db) {
  412. if (!createTableIfRequired) {
  413. // skip table and index creation
  414. // but still check for upgrades
  415. db.upgradeTable(this);
  416. return this;
  417. }
  418. SQLDialect dialect = db.getDialect();
  419. SQLStatement stat = new SQLStatement(db);
  420. StatementBuilder buff;
  421. if (memoryTable && dialect.supportsMemoryTables()) {
  422. buff = new StatementBuilder("CREATE MEMORY TABLE IF NOT EXISTS ");
  423. } else {
  424. buff = new StatementBuilder("CREATE TABLE IF NOT EXISTS ");
  425. }
  426. buff.append(dialect.prepareTableName(schemaName, tableName)).append('(');
  427. for (FieldDefinition field : fields) {
  428. buff.appendExceptFirst(", ");
  429. buff.append(dialect.prepareColumnName(field.columnName)).append(' ').append(field.dataType);
  430. if (field.maxLength > 0) {
  431. buff.append('(').append(field.maxLength).append(')');
  432. }
  433. if (field.isAutoIncrement) {
  434. buff.append(" AUTO_INCREMENT");
  435. }
  436. if (!field.allowNull) {
  437. buff.append(" NOT NULL");
  438. }
  439. // default values
  440. if (!field.isAutoIncrement && !field.isPrimaryKey) {
  441. String dv = field.defaultValue;
  442. if (!StringUtils.isNullOrEmpty(dv)) {
  443. if (ModelUtils.isProperlyFormattedDefaultValue(dv)
  444. && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {
  445. buff.append(" DEFAULT " + dv);
  446. }
  447. }
  448. }
  449. }
  450. // primary key
  451. if (primaryKeyColumnNames != null && primaryKeyColumnNames.size() > 0) {
  452. buff.append(", PRIMARY KEY(");
  453. buff.resetCount();
  454. for (String n : primaryKeyColumnNames) {
  455. buff.appendExceptFirst(", ");
  456. buff.append(n);
  457. }
  458. buff.append(')');
  459. }
  460. buff.append(')');
  461. stat.setSQL(buff.toString());
  462. StatementLogger.create(stat.getSQL());
  463. stat.executeUpdate();
  464. // create indexes
  465. for (IndexDefinition index : indexes) {
  466. String sql = db.getDialect().prepareCreateIndex(schemaName, tableName, index);
  467. stat.setSQL(sql);
  468. StatementLogger.create(stat.getSQL());
  469. stat.executeUpdate();
  470. }
  471. // tables are created using IF NOT EXISTS
  472. // but we may still need to upgrade
  473. db.upgradeTable(this);
  474. return this;
  475. }
  476. /**
  477. * Retrieve list of columns from index definition.
  478. *
  479. * @param index
  480. * the index columns, separated by space
  481. * @return the column list
  482. */
  483. private List<String> getColumns(String index) {
  484. List<String> cols = Utils.newArrayList();
  485. if (index == null || index.length() == 0) {
  486. return null;
  487. }
  488. String[] cs = index.split("(,|\\s)");
  489. for (String c : cs) {
  490. if (c != null && c.trim().length() > 0) {
  491. cols.add(c.trim());
  492. }
  493. }
  494. if (cols.size() == 0) {
  495. return null;
  496. }
  497. return cols;
  498. }
  499. void mapObject(Object obj) {
  500. fieldMap.clear();
  501. initObject(obj, fieldMap);
  502. if (clazz.isAnnotationPresent(IQSchema.class)) {
  503. IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class);
  504. // setup schema name mapping, if properly annotated
  505. if (!StringUtils.isNullOrEmpty(schemaAnnotation.name())) {
  506. schemaName = schemaAnnotation.name();
  507. }
  508. }
  509. if (clazz.isAnnotationPresent(IQTable.class)) {
  510. IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
  511. // setup table name mapping, if properly annotated
  512. if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) {
  513. tableName = tableAnnotation.name();
  514. }
  515. // allow control over createTableIfRequired()
  516. createTableIfRequired = tableAnnotation.createIfRequired();
  517. // model version
  518. if (tableAnnotation.version() > 0) {
  519. tableVersion = tableAnnotation.version();
  520. }
  521. // setup the primary index, if properly annotated
  522. List<String> primaryKey = getColumns(tableAnnotation.primaryKey());
  523. if (primaryKey != null) {
  524. setPrimaryKey(primaryKey);
  525. }
  526. }
  527. if (clazz.isAnnotationPresent(IQIndex.class)) {
  528. IQIndex indexAnnotation = clazz.getAnnotation(IQIndex.class);
  529. // setup the indexes, if properly annotated
  530. addIndexes(IndexType.STANDARD, indexAnnotation.standard());
  531. addIndexes(IndexType.UNIQUE, indexAnnotation.unique());
  532. addIndexes(IndexType.HASH, indexAnnotation.hash());
  533. addIndexes(IndexType.UNIQUE_HASH, indexAnnotation.uniqueHash());
  534. }
  535. }
  536. void addIndexes(IndexType type, String[] indexes) {
  537. for (String index : indexes) {
  538. List<String> validatedColumns = getColumns(index);
  539. if (validatedColumns == null) {
  540. return;
  541. }
  542. addIndex(type, validatedColumns);
  543. }
  544. }
  545. List<IndexDefinition> getIndexes(IndexType type) {
  546. List<IndexDefinition> list = Utils.newArrayList();
  547. for (IndexDefinition def : indexes) {
  548. if (def.type.equals(type)) {
  549. list.add(def);
  550. }
  551. }
  552. return list;
  553. }
  554. void initObject(Object obj, Map<Object, FieldDefinition> map) {
  555. for (FieldDefinition def : fields) {
  556. Object newValue = def.initWithNewObject(obj);
  557. map.put(newValue, def);
  558. }
  559. }
  560. void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map) {
  561. for (FieldDefinition def : fields) {
  562. Object newValue = def.initWithNewObject(obj);
  563. SelectColumn<T> column = new SelectColumn<T>(table, def);
  564. map.put(newValue, column);
  565. }
  566. }
  567. void readRow(Object item, ResultSet rs) {
  568. for (int i = 0; i < fields.size(); i++) {
  569. FieldDefinition def = fields.get(i);
  570. Object o = def.read(rs, i + 1);
  571. def.setValue(item, o);
  572. }
  573. }
  574. void appendSelectList(SQLStatement stat) {
  575. for (int i = 0; i < fields.size(); i++) {
  576. if (i > 0) {
  577. stat.appendSQL(", ");
  578. }
  579. FieldDefinition def = fields.get(i);
  580. stat.appendColumn(def.columnName);
  581. }
  582. }
  583. <Y, X> void appendSelectList(SQLStatement stat, Query<Y> query, X x) {
  584. for (int i = 0; i < fields.size(); i++) {
  585. if (i > 0) {
  586. stat.appendSQL(", ");
  587. }
  588. FieldDefinition def = fields.get(i);
  589. Object obj = def.getValue(x);
  590. query.appendSQL(stat, obj);
  591. }
  592. }
  593. <Y, X> void copyAttributeValues(Query<Y> query, X to, X map) {
  594. for (FieldDefinition def : fields) {
  595. Object obj = def.getValue(map);
  596. SelectColumn<Y> col = query.getSelectColumn(obj);
  597. Object value = col.getCurrentValue();
  598. def.setValue(to, value);
  599. }
  600. }
  601. }