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/PostgresqlExtensionsGrailsPlugin.groovy b/PostgresqlExtensionsGrailsPlugin.groovy index 5194114..6c27f59 100644 --- a/PostgresqlExtensionsGrailsPlugin.groovy +++ b/PostgresqlExtensionsGrailsPlugin.groovy @@ -1,8 +1,9 @@ -import net.kaleidos.hibernate.postgresql.PostgresqlArrays +import net.kaleidos.hibernate.postgresql.criteria.ArrayCriterias +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 @@ -69,7 +70,8 @@ This plugin provides support for Postgresql Native Types like Arrays, HStores, J } def doWithDynamicMethods = { ctx -> - new PostgresqlArrays() + new ArrayCriterias() + new HstoreCriterias() } def doWithApplicationContext = { ctx -> 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! 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..1de3884 --- /dev/null +++ b/grails-app/services/test/criteria/hstore/PgHstoreContainsKeyService.groovy @@ -0,0 +1,14 @@ +package test.criteria.hstore + +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/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/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 +} 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..e692531 --- /dev/null +++ b/src/groovy/net/kaleidos/hibernate/postgresql/criteria/HstoreCriterias.groovy @@ -0,0 +1,47 @@ +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() { + 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")) + } + } + 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/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/PgHstoreOperatorExpression.java b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java new file mode 100644 index 0000000..9709631 --- /dev/null +++ b/src/java/net/kaleidos/hibernate/criterion/hstore/PgHstoreOperatorExpression.java @@ -0,0 +1,48 @@ +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 m) { if (m == null || m.isEmpty()) { return ""; @@ -20,7 +26,26 @@ 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--; + } + } + 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--; @@ -29,6 +54,18 @@ public static String toString(Map m) { 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 +82,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 new file mode 100644 index 0000000..7b1351a --- /dev/null +++ b/test/integration/net/kaleidos/hibernate/hstore/PgHstoreContainsKeyIntegrationSpec.groovy @@ -0,0 +1,42 @@ +package net.kaleidos.hibernate.hstore + +import grails.plugin.spock.* +import spock.lang.* + +import test.hstore.TestHstore + +class PgHstoreContainsKeyIntegrationSpec extends IntegrationSpec { + def pgHstoreContainsKeyService + + 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("b") + + 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 + } + + 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/integration/net/kaleidos/hibernate/hstore/PostgresqlHstoreDomainIntegrationSpec.groovy b/test/integration/net/kaleidos/hibernate/hstore/PostgresqlHstoreDomainIntegrationSpec.groovy index 99ea6fb..8de7126 100644 --- a/test/integration/net/kaleidos/hibernate/hstore/PostgresqlHstoreDomainIntegrationSpec.groovy +++ b/test/integration/net/kaleidos/hibernate/hstore/PostgresqlHstoreDomainIntegrationSpec.groovy @@ -14,6 +14,7 @@ class PostgresqlHstoreDomainIntegrationSpec extends IntegrationSpec { when: testHstore.save() + println testHstore.errors then: testHstore.hasErrors() == false diff --git a/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy b/test/unit/net/kaleidos/hibernate/usertype/HstoreHelperSpec.groovy index 10bf20c..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] @@ -101,4 +110,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"]] + } +}