123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- /*
- * Copyright 2004-2011 H2 Group.
- * Copyright 2011 James Moger.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
- package com.iciql;
-
- import java.lang.reflect.Field;
- import java.lang.reflect.Modifier;
- import java.sql.ResultSet;
- import java.sql.SQLException;
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.IdentityHashMap;
- import java.util.List;
- import java.util.Map;
-
- import com.iciql.Iciql.IndexType;
- import com.iciql.Iciql.IQColumn;
- import com.iciql.Iciql.IQIndex;
- import com.iciql.Iciql.IQSchema;
- import com.iciql.Iciql.IQTable;
- import com.iciql.util.StatementBuilder;
- import com.iciql.util.StatementLogger;
- import com.iciql.util.StringUtils;
- import com.iciql.util.Utils;
-
- /**
- * A table definition contains the index definitions of a table, the field
- * definitions, the table name, and other meta data.
- *
- * @param <T>
- * the table type
- */
-
- class TableDefinition<T> {
-
- /**
- * The meta data of an index.
- */
-
- static class IndexDefinition {
- IndexType type;
- String indexName;
-
- List<String> columnNames;
- }
-
- /**
- * The meta data of a field.
- */
-
- static class FieldDefinition {
- String columnName;
- Field field;
- String dataType;
- int maxLength;
- boolean isPrimaryKey;
- boolean isAutoIncrement;
- boolean trimString;
- boolean allowNull;
- String defaultValue;
-
- Object getValue(Object obj) {
- try {
- return field.get(obj);
- } catch (Exception e) {
- throw new IciqlException(e);
- }
- }
-
- Object initWithNewObject(Object obj) {
- Object o = Utils.newObject(field.getType());
- setValue(obj, o);
- return o;
- }
-
- void setValue(Object obj, Object o) {
- try {
- if (!field.isAccessible()) {
- field.setAccessible(true);
- }
- o = Utils.convert(o, field.getType());
- field.set(obj, o);
- } catch (Exception e) {
- throw new IciqlException(e);
- }
- }
-
- Object read(ResultSet rs, int columnIndex) {
- try {
- return rs.getObject(columnIndex);
- } catch (SQLException e) {
- throw new IciqlException(e);
- }
- }
- }
-
- String schemaName;
- String tableName;
- int tableVersion;
- private boolean createTableIfRequired = true;
- private Class<T> clazz;
- private ArrayList<FieldDefinition> fields = Utils.newArrayList();
- private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();
-
- private List<String> primaryKeyColumnNames;
- private ArrayList<IndexDefinition> indexes = Utils.newArrayList();
- private boolean memoryTable;
-
- TableDefinition(Class<T> clazz) {
- this.clazz = clazz;
- schemaName = null;
- tableName = clazz.getSimpleName();
- }
-
- Class<T> getModelClass() {
- return clazz;
- }
-
- List<FieldDefinition> getFields() {
- return fields;
- }
-
- FieldDefinition getField(String name) {
- for (FieldDefinition field:fields) {
- if (field.columnName.equalsIgnoreCase(name)) {
- return field;
- }
- }
- return null;
- }
-
- void setSchemaName(String schemaName) {
- this.schemaName = schemaName;
- }
-
- void setTableName(String tableName) {
- this.tableName = tableName;
- }
-
- /**
- * Define a primary key by the specified model fields.
- *
- * @param modelFields
- * the ordered list of model fields
- */
- void setPrimaryKey(Object[] modelFields) {
- List<String> columnNames = mapColumnNames(modelFields);
- setPrimaryKey(columnNames);
- }
-
- /**
- * Define a primary key by the specified column names.
- *
- * @param columnNames
- * the ordered list of column names
- */
- void setPrimaryKey(List<String> columnNames) {
- primaryKeyColumnNames = Utils.newArrayList(columnNames);
- // set isPrimaryKey flag for all field definitions
- for (FieldDefinition fieldDefinition : fieldMap.values()) {
- fieldDefinition.isPrimaryKey = this.primaryKeyColumnNames.contains(fieldDefinition.columnName);
- }
- }
-
- <A> String getColumnName(A fieldObject) {
- FieldDefinition def = fieldMap.get(fieldObject);
- return def == null ? null : def.columnName;
- }
-
- private ArrayList<String> mapColumnNames(Object[] columns) {
- ArrayList<String> columnNames = Utils.newArrayList();
- for (Object column : columns) {
- columnNames.add(getColumnName(column));
- }
- return columnNames;
- }
-
- /**
- * Defines an index with the specified model fields.
- *
- * @param type
- * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
- * @param modelFields
- * the ordered list of model fields
- */
- void addIndex(IndexType type, Object[] modelFields) {
- List<String> columnNames = mapColumnNames(modelFields);
- addIndex(type, columnNames);
- }
-
- /**
- * Defines an index with the specified column names.
- *
- * @param type
- * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
- * @param columnNames
- * the ordered list of column names
- */
- void addIndex(IndexType type, List<String> columnNames) {
- IndexDefinition index = new IndexDefinition();
- index.indexName = tableName + "_" + indexes.size();
- index.columnNames = Utils.newArrayList(columnNames);
- index.type = type;
- indexes.add(index);
- }
-
- public void setColumnName(Object column, String columnName) {
- FieldDefinition def = fieldMap.get(column);
- if (def != null) {
- def.columnName = columnName;
- }
- }
-
- public void setMaxLength(Object column, int maxLength) {
- FieldDefinition def = fieldMap.get(column);
- if (def != null) {
- def.maxLength = maxLength;
- }
- }
-
- void mapFields() {
- boolean byAnnotationsOnly = false;
- boolean inheritColumns = false;
- boolean strictTypeMapping = false;
- if (clazz.isAnnotationPresent(IQTable.class)) {
- IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
- byAnnotationsOnly = tableAnnotation.annotationsOnly();
- inheritColumns = tableAnnotation.inheritColumns();
- strictTypeMapping = tableAnnotation.strictTypeMapping();
- }
-
- List<Field> classFields = Utils.newArrayList();
- classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
- if (inheritColumns) {
- Class<?> superClass = clazz.getSuperclass();
- classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));
- }
-
- for (Field f : classFields) {
- // default to field name
- String columnName = f.getName();
- boolean isAutoIncrement = false;
- boolean isPrimaryKey = false;
- int maxLength = 0;
- boolean trimString = false;
- boolean allowNull = true;
- String defaultValue = "";
- boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);
- if (hasAnnotation) {
- IQColumn col = f.getAnnotation(IQColumn.class);
- if (!StringUtils.isNullOrEmpty(col.name())) {
- columnName = col.name();
- }
- isAutoIncrement = col.autoIncrement();
- isPrimaryKey = col.primaryKey();
- maxLength = col.maxLength();
- trimString = col.trimString();
- allowNull = col.allowNull();
- defaultValue = col.defaultValue();
- }
- boolean isPublic = Modifier.isPublic(f.getModifiers());
- boolean reflectiveMatch = isPublic && !byAnnotationsOnly;
- if (reflectiveMatch || hasAnnotation) {
- FieldDefinition fieldDef = new FieldDefinition();
- fieldDef.field = f;
- fieldDef.columnName = columnName;
- fieldDef.isAutoIncrement = isAutoIncrement;
- fieldDef.isPrimaryKey = isPrimaryKey;
- fieldDef.maxLength = maxLength;
- fieldDef.trimString = trimString;
- fieldDef.allowNull = allowNull;
- fieldDef.defaultValue = defaultValue;
- fieldDef.dataType = ModelUtils.getDataType(fieldDef, strictTypeMapping);
- fields.add(fieldDef);
- }
- }
- List<String> primaryKey = Utils.newArrayList();
- for (FieldDefinition fieldDef : fields) {
- if (fieldDef.isPrimaryKey) {
- primaryKey.add(fieldDef.columnName);
- }
- }
- if (primaryKey.size() > 0) {
- setPrimaryKey(primaryKey);
- }
- }
-
- /**
- * Optionally truncates strings to the maximum length
- */
- private Object getValue(Object obj, FieldDefinition field) {
- Object value = field.getValue(obj);
- if (field.trimString && field.maxLength > 0) {
- if (value instanceof String) {
- // clip strings
- String s = (String) value;
- if (s.length() > field.maxLength) {
- return s.substring(0, field.maxLength);
- }
- return s;
- }
- return value;
- }
- // standard behavior
- return value;
- }
-
- long insert(Db db, Object obj, boolean returnKey) {
- SQLStatement stat = new SQLStatement(db);
- StatementBuilder buff = new StatementBuilder("INSERT INTO ");
- buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
- for (FieldDefinition field : fields) {
- buff.appendExceptFirst(", ");
- buff.append(db.getDialect().prepareColumnName(field.columnName));
- }
- buff.append(") VALUES(");
- buff.resetCount();
- for (FieldDefinition field : fields) {
- buff.appendExceptFirst(", ");
- buff.append('?');
- Object value = getValue(obj, field);
- stat.addParameter(value);
- }
- buff.append(')');
- stat.setSQL(buff.toString());
- StatementLogger.insert(stat.getSQL());
- if (returnKey) {
- return stat.executeInsert();
- }
- return stat.executeUpdate();
- }
-
- void merge(Db db, Object obj) {
- if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
- throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
- + " - no update possible");
- }
- SQLStatement stat = new SQLStatement(db);
- StatementBuilder buff = new StatementBuilder("MERGE INTO ");
- buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" (");
- buff.resetCount();
- for (FieldDefinition field : fields) {
- buff.appendExceptFirst(", ");
- buff.append(db.getDialect().prepareColumnName(field.columnName));
- }
- buff.append(") KEY(");
- buff.resetCount();
- for (FieldDefinition field : fields) {
- if (field.isPrimaryKey) {
- buff.appendExceptFirst(", ");
- buff.append(db.getDialect().prepareColumnName(field.columnName));
- }
- }
- buff.append(") ");
- buff.resetCount();
- buff.append("VALUES (");
- for (FieldDefinition field : fields) {
- buff.appendExceptFirst(", ");
- buff.append('?');
- Object value = getValue(obj, field);
- stat.addParameter(value);
- }
- buff.append(')');
- stat.setSQL(buff.toString());
-
- StatementLogger.merge(stat.getSQL());
- stat.executeUpdate();
- }
-
- void update(Db db, Object obj) {
- if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
- throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
- + " - no update possible");
- }
- SQLStatement stat = new SQLStatement(db);
- StatementBuilder buff = new StatementBuilder("UPDATE ");
- buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET ");
- buff.resetCount();
-
- for (FieldDefinition field : fields) {
- if (!field.isPrimaryKey) {
- buff.appendExceptFirst(", ");
- buff.append(db.getDialect().prepareColumnName(field.columnName));
- buff.append(" = ?");
- Object value = getValue(obj, field);
- stat.addParameter(value);
- }
- }
- Object alias = Utils.newObject(obj.getClass());
- Query<Object> query = Query.from(db, alias);
- boolean firstCondition = true;
- for (FieldDefinition field : fields) {
- if (field.isPrimaryKey) {
- Object aliasValue = field.getValue(alias);
- Object value = field.getValue(obj);
- if (!firstCondition) {
- query.addConditionToken(ConditionAndOr.AND);
- }
- firstCondition = false;
- query.addConditionToken(new Condition<Object>(aliasValue, value, CompareType.EQUAL));
- }
- }
- stat.setSQL(buff.toString());
- query.appendWhere(stat);
- StatementLogger.update(stat.getSQL());
- stat.executeUpdate();
- }
-
- void delete(Db db, Object obj) {
- if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {
- throw new IllegalStateException("No primary key columns defined " + "for table " + obj.getClass()
- + " - no update possible");
- }
- SQLStatement stat = new SQLStatement(db);
- StatementBuilder buff = new StatementBuilder("DELETE FROM ");
- buff.append(db.getDialect().prepareTableName(schemaName, tableName));
- buff.resetCount();
- Object alias = Utils.newObject(obj.getClass());
- Query<Object> query = Query.from(db, alias);
- boolean firstCondition = true;
- for (FieldDefinition field : fields) {
- if (field.isPrimaryKey) {
- Object aliasValue = field.getValue(alias);
- Object value = field.getValue(obj);
- if (!firstCondition) {
- query.addConditionToken(ConditionAndOr.AND);
- }
- firstCondition = false;
- query.addConditionToken(new Condition<Object>(aliasValue, value, CompareType.EQUAL));
- }
- }
- stat.setSQL(buff.toString());
- query.appendWhere(stat);
- StatementLogger.delete(stat.getSQL());
- stat.executeUpdate();
- }
-
- TableDefinition<T> createTableIfRequired(Db db) {
- if (!createTableIfRequired) {
- // skip table and index creation
- // but still check for upgrades
- db.upgradeTable(this);
- return this;
- }
- SQLDialect dialect = db.getDialect();
- SQLStatement stat = new SQLStatement(db);
- StatementBuilder buff;
- if (memoryTable && dialect.supportsMemoryTables()) {
- buff = new StatementBuilder("CREATE MEMORY TABLE IF NOT EXISTS ");
- } else {
- buff = new StatementBuilder("CREATE TABLE IF NOT EXISTS ");
- }
-
- buff.append(dialect.prepareTableName(schemaName, tableName)).append('(');
-
- for (FieldDefinition field : fields) {
- buff.appendExceptFirst(", ");
- buff.append(dialect.prepareColumnName(field.columnName)).append(' ').append(field.dataType);
- if (field.maxLength > 0) {
- buff.append('(').append(field.maxLength).append(')');
- }
-
- if (field.isAutoIncrement) {
- buff.append(" AUTO_INCREMENT");
- }
-
- if (!field.allowNull) {
- buff.append(" NOT NULL");
- }
-
- // default values
- if (!field.isAutoIncrement && !field.isPrimaryKey) {
- String dv = field.defaultValue;
- if (!StringUtils.isNullOrEmpty(dv)) {
- if (ModelUtils.isProperlyFormattedDefaultValue(dv)
- && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {
- buff.append(" DEFAULT " + dv);
- }
- }
- }
- }
-
- // primary key
- if (primaryKeyColumnNames != null && primaryKeyColumnNames.size() > 0) {
- buff.append(", PRIMARY KEY(");
- buff.resetCount();
- for (String n : primaryKeyColumnNames) {
- buff.appendExceptFirst(", ");
- buff.append(n);
- }
- buff.append(')');
- }
- buff.append(')');
- stat.setSQL(buff.toString());
- StatementLogger.create(stat.getSQL());
- stat.executeUpdate();
-
- // create indexes
- for (IndexDefinition index : indexes) {
- String sql = db.getDialect().prepareCreateIndex(schemaName, tableName, index);
- stat.setSQL(sql);
- StatementLogger.create(stat.getSQL());
- stat.executeUpdate();
- }
-
- // tables are created using IF NOT EXISTS
- // but we may still need to upgrade
- db.upgradeTable(this);
- return this;
- }
-
- /**
- * Retrieve list of columns from index definition.
- *
- * @param index
- * the index columns, separated by space
- * @return the column list
- */
- private List<String> getColumns(String index) {
- List<String> cols = Utils.newArrayList();
- if (index == null || index.length() == 0) {
- return null;
- }
- String[] cs = index.split("(,|\\s)");
- for (String c : cs) {
- if (c != null && c.trim().length() > 0) {
- cols.add(c.trim());
- }
- }
- if (cols.size() == 0) {
- return null;
- }
- return cols;
- }
-
- void mapObject(Object obj) {
- fieldMap.clear();
- initObject(obj, fieldMap);
-
- if (clazz.isAnnotationPresent(IQSchema.class)) {
- IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class);
- // setup schema name mapping, if properly annotated
- if (!StringUtils.isNullOrEmpty(schemaAnnotation.name())) {
- schemaName = schemaAnnotation.name();
- }
- }
-
- if (clazz.isAnnotationPresent(IQTable.class)) {
- IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
-
- // setup table name mapping, if properly annotated
- if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) {
- tableName = tableAnnotation.name();
- }
-
- // allow control over createTableIfRequired()
- createTableIfRequired = tableAnnotation.createIfRequired();
-
- // model version
- if (tableAnnotation.version() > 0) {
- tableVersion = tableAnnotation.version();
- }
-
- // setup the primary index, if properly annotated
- List<String> primaryKey = getColumns(tableAnnotation.primaryKey());
- if (primaryKey != null) {
- setPrimaryKey(primaryKey);
- }
- }
-
- if (clazz.isAnnotationPresent(IQIndex.class)) {
- IQIndex indexAnnotation = clazz.getAnnotation(IQIndex.class);
-
- // setup the indexes, if properly annotated
- addIndexes(IndexType.STANDARD, indexAnnotation.standard());
- addIndexes(IndexType.UNIQUE, indexAnnotation.unique());
- addIndexes(IndexType.HASH, indexAnnotation.hash());
- addIndexes(IndexType.UNIQUE_HASH, indexAnnotation.uniqueHash());
- }
- }
-
- void addIndexes(IndexType type, String[] indexes) {
- for (String index : indexes) {
- List<String> validatedColumns = getColumns(index);
- if (validatedColumns == null) {
- return;
- }
- addIndex(type, validatedColumns);
- }
- }
-
- List<IndexDefinition> getIndexes(IndexType type) {
- List<IndexDefinition> list = Utils.newArrayList();
- for (IndexDefinition def : indexes) {
- if (def.type.equals(type)) {
- list.add(def);
- }
- }
- return list;
- }
-
- void initObject(Object obj, Map<Object, FieldDefinition> map) {
- for (FieldDefinition def : fields) {
- Object newValue = def.initWithNewObject(obj);
- map.put(newValue, def);
- }
- }
-
- void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map) {
- for (FieldDefinition def : fields) {
- Object newValue = def.initWithNewObject(obj);
- SelectColumn<T> column = new SelectColumn<T>(table, def);
- map.put(newValue, column);
- }
- }
-
- void readRow(Object item, ResultSet rs) {
- for (int i = 0; i < fields.size(); i++) {
- FieldDefinition def = fields.get(i);
- Object o = def.read(rs, i + 1);
- def.setValue(item, o);
- }
- }
-
- void appendSelectList(SQLStatement stat) {
- for (int i = 0; i < fields.size(); i++) {
- if (i > 0) {
- stat.appendSQL(", ");
- }
- FieldDefinition def = fields.get(i);
- stat.appendColumn(def.columnName);
- }
- }
-
- <Y, X> void appendSelectList(SQLStatement stat, Query<Y> query, X x) {
- for (int i = 0; i < fields.size(); i++) {
- if (i > 0) {
- stat.appendSQL(", ");
- }
- FieldDefinition def = fields.get(i);
- Object obj = def.getValue(x);
- query.appendSQL(stat, obj);
- }
- }
-
- <Y, X> void copyAttributeValues(Query<Y> query, X to, X map) {
- for (FieldDefinition def : fields) {
- Object obj = def.getValue(map);
- SelectColumn<Y> col = query.getSelectColumn(obj);
- Object value = col.getCurrentValue();
- def.setValue(to, value);
- }
- }
-
- }
|