]> source.dussan.org Git - iciql.git/commitdiff
add foreign key constraint annotation 3/head
authorbartolomiew <frederic.gaillard.home@gmail.com>
Fri, 12 Oct 2012 15:25:40 +0000 (17:25 +0200)
committerbartolomiew <frederic.gaillard.home@gmail.com>
Fri, 12 Oct 2012 15:25:40 +0000 (17:25 +0200)
add unique constraint annotation
fix small bug in build.xml for java source destination

build.xml
src/com/iciql/Define.java
src/com/iciql/Iciql.java
src/com/iciql/SQLDialect.java
src/com/iciql/SQLDialectDefault.java
src/com/iciql/TableDefinition.java
src/com/iciql/TableInspector.java
tests/com/iciql/test/ForeignKeyTest.java [new file with mode: 0644]
tests/com/iciql/test/IciqlSuite.java
tests/com/iciql/test/models/CategoryAnnotationOnly.java [new file with mode: 0644]
tests/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java [new file with mode: 0644]

index bc6808743f4884f1386f6551d758d0b99c83e324..351c43b48bf9e4e77aad06318b53c121929c30c1 100644 (file)
--- a/build.xml
+++ b/build.xml
@@ -78,7 +78,7 @@
 \r
                <property name="library.jar" value="iciql-${iq.version}.jar" />\r
                <property name="javadoc.jar" value="iciql-${iq.version}-javadoc.jar" />\r
-               <property name="sources.jar" value="/iciql-${iq.version}-sources.jar" />\r
+               <property name="sources.jar" value="iciql-${iq.version}-sources.jar" />\r
                <property name="distribution.zip" value="iciql-${iq.version}.zip" />\r
        </target>\r
 \r
