aboutsummaryrefslogtreecommitdiffstats
path: root/src/main/java
diff options
context:
space:
mode:
authorJames Moger <james.moger@gitblit.com>2014-11-03 08:15:00 -0500
committerJames Moger <james.moger@gitblit.com>2014-11-03 12:51:16 -0500
commit3529ae168b78dd699c3036ebc8a2ccfd268083bc (patch)
treeec4ba694ae3a31b12a06ef81f26f5a3de1119018 /src/main/java
parent0fd12a7004a40d8dd1f1857e791c23d31267ac1f (diff)
downloadiciql-3529ae168b78dd699c3036ebc8a2ccfd268083bc.tar.gz
iciql-3529ae168b78dd699c3036ebc8a2ccfd268083bc.zip
Move constraint definitions into CREATE TABLE instead of ALTER TABLE
Diffstat (limited to 'src/main/java')
-rw-r--r--src/main/java/com/iciql/Db.java5
-rw-r--r--src/main/java/com/iciql/SQLDialect.java40
-rw-r--r--src/main/java/com/iciql/SQLDialectDefault.java900
-rw-r--r--src/main/java/com/iciql/TableDefinition.java2524
4 files changed, 1747 insertions, 1722 deletions
diff --git a/src/main/java/com/iciql/Db.java b/src/main/java/com/iciql/Db.java
index 4c99037..67e73b0 100644
--- a/src/main/java/com/iciql/Db.java
+++ b/src/main/java/com/iciql/Db.java
@@ -90,15 +90,14 @@ public class Db implements AutoCloseable {
private Db(Connection conn) {
this.conn = conn;
String databaseName = null;
- DatabaseMetaData data = null;
try {
- data = conn.getMetaData();
+ DatabaseMetaData data = conn.getMetaData();
databaseName = data.getDatabaseProductName();
} catch (SQLException s) {
throw new IciqlException(s, "failed to retrieve database metadata!");
}
dialect = getDialect(databaseName, conn.getClass().getName());
- dialect.configureDialect(databaseName, data);
+ dialect.configureDialect(this);
}
/**
diff --git a/src/main/java/com/iciql/SQLDialect.java b/src/main/java/com/iciql/SQLDialect.java
index 5d3522a..33c5369 100644
--- a/src/main/java/com/iciql/SQLDialect.java
+++ b/src/main/java/com/iciql/SQLDialect.java
@@ -18,11 +18,7 @@
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.
@@ -170,35 +165,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.
*
* @param stat
diff --git a/src/main/java/com/iciql/SQLDialectDefault.java b/src/main/java/com/iciql/SQLDialectDefault.java
index d601302..17ee763 100644
--- a/src/main/java/com/iciql/SQLDialectDefault.java
+++ b/src/main/java/com/iciql/SQLDialectDefault.java
@@ -1,406 +1,496 @@
-/*
- * 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.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(String databaseName, DatabaseMetaData data) {
- this.databaseName = databaseName;
- try {
- databaseMajorVersion = data.getDatabaseMajorVersion();
- databaseMinorVersion = data.getDatabaseMinorVersion();
- databaseVersion = Float.parseFloat(databaseMajorVersion + "."
- + databaseMinorVersion);
- productVersion = data.getDatabaseProductVersion();
- } catch (SQLException e) {
- throw new IciqlException(e);
- }
- }
-
- @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(')');
- }
- }
- 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();
- }
-
+/*
+ * 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
diff --git a/src/main/java/com/iciql/TableDefinition.java b/src/main/java/com/iciql/TableDefinition.java
index db67fc3..fc83d29 100644
--- a/src/main/java/com/iciql/TableDefinition.java
+++ b/src/main/java/com/iciql/TableDefinition.java
@@ -1,1277 +1,1247 @@
-/*
- * 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();
- private ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList();
- private 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;
- }
- }
- }
-
- // create unique constraints
- for (ConstraintUniqueDefinition constraint : constraintsUnique) {
- stat = new SQLStatement(db);
- db.getDialect().prepareCreateConstraintUnique(stat, schemaName, tableName, constraint);
- 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;
- }
- }
- }
-
- // create foreign keys constraints
- for (ConstraintForeignKeyDefinition constraint : constraintsForeignKey) {
- stat = new SQLStatement(db);
- db.getDialect().prepareCreateConstraintForeignKey(stat, schemaName, tableName, constraint);
- 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);
- }
- }
- }
-}
+/*
+ * 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);
+ }
+ }
+ }
+}