add unique constraint annotation fix small bug in build.xml for java source destinationtags/v1.2.0
@@ -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> | |||
@@ -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); |
@@ -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. | |||
* <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. | |||
*/ |
@@ -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); |
@@ -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()); | |||
} | |||
} |
@@ -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<T> { | |||
public List<String> columnNames; | |||
} | |||
/** | |||
* The meta data of a constraint on foreign key. | |||
*/ | |||
public static class ConstraintForeignKeyDefinition { | |||
public String constraintName; | |||
public List<String> foreignColumns; | |||
public String referenceTable; | |||
public List<String> referenceColumns; | |||
public ConstraintDeleteType deleteType = ConstraintDeleteType.UNSET; | |||
public ConstraintUpdateType updateType = ConstraintUpdateType.UNSET; | |||
public ConstraintDeferrabilityType deferrabilityType = ConstraintDeferrabilityType.UNSET; | |||
} | |||
/** | |||
* The meta data of a unique constraint. | |||
*/ | |||
public static class ConstraintUniqueDefinition { | |||
public String constraintName; | |||
public List<String> uniqueColumns; | |||
} | |||
/** | |||
* The meta data of a field. | |||
*/ | |||
@@ -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); |
@@ -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(); | |||
} | |||
/** |
@@ -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); | |||
} | |||
} |
@@ -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 = { |
@@ -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; | |||
} | |||
} |
@@ -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; | |||
} | |||
} |