From 2c95d9dc9a6d64b0f1a8da0b3bdaf6df67921bed Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Thu, 5 May 2016 16:36:16 +1200
Subject: [PATCH] #693 - ENH: Add support for mapping @DbJson to list of scalar
types like List
---
.../com/avaje/ebean/annotation/DbJsonB.java | 4 +
.../avaje/ebean/config/dbplatform/DbType.java | 16 +-
.../ebean/config/dbplatform/DbTypeMap.java | 17 ++-
.../config/dbplatform/PostgresPlatform.java | 6 +-
.../platform/util/PlatformTypeConverter.java | 4 +-
.../model/build/ModelBuildContext.java | 7 +-
.../build/ModelBuildIntersectionTable.java | 2 +-
.../build/ModelBuildPropertyVisitor.java | 6 +-
.../java/com/avaje/ebean/text/json/EJson.java | 21 ++-
.../avaje/ebean/text/json/EJsonReader.java | 15 +-
.../server/deploy/BeanProperty.java | 4 +-
.../server/deploy/parse/AnnotationFields.java | 12 +-
.../deploy/parse/DeployCreateProperties.java | 9 +-
.../server/deploy/parse/DeployUtil.java | 16 +-
.../server/type/DefaultTypeManager.java | 18 ++-
.../server/type/ModifyAwareList.java | 4 +
.../server/type/PostgresHelper.java | 29 ++++
.../server/type/ScalarTypeJsonCollection.java | 75 ++++++++++
.../server/type/ScalarTypeJsonList.java | 137 ++++++++++++++++++
.../server/type/ScalarTypeJsonMap.java | 4 +-
.../type/ScalarTypeJsonMapPostgres.java | 17 +--
.../server/type/ScalarTypeJsonNode.java | 4 +-
.../type/ScalarTypeJsonNodePostgres.java | 18 +--
.../server/type/TypeManager.java | 2 +-
.../config/dbplatform/DbTypeMapTest.java | 18 +--
.../java/com/avaje/ebean/json/EJsonTests.java | 14 ++
.../cache/CachedBeanDataSerializeTest.java | 2 +-
.../com/avaje/tests/json/TestDbJson_List.java | 78 ++++++++++
.../tests/model/json/EBasicJsonList.java | 57 ++++++++
src/test/resources/logback-test.xml | 6 +-
30 files changed, 524 insertions(+), 98 deletions(-)
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/PostgresHelper.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java
create mode 100644 src/test/java/com/avaje/tests/json/TestDbJson_List.java
create mode 100644 src/test/java/com/avaje/tests/model/json/EBasicJsonList.java
diff --git a/src/main/java/com/avaje/ebean/annotation/DbJsonB.java b/src/main/java/com/avaje/ebean/annotation/DbJsonB.java
index 1982c8db7..ebfaf9270 100644
--- a/src/main/java/com/avaje/ebean/annotation/DbJsonB.java
+++ b/src/main/java/com/avaje/ebean/annotation/DbJsonB.java
@@ -36,4 +36,8 @@ import java.lang.annotation.Target;
@Target(ElementType.FIELD)
public @interface DbJsonB {
+ /**
+ * For VARCHAR storage specify the column length.
+ */
+ int length() default 0;
}
\ No newline at end of file
diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbType.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbType.java
index b2b7080aa..4c51be3de 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/DbType.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbType.java
@@ -117,14 +117,26 @@ public class DbType {
* the scale defined by deployment on a specific property.
*/
public String renderType(int deployLength, int deployScale) {
+ return renderType(deployLength, deployScale, true);
+ }
+
+ /**
+ * Render the type defining strict mode.
+ *
+ * If strict mode if OFF then this will render with a scale value even if
+ * that is not strictly supported. The reason for supporting this is to enable
+ * use to use types like jsonb(200) as a "logical" type that maps to JSONB for
+ * Postgres and VARCHAR(200) for other databases.
+ *
+ */
+ public String renderType(int deployLength, int deployScale, boolean strict) {
StringBuilder sb = new StringBuilder();
sb.append(name);
- if (canHaveLength) {
+ if (canHaveLength || !strict) {
// see if there is a precision/scale to add (or not)
int len = deployLength != 0 ? deployLength : defaultLength;
-
if (len > 0) {
sb.append("(");
sb.append(len);
diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/DbTypeMap.java b/src/main/java/com/avaje/ebean/config/dbplatform/DbTypeMap.java
index ab15a30e1..9dbe17b2e 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/DbTypeMap.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbTypeMap.java
@@ -108,9 +108,9 @@ public class DbTypeMap {
if (logicalTypes) {
// keep it logical for 2 layer DDL generation
- put(DbType.HSTORE, new DbType("hstore"));
- put(DbType.JSON, new DbType("json"));
- put(DbType.JSONB, new DbType("jsonb"));
+ put(DbType.HSTORE, new DbType("hstore", false));
+ put(DbType.JSON, new DbType("json", false));
+ put(DbType.JSONB, new DbType("jsonb", false));
put(DbType.JSONClob, new DbType("jsonclob"));
put(DbType.JSONBlob, new DbType("jsonblob"));
put(DbType.JSONVarchar, new DbType("jsonvarchar", 1000));
@@ -137,7 +137,7 @@ public class DbTypeMap {
/**
* Lookup the platform specific DbType given the standard sql type name.
*/
- public DbType lookup(String name) {
+ public DbType lookup(String name, boolean withScale) {
name = name.trim().toUpperCase();
Integer typeKey = lookup.get(name);
if (typeKey == null) {
@@ -152,18 +152,19 @@ public class DbTypeMap {
case DbType.JSONVarchar:
return get(Types.VARCHAR);
case DbType.JSON:
- return getJsonType(DbType.JSON);
+ return getJsonType(DbType.JSON, withScale);
case DbType.JSONB:
- return getJsonType(DbType.JSONB);
+ return getJsonType(DbType.JSONB, withScale);
default:
return get(typeKey);
}
}
- private DbType getJsonType(int type) {
+ private DbType getJsonType(int type, boolean withScale) {
DbType dbType = get(type);
if (dbType == JSON_CLOB_PLACEHOLDER) {
- return get(Types.CLOB);
+ // if we have scale that implies this maps to varchar
+ return withScale ? get(Types.VARCHAR) : get(Types.CLOB);
}
if (dbType == JSON_BLOB_PLACEHOLDER) {
return get(Types.BLOB);
diff --git a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java
index 090a3c7b0..17a537988 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/PostgresPlatform.java
@@ -43,9 +43,9 @@ public class PostgresPlatform extends DatabasePlatform {
DbType dbTypeText = new DbType("text");
DbType dbBytea = new DbType("bytea", false);
- dbTypeMap.put(DbType.HSTORE, new DbType("hstore"));
- dbTypeMap.put(DbType.JSON, new DbType("json"));
- dbTypeMap.put(DbType.JSONB, new DbType("jsonb"));
+ dbTypeMap.put(DbType.HSTORE, new DbType("hstore", false));
+ dbTypeMap.put(DbType.JSON, new DbType("json", false));
+ dbTypeMap.put(DbType.JSONB, new DbType("jsonb", false));
dbTypeMap.put(Types.INTEGER, new DbType("integer", false));
dbTypeMap.put(Types.DOUBLE, new DbType("float"));
diff --git a/src/main/java/com/avaje/ebean/dbmigration/ddlgeneration/platform/util/PlatformTypeConverter.java b/src/main/java/com/avaje/ebean/dbmigration/ddlgeneration/platform/util/PlatformTypeConverter.java
index d41ba3611..53fe5f23b 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/ddlgeneration/platform/util/PlatformTypeConverter.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/ddlgeneration/platform/util/PlatformTypeConverter.java
@@ -47,7 +47,7 @@ public class PlatformTypeConverter {
String suffix = close + 1 < columnDefinition.length() ? columnDefinition.substring(close + 1) : "";
String type = columnDefinition.substring(0, open);
try {
- DbType dbType = platformTypes.lookup(type);
+ DbType dbType = platformTypes.lookup(type, true);
int comma = columnDefinition.indexOf(',', open);
if (comma > -1) {
// scale and precision - decimal(10,4)
@@ -73,7 +73,7 @@ public class PlatformTypeConverter {
protected String convertNoScale(String columnDefinition) {
try {
- DbType dbType = platformTypes.lookup(columnDefinition);
+ DbType dbType = platformTypes.lookup(columnDefinition, false);
return dbType.renderType(0, 0);
} catch (IllegalArgumentException e) {
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java
index 52ac6c998..dc1c3cf49 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildContext.java
@@ -108,12 +108,15 @@ public class ModelBuildContext {
}
- public String getColumnDefn(BeanProperty p) {
+ /**
+ * Render the DB type for this property given the strict mode.
+ */
+ public String getColumnDefn(BeanProperty p, boolean strict) {
DbType dbType = getDbType(p);
if (dbType == null) {
throw new IllegalStateException("Unknown DbType mapping for " + p.getFullBeanName());
}
- return p.renderDbType(dbType);
+ return p.renderDbType(dbType, strict);
}
private DbType getDbType(BeanProperty p) {
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java
index d895bcbb4..d85d1ca51 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildIntersectionTable.java
@@ -107,7 +107,7 @@ public class ModelBuildIntersectionTable {
throw new RuntimeException("Could not find id property for " + findPropColumn);
}
- MColumn col = new MColumn(column, ctx.getColumnDefn(p), true);
+ MColumn col = new MColumn(column, ctx.getColumnDefn(p, true), true);
col.setPrimaryKey(true);
table.addColumn(col);
}
diff --git a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java
index 3ed4c2bac..a2f694385 100644
--- a/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java
+++ b/src/main/java/com/avaje/ebean/dbmigration/model/build/ModelBuildPropertyVisitor.java
@@ -184,7 +184,7 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
if (importedProperty == null) {
throw new RuntimeException("Imported BeanProperty not found?");
}
- String columnDefn = ctx.getColumnDefn(importedProperty);
+ String columnDefn = ctx.getColumnDefn(importedProperty, true);
String refColumn = importedProperty.getDbColumn();
MColumn col = table.addColumn(dbCol, columnDefn, !p.isNullable());
@@ -229,7 +229,9 @@ public class ModelBuildPropertyVisitor extends BaseTablePropertyVisitor {
return;
}
- MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p));
+ // using non-strict mode to render the DB type such that we have a
+ // "logical" type like jsonb(200) that can map to JSONB or VARCHAR(200)
+ MColumn col = new MColumn(p.getDbColumn(), ctx.getColumnDefn(p, false));
col.setDefaultValue(p.getDbColumnDefault());
col.setComment(p.getDbComment());
col.setDraftOnly(p.isDraftOnly());
diff --git a/src/main/java/com/avaje/ebean/text/json/EJson.java b/src/main/java/com/avaje/ebean/text/json/EJson.java
index bc38e30d4..3090f0bf4 100644
--- a/src/main/java/com/avaje/ebean/text/json/EJson.java
+++ b/src/main/java/com/avaje/ebean/text/json/EJson.java
@@ -90,9 +90,15 @@ public class EJson {
return EJsonReader.parseObject(parser, token);
}
+ /**
+ * Parse the json and return as a modify aware List.
+ */
+ public static List