Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 28 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]
}
}
```
Expand All @@ -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()
```

Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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 :-)

Expand Down
25 changes: 25 additions & 0 deletions grails-app/domain/test/array/TestEnum.groovy
Original file line number Diff line number Diff line change
@@ -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]
}
}
20 changes: 19 additions & 1 deletion grails-app/domain/test/criteria/array/Like.groovy
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -11,12 +12,29 @@ 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"

favoriteNumbers type:IntegerArrayType
favoriteMovies type:StringArrayType
favoriteLongNumbers type:LongArrayType
favoriteJuices type:IdentityEnumArrayType, params: [enumClass: Juice]
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ class PgContainsCriteriaTestService {
return result
}

/**
* Search "likes" with enum in array
*/
public List<Like> searchWithCriteriaEnumArray(Like.Juice juice) {
def result = Like.withCriteria {
pgArrayContains 'favoriteJuices', juice
}

return result
}


/**
* Search "likes" with n integers in array
*/
Expand Down Expand Up @@ -69,6 +81,17 @@ class PgContainsCriteriaTestService {
return result
}

/**
* Search "likes" with n enums in array
*/
public List<Like> searchWithCriteriaEnumArray(List<Like.Juice> juice) {
def result = Like.withCriteria {
pgArrayContains 'favoriteJuices', juice
}

return result
}

/**
* Search with a join
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,28 @@ class PgIsContainedByCriteriaTestService {
return result
}

/**
* Search for "likes" which elements are equals to the parameter
*/
public List<Like> searchIsContainedByEnum(Like.Juice juice) {
def result = Like.withCriteria {
pgArrayIsContainedBy 'favoriteJuices', juice
}

return result
}

/**
* Search for "likes" contained by the parameter
*/
public List<Like> searchIsContainedByEnum(List<Like.Juice> juices) {
def result = Like.withCriteria {
pgArrayIsContainedBy 'favoriteJuices', juices
}

return result
}

/**
* Search with a join
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ class PgIsEmptyCriteriaTestService {
return result
}

/**
* Search "empty" enum arrays
*/
public List<Like> searchEmptyEnumArray() {
def result = Like.withCriteria {
pgArrayIsEmpty 'favoriteJuices'
}

return result
}

/**
* Search "empty" arrays with a join
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ class PgIsNotEmptyCriteriaTestService {
return result
}

/**
* Search "non empty" enum arrays
*/
public List<Like> searchNonEmptyEnumArray() {
def result = Like.withCriteria {
pgArrayIsNotEmpty 'favoriteJuices'
}

return result
}

/**
* Search "non empty" arrays with a join
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,28 @@ class PgOverlapsCriteriaTestService {
return result
}

/**
* Search overlaps "likes" with enum in array
*/
public List<Like> overlapsEnumArray(Like.Juice juice) {
def result = Like.withCriteria {
pgArrayOverlaps 'favoriteJuices', juice
}

return result
}

/**
* Search overlaps "likes" with n enums in array
*/
public List<Like> overlapsEnumArray(List<Like.Juice> juices) {
def result = Like.withCriteria {
pgArrayOverlaps 'favoriteJuices', juices
}

return result
}

/**
* Search overlaps with a join
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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[]");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,58 @@ 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<Object> valueAsList = (List<Object>)targetValue;
arrValue = (Object[]) Array.newInstance(expectedType, valueAsList.size());

// We will iterate the collection and if the value it's not a valid value we throw the exception
for(int i=0; i<valueAsList.size(); i++) {
if (expectedType.isInstance(valueAsList.get(i))) {
arrValue[i] = expectedType.cast(valueAsList.get(i));
} else if (mapFunction != null) {
arrValue[i] = expectedType.cast(mapFunction.map(valueAsList.get(i)));
} else {
throw new HibernateException("criteria doesn't support values of type: " +
targetValue.getClass().getName() + ". Try: " + expectedType + " or List<" + expectedType + "> 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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading