summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build.xml2
-rw-r--r--src/com/iciql/Define.java16
-rw-r--r--src/com/iciql/Iciql.java155
-rw-r--r--src/com/iciql/SQLDialect.java49
-rw-r--r--src/com/iciql/SQLDialectDefault.java105
-rw-r--r--src/com/iciql/TableDefinition.java241
-rw-r--r--src/com/iciql/TableInspector.java7
-rw-r--r--tests/com/iciql/test/ForeignKeyTest.java64
-rw-r--r--tests/com/iciql/test/IciqlSuite.java3
-rw-r--r--tests/com/iciql/test/models/CategoryAnnotationOnly.java69
-rw-r--r--tests/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java103
11 files changed, 809 insertions, 5 deletions
diff --git a/build.xml b/build.xml
index bc68087..351c43b 100644
--- a/build.xml
+++ b/build.xml
@@ -78,7 +78,7 @@
<property name="library.jar" value="iciql-${iq.version}.jar" />
<property name="javadoc.jar" value="iciql-${iq.version}-javadoc.jar" />
- <property name="sources.jar" value="/iciql-${iq.version}-sources.jar" />
+ <property name="sources.jar" value="iciql-${iq.version}-sources.jar" />
<property name="distribution.zip" value="iciql-${iq.version}.zip" />
</target>
diff --git a/src/com/iciql/Define.java b/src/com/iciql/Define.java
index 53f9862..5d7000e 100644
--- a/src/com/iciql/Define.java
+++ b/src/com/iciql/Define.java
@@ -1,6 +1,7 @@
/*
* 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.
@@ -44,6 +45,21 @@ public class Define {
currentTableDefinition.defineIndex(name, type, columns);
}
+ public static void constraintUnique(String name, Object... columns) {
+ checkInDefine();
+ currentTableDefinition.defineConstraintUnique(name, columns);
+ }
+
+ /*
+ * The variable argument type Object can't be used twice :-)
+ */
+// public static void constraintForeignKey(String name, String refTableName,
+// ConstraintDeleteType deleteType, ConstraintUpdateType updateType,
+// ConstraintDeferrabilityType deferrabilityType, Object... columns, Object... refColumns) {
+// checkInDefine();
+// currentTableDefinition.defineForeignKey(name, columns, refTableName, Columns, deleteType, updateType, deferrabilityType);
+// }
+
public static void primaryKey(Object... columns) {
checkInDefine();
currentTableDefinition.definePrimaryKey(columns);
diff --git a/src/com/iciql/Iciql.java b/src/com/iciql/Iciql.java
index 7b3a7c1..9956ac4 100644
--- a/src/com/iciql/Iciql.java
+++ b/src/com/iciql/Iciql.java
@@ -1,6 +1,7 @@
/*
* 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.
@@ -294,6 +295,160 @@ public interface Iciql {
}
/**
+ * Enumeration defining the ON DELETE actions.
+ */
+ public static enum ConstraintDeleteType {
+ UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT;
+ }
+
+ /**
+ * Enumeration defining the ON UPDATE actions.
+ */
+ public static enum ConstraintUpdateType {
+ UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT;
+ }
+
+ /**
+ * Enumeration defining the deferrability.
+ */
+ public static enum ConstraintDeferrabilityType {
+ UNSET, DEFERRABLE_INITIALLY_DEFERRED, DEFERRABLE_INITIALLY_IMMEDIATE, NOT_DEFERRABLE;
+ }
+
+ /**
+ * A foreign key constraint annotation.
+ * <p>
+ * <ul>
+ * <li>@IQContraintForeignKey(
+ * foreignColumns = { "idaccount"},
+ * referenceName = "account",
+ * referenceColumns = { "id" },
+ * deleteType = ConstrainDeleteType.CASCADE,
+ * updateType = ConstraintUpdateType.NO_ACTION )
+ * </ul>
+ * Note : reference columns should have a unique constraint defined in referenceName table,
+ * some database used to define a unique index instead of a unique constraint
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintForeignKey {
+
+ /**
+ * Constraint name. If null or empty, iciql will generate one.
+ */
+ String name() default "";
+
+ /**
+ * Type of the action on delete, default to unspecified.
+ * <ul>
+ * <li>com.iciql.iciql.ConstrainDeleteType.CASCADE
+ * <li>com.iciql.iciql.ConstrainDeleteType.RESTRICT
+ * <li>com.iciql.iciql.ConstrainDeleteType.SET_NULL
+ * <li>com.iciql.iciql.ConstrainDeleteType.NO_ACTION
+ * <li>com.iciql.iciql.ConstrainDeleteType.SET_DEFAULT
+ * </ul>
+ */
+ ConstraintDeleteType deleteType() default ConstraintDeleteType.UNSET;
+
+ /**
+ * Type of the action on update, default to unspecified.
+ * <ul>
+ * <li>com.iciql.iciql.ConstrainUpdateType.CASCADE
+ * <li>com.iciql.iciql.ConstrainUpdateType.RESTRICT
+ * <li>com.iciql.iciql.ConstrainUpdateType.SET_NULL
+ * <li>com.iciql.iciql.ConstrainUpdateType.NO_ACTION
+ * <li>com.iciql.iciql.ConstrainUpdateType.SET_DEFAULT
+ * </ul>
+ */
+ ConstraintUpdateType updateType() default ConstraintUpdateType.UNSET;
+
+ /**
+ * Type of the deferrability mode, default to unspecified
+ * <ul>
+ * <li>com.iciql.iciql.ConstrainUpdateType.CASCADE
+ * <li>ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_DEFERRED
+ * <li>ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_IMMEDIATE
+ * <li>ConstraintDeferrabilityType.NOT_DEFERRABLE
+ * </ul>
+ */
+ ConstraintDeferrabilityType deferrabilityType() default ConstraintDeferrabilityType.UNSET;
+
+ /**
+ * The source table for the columns defined as foreign.
+ */
+ String tableName() default "";
+
+ /**
+ * Columns defined as 'foreign'.
+ * <ul>
+ * <li>single column : foreignColumns = "id"
+ * <li>multiple column : foreignColumns = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] foreignColumns() default {};
+
+ /**
+ * The reference table for the columns defined as references.
+ */
+ String referenceName() default "";
+
+ /**
+ * Columns defined as 'references'.
+ * <ul>
+ * <li>single column : referenceColumns = "id"
+ * <li>multiple column : referenceColumns = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] referenceColumns() default {};
+ }
+
+ /**
+ * Annotation to specify multiple foreign keys constraints.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintsForeignKey {
+ IQContraintForeignKey[] value() default {};
+ }
+
+ /**
+ * A unique constraint annotation.
+ * <p>
+ * <ul>
+ * <li>@IQContraintUnique(uniqueColumns = { "street", "city" })
+ * <li>@IQContraintUnique(name="streetconstraint", uniqueColumns = { "street", "city" })
+ * </ul>
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintUnique {
+
+ /**
+ * Constraint name. If null or empty, iciql will generate one.
+ */
+ String name() default "";
+
+ /**
+ * Columns defined as 'unique'.
+ * <ul>
+ * <li>single column : uniqueColumns = "id"
+ * <li>multiple column : uniqueColumns = { "id", "name", "date" }
+ * </ul>
+ */
+ String[] uniqueColumns() default {};
+
+ }
+
+ /**
+ * Annotation to specify multiple unique constraints.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface IQContraintsUnique {
+ IQContraintUnique[] value() default {};
+ }
+
+ /**
* Annotation to define a view.
*/
@Retention(RetentionPolicy.RUNTIME)
diff --git a/src/com/iciql/SQLDialect.java b/src/com/iciql/SQLDialect.java
index 8e3e3d2..5e9625f 100644
--- a/src/com/iciql/SQLDialect.java
+++ b/src/com/iciql/SQLDialect.java
@@ -1,6 +1,7 @@
/*
* 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.
@@ -19,6 +20,8 @@ package com.iciql;
import java.sql.DatabaseMetaData;
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;
import com.iciql.TableDefinition.IndexDefinition;
/**
@@ -84,7 +87,9 @@ public interface SQLDialect {
* Get the CREATE VIEW statement.
*
* @param stat
+ * return the SQL statement
* @param def
+ * table definition
*/
<T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def);
@@ -92,7 +97,9 @@ public interface SQLDialect {
* Get the CREATE VIEW statement.
*
* @param stat
+ * return the SQL statement
* @param def
+ * table definition
* @param fromWhere
*/
<T> void prepareCreateView(SQLStatement stat, TableDefinition<T> def, String fromWhere);
@@ -101,32 +108,68 @@ public interface SQLDialect {
* Get the DROP VIEW statement.
*
* @param stat
+ * return the SQL statement
* @param def
+ * table definition
*/
<T> void prepareDropView(SQLStatement stat, TableDefinition<T> def);
/**
* Get the CREATE INDEX statement.
*
+ * @param stat
+ * return the SQL statement
* @param schemaName
* the schema name
* @param tableName
* the table name
* @param index
* the index definition
- * @return the SQL statement
*/
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
+ * return the SQL statement
* @param schemaName
* the schema name
* @param tableName
* the table name
- * @param index
- * the index definition
+ * @param def
+ * the table definition
+ * @param obj
+ * values
*/
<T> void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition<T> def,
Object obj);
diff --git a/src/com/iciql/SQLDialectDefault.java b/src/com/iciql/SQLDialectDefault.java
index 0cd0448..afdc36d 100644
--- a/src/com/iciql/SQLDialectDefault.java
+++ b/src/com/iciql/SQLDialectDefault.java
@@ -1,6 +1,7 @@
/*
* 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.
@@ -22,6 +23,10 @@ import java.sql.SQLException;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
+import com.iciql.Iciql.ConstraintDeleteType;
+import com.iciql.Iciql.ConstraintUpdateType;
+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;
@@ -257,6 +262,8 @@ public class SQLDialectDefault implements SQLDialect {
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) {
@@ -337,4 +344,102 @@ public class SQLDialectDefault implements SQLDialect {
}
return o.toString();
}
+
+ @SuppressWarnings("incomplete-switch")
+ @Override
+ public void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("ALTER TABLE ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" ADD CONSTRAINT ");
+ buff.append(constraint.constraintName);
+ buff.append(" FOREIGN KEY ");
+ buff.append(" (");
+ for (String col : constraint.foreignColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ buff.append(" REFERENCES ");
+ buff.append(constraint.referenceTable);
+ buff.append(" (");
+ buff.resetCount();
+ for (String col : constraint.referenceColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ 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;
+ }
+ stat.setSQL(buff.toString().trim());
+ }
+
+ @Override
+ public void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint) {
+ StatementBuilder buff = new StatementBuilder();
+ buff.append("ALTER TABLE ");
+ buff.append(prepareTableName(schemaName, tableName));
+ buff.append(" ADD CONSTRAINT ");
+ buff.append(constraint.constraintName);
+ buff.append(" UNIQUE ");
+ buff.append(" (");
+ for (String col : constraint.uniqueColumns) {
+ buff.appendExceptFirst(", ");
+ buff.append(prepareColumnName(col));
+ }
+ buff.append(") ");
+ stat.setSQL(buff.toString().trim());
+ }
+
} \ No newline at end of file
diff --git a/src/com/iciql/TableDefinition.java b/src/com/iciql/TableDefinition.java
index de2b9ed..c348295 100644
--- a/src/com/iciql/TableDefinition.java
+++ b/src/com/iciql/TableDefinition.java
@@ -1,6 +1,7 @@
/*
* 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.
@@ -29,11 +30,18 @@ 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.EnumId;
import com.iciql.Iciql.EnumType;
import com.iciql.Iciql.IQColumn;
import com.iciql.Iciql.IQConstraint;
+import com.iciql.Iciql.IQContraintUnique;
+import com.iciql.Iciql.IQContraintsUnique;
import com.iciql.Iciql.IQEnum;
+import com.iciql.Iciql.IQContraintForeignKey;
+import com.iciql.Iciql.IQContraintsForeignKey;
import com.iciql.Iciql.IQIgnore;
import com.iciql.Iciql.IQIndex;
import com.iciql.Iciql.IQIndexes;
@@ -69,6 +77,32 @@ public class TableDefinition<T> {
}
/**
+ * 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.
*/
@@ -155,6 +189,8 @@ public class TableDefinition<T> {
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;
@@ -267,6 +303,77 @@ public class TableDefinition<T> {
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 + "_" + 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);
+ addForeignKey(name, columnNames, refTableName, referenceColumnNames,
+ deleteType, updateType, deferrabilityType);
+ }
+
+ /**
+ * Defines a foreign key constraint.
+ *
+ * @param name
+ * @param columnNames
+ */
+ private void addForeignKey(String name, List<String> columnNames, String referenceTableName,
+ List<String> referenceColumnNames, ConstraintDeleteType deleteType,
+ ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {
+ ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ constraint.constraintName = tableName + "_" + constraintsUnique.size();
+ } else {
+ constraint.constraintName = name;
+ }
+ constraint.foreignColumns = Utils.newArrayList(columnNames);
+ constraint.referenceTable = referenceTableName;
+ constraint.referenceColumns = Utils.newArrayList(referenceColumnNames);
+ constraint.deleteType = deleteType;
+ constraint.updateType = updateType;
+ constraint.deferrabilityType = deferrabilityType;
+ constraintsForeignKey.add(constraint);
+ }
+
void defineColumnName(Object column, String columnName) {
FieldDefinition def = fieldMap.get(column);
if (def != null) {
@@ -795,6 +902,36 @@ public class TableDefinition<T> {
}
}
+ // 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) {
+ // maybe we should check more error codes
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {
+ 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) {
+ // maybe we should check more error codes
+ if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {
+ throw e;
+ }
+ }
+ }
+
// tables are created using IF NOT EXISTS
// but we may still need to upgrade
db.upgradeTable(this);
@@ -911,6 +1048,102 @@ public class TableDefinition<T> {
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());
+ addContraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType());
+ }
+
+ private void addConstraintUnique(IQContraintUnique constraint) {
+ List<String> uniqueColumns = Arrays.asList(constraint.uniqueColumns());
+ addContraintUnique(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 addContraintForeignKey(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 + "_" + 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);
+ }
+
+ /**
+ * Defines a unique constraint with the specified parameters.
+ *
+ * @param name
+ * name of the constraint
+ * @param uniqueColumns
+ * list of columns declared as unique
+ */
+ private void addContraintUnique(String name, List<String> uniqueColumns) {
+ ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();
+ if (StringUtils.isNullOrEmpty(name)) {
+ constraint.constraintName = tableName + "_" + constraintsUnique.size();
+ } else {
+ constraint.constraintName = name;
+ }
+ constraint.uniqueColumns = Utils.newArrayList(uniqueColumns);
+ constraintsUnique.add(constraint);
}
private void addIndex(IQIndex index) {
@@ -922,6 +1155,14 @@ public class TableDefinition<T> {
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);
diff --git a/src/com/iciql/TableInspector.java b/src/com/iciql/TableInspector.java
index 71eb16d..fa6722d 100644
--- a/src/com/iciql/TableInspector.java
+++ b/src/com/iciql/TableInspector.java
@@ -1,6 +1,7 @@
/*
* 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.
@@ -42,6 +43,8 @@ import com.iciql.Iciql.IQIndexes;
import com.iciql.Iciql.IQSchema;
import com.iciql.Iciql.IQTable;
import com.iciql.Iciql.IndexType;
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;
import com.iciql.TableDefinition.FieldDefinition;
import com.iciql.TableDefinition.IndexDefinition;
import com.iciql.util.StatementBuilder;
@@ -470,6 +473,10 @@ public class TableInspector {
}
// TODO complete index validation.
// need to actually compare index types and columns within each index.
+
+ // TODO add constraints validation
+ List<ConstraintUniqueDefinition> defContraintsU = def.getContraintsUnique();
+ List<ConstraintForeignKeyDefinition> defContraintsFK = def.getContraintsForeignKey();
}
/**
diff --git a/tests/com/iciql/test/ForeignKeyTest.java b/tests/com/iciql/test/ForeignKeyTest.java
new file mode 100644
index 0000000..0941516
--- /dev/null
+++ b/tests/com/iciql/test/ForeignKeyTest.java
@@ -0,0 +1,64 @@
+/*
+ * 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.test;
+
+import static org.junit.Assert.assertEquals;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.iciql.Db;
+import com.iciql.test.models.CategoryAnnotationOnly;
+import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey;
+
+public class ForeignKeyTest {
+
+ /**
+ * This object represents a database (actually a connection to the
+ * database).
+ */
+
+ private Db db;
+
+ @Before
+ public void setUp() {
+ db = IciqlSuite.openNewDb();
+ db.insertAll(CategoryAnnotationOnly.getList());
+ db.insertAll(ProductAnnotationOnlyWithForeignKey.getList());
+ }
+
+ @After
+ public void tearDown() {
+ db.close();
+ }
+
+ @Test
+ public void testForeignKeyWithOnDeleteCascade() {
+ ProductAnnotationOnlyWithForeignKey p = new ProductAnnotationOnlyWithForeignKey();
+ long count1 = db.from(p).selectCount();
+
+ // should remove 2 associated products
+ CategoryAnnotationOnly c = new CategoryAnnotationOnly();
+ db.from(c).where(c.categoryId).is(1L).delete();
+
+ long count2 = db.from(p).selectCount();
+
+ assertEquals(count1, count2 + 2L);
+ }
+
+
+}
diff --git a/tests/com/iciql/test/IciqlSuite.java b/tests/com/iciql/test/IciqlSuite.java
index 68156f8..f888a8c 100644
--- a/tests/com/iciql/test/IciqlSuite.java
+++ b/tests/com/iciql/test/IciqlSuite.java
@@ -1,5 +1,6 @@
/*
* 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.
@@ -89,7 +90,7 @@ import com.iciql.util.Utils;
@SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, BooleanModelTest.class, ClobTest.class,
ConcurrencyTest.class, EnumsTest.class, ModelsTest.class, PrimitivesTest.class,
RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UpgradesTest.class, JoinTest.class,
- UUIDTest.class, ViewsTest.class })
+ UUIDTest.class, ViewsTest.class, ForeignKeyTest.class })
public class IciqlSuite {
private static final TestDb[] TEST_DBS = {
diff --git a/tests/com/iciql/test/models/CategoryAnnotationOnly.java b/tests/com/iciql/test/models/CategoryAnnotationOnly.java
new file mode 100644
index 0000000..7bccfe7
--- /dev/null
+++ b/tests/com/iciql/test/models/CategoryAnnotationOnly.java
@@ -0,0 +1,69 @@
+/*
+ * 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.test.models;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQContraintUnique;
+import com.iciql.Iciql.IQTable;
+
+/**
+ * A table containing category data.
+ */
+
+@IQTable(name = "AnnotatedCatagory", primaryKey = "id")
+// @IQIndex(value = "categ", type=IndexType.UNIQUE)
+@IQContraintUnique(uniqueColumns = { "categ" })
+public class CategoryAnnotationOnly {
+
+ @IQColumn(name = "id", autoIncrement = true)
+ public Long categoryId;
+
+ @IQColumn(name = "categ", length = 15, trim = true)
+ public String category;
+
+ public CategoryAnnotationOnly() {
+ // public constructor
+ }
+
+ private CategoryAnnotationOnly(long categoryId, String category) {
+ this.categoryId = categoryId;
+ this.category = category;
+ }
+
+ private static CategoryAnnotationOnly create(int categoryId, String category) {
+ return new CategoryAnnotationOnly(categoryId, category);
+ }
+
+ public static List<CategoryAnnotationOnly> getList() {
+ CategoryAnnotationOnly[] list = {
+ create(1, "Beverages"),
+ create(2, "Condiments"),
+ create(3, "Produce"),
+ create(4, "Meat/Poultry"),
+ create(5,"Seafood")
+ };
+ return Arrays.asList(list);
+ }
+
+ public String toString() {
+ return category;
+ }
+
+}
diff --git a/tests/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java b/tests/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java
new file mode 100644
index 0000000..b0e5837
--- /dev/null
+++ b/tests/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java
@@ -0,0 +1,103 @@
+/*
+ * 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.test.models;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.iciql.Iciql.ConstraintDeleteType;
+import com.iciql.Iciql.IQColumn;
+import com.iciql.Iciql.IQContraintForeignKey;
+import com.iciql.Iciql.IQIndex;
+import com.iciql.Iciql.IQIndexes;
+import com.iciql.Iciql.IQTable;
+import com.iciql.Iciql.IndexType;
+
+/**
+ * A table containing product data.
+ */
+
+@IQTable(name = "AnnotatedProduct", primaryKey = "id")
+@IQIndexes({ @IQIndex({ "name", "cat" }), @IQIndex(name = "nameidx", type = IndexType.HASH, value = "name") })
+@IQContraintForeignKey(
+ foreignColumns= { "cat" },
+ referenceName = "AnnotatedCatagory",
+ referenceColumns = { "categ" },
+ deleteType = ConstraintDeleteType.CASCADE
+)
+public class ProductAnnotationOnlyWithForeignKey {
+
+ public String unmappedField;
+
+ @IQColumn(name = "id", autoIncrement = true)
+ public Long productId;
+
+ @IQColumn(name = "cat", length = 15, trim = true)
+ public String category;
+
+ @IQColumn(name = "name", length = 50)
+ public String productName;
+
+ @SuppressWarnings("unused")
+ @IQColumn
+ private Double unitPrice;
+
+ @IQColumn
+ private Integer unitsInStock;
+
+ public ProductAnnotationOnlyWithForeignKey() {
+ // public constructor
+ }
+
+ private ProductAnnotationOnlyWithForeignKey(long productId, String productName, String category, double unitPrice,
+ int unitsInStock, String unmappedField) {
+ this.productId = productId;
+ this.productName = productName;
+ this.category = category;
+ this.unitPrice = unitPrice;
+ this.unitsInStock = unitsInStock;
+ this.unmappedField = unmappedField;
+ }
+
+ private static ProductAnnotationOnlyWithForeignKey create(int productId, String productName, String category,
+ double unitPrice, int unitsInStock, String unmappedField) {
+ return new ProductAnnotationOnlyWithForeignKey(productId, productName, category, unitPrice, unitsInStock,
+ unmappedField);
+ }
+
+ public static List<ProductAnnotationOnlyWithForeignKey> getList() {
+ String unmappedField = "unmapped";
+ ProductAnnotationOnlyWithForeignKey[] list = { create(1, "Chai", "Beverages", 18, 39, unmappedField),
+ create(2, "Chang", "Beverages", 19.0, 17, unmappedField),
+ create(3, "Aniseed Syrup", "Condiments", 10.0, 13, unmappedField),
+ create(4, "Chef Anton's Cajun Seasoning", "Condiments", 22.0, 53, unmappedField),
+ create(5, "Chef Anton's Gumbo Mix", "Condiments", 21.3500, 0, unmappedField),
+ create(6, "Grandma's Boysenberry Spread", "Condiments", 25.0, 120, unmappedField),
+ create(7, "Uncle Bob's Organic Dried Pears", "Produce", 30.0, 15, unmappedField),
+ create(8, "Northwoods Cranberry Sauce", "Condiments", 40.0, 6, unmappedField),
+ create(9, "Mishi Kobe Niku", "Meat/Poultry", 97.0, 29, unmappedField),
+ create(10, "Ikura", "Seafood", 31.0, 31, unmappedField), };
+ return Arrays.asList(list);
+ }
+
+ public String toString() {
+ return productName + ": " + unitsInStock;
+ }
+
+}