From 95d8f4bd13579a6a6b7e59e7b7031291b2309d8a Mon Sep 17 00:00:00 2001 From: bartolomiew Date: Fri, 12 Oct 2012 17:25:40 +0200 Subject: [PATCH] add foreign key constraint annotation add unique constraint annotation fix small bug in build.xml for java source destination --- build.xml | 2 +- src/com/iciql/Define.java | 16 ++ src/com/iciql/Iciql.java | 155 +++++++++++ src/com/iciql/SQLDialect.java | 49 +++- src/com/iciql/SQLDialectDefault.java | 105 ++++++++ src/com/iciql/TableDefinition.java | 241 ++++++++++++++++++ src/com/iciql/TableInspector.java | 7 + tests/com/iciql/test/ForeignKeyTest.java | 64 +++++ tests/com/iciql/test/IciqlSuite.java | 3 +- .../test/models/CategoryAnnotationOnly.java | 69 +++++ .../ProductAnnotationOnlyWithForeignKey.java | 103 ++++++++ 11 files changed, 809 insertions(+), 5 deletions(-) create mode 100644 tests/com/iciql/test/ForeignKeyTest.java create mode 100644 tests/com/iciql/test/models/CategoryAnnotationOnly.java create mode 100644 tests/com/iciql/test/models/ProductAnnotationOnlyWithForeignKey.java diff --git a/build.xml b/build.xml index bc68087..351c43b 100644 --- a/build.xml +++ b/build.xml @@ -78,7 +78,7 @@ - + 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. @@ -293,6 +294,160 @@ public interface Iciql { String[] value() default {}; } + /** + * 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. + *

+ *

    + *
  • @IQContraintForeignKey( + * foreignColumns = { "idaccount"}, + * referenceName = "account", + * referenceColumns = { "id" }, + * deleteType = ConstrainDeleteType.CASCADE, + * updateType = ConstraintUpdateType.NO_ACTION ) + *
+ * 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. + *
    + *
  • com.iciql.iciql.ConstrainDeleteType.CASCADE + *
  • com.iciql.iciql.ConstrainDeleteType.RESTRICT + *
  • com.iciql.iciql.ConstrainDeleteType.SET_NULL + *
  • com.iciql.iciql.ConstrainDeleteType.NO_ACTION + *
  • com.iciql.iciql.ConstrainDeleteType.SET_DEFAULT + *
+ */ + ConstraintDeleteType deleteType() default ConstraintDeleteType.UNSET; + + /** + * Type of the action on update, default to unspecified. + *
    + *
  • com.iciql.iciql.ConstrainUpdateType.CASCADE + *
  • com.iciql.iciql.ConstrainUpdateType.RESTRICT + *
  • com.iciql.iciql.ConstrainUpdateType.SET_NULL + *
  • com.iciql.iciql.ConstrainUpdateType.NO_ACTION + *
  • com.iciql.iciql.ConstrainUpdateType.SET_DEFAULT + *
+ */ + ConstraintUpdateType updateType() default ConstraintUpdateType.UNSET; + + /** + * Type of the deferrability mode, default to unspecified + *
    + *
  • com.iciql.iciql.ConstrainUpdateType.CASCADE + *
  • ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_DEFERRED + *
  • ConstraintDeferrabilityType.DEFERRABLE_INITIALLY_IMMEDIATE + *
  • ConstraintDeferrabilityType.NOT_DEFERRABLE + *
+ */ + ConstraintDeferrabilityType deferrabilityType() default ConstraintDeferrabilityType.UNSET; + + /** + * The source table for the columns defined as foreign. + */ + String tableName() default ""; + + /** + * Columns defined as 'foreign'. + *
    + *
  • single column : foreignColumns = "id" + *
  • multiple column : foreignColumns = { "id", "name", "date" } + *
+ */ + String[] foreignColumns() default {}; + + /** + * The reference table for the columns defined as references. + */ + String referenceName() default ""; + + /** + * Columns defined as 'references'. + *
    + *
  • single column : referenceColumns = "id" + *
  • multiple column : referenceColumns = { "id", "name", "date" } + *
+ */ + 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. + *

+ *

    + *
  • @IQContraintUnique(uniqueColumns = { "street", "city" }) + *
  • @IQContraintUnique(name="streetconstraint", uniqueColumns = { "street", "city" }) + *
+ */ + @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'. + *
    + *
  • single column : uniqueColumns = "id" + *
  • multiple column : uniqueColumns = { "id", "name", "date" } + *
+ */ + 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. */ 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 */ void prepareCreateView(SQLStatement stat, TableDefinition 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 */ void prepareCreateView(SQLStatement stat, TableDefinition 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 */ void prepareDropView(SQLStatement stat, TableDefinition 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 */ void prepareMerge(SQLStatement stat, String schemaName, String tableName, TableDefinition 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; @@ -68,6 +76,32 @@ public class TableDefinition { public List columnNames; } + /** + * The meta data of a constraint on foreign key. + */ + + public static class ConstraintForeignKeyDefinition { + + public String constraintName; + public List foreignColumns; + public String referenceTable; + public List 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 uniqueColumns; + } + + /** * The meta data of a field. */ @@ -155,6 +189,8 @@ public class TableDefinition { private Class clazz; private IdentityHashMap fieldMap = Utils.newIdentityHashMap(); private ArrayList indexes = Utils.newArrayList(); + private ArrayList constraintsForeignKey = Utils.newArrayList(); + private ArrayList constraintsUnique = Utils.newArrayList(); TableDefinition(Class clazz) { this.clazz = clazz; @@ -267,6 +303,77 @@ public class TableDefinition { 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 columnNames = mapColumnNames(modelFields); + addConstraintUnique(name, columnNames); + } + + /** + * Defines an unique constraint. + * + * @param name + * @param columnNames + */ + private void addConstraintUnique(String name, List 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 columnNames = mapColumnNames(modelFields); + List 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 columnNames, String referenceTableName, + List 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 { } } + // 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 { 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 foreignColumns = Arrays.asList(constraint.foreignColumns()); + List referenceColumns = Arrays.asList(constraint.referenceColumns()); + addContraintForeignKey(constraint.name(), foreignColumns, constraint.referenceName(), referenceColumns, constraint.deleteType(), constraint.updateType(), constraint.deferrabilityType()); + } + + private void addConstraintUnique(IQContraintUnique constraint) { + List 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 foreignColumns, String referenceName, + List 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 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 { return indexes; } + List getContraintsUnique() { + return constraintsUnique; + } + + List getContraintsForeignKey() { + return constraintsForeignKey; + } + private void initObject(Object obj, Map 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 defContraintsU = def.getContraintsUnique(); + List 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 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 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; + } + +} -- 2.39.5