From 8f8ff0b22c70e6009eab078501c6496613a29d56 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Tue, 5 Nov 2013 20:50:08 +0100 Subject: [PATCH 1/5] Added HSTOR Contains Key criteria --- PostgresqlExtensionsGrailsPlugin.groovy | 2 + .../domain/test/hstore/TestHstore.groovy | 6 +++ .../hstore/PgHstoreContainsKeyService.groovy | 14 ++++++ .../criteria/HstoreCriterias.groovy | 23 ++++++++++ .../criterion/array/PgArrayExpression.java | 3 +- .../hstore/PgHstoreValueFunction.java | 45 +++++++++++++++++++ .../PgHstoreContainsKeyIntegrationSpec.groovy | 28 ++++++++++++ ...stgresqlHstoreDomainIntegrationSpec.groovy | 1 + 8 files changed, 121 insertions(+), 1 deletion(-) create mode 100644 grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy create mode 100644 src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy create mode 100644 src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreValueFunction.java create mode 100644 test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsKeyIntegrationSpec.groovy diff --git a/PostgresqlExtensionsGrailsPlugin.groovy b/PostgresqlExtensionsGrailsPlugin.groovy index 5194114..bb6643a 100644 --- a/PostgresqlExtensionsGrailsPlugin.groovy +++ b/PostgresqlExtensionsGrailsPlugin.groovy @@ -1,4 +1,5 @@ import net.kaleidos.hibernate.postgresql.PostgresqlArrays +import net.kaleidos.hibernate.postgresql.criteria.HstoreCriterias class PostgresqlExtensionsGrailsPlugin { // the plugin version @@ -70,6 +71,7 @@ This plugin provides support for Postgresql Native Types like Arrays, HStores, J def doWithDynamicMethods = { ctx -> new PostgresqlArrays() + new HstoreCriterias() } def doWithApplicationContext = { ctx -> diff --git a/grails-app/domain/test/hstore/TestHstore.groovy b/grails-app/domain/test/hstore/TestHstore.groovy index 46feaa7..efe09c4 100644 --- a/grails-app/domain/test/hstore/TestHstore.groovy +++ b/grails-app/domain/test/hstore/TestHstore.groovy @@ -4,10 +4,16 @@ import net.kaleidos.hibernate.postgresql.hstore.Hstore import net.kaleidos.hibernate.usertype.HstoreType class TestHstore { + String name + Integer luckyNumber @Hstore Map testAttributes + static constraints = { + name nullable: true + luckyNumber nullable: true + } static mapping = { testAttributes type:HstoreType } diff --git a/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy b/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy new file mode 100644 index 0000000..34f35a1 --- /dev/null +++ b/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy @@ -0,0 +1,14 @@ +package test.criteria.array + +import test.hstore.TestHstore + +class PgHstoreContainsKeyService { + static transactional = false + + public List searchElementsWithKey(String key) { + def result = TestHstore.withCriteria { + pgHstoreContainsKey 'testAttributes', key + } + return result + } +} diff --git a/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy new file mode 100644 index 0000000..4987750 --- /dev/null +++ b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy @@ -0,0 +1,23 @@ +package net.kaleidos.hibernate.postgresql.criteria + +import grails.orm.HibernateCriteriaBuilder +import net.kaleidos.hibernate.criterion.hstore.PgHstoreValueFunction + +class HstoreCriterias { + public HstoreCriterias() { + addPgHstoreContainsKey() + } + + public void addPgHstoreContainsKey() { + HibernateCriteriaBuilder.metaClass.pgHstoreContainsKey = { String propertyName, Object propertyValue -> + if (!validateSimpleExpression()) { + throwRuntimeException(new IllegalArgumentException("Call to [pgHstoreContains] with propertyName [" + + propertyName + "] and value [" + propertyValue + "] not allowed here.")) + } + propertyName = calculatePropertyName(propertyName) + propertyValue = calculatePropertyValue(propertyValue) + + return addToCriteria(new PgHstoreValueFunction(propertyName, propertyValue, "exist")) + } + } +} diff --git a/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java b/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java index d95fe2d..8fe33dc 100644 --- a/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java +++ b/src/java/net/kaleidos/hibernate/criterion/array/PgArrayExpression.java @@ -27,6 +27,7 @@ protected PgArrayExpression(String propertyName, Object value, String op) { this.op = op; } + @Override public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { return StringHelper.join( " and ", @@ -69,4 +70,4 @@ public Object map(Object o) { criteriaQuery.getTypedValue(criteria, propertyName, arrValue) }; } -} \ No newline at end of file +} diff --git a/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreValueFunction.java b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreValueFunction.java new file mode 100644 index 0000000..473e678 --- /dev/null +++ b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreValueFunction.java @@ -0,0 +1,45 @@ +package net.kaleidos.hibernate.criterion.hstore; + +import org.hibernate.Criteria; +import org.hibernate.HibernateException; +import org.hibernate.criterion.CriteriaQuery; +import org.hibernate.criterion.Criterion; +import org.hibernate.engine.TypedValue; +import org.hibernate.type.Type; +import org.hibernate.type.StringType; +import org.hibernate.util.StringHelper; + +/** + * Constrains a property in an hstore + */ +public class PgHstoreValueFunction implements Criterion { + private static final long serialVersionUID = 2872183637309166619L; + + // private final PgCriteriaUtils pgCriteriaUtils = new PgCriteriaUtils(); + + private final String propertyName; + private final Object value; + private final String function; + + protected PgHstoreValueFunction(String propertyName, Object value, String function) { + this.propertyName = propertyName; + this.value = value; + this.function = function; + } + + @Override + public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { + String[] columns = StringHelper.suffix(criteriaQuery.findColumns(propertyName, criteria), ""); + for (int i=0; i Date: Tue, 5 Nov 2013 20:53:51 +0100 Subject: [PATCH 2/5] Renamed array criteria class --- PostgresqlExtensionsGrailsPlugin.groovy | 4 ++-- .../ArrayCriterias.groovy} | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) rename src/groovy/net/kaleidos/hibernate/postgresql/{PostgresqlArrays.groovy => criteria/ArrayCriterias.groovy} (97%) diff --git a/PostgresqlExtensionsGrailsPlugin.groovy b/PostgresqlExtensionsGrailsPlugin.groovy index bb6643a..a2055c0 100644 --- a/PostgresqlExtensionsGrailsPlugin.groovy +++ b/PostgresqlExtensionsGrailsPlugin.groovy @@ -1,4 +1,4 @@ -import net.kaleidos.hibernate.postgresql.PostgresqlArrays +import net.kaleidos.hibernate.postgresql.criteria.ArrayCriterias import net.kaleidos.hibernate.postgresql.criteria.HstoreCriterias class PostgresqlExtensionsGrailsPlugin { @@ -70,7 +70,7 @@ This plugin provides support for Postgresql Native Types like Arrays, HStores, J } def doWithDynamicMethods = { ctx -> - new PostgresqlArrays() + new ArrayCriterias() new HstoreCriterias() } diff --git a/src/groovy/net/kaleidos/hibernate/postgresql/PostgresqlArrays.groovy b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/ArrayCriterias.groovy similarity index 97% rename from src/groovy/net/kaleidos/hibernate/postgresql/PostgresqlArrays.groovy rename to src/groovy/net/kaleidos/hibernate/postgresql/criteria/ArrayCriterias.groovy index fbb52fa..f50060e 100644 --- a/src/groovy/net/kaleidos/hibernate/postgresql/PostgresqlArrays.groovy +++ b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/ArrayCriterias.groovy @@ -1,4 +1,4 @@ -package net.kaleidos.hibernate.postgresql +package net.kaleidos.hibernate.postgresql.criteria import grails.orm.HibernateCriteriaBuilder import net.kaleidos.hibernate.criterion.array.PgArrayExpression @@ -6,9 +6,9 @@ import net.kaleidos.hibernate.criterion.array.PgEmptinessExpression import org.hibernate.criterion.Restrictions -class PostgresqlArrays { +class ArrayCriterias { - public PostgresqlArrays() { + public ArrayCriterias() { addContainsOperator() addIsContainedByOperator() addOverlapsOperator() @@ -113,4 +113,4 @@ class PostgresqlArrays { } -} \ No newline at end of file +} From b6036a2d00ef4771a9c2b6cb2512acb0c974fe96 Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Wed, 6 Nov 2013 20:41:20 +0100 Subject: [PATCH 3/5] Added 'contains' and 'isContained' operations --- .../hstore/PgHstoreContainsKeyService.groovy | 2 +- .../hstore/PgHstoreContainsService.groovy | 14 +++ .../hstore/PgHstoreIsContainedService.groovy | 14 +++ .../criteria/HstoreCriterias.groovy | 24 ++++ .../hstore/PgHstoreOperatorExpression.java | 57 ++++++++++ .../hibernate/usertype/HstoreHelper.java | 32 +++++- .../PgHstoreContainsIntegrationSpec.groovy | 103 ++++++++++++++++++ .../PgHstoreContainsKeyIntegrationSpec.groovy | 20 +++- .../PgHstoreIsContainedIntegrationSpec.groovy | 69 ++++++++++++ .../usertype/HstoreHelperSpec.groovy | 28 ++++- 10 files changed, 357 insertions(+), 6 deletions(-) create mode 100644 grails-app/services/test/criteria/hstore/PgHstoreContainsService.groovy create mode 100644 grails-app/services/test/criteria/hstore/PgHstoreIsContainedService.groovy create mode 100644 src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java create mode 100644 test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsIntegrationSpec.groovy create mode 100644 test/integration/net/kaleidos/hibernate/hstore/PgHstoreIsContainedIntegrationSpec.groovy diff --git a/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy b/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy index 34f35a1..1de3884 100644 --- a/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy +++ b/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy @@ -1,4 +1,4 @@ -package test.criteria.array +package test.criteria.hstore import test.hstore.TestHstore diff --git a/grails-app/services/test/criteria/hstore/PgHstoreContainsService.groovy b/grails-app/services/test/criteria/hstore/PgHstoreContainsService.groovy new file mode 100644 index 0000000..397c77b --- /dev/null +++ b/grails-app/services/test/criteria/hstore/PgHstoreContainsService.groovy @@ -0,0 +1,14 @@ +package test.criteria.hstore + +import test.hstore.TestHstore + +class PgHstoreContainsService { + static transactional = false + + public List searchElementsWithValues(Map map) { + def result = TestHstore.withCriteria { + pgHstoreContains 'testAttributes', map + } + return result + } +} diff --git a/grails-app/services/test/criteria/hstore/PgHstoreIsContainedService.groovy b/grails-app/services/test/criteria/hstore/PgHstoreIsContainedService.groovy new file mode 100644 index 0000000..51e2397 --- /dev/null +++ b/grails-app/services/test/criteria/hstore/PgHstoreIsContainedService.groovy @@ -0,0 +1,14 @@ +package test.criteria.hstore + +import test.hstore.TestHstore + +class PgHstoreIsContainedService { + static transactional = false + + public List searchElementsWithValues(Map map) { + def result = TestHstore.withCriteria { + pgHstoreIsContained 'testAttributes', map + } + return result + } +} diff --git a/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy index 4987750..e692531 100644 --- a/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy +++ b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy @@ -2,10 +2,13 @@ package net.kaleidos.hibernate.postgresql.criteria import grails.orm.HibernateCriteriaBuilder import net.kaleidos.hibernate.criterion.hstore.PgHstoreValueFunction +import net.kaleidos.hibernate.criterion.hstore.PgHstoreOperatorExpression class HstoreCriterias { public HstoreCriterias() { addPgHstoreContainsKey() + addPgHstoreContains() + addPgHstoreIsContained() } public void addPgHstoreContainsKey() { @@ -20,4 +23,25 @@ class HstoreCriterias { return addToCriteria(new PgHstoreValueFunction(propertyName, propertyValue, "exist")) } } + public void addPgHstoreContains() { + HibernateCriteriaBuilder.metaClass.pgHstoreContains = { String propertyName, Map values -> + if (!validateSimpleExpression()) { + throwRuntimeException(new IllegalArgumentException("Call to [pgHstoreContains] with propertyName [" + + propertyName + "] and value [" + propertyValue + "] not allowed here.")) + } + propertyName = calculatePropertyName(propertyName) + return addToCriteria(new PgHstoreOperatorExpression(propertyName, values, "@>")) + } + } + + public void addPgHstoreIsContained() { + HibernateCriteriaBuilder.metaClass.pgHstoreIsContained = { String propertyName, Map values -> + if (!validateSimpleExpression()) { + throwRuntimeException(new IllegalArgumentException("Call to [pgHstoreIsContained] with propertyName [" + + propertyName + "] and value [" + propertyValue + "] not allowed here.")) + } + propertyName = calculatePropertyName(propertyName) + return addToCriteria(new PgHstoreOperatorExpression(propertyName, values, "<@")) + } + } } diff --git a/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java new file mode 100644 index 0000000..103e754 --- /dev/null +++ b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java @@ -0,0 +1,57 @@ +package net.kaleidos.hibernate.criterion.hstore; + +import java.util.Map; +import java.util.List; + +import org.hibernate.Criteria; +import org.hibernate.HibernateException; +import org.hibernate.criterion.CriteriaQuery; +import org.hibernate.criterion.Criterion; +import org.hibernate.engine.TypedValue; +import org.hibernate.type.Type; +import org.hibernate.type.StringType; +import org.hibernate.util.StringHelper; + +import net.kaleidos.hibernate.usertype.HstoreHelper; + +/** + * Constrains a property in an hstore + */ +public class PgHstoreOperatorExpression implements Criterion { + private static final long serialVersionUID = 2872183637309166619L; + + private final String propertyName; + private final Map value; + private final String operator; + + private static final TypedValue[] NO_VALUES = new TypedValue[0]; + + protected PgHstoreOperatorExpression(String propertyName, Map value, String operator) { + this.propertyName = propertyName; + this.value = value; + this.operator = operator; + } + + @Override + public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { + String[] columns = StringHelper.suffix(criteriaQuery.findColumns(propertyName, criteria), ""); + for (int i=0; i listKeyValue = HstoreHelper.asListKeyValue(this.value); + TypedValue[] result = new TypedValue[listKeyValue.size()]; + for (int i=0; i m) { return sb.toString(); } + public static String asStatement(Map m) { + if (m == null || m.isEmpty()) { + return ""; + } + StringBuilder sb = new StringBuilder(); + int n = m.size(); + for (String key : m.keySet()) { + sb.append("?" + K_V_SEPARATOR + "\"" + "?" + "\""); + if (n > 1) { + sb.append(", "); + n--; + } + } + return sb.toString(); + } + + + public static List asListKeyValue(Map m) { + List result = new LinkedList(); + if (m != null && !m.isEmpty()) { + for (String key : m.keySet()) { + result.add(key); + result.add(String.valueOf(m.get(key))); + } + } + return result; + } + public static HstoreDomainType toMap(String s) { Map m = new HashMap(); if (s == null || s.equals("")) { @@ -45,4 +75,4 @@ public static HstoreDomainType toMap(String s) { } return new HstoreDomainType(m); } -} \ No newline at end of file +} diff --git a/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsIntegrationSpec.groovy new file mode 100644 index 0000000..f738c68 --- /dev/null +++ b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsIntegrationSpec.groovy @@ -0,0 +1,103 @@ +package net.kaleidos.hibernate.hstore + +import grails.plugin.spock.* +import spock.lang.* + +import test.hstore.TestHstore + +class PgHstoreContainsIntegrationSpec extends IntegrationSpec { + def pgHstoreContainsService + + void 'Test only one value result 2 different elements'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreContainsService.searchElementsWithValues(map) + + then: + result.size() == 2 + result.find { it.name=="test1" } != null + result.find { it.name=="test2" } == null + result.find { it.name=="test3" } == null + result.find { it.name=="test4" } != null + + where: + map = ["b": "1"] + } + + void 'Test two values that matches partialy with one element'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreContainsService.searchElementsWithValues(map) + + then: + result.size() == 1 + result.find { it.name=="test1" } == null + result.find { it.name=="test2" } == null + result.find { it.name=="test3" } == null + result.find { it.name=="test4" } != null + + where: + map = ["b": "1", "c": "test"] + } + + void 'No matches with the same combination key/value'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreContainsService.searchElementsWithValues(map) + + then: + result.size() == 0 + + where: + map = ["b": "3"] + } + + void 'No matches with the same combination but one of the elements matches'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreContainsService.searchElementsWithValues(map) + + then: + result.size() == 0 + + where: + map = ["b": "1", "c": "test-otro"] + } + + void 'When empty map returns all elements'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreContainsService.searchElementsWithValues(map) + + then: + result.size() == 4 + + where: + map = [:] + } +} diff --git a/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsKeyIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsKeyIntegrationSpec.groovy index 418ae5a..7b1351a 100644 --- a/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsKeyIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsKeyIntegrationSpec.groovy @@ -11,9 +11,9 @@ class PgHstoreContainsKeyIntegrationSpec extends IntegrationSpec { void 'Test find hstore that contains key'() { setup: new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) - new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) - new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) - new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "3"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "3"]).save(flush: true) when: def result = pgHstoreContainsKeyService.searchElementsWithKey("b") @@ -25,4 +25,18 @@ class PgHstoreContainsKeyIntegrationSpec extends IntegrationSpec { result.find { it.name=="test3" } == null result.find { it.name=="test4" } != null } + + void 'Test find hstore that contains key'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["b": "2"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "3"]).save(flush: true) + + when: + def result = pgHstoreContainsKeyService.searchElementsWithKey("X") + + then: + result.size() == 0 + } } diff --git a/test/integration/net/kaleidos/hibernate/hstore/PgHstoreIsContainedIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreIsContainedIntegrationSpec.groovy new file mode 100644 index 0000000..c7d012e --- /dev/null +++ b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreIsContainedIntegrationSpec.groovy @@ -0,0 +1,69 @@ +package net.kaleidos.hibernate.hstore + +import grails.plugin.spock.* +import spock.lang.* + +import test.hstore.TestHstore + +class PgHstoreIsContainedIntegrationSpec extends IntegrationSpec { + def pgHstoreIsContainedService + + void 'No element matches with the empty set'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["d": "10"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreIsContainedService.searchElementsWithValues(map) + + then: + result.size() == 0 + + where: + map = [:] + } + + void 'All elements matches'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["d": "10"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreIsContainedService.searchElementsWithValues(map) + + then: + result.size() == 4 + result.find { it.name=="test1" } != null + result.find { it.name=="test2" } != null + result.find { it.name=="test3" } != null + result.find { it.name=="test4" } != null + + where: + map = ["a": "test", "b": "1", "c": "test", "d": "10"] + } + + void 'Some elements matches'() { + setup: + new TestHstore(name: "test1", testAttributes: ["a": "test", "b": "1"]).save(flush: true) + new TestHstore(name: "test2", testAttributes: ["d": "10"]).save(flush: true) + new TestHstore(name: "test3", testAttributes: ["a": "test"]).save(flush: true) + new TestHstore(name: "test4", testAttributes: ["c": "test", "b": "1"]).save(flush: true) + + when: + def result = pgHstoreIsContainedService.searchElementsWithValues(map) + + then: + result.size() == 3 + result.find { it.name=="test1" } != null + result.find { it.name=="test2" } == null + result.find { it.name=="test3" } != null + result.find { it.name=="test4" } != null + + where: + map = ["a": "test", "b": "1", "c": "test"] + } +} diff --git a/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy b/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy index 10bf20c..ef9e695 100644 --- a/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy +++ b/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy @@ -101,4 +101,30 @@ public class HstoreHelperSpec extends Specification { where: value << [123, true, null, 999L, new Date(), 87987.8976] } -} \ No newline at end of file + + @Unroll + void 'Test asStatement'() { + when: + def result = HstoreHelper.asStatement(map) + + then: + result == expected + + where: + map << [null, [:], ["a":"b"], ["a": "b", "c": "d"], ["a":"b","c":"d","e":"f"]] + expected << ["", "", '?=>"?"', '?=>"?", ?=>"?"', '?=>"?", ?=>"?", ?=>"?"'] + } + + @Unroll + void 'Test asListKeyValue'() { + when: + def result = HstoreHelper.asListKeyValue(map) + + then: + result == expected + + where: + map << [null, [:], ["a":"b"], ["a": "b", "c": "d"], ["a":"b","c":"d","e":"f"]] + expected << [[], [], ["a","b"], ["a","b","c","d"],["a","b","c","d","e","f"]] + } +} From b67b7f8fcbd98b82cb672016502865d06f251c32 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Thu, 7 Nov 2013 08:45:57 +0100 Subject: [PATCH 4/5] Escaped quotes on Hstores --- .gitignore | 3 ++- .../hstore/PgHstoreOperatorExpression.java | 9 --------- .../hibernate/usertype/HstoreHelper.java | 11 +++++++++-- .../hibernate/usertype/HstoreHelperSpec.groovy | 17 +++++++++++++---- 4 files changed, 24 insertions(+), 16 deletions(-) diff --git a/.gitignore b/.gitignore index bb263cb..2689fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ grails-app/conf/UrlMappings.groovy web-app/* grails-postgresql-extensions-*.zip .idea/ -*.iml \ No newline at end of file +*.iml +*.swp diff --git a/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java index 103e754..9709631 100644 --- a/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java +++ b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java @@ -36,7 +36,6 @@ protected PgHstoreOperatorExpression(String propertyName, Map val public String toSqlString(Criteria criteria, CriteriaQuery criteriaQuery) throws HibernateException { String[] columns = StringHelper.suffix(criteriaQuery.findColumns(propertyName, criteria), ""); for (int i=0; i listKeyValue = HstoreHelper.asListKeyValue(this.value); - TypedValue[] result = new TypedValue[listKeyValue.size()]; - for (int i=0; i m) { if (m == null || m.isEmpty()) { return ""; @@ -22,7 +26,10 @@ public static String toString(Map m) { StringBuilder sb = new StringBuilder(); int n = m.size(); for (String key : m.keySet()) { - sb.append(key + K_V_SEPARATOR + "\"" + String.valueOf(m.get(key)) + "\""); + sb.append('"').append(escapeQuotes(key)).append('"'); + sb.append(K_V_SEPARATOR); + sb.append('"').append(escapeQuotes(String.valueOf(m.get(key)))).append('"'); + if (n > 1) { sb.append(", "); n--; @@ -38,7 +45,7 @@ public static String asStatement(Map m) { StringBuilder sb = new StringBuilder(); int n = m.size(); for (String key : m.keySet()) { - sb.append("?" + K_V_SEPARATOR + "\"" + "?" + "\""); + sb.append("\"?\"" + K_V_SEPARATOR + "\"" + "?" + "\""); if (n > 1) { sb.append(", "); n--; diff --git a/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy b/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy index ef9e695..bc29528 100644 --- a/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy +++ b/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy @@ -27,7 +27,16 @@ public class HstoreHelperSpec extends Specification { m.foo = "bar" expect: - HstoreHelper.toString(m) == 'foo=>"bar"' + HstoreHelper.toString(m) == '"foo"=>"bar"' + } + + void 'transform map with doulbe quotes'() { + setup: + def m = [:] + m['Test "cosa"'] = "bar" + + expect: + HstoreHelper.toString(m) == '"Test \'cosa\'"=>"bar"' } void 'map with two values to string'() { @@ -37,7 +46,7 @@ public class HstoreHelperSpec extends Specification { m.xxx = "Groovy Rocks!" expect: - HstoreHelper.toString(m) == 'foo=>"bar", xxx=>"Groovy Rocks!"' + HstoreHelper.toString(m) == '"foo"=>"bar", "xxx"=>"Groovy Rocks!"' } @Unroll @@ -47,7 +56,7 @@ public class HstoreHelperSpec extends Specification { m.prop = value expect: - HstoreHelper.toString(m) == "prop=>\"${value}\"" + HstoreHelper.toString(m) == "\"prop\"=>\"${value}\"" where: value << [123, true, null, 999L, new Date(), 87987.8976] @@ -112,7 +121,7 @@ public class HstoreHelperSpec extends Specification { where: map << [null, [:], ["a":"b"], ["a": "b", "c": "d"], ["a":"b","c":"d","e":"f"]] - expected << ["", "", '?=>"?"', '?=>"?", ?=>"?"', '?=>"?", ?=>"?", ?=>"?"'] + expected << ["", "", '"?"=>"?"', '"?"=>"?", "?"=>"?"', '"?"=>"?", "?"=>"?", "?"=>"?"'] } @Unroll From 81f19d0b2f1ab148430698eb318d582962d2861b Mon Sep 17 00:00:00 2001 From: Alonso Torres Date: Thu, 7 Nov 2013 23:24:05 +0100 Subject: [PATCH 5/5] Changed readme and version update --- PostgresqlExtensionsGrailsPlugin.groovy | 2 +- README.md | 52 +++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/PostgresqlExtensionsGrailsPlugin.groovy b/PostgresqlExtensionsGrailsPlugin.groovy index a2055c0..6c27f59 100644 --- a/PostgresqlExtensionsGrailsPlugin.groovy +++ b/PostgresqlExtensionsGrailsPlugin.groovy @@ -3,7 +3,7 @@ import net.kaleidos.hibernate.postgresql.criteria.HstoreCriterias class PostgresqlExtensionsGrailsPlugin { // the plugin version - def version = "0.4" + def version = "0.5" // the version or versions of Grails the plugin is designed for def grailsVersion = "2.0 > *" // the other plugins this plugin depends on diff --git a/README.md b/README.md index 3576ac1..7438b70 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,10 @@ Currently the plugin supports arrays and hstore and some query methods has been * [Is Empty](#is-empty) * [Is Not Empty](#is-not-empty) * [Hstore](#hstore) + * [Criterias](#hstore-criterias) + * [Contains Key](#contains-key) + * [Contains](#contains-1) + * [Is Contained](#is-contained-1) * [Authors](#authors) * [Release Notes](#release-notes) @@ -257,6 +261,53 @@ instance2.save() ``` +#### HSTORE Criterias + +We have added the following criteria operations to query rows using the Hstore custom type. You can +check the [services](https://github.com/kaleidos/grails-postgresql-extensions/tree/master/grails-app/services/test/criteria/hstore) +and the [tests](https://github.com/kaleidos/grails-postgresql-extensions/tree/master/test/integration/net/kaleidos/hibernate/hstore) +to help you to developp your own criterias. + +You can also check the official [Postgresql Hstore operators](http://www.postgresql.org/docs/9.0/static/hstore.html). + +##### Contains Key + +With this operation you can search for rows that contains an Hstore with the key passd as parameter. + +```groovy +def wantedKey = "my-custom-key" +def result = MyDomain.withCriteria { + pgHstoreContainsKey "attributes", wantedKey +} +``` + +##### Contains + +You can search for data that contains certain pairs of (key,value) +```groovy +def result = Users.withCriteria { + pgHstoreContains 'configuration', ["language": "es"] +} +``` + +##### Is Contained + +The operation is contained can be used when looking for rows that has all the elements in the map +passed as parameter. + +```groovy +def result = TestHstore.withCriteria { + pgHstoreIsContained 'testAttributes', ["1" : "a", "2" : "b"] +} +``` +The example above returns the rows that contains elements like: +``` +testAttributes = ["1" : "a"] +testAttributes = ["2" : "b"] +testAttributes = ["1" : "a", "2" : "b"] +``` +This criteria can also be used to look for exact matches + Authors ------- @@ -272,6 +323,7 @@ Collaborations are appreciated :-) Release Notes ------------- +* 0.5 - 08/Nov/2013 - Added criteria operation for Hstore types * 0.4 - 28/Oct/2013 - Add support to Hstore. It's only possible to save and get, but no queries has been implemented. * 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!