index 53f9862be4aabe8a00c313a2fe4189abd1357e91..5d7000efe19e600cd0e7e1326caea4ea3e5e9757 100644 (file)
@@ -1,6 +1,7 @@
 /*\r
  * Copyright 2004-2011 H2 Group.\r
  * Copyright 2011 James Moger.\r
+ * Copyright 2012 Frédéric Gaillard.\r
  *\r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -44,6 +45,21 @@ public class Define {
                currentTableDefinition.defineIndex(name, type, columns);\r
        }\r
 \r
+       public static void constraintUnique(String name, Object... columns) {\r
+               checkInDefine();\r
+               currentTableDefinition.defineConstraintUnique(name, columns);\r
+       }\r
+       \r
+       /*\r
+        * The variable argument type Object can't be used twice :-)\r
+        */\r
+//     public static void constraintForeignKey(String name, String refTableName,\r
+//                     ConstraintDeleteType deleteType, ConstraintUpdateType updateType,\r
+//                     ConstraintDeferrabilityType deferrabilityType, Object... columns, Object... refColumns) {\r
+//             checkInDefine();\r
+//             currentTableDefinition.defineForeignKey(name, columns, refTableName, Columns, deleteType, updateType, deferrabilityType);\r
+//     }\r
+       \r
        public static void primaryKey(Object... columns) {\r
                checkInDefine();\r
                currentTableDefinition.definePrimaryKey(columns);\r
index 7b3a7c13b9e26f3f88a275b8abcddf25d7e35df2..9956ac48e7bd44b9ee9342db9e3702994a8c2142 100644 (file)
@@ -1,6 +1,7 @@
 /*\r
  * Copyright 2004-2011 H2 Group.\r
  * Copyright 2011 James Moger.\r
+ * Copyright 2012 Frédéric Gaillard.\r
  *\r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -293,6 +294,160 @@ public interface Iciql {
                String[] value() default {};\r
        }\r
 \r
+       /**\r
+        * Enumeration defining the ON DELETE actions.\r
+        */\r
+       public static enum ConstraintDeleteType {\r
+               UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT;\r
+       }\r
+\r
+       /**\r
+        * Enumeration defining the ON UPDATE actions.\r
+        */\r
+       public static enum ConstraintUpdateType {\r
+               UNSET, CASCADE, RESTRICT, SET_NULL, NO_ACTION, SET_DEFAULT;\r
+       }\r
+       \r
+       /**\r
+        * Enumeration defining the deferrability.\r
+        */\r
+       public static enum ConstraintDeferrabilityType {\r
+               UNSET, DEFERRABLE_INITIALLY_DEFERRED, DEFERRABLE_INITIALLY_IMMEDIATE, NOT_DEFERRABLE;\r
+       }\r
+       \r
+       /**\r
+        * A foreign key constraint annotation.\r
+        * <p>\r
+        * <ul>\r
+        * <li>@IQContraintForeignKey(\r
+        *    foreignColumns = { "idaccount"}, \r
+        *    referenceName = "account", \r
+        *    referenceColumns = { "id" },\r
+        *    deleteType = ConstrainDeleteType.CASCADE,\r
+        *    updateType = ConstraintUpdateType.NO_ACTION )\r
+        * </ul>\r
+        * Note : reference columns should have a unique constraint defined in referenceName table,\r
+        * some database used to define a unique index instead of a unique constraint\r
+        */\r
+       @Retention(RetentionPolicy.RUNTIME)\r
+       @Target(ElementType.TYPE)\r
+       public @interface IQContraintForeignKey {\r
+\r
+               /**\r
+                * Constraint name. If null or empty, iciql will generate one.\r
+                */\r
+               String name() default "";\r
+\r
+               /**\r
+                * Type of the action on delete, default to unspecified.\r
+                * <ul>\r
+                * <li>com.iciql.iciql.ConstrainDeleteType.CASCADE\r
+                * <li>com.iciql.iciql.ConstrainDeleteType.RESTRICT\r
+                * <li>com.iciql.iciql.ConstrainDeleteType.SET_NULL\r
+                * <li>com.iciql.iciql.ConstrainDeleteType.NO_ACTION\r
+                * <li>com.iciql.iciql.ConstrainDeleteType.SET_DEFAULT\r
+                * </ul>\r
+                */\r
+               ConstraintDeleteType deleteType() default ConstraintDeleteType.UNSET;\r
+\r
+               /**\r
+                * Type of the action on update, default to unspecified.\r
+                * <ul>\r
+                * <li>com.iciql.iciql.ConstrainUpdateType.CASCADE\r
+                * <li>com.iciql.iciql.ConstrainUpdateType.RESTRICT\r
+                * <li>com.iciql.iciql.ConstrainUpdateType.SET_NULL\r
+                * <li>com.iciql.iciql.ConstrainUpdateType.NO_ACTION\r
+                * <li>com.iciql.iciql.ConstrainUpdateType.SET_DEFAULT\r
+                * </ul>\r
+                */\r
+               ConstraintUpdateType updateType() default ConstraintUpdateType.UNSET;\r
+\r
+               /**\r
+                * Type of the deferrability mode, default to unspecified\r
+                * <ul>\r
+                * <li>com.iciql.iciql.ConstrainUpdateType.CASCADE\r
+                * <li>ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_DEFERRED\r
+                * <li>ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_IMMEDIATE\r
+                * <li>ConstraintDeferrabilityType.NOT_DEFERRABLE\r
+                * </ul>\r
+                */\r
+               ConstraintDeferrabilityType deferrabilityType() default ConstraintDeferrabilityType.UNSET;\r
+               \r
+               /**\r
+                * The source table for the columns defined as foreign.\r
+                */\r
+               String tableName() default "";\r
+\r
+               /**\r
+                * Columns defined as 'foreign'.\r
+                * <ul>\r
+                * <li>single column : foreignColumns = "id"\r
+                * <li>multiple column : foreignColumns = { "id", "name", "date" }\r
+                * </ul>\r
+                */\r
+               String[] foreignColumns() default {};\r
+\r
+               /**\r
+                * The reference table for the columns defined as references.\r
+                */\r
+               String referenceName() default "";\r
+               \r
+               /**\r
+                * Columns defined as 'references'.\r
+                * <ul>\r
+                * <li>single column : referenceColumns = "id"\r
+                * <li>multiple column : referenceColumns = { "id", "name", "date" }\r
+                * </ul>\r
+                */\r
+               String[] referenceColumns() default {};\r
+       }\r
+\r
+       /**\r
+        * Annotation to specify multiple foreign keys constraints.\r
+        */\r
+       @Retention(RetentionPolicy.RUNTIME)\r
+       @Target(ElementType.TYPE)\r
+       public @interface IQContraintsForeignKey {\r
+               IQContraintForeignKey[] value() default {};\r
+       }\r
+       \r
+       /**\r
+        * A unique constraint annotation.\r
+        * <p>\r
+        * <ul>\r
+        * <li>@IQContraintUnique(uniqueColumns = { "street", "city" })\r
+        * <li>@IQContraintUnique(name="streetconstraint", uniqueColumns = { "street", "city" })\r
+        * </ul>\r
+        */\r
+       @Retention(RetentionPolicy.RUNTIME)\r
+       @Target(ElementType.TYPE)\r
+       public @interface IQContraintUnique {\r
+\r
+               /**\r
+                * Constraint name. If null or empty, iciql will generate one.\r
+                */\r
+               String name() default "";\r
+\r
+               /**\r
+                * Columns defined as 'unique'.\r
+                * <ul>\r
+                * <li>single column : uniqueColumns = "id"\r
+                * <li>multiple column : uniqueColumns = { "id", "name", "date" }\r
+                * </ul>\r
+                */\r
+               String[] uniqueColumns() default {};\r
+\r
+       }\r
+\r
+       /**\r
+        * Annotation to specify multiple unique constraints.\r
+        */\r
+       @Retention(RetentionPolicy.RUNTIME)\r
+       @Target(ElementType.TYPE)\r
+       public @interface IQContraintsUnique {\r
+               IQContraintUnique[] value() default {};\r
+       }\r
+       \r
        /**\r
         * Annotation to define a view.\r
         */\r
index 8e3e3d2082c82873989f3c68a27c42f345077579..5e9625f225b8d6720d7d5f58d15251592a05d6d5 100644 (file)
@@ -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);
index 0cd0448810c75194b40c5c8f998aa27adca8f194..afdc36d2d57aa7c41213e8e6fe0162f8e1a5781e 100644 (file)
@@ -1,6 +1,7 @@
 /*\r
  * Copyright 2004-2011 H2 Group.\r
  * Copyright 2011 James Moger.\r
+ * Copyright 2012 Frédéric Gaillard.\r
  *\r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -22,6 +23,10 @@ import java.sql.SQLException;
 import java.text.MessageFormat;\r
 import java.text.SimpleDateFormat;\r
 \r
+import com.iciql.Iciql.ConstraintDeleteType;\r
+import com.iciql.Iciql.ConstraintUpdateType;\r
+import com.iciql.TableDefinition.ConstraintForeignKeyDefinition;\r
+import com.iciql.TableDefinition.ConstraintUniqueDefinition;\r
 import com.iciql.TableDefinition.FieldDefinition;\r
 import com.iciql.TableDefinition.IndexDefinition;\r
 import com.iciql.util.IciqlLogger;\r
@@ -257,6 +262,8 @@ public class SQLDialectDefault implements SQLDialect {
                buff.append("INDEX ");\r
                buff.append(index.indexName);\r
                buff.append(" ON ");\r
+               // FIXME maybe we can use schemaName ?\r
+               // buff.append(prepareTableName(schemaName, tableName));\r
                buff.append(tableName);\r
                buff.append("(");\r
                for (String col : index.columnNames) {\r
@@ -337,4 +344,102 @@ public class SQLDialectDefault implements SQLDialect {
                }\r
                return o.toString();\r
        }\r
+\r
+       @SuppressWarnings("incomplete-switch")\r
+       @Override\r
+       public void prepareCreateConstraintForeignKey(SQLStatement stat, String schemaName, String tableName, ConstraintForeignKeyDefinition constraint) {\r
+               StatementBuilder buff = new StatementBuilder();\r
+               buff.append("ALTER TABLE ");\r
+               buff.append(prepareTableName(schemaName, tableName));\r
+               buff.append(" ADD CONSTRAINT ");\r
+               buff.append(constraint.constraintName);\r
+               buff.append(" FOREIGN KEY ");\r
+               buff.append(" (");\r
+               for (String col : constraint.foreignColumns) {\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(prepareColumnName(col));\r
+               }\r
+               buff.append(") ");\r
+               buff.append(" REFERENCES ");\r
+               buff.append(constraint.referenceTable);\r
+               buff.append(" (");\r
+               buff.resetCount();\r
+               for (String col : constraint.referenceColumns) {\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(prepareColumnName(col));\r
+               }\r
+               buff.append(") ");\r
+               if (constraint.deleteType != ConstraintDeleteType.UNSET) {\r
+                       buff.append(" ON DELETE ");\r
+                       switch (constraint.deleteType) {\r
+                       case CASCADE:\r
+                               buff.append("CASCADE ");\r
+                               break;\r
+                       case RESTRICT:\r
+                               buff.append("RESTRICT ");\r
+                               break;\r
+                       case SET_NULL:\r
+                               buff.append("SET NULL ");\r
+                               break;\r
+                       case NO_ACTION:\r
+                               buff.append("NO ACTION ");\r
+                               break;\r
+                       case SET_DEFAULT:\r
+                               buff.append("SET DEFAULT ");\r
+                               break;\r
+                       }\r
+               }\r
+               if (constraint.updateType != ConstraintUpdateType.UNSET) {\r
+                       buff.append(" ON UPDATE ");\r
+                       switch (constraint.updateType) {\r
+                       case CASCADE:\r
+                               buff.append("CASCADE ");\r
+                               break;\r
+                       case RESTRICT:\r
+                               buff.append("RESTRICT ");\r
+                               break;\r
+                       case SET_NULL:\r
+                               buff.append("SET NULL ");\r
+                               break;\r
+                       case NO_ACTION:\r
+                               buff.append("NO ACTION ");\r
+                               break;\r
+                       case SET_DEFAULT:\r
+                               buff.append("SET DEFAULT ");\r
+                               break;\r
+                       }\r
+               }\r
+               switch (constraint.deferrabilityType) {\r
+               case DEFERRABLE_INITIALLY_DEFERRED:\r
+                       buff.append("DEFERRABLE INITIALLY DEFERRED ");\r
+                       break;\r
+               case DEFERRABLE_INITIALLY_IMMEDIATE:\r
+                       buff.append("DEFERRABLE INITIALLY IMMEDIATE ");\r
+                       break;\r
+               case NOT_DEFERRABLE:\r
+                       buff.append("NOT DEFERRABLE ");\r
+                       break;\r
+               case UNSET:\r
+                       break;\r
+               }\r
+               stat.setSQL(buff.toString().trim());\r
+       }\r
+\r
+       @Override\r
+       public void prepareCreateConstraintUnique(SQLStatement stat, String schemaName, String tableName, ConstraintUniqueDefinition constraint) {\r
+               StatementBuilder buff = new StatementBuilder();\r
+               buff.append("ALTER TABLE ");\r
+               buff.append(prepareTableName(schemaName, tableName));\r
+               buff.append(" ADD CONSTRAINT ");\r
+               buff.append(constraint.constraintName);\r
+               buff.append(" UNIQUE ");\r
+               buff.append(" (");\r
+               for (String col : constraint.uniqueColumns) {\r
+                       buff.appendExceptFirst(", ");\r
+                       buff.append(prepareColumnName(col));\r
+               }\r
+               buff.append(") ");\r
+               stat.setSQL(buff.toString().trim());\r
+       }\r
+\r
 }
\ No newline at end of file
index de2b9ed4d79e630d6ff2a74e4b2a2527daf87980..c348295bcabd79726555210923a93a6e861ca1c7 100644 (file)
@@ -1,6 +1,7 @@
 /*\r
  * Copyright 2004-2011 H2 Group.\r
  * Copyright 2011 James Moger.\r
+ * Copyright 2012 Frédéric Gaillard.\r
  *\r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -29,11 +30,18 @@ import java.util.List;
 import java.util.Map;\r
 import java.util.Set;\r
 \r
+import com.iciql.Iciql.ConstraintDeferrabilityType;\r
+import com.iciql.Iciql.ConstraintDeleteType;\r
+import com.iciql.Iciql.ConstraintUpdateType;\r
 import com.iciql.Iciql.EnumId;\r
 import com.iciql.Iciql.EnumType;\r
 import com.iciql.Iciql.IQColumn;\r
 import com.iciql.Iciql.IQConstraint;\r
+import com.iciql.Iciql.IQContraintUnique;\r
+import com.iciql.Iciql.IQContraintsUnique;\r
 import com.iciql.Iciql.IQEnum;\r
+import com.iciql.Iciql.IQContraintForeignKey;\r
+import com.iciql.Iciql.IQContraintsForeignKey;\r
 import com.iciql.Iciql.IQIgnore;\r
 import com.iciql.Iciql.IQIndex;\r
 import com.iciql.Iciql.IQIndexes;\r
@@ -68,6 +76,32 @@ public class TableDefinition<T> {
                public List<String> columnNames;\r
        }\r
 \r
+       /**\r
+        * The meta data of a constraint on foreign key.\r
+        */\r
+       \r
+       public static class ConstraintForeignKeyDefinition {\r
+\r
+               public String constraintName;\r
+               public List<String> foreignColumns;\r
+               public String referenceTable;\r
+               public List<String> referenceColumns;\r
+               public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET;\r
+               public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET;\r
+               public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET;\r
+       }\r
+       \r
+       /**\r
+        * The meta data of a unique constraint.\r
+        */\r
+       \r
+       public static class ConstraintUniqueDefinition {\r
+\r
+               public String constraintName;\r
+               public List<String> uniqueColumns;\r
+       }\r
+       \r
+       \r
        /**\r
         * The meta data of a field.\r
         */\r
