-/*\r
- * Copyright 2004-2011 H2 Group.\r
- * Copyright 2011 James Moger.\r
- * Copyright 2012 Frédéric Gaillard.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.iciql;\r
-\r
-import java.sql.DatabaseMetaData;\r
-import java.sql.SQLException;\r
-import java.text.MessageFormat;\r
-import java.text.SimpleDateFormat;\r
-import java.util.Map;\r
-import java.util.concurrent.ConcurrentHashMap;\r
-\r
-import com.iciql.Iciql.ConstraintDeleteType;\r
-import com.iciql.Iciql.ConstraintUpdateType;\r
-import com.iciql.Iciql.DataTypeAdapter;\r
-import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;\r
-import com.iciql.TableDefinition.ConstraintUniqueDefinition;\r
-import com.iciql.TableDefinition.FieldDefinition;\r
-import com.iciql.TableDefinition.IndexDefinition;\r
-import com.iciql.util.IciqlLogger;\r
-import com.iciql.util.StatementBuilder;\r
-import com.iciql.util.StringUtils;\r
-import com.iciql.util.Utils;\r
-\r
-/**\r
- * Default implementation of an SQL dialect.\r
- */\r
-public class SQLDialectDefault implements SQLDialect {\r
-\r
- final String LITERAL = "'";\r
-\r
- float databaseVersion;\r
- int databaseMajorVersion;\r
- int databaseMinorVersion;\r
- String databaseName;\r
- String productVersion;\r
- Map<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>> typeAdapters;\r
-\r
- public SQLDialectDefault() {\r
- typeAdapters = new ConcurrentHashMap<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>>();\r
- }\r
-\r
- @Override\r
- public String toString() {\r
- return getClass().getName() + ": " + databaseName + " " + productVersion;\r
- }\r
-\r
- @Override\r
- public void configureDialect(String databaseName, DatabaseMetaData data) {\r
- this.databaseName = databaseName;\r
- try {\r
- databaseMajorVersion = data.getDatabaseMajorVersion();\r
- databaseMinorVersion = data.getDatabaseMinorVersion();\r
- databaseVersion = Float.parseFloat(databaseMajorVersion + "."\r
- + databaseMinorVersion);\r
- productVersion = data.getDatabaseProductVersion();\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- @Override\r
- public boolean supportsSavePoints() {\r
- return true;\r
- }\r
-\r
- /**\r
- * Allows subclasses to change the type of a column for a CREATE statement.\r
- *\r
- * @param sqlType\r
- * @return the SQL type or a preferred alternative\r
- */\r
- @Override\r
- public String convertSqlType(String sqlType) {\r
- return sqlType;\r
- }\r
-\r
- @Override\r
- public Class<? extends java.util.Date> getDateTimeClass() {\r
- return java.util.Date.class;\r
- }\r
-\r
- @Override\r
- public String prepareTableName(String schemaName, String tableName) {\r
- if (StringUtils.isNullOrEmpty(schemaName)) {\r
- return tableName;\r
- }\r
- return schemaName + "." + tableName;\r
- }\r
-\r
- @Override\r
- public String prepareColumnName(String name) {\r
- return name;\r
- }\r
-\r
- @Override\r
- public <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def) {\r
- StatementBuilder buff = new StatementBuilder("DROP TABLE IF EXISTS "\r
- + prepareTableName(def.schemaName, def.tableName));\r
- stat.setSQL(buff.toString());\r
- return;\r
- }\r
-\r
- protected <T> String prepareCreateTable(TableDefinition<T> def) {\r
- return "CREATE TABLE";\r
- }\r
-\r
- @Override\r
- public <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def) {\r
- StatementBuilder buff = new StatementBuilder();\r
- buff.append(prepareCreateTable(def));\r
- buff.append(" ");\r
- buff.append(prepareTableName(def.schemaName, def.tableName)).append('(');\r
-\r
- boolean hasIdentityColumn = false;\r
- for (FieldDefinition field : def.fields) {\r
- buff.appendExceptFirst(", ");\r
- buff.append(prepareColumnName(field.columnName)).append(' ');\r
- String dataType = field.dataType;\r
- if (dataType.equals("VARCHAR")) {\r
- // check to see if we should use VARCHAR or CLOB\r
- if (field.length <= 0) {\r
- dataType = "CLOB";\r
- }\r
- buff.append(convertSqlType(dataType));\r
- if (field.length > 0) {\r
- buff.append('(').append(field.length).append(')');\r
- }\r
- } else if (dataType.equals("DECIMAL")) {\r
- // DECIMAL(precision,scale)\r
- buff.append(convertSqlType(dataType));\r
- if (field.length > 0) {\r
- buff.append('(').append(field.length);\r
- if (field.scale > 0) {\r
- buff.append(',').append(field.scale);\r
- }\r
- buff.append(')');\r
- }\r
- } else {\r
- // other\r
- hasIdentityColumn |= prepareColumnDefinition(buff, convertSqlType(dataType),\r
- field.isAutoIncrement, field.isPrimaryKey);\r
- }\r
-\r
- // default values\r
- if (!field.isAutoIncrement && !field.isPrimaryKey) {\r
- String dv = field.defaultValue;\r
- if (!StringUtils.isNullOrEmpty(dv)) {\r
- if (ModelUtils.isProperlyFormattedDefaultValue(dv)\r
- && ModelUtils.isValidDefaultValue(field.field.getType(), dv)) {\r
- buff.append(" DEFAULT " + dv);\r
- }\r
- }\r
- }\r
-\r
- if (!field.nullable) {\r
- buff.append(" NOT NULL");\r
- }\r
- }\r
-\r
- // if table does not have identity column then specify primary key\r
- if (!hasIdentityColumn) {\r
- if (def.primaryKeyColumnNames != null && def.primaryKeyColumnNames.size() > 0) {\r
- buff.append(", PRIMARY KEY(");\r
- buff.resetCount();\r
- for (String n : def.primaryKeyColumnNames) {\r
- buff.appendExceptFirst(", ");\r
- buff.append(prepareColumnName(n));\r
- }\r
- buff.append(')');\r
- }\r
- }\r
- buff.append(')');\r
- stat.setSQL(buff.toString());\r
- }\r
-\r
- @Override\r
- public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {\r
- StatementBuilder buff = new StatementBuilder("DROP VIEW "\r
- + prepareTableName(def.schemaName, def.tableName));\r
- stat.setSQL(buff.toString());\r
- return;\r
- }\r
-\r
- protected <T> String prepareCreateView(TableDefinition<T> def) {\r
- return "CREATE VIEW";\r
- }\r
-\r
- @Override\r
- public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def) {\r
- StatementBuilder buff = new StatementBuilder();\r
- buff.append(" FROM ");\r
- buff.append(prepareTableName(def.schemaName, def.viewTableName));\r
-\r
- StatementBuilder where = new StatementBuilder();\r
- for (FieldDefinition field : def.fields) {\r
- if (!StringUtils.isNullOrEmpty(field.constraint)) {\r
- where.appendExceptFirst(", ");\r
- String col = prepareColumnName(field.columnName);\r
- String constraint = field.constraint.replace("{0}", col).replace("this", col);\r
- where.append(constraint);\r
- }\r
- }\r
- if (where.length() > 0) {\r
- buff.append(" WHERE ");\r
- buff.append(where.toString());\r
- }\r
-\r
- prepareCreateView(stat, def, buff.toString());\r
- }\r
-\r
- @Override\r
- public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere) {\r
- StatementBuilder buff = new StatementBuilder();\r
- buff.append(prepareCreateView(def));\r
- buff.append(" ");\r
- buff.append(prepareTableName(def.schemaName, def.tableName));\r
-\r
- buff.append(" AS SELECT ");\r
- for (FieldDefinition field : def.fields) {\r
- buff.appendExceptFirst(", ");\r
- buff.append(prepareColumnName(field.columnName));\r
- }\r
- buff.append(fromWhere);\r
- stat.setSQL(buff.toString());\r
- }\r
-\r
- protected boolean isIntegerType(String dataType) {\r
- if ("INT".equals(dataType)) {\r
- return true;\r
- } else if ("BIGINT".equals(dataType)) {\r
- return true;\r
- } else if ("TINYINT".equals(dataType)) {\r
- return true;\r
- } else if ("SMALLINT".equals(dataType)) {\r
- return true;\r
- }\r
- return false;\r
- }\r
-\r
- protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,\r
- boolean isAutoIncrement, boolean isPrimaryKey) {\r
- buff.append(dataType);\r
- if (isAutoIncrement) {\r
- buff.append(" AUTO_INCREMENT");\r
- }\r
- return false;\r
- }\r
-\r
- @Override\r
- public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName,\r
- IndexDefinition index) {\r
- StatementBuilder buff = new StatementBuilder();\r
- buff.append("CREATE ");\r
- switch (index.type) {\r
- case UNIQUE:\r
- buff.append("UNIQUE ");\r
- break;\r
- case UNIQUE_HASH:\r
- buff.append("UNIQUE ");\r
- break;\r
- default:\r
- IciqlLogger.warn("{0} does not support hash indexes", getClass().getSimpleName());\r
- }\r
- buff.append("INDEX ");\r
- buff.append(index.indexName);\r
- buff.append(" ON ");\r
- // FIXME maybe we can use schemaName ?\r
- // buff.append(prepareTableName(schemaName, tableName));\r
- buff.append(tableName);\r
- buff.append("(");\r
- for (String col : index.columnNames) {\r
- buff.appendExceptFirst(", ");\r
- buff.append(prepareColumnName(col));\r
- }\r
- buff.append(") ");\r
-\r
- stat.setSQL(buff.toString().trim());\r
- }\r
-\r
- /**\r
- * PostgreSQL and Derby do not support the SQL2003 MERGE syntax, but we can\r
- * use a trick to insert a row if it does not exist and call update() in\r
- * Db.merge() if the affected row count is 0.\r
- * <p>\r
- * Databases that do support a MERGE syntax should override this method.\r
- * <p>\r
- * http://stackoverflow.com/questions/407688\r
- */\r
- @Override\r
- public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,\r
- TableDefinition<T> def, Object obj) {\r
- StatementBuilder buff = new StatementBuilder("INSERT INTO ");\r
- buff.append(prepareTableName(schemaName, tableName));\r
- buff.append(" (");\r
- buff.resetCount();\r
- for (FieldDefinition field : def.fields) {\r
- buff.appendExceptFirst(", ");\r
- buff.append(prepareColumnName(field.columnName));\r
- }\r
- buff.append(") (SELECT ");\r
- buff.resetCount();\r
- for (FieldDefinition field : def.fields) {\r
- buff.appendExceptFirst(", ");\r
- buff.append('?');\r
- Object value = def.getValue(obj, field);\r
- Object parameter = serialize(value, field.typeAdapter);\r
- stat.addParameter(parameter);\r
- }\r
- buff.append(" FROM ");\r
- buff.append(prepareTableName(schemaName, tableName));\r
- buff.append(" WHERE ");\r
- buff.resetCount();\r
- for (FieldDefinition field : def.fields) {\r
- if (field.isPrimaryKey) {\r
- buff.appendExceptFirst(" AND ");\r
- buff.append(MessageFormat.format("{0} = ?", prepareColumnName(field.columnName)));\r
- Object value = def.getValue(obj, field);\r
- Object parameter = serialize(value, field.typeAdapter);\r
- stat.addParameter(parameter);\r
- }\r
- }\r
- buff.append(" HAVING count(*)=0)");\r
- stat.setSQL(buff.toString());\r
- }\r
-\r
- @Override\r
- public void appendLimitOffset(SQLStatement stat, long limit, long offset) {\r
- if (limit > 0) {\r
- stat.appendSQL(" LIMIT " + limit);\r
- }\r
- if (offset > 0) {\r
- stat.appendSQL(" OFFSET " + offset);\r
- }\r
- }\r
-\r
- @Override\r
- public void registerAdapter(DataTypeAdapter<?> typeAdapter) {\r
- typeAdapters.put((Class<? extends DataTypeAdapter<?>>) typeAdapter.getClass(), typeAdapter);\r
- }\r
-\r
- @Override\r
- public DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter) {\r
- DataTypeAdapter<?> dtt = typeAdapters.get(typeAdapter);\r
- if (dtt == null) {\r
- dtt = Utils.newObject(typeAdapter);\r
- typeAdapters.put(typeAdapter, dtt);\r
- }\r
- return dtt;\r
- }\r
-\r
- @SuppressWarnings("unchecked")\r
- @Override\r
- public <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter) {\r
- if (typeAdapter == null) {\r
- // pass-through\r
- return value;\r
- }\r
-\r
- DataTypeAdapter<T> dtt = (DataTypeAdapter<T>) getAdapter(typeAdapter);\r
- return dtt.serialize(value);\r
- }\r
-\r
- @Override\r
- public Object deserialize(Object value, Class<? extends DataTypeAdapter<?>> typeAdapter) {\r
- DataTypeAdapter<?> dtt = typeAdapters.get(typeAdapter);\r
- if (dtt == null) {\r
- dtt = Utils.newObject(typeAdapter);\r
- typeAdapters.put(typeAdapter, dtt);\r
- }\r
-\r
- return dtt.deserialize(value);\r
- }\r
-\r
- @Override\r
- public String prepareStringParameter(Object o) {\r
- if (o instanceof String) {\r
- return LITERAL + o.toString().replace(LITERAL, "''") + LITERAL;\r
- } else if (o instanceof Character) {\r
- return LITERAL + o.toString() + LITERAL;\r
- } else if (o instanceof java.sql.Time) {\r
- return LITERAL + new SimpleDateFormat("HH:mm:ss").format(o) + LITERAL;\r
- } else if (o instanceof java.sql.Date) {\r
- return LITERAL + new SimpleDateFormat("yyyy-MM-dd").format(o) + LITERAL;\r
- } else if (o instanceof java.util.Date) {\r
- return LITERAL + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(o) + LITERAL;\r
- }\r
- return o.toString();\r
- }\r
-\r
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.SQLException;
+import java.text.MessageFormat;
+import java.text.SimpleDateFormat;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import com.iciql.Iciql.ConstraintDeleteType;
+import com.iciql.Iciql.ConstraintUpdateType;
+import com.iciql.Iciql.DataTypeAdapter;
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;
+import com.iciql.TableDefinition.FieldDefinition;
+import com.iciql.TableDefinition.IndexDefinition;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.StatementBuilder;
+import com.iciql.util.StringUtils;
+import com.iciql.util.Utils;
+
+/**
+ * Default implementation of an SQL dialect.
+ */
+public class SQLDialectDefault implements SQLDialect {
+
+ final String LITERAL = "'";
+
+ float databaseVersion;
+ int databaseMajorVersion;
+ int databaseMinorVersion;
+ String databaseName;
+ String productVersion;
+ Map<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>> typeAdapters;
+
+ public SQLDialectDefault() {
+ typeAdapters = new ConcurrentHashMap<Class<? extends DataTypeAdapter<?>>, DataTypeAdapter<?>>();
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + ": " + databaseName + " " + productVersion;
+ }
+
+ @Override
+ public void configureDialect(Db db) {
+ Connection conn = db.getConnection();
+ DatabaseMetaData data = null;
+ try {
+ data = conn.getMetaData();
+ databaseName = data.getDatabaseProductName();
+ databaseMajorVersion = data.getDatabaseMajorVersion();
+ databaseMinorVersion = data.getDatabaseMinorVersion();
+ databaseVersion = Float.parseFloat(databaseMajorVersion + "."
+ + databaseMinorVersion);
+ productVersion = data.getDatabaseProductVersion();
+ } catch (SQLException e) {
+ throw new IciqlException(e, "failed to retrieve database metadata!");
+ }
+ }
+
+ @Override
+ public boolean supportsSavePoints() {
+ return true;
+ }
+
+ /**
+ * Allows subclasses to change the type of a column for a CREATE statement.
+ *
+ * @param sqlType
+ * @return the SQL type or a preferred alternative
+ */
+ @Override
+ public String convertSqlType(String sqlType) {
+ return sqlType;
+ }
+
+ @Override
+ public Class<? extends java.util.Date> getDateTimeClass() {
+ return java.util.Date.class;
+ }
+
+ @Override
+ public String prepareTableName(String schemaName, String tableName) {
+ if (StringUtils.isNullOrEmpty(schemaName)) {
+ return tableName;
+ }
+ return schemaName + "." + tableName;
+ }
+
+ @Override
+ public String prepareColumnName(String name) {
+ return name;
+ }
+
+ @Override
+ public <T> void prepareDropTable(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP TABLE IF EXISTS "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ protected <T> String prepareCreateTable(TableDefinition<T> def) {
+ return "CREATE TABLE";
+ }
+
+ @Override
+ public <T> void prepareCreateTable(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append(prepareCreateTable(def));
+ buff.append(" ");
+ buff.append(prepareTableName(def.schemaName, def.tableName)).append('(');
+
+ boolean hasIdentityColumn = false;
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName)).append(' ');
+ String dataType = field.dataType;
+ if (dataType.equals("VARCHAR")) {
+ // check to see if we should use VARCHAR or CLOB
+ if (field.length <= 0) {
+ dataType = "CLOB";
+ }
+ buff.append(convertSqlType(dataType));
+ if (field.length > 0) {
+ buff.append('(').append(field.length).append(')');
+ }
+ } else if (dataType.equals("DECIMAL")) {
+ // DECIMAL(precision,scale)
+ buff.append(convertSqlType(dataType));
+ if (field.length > 0) {
+ buff.append('(').append(field.length);
+ if (field.scale > 0) {
+ buff.append(',').append(field.scale);
+ }
+ buff.append(')');
+ }
+ } else {
+ // other
+ hasIdentityColumn |= prepareColumnDefinition(buff, convertSqlType(dataType),
+ field.isAutoIncrement, field.isPrimaryKey);
+ }
+
+ // 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);
+ }
+ }
+ }
+
+ if (!field.nullable) {
+ buff.append(" NOT NULL");
+ }
+ }
+
+ // if table does not have identity column then specify primary key
+ if (!hasIdentityColumn) {
+ if (def.primaryKeyColumnNames != null && def.primaryKeyColumnNames.size() > 0) {
+ buff.append(", PRIMARY KEY(");
+ buff.resetCount();
+ for (String n : def.primaryKeyColumnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(n));
+ }
+ buff.append(')');
+ }
+ }
+
+ // create unique constraints
+ if (def.constraintsUnique.size() > 0) {
+ buff.append(", ");
+ buff.resetCount();
+ for (ConstraintUniqueDefinition constraint : def.constraintsUnique) {
+ buff.append("CONSTRAINT ");
+ buff.append(constraint.constraintName);
+ buff.append(" UNIQUE ");
+ buff.append(" (");
+ for (String col : constraint.uniqueColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ }
+ }
+
+ // create foreign key constraints
+ if (def.constraintsForeignKey.size() > 0) {
+ buff.append(", ");
+ buff.resetCount();
+ for (ConstraintForeignKeyDefinition constraint : def.constraintsForeignKey) {
+ buff.appendExceptFirst(", ");
+ buff.append(String.format("CONSTRAINT %s FOREIGN KEY(%s) REFERENCES %s(%s)",
+ constraint.constraintName,
+ constraint.foreignColumns.get(0),
+ constraint.referenceTable,
+ constraint.referenceColumns.get(0)));
+
+ if (constraint.deleteType != ConstraintDeleteType.UNSET) {
+ buff.append(" ON DELETE ");
+ switch (constraint.deleteType) {
+ case CASCADE:
+ buff.append("CASCADE ");
+ break;
+ case RESTRICT:
+ buff.append("RESTRICT ");
+ break;
+ case SET_NULL:
+ buff.append("SET NULL ");
+ break;
+ case NO_ACTION:
+ buff.append("NO ACTION ");
+ break;
+ case SET_DEFAULT:
+ buff.append("SET DEFAULT ");
+ break;
+ }
+ }
+ if (constraint.updateType != ConstraintUpdateType.UNSET) {
+ buff.append(" ON UPDATE ");
+ switch (constraint.updateType) {
+ case CASCADE:
+ buff.append("CASCADE ");
+ break;
+ case RESTRICT:
+ buff.append("RESTRICT ");
+ break;
+ case SET_NULL:
+ buff.append("SET NULL ");
+ break;
+ case NO_ACTION:
+ buff.append("NO ACTION ");
+ break;
+ case SET_DEFAULT:
+ buff.append("SET DEFAULT ");
+ break;
+ }
+ }
+ switch (constraint.deferrabilityType) {
+ case DEFERRABLE_INITIALLY_DEFERRED:
+ buff.append("DEFERRABLE INITIALLY DEFERRED ");
+ break;
+ case DEFERRABLE_INITIALLY_IMMEDIATE:
+ buff.append("DEFERRABLE INITIALLY IMMEDIATE ");
+ break;
+ case NOT_DEFERRABLE:
+ buff.append("NOT DEFERRABLE ");
+ break;
+ case UNSET:
+ break;
+ }
+ }
+ }
+
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ }
+
+ @Override
+ public <T> void prepareDropView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder("DROP VIEW "
+ + prepareTableName(def.schemaName, def.tableName));
+ stat.setSQL(buff.toString());
+ return;
+ }
+
+ protected <T> String prepareCreateView(TableDefinition<T> def) {
+ return "CREATE VIEW";
+ }
+
+ @Override
+ public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append(" FROM ");
+ buff.append(prepareTableName(def.schemaName, def.viewTableName));
+
+ StatementBuilder where = new StatementBuilder();
+ for (FieldDefinition field : def.fields) {
+ if (!StringUtils.isNullOrEmpty(field.constraint)) {
+ where.appendExceptFirst(", ");
+ String col = prepareColumnName(field.columnName);
+ String constraint = field.constraint.replace("{0}", col).replace("this", col);
+ where.append(constraint);
+ }
+ }
+ if (where.length() > 0) {
+ buff.append(" WHERE ");
+ buff.append(where.toString());
+ }
+
+ prepareCreateView(stat, def, buff.toString());
+ }
+
+ @Override
+ public <T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append(prepareCreateView(def));
+ buff.append(" ");
+ buff.append(prepareTableName(def.schemaName, def.tableName));
+
+ buff.append(" AS SELECT ");
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName));
+ }
+ buff.append(fromWhere);
+ stat.setSQL(buff.toString());
+ }
+
+ protected boolean isIntegerType(String dataType) {
+ if ("INT".equals(dataType)) {
+ return true;
+ } else if ("BIGINT".equals(dataType)) {
+ return true;
+ } else if ("TINYINT".equals(dataType)) {
+ return true;
+ } else if ("SMALLINT".equals(dataType)) {
+ return true;
+ }
+ return false;
+ }
+
+ protected boolean prepareColumnDefinition(StatementBuilder buff, String dataType,
+ boolean isAutoIncrement, boolean isPrimaryKey) {
+ buff.append(dataType);
+ if (isAutoIncrement) {
+ buff.append(" AUTO_INCREMENT");
+ }
+ return false;
+ }
+
+ @Override
+ public void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName,
+ IndexDefinition index) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("CREATE ");
+ switch (index.type) {
+ case UNIQUE:
+ buff.append("UNIQUE ");
+ break;
+ case UNIQUE_HASH:
+ buff.append("UNIQUE ");
+ break;
+ default:
+ IciqlLogger.warn("{0} does not support hash indexes", getClass().getSimpleName());
+ }
+ buff.append("INDEX ");
+ buff.append(index.indexName);
+ buff.append(" ON ");
+ // FIXME maybe we can use schemaName ?
+ // buff.append(prepareTableName(schemaName, tableName));
+ buff.append(tableName);
+ buff.append("(");
+ for (String col : index.columnNames) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+
+ stat.setSQL(buff.toString().trim());
+ }
+
+ /**
+ * PostgreSQL and Derby do not support the SQL2003 MERGE syntax, but we can
+ * use a trick to insert a row if it does not exist and call update() in
+ * Db.merge() if the affected row count is 0.
+ * <p>
+ * Databases that do support a MERGE syntax should override this method.
+ * <p>
+ * http://stackoverflow.com/questions/407688
+ */
+ @Override
+ public <T> void prepareMerge(SQLStatement stat, String schemaName, String tableName,
+ TableDefinition<T> def, Object obj) {
+ StatementBuilder buff = new StatementBuilder("INSERT INTO ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" (");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(field.columnName));
+ }
+ buff.append(") (SELECT ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = def.getValue(obj, field);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ buff.append(" FROM ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" WHERE ");
+ buff.resetCount();
+ for (FieldDefinition field : def.fields) {
+ if (field.isPrimaryKey) {
+ buff.appendExceptFirst(" AND ");
+ buff.append(MessageFormat.format("{0} = ?", prepareColumnName(field.columnName)));
+ Object value = def.getValue(obj, field);
+ Object parameter = serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ }
+ buff.append(" HAVING count(*)=0)");
+ stat.setSQL(buff.toString());
+ }
+
+ @Override
+ public void appendLimitOffset(SQLStatement stat, long limit, long offset) {
+ if (limit > 0) {
+ stat.appendSQL(" LIMIT " + limit);
+ }
+ if (offset > 0) {
+ stat.appendSQL(" OFFSET " + offset);
+ }
+ }
+
+ @Override
+ public void registerAdapter(DataTypeAdapter<?> typeAdapter) {
+ typeAdapters.put((Class<? extends DataTypeAdapter<?>>) typeAdapter.getClass(), typeAdapter);
+ }
+
+ @Override
+ public DataTypeAdapter<?> getAdapter(Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ DataTypeAdapter<?> dtt = typeAdapters.get(typeAdapter);
+ if (dtt == null) {
+ dtt = Utils.newObject(typeAdapter);
+ typeAdapters.put(typeAdapter, dtt);
+ }
+ return dtt;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T> Object serialize(T value, Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ if (typeAdapter == null) {
+ // pass-through
+ return value;
+ }
+
+ DataTypeAdapter<T> dtt = (DataTypeAdapter<T>) getAdapter(typeAdapter);
+ return dtt.serialize(value);
+ }
+
+ @Override
+ public Object deserialize(Object value, Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ DataTypeAdapter<?> dtt = typeAdapters.get(typeAdapter);
+ if (dtt == null) {
+ dtt = Utils.newObject(typeAdapter);
+ typeAdapters.put(typeAdapter, dtt);
+ }
+
+ return dtt.deserialize(value);
+ }
+
+ @Override
+ public String prepareStringParameter(Object o) {
+ if (o instanceof String) {
+ return LITERAL + o.toString().replace(LITERAL, "''") + LITERAL;
+ } else if (o instanceof Character) {
+ return LITERAL + o.toString() + LITERAL;
+ } else if (o instanceof java.sql.Time) {
+ return LITERAL + new SimpleDateFormat("HH:mm:ss").format(o) + LITERAL;
+ } else if (o instanceof java.sql.Date) {
+ return LITERAL + new SimpleDateFormat("yyyy-MM-dd").format(o) + LITERAL;
+ } else if (o instanceof java.util.Date) {
+ return LITERAL + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(o) + LITERAL;
+ }
+ return o.toString();
+ }
+
}
\ No newline at end of file
-/*\r
- * Copyright 2004-2011 H2 Group.\r
- * Copyright 2011 James Moger.\r
- * Copyright 2012 Frédéric Gaillard.\r
- *\r
- * Licensed under the Apache License, Version 2.0 (the "License");\r
- * you may not use this file except in compliance with the License.\r
- * You may obtain a copy of the License at\r
- *\r
- * http://www.apache.org/licenses/LICENSE-2.0\r
- *\r
- * Unless required by applicable law or agreed to in writing, software\r
- * distributed under the License is distributed on an "AS IS" BASIS,\r
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
- * See the License for the specific language governing permissions and\r
- * limitations under the License.\r
- */\r
-\r
-package com.iciql;\r
-\r
-import java.lang.reflect.Field;\r
-import java.sql.PreparedStatement;\r
-import java.sql.ResultSet;\r
-import java.sql.SQLException;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.IdentityHashMap;\r
-import java.util.LinkedHashSet;\r
-import java.util.List;\r
-import java.util.Map;\r
-import java.util.Set;\r
-\r
-import com.iciql.Iciql.ConstraintDeferrabilityType;\r
-import com.iciql.Iciql.ConstraintDeleteType;\r
-import com.iciql.Iciql.ConstraintUpdateType;\r
-import com.iciql.Iciql.DataTypeAdapter;\r
-import com.iciql.Iciql.EnumId;\r
-import com.iciql.Iciql.EnumType;\r
-import com.iciql.Iciql.IQColumn;\r
-import com.iciql.Iciql.IQConstraint;\r
-import com.iciql.Iciql.IQContraintForeignKey;\r
-import com.iciql.Iciql.IQContraintUnique;\r
-import com.iciql.Iciql.IQContraintsForeignKey;\r
-import com.iciql.Iciql.IQContraintsUnique;\r
-import com.iciql.Iciql.IQEnum;\r
-import com.iciql.Iciql.IQIgnore;\r
-import com.iciql.Iciql.IQIndex;\r
-import com.iciql.Iciql.IQIndexes;\r
-import com.iciql.Iciql.IQSchema;\r
-import com.iciql.Iciql.IQTable;\r
-import com.iciql.Iciql.IQVersion;\r
-import com.iciql.Iciql.IQView;\r
-import com.iciql.Iciql.IndexType;\r
-import com.iciql.Iciql.StandardJDBCTypeAdapter;\r
-import com.iciql.util.IciqlLogger;\r
-import com.iciql.util.StatementBuilder;\r
-import com.iciql.util.StringUtils;\r
-import com.iciql.util.Utils;\r
-\r
-/**\r
- * A table definition contains the index definitions of a table, the field\r
- * definitions, the table name, and other meta data.\r
- *\r
- * @param <T>\r
- * the table type\r
- */\r
-\r
-public class TableDefinition<T> {\r
-\r
- /**\r
- * The meta data of an index.\r
- */\r
-\r
- public static class IndexDefinition {\r
- public IndexType type;\r
- public String indexName;\r
-\r
- public List<String> columnNames;\r
- }\r
-\r
- /**\r
- * The meta data of a constraint on foreign key.\r
- */\r
-\r
- public static class ConstraintForeignKeyDefinition {\r
-\r
- public String constraintName;\r
- public List<String> foreignColumns;\r
- public String referenceTable;\r
- public List<String> referenceColumns;\r
- public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET;\r
- public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET;\r
- public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET;\r
- }\r
-\r
- /**\r
- * The meta data of a unique constraint.\r
- */\r
-\r
- public static class ConstraintUniqueDefinition {\r
-\r
- public String constraintName;\r
- public List<String> uniqueColumns;\r
- }\r
-\r
-\r
- /**\r
- * The meta data of a field.\r
- */\r
-\r
- static class FieldDefinition {\r
- String columnName;\r
- Field field;\r
- String dataType;\r
- int length;\r
- int scale;\r
- boolean isPrimaryKey;\r
- boolean isAutoIncrement;\r
- boolean trim;\r
- boolean nullable;\r
- String defaultValue;\r
- EnumType enumType;\r
- Class<?> enumTypeClass;\r
- boolean isPrimitive;\r
- String constraint;\r
- Class<? extends DataTypeAdapter<?>> typeAdapter;\r
-\r
- Object getValue(Object obj) {\r
- try {\r
- return field.get(obj);\r
- } catch (Exception e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- private Object initWithNewObject(Object obj) {\r
- Object o = Utils.newObject(field.getType());\r
- setValue(null, obj, o);\r
- return o;\r
- }\r
-\r
- private void setValue(SQLDialect dialect, Object obj, Object o) {\r
- try {\r
- if (!field.isAccessible()) {\r
- field.setAccessible(true);\r
- }\r
- Class<?> targetType = field.getType();\r
- if (targetType.isEnum()) {\r
- o = Utils.convertEnum(o, targetType, enumType);\r
- } else if (dialect != null && typeAdapter != null) {\r
- o = dialect.deserialize(o, typeAdapter);\r
- } else {\r
- o = Utils.convert(o, targetType);\r
- }\r
-\r
- if (targetType.isPrimitive() && o == null) {\r
- // do not attempt to set a primitive to null\r
- return;\r
- }\r
-\r
- field.set(obj, o);\r
- } catch (IciqlException e) {\r
- throw e;\r
- } catch (Exception e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- private Object read(ResultSet rs, int columnIndex) {\r
- try {\r
- return rs.getObject(columnIndex);\r
- } catch (SQLException e) {\r
- throw new IciqlException(e);\r
- }\r
- }\r
-\r
- @Override\r
- public int hashCode() {\r
- return columnName.hashCode();\r
- }\r
-\r
- @Override\r
- public boolean equals(Object o) {\r
- if (o instanceof FieldDefinition) {\r
- return o.hashCode() == hashCode();\r
- }\r
- return false;\r
- }\r
- }\r
-\r
- public ArrayList<FieldDefinition> fields = Utils.newArrayList();\r
- String schemaName;\r
- String tableName;\r
- String viewTableName;\r
- int tableVersion;\r
- List<String> primaryKeyColumnNames;\r
- boolean memoryTable;\r
- boolean multiplePrimitiveBools;\r
-\r
- private boolean createIfRequired = true;\r
- private Class<T> clazz;\r
- private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();\r
- private ArrayList<IndexDefinition> indexes = Utils.newArrayList();\r
- private ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList();\r
- private ArrayList<ConstraintUniqueDefinition> constraintsUnique = Utils.newArrayList();\r
-\r
- TableDefinition(Class<T> clazz) {\r
- this.clazz = clazz;\r
- schemaName = null;\r
- tableName = clazz.getSimpleName();\r
- }\r
-\r
- Class<T> getModelClass() {\r
- return clazz;\r
- }\r
-\r
- List<FieldDefinition> getFields() {\r
- return fields;\r
- }\r
-\r
- void defineSchemaName(String schemaName) {\r
- this.schemaName = schemaName;\r
- }\r
-\r
- void defineTableName(String tableName) {\r
- this.tableName = tableName;\r
- }\r
-\r
- void defineViewTableName(String viewTableName) {\r
- this.viewTableName = viewTableName;\r
- }\r
-\r
- void defineMemoryTable() {\r
- this.memoryTable = true;\r
- }\r
-\r
- void defineSkipCreate() {\r
- this.createIfRequired = false;\r
- }\r
-\r
- /**\r
- * Define a primary key by the specified model fields.\r
- *\r
- * @param modelFields\r
- * the ordered list of model fields\r
- */\r
- void definePrimaryKey(Object[] modelFields) {\r
- List<String> columnNames = mapColumnNames(modelFields);\r
- setPrimaryKey(columnNames);\r
- }\r
-\r
- /**\r
- * Define a primary key by the specified column names.\r
- *\r
- * @param columnNames\r
- * the ordered list of column names\r
- */\r
- private void setPrimaryKey(List<String> columnNames) {\r
- primaryKeyColumnNames = Utils.newArrayList(columnNames);\r
- List<String> pkNames = Utils.newArrayList();\r
- for (String name : columnNames) {\r
- pkNames.add(name.toLowerCase());\r
- }\r
- // set isPrimaryKey flag for all field definitions\r
- for (FieldDefinition fieldDefinition : fieldMap.values()) {\r
- fieldDefinition.isPrimaryKey = pkNames.contains(fieldDefinition.columnName.toLowerCase());\r
- }\r
- }\r
-\r
- private <A> String getColumnName(A fieldObject) {\r
- FieldDefinition def = fieldMap.get(fieldObject);\r
- return def == null ? null : def.columnName;\r
- }\r
-\r
- private ArrayList<String> mapColumnNames(Object[] columns) {\r
- ArrayList<String> columnNames = Utils.newArrayList();\r
- for (Object column : columns) {\r
- columnNames.add(getColumnName(column));\r
- }\r
- return columnNames;\r
- }\r
-\r
- /**\r
- * Defines an index with the specified model fields.\r
- *\r
- * @param name\r
- * the index name (optional)\r
- * @param type\r
- * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)\r
- * @param modelFields\r
- * the ordered list of model fields\r
- */\r
- void defineIndex(String name, IndexType type, Object[] modelFields) {\r
- List<String> columnNames = mapColumnNames(modelFields);\r
- addIndex(name, type, columnNames);\r
- }\r
-\r
- /**\r
- * Defines an index with the specified column names.\r
- *\r
- * @param type\r
- * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)\r
- * @param columnNames\r
- * the ordered list of column names\r
- */\r
- private void addIndex(String name, IndexType type, List<String> columnNames) {\r
- IndexDefinition index = new IndexDefinition();\r
- if (StringUtils.isNullOrEmpty(name)) {\r
- index.indexName = tableName + "_idx_" + indexes.size();\r
- } else {\r
- index.indexName = name;\r
- }\r
- index.columnNames = Utils.newArrayList(columnNames);\r
- index.type = type;\r
- indexes.add(index);\r
- }\r
-\r
- /**\r
- * Defines an unique constraint with the specified model fields.\r
- *\r
- * @param name\r
- * the constraint name (optional)\r
- * @param modelFields\r
- * the ordered list of model fields\r
- */\r
- void defineConstraintUnique(String name, Object[] modelFields) {\r
- List<String> columnNames = mapColumnNames(modelFields);\r
- addConstraintUnique(name, columnNames);\r
- }\r
-\r
- /**\r
- * Defines an unique constraint.\r
- *\r
- * @param name\r
- * @param columnNames\r
- */\r
- private void addConstraintUnique(String name, List<String> columnNames) {\r
- ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();\r
- if (StringUtils.isNullOrEmpty(name)) {\r
- constraint.constraintName = tableName + "_unique_" + constraintsUnique.size();\r
- } else {\r
- constraint.constraintName = name;\r
- }\r
- constraint.uniqueColumns = Utils.newArrayList(columnNames);\r
- constraintsUnique.add(constraint);\r
- }\r
-\r
- /**\r
- * Defines a foreign key constraint with the specified model fields.\r
- *\r
- * @param name\r
- * the constraint name (optional)\r
- * @param modelFields\r
- * the ordered list of model fields\r
- */\r
- void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields,\r
- ConstraintDeleteType deleteType, ConstraintUpdateType updateType,\r
- ConstraintDeferrabilityType deferrabilityType) {\r
- List<String> columnNames = mapColumnNames(modelFields);\r
- List<String> referenceColumnNames = mapColumnNames(refModelFields);\r
- addConstraintForeignKey(name, columnNames, refTableName, referenceColumnNames,\r
- deleteType, updateType, deferrabilityType);\r
- }\r
-\r
- void defineColumnName(Object column, String columnName) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.columnName = columnName;\r
- }\r
- }\r
-\r
- void defineAutoIncrement(Object column) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.isAutoIncrement = true;\r
- }\r
- }\r
-\r
- void defineLength(Object column, int length) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.length = length;\r
- }\r
- }\r
-\r
- void defineScale(Object column, int scale) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.scale = scale;\r
- }\r
- }\r
-\r
- void defineTrim(Object column) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.trim = true;\r
- }\r
- }\r
-\r
- void defineNullable(Object column, boolean isNullable) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.nullable = isNullable;\r
- }\r
- }\r
-\r
- void defineDefaultValue(Object column, String defaultValue) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.defaultValue = defaultValue;\r
- }\r
- }\r
-\r
- void defineConstraint(Object column, String constraint) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.constraint = constraint;\r
- }\r
- }\r
-\r
- void defineTypeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) {\r
- FieldDefinition def = fieldMap.get(column);\r
- if (def != null) {\r
- def.typeAdapter = typeAdapter;\r
- }\r
- }\r
-\r
- void mapFields(Db db) {\r
- boolean byAnnotationsOnly = false;\r
- boolean inheritColumns = false;\r
- if (clazz.isAnnotationPresent(IQTable.class)) {\r
- IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);\r
- byAnnotationsOnly = tableAnnotation.annotationsOnly();\r
- inheritColumns = tableAnnotation.inheritColumns();\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQView.class)) {\r
- IQView viewAnnotation = clazz.getAnnotation(IQView.class);\r
- byAnnotationsOnly = viewAnnotation.annotationsOnly();\r
- inheritColumns = viewAnnotation.inheritColumns();\r
- }\r
-\r
- List<Field> classFields = Utils.newArrayList();\r
- classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));\r
- if (inheritColumns) {\r
- Class<?> superClass = clazz.getSuperclass();\r
- classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));\r
-\r
- if (superClass.isAnnotationPresent(IQView.class)) {\r
- IQView superView = superClass.getAnnotation(IQView.class);\r
- if (superView.inheritColumns()) {\r
- // inherit columns from super.super.class\r
- Class<?> superSuperClass = superClass.getSuperclass();\r
- classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));\r
- }\r
- } else if (superClass.isAnnotationPresent(IQTable.class)) {\r
- IQTable superTable = superClass.getAnnotation(IQTable.class);\r
- if (superTable.inheritColumns()) {\r
- // inherit columns from super.super.class\r
- Class<?> superSuperClass = superClass.getSuperclass();\r
- classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));\r
- }\r
- }\r
- }\r
-\r
- Set<FieldDefinition> uniqueFields = new LinkedHashSet<FieldDefinition>();\r
- T defaultObject = Db.instance(clazz);\r
- for (Field f : classFields) {\r
- // check if we should skip this field\r
- if (f.isAnnotationPresent(IQIgnore.class)) {\r
- continue;\r
- }\r
-\r
- // default to field name\r
- String columnName = f.getName();\r
- boolean isAutoIncrement = false;\r
- boolean isPrimaryKey = false;\r
- int length = 0;\r
- int scale = 0;\r
- boolean trim = false;\r
- boolean nullable = !f.getType().isPrimitive();\r
- EnumType enumType = null;\r
- Class<?> enumTypeClass = null;\r
- String defaultValue = "";\r
- String constraint = "";\r
- String dataType = null;\r
- Class<? extends DataTypeAdapter<?>> typeAdapter = null;\r
-\r
- // configure Java -> SQL enum mapping\r
- if (f.getType().isEnum()) {\r
- enumType = EnumType.DEFAULT_TYPE;\r
- if (f.getType().isAnnotationPresent(IQEnum.class)) {\r
- // enum definition is annotated for all instances\r
- IQEnum iqenum = f.getType().getAnnotation(IQEnum.class);\r
- enumType = iqenum.value();\r
- }\r
- if (f.isAnnotationPresent(IQEnum.class)) {\r
- // this instance of the enum is annotated\r
- IQEnum iqenum = f.getAnnotation(IQEnum.class);\r
- enumType = iqenum.value();\r
- }\r
-\r
- if (EnumId.class.isAssignableFrom(f.getType())) {\r
- // custom enumid mapping\r
- enumTypeClass = ((EnumId<?>) f.getType().getEnumConstants()[0]).enumIdClass();\r
- }\r
- }\r
-\r
- // try using default object\r
- try {\r
- f.setAccessible(true);\r
- Object value = f.get(defaultObject);\r
- if (value != null) {\r
- if (value.getClass().isEnum()) {\r
- // enum default, convert to target type\r
- Enum<?> anEnum = (Enum<?>) value;\r
- Object o = Utils.convertEnum(anEnum, enumType);\r
- defaultValue = ModelUtils.formatDefaultValue(o);\r
- } else {\r
- // object default\r
- defaultValue = ModelUtils.formatDefaultValue(value);\r
- }\r
- }\r
- } catch (IllegalAccessException e) {\r
- throw new IciqlException(e, "failed to get default object for {0}", columnName);\r
- }\r
-\r
- boolean hasAnnotation = f.isAnnotationPresent(IQColumn.class);\r
- if (hasAnnotation) {\r
- IQColumn col = f.getAnnotation(IQColumn.class);\r
- if (!StringUtils.isNullOrEmpty(col.name())) {\r
- columnName = col.name();\r
- }\r
- isAutoIncrement = col.autoIncrement();\r
- isPrimaryKey = col.primaryKey();\r
- length = col.length();\r
- scale = col.scale();\r
- trim = col.trim();\r
- nullable = col.nullable();\r
-\r
- if (col.typeAdapter() != null && col.typeAdapter() != StandardJDBCTypeAdapter.class) {\r
- typeAdapter = col.typeAdapter();\r
- DataTypeAdapter<?> dtt = db.getDialect().getAdapter(col.typeAdapter());\r
- dataType = dtt.getDataType();\r
- }\r
-\r
- // annotation overrides\r
- if (!StringUtils.isNullOrEmpty(col.defaultValue())) {\r
- defaultValue = col.defaultValue();\r
- }\r
- }\r
-\r
- boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class);\r
- if (hasConstraint) {\r
- IQConstraint con = f.getAnnotation(IQConstraint.class);\r
- // annotation overrides\r
- if (!StringUtils.isNullOrEmpty(con.value())) {\r
- constraint = con.value();\r
- }\r
- }\r
-\r
- boolean reflectiveMatch = !byAnnotationsOnly;\r
- if (reflectiveMatch || hasAnnotation || hasConstraint) {\r
- FieldDefinition fieldDef = new FieldDefinition();\r
- fieldDef.isPrimitive = f.getType().isPrimitive();\r
- fieldDef.field = f;\r
- fieldDef.columnName = columnName;\r
- fieldDef.isAutoIncrement = isAutoIncrement;\r
- fieldDef.isPrimaryKey = isPrimaryKey;\r
- fieldDef.length = length;\r
- fieldDef.scale = scale;\r
- fieldDef.trim = trim;\r
- fieldDef.nullable = nullable;\r
- fieldDef.defaultValue = defaultValue;\r
- fieldDef.enumType = enumType;\r
- fieldDef.enumTypeClass = enumTypeClass;\r
- fieldDef.dataType = StringUtils.isNullOrEmpty(dataType) ? ModelUtils.getDataType(fieldDef) : dataType;\r
- fieldDef.typeAdapter = typeAdapter;\r
- fieldDef.constraint = constraint;\r
- uniqueFields.add(fieldDef);\r
- }\r
- }\r
- fields.addAll(uniqueFields);\r
-\r
- List<String> primaryKey = Utils.newArrayList();\r
- int primitiveBoolean = 0;\r
- for (FieldDefinition fieldDef : fields) {\r
- if (fieldDef.isPrimaryKey) {\r
- primaryKey.add(fieldDef.columnName);\r
- }\r
- if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) {\r
- primitiveBoolean++;\r
- }\r
- }\r
- if (primitiveBoolean > 1) {\r
- multiplePrimitiveBools = true;\r
- IciqlLogger\r
- .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!");\r
- }\r
- if (primaryKey.size() > 0) {\r
- setPrimaryKey(primaryKey);\r
- }\r
- }\r
-\r
- void checkMultipleBooleans() {\r
- if (multiplePrimitiveBools) {\r
- throw new IciqlException(\r
- "Can not explicitly reference a primitive boolean if there are multiple boolean fields in your model class!");\r
- }\r
- }\r
-\r
- void checkMultipleEnums(Object o) {\r
- if (o == null) {\r
- return;\r
- }\r
- Class<?> clazz = o.getClass();\r
- if (!clazz.isEnum()) {\r
- return;\r
- }\r
-\r
- int fieldCount = 0;\r
- for (FieldDefinition fieldDef : fields) {\r
- Class<?> targetType = fieldDef.field.getType();\r
- if (clazz.equals(targetType)) {\r
- fieldCount++;\r
- }\r
- }\r
-\r
- if (fieldCount > 1) {\r
- throw new IciqlException(\r
- "Can not explicitly reference {0} because there are {1} {0} fields in your model class!",\r
- clazz.getSimpleName(), fieldCount);\r
- }\r
- }\r
-\r
- /**\r
- * Optionally truncates strings to the maximum length and converts\r
- * java.lang.Enum types to Strings or Integers.\r
- */\r
- Object getValue(Object obj, FieldDefinition field) {\r
- Object value = field.getValue(obj);\r
- if (value == null) {\r
- return value;\r
- }\r
- if (field.enumType != null) {\r
- // convert enumeration to INT or STRING\r
- Enum<?> iqenum = (Enum<?>) value;\r
- switch (field.enumType) {\r
- case NAME:\r
- if (field.trim && field.length > 0) {\r
- if (iqenum.name().length() > field.length) {\r
- return iqenum.name().substring(0, field.length);\r
- }\r
- }\r
- return iqenum.name();\r
- case ORDINAL:\r
- return iqenum.ordinal();\r
- case ENUMID:\r
- if (!EnumId.class.isAssignableFrom(value.getClass())) {\r
- throw new IciqlException(field.field.getName() + " does not implement EnumId!");\r
- }\r
- EnumId<?> enumid = (EnumId<?>) value;\r
- return enumid.enumId();\r
- }\r
- }\r
-\r
- if (field.trim && field.length > 0) {\r
- if (value instanceof String) {\r
- // clip strings\r
- String s = (String) value;\r
- if (s.length() > field.length) {\r
- return s.substring(0, field.length);\r
- }\r
- return s;\r
- }\r
- return value;\r
- }\r
-\r
- // return the value unchanged\r
- return value;\r
- }\r
-\r
- PreparedStatement createInsertStatement(Db db, Object obj, boolean returnKey) {\r
- SQLStatement stat = new SQLStatement(db);\r
- StatementBuilder buff = new StatementBuilder("INSERT INTO ");\r
- buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');\r
- for (FieldDefinition field : fields) {\r
- if (skipInsertField(field, obj)) {\r
- continue;\r
- }\r
- buff.appendExceptFirst(", ");\r
- buff.append(db.getDialect().prepareColumnName(field.columnName));\r
- }\r
- buff.append(") VALUES(");\r
- buff.resetCount();\r
- for (FieldDefinition field : fields) {\r
- if (skipInsertField(field, obj)) {\r
- continue;\r
- }\r
- buff.appendExceptFirst(", ");\r
- buff.append('?');\r
- Object value = getValue(obj, field);\r
- if (value == null && !field.nullable) {\r
- // try to interpret and instantiate a default value\r
- value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());\r
- }\r
- Object parameter = db.getDialect().serialize(value, field.typeAdapter);\r
- stat.addParameter(parameter);\r
- }\r
- buff.append(')');\r
- stat.setSQL(buff.toString());\r
- IciqlLogger.insert(stat.getSQL());\r
- return stat.prepare(returnKey);\r
- }\r
-\r
- long insert(Db db, Object obj, boolean returnKey) {\r
- if (!StringUtils.isNullOrEmpty(viewTableName)) {\r
- throw new IciqlException("Iciql does not support inserting rows into views!");\r
- }\r
- SQLStatement stat = new SQLStatement(db);\r
- StatementBuilder buff = new StatementBuilder("INSERT INTO ");\r
- buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');\r
- for (FieldDefinition field : fields) {\r
- if (skipInsertField(field, obj)) {\r
- continue;\r
- }\r
- buff.appendExceptFirst(", ");\r
- buff.append(db.getDialect().prepareColumnName(field.columnName));\r
- }\r
- buff.append(") VALUES(");\r
- buff.resetCount();\r
- for (FieldDefinition field : fields) {\r
- if (skipInsertField(field, obj)) {\r
- continue;\r
- }\r
- buff.appendExceptFirst(", ");\r
- buff.append('?');\r
- Object value = getValue(obj, field);\r
- if (value == null && !field.nullable) {\r
- // try to interpret and instantiate a default value\r
- value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());\r
- }\r
- Object parameter = db.getDialect().serialize(value, field.typeAdapter);\r
- stat.addParameter(parameter);\r
- }\r
- buff.append(')');\r
- stat.setSQL(buff.toString());\r
- IciqlLogger.insert(stat.getSQL());\r
- if (returnKey) {\r
- return stat.executeInsert();\r
- }\r
- return stat.executeUpdate();\r
- }\r
-\r
- private boolean skipInsertField(FieldDefinition field, Object obj) {\r
- if (field.isAutoIncrement) {\r
- Object value = getValue(obj, field);\r
- if (field.isPrimitive) {\r
- // skip uninitialized primitive autoincrement values\r
- if (value.toString().equals("0")) {\r
- return true;\r
- }\r
- } else if (value == null) {\r
- // skip null object autoincrement values\r
- return true;\r
- }\r
- } else {\r
- // conditionally skip insert of null\r
- Object value = getValue(obj, field);\r
- if (value == null) {\r
- if (field.nullable) {\r
- // skip null assignment, field is nullable\r
- return true;\r
- } else if (StringUtils.isNullOrEmpty(field.defaultValue)) {\r
- IciqlLogger.warn("no default value, skipping null insert assignment for {0}.{1}",\r
- tableName, field.columnName);\r
- return true;\r
- }\r
- }\r
- }\r
- return false;\r
- }\r
-\r
- int merge(Db db, Object obj) {\r
- if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {\r
- throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()\r
- + " - no update possible");\r
- }\r
- SQLStatement stat = new SQLStatement(db);\r
- db.getDialect().prepareMerge(stat, schemaName, tableName, this, obj);\r
- IciqlLogger.merge(stat.getSQL());\r
- return stat.executeUpdate();\r
- }\r
-\r
- int update(Db db, Object obj) {\r
- if (!StringUtils.isNullOrEmpty(viewTableName)) {\r
- throw new IciqlException("Iciql does not support updating rows in views!");\r
- }\r
- if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {\r
- throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()\r
- + " - no update possible");\r
- }\r
- SQLStatement stat = new SQLStatement(db);\r
- StatementBuilder buff = new StatementBuilder("UPDATE ");\r
- buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append(" SET ");\r
- buff.resetCount();\r
-\r
- for (FieldDefinition field : fields) {\r
- if (!field.isPrimaryKey) {\r
- Object value = getValue(obj, field);\r
- if (value == null && !field.nullable) {\r
- // try to interpret and instantiate a default value\r
- value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());\r
- }\r
- buff.appendExceptFirst(", ");\r
- buff.append(db.getDialect().prepareColumnName(field.columnName));\r
- buff.append(" = ?");\r
- Object parameter = db.getDialect().serialize(value, field.typeAdapter);\r
- stat.addParameter(parameter);\r
- }\r
- }\r
- Object alias = Utils.newObject(obj.getClass());\r
- Query<Object> query = Query.from(db, alias);\r
- boolean firstCondition = true;\r
- for (FieldDefinition field : fields) {\r
- if (field.isPrimaryKey) {\r
- Object fieldAlias = field.getValue(alias);\r
- Object value = field.getValue(obj);\r
- if (field.isPrimitive) {\r
- fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);\r
- }\r
- if (!firstCondition) {\r
- query.addConditionToken(ConditionAndOr.AND);\r
- }\r
- firstCondition = false;\r
- query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));\r
- }\r
- }\r
- stat.setSQL(buff.toString());\r
- query.appendWhere(stat);\r
- IciqlLogger.update(stat.getSQL());\r
- return stat.executeUpdate();\r
- }\r
-\r
- int delete(Db db, Object obj) {\r
- if (!StringUtils.isNullOrEmpty(viewTableName)) {\r
- throw new IciqlException("Iciql does not support deleting rows from views!");\r
- }\r
- if (primaryKeyColumnNames == null || primaryKeyColumnNames.size() == 0) {\r
- throw new IllegalStateException("No primary key columns defined for table " + obj.getClass()\r
- + " - no update possible");\r
- }\r
- SQLStatement stat = new SQLStatement(db);\r
- StatementBuilder buff = new StatementBuilder("DELETE FROM ");\r
- buff.append(db.getDialect().prepareTableName(schemaName, tableName));\r
- buff.resetCount();\r
- Object alias = Utils.newObject(obj.getClass());\r
- Query<Object> query = Query.from(db, alias);\r
- boolean firstCondition = true;\r
- for (FieldDefinition field : fields) {\r
- if (field.isPrimaryKey) {\r
- Object fieldAlias = field.getValue(alias);\r
- Object value = field.getValue(obj);\r
- if (field.isPrimitive) {\r
- fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);\r
- }\r
- if (!firstCondition) {\r
- query.addConditionToken(ConditionAndOr.AND);\r
- }\r
- firstCondition = false;\r
- query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));\r
- }\r
- }\r
- stat.setSQL(buff.toString());\r
- query.appendWhere(stat);\r
- IciqlLogger.delete(stat.getSQL());\r
- return stat.executeUpdate();\r
- }\r
-\r
- TableDefinition<T> createIfRequired(Db db) {\r
- // globally enable/disable check of create if required\r
- if (db.getSkipCreate()) {\r
- return this;\r
- }\r
- if (!createIfRequired) {\r
- // skip table and index creation\r
- // but still check for upgrades\r
- db.upgradeTable(this);\r
- return this;\r
- }\r
- if (db.hasCreated(clazz)) {\r
- return this;\r
- }\r
- SQLStatement stat = new SQLStatement(db);\r
- if (StringUtils.isNullOrEmpty(viewTableName)) {\r
- db.getDialect().prepareCreateTable(stat, this);\r
- } else {\r
- db.getDialect().prepareCreateView(stat, this);\r
- }\r
- IciqlLogger.create(stat.getSQL());\r
- try {\r
- stat.executeUpdate();\r
- } catch (IciqlException e) {\r
- if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {\r
- throw e;\r
- }\r
- }\r
-\r
- // create indexes\r
- for (IndexDefinition index : indexes) {\r
- stat = new SQLStatement(db);\r
- db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index);\r
- IciqlLogger.create(stat.getSQL());\r
- try {\r
- stat.executeUpdate();\r
- } catch (IciqlException e) {\r
- if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS\r
- && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {\r
- throw e;\r
- }\r
- }\r
- }\r
-\r
- // create unique constraints\r
- for (ConstraintUniqueDefinition constraint : constraintsUnique) {\r
- stat = new SQLStatement(db);\r
- db.getDialect().prepareCreateConstraintUnique(stat, schemaName, tableName, constraint);\r
- IciqlLogger.create(stat.getSQL());\r
- try {\r
- stat.executeUpdate();\r
- } catch (IciqlException e) {\r
- if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS\r
- && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {\r
- throw e;\r
- }\r
- }\r
- }\r
-\r
- // create foreign keys constraints\r
- for (ConstraintForeignKeyDefinition constraint : constraintsForeignKey) {\r
- stat = new SQLStatement(db);\r
- db.getDialect().prepareCreateConstraintForeignKey(stat, schemaName, tableName, constraint);\r
- IciqlLogger.create(stat.getSQL());\r
- try {\r
- stat.executeUpdate();\r
- } catch (IciqlException e) {\r
- if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS\r
- && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {\r
- throw e;\r
- }\r
- }\r
- }\r
-\r
- // tables are created using IF NOT EXISTS\r
- // but we may still need to upgrade\r
- db.upgradeTable(this);\r
- return this;\r
- }\r
-\r
- void mapObject(Object obj) {\r
- fieldMap.clear();\r
- initObject(obj, fieldMap);\r
-\r
- if (clazz.isAnnotationPresent(IQSchema.class)) {\r
- IQSchema schemaAnnotation = clazz.getAnnotation(IQSchema.class);\r
- // setup schema name mapping, if properly annotated\r
- if (!StringUtils.isNullOrEmpty(schemaAnnotation.value())) {\r
- schemaName = schemaAnnotation.value();\r
- }\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQTable.class)) {\r
- IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);\r
-\r
- // setup table name mapping, if properly annotated\r
- if (!StringUtils.isNullOrEmpty(tableAnnotation.name())) {\r
- tableName = tableAnnotation.name();\r
- }\r
-\r
- // allow control over createTableIfRequired()\r
- createIfRequired = tableAnnotation.create();\r
-\r
- // model version\r
- if (clazz.isAnnotationPresent(IQVersion.class)) {\r
- IQVersion versionAnnotation = clazz.getAnnotation(IQVersion.class);\r
- if (versionAnnotation.value() > 0) {\r
- tableVersion = versionAnnotation.value();\r
- }\r
- }\r
-\r
- // setup the primary index, if properly annotated\r
- if (tableAnnotation.primaryKey().length > 0) {\r
- List<String> primaryKey = Utils.newArrayList();\r
- primaryKey.addAll(Arrays.asList(tableAnnotation.primaryKey()));\r
- setPrimaryKey(primaryKey);\r
- }\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQView.class)) {\r
- IQView viewAnnotation = clazz.getAnnotation(IQView.class);\r
-\r
- // setup view name mapping, if properly annotated\r
- // set this as the table name so it fits in seemlessly with iciql\r
- if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) {\r
- tableName = viewAnnotation.name();\r
- } else {\r
- tableName = clazz.getSimpleName();\r
- }\r
-\r
- // setup source table name mapping, if properly annotated\r
- if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) {\r
- viewTableName = viewAnnotation.tableName();\r
- } else {\r
- // check for IQTable annotation on super class\r
- Class<?> superClass = clazz.getSuperclass();\r
- if (superClass.isAnnotationPresent(IQTable.class)) {\r
- IQTable table = superClass.getAnnotation(IQTable.class);\r
- if (StringUtils.isNullOrEmpty(table.name())) {\r
- // super.SimpleClassName\r
- viewTableName = superClass.getSimpleName();\r
- } else {\r
- // super.IQTable.name()\r
- viewTableName = table.name();\r
- }\r
- } else if (superClass.isAnnotationPresent(IQView.class)) {\r
- // super class is a view\r
- IQView parentView = superClass.getAnnotation(IQView.class);\r
- if (StringUtils.isNullOrEmpty(parentView.tableName())) {\r
- // parent view does not define a tableName, must be inherited\r
- Class<?> superParent = superClass.getSuperclass();\r
- if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) {\r
- IQTable superParentTable = superParent.getAnnotation(IQTable.class);\r
- if (StringUtils.isNullOrEmpty(superParentTable.name())) {\r
- // super.super.SimpleClassName\r
- viewTableName = superParent.getSimpleName();\r
- } else {\r
- // super.super.IQTable.name()\r
- viewTableName = superParentTable.name();\r
- }\r
- }\r
- } else {\r
- // super.IQView.tableName()\r
- viewTableName = parentView.tableName();\r
- }\r
- }\r
-\r
- if (StringUtils.isNullOrEmpty(viewTableName)) {\r
- // still missing view table name\r
- throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName);\r
- }\r
- }\r
-\r
- // allow control over createTableIfRequired()\r
- createIfRequired = viewAnnotation.create();\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQIndex.class)) {\r
- // single table index\r
- IQIndex index = clazz.getAnnotation(IQIndex.class);\r
- addIndex(index);\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQIndexes.class)) {\r
- // multiple table indexes\r
- IQIndexes indexes = clazz.getAnnotation(IQIndexes.class);\r
- for (IQIndex index : indexes.value()) {\r
- addIndex(index);\r
- }\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQContraintUnique.class)) {\r
- // single table unique constraint\r
- IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class);\r
- addConstraintUnique(constraint);\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQContraintsUnique.class)) {\r
- // multiple table unique constraints\r
- IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class);\r
- for (IQContraintUnique constraint : constraints.value()) {\r
- addConstraintUnique(constraint);\r
- }\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) {\r
- // single table constraint\r
- IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class);\r
- addConstraintForeignKey(constraint);\r
- }\r
-\r
- if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) {\r
- // multiple table constraints\r
- IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class);\r
- for (IQContraintForeignKey constraint : constraints.value()) {\r
- addConstraintForeignKey(constraint);\r
- }\r
- }\r
-\r
- }\r
-\r
- private void addConstraintForeignKey(IQContraintForeignKey constraint) {\r
- List<String> foreignColumns = Arrays.asList(constraint.foreignColumns());\r
- List<String> referenceColumns = Arrays.asList(constraint.referenceColumns());\r
- addConstraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType());\r
- }\r
-\r
- private void addConstraintUnique(IQContraintUnique constraint) {\r
- List<String> uniqueColumns = Arrays.asList(constraint.uniqueColumns());\r
- addConstraintUnique(constraint.name(), uniqueColumns);\r
- }\r
-\r
- /**\r
- * Defines a foreign key constraint with the specified parameters.\r
- *\r
- * @param name\r
- * name of the constraint\r
- * @param foreignColumns\r
- * list of columns declared as foreign\r
- * @param referenceName\r
- * reference table name\r
- * @param referenceColumns\r
- * list of columns used in reference table\r
- * @param deleteType\r
- * action on delete\r
- * @param updateType\r
- * action on update\r
- * @param deferrabilityType\r
- * deferrability mode\r
- */\r
- private void addConstraintForeignKey(String name,\r
- List<String> foreignColumns, String referenceName,\r
- List<String> referenceColumns, ConstraintDeleteType deleteType,\r
- ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {\r
- ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();\r
- if (StringUtils.isNullOrEmpty(name)) {\r
- constraint.constraintName = tableName + "_fkey_" + constraintsForeignKey.size();\r
- } else {\r
- constraint.constraintName = name;\r
- }\r
- constraint.foreignColumns = Utils.newArrayList(foreignColumns);\r
- constraint.referenceColumns = Utils.newArrayList(referenceColumns);\r
- constraint.referenceTable = referenceName;\r
- constraint.deleteType = deleteType;\r
- constraint.updateType = updateType;\r
- constraint.deferrabilityType = deferrabilityType;\r
- constraintsForeignKey.add(constraint);\r
- }\r
-\r
- private void addIndex(IQIndex index) {\r
- List<String> columns = Arrays.asList(index.value());\r
- addIndex(index.name(), index.type(), columns);\r
- }\r
-\r
- List<IndexDefinition> getIndexes() {\r
- return indexes;\r
- }\r
-\r
- List<ConstraintUniqueDefinition> getContraintsUnique() {\r
- return constraintsUnique;\r
- }\r
-\r
- List<ConstraintForeignKeyDefinition> getContraintsForeignKey() {\r
- return constraintsForeignKey;\r
- }\r
-\r
- private void initObject(Object obj, Map<Object, FieldDefinition> map) {\r
- for (FieldDefinition def : fields) {\r
- Object newValue = def.initWithNewObject(obj);\r
- map.put(newValue, def);\r
- }\r
- }\r
-\r
- void initSelectObject(SelectTable<T> table, Object obj, Map<Object, SelectColumn<T>> map, boolean reuse) {\r
- for (FieldDefinition def : fields) {\r
- Object value;\r
- if (!reuse) {\r
- value = def.initWithNewObject(obj);\r
- } else {\r
- value = def.getValue(obj);\r
- }\r
- SelectColumn<T> column = new SelectColumn<T>(table, def);\r
- map.put(value, column);\r
- }\r
- }\r
-\r
- /**\r
- * Most queries executed by iciql have named select lists (select alpha,\r
- * beta where...) but sometimes a wildcard select is executed (select *).\r
- * When a wildcard query is executed on a table that has more columns than\r
- * are mapped in your model object, this creates a column mapping issue.\r
- * JaQu assumed that you can always use the integer index of the\r
- * reflectively mapped field definition to determine position in the result\r
- * set.\r
- *\r
- * This is not always true.\r
- *\r
- * iciql identifies when a select * query is executed and maps column names\r
- * to a column index from the result set. If the select statement is\r
- * explicit, then the standard assumed column index is used instead.\r
- *\r
- * @param rs\r
- * @return\r
- */\r
- int[] mapColumns(boolean wildcardSelect, ResultSet rs) {\r
- int[] columns = new int[fields.size()];\r
- for (int i = 0; i < fields.size(); i++) {\r
- try {\r
- FieldDefinition def = fields.get(i);\r
- int columnIndex;\r
- if (wildcardSelect) {\r
- // select *\r
- // create column index by field name\r
- columnIndex = rs.findColumn(def.columnName);\r
- } else {\r
- // select alpha, beta, gamma, etc\r
- // explicit select order\r
- columnIndex = i + 1;\r
- }\r
- columns[i] = columnIndex;\r
- } catch (SQLException s) {\r
- throw new IciqlException(s);\r
- }\r
- }\r
- return columns;\r
- }\r
-\r
- void readRow(SQLDialect dialect, Object item, ResultSet rs, int[] columns) {\r
- for (int i = 0; i < fields.size(); i++) {\r
- FieldDefinition def = fields.get(i);\r
- int index = columns[i];\r
- Object o = def.read(rs, index);\r
- def.setValue(dialect, item, o);\r
- }\r
- }\r
-\r
- void appendSelectList(SQLStatement stat) {\r
- for (int i = 0; i < fields.size(); i++) {\r
- if (i > 0) {\r
- stat.appendSQL(", ");\r
- }\r
- FieldDefinition def = fields.get(i);\r
- stat.appendColumn(def.columnName);\r
- }\r
- }\r
-\r
- <Y, X> void appendSelectList(SQLStatement stat, Query<Y> query, X x) {\r
- // select t0.col1, t0.col2, t0.col3...\r
- // select table1.col1, table1.col2, table1.col3...\r
- String selectDot = "";\r
- SelectTable<?> sel = query.getSelectTable(x);\r
- if (sel != null) {\r
- if (query.isJoin()) {\r
- selectDot = sel.getAs() + ".";\r
- } else {\r
- String sn = sel.getAliasDefinition().schemaName;\r
- String tn = sel.getAliasDefinition().tableName;\r
- selectDot = query.getDb().getDialect().prepareTableName(sn, tn) + ".";\r
- }\r
- }\r
-\r
- for (int i = 0; i < fields.size(); i++) {\r
- if (i > 0) {\r
- stat.appendSQL(", ");\r
- }\r
- stat.appendSQL(selectDot);\r
- FieldDefinition def = fields.get(i);\r
- if (def.isPrimitive) {\r
- Object obj = def.getValue(x);\r
- Object alias = query.getPrimitiveAliasByValue(obj);\r
- query.appendSQL(stat, x, alias);\r
- } else {\r
- Object obj = def.getValue(x);\r
- query.appendSQL(stat, x, obj);\r
- }\r
- }\r
- }\r
-}\r
+/*
+ * Copyright 2004-2011 H2 Group.
+ * Copyright 2011 James Moger.
+ * Copyright 2012 Frédéric Gaillard.
+ *
+ * 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.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import com.iciql.Iciql.ConstraintDeferrabilityType;
+import com.iciql.Iciql.ConstraintDeleteType;
+import com.iciql.Iciql.ConstraintUpdateType;
+import com.iciql.Iciql.DataTypeAdapter;
+import com.iciql.Iciql.EnumId;
+import com.iciql.Iciql.EnumType;
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQConstraint;
+import com.iciql.Iciql.IQContraintForeignKey;
+import com.iciql.Iciql.IQContraintUnique;
+import com.iciql.Iciql.IQContraintsForeignKey;
+import com.iciql.Iciql.IQContraintsUnique;
+import com.iciql.Iciql.IQEnum;
+import com.iciql.Iciql.IQIgnore;
+import com.iciql.Iciql.IQIndex;
+import com.iciql.Iciql.IQIndexes;
+import com.iciql.Iciql.IQSchema;
+import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.IQVersion;
+import com.iciql.Iciql.IQView;
+import com.iciql.Iciql.IndexType;
+import com.iciql.Iciql.StandardJDBCTypeAdapter;
+import com.iciql.util.IciqlLogger;
+import com.iciql.util.StatementBuilder;
+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
+ */
+
+public class TableDefinition<T> {
+
+ /**
+ * The meta data of an index.
+ */
+
+ public static class IndexDefinition {
+ public IndexType type;
+ public String indexName;
+
+ public List<String> columnNames;
+ }
+
+ /**
+ * The meta data of a constraint on foreign key.
+ */
+
+ public static class ConstraintForeignKeyDefinition {
+
+ public String constraintName;
+ public List<String> foreignColumns;
+ public String referenceTable;
+ public List<String> referenceColumns;
+ public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET;
+ public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET;
+ public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET;
+ }
+
+ /**
+ * The meta data of a unique constraint.
+ */
+
+ public static class ConstraintUniqueDefinition {
+
+ public String constraintName;
+ public List<String> uniqueColumns;
+ }
+
+
+ /**
+ * The meta data of a field.
+ */
+
+ static class FieldDefinition {
+ String columnName;
+ Field field;
+ String dataType;
+ int length;
+ int scale;
+ boolean isPrimaryKey;
+ boolean isAutoIncrement;
+ boolean trim;
+ boolean nullable;
+ String defaultValue;
+ EnumType enumType;
+ Class<?> enumTypeClass;
+ boolean isPrimitive;
+ String constraint;
+ Class<? extends DataTypeAdapter<?>> typeAdapter;
+
+ Object getValue(Object obj) {
+ try {
+ return field.get(obj);
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ private Object initWithNewObject(Object obj) {
+ Object o = Utils.newObject(field.getType());
+ setValue(null, obj, o);
+ return o;
+ }
+
+ private void setValue(SQLDialect dialect, Object obj, Object o) {
+ try {
+ if (!field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ Class<?> targetType = field.getType();
+ if (targetType.isEnum()) {
+ o = Utils.convertEnum(o, targetType, enumType);
+ } else if (dialect != null && typeAdapter != null) {
+ o = dialect.deserialize(o, typeAdapter);
+ } else {
+ o = Utils.convert(o, targetType);
+ }
+
+ if (targetType.isPrimitive() && o == null) {
+ // do not attempt to set a primitive to null
+ return;
+ }
+
+ field.set(obj, o);
+ } catch (IciqlException e) {
+ throw e;
+ } catch (Exception e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ private Object read(ResultSet rs, int columnIndex) {
+ try {
+ return rs.getObject(columnIndex);
+ } catch (SQLException e) {
+ throw new IciqlException(e);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return columnName.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o instanceof FieldDefinition) {
+ return o.hashCode() == hashCode();
+ }
+ return false;
+ }
+ }
+
+ public ArrayList<FieldDefinition> fields = Utils.newArrayList();
+ String schemaName;
+ String tableName;
+ String viewTableName;
+ int tableVersion;
+ List<String> primaryKeyColumnNames;
+ boolean memoryTable;
+ boolean multiplePrimitiveBools;
+
+ private boolean createIfRequired = true;
+ private Class<T> clazz;
+ private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();
+ private ArrayList<IndexDefinition> indexes = Utils.newArrayList();
+ ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList();
+ ArrayList<ConstraintUniqueDefinition> constraintsUnique = Utils.newArrayList();
+
+ TableDefinition(Class<T> clazz) {
+ this.clazz = clazz;
+ schemaName = null;
+ tableName = clazz.getSimpleName();
+ }
+
+ Class<T> getModelClass() {
+ return clazz;
+ }
+
+ List<FieldDefinition> getFields() {
+ return fields;
+ }
+
+ void defineSchemaName(String schemaName) {
+ this.schemaName = schemaName;
+ }
+
+ void defineTableName(String tableName) {
+ this.tableName = tableName;
+ }
+
+ void defineViewTableName(String viewTableName) {
+ this.viewTableName = viewTableName;
+ }
+
+ void defineMemoryTable() {
+ this.memoryTable = true;
+ }
+
+ void defineSkipCreate() {
+ this.createIfRequired = false;
+ }
+
+ /**
+ * Define a primary key by the specified model fields.
+ *
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void definePrimaryKey(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
+ */
+ private void setPrimaryKey(List<String> columnNames) {
+ primaryKeyColumnNames = Utils.newArrayList(columnNames);
+ List<String> pkNames = Utils.newArrayList();
+ for (String name : columnNames) {
+ pkNames.add(name.toLowerCase());
+ }
+ // set isPrimaryKey flag for all field definitions
+ for (FieldDefinition fieldDefinition : fieldMap.values()) {
+ fieldDefinition.isPrimaryKey = pkNames.contains(fieldDefinition.columnName.toLowerCase());
+ }
+ }
+
+ private <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 name
+ * the index name (optional)
+ * @param type
+ * the index type (STANDARD, HASH, UNIQUE, UNIQUE_HASH)
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void defineIndex(String name, IndexType type, Object[] modelFields) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ addIndex(name, 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
+ */
+ private void addIndex(String name, IndexType type, List<String> columnNames) {
+ IndexDefinition index = new IndexDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ index.indexName = tableName + "_idx_" + indexes.size();
+ } else {
+ index.indexName = name;
+ }
+ index.columnNames = Utils.newArrayList(columnNames);
+ index.type = type;
+ indexes.add(index);
+ }
+
+ /**
+ * Defines an unique constraint with the specified model fields.
+ *
+ * @param name
+ * the constraint name (optional)
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void defineConstraintUnique(String name, Object[] modelFields) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ addConstraintUnique(name, columnNames);
+ }
+
+ /**
+ * Defines an unique constraint.
+ *
+ * @param name
+ * @param columnNames
+ */
+ private void addConstraintUnique(String name, List<String> columnNames) {
+ ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ constraint.constraintName = tableName + "_unique_" + constraintsUnique.size();
+ } else {
+ constraint.constraintName = name;
+ }
+ constraint.uniqueColumns = Utils.newArrayList(columnNames);
+ constraintsUnique.add(constraint);
+ }
+
+ /**
+ * Defines a foreign key constraint with the specified model fields.
+ *
+ * @param name
+ * the constraint name (optional)
+ * @param modelFields
+ * the ordered list of model fields
+ */
+ void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields,
+ ConstraintDeleteType deleteType, ConstraintUpdateType updateType,
+ ConstraintDeferrabilityType deferrabilityType) {
+ List<String> columnNames = mapColumnNames(modelFields);
+ List<String> referenceColumnNames = mapColumnNames(refModelFields);
+ addConstraintForeignKey(name, columnNames, refTableName, referenceColumnNames,
+ deleteType, updateType, deferrabilityType);
+ }
+
+ void defineColumnName(Object column, String columnName) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.columnName = columnName;
+ }
+ }
+
+ void defineAutoIncrement(Object column) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.isAutoIncrement = true;
+ }
+ }
+
+ void defineLength(Object column, int length) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.length = length;
+ }
+ }
+
+ void defineScale(Object column, int scale) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.scale = scale;
+ }
+ }
+
+ void defineTrim(Object column) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.trim = true;
+ }
+ }
+
+ void defineNullable(Object column, boolean isNullable) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.nullable = isNullable;
+ }
+ }
+
+ void defineDefaultValue(Object column, String defaultValue) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.defaultValue = defaultValue;
+ }
+ }
+
+ void defineConstraint(Object column, String constraint) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.constraint = constraint;
+ }
+ }
+
+ void defineTypeAdapter(Object column, Class<? extends DataTypeAdapter<?>> typeAdapter) {
+ FieldDefinition def = fieldMap.get(column);
+ if (def != null) {
+ def.typeAdapter = typeAdapter;
+ }
+ }
+
+ void mapFields(Db db) {
+ boolean byAnnotationsOnly = false;
+ boolean inheritColumns = false;
+ if (clazz.isAnnotationPresent(IQTable.class)) {
+ IQTable tableAnnotation = clazz.getAnnotation(IQTable.class);
+ byAnnotationsOnly = tableAnnotation.annotationsOnly();
+ inheritColumns = tableAnnotation.inheritColumns();
+ }
+
+ if (clazz.isAnnotationPresent(IQView.class)) {
+ IQView viewAnnotation = clazz.getAnnotation(IQView.class);
+ byAnnotationsOnly = viewAnnotation.annotationsOnly();
+ inheritColumns = viewAnnotation.inheritColumns();
+ }
+
+ List<Field> classFields = Utils.newArrayList();
+ classFields.addAll(Arrays.asList(clazz.getDeclaredFields()));
+ if (inheritColumns) {
+ Class<?> superClass = clazz.getSuperclass();
+ classFields.addAll(Arrays.asList(superClass.getDeclaredFields()));
+
+ if (superClass.isAnnotationPresent(IQView.class)) {
+ IQView superView = superClass.getAnnotation(IQView.class);
+ if (superView.inheritColumns()) {
+ // inherit columns from super.super.class
+ Class<?> superSuperClass = superClass.getSuperclass();
+ classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));
+ }
+ } else if (superClass.isAnnotationPresent(IQTable.class)) {
+ IQTable superTable = superClass.getAnnotation(IQTable.class);
+ if (superTable.inheritColumns()) {
+ // inherit columns from super.super.class
+ Class<?> superSuperClass = superClass.getSuperclass();
+ classFields.addAll(Arrays.asList(superSuperClass.getDeclaredFields()));
+ }
+ }
+ }
+
+ Set<FieldDefinition> uniqueFields = new LinkedHashSet<FieldDefinition>();
+ T defaultObject = Db.instance(clazz);
+ for (Field f : classFields) {
+ // check if we should skip this field
+ if (f.isAnnotationPresent(IQIgnore.class)) {
+ continue;
+ }
+
+ // default to field name
+ String columnName = f.getName();
+ boolean isAutoIncrement = false;
+ boolean isPrimaryKey = false;
+ int length = 0;
+ int scale = 0;
+ boolean trim = false;
+ boolean nullable = !f.getType().isPrimitive();
+ EnumType enumType = null;
+ Class<?> enumTypeClass = null;
+ String defaultValue = "";
+ String constraint = "";
+ String dataType = null;
+ Class<? extends DataTypeAdapter<?>> typeAdapter = null;
+
+ // configure Java -> SQL enum mapping
+ if (f.getType().isEnum()) {
+ enumType = EnumType.DEFAULT_TYPE;
+ if (f.getType().isAnnotationPresent(IQEnum.class)) {
+ // enum definition is annotated for all instances
+ IQEnum iqenum = f.getType().getAnnotation(IQEnum.class);
+ enumType = iqenum.value();
+ }
+ if (f.isAnnotationPresent(IQEnum.class)) {
+ // this instance of the enum is annotated
+ IQEnum iqenum = f.getAnnotation(IQEnum.class);
+ enumType = iqenum.value();
+ }
+
+ if (EnumId.class.isAssignableFrom(f.getType())) {
+ // custom enumid mapping
+ enumTypeClass = ((EnumId<?>) f.getType().getEnumConstants()[0]).enumIdClass();
+ }
+ }
+
+ // try using default object
+ try {
+ f.setAccessible(true);
+ Object value = f.get(defaultObject);
+ if (value != null) {
+ if (value.getClass().isEnum()) {
+ // enum default, convert to target type
+ Enum<?> anEnum = (Enum<?>) value;
+ Object o = Utils.convertEnum(anEnum, enumType);
+ defaultValue = ModelUtils.formatDefaultValue(o);
+ } else {
+ // object default
+ defaultValue = ModelUtils.formatDefaultValue(value);
+ }
+ }
+ } catch (IllegalAccessException e) {
+ throw new IciqlException(e, "failed to get default object for {0}", columnName);
+ }
+
+ 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();
+ length = col.length();
+ scale = col.scale();
+ trim = col.trim();
+ nullable = col.nullable();
+
+ if (col.typeAdapter() != null && col.typeAdapter() != StandardJDBCTypeAdapter.class) {
+ typeAdapter = col.typeAdapter();
+ DataTypeAdapter<?> dtt = db.getDialect().getAdapter(col.typeAdapter());
+ dataType = dtt.getDataType();
+ }
+
+ // annotation overrides
+ if (!StringUtils.isNullOrEmpty(col.defaultValue())) {
+ defaultValue = col.defaultValue();
+ }
+ }
+
+ boolean hasConstraint = f.isAnnotationPresent(IQConstraint.class);
+ if (hasConstraint) {
+ IQConstraint con = f.getAnnotation(IQConstraint.class);
+ // annotation overrides
+ if (!StringUtils.isNullOrEmpty(con.value())) {
+ constraint = con.value();
+ }
+ }
+
+ boolean reflectiveMatch = !byAnnotationsOnly;
+ if (reflectiveMatch || hasAnnotation || hasConstraint) {
+ FieldDefinition fieldDef = new FieldDefinition();
+ fieldDef.isPrimitive = f.getType().isPrimitive();
+ fieldDef.field = f;
+ fieldDef.columnName = columnName;
+ fieldDef.isAutoIncrement = isAutoIncrement;
+ fieldDef.isPrimaryKey = isPrimaryKey;
+ fieldDef.length = length;
+ fieldDef.scale = scale;
+ fieldDef.trim = trim;
+ fieldDef.nullable = nullable;
+ fieldDef.defaultValue = defaultValue;
+ fieldDef.enumType = enumType;
+ fieldDef.enumTypeClass = enumTypeClass;
+ fieldDef.dataType = StringUtils.isNullOrEmpty(dataType) ? ModelUtils.getDataType(fieldDef) : dataType;
+ fieldDef.typeAdapter = typeAdapter;
+ fieldDef.constraint = constraint;
+ uniqueFields.add(fieldDef);
+ }
+ }
+ fields.addAll(uniqueFields);
+
+ List<String> primaryKey = Utils.newArrayList();
+ int primitiveBoolean = 0;
+ for (FieldDefinition fieldDef : fields) {
+ if (fieldDef.isPrimaryKey) {
+ primaryKey.add(fieldDef.columnName);
+ }
+ if (fieldDef.isPrimitive && fieldDef.field.getType().equals(boolean.class)) {
+ primitiveBoolean++;
+ }
+ }
+ if (primitiveBoolean > 1) {
+ multiplePrimitiveBools = true;
+ IciqlLogger
+ .warn("Model {0} has multiple primitive booleans! Possible where,set,join clause problem!");
+ }
+ if (primaryKey.size() > 0) {
+ setPrimaryKey(primaryKey);
+ }
+ }
+
+ void checkMultipleBooleans() {
+ if (multiplePrimitiveBools) {
+ throw new IciqlException(
+ "Can not explicitly reference a primitive boolean if there are multiple boolean fields in your model class!");
+ }
+ }
+
+ void checkMultipleEnums(Object o) {
+ if (o == null) {
+ return;
+ }
+ Class<?> clazz = o.getClass();
+ if (!clazz.isEnum()) {
+ return;
+ }
+
+ int fieldCount = 0;
+ for (FieldDefinition fieldDef : fields) {
+ Class<?> targetType = fieldDef.field.getType();
+ if (clazz.equals(targetType)) {
+ fieldCount++;
+ }
+ }
+
+ if (fieldCount > 1) {
+ throw new IciqlException(
+ "Can not explicitly reference {0} because there are {1} {0} fields in your model class!",
+ clazz.getSimpleName(), fieldCount);
+ }
+ }
+
+ /**
+ * Optionally truncates strings to the maximum length and converts
+ * java.lang.Enum types to Strings or Integers.
+ */
+ Object getValue(Object obj, FieldDefinition field) {
+ Object value = field.getValue(obj);
+ if (value == null) {
+ return value;
+ }
+ if (field.enumType != null) {
+ // convert enumeration to INT or STRING
+ Enum<?> iqenum = (Enum<?>) value;
+ switch (field.enumType) {
+ case NAME:
+ if (field.trim && field.length > 0) {
+ if (iqenum.name().length() > field.length) {
+ return iqenum.name().substring(0, field.length);
+ }
+ }
+ return iqenum.name();
+ case ORDINAL:
+ return iqenum.ordinal();
+ case ENUMID:
+ if (!EnumId.class.isAssignableFrom(value.getClass())) {
+ throw new IciqlException(field.field.getName() + " does not implement EnumId!");
+ }
+ EnumId<?> enumid = (EnumId<?>) value;
+ return enumid.enumId();
+ }
+ }
+
+ if (field.trim && field.length > 0) {
+ if (value instanceof String) {
+ // clip strings
+ String s = (String) value;
+ if (s.length() > field.length) {
+ return s.substring(0, field.length);
+ }
+ return s;
+ }
+ return value;
+ }
+
+ // return the value unchanged
+ return value;
+ }
+
+ PreparedStatement createInsertStatement(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) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append(db.getDialect().prepareColumnName(field.columnName));
+ }
+ buff.append(") VALUES(");
+ buff.resetCount();
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = getValue(obj, field);
+ if (value == null && !field.nullable) {
+ // try to interpret and instantiate a default value
+ value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
+ }
+ Object parameter = db.getDialect().serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ IciqlLogger.insert(stat.getSQL());
+ return stat.prepare(returnKey);
+ }
+
+ long insert(Db db, Object obj, boolean returnKey) {
+ if (!StringUtils.isNullOrEmpty(viewTableName)) {
+ throw new IciqlException("Iciql does not support inserting rows into views!");
+ }
+ SQLStatement stat = new SQLStatement(db);
+ StatementBuilder buff = new StatementBuilder("INSERT INTO ");
+ buff.append(db.getDialect().prepareTableName(schemaName, tableName)).append('(');
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append(db.getDialect().prepareColumnName(field.columnName));
+ }
+ buff.append(") VALUES(");
+ buff.resetCount();
+ for (FieldDefinition field : fields) {
+ if (skipInsertField(field, obj)) {
+ continue;
+ }
+ buff.appendExceptFirst(", ");
+ buff.append('?');
+ Object value = getValue(obj, field);
+ if (value == null && !field.nullable) {
+ // try to interpret and instantiate a default value
+ value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
+ }
+ Object parameter = db.getDialect().serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ buff.append(')');
+ stat.setSQL(buff.toString());
+ IciqlLogger.insert(stat.getSQL());
+ if (returnKey) {
+ return stat.executeInsert();
+ }
+ return stat.executeUpdate();
+ }
+
+ private boolean skipInsertField(FieldDefinition field, Object obj) {
+ if (field.isAutoIncrement) {
+ Object value = getValue(obj, field);
+ if (field.isPrimitive) {
+ // skip uninitialized primitive autoincrement values
+ if (value.toString().equals("0")) {
+ return true;
+ }
+ } else if (value == null) {
+ // skip null object autoincrement values
+ return true;
+ }
+ } else {
+ // conditionally skip insert of null
+ Object value = getValue(obj, field);
+ if (value == null) {
+ if (field.nullable) {
+ // skip null assignment, field is nullable
+ return true;
+ } else if (StringUtils.isNullOrEmpty(field.defaultValue)) {
+ IciqlLogger.warn("no default value, skipping null insert assignment for {0}.{1}",
+ tableName, field.columnName);
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ int 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);
+ db.getDialect().prepareMerge(stat, schemaName, tableName, this, obj);
+ IciqlLogger.merge(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ int update(Db db, Object obj) {
+ if (!StringUtils.isNullOrEmpty(viewTableName)) {
+ throw new IciqlException("Iciql does not support updating rows in views!");
+ }
+ 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) {
+ Object value = getValue(obj, field);
+ if (value == null && !field.nullable) {
+ // try to interpret and instantiate a default value
+ value = ModelUtils.getDefaultValue(field, db.getDialect().getDateTimeClass());
+ }
+ buff.appendExceptFirst(", ");
+ buff.append(db.getDialect().prepareColumnName(field.columnName));
+ buff.append(" = ?");
+ Object parameter = db.getDialect().serialize(value, field.typeAdapter);
+ stat.addParameter(parameter);
+ }
+ }
+ Object alias = Utils.newObject(obj.getClass());
+ Query<Object> query = Query.from(db, alias);
+ boolean firstCondition = true;
+ for (FieldDefinition field : fields) {
+ if (field.isPrimaryKey) {
+ Object fieldAlias = field.getValue(alias);
+ Object value = field.getValue(obj);
+ if (field.isPrimitive) {
+ fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);
+ }
+ if (!firstCondition) {
+ query.addConditionToken(ConditionAndOr.AND);
+ }
+ firstCondition = false;
+ query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));
+ }
+ }
+ stat.setSQL(buff.toString());
+ query.appendWhere(stat);
+ IciqlLogger.update(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ int delete(Db db, Object obj) {
+ if (!StringUtils.isNullOrEmpty(viewTableName)) {
+ throw new IciqlException("Iciql does not support deleting rows from views!");
+ }
+ 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 fieldAlias = field.getValue(alias);
+ Object value = field.getValue(obj);
+ if (field.isPrimitive) {
+ fieldAlias = query.getPrimitiveAliasByValue(fieldAlias);
+ }
+ if (!firstCondition) {
+ query.addConditionToken(ConditionAndOr.AND);
+ }
+ firstCondition = false;
+ query.addConditionToken(new Condition<Object>(fieldAlias, value, CompareType.EQUAL));
+ }
+ }
+ stat.setSQL(buff.toString());
+ query.appendWhere(stat);
+ IciqlLogger.delete(stat.getSQL());
+ return stat.executeUpdate();
+ }
+
+ TableDefinition<T> createIfRequired(Db db) {
+ // globally enable/disable check of create if required
+ if (db.getSkipCreate()) {
+ return this;
+ }
+ if (!createIfRequired) {
+ // skip table and index creation
+ // but still check for upgrades
+ db.upgradeTable(this);
+ return this;
+ }
+ if (db.hasCreated(clazz)) {
+ return this;
+ }
+ SQLStatement stat = new SQLStatement(db);
+ if (StringUtils.isNullOrEmpty(viewTableName)) {
+ db.getDialect().prepareCreateTable(stat, this);
+ } else {
+ db.getDialect().prepareCreateView(stat, this);
+ }
+ IciqlLogger.create(stat.getSQL());
+ try {
+ stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {
+ throw e;
+ }
+ }
+
+ // create indexes
+ for (IndexDefinition index : indexes) {
+ stat = new SQLStatement(db);
+ db.getDialect().prepareCreateIndex(stat, schemaName, tableName, index);
+ IciqlLogger.create(stat.getSQL());
+ try {
+ stat.executeUpdate();
+ } catch (IciqlException e) {
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS
+ && e.getIciqlCode() != IciqlException.CODE_DUPLICATE_KEY) {
+ throw e;
+ }
+ }
+ }
+
+ // tables are created using IF NOT EXISTS
+ // but we may still need to upgrade
+ db.upgradeTable(this);
+ return this;
+ }
+
+ 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.value())) {
+ schemaName = schemaAnnotation.value();
+ }
+ }
+
+ 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()
+ createIfRequired = tableAnnotation.create();
+
+ // model version
+ if (clazz.isAnnotationPresent(IQVersion.class)) {
+ IQVersion versionAnnotation = clazz.getAnnotation(IQVersion.class);
+ if (versionAnnotation.value() > 0) {
+ tableVersion = versionAnnotation.value();
+ }
+ }
+
+ // setup the primary index, if properly annotated
+ if (tableAnnotation.primaryKey().length > 0) {
+ List<String> primaryKey = Utils.newArrayList();
+ primaryKey.addAll(Arrays.asList(tableAnnotation.primaryKey()));
+ setPrimaryKey(primaryKey);
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQView.class)) {
+ IQView viewAnnotation = clazz.getAnnotation(IQView.class);
+
+ // setup view name mapping, if properly annotated
+ // set this as the table name so it fits in seemlessly with iciql
+ if (!StringUtils.isNullOrEmpty(viewAnnotation.name())) {
+ tableName = viewAnnotation.name();
+ } else {
+ tableName = clazz.getSimpleName();
+ }
+
+ // setup source table name mapping, if properly annotated
+ if (!StringUtils.isNullOrEmpty(viewAnnotation.tableName())) {
+ viewTableName = viewAnnotation.tableName();
+ } else {
+ // check for IQTable annotation on super class
+ Class<?> superClass = clazz.getSuperclass();
+ if (superClass.isAnnotationPresent(IQTable.class)) {
+ IQTable table = superClass.getAnnotation(IQTable.class);
+ if (StringUtils.isNullOrEmpty(table.name())) {
+ // super.SimpleClassName
+ viewTableName = superClass.getSimpleName();
+ } else {
+ // super.IQTable.name()
+ viewTableName = table.name();
+ }
+ } else if (superClass.isAnnotationPresent(IQView.class)) {
+ // super class is a view
+ IQView parentView = superClass.getAnnotation(IQView.class);
+ if (StringUtils.isNullOrEmpty(parentView.tableName())) {
+ // parent view does not define a tableName, must be inherited
+ Class<?> superParent = superClass.getSuperclass();
+ if (superParent != null && superParent.isAnnotationPresent(IQTable.class)) {
+ IQTable superParentTable = superParent.getAnnotation(IQTable.class);
+ if (StringUtils.isNullOrEmpty(superParentTable.name())) {
+ // super.super.SimpleClassName
+ viewTableName = superParent.getSimpleName();
+ } else {
+ // super.super.IQTable.name()
+ viewTableName = superParentTable.name();
+ }
+ }
+ } else {
+ // super.IQView.tableName()
+ viewTableName = parentView.tableName();
+ }
+ }
+
+ if (StringUtils.isNullOrEmpty(viewTableName)) {
+ // still missing view table name
+ throw new IciqlException("View model class \"{0}\" is missing a table name!", tableName);
+ }
+ }
+
+ // allow control over createTableIfRequired()
+ createIfRequired = viewAnnotation.create();
+ }
+
+ if (clazz.isAnnotationPresent(IQIndex.class)) {
+ // single table index
+ IQIndex index = clazz.getAnnotation(IQIndex.class);
+ addIndex(index);
+ }
+
+ if (clazz.isAnnotationPresent(IQIndexes.class)) {
+ // multiple table indexes
+ IQIndexes indexes = clazz.getAnnotation(IQIndexes.class);
+ for (IQIndex index : indexes.value()) {
+ addIndex(index);
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintUnique.class)) {
+ // single table unique constraint
+ IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class);
+ addConstraintUnique(constraint);
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintsUnique.class)) {
+ // multiple table unique constraints
+ IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class);
+ for (IQContraintUnique constraint : constraints.value()) {
+ addConstraintUnique(constraint);
+ }
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) {
+ // single table constraint
+ IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class);
+ addConstraintForeignKey(constraint);
+ }
+
+ if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) {
+ // multiple table constraints
+ IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class);
+ for (IQContraintForeignKey constraint : constraints.value()) {
+ addConstraintForeignKey(constraint);
+ }
+ }
+
+ }
+
+ private void addConstraintForeignKey(IQContraintForeignKey constraint) {
+ List<String> foreignColumns = Arrays.asList(constraint.foreignColumns());
+ List<String> referenceColumns = Arrays.asList(constraint.referenceColumns());
+ addConstraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType());
+ }
+
+ private void addConstraintUnique(IQContraintUnique constraint) {
+ List<String> uniqueColumns = Arrays.asList(constraint.uniqueColumns());
+ addConstraintUnique(constraint.name(), uniqueColumns);
+ }
+
+ /**
+ * Defines a foreign key constraint with the specified parameters.
+ *
+ * @param name
+ * name of the constraint
+ * @param foreignColumns
+ * list of columns declared as foreign
+ * @param referenceName
+ * reference table name
+ * @param referenceColumns
+ * list of columns used in reference table
+ * @param deleteType
+ * action on delete
+ * @param updateType
+ * action on update
+ * @param deferrabilityType
+ * deferrability mode
+ */
+ private void addConstraintForeignKey(String name,
+ List<String> foreignColumns, String referenceName,
+ List<String> referenceColumns, ConstraintDeleteType deleteType,
+ ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {
+ ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ constraint.constraintName = tableName + "_fkey_" + constraintsForeignKey.size();
+ } else {
+ constraint.constraintName = name;
+ }
+ constraint.foreignColumns = Utils.newArrayList(foreignColumns);
+ constraint.referenceColumns = Utils.newArrayList(referenceColumns);
+ constraint.referenceTable = referenceName;
+ constraint.deleteType = deleteType;
+ constraint.updateType = updateType;
+ constraint.deferrabilityType = deferrabilityType;
+ constraintsForeignKey.add(constraint);
+ }
+
+ private void addIndex(IQIndex index) {
+ List<String> columns = Arrays.asList(index.value());
+ addIndex(index.name(), index.type(), columns);
+ }
+
+ List<IndexDefinition> getIndexes() {
+ return indexes;
+ }
+
+ List<ConstraintUniqueDefinition> getContraintsUnique() {
+ return constraintsUnique;
+ }
+
+ List<ConstraintForeignKeyDefinition> getContraintsForeignKey() {
+ return constraintsForeignKey;
+ }
+
+ private 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, boolean reuse) {
+ for (FieldDefinition def : fields) {
+ Object value;
+ if (!reuse) {
+ value = def.initWithNewObject(obj);
+ } else {
+ value = def.getValue(obj);
+ }
+ SelectColumn<T> column = new SelectColumn<T>(table, def);
+ map.put(value, column);
+ }
+ }
+
+ /**
+ * Most queries executed by iciql have named select lists (select alpha,
+ * beta where...) but sometimes a wildcard select is executed (select *).
+ * When a wildcard query is executed on a table that has more columns than
+ * are mapped in your model object, this creates a column mapping issue.
+ * JaQu assumed that you can always use the integer index of the
+ * reflectively mapped field definition to determine position in the result
+ * set.
+ *
+ * This is not always true.
+ *
+ * iciql identifies when a select * query is executed and maps column names
+ * to a column index from the result set. If the select statement is
+ * explicit, then the standard assumed column index is used instead.
+ *
+ * @param rs
+ * @return
+ */
+ int[] mapColumns(boolean wildcardSelect, ResultSet rs) {
+ int[] columns = new int[fields.size()];
+ for (int i = 0; i < fields.size(); i++) {
+ try {
+ FieldDefinition def = fields.get(i);
+ int columnIndex;
+ if (wildcardSelect) {
+ // select *
+ // create column index by field name
+ columnIndex = rs.findColumn(def.columnName);
+ } else {
+ // select alpha, beta, gamma, etc
+ // explicit select order
+ columnIndex = i + 1;
+ }
+ columns[i] = columnIndex;
+ } catch (SQLException s) {
+ throw new IciqlException(s);
+ }
+ }
+ return columns;
+ }
+
+ void readRow(SQLDialect dialect, Object item, ResultSet rs, int[] columns) {
+ for (int i = 0; i < fields.size(); i++) {
+ FieldDefinition def = fields.get(i);
+ int index = columns[i];
+ Object o = def.read(rs, index);
+ def.setValue(dialect, 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) {
+ // select t0.col1, t0.col2, t0.col3...
+ // select table1.col1, table1.col2, table1.col3...
+ String selectDot = "";
+ SelectTable<?> sel = query.getSelectTable(x);
+ if (sel != null) {
+ if (query.isJoin()) {
+ selectDot = sel.getAs() + ".";
+ } else {
+ String sn = sel.getAliasDefinition().schemaName;
+ String tn = sel.getAliasDefinition().tableName;
+ selectDot = query.getDb().getDialect().prepareTableName(sn, tn) + ".";
+ }
+ }
+
+ for (int i = 0; i < fields.size(); i++) {
+ if (i > 0) {
+ stat.appendSQL(", ");
+ }
+ stat.appendSQL(selectDot);
+ FieldDefinition def = fields.get(i);
+ if (def.isPrimitive) {
+ Object obj = def.getValue(x);
+ Object alias = query.getPrimitiveAliasByValue(obj);
+ query.appendSQL(stat, x, alias);
+ } else {
+ Object obj = def.getValue(x);
+ query.appendSQL(stat, x, obj);
+ }
+ }
+ }
+}