From 583c5f74eb8a0c2b685cd45cbf0d6c083a805d33 Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Tue, 14 Jun 2022 14:57:50 +1200 Subject: [PATCH] Support Postgres JSONB/JSON with String - @DbJsonb String content; --- .../server/type/DefaultTypeManager.java | 3 + .../server/type/ScalarTypeJsonString.java | 41 ++++++++++++ ebean-test/pom.xml | 3 +- .../src/test/java/main/StartPostgres.java | 10 +-- .../org/tests/json/TestDbJsonBStringType.java | 47 ++++++++++++++ .../org/tests/json/TestDbJsonStringType.java | 47 ++++++++++++++ .../tests/model/json/EBasicJsonBString.java | 62 +++++++++++++++++++ .../tests/model/json/EBasicJsonString.java | 62 +++++++++++++++++++ .../src/test/resources/ebean.properties | 2 +- .../src/test/resources/logback-test.xml | 6 +- 10 files changed, 273 insertions(+), 10 deletions(-) create mode 100644 ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonString.java create mode 100644 ebean-test/src/test/java/org/tests/json/TestDbJsonBStringType.java create mode 100644 ebean-test/src/test/java/org/tests/json/TestDbJsonStringType.java create mode 100644 ebean-test/src/test/java/org/tests/model/json/EBasicJsonBString.java create mode 100644 ebean-test/src/test/java/org/tests/model/json/EBasicJsonString.java diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java index 020ba58ae..165d86c5a 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/DefaultTypeManager.java @@ -353,6 +353,9 @@ public final class DefaultTypeManager implements TypeManager { Type genericType = prop.getGenericType(); boolean hasJacksonAnnotations = objectMapperPresent && checkJacksonAnnotations(prop); + if (type.equals(String.class)) { + return ScalarTypeJsonString.typeFor(postgres, dbType); + } if (type.equals(List.class)) { DocPropertyType docType = getDocType(genericType); if (!hasJacksonAnnotations && isValueTypeSimple(genericType)) { diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonString.java b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonString.java new file mode 100644 index 000000000..51fb54d4b --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/type/ScalarTypeJsonString.java @@ -0,0 +1,41 @@ +package io.ebeaninternal.server.type; + +import io.ebean.config.dbplatform.DbPlatformType; +import io.ebean.config.dbplatform.ExtraDbTypes; +import io.ebean.core.type.DataBinder; +import io.ebean.core.type.ScalarType; + +import java.sql.SQLException; + +final class ScalarTypeJsonString { + + static final Postgres JSONB = new Postgres(ExtraDbTypes.JSONB, PostgresHelper.JSONB_TYPE); + static final Postgres JSON = new Postgres(ExtraDbTypes.JSON, PostgresHelper.JSON_TYPE); + + static ScalarType typeFor(boolean postgres, int dbType) { + if (postgres) { + switch (dbType) { + case DbPlatformType.JSONB: + return JSONB; + case DbPlatformType.JSON: + return JSON; + } + } + return ScalarTypeString.INSTANCE; + } + + private static class Postgres extends ScalarTypeStringBase { + final String postgresType; + + Postgres(int jdbcType, String postgresType) { + super(true, jdbcType); + this.postgresType = postgresType; + } + + @Override + public void bind(DataBinder binder, String rawJson) throws SQLException { + binder.setObject(PostgresHelper.asObject(postgresType, rawJson)); + } + } + +} diff --git a/ebean-test/pom.xml b/ebean-test/pom.xml index 4fb24555a..7ea9d8632 100644 --- a/ebean-test/pom.xml +++ b/ebean-test/pom.xml @@ -163,7 +163,8 @@ org.postgresql postgresql - 42.3.3 + + 42.2.9 org.checkerframework diff --git a/ebean-test/src/test/java/main/StartPostgres.java b/ebean-test/src/test/java/main/StartPostgres.java index b65aea1f2..0356ff25c 100644 --- a/ebean-test/src/test/java/main/StartPostgres.java +++ b/ebean-test/src/test/java/main/StartPostgres.java @@ -5,12 +5,12 @@ import io.ebean.test.containers.PostgresContainer; public class StartPostgres { public static void main(String[] args) { - PostgresContainer.builder("13") - .port(5432) + PostgresContainer.builder("14") .dbName("unit") - .user("unit") - .password("unit") - .containerName("pg13x") + //.port(6432) + //.user("unit") + //.password("test") + //.containerName("ut_postgres") .extensions("hstore,pgcrypto") .build() .startWithDropCreate(); diff --git a/ebean-test/src/test/java/org/tests/json/TestDbJsonBStringType.java b/ebean-test/src/test/java/org/tests/json/TestDbJsonBStringType.java new file mode 100644 index 000000000..f26235d68 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/json/TestDbJsonBStringType.java @@ -0,0 +1,47 @@ +package org.tests.json; + +import io.ebean.DB; +import io.ebean.test.LoggedSql; +import org.junit.jupiter.api.Test; +import org.tests.model.json.EBasicJsonBString; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestDbJsonBStringType { + + @Test + void test() { + var bean = new EBasicJsonBString("hi").content("{\"mykey\": 42}"); // JsonB format with space + DB.save(bean); + + var found = DB.find(EBasicJsonBString.class, bean.id()); + // Note that Postgres JsonB will format the result + assertThat(found.content()).isEqualTo("{\"mykey\": 42}"); + + LoggedSql.start(); + + // change title only, expect content not in update + found.title("changeTitleOnly"); + DB.save(found); + List sql = LoggedSql.collect(); + // update does NOT contain our json content + assertThat(sql.get(0)).contains("update ebasic_json_bstring set title=?, version=? where id=? and version=?"); + + // change title and content + found.title("changeAgain"); + found.content("{\"mykey\": 92}"); + DB.save(found); + sql = LoggedSql.collect(); + assertThat(sql.get(0)).contains("update ebasic_json_bstring set title=?, content=?, version=? where id=? and version=?"); + + + // change content only + found.content("{\"mykey\": 95}"); + DB.save(found); + sql = LoggedSql.stop(); + assertThat(sql.get(0)).contains("update ebasic_json_bstring set content=?, version=? where id=? and version=?"); + + } +} diff --git a/ebean-test/src/test/java/org/tests/json/TestDbJsonStringType.java b/ebean-test/src/test/java/org/tests/json/TestDbJsonStringType.java new file mode 100644 index 000000000..307b0e561 --- /dev/null +++ b/ebean-test/src/test/java/org/tests/json/TestDbJsonStringType.java @@ -0,0 +1,47 @@ +package org.tests.json; + +import io.ebean.DB; +import io.ebean.test.LoggedSql; +import org.junit.jupiter.api.Test; +import org.tests.model.json.EBasicJsonString; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +class TestDbJsonStringType { + + @Test + void test() { + var bean = new EBasicJsonString("hi").content("{\"mykey\": 52}"); // JsonB format with space + DB.save(bean); + + var found = DB.find(EBasicJsonString.class, bean.id()); + // Note that Postgres JsonB will format the result + assertThat(found.content()).isEqualTo("{\"mykey\": 52}"); + + LoggedSql.start(); + + // change title only, expect content not in update + found.title("changeTitleOnly"); + DB.save(found); + List sql = LoggedSql.collect(); + // update does NOT contain our json content + assertThat(sql.get(0)).contains("update ebasic_json_string set title=?, version=? where id=? and version=?"); + + // change title and content + found.title("changeAgain"); + found.content("{\"mykey\": 92}"); + DB.save(found); + sql = LoggedSql.collect(); + assertThat(sql.get(0)).contains("update ebasic_json_string set title=?, content=?, version=? where id=? and version=?"); + + + // change content only + found.content("{\"mykey\": 95}"); + DB.save(found); + sql = LoggedSql.stop(); + assertThat(sql.get(0)).contains("update ebasic_json_string set content=?, version=? where id=? and version=?"); + + } +} diff --git a/ebean-test/src/test/java/org/tests/model/json/EBasicJsonBString.java b/ebean-test/src/test/java/org/tests/model/json/EBasicJsonBString.java new file mode 100644 index 000000000..ee4617c0e --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/json/EBasicJsonBString.java @@ -0,0 +1,62 @@ +package org.tests.model.json; + +import io.ebean.annotation.DbJsonB; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +@Entity +public class EBasicJsonBString { + + @Id + long id; + + String title; + + @DbJsonB + String content; + + @Version + long version; + + public EBasicJsonBString(String title) { + this.title = title; + } + + public long id() { + return id; + } + + public EBasicJsonBString id(long id) { + this.id = id; + return this; + } + + public String title() { + return title; + } + + public EBasicJsonBString title(String title) { + this.title = title; + return this; + } + + public String content() { + return content; + } + + public EBasicJsonBString content(String content) { + this.content = content; + return this; + } + + public long version() { + return version; + } + + public EBasicJsonBString version(long version) { + this.version = version; + return this; + } +} diff --git a/ebean-test/src/test/java/org/tests/model/json/EBasicJsonString.java b/ebean-test/src/test/java/org/tests/model/json/EBasicJsonString.java new file mode 100644 index 000000000..45af516dc --- /dev/null +++ b/ebean-test/src/test/java/org/tests/model/json/EBasicJsonString.java @@ -0,0 +1,62 @@ +package org.tests.model.json; + +import io.ebean.annotation.DbJson; + +import javax.persistence.Entity; +import javax.persistence.Id; +import javax.persistence.Version; + +@Entity +public class EBasicJsonString { + + @Id + long id; + + String title; + + @DbJson + String content; + + @Version + long version; + + public EBasicJsonString(String title) { + this.title = title; + } + + public long id() { + return id; + } + + public EBasicJsonString id(long id) { + this.id = id; + return this; + } + + public String title() { + return title; + } + + public EBasicJsonString title(String title) { + this.title = title; + return this; + } + + public String content() { + return content; + } + + public EBasicJsonString content(String content) { + this.content = content; + return this; + } + + public long version() { + return version; + } + + public EBasicJsonString version(long version) { + this.version = version; + return this; + } +} diff --git a/ebean-test/src/test/resources/ebean.properties b/ebean-test/src/test/resources/ebean.properties index 91c0d0c29..326ef0d42 100644 --- a/ebean-test/src/test/resources/ebean.properties +++ b/ebean-test/src/test/resources/ebean.properties @@ -22,7 +22,7 @@ ebean.ddl.generate=true ebean.ddl.run=true ebean.ddl.header=-- Generated by ebean ${version} at ${timestamp} ebean.packages=org.tests,org.etest -datasource.default=h2 +datasource.default=pg #datasource.default=sqlserver #datasource.h2.capturestacktrace=true diff --git a/ebean-test/src/test/resources/logback-test.xml b/ebean-test/src/test/resources/logback-test.xml index e95579b21..99d10dab3 100644 --- a/ebean-test/src/test/resources/logback-test.xml +++ b/ebean-test/src/test/resources/logback-test.xml @@ -80,9 +80,9 @@ - - - + + +