diff --git a/README.md b/README.md index 94de580..efbd67f 100644 --- a/README.md +++ b/README.md @@ -51,22 +51,36 @@ If you just only add the dialect, hibernate will create a new sequence for every ### Arrays -The plugin supports the definition of `Integer`, `Long` and `String` arrays in your domain classes. +The plugin supports the definition of `Integer`, `Long`, `String`, and `Enum` arrays in your domain classes. + +The EnumArrayType behaves almost identical to IntegerArrayType in that it stores and retrieves an array of ints. The difference, however, is that this is used with an Array of Enums, rather than Ints. The Enums are serialized to their ordinal value before persisted to the database. On retrieval, they are then converted back into their original Enum type. ```groovy import net.kaleidos.hibernate.usertype.IntegerArrayType import net.kaleidos.hibernate.usertype.LongArrayType import net.kaleidos.hibernate.usertype.StringArrayType +import net.kaleidos.hibernate.usertype.IdentityEnumArrayType class Like { Integer[] favoriteNumbers = [] Long[] favoriteLongNumbers = [] String[] favoriteMovies = [] + Juice[] favoriteJuices = [] + + static enum Juice { + ORANGE(0), + APPLE(1), + GRAPE(2) + + private final int value + Juice(int value) { this.value = value } + } static mapping = { favoriteNumbers type:IntegerArrayType favoriteLongNumbers type:LongArrayType favoriteMovies type:StringArrayType + favoriteJuices type:IdentityEnumArrayType, params:[enumClass: Juice] } } ``` @@ -76,7 +90,8 @@ Now you can create domain objects using lists of integers, longs and strings and ```groovy def like1 = new Like(favoriteNumbers:[5, 17, 9, 6], favoriteLongNumbers:[123, 239, 3498239, 2344235], - favoriteMovies:["Spiderman", "Blade Runner", "Starwars"]) + favoriteMovies:["Spiderman", "Blade Runner", "Starwars"], + favoriteJuices:[Like.Juice.ORANGE, Like.Juice.GRAPE]) like1.save() ``` @@ -85,9 +100,9 @@ And now, with `psql`: ``` =# select * from like; - id | favorite_long_numbers | favorite_movies | favorite_numbers -----+-------------------------- +----------------------------------------+------------------ - 1 | {123,239,3498239,2344235} | {Spiderman,"Blade Runner",Starwars} | {5,17,9,6} + id | favorite_long_numbers | favorite_movies | favorite_numbers | favorite_juices +----+-------------------------- +----------------------------------------+------------------+---------------- + 1 | {123,239,3498239,2344235} | {Spiderman,"Blade Runner",Starwars} | {5,17,9,6} | {0,2} ``` #### Criterias @@ -111,6 +126,13 @@ def numbers = [5, 17] def result = Like.withCriteria { pgArrayContains 'favoriteNumbers', numbers } + +// If using enums, pass the enum right through +def juices = Like.Juice.ORANGE +def result = Like.withCriteria { + pgArrayContains 'favoriteJuices', juices +} + ``` #### Is contained @@ -172,6 +194,7 @@ You can send any questions to: - Iván López: lopez.ivan@gmail.com - Alonso Torres: alonso.javier.torres@gmail.com +- Matt Feury: mattfeury@gmail.com Collaborations are appreciated :-) diff --git a/grails-app/domain/test/array/TestEnum.groovy b/grails-app/domain/test/array/TestEnum.groovy new file mode 100644 index 0000000..7541c07 --- /dev/null +++ b/grails-app/domain/test/array/TestEnum.groovy @@ -0,0 +1,25 @@ +package test.array + +import net.kaleidos.hibernate.usertype.IdentityEnumArrayType + +class TestEnum { + + static enum Day { + MONDAY(0), + TUESDAY(1), + WEDNESDAY(2), + THURSDAY(3), + FRIDAY(4), + SATURDAY(5), + SUNDAY(6) + + private final int value + Day(int value) { this.value = value } + } + + Day[] days + + static mapping = { + days type: IdentityEnumArrayType, params: [enumClass: Day] + } +} diff --git a/grails-app/domain/test/criteria/array/Like.groovy b/grails-app/domain/test/criteria/array/Like.groovy index 07cfa0c..eba5da5 100644 --- a/grails-app/domain/test/criteria/array/Like.groovy +++ b/grails-app/domain/test/criteria/array/Like.groovy @@ -1,5 +1,6 @@ package test.criteria.array +import net.kaleidos.hibernate.usertype.IdentityEnumArrayType import net.kaleidos.hibernate.usertype.IntegerArrayType import net.kaleidos.hibernate.usertype.LongArrayType import net.kaleidos.hibernate.usertype.StringArrayType @@ -11,6 +12,22 @@ class Like { Integer[] favoriteNumbers = [] Long[] favoriteLongNumbers = [] String[] favoriteMovies = [] + Juice[] favoriteJuices = [] + + static enum Juice { + ORANGE(0), + APPLE(1), + GRAPE(2), + PINEAPPLE(3), + TOMATO(4), + GRAPEFRUIT(5), + CRANBERRY(6), + CARROT(7), + LEMON(8) + + private final int value + Juice(int value) { this.value = value } + } static mapping = { table "pg_extensions_like" @@ -18,5 +35,6 @@ class Like { favoriteNumbers type:IntegerArrayType favoriteMovies type:StringArrayType favoriteLongNumbers type:LongArrayType + favoriteJuices type:IdentityEnumArrayType, params: [enumClass: Juice] } -} \ No newline at end of file +} diff --git a/grails-app/services/test/criteria/array/PgContainsCriteriaTestService.groovy b/grails-app/services/test/criteria/array/PgContainsCriteriaTestService.groovy index 9e9e711..eaa41b7 100644 --- a/grails-app/services/test/criteria/array/PgContainsCriteriaTestService.groovy +++ b/grails-app/services/test/criteria/array/PgContainsCriteriaTestService.groovy @@ -36,6 +36,18 @@ class PgContainsCriteriaTestService { return result } + /** + * Search "likes" with enum in array + */ + public List searchWithCriteriaEnumArray(Like.Juice juice) { + def result = Like.withCriteria { + pgArrayContains 'favoriteJuices', juice + } + + return result + } + + /** * Search "likes" with n integers in array */ @@ -69,6 +81,17 @@ class PgContainsCriteriaTestService { return result } + /** + * Search "likes" with n enums in array + */ + public List searchWithCriteriaEnumArray(List juice) { + def result = Like.withCriteria { + pgArrayContains 'favoriteJuices', juice + } + + return result + } + /** * Search with a join */ diff --git a/grails-app/services/test/criteria/array/PgIsContainedByCriteriaTestService.groovy b/grails-app/services/test/criteria/array/PgIsContainedByCriteriaTestService.groovy index 75fdc19..f3b4354 100644 --- a/grails-app/services/test/criteria/array/PgIsContainedByCriteriaTestService.groovy +++ b/grails-app/services/test/criteria/array/PgIsContainedByCriteriaTestService.groovy @@ -71,6 +71,28 @@ class PgIsContainedByCriteriaTestService { return result } + /** + * Search for "likes" which elements are equals to the parameter + */ + public List searchIsContainedByEnum(Like.Juice juice) { + def result = Like.withCriteria { + pgArrayIsContainedBy 'favoriteJuices', juice + } + + return result + } + + /** + * Search for "likes" contained by the parameter + */ + public List searchIsContainedByEnum(List juices) { + def result = Like.withCriteria { + pgArrayIsContainedBy 'favoriteJuices', juices + } + + return result + } + /** * Search with a join */ diff --git a/grails-app/services/test/criteria/array/PgIsEmptyCriteriaTestService.groovy b/grails-app/services/test/criteria/array/PgIsEmptyCriteriaTestService.groovy index f7e2e77..9558346 100644 --- a/grails-app/services/test/criteria/array/PgIsEmptyCriteriaTestService.groovy +++ b/grails-app/services/test/criteria/array/PgIsEmptyCriteriaTestService.groovy @@ -36,6 +36,17 @@ class PgIsEmptyCriteriaTestService { return result } + /** + * Search "empty" enum arrays + */ + public List searchEmptyEnumArray() { + def result = Like.withCriteria { + pgArrayIsEmpty 'favoriteJuices' + } + + return result + } + /** * Search "empty" arrays with a join */ diff --git a/grails-app/services/test/criteria/array/PgIsNotEmptyCriteriaTestService.groovy b/grails-app/services/test/criteria/array/PgIsNotEmptyCriteriaTestService.groovy index 149f0bf..e33a0b9 100644 --- a/grails-app/services/test/criteria/array/PgIsNotEmptyCriteriaTestService.groovy +++ b/grails-app/services/test/criteria/array/PgIsNotEmptyCriteriaTestService.groovy @@ -36,6 +36,17 @@ class PgIsNotEmptyCriteriaTestService { return result } + /** + * Search "non empty" enum arrays + */ + public List searchNonEmptyEnumArray() { + def result = Like.withCriteria { + pgArrayIsNotEmpty 'favoriteJuices' + } + + return result + } + /** * Search "non empty" arrays with a join */ diff --git a/grails-app/services/test/criteria/array/PgOverlapsCriteriaTestService.groovy b/grails-app/services/test/criteria/array/PgOverlapsCriteriaTestService.groovy index af524a7..2e57b6b 100644 --- a/grails-app/services/test/criteria/array/PgOverlapsCriteriaTestService.groovy +++ b/grails-app/services/test/criteria/array/PgOverlapsCriteriaTestService.groovy @@ -69,6 +69,28 @@ class PgOverlapsCriteriaTestService { return result } + /** + * Search overlaps "likes" with enum in array + */ + public List overlapsEnumArray(Like.Juice juice) { + def result = Like.withCriteria { + pgArrayOverlaps 'favoriteJuices', juice + } + + return result + } + + /** + * Search overlaps "likes" with n enums in array + */ + public List overlapsEnumArray(List juices) { + def result = Like.withCriteria { + pgArrayOverlaps 'favoriteJuices', juices + } + + return result + } + /** * Search overlaps with a join */ diff --git a/src/java/net/kaleidos/hibernate/PostgresqlExtensionsDialect.java b/src/java/net/kaleidos/hibernate/PostgresqlExtensionsDialect.java index f06af49..483bc46 100644 --- a/src/java/net/kaleidos/hibernate/PostgresqlExtensionsDialect.java +++ b/src/java/net/kaleidos/hibernate/PostgresqlExtensionsDialect.java @@ -3,6 +3,7 @@ import net.kaleidos.hibernate.usertype.IntegerArrayType; import net.kaleidos.hibernate.usertype.LongArrayType; import net.kaleidos.hibernate.usertype.StringArrayType; +import net.kaleidos.hibernate.usertype.IdentityEnumArrayType; import org.hibernate.dialect.Dialect; import org.hibernate.dialect.PostgreSQLDialect; @@ -23,6 +24,7 @@ public PostgresqlExtensionsDialect() { registerColumnType(Types.ARRAY, "array"); registerColumnType(LongArrayType.SQLTYPE, "int8[]"); registerColumnType(IntegerArrayType.SQLTYPE, "int[]"); + registerColumnType(IdentityEnumArrayType.SQLTYPE, "int[]"); registerColumnType(StringArrayType.SQLTYPE, "varchar[]"); } diff --git a/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java b/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java index aada951..94a9aa2 100644 --- a/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java +++ b/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java @@ -8,6 +8,9 @@ import org.hibernate.type.Type; import org.hibernate.util.StringHelper; +import java.lang.reflect.Array; +import java.util.List; + /** * Constrains a property in an array */ @@ -42,6 +45,20 @@ public TypedValue[] getTypedValues(Criteria criteria, CriteriaQuery criteriaQuer Object[] arrValue; if ("net.kaleidos.hibernate.usertype.IntegerArrayType".equals(propertyTypeName)) { arrValue = pgCriteriaUtils.getValueAsArrayOfType(value, Integer.class); + } else if ("net.kaleidos.hibernate.usertype.IdentityEnumArrayType".equals(propertyTypeName)) { + arrValue = pgCriteriaUtils.getValueAsArrayOfType( + value, + Integer.class, + new PgCriteriaUtils.MapFunction() { + public Object map(Object o) { + try { + return ((Enum)o).ordinal(); + } catch (ClassCastException e) { + throw new HibernateException("Unable to cast object " + o + " to Enum."); + } + } + } + ); } else if ("net.kaleidos.hibernate.usertype.LongArrayType".equals(propertyTypeName)) { arrValue = pgCriteriaUtils.getValueAsArrayOfType(value, Long.class); } else if ("net.kaleidos.hibernate.usertype.StringArrayType".equals(propertyTypeName)) { diff --git a/src/java/net/kaleidos/hibernate/criterion/array/PgCriteriaUtils.java b/src/java/net/kaleidos/hibernate/criterion/array/PgCriteriaUtils.java index 727a8c1..4b3b0d0 100644 --- a/src/java/net/kaleidos/hibernate/criterion/array/PgCriteriaUtils.java +++ b/src/java/net/kaleidos/hibernate/criterion/array/PgCriteriaUtils.java @@ -14,15 +14,13 @@ class PgCriteriaUtils { * will be the type passed as parameter * @param targetValue The value we want to wrap as an array * @param expectedType The expected type of the returned array + * @param mapFunction If non-null, it will transform each object in the array to a given object. * @return */ @SuppressWarnings("unchecked") - public Object[] getValueAsArrayOfType(Object targetValue, Class expectedType) { + public Object[] getValueAsArrayOfType(Object targetValue, Class expectedType, MapFunction mapFunction) { Object[] arrValue; - if (expectedType.isInstance(targetValue)) { - arrValue = (Object[]) Array.newInstance(expectedType, 1); - arrValue[0] = expectedType.cast(targetValue); - } else if (targetValue instanceof List) { + if (targetValue instanceof List) { List valueAsList = (List)targetValue; arrValue = (Object[]) Array.newInstance(expectedType, valueAsList.size()); @@ -30,15 +28,44 @@ public Object[] getValueAsArrayOfType(Object targetValue, Class expectedType) for(int i=0; i instead"); } } + } else if (expectedType.isInstance(targetValue) || mapFunction != null) { + arrValue = (Object[]) Array.newInstance(expectedType, 1); + + if (mapFunction != null) { + arrValue[0] = expectedType.cast(mapFunction.map(targetValue)); + } else { + arrValue[0] = expectedType.cast(targetValue); + } } else { throw new HibernateException("criteria doesn't support values of type: " + targetValue.getClass().getName() + ". Try: " + expectedType + " or List<" + expectedType + "> instead"); } return arrValue; } + + /** + * Overloaded version of getValueAsArrayOfType that doesn't use a mapFunction + */ + public Object[] getValueAsArrayOfType(Object targetValue, Class expectedType) { + return getValueAsArrayOfType(targetValue, expectedType, null); + } + + /** + * Simple class for passing a closure that takes an Object and transforms it into a new value. + */ + public static abstract class MapFunction { + /** + * Transforms an object into some new value. + * @param o the object we want to transform + * @return some transformed version of the object + */ + public abstract Object map(Object o); + } } diff --git a/src/java/net/kaleidos/hibernate/usertype/AbstractArrayType.java b/src/java/net/kaleidos/hibernate/usertype/AbstractArrayType.java index 9ef44a3..a6a7dea 100644 --- a/src/java/net/kaleidos/hibernate/usertype/AbstractArrayType.java +++ b/src/java/net/kaleidos/hibernate/usertype/AbstractArrayType.java @@ -9,6 +9,7 @@ public abstract class AbstractArrayType implements UserType { protected static final int INTEGER_ARRAY = 90001; protected static final int LONG_ARRAY = 90002; protected static final int STRING_ARRAY = 90003; + protected static final int ENUM_INTEGER_ARRAY = 90004; @Override public Object assemble(Serializable cached, Object owner) throws HibernateException { diff --git a/src/java/net/kaleidos/hibernate/usertype/IdentityEnumArrayType.java b/src/java/net/kaleidos/hibernate/usertype/IdentityEnumArrayType.java new file mode 100644 index 0000000..fe7145a --- /dev/null +++ b/src/java/net/kaleidos/hibernate/usertype/IdentityEnumArrayType.java @@ -0,0 +1,161 @@ +package net.kaleidos.hibernate.usertype; + +import java.sql.Array; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.hibernate.HibernateException; + +import grails.util.GrailsWebUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.hibernate.MappingException; +import org.hibernate.usertype.ParameterizedType; + +import java.io.Serializable; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.*; + +/** +* This class behaves almost identical to IntegerArrayType in that it +* stores and retrieves an array of ints. The difference, however, is that +* this is used with an Array of Enums, rather than Ints. The Enums are serialized +* to their ordinal value before persisted to the database. On retrieval, +* they are then converted back into their original Enum type. +* +* A lot of this code was pieced together using Grails' Hibernate's IdentityEnumType, as seen here: +* http://grepcode.com/file_/repo1.maven.org/maven2/org.grails/grails-hibernate/2.1.1/org/codehaus/groovy/grails/orm/hibernate/cfg/IdentityEnumType.java/ +* +* The BidiEnumMap was lifted 100% from that class. Although it is static, it is declared as Private +* so we are unable to access it without copying the code :(. +*/ +public class IdentityEnumArrayType extends IntegerArrayType implements ParameterizedType { + public static int SQLTYPE = AbstractArrayType.ENUM_INTEGER_ARRAY; + private static final Log LOG = LogFactory.getLog(IdentityEnumArrayType.class); + + private Class> enumClass; + private BidiEnumMap bidiMap; + public static final String ENUM_ID_ACCESSOR = "getId"; + public static final String PARAM_ENUM_CLASS = "enumClass"; + + @Override + public Class returnedClass() { + // This may not be 100%. + // Is there anyway to return the actual enumClass as an array and not cause issues? + return Enum[].class; + } + + @Override + public int[] sqlTypes() { + return new int[] { SQLTYPE }; + } + + public Object idToEnum(Object id) throws HibernateException, SQLException { + try { + if (bidiMap == null) { + bidiMap = new BidiEnumMap(enumClass); + } + + return bidiMap.getEnumValue(id); + } catch (Exception e) { + throw new HibernateException("Uh oh. Unable to create bidirectional enum map for " + enumClass + " with nested exception: " + e.toString()); + } + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException { + Object result = super.nullSafeGet(rs, names, owner); + + if (result == null) { + return result; + } else { + Integer[] results = (Integer[]) result; + Object converted = java.lang.reflect.Array.newInstance(enumClass, results.length); + + for (int i = 0; i < results.length; i++) { + java.lang.reflect.Array.set(converted, i, idToEnum(results[i])); + } + + return converted; + } + } + + @Override + public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException { + Object converted = value; + + if (value != null) { + Object[] o = (Object[])value; + Integer[] result = new Integer[o.length]; + for (int i = 0; i < o.length; i++) { + if (! (o[i] instanceof Integer)) { + result[i] = ((Enum)o[i]).ordinal(); + } else { + result[i] = (Integer)o[i]; + } + } + converted = result; + } + + super.nullSafeSet(st, converted, index); + } + + public void setParameterValues(Properties properties) { + try { + enumClass = (Class>)properties.get(PARAM_ENUM_CLASS); + } catch (Exception e) { + throw new MappingException("Error mapping Enum Class using IdentityEnumArrayType", e); + } + } + + // This code was lifted from IdentityEnumType.java (see class comments for more info) + @SuppressWarnings({"rawtypes", "unchecked"}) + public static class BidiEnumMap implements Serializable { + + private static final long serialVersionUID = 3325751131102095834L; + private final Map enumToKey; + private final Map keytoEnum; + private Class keyType; + + private BidiEnumMap(Class enumClass) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Building Bidirectional Enum Map...")); + } + + @SuppressWarnings("hiding") + EnumMap enumToKey = new EnumMap(enumClass); + @SuppressWarnings("hiding") + HashMap keytoEnum = new HashMap(); + + Method idAccessor = enumClass.getMethod(ENUM_ID_ACCESSOR); + + keyType = idAccessor.getReturnType(); + + Method valuesAccessor = enumClass.getMethod("values"); + Object[] values = (Object[]) valuesAccessor.invoke(enumClass); + + for (Object value : values) { + Object id = idAccessor.invoke(value); + enumToKey.put((Enum) value, id); + if (keytoEnum.containsKey(id)) { + LOG.warn(String.format("Duplicate Enum ID '%s' detected for Enum %s!", id, enumClass.getName())); + } + keytoEnum.put(id, value); + } + + this.enumToKey = Collections.unmodifiableMap(enumToKey); + this.keytoEnum = Collections.unmodifiableMap(keytoEnum); + } + + public Object getEnumValue(Object id) { + return keytoEnum.get(id); + } + + public Object getKey(Object enumValue) { + return enumToKey.get(enumValue); + } + } +} diff --git a/test/integration/net/kaleidos/hibernate/array/PgContainsCriteriaTestServiceIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PgContainsCriteriaTestServiceIntegrationSpec.groovy index 3d6dc92..32c10ae 100644 --- a/test/integration/net/kaleidos/hibernate/array/PgContainsCriteriaTestServiceIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PgContainsCriteriaTestServiceIntegrationSpec.groovy @@ -91,6 +91,38 @@ class PgContainsCriteriaTestServiceIntegrationSpec extends IntegrationSpec { [] | 4 } + @Unroll + void 'search #juice in an array of enums'() { + setup: + new Like(favoriteJuices:[Like.Juice.ORANGE, Like.Juice.GRAPE]).save() + new Like(favoriteJuices:[Like.Juice.PINEAPPLE, Like.Juice.GRAPE, Like.Juice.CARROT, Like.Juice.CRANBERRY]).save() + new Like(favoriteJuices:[Like.Juice.APPLE, Like.Juice.TOMATO, Like.Juice.CARROT]).save() + new Like(favoriteJuices:[Like.Juice.ORANGE, Like.Juice.TOMATO, Like.Juice.CARROT]).save() + + when: + def result = pgContainsCriteriaTestService.searchWithCriteriaEnumArray(juice) + + then: + result.size() == resultSize + + where: + juice | resultSize + Like.Juice.CRANBERRY | 1 + Like.Juice.ORANGE | 2 + Like.Juice.LEMON | 0 + Like.Juice.APPLE | 1 + Like.Juice.GRAPE | 2 + Like.Juice.PINEAPPLE | 1 + Like.Juice.TOMATO | 2 + Like.Juice.CARROT | 3 + Like.Juice.GRAPEFRUIT | 0 + [Like.Juice.ORANGE, Like.Juice.GRAPE] | 1 + [Like.Juice.GRAPE, Like.Juice.PINEAPPLE] | 1 + [Like.Juice.CARROT] | 3 + [Like.Juice.CARROT, Like.Juice.TOMATO] | 2 + [] | 4 + } + void 'search in an array of strings with join with another domain class'() { setup: def user1 = new User(name:'John', like: new Like(favoriteMovies:["The Matrix", "The Lord of the Rings"])).save() @@ -167,4 +199,16 @@ class PgContainsCriteriaTestServiceIntegrationSpec extends IntegrationSpec { movie << [[1], ["Test", 1], [1L], ["Test", 1L]] } -} \ No newline at end of file + @Unroll + void 'search an invalid list inside the array of enum'() { + when: + def result = pgContainsCriteriaTestService.searchWithCriteriaEnumArray(juice) + + then: + thrown(HibernateException) + + where: + juice << [["Test"], [Like.Juice.ORANGE, "Test"], [1L], [Like.Juice.APPLE, 1L]] + } + +} diff --git a/test/integration/net/kaleidos/hibernate/array/PgIsContainedByCriteriaTestServiceIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PgIsContainedByCriteriaTestServiceIntegrationSpec.groovy index 2c22c9c..ad70108 100644 --- a/test/integration/net/kaleidos/hibernate/array/PgIsContainedByCriteriaTestServiceIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PgIsContainedByCriteriaTestServiceIntegrationSpec.groovy @@ -88,6 +88,38 @@ class PgIsContainedByCriteriaTestServiceIntegrationSpec extends IntegrationSpec [] | 0 } + @Unroll + void 'search #juice in an array of enums'() { + setup: + new Like(favoriteJuices:[Like.Juice.ORANGE]).save() + new Like(favoriteJuices:[Like.Juice.PINEAPPLE, Like.Juice.GRAPE, Like.Juice.CARROT, Like.Juice.CRANBERRY]).save() + new Like(favoriteJuices:[Like.Juice.APPLE]).save() + new Like(favoriteJuices:[Like.Juice.ORANGE, Like.Juice.CARROT]).save() + + when: + def result = pgIsContainedByCriteriaTestService.searchIsContainedByEnum(juice) + + then: + result.size() == resultSize + + where: + juice | resultSize + Like.Juice.CRANBERRY | 0 + Like.Juice.ORANGE | 1 + Like.Juice.LEMON | 0 + Like.Juice.APPLE | 1 + Like.Juice.GRAPE | 0 + Like.Juice.PINEAPPLE | 0 + Like.Juice.TOMATO | 0 + Like.Juice.CARROT | 0 + Like.Juice.GRAPEFRUIT | 0 + [Like.Juice.ORANGE, Like.Juice.GRAPE] | 1 + [Like.Juice.GRAPE, Like.Juice.PINEAPPLE, + Like.Juice.ORANGE, Like.Juice.CARROT] | 2 + [Like.Juice.APPLE] | 1 + [Like.Juice.CARROT, Like.Juice.ORANGE] | 2 + [] | 0 + } void 'search in an array of strings with join with another domain class'() { setup: @@ -165,4 +197,16 @@ class PgIsContainedByCriteriaTestServiceIntegrationSpec extends IntegrationSpec movie << [[1], ["Test", 1], [1L], ["Test", 1L]] } -} \ No newline at end of file + @Unroll + void 'search an invalid list #juice inside the array of enum'() { + when: + def result = pgIsContainedByCriteriaTestService.searchIsContainedByEnum(juice) + + then: + thrown(HibernateException) + + where: + juice << [["Test"], ["Test", Like.Juice.ORANGE], [1L], ["Test", 1L]] + } + +} diff --git a/test/integration/net/kaleidos/hibernate/array/PgIsEmptyCriteriaTestServiceIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PgIsEmptyCriteriaTestServiceIntegrationSpec.groovy index b37f525..0a49bed 100644 --- a/test/integration/net/kaleidos/hibernate/array/PgIsEmptyCriteriaTestServiceIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PgIsEmptyCriteriaTestServiceIntegrationSpec.groovy @@ -51,6 +51,19 @@ class PgIsEmptyCriteriaTestServiceIntegrationSpec extends IntegrationSpec { result.size() == 2 } + void 'search for empty enum arrays'() { + setup: + new Like(favoriteJuices:[Like.Juice.APPLE, Like.Juice.TOMATO]).save() + new Like(favoriteJuices:[]).save() + new Like(favoriteJuices:[]).save() + + when: + def result = pgIsEmptyCriteriaTestService.searchEmptyEnumArray() + + then: + result.size() == 2 + } + void 'search in an array of strings with join with another domain class'() { setup: def user1 = new User(name:'John', like: new Like(favoriteMovies:["The Matrix", "The Lord of the Rings"])).save() diff --git a/test/integration/net/kaleidos/hibernate/array/PgIsNotEmptyCriteriaTestServiceIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PgIsNotEmptyCriteriaTestServiceIntegrationSpec.groovy index 6382d02..9516408 100644 --- a/test/integration/net/kaleidos/hibernate/array/PgIsNotEmptyCriteriaTestServiceIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PgIsNotEmptyCriteriaTestServiceIntegrationSpec.groovy @@ -51,6 +51,19 @@ class PgIsNotEmptyCriteriaTestServiceIntegrationSpec extends IntegrationSpec { result.size() == 1 } + void 'search for empty enum arrays'() { + setup: + new Like(favoriteJuices:[Like.Juice.APPLE, Like.Juice.TOMATO]).save() + new Like(favoriteJuices:[]).save() + new Like(favoriteJuices:[]).save() + + when: + def result = pgIsNotEmptyCriteriaTestService.searchNonEmptyEnumArray() + + then: + result.size() == 1 + } + void 'search in an array of strings with join with another domain class'() { setup: def user1 = new User(name:'John', like: new Like(favoriteMovies:["The Matrix", "The Lord of the Rings"])).save() diff --git a/test/integration/net/kaleidos/hibernate/array/PgOverlapsCriteriaTestServiceIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PgOverlapsCriteriaTestServiceIntegrationSpec.groovy index 87de3eb..b4cfeb5 100644 --- a/test/integration/net/kaleidos/hibernate/array/PgOverlapsCriteriaTestServiceIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PgOverlapsCriteriaTestServiceIntegrationSpec.groovy @@ -91,6 +91,38 @@ class PgOverlapsCriteriaTestServiceIntegrationSpec extends IntegrationSpec { [] | 0 } + @Unroll + void 'search #juice in an array of enums'() { + setup: + new Like(favoriteJuices:[Like.Juice.ORANGE, Like.Juice.GRAPE]).save() + new Like(favoriteJuices:[Like.Juice.PINEAPPLE, Like.Juice.GRAPE, Like.Juice.CARROT, Like.Juice.CRANBERRY]).save() + new Like(favoriteJuices:[Like.Juice.APPLE, Like.Juice.TOMATO, Like.Juice.CARROT]).save() + new Like(favoriteJuices:[Like.Juice.ORANGE, Like.Juice.TOMATO, Like.Juice.CARROT]).save() + + when: + def result = pgOverlapsCriteriaTestService.overlapsEnumArray(juice) + + then: + result.size() == resultSize + + where: + juice | resultSize + Like.Juice.CRANBERRY | 1 + Like.Juice.ORANGE | 2 + Like.Juice.LEMON | 0 + Like.Juice.APPLE | 1 + Like.Juice.GRAPE | 2 + Like.Juice.PINEAPPLE | 1 + Like.Juice.TOMATO | 2 + Like.Juice.CARROT | 3 + Like.Juice.GRAPEFRUIT | 0 + [Like.Juice.ORANGE, Like.Juice.GRAPE] | 3 + [Like.Juice.GRAPE, Like.Juice.PINEAPPLE] | 2 + [Like.Juice.CARROT] | 3 + [Like.Juice.CARROT, Like.Juice.TOMATO] | 3 + [] | 0 + } + void 'search in an array of strings with join with another domain class'() { setup: def user1 = new User(name:'John', like: new Like(favoriteMovies:["The Matrix", "The Lord of the Rings"])).save() @@ -167,4 +199,15 @@ class PgOverlapsCriteriaTestServiceIntegrationSpec extends IntegrationSpec { movie << [[1], ["Test", 1], [1L], ["Test", 1L]] } -} \ No newline at end of file + @Unroll + void 'search an invalid list inside the array of enum'() { + when: + def result = pgOverlapsCriteriaTestService.overlapsEnumArray(juice) + + then: + thrown(HibernateException) + + where: + juice << [["Test"], [Like.Juice.ORANGE, "Test"], [1L], [Like.Juice.APPLE, 1L]] + } +} diff --git a/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy index 3c3432d..dd6cef6 100644 --- a/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/array/PostgresqlArraysDomainIntegrationSpec.groovy @@ -3,6 +3,7 @@ package net.kaleidos.hibernate.array import grails.plugin.spock.* import spock.lang.* +import test.array.TestEnum import test.array.TestInteger import test.array.TestLong import test.array.TestString @@ -57,4 +58,20 @@ class PostgresqlArraysDomainIntegrationSpec extends IntegrationSpec { strings << [ [], ["string 1"], ["string 1", "string 2"], ["string 1", "string 2", "string 3"] ] } -} \ No newline at end of file + @Unroll + void 'save a domain class with an enum array value'() { + setup: + def testEnum = new TestEnum(days: days) + + when: + testEnum.save() + + then: + testEnum.hasErrors() == false + testEnum.days.length == days.size() + + where: + days << [ [], [TestEnum.Day.MONDAY], [TestEnum.Day.SUNDAY, TestEnum.Day.SATURDAY], [TestEnum.Day.WEDNESDAY, TestEnum.Day.THURSDAY, TestEnum.Day.TUESDAY] ] + } + +}