]> source.dussan.org Git - iciql.git/commitdiff
Move constraint definitions into CREATE TABLE instead of ALTER TABLE
authorJames Moger <james.moger@gitblit.com>
Mon, 3 Nov 2014 13:15:00 +0000 (08:15 -0500)
committerJames Moger <james.moger@gitblit.com>
Mon, 3 Nov 2014 17:51:16 +0000 (12:51 -0500)
src/main/java/com/iciql/Db.java
src/main/java/com/iciql/SQLDialect.java
src/main/java/com/iciql/SQLDialectDefault.java
src/main/java/com/iciql/TableDefinition.java

index 4c99037f1cd51079287413364693b017fb71c195..67e73b09a114dfe7d2f2efb9830b4a04a1ab7e07 100644 (file)
@@ -90,15 +90,14 @@ public class Db implements AutoCloseable {
        private Db(Connection conn) {\r
                this.conn = conn;\r
                String databaseName = null;\r
-               DatabaseMetaData data = null;\r
                try {\r
-                       data = conn.getMetaData();\r
+                       DatabaseMetaData data = conn.getMetaData();\r
                        databaseName = data.getDatabaseProductName();\r
                } catch (SQLException s) {\r
                        throw new IciqlException(s, "failed to retrieve database metadata!");\r
                }\r
                dialect = getDialect(databaseName, conn.getClass().getName());\r
-               dialect.configureDialect(databaseName, data);\r
+               dialect.configureDialect(this);\r
        }\r
 \r
        /**\r
index 5d3522af5c739f1ffa25cd8961884005d5d39bce..33c5369ffa21d8fc307d9193178d69cca7d80823 100644 (file)
 
 package com.iciql;
 
-import java.sql.DatabaseMetaData;
-
 import com.iciql.Iciql.DataTypeAdapter;
-import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
-import com.iciql.TableDefinition.ConstraintUniqueDefinition;
 import com.iciql.TableDefinition.IndexDefinition;
 
 /**
@@ -65,12 +61,11 @@ public interface SQLDialect {
        Object deserialize(Object value, Class<? extends DataTypeAdapter<?>> typeAdapter);
 
        /**
-        * Configure the dialect from the database metadata.
+        * Configure the dialect.
         *
-        * @param databaseName
-        * @param data
+        * @param db
         */
-       void configureDialect(String databaseName, DatabaseMetaData data);
+       void configureDialect(Db db);
 
        /**
         * Returns true if savepoints are supported.
@@ -169,35 +164,6 @@ public interface SQLDialect {
         */
        void prepareCreateIndex(SQLStatement stat, String schemaName, String tableName, IndexDefinition index);
 
-       /**
-        * Get the ALTER statement.
-        *
-        * @param stat
-        *            return the SQL statement
-        * @param schemaName
-        *            the schema name
-        * @param tableName
-        *            the table name
-        * @param constraint
-        *            the constraint definition
-        */
-       void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint);
-
-       /**
-        * Get the ALTER statement.
-        *
-        * @param stat
-        *            return the SQL statement
-        * @param schemaName
-        *            the schema name
-        * @param tableName
-        *            the table name
-        * @param constraint
-        *            the constraint definition
-        * return the SQL statement
-        */
-       void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint);
-
        /**
         * Get a MERGE or REPLACE INTO statement.
         *
index d6013022e62a72f586b899436a28ebcfd19a0483..17ee763f2aaa8fb1f1827e8f941f588133fb0145 100644 (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.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
index db67fc3126bdfa412e24b489232373f37746d9c2..fc83d292e4b8a38e4b052e1a1881402586eef5cf 100644 (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);
+                       }
+               }
+       }
+}