@@ -155,6 +189,8 @@ public class TableDefinition<T> {
        private Class<T> clazz;\r
        private IdentityHashMap<Object, FieldDefinition> fieldMap = Utils.newIdentityHashMap();\r
        private ArrayList<IndexDefinition> indexes = Utils.newArrayList();\r
+       private ArrayList<ConstraintForeignKeyDefinition> constraintsForeignKey = Utils.newArrayList();\r
+       private ArrayList<ConstraintUniqueDefinition> constraintsUnique = Utils.newArrayList();\r
 \r
        TableDefinition(Class<T> clazz) {\r
                this.clazz = clazz;\r
@@ -267,6 +303,77 @@ public class TableDefinition<T> {
                indexes.add(index);\r
        }\r
 \r
+       /**\r
+        * Defines an unique constraint with the specified model fields.\r
+        * \r
+        * @param name\r
+        *            the constraint name (optional)\r
+        * @param modelFields\r
+        *            the ordered list of model fields\r
+        */\r
+       void defineConstraintUnique(String name, Object[] modelFields) {\r
+               List<String> columnNames = mapColumnNames(modelFields);\r
+               addConstraintUnique(name, columnNames);\r
+       }\r
+\r
+       /**\r
+        * Defines an unique constraint.\r
+        * \r
+        * @param name\r
+        * @param columnNames\r
+        */\r
+       private void addConstraintUnique(String name, List<String> columnNames) {\r
+               ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();\r
+               if (StringUtils.isNullOrEmpty(name)) {\r
+                       constraint.constraintName = tableName + "_" + constraintsUnique.size();\r
+               } else {\r
+                       constraint.constraintName = name;\r
+               }\r
+               constraint.uniqueColumns = Utils.newArrayList(columnNames);\r
+               constraintsUnique.add(constraint);\r
+       }\r
+\r
+       /**\r
+        * Defines a foreign key constraint with the specified model fields.\r
+        * \r
+        * @param name\r
+        *            the constraint name (optional)\r
+        * @param modelFields\r
+        *            the ordered list of model fields\r
+        */\r
+       void defineForeignKey(String name, Object[] modelFields, String refTableName, Object[] refModelFields,\r
+                       ConstraintDeleteType deleteType, ConstraintUpdateType updateType,\r
+                       ConstraintDeferrabilityType deferrabilityType) {\r
+               List<String> columnNames = mapColumnNames(modelFields);\r
+               List<String> referenceColumnNames = mapColumnNames(refModelFields);\r
+               addForeignKey(name, columnNames, refTableName, referenceColumnNames,\r
+                               deleteType, updateType, deferrabilityType);\r
+       }\r
+\r
+       /**\r
+        * Defines a foreign key constraint.\r
+        * \r
+        * @param name\r
+        * @param columnNames\r
+        */\r
+       private void addForeignKey(String name, List<String> columnNames, String referenceTableName, \r
+                       List<String> referenceColumnNames, ConstraintDeleteType deleteType, \r
+                       ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {\r
+               ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();\r
+               if (StringUtils.isNullOrEmpty(name)) {\r
+                       constraint.constraintName = tableName + "_" + constraintsUnique.size();\r
+               } else {\r
+                       constraint.constraintName = name;\r
+               }\r
+               constraint.foreignColumns = Utils.newArrayList(columnNames);\r
+               constraint.referenceTable = referenceTableName;\r
+               constraint.referenceColumns = Utils.newArrayList(referenceColumnNames);\r
+               constraint.deleteType = deleteType;\r
+               constraint.updateType = updateType;\r
+               constraint.deferrabilityType = deferrabilityType;\r
+               constraintsForeignKey.add(constraint);\r
+       }\r
+       \r
        void defineColumnName(Object column, String columnName) {\r
                FieldDefinition def = fieldMap.get(column);\r
                if (def != null) {\r
@@ -795,6 +902,36 @@ public class TableDefinition<T> {
                        }\r
                }\r
 \r
+               // create unique constraints\r
+               for (ConstraintUniqueDefinition constraint : constraintsUnique) {\r
+                       stat = new SQLStatement(db);\r
+                       db.getDialect().prepareCreateConstraintUnique(stat, schemaName, tableName, constraint);\r
+                       IciqlLogger.create(stat.getSQL());\r
+                       try {\r
+                               stat.executeUpdate();\r
+                       } catch (IciqlException e) {\r
+                               // maybe we should check more error codes\r
+                               if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {\r
+                                       throw e;\r
+                               }\r
+                       }\r
+               }\r
+               \r
+               // create foreign keys constraints\r
+               for (ConstraintForeignKeyDefinition constraint : constraintsForeignKey) {\r
+                       stat = new SQLStatement(db);\r
+                       db.getDialect().prepareCreateConstraintForeignKey(stat, schemaName, tableName, constraint);\r
+                       IciqlLogger.create(stat.getSQL());\r
+                       try {\r
+                               stat.executeUpdate();\r
+                       } catch (IciqlException e) {\r
+                               // maybe we should check more error codes\r
+                               if (e.getIciqlCode() != IciqlException.CODE_OBJECT_ALREADY_EXISTS) {\r
+                                       throw e;\r
+                               }\r
+                       }\r
+               }\r
+\r
                // tables are created using IF NOT EXISTS\r
                // but we may still need to upgrade\r
                db.upgradeTable(this);\r
@@ -911,6 +1048,102 @@ public class TableDefinition<T> {
                                addIndex(index);\r
                        }\r
                }\r
+               \r
+               if (clazz.isAnnotationPresent(IQContraintUnique.class)) {\r
+                       // single table unique constraint\r
+                       IQContraintUnique constraint = clazz.getAnnotation(IQContraintUnique.class);\r
+                       addConstraintUnique(constraint);\r
+               }\r
+\r
+               if (clazz.isAnnotationPresent(IQContraintsUnique.class)) {\r
+                       // multiple table unique constraints\r
+                       IQContraintsUnique constraints = clazz.getAnnotation(IQContraintsUnique.class);\r
+                       for (IQContraintUnique constraint : constraints.value()) {\r
+                               addConstraintUnique(constraint);\r
+                       }\r
+               }\r
+               \r
+               if (clazz.isAnnotationPresent(IQContraintForeignKey.class)) {\r
+                       // single table constraint\r
+                       IQContraintForeignKey constraint = clazz.getAnnotation(IQContraintForeignKey.class);\r
+                       addConstraintForeignKey(constraint);\r
+               }\r
+\r
+               if (clazz.isAnnotationPresent(IQContraintsForeignKey.class)) {\r
+                       // multiple table constraints\r
+                       IQContraintsForeignKey constraints = clazz.getAnnotation(IQContraintsForeignKey.class);\r
+                       for (IQContraintForeignKey constraint : constraints.value()) {\r
+                               addConstraintForeignKey(constraint);\r
+                       }\r
+               }\r
+               \r
+       }\r
+\r
+       private void addConstraintForeignKey(IQContraintForeignKey constraint) {\r
+               List<String> foreignColumns = Arrays.asList(constraint.foreignColumns());\r
+               List<String> referenceColumns = Arrays.asList(constraint.referenceColumns());\r
+               addContraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType());\r
+       }\r
+       \r
+       private void addConstraintUnique(IQContraintUnique constraint) {\r
+               List<String> uniqueColumns = Arrays.asList(constraint.uniqueColumns());\r
+               addContraintUnique(constraint.name(), uniqueColumns);\r
+       }\r
+       \r
+       /**\r
+        * Defines a foreign key constraint with the specified parameters.\r
+        * \r
+        * @param name\r
+        *            name of the constraint\r
+        * @param foreignColumns\r
+        *            list of columns declared as foreign\r
+        * @param referenceName\r
+        *            reference table name\r
+        * @param referenceColumns\r
+        *            list of columns used in reference table\r
+        * @param deleteType\r
+        *            action on delete\r
+        * @param updateType\r
+        *            action on update\r
+        * @param deferrabilityType\r
+        *            deferrability mode\r
+        */\r
+       private void addContraintForeignKey(String name,\r
+                       List<String> foreignColumns, String referenceName,\r
+                       List<String> referenceColumns, ConstraintDeleteType deleteType,\r
+                       ConstraintUpdateType updateType, ConstraintDeferrabilityType deferrabilityType) {\r
+               ConstraintForeignKeyDefinition constraint = new ConstraintForeignKeyDefinition();\r
+               if (StringUtils.isNullOrEmpty(name)) {\r
+                       constraint.constraintName = tableName + "_" + constraintsForeignKey.size();\r
+               } else {\r
+                       constraint.constraintName = name;\r
+               }\r
+               constraint.foreignColumns = Utils.newArrayList(foreignColumns);\r
+               constraint.referenceColumns = Utils.newArrayList(referenceColumns);\r
+               constraint.referenceTable = referenceName;\r
+               constraint.deleteType = deleteType;\r
+               constraint.updateType = updateType;\r
+               constraint.deferrabilityType = deferrabilityType;\r
+               constraintsForeignKey.add(constraint);\r
+       }\r
+\r
+       /**\r
+        * Defines a unique constraint with the specified parameters.\r
+        * \r
+        * @param name\r
+        *            name of the constraint\r
+        * @param uniqueColumns\r
+        *            list of columns declared as unique\r
+        */\r
+       private void addContraintUnique(String name, List<String> uniqueColumns) {\r
+               ConstraintUniqueDefinition constraint = new ConstraintUniqueDefinition();\r
+               if (StringUtils.isNullOrEmpty(name)) {\r
+                       constraint.constraintName = tableName + "_" + constraintsUnique.size();\r
+               } else {\r
+                       constraint.constraintName = name;\r
+               }\r
+               constraint.uniqueColumns = Utils.newArrayList(uniqueColumns);\r
+               constraintsUnique.add(constraint);\r
        }\r
 \r
        private void addIndex(IQIndex index) {\r
@@ -922,6 +1155,14 @@ public class TableDefinition<T> {
                return indexes;\r
        }\r
 \r
+       List<ConstraintUniqueDefinition> getContraintsUnique() {\r
+               return constraintsUnique;\r
+       }\r
+       \r
+       List<ConstraintForeignKeyDefinition> getContraintsForeignKey() {\r
+               return constraintsForeignKey;\r
+       }\r
+       \r
        private void initObject(Object obj, Map<Object, FieldDefinition> map) {\r
                for (FieldDefinition def : fields) {\r
                        Object newValue = def.initWithNewObject(obj);\r
index 71eb16d77fc424612276c4c6bc29c1fdd132c2ca..fa6722d66ce218fbc1f5a97c4b748ad8b6e57a0c 100644 (file)
@@ -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 (file)
index 0000000..0941516
--- /dev/null
@@ -0,0 +1,64 @@
+/*\r
+ * Copyright 2012 Frédéric Gaillard.\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ *     http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+package com.iciql.test;\r
+\r
+import static org.junit.Assert.assertEquals;\r
+\r
+import org.junit.After;\r
+import org.junit.Before;\r
+import org.junit.Test;\r
+\r
+import com.iciql.Db;\r
+import com.iciql.test.models.CategoryAnnotationOnly;\r
+import com.iciql.test.models.ProductAnnotationOnlyWithForeignKey;\r
+\r
+public class ForeignKeyTest {\r
+\r
+       /**\r
+        * This object represents a database (actually a connection to the\r
+        * database).\r
+        */\r
+\r
+       private Db db;\r
+\r
+       @Before\r
+       public void setUp() {\r
+               db = IciqlSuite.openNewDb();\r
+               db.insertAll(CategoryAnnotationOnly.getList());\r
+               db.insertAll(ProductAnnotationOnlyWithForeignKey.getList());\r
+       }\r
+\r
+       @After\r
+       public void tearDown() {\r
+               db.close();\r
+       }\r
+\r
+       @Test\r
+       public void testForeignKeyWithOnDeleteCascade() {\r
+               ProductAnnotationOnlyWithForeignKey p = new ProductAnnotationOnlyWithForeignKey();\r
+               long count1 = db.from(p).selectCount();\r
+               \r
+               // should remove 2 associated products\r
+               CategoryAnnotationOnly c = new CategoryAnnotationOnly();\r
+               db.from(c).where(c.categoryId).is(1L).delete();\r
+               \r
+               long count2 = db.from(p).selectCount();\r
+               \r
+               assertEquals(count1, count2 + 2L);\r
+       }\r
+\r
+\r
+}\r
index 68156f8948f18536c0a44409184ff4d7278f2d76..f888a8cb4531a876781e91f17ef066d3dc1ddc5a 100644 (file)
@@ -1,5 +1,6 @@
 /*\r
  * Copyright 2011 James Moger.\r
+ * Copyright 2012 Frédéric Gaillard.\r
  *\r
  * Licensed under the Apache License, Version 2.0 (the "License");\r
  * you may not use this file except in compliance with the License.\r
@@ -89,7 +90,7 @@ import com.iciql.util.Utils;
 @SuiteClasses({ AliasMapTest.class, AnnotationsTest.class, BooleanModelTest.class, ClobTest.class,\r
                ConcurrencyTest.class, EnumsTest.class, ModelsTest.class, PrimitivesTest.class,\r
                RuntimeQueryTest.class, SamplesTest.class, UpdateTest.class, UpgradesTest.class, JoinTest.class,\r
-               UUIDTest.class, ViewsTest.class })\r
+               UUIDTest.class, ViewsTest.class, ForeignKeyTest.class })\r
 public class IciqlSuite {\r
 \r
        private static final TestDb[] TEST_DBS = {\r
diff --git a/tests/com/iciql/test/models/CategoryAnnotationOnly.java b/tests/com/iciql/test/models/CategoryAnnotationOnly.java
new file mode 100644 (file)
index 0000000..7bccfe7
--- /dev/null
@@ -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 (file)
index 0000000..b0e5837
--- /dev/null
@@ -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;
+       }
+
+}