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
4 changes: 2 additions & 2 deletions PostgresqlExtensionsGrailsPlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import net.kaleidos.hibernate.postgresql.criteria.HstoreCriterias

class PostgresqlExtensionsGrailsPlugin {
// the plugin version
def version = "3.0.0"
def version = "3.1.0"
// the version or versions of Grails the plugin is designed for
def grailsVersion = "2.0 > *"
// the other plugins this plugin depends on
Expand All @@ -25,7 +25,7 @@ class PostgresqlExtensionsGrailsPlugin {
def author = "Iván López"
def authorEmail = "lopez.ivan@gmail.com"
def description = '''\
This plugin provides hibernate user types to support for Postgresql Native Types like Arrays, HStores, JSON,... as well as new criterias to query this native types
This plugin provides hibernate user types to support for Postgresql Native Types like Array, HStore, JSON,... as well as new criterias to query this native types
'''

// URL to the plugin's documentation
Expand Down
59 changes: 55 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ Grails Postgresql Extensions
[![Build Status](https://travis-ci.org/kaleidos/grails-postgresql-extensions.svg?branch=3.x)](https://travis-ci.org/kaleidos/grails-postgresql-extensions)
[![Coverage Status](https://coveralls.io/repos/kaleidos/grails-postgresql-extensions/badge.png?branch=3.x)](https://coveralls.io/r/kaleidos/grails-postgresql-extensions?branch=3.x)

This is a grails plugin that provides hibernate user types to use postgresql native types such as arrays, hstores, json,... from a Grails application. It also provides new criterias to query this new native types.
This is a grails plugin that provides hibernate user types to use Postgresql native types such as Array, Hstore, Json,... from a Grails application. It also provides new criterias to query this new native types.

Currently the plugin supports arrays and hstore and some query methods has been implemented. More native types and query methods will be added in the future.
Currently the plugin supports array, hstore and json fields as well as some query methods. More native types and query methods will be added in the future.

* [Installation](#installation)
* [Configuration](#configuration)
Expand All @@ -30,13 +30,17 @@ Currently the plugin supports arrays and hstore and some query methods has been
* [Contains Key](#contains-key)
* [Contains](#contains-1)
* [Is Contained](#is-contained-1)
* [JSON](#json)
* [Authors](#authors)
* [Release Notes](#release-notes)


Installation
------------

For Hibernate 3.x you have to use a 3.x version and for Hibernate 4.x you must use a 4.x version of the plugin.
At the moment, both versions contains the same features and functionallity.

In `BuildConfig` and:

```groovy
Expand Down Expand Up @@ -406,6 +410,49 @@ testAttributes = ["1" : "a", "2" : "b"]
```
This criteria can also be used to look for exact matches


### JSON

Currently the Json support is only available in Grails 2.2.5 and 2.3.1+. Just like with the Hstore support, it's not possible to use a Map in domain classes with old Grails versions and be able to set the database type for the Map.

To define a json field you only have to define a `Map` field and use the `JsonMapType` hibernate user type.

```groovy
import net.kaleidos.hibernate.usertype.JsonMapType

class TestMapJson {

Map data

static constraints = {
}
static mapping = {
data type: JsonMapType
}
}
```

#### Using Json

Now you can create and instance of the domain class:

```groovy
def instance = new TestMapJson(data: [name: "Iván", age: 34, hasChilds: true, childs: [[name: 'Judith', age: 7], [name: 'Adriana', age: 4]]])
instance.save()
```


```
=# select * from test_map_json;

id | version | data
----+---------+-------------------------------------------------------------------------------------------------------------
1 | 0 | {"hasChilds":true,"age":34,"name":"Iván","childs":[{"name":"Judith","age":7},{"name":"Adriana","age":4}]}
```

As you can see the plugin converts to Json automatically the attributes and the lists in the map type.


Authors
-------

Expand All @@ -420,7 +467,11 @@ Collaborations are appreciated :-)
Release Notes
-------------

* [0.9](https://github.com/kaleidos/grails-postgresql-extensions/issues?milestone=1) - 16/Jun/2014 - Add new array criterias: pgArrayEquals, pgArrayNotEquals
* 3.1.0 - 25/Jul/2014 - Add JSON support for Hibernate 3.x. It's now possible to store and read domain classes with map types persisted to json.
* 4.1.0 - 24/Jul/2014 - Add JSON support for Hibernate 4.x. It's now possible to store and read domain classes with map types persisted to json.
* 4.0.0 - 18/Jul/2014 - Version compatible with Hibernate 4.x.
* 3.0.0 - 18/Jul/2014 - Version compatible with Hibernate 3.x.
* [0.9](https://github.com/kaleidos/grails-postgresql-extensions/issues?milestone=1) - 16/Jun/2014 - Add new array criterias: pgArrayEquals, pgArrayNotEquals.
* 0.8.1 - 24/Apr/2014 - Fix NPE when array is null.
* 0.8 - 24/Apr/2014 - Added support for Double and Float arrays. Refactored the ArrayType to be used as a parametrized type.
* 0.7 - Unreleased - New HstoreMapType and update plugin to Grails 2.2.5.
Expand All @@ -440,4 +491,4 @@ Release Notes
* 0.3 - 18/Sep/2013 - Add support to define the schema name for the sequences.
* 0.2 - 25/Aug/2013 - Support for arrays of Enums with automatic serialization/deserialization to ordinal integer value. Thanks to Matt Feury!
* 0.1.1 - 22/Jul/2013 - Some refactors of the code. No functionality added.
* 0.1 - 16/Jul/2013 - Initial version of the plugin with support for integer, long and string array types and criterias pgArrayContains, pgArrayIsContainedBy, pgArrayOverlaps, pgArrayIsEmpty and pgArrayIsNotEmpty.
* 0.1 - 16/Jul/2013 - Initial version of the plugin with support for integer, long and string array types and criterias pgArrayContains, pgArrayIsContainedBy, pgArrayOverlaps, pgArrayIsEmpty and pgArrayIsNotEmpty.
2 changes: 2 additions & 0 deletions grails-app/conf/BuildConfig.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ grails.project.dependency.resolution = {

test "org.spockframework:spock-grails-support:0.7-groovy-2.0"

compile 'com.google.code.gson:gson:2.2.4'

// Coveralls plugin
build 'org.apache.httpcomponents:httpcore:4.3.2'
build 'org.apache.httpcomponents:httpclient:4.3.2'
Expand Down
14 changes: 14 additions & 0 deletions grails-app/domain/test/json/TestMapJson.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package test.json

import net.kaleidos.hibernate.usertype.JsonMapType

class TestMapJson {

Map data

static constraints = {
}
static mapping = {
data type: JsonMapType
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import net.kaleidos.hibernate.usertype.ArrayType;
import net.kaleidos.hibernate.usertype.HstoreType;
import net.kaleidos.hibernate.usertype.JsonMapType;
import org.hibernate.dialect.Dialect;
import org.hibernate.dialect.PostgreSQLDialect;
import org.hibernate.id.PersistentIdentifierGenerator;
Expand All @@ -26,6 +27,7 @@ public PostgresqlExtensionsDialect() {
registerColumnType(ArrayType.DOUBLE_ARRAY, "float8[]");
registerColumnType(ArrayType.FLOAT_ARRAY, "float[]");
registerColumnType(HstoreType.SQLTYPE, "hstore");
registerColumnType(JsonMapType.SQLTYPE, "json");
}

/**
Expand Down
94 changes: 94 additions & 0 deletions src/java/net/kaleidos/hibernate/usertype/JsonMapType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package net.kaleidos.hibernate.usertype;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import org.apache.commons.lang.ObjectUtils;
import org.hibernate.HibernateException;
import org.hibernate.usertype.UserType;

import java.io.Serializable;
import java.lang.reflect.Type;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.HashMap;
import java.util.Map;

public class JsonMapType implements UserType {

public static int SQLTYPE = 90021;

private final Type userType = Map.class;

private final Gson gson = new GsonBuilder().create();

@Override
public int[] sqlTypes() {
return new int[]{SQLTYPE};
}

@Override
public Class returnedClass() {
return userType.getClass();
}

@Override
public boolean equals(Object x, Object y) throws HibernateException {
return ObjectUtils.equals(x, y);
}

@Override
public int hashCode(Object x) throws HibernateException {
return x == null ? 0 : x.hashCode();
}

@Override
public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {
String jsonString = rs.getString(names[0]);
return gson.fromJson(jsonString, userType);
}

@Override
public void nullSafeSet(PreparedStatement st, Object value, int index) throws HibernateException, SQLException {
if (value == null) {
st.setNull(index, Types.OTHER);
} else {
st.setObject(index, gson.toJson(value, userType), Types.OTHER);
}
}

@Override
public Object deepCopy(Object value) throws HibernateException {
if (value != null) {
Map m = (Map) value;

if (m == null) {
m = new HashMap();
}
return new HashMap(m);
} else {
return null;
}
}

@Override
public boolean isMutable() {
return true;
}

@Override
public Serializable disassemble(Object value) throws HibernateException {
return gson.toJson(value, userType);
}

@Override
public Object assemble(Serializable cached, Object owner) throws HibernateException {
return gson.fromJson((String) cached, userType);
}

@Override
public Object replace(Object original, Object target, Object owner) throws HibernateException {
return original;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package net.kaleidos.hibernate.json

import spock.lang.Specification
import spock.lang.Unroll
import test.json.TestMapJson

class PostgresqlJsonMapDomainIntegrationSpec extends Specification {

@Unroll
void 'save a domain class with a map #map to json'() {
setup:
def testMapJson = new TestMapJson(data: map)

when:
testMapJson.save(flush: true)

then:
testMapJson.hasErrors() == false
testMapJson.data == map

where:
map << [null, [:], [name: 'Ivan', age: 34]]
}

void 'save and read a domain class with json'() {
setup:
def value = [name: 'Ivan', age: 34, hasChilds: true, childs: [[name: 'Judith', age: 7], [name: 'Adriana', age: 4]]]
def testMapJson = new TestMapJson(data: value)

when:
testMapJson.save(flush: true)

then:
testMapJson.hasErrors() == false

and:
def obj = testMapJson.get(testMapJson.id)
obj.data.keySet().collect { it.toString() }.equals(['name', 'age', 'hasChilds', 'childs'])
obj.data.childs.size() == 2
}
}