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 parseList(String json, boolean modifyAware) throws IOException {
+ return EJsonReader.parseList(json, modifyAware);
+ }
+
/**
* Parse the json and return as a List.
- * @throws IOException
*/
public static List parseList(String json) throws IOException {
return EJsonReader.parseList(json);
@@ -100,7 +106,6 @@ public class EJson {
/**
* Parse the json and return as a List taking a Reader.
- * @throws IOException
*/
public static List parseList(Reader reader) throws IOException {
return EJsonReader.parseList(reader);
@@ -110,10 +115,16 @@ public class EJson {
* Parse the json and return as a List taking a JsonParser.
*/
public static List parseList(JsonParser parser) throws IOException {
- return EJsonReader.parseList(parser);
+ return EJsonReader.parseList(parser, false);
}
-
-
+
+ /**
+ * Parse the json returning as a List taking into account the current token.
+ */
+ public static List parseList(JsonParser parser, JsonToken currentToken) throws IOException {
+ return (List)EJsonReader.parse(parser, currentToken, false);
+ }
+
/**
* Parse the json and return as a List or Map.
*/
diff --git a/src/main/java/com/avaje/ebean/text/json/EJsonReader.java b/src/main/java/com/avaje/ebean/text/json/EJsonReader.java
index 15badf35b..dba25227c 100644
--- a/src/main/java/com/avaje/ebean/text/json/EJsonReader.java
+++ b/src/main/java/com/avaje/ebean/text/json/EJsonReader.java
@@ -51,6 +51,11 @@ class EJsonReader {
return (Map)parse(parser, token, false);
}
+ @SuppressWarnings("unchecked")
+ static List parseList(String json, boolean modifyAware) throws IOException {
+ return (List) parse(json, modifyAware);
+ }
+
@SuppressWarnings("unchecked")
static List parseList(String json) throws IOException {
return (List) parse(json);
@@ -62,15 +67,21 @@ class EJsonReader {
}
@SuppressWarnings("unchecked")
- static List parseList(JsonParser parser) throws IOException {
- return (List) parse(parser);
+ static List parseList(JsonParser parser, boolean modifyAware) throws IOException {
+ return (List) parse(parser, modifyAware);
}
static Object parse(String json) throws IOException {
+ if (json == null) {
+ return null;
+ }
return parse(new StringReader(json));
}
static Object parse(String json, boolean modifyAware) throws IOException {
+ if (json == null) {
+ return null;
+ }
return parse(new StringReader(json), modifyAware);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
index 06091ed55..04aa3ec51 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
@@ -984,11 +984,11 @@ public class BeanProperty implements ElPropertyValue, Property {
/**
* Return the DB column type definition.
*/
- public String renderDbType(DbType dbType) {
+ public String renderDbType(DbType dbType, boolean strict) {
if (dbColumnDefn != null) {
return dbColumnDefn;
}
- return dbType.renderType(dbLength, dbScale);
+ return dbType.renderType(dbLength, dbScale, strict);
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
index 978fa0694..3d7a8cfd5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/AnnotationFields.java
@@ -175,12 +175,16 @@ public class AnnotationFields extends AnnotationParser {
if (comment != null) {
prop.setDbComment(comment.value());
}
+ if (get(prop, DbHstore.class) != null) {
+ util.setDbHstore(prop);
+ }
DbJson dbJson = get(prop, DbJson.class);
if (dbJson != null) {
util.setDbJsonType(prop, dbJson);
} else {
- if (get(prop, DbJsonB.class) != null) {
- util.setDbJsonBType(prop);
+ DbJsonB dbJsonB = get(prop, DbJsonB.class);
+ if (dbJsonB != null) {
+ util.setDbJsonBType(prop, dbJsonB);
}
}
@@ -197,10 +201,6 @@ public class AnnotationFields extends AnnotationParser {
prop.setDocProperty(docProperty);
}
- if (get(prop, DbHstore.class) != null) {
- util.setDbHstore(prop);
- }
-
Formula formula = get(prop, Formula.class);
if (formula != null) {
prop.setSqlFormula(formula.select(), formula.join());
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java
index c4961e248..071c57fbb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployCreateProperties.java
@@ -223,21 +223,18 @@ public class DeployCreateProperties {
Class> propertyType = field.getType();
ManyToOne manyToOne = field.getAnnotation(ManyToOne.class);
-
if (manyToOne != null){
Class> tt = manyToOne.targetEntity();
-
- if (tt != null && !tt.equals(void.class)){
+ if (!tt.equals(void.class)){
propertyType = tt;
}
}
- if (isMappedType(field)) {
+ if (isSpecialScalarType(field)) {
return new DeployBeanProperty(desc, propertyType, null, null);
}
// check for Collection type (list, set or map)
ManyType manyType = determineManyType.getManyType(propertyType);
-
if (manyType != null) {
// List, Set or Map based object
Class> targetType = determineTargetType(field);
@@ -299,7 +296,7 @@ public class DeployCreateProperties {
/**
* Return true if the field has one of the special mappings.
*/
- private boolean isMappedType(Field field) {
+ private boolean isSpecialScalarType(Field field) {
return (field.getAnnotation(DbJson.class) != null)
|| (field.getAnnotation(DbJsonB.class) != null)
|| (field.getAnnotation(DbHstore.class) != null);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java
index 01b89783a..52124a5f6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/parse/DeployUtil.java
@@ -6,6 +6,7 @@ import javax.persistence.Enumerated;
import javax.persistence.PersistenceException;
import com.avaje.ebean.annotation.DbJson;
+import com.avaje.ebean.annotation.DbJsonB;
import com.avaje.ebean.annotation.DbJsonType;
import com.avaje.ebean.config.EncryptDeploy;
import com.avaje.ebean.config.EncryptDeployManager;
@@ -103,6 +104,7 @@ public class DeployUtil {
return new DataEncryptSupport(encryptKeyManager, bytesEncryptor, table, column);
}
+ @SuppressWarnings("unchecked")
public void setEnumScalarType(Enumerated enumerated, DeployBeanProperty prop) {
Class> enumType = prop.getPropertyType();
@@ -200,11 +202,9 @@ public class DeployUtil {
* Map to Postgres HSTORE type.
*/
public void setDbHstore(DeployBeanProperty prop) {
-
ScalarType> scalarType = typeManager.getScalarType(DbType.HSTORE);
if (scalarType == null) {
- // this should never occur actually
- throw new RuntimeException("No ScalarType found for HSTORE on [" + prop.getFullBeanName() + "]");
+ throw new RuntimeException("No ScalarType found for HSTORE on [" + prop.getFullBeanName() + "] ?");
}
prop.setDbType(DbType.HSTORE);
prop.setScalarType(scalarType);
@@ -219,22 +219,20 @@ public class DeployUtil {
setDbJsonType(prop, dbType, dbJsonType.length());
}
- public void setDbJsonBType(DeployBeanProperty prop) {
- setDbJsonType(prop, DbType.JSONB, 0);
+ public void setDbJsonBType(DeployBeanProperty prop, DbJsonB dbJsonB) {
+ setDbJsonType(prop, DbType.JSONB, dbJsonB.length());
}
private void setDbJsonType(DeployBeanProperty prop, int dbType, int dbLength) {
Class> type = prop.getPropertyType();
-
- ScalarType> scalarType = typeManager.getJsonScalarType(type, dbType);
+ ScalarType> scalarType = typeManager.getJsonScalarType(type, dbType, dbLength);
if (scalarType == null) {
- // this should never occur actually
throw new RuntimeException("No ScalarType for JSON type [" + type + "] [" + dbType + "]");
}
prop.setDbType(dbType);
prop.setScalarType(scalarType);
- if (dbType == Types.VARCHAR) {
+ if (dbType == Types.VARCHAR || dbLength > 0) {
// determine the db column size
int columnLength = (dbLength > 0) ? dbLength : DEFAULT_JSON_VARCHAR_LENGTH;
prop.setDbLength(columnLength);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
index 3f063c9db..44c116fe9 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
@@ -155,6 +155,8 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
private final boolean java7Present;
+ private final boolean postgres;
+
// OPTIONAL ScalarTypes registered if Jackson/JsonNode is in the classpath
/**
@@ -195,14 +197,14 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
this.objectMapperPresent = config.getClassLoadConfig().isJacksonObjectMapperPresent();
this.extraTypeFactory = new DefaultTypeFactory(config);
+ this.postgres = isPostgres(config.getDatabasePlatform());
initialiseStandard(jsonDateTime, config);
initialiseJavaTimeTypes(jsonDateTime, config);
initialiseJodaTypes(jsonDateTime, config);
initialiseJacksonTypes(config);
- if (isPostgres(config.getDatabasePlatform())) {
- // Postgres has special DB types for JSON/JSONB
+ if (postgres) {
this.jsonMapJson = new ScalarTypeJsonMapPostgres.JSON();
this.jsonMapJsonb = new ScalarTypeJsonMapPostgres.JSONB();
} else {
@@ -368,7 +370,17 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
}
@Override
- public ScalarType> getJsonScalarType(Class> type, int dbType) {
+ public ScalarType> getJsonScalarType(Class> type, int dbType, int dbLength) {
+
+ if (type.equals(List.class)) {
+ if (postgres) {
+ switch (dbType) {
+ case DbType.JSONB: return ScalarTypeJsonList.JSONB;
+ case DbType.JSON: return ScalarTypeJsonList.JSON;
+ }
+ }
+ return ScalarTypeJsonList.VARCHAR;
+ }
if (type.equals(Map.class)) {
// @DbJson Map property
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareList.java b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareList.java
index 646116ddc..10b35fcbd 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareList.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ModifyAwareList.java
@@ -24,6 +24,10 @@ public class ModifyAwareList implements List, ModifyAwareOwner {
this.owner = owner;
}
+ public String toString() {
+ return list.toString();
+ }
+
@Override
public boolean isMarkedDirty() {
return owner.isMarkedDirty();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/PostgresHelper.java b/src/main/java/com/avaje/ebeaninternal/server/type/PostgresHelper.java
new file mode 100644
index 000000000..e1d53bf09
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/PostgresHelper.java
@@ -0,0 +1,29 @@
+package com.avaje.ebeaninternal.server.type;
+
+import org.postgresql.util.PGobject;
+
+import java.sql.SQLException;
+
+public class PostgresHelper {
+
+ /**
+ * The Postgres JSON DB type.
+ */
+ public static final String JSON_TYPE = "json";
+
+ /**
+ * The Postgres JSONB DB type.
+ */
+ public static final String JSONB_TYPE = "jsonb";
+
+ /**
+ * Construct and return Postgres specific PG object.
+ */
+ public static Object asObject(String pgType, String rawJson) throws SQLException {
+
+ PGobject pgo = new PGobject();
+ pgo.setType(pgType);
+ pgo.setValue(rawJson);
+ return pgo;
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java
new file mode 100644
index 000000000..d37442385
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java
@@ -0,0 +1,75 @@
+package com.avaje.ebeaninternal.server.type;
+
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+
+abstract class ScalarTypeJsonCollection extends ScalarTypeBase {
+
+ public ScalarTypeJsonCollection(Class type, int dbType) {
+ super(type, false, dbType);
+ }
+
+ /**
+ * Consider as a mutable type. Use the isDirty() method to check for dirty state.
+ */
+ @Override
+ public boolean isMutable() {
+ return true;
+ }
+
+ /**
+ * Return true if the value should be considered dirty (and included in an update).
+ */
+ @Override
+ public boolean isDirty(Object value) {
+ return !(value instanceof ModifyAwareOwner) || ((ModifyAwareOwner) value).isMarkedDirty();
+ }
+
+ @Override
+ public Object toJdbcType(Object value) {
+ return value;
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ public T toBeanType(Object value) {
+ return (T)value;
+ }
+
+ @Override
+ public DocPropertyType getDocType() {
+ return DocPropertyType.LIST;
+ }
+
+ @Override
+ public boolean isDateTimeCapable() {
+ return false;
+ }
+
+ @Override
+ public T convertFromMillis(long dateTime) {
+ return null;
+ }
+
+ @Override
+ public T readData(DataInput dataInput) throws IOException {
+ if (!dataInput.readBoolean()) {
+ return null;
+ } else {
+ return parse(dataInput.readUTF());
+ }
+ }
+
+ @Override
+ public void writeData(DataOutput dataOutput, T value) throws IOException {
+ if (value == null) {
+ dataOutput.writeBoolean(false);
+ } else {
+ ScalarHelp.writeUTF(dataOutput, format(value));
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java
new file mode 100644
index 000000000..6af41ce8e
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java
@@ -0,0 +1,137 @@
+package com.avaje.ebeaninternal.server.type;
+
+import com.avaje.ebean.config.dbplatform.DbType;
+import com.avaje.ebean.text.json.EJson;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+
+import javax.persistence.PersistenceException;
+import java.io.IOException;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.List;
+
+/**
+ * Types for mapping List in JSON format to DB types VARCHAR, JSON and JSONB.
+ */
+public class ScalarTypeJsonList {
+
+ public static final ScalarTypeJsonCollection VARCHAR = new ScalarTypeJsonList.Varchar();
+
+ public static final ScalarTypeJsonCollection JSON = new ScalarTypeJsonList.Json();
+
+ public static final ScalarTypeJsonCollection JSONB = new ScalarTypeJsonList.JsonB();
+
+ /**
+ * List mapped to DB VARCHAR.
+ */
+ private static class Varchar extends ScalarTypeJsonList.Base {
+ public Varchar() {
+ super(Types.VARCHAR);
+ }
+ }
+
+ /**
+ * List mapped to Postgres JSON.
+ */
+ private static class Json extends ScalarTypeJsonList.PgBase {
+ public Json() {
+ super(DbType.JSON, PostgresHelper.JSON_TYPE);
+ }
+ }
+
+ /**
+ * List mapped to Postgres JSONB.
+ */
+ private static class JsonB extends ScalarTypeJsonList.PgBase {
+ public JsonB() {
+ super(DbType.JSONB, PostgresHelper.JSONB_TYPE);
+ }
+ }
+
+ /**
+ * Base class for List handling.
+ */
+ private abstract static class Base extends ScalarTypeJsonCollection {
+
+ public Base(int dbType) {
+ super(List.class, dbType);
+ }
+
+ @Override
+ public List read(DataReader dataReader) throws SQLException {
+ try {
+ // parse JSON into modifyAware list
+ return EJson.parseList(dataReader.getString(), true);
+ } catch (IOException e) {
+ throw new SQLException("Failed to parse JSON content as List: ["+ dataReader.getString() +"]", e);
+ }
+ }
+
+ @Override
+ public void bind(DataBind b, List value) throws SQLException {
+
+ if (value == null) {
+ b.setNull(Types.VARCHAR);
+ } else if (value.isEmpty()) {
+ b.setString("[]");
+ } else {
+ try {
+ b.setString(EJson.write(value));
+ } catch (IOException e) {
+ throw new SQLException("Failed to format List into JSON content", e);
+ }
+ }
+ }
+
+ @Override
+ public String formatValue(List value) {
+ try {
+ return EJson.write(value);
+ } catch (IOException e) {
+ throw new PersistenceException("Failed to format List into JSON content", e);
+ }
+ }
+
+ @Override
+ public List parse(String value) {
+ try {
+ return EJson.parseList(value, false);
+ } catch (IOException e) {
+ throw new PersistenceException("Failed to parse JSON content as List: ["+value+"]", e);
+ }
+ }
+
+ @Override
+ public List jsonRead(JsonParser parser) throws IOException {
+ return EJson.parseList(parser, parser.getCurrentToken());
+ }
+
+ @Override
+ public void jsonWrite(JsonGenerator writer, List value) throws IOException {
+ EJson.write(value, writer);
+ }
+
+ }
+
+ /**
+ * Postgres extension to base List handling.
+ */
+ private static class PgBase extends ScalarTypeJsonList.Base {
+
+ final String pgType;
+
+ PgBase(int jdbcType, String pgType) {
+ super(jdbcType);
+ this.pgType = pgType;
+ }
+
+ @Override
+ public void bind(DataBind bind, List value) throws SQLException {
+
+ String rawJson = (value == null) ? null : formatValue(value);
+ bind.setObject(PostgresHelper.asObject(pgType, rawJson));
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java
index e24811032..753bdd8b2 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMap.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.text.TextException;
import com.avaje.ebean.text.json.EJson;
+import com.avaje.ebeaninternal.util.EncodeUtil;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -12,7 +13,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
-import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.sql.Types;
import java.util.Map;
@@ -77,7 +77,7 @@ public abstract class ScalarTypeJsonMap extends ScalarTypeBase {
b.setNull(Types.BLOB);
} else {
String rawJson = formatValue(value);
- b.setBytes(rawJson.getBytes(StandardCharsets.UTF_8));
+ b.setBytes(EncodeUtil.utf8ToBytes(rawJson));
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java
index 388826545..cd51a3fff 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonMapPostgres.java
@@ -1,7 +1,6 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
-import org.postgresql.util.PGobject;
import java.sql.SQLException;
import java.util.Map;
@@ -11,10 +10,6 @@ import java.util.Map;
*/
public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap {
- private static final String POSTGRES_TYPE_JSON = "json";
-
- private static final String POSTGRES_TYPE_JSONB = "jsonb";
-
final String postgresType;
ScalarTypeJsonMapPostgres(int jdbcType, String postgresType) {
@@ -23,14 +18,10 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap {
}
@Override
- public void bind(DataBind b, Map value) throws SQLException {
+ public void bind(DataBind bind, Map value) throws SQLException {
String rawJson = (value == null) ? null : formatValue(value);
-
- PGobject pgo = new PGobject();
- pgo.setType(postgresType);
- pgo.setValue(rawJson);
- b.setObject(pgo);
+ bind.setObject(PostgresHelper.asObject(postgresType, rawJson));
}
/**
@@ -39,7 +30,7 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap {
public static class JSON extends ScalarTypeJsonMapPostgres {
public JSON() {
- super(DbType.JSON, POSTGRES_TYPE_JSON);
+ super(DbType.JSON, PostgresHelper.JSON_TYPE);
}
}
@@ -49,7 +40,7 @@ public abstract class ScalarTypeJsonMapPostgres extends ScalarTypeJsonMap {
public static class JSONB extends ScalarTypeJsonMapPostgres {
public JSONB() {
- super(DbType.JSONB, POSTGRES_TYPE_JSONB);
+ super(DbType.JSONB, PostgresHelper.JSONB_TYPE);
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java
index 16c3447b2..70abd5ad5 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNode.java
@@ -1,6 +1,7 @@
package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.text.TextException;
+import com.avaje.ebeaninternal.util.EncodeUtil;
import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -13,7 +14,6 @@ import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
-import java.nio.charset.StandardCharsets;
import java.sql.SQLException;
import java.sql.Types;
@@ -85,7 +85,7 @@ public abstract class ScalarTypeJsonNode extends ScalarTypeBase {
dataBind.setNull(Types.BLOB);
} else {
String rawJson = formatValue(value);
- dataBind.setBlob(rawJson.getBytes(StandardCharsets.UTF_8));
+ dataBind.setBlob(EncodeUtil.utf8ToBytes(rawJson));
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNodePostgres.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNodePostgres.java
index ae12e8fe4..ae01ee6b8 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNodePostgres.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonNodePostgres.java
@@ -3,7 +3,6 @@ package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
-import org.postgresql.util.PGobject;
import java.sql.SQLException;
@@ -12,10 +11,6 @@ import java.sql.SQLException;
*/
public abstract class ScalarTypeJsonNodePostgres extends ScalarTypeJsonNode {
- private static final String POSTGRES_TYPE_JSON = "json";
-
- private static final String POSTGRES_TYPE_JSONB = "jsonb";
-
final ObjectMapper objectMapper;
final String postgresType;
@@ -27,14 +22,9 @@ public abstract class ScalarTypeJsonNodePostgres extends ScalarTypeJsonNode {
}
@Override
- public void bind(DataBind dataBind, JsonNode value) throws SQLException {
-
+ public void bind(DataBind bind, JsonNode value) throws SQLException {
String rawJson = (value == null) ? null : formatValue(value);
-
- PGobject pgo = new PGobject();
- pgo.setType(postgresType);
- pgo.setValue(rawJson);
- dataBind.setObject(pgo);
+ bind.setObject(PostgresHelper.asObject(postgresType, rawJson));
}
/**
@@ -43,7 +33,7 @@ public abstract class ScalarTypeJsonNodePostgres extends ScalarTypeJsonNode {
public static class JSON extends ScalarTypeJsonNodePostgres {
public JSON(ObjectMapper objectMapper) {
- super(objectMapper, DbType.JSON, POSTGRES_TYPE_JSON);
+ super(objectMapper, DbType.JSON, PostgresHelper.JSON_TYPE);
}
}
@@ -53,7 +43,7 @@ public abstract class ScalarTypeJsonNodePostgres extends ScalarTypeJsonNode {
public static class JSONB extends ScalarTypeJsonNodePostgres {
public JSONB(ObjectMapper objectMapper) {
- super(objectMapper, DbType.JSONB, POSTGRES_TYPE_JSONB);
+ super(objectMapper, DbType.JSONB, PostgresHelper.JSONB_TYPE);
}
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java
index 56c2119d3..be47722d0 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java
@@ -63,5 +63,5 @@ public interface TypeManager {
* Note that type expected to be JsonNode or Map.
*
*/
- ScalarType> getJsonScalarType(Class> type, int dbType);
+ ScalarType> getJsonScalarType(Class> type, int dbType, int dbLength);
}
diff --git a/src/test/java/com/avaje/ebean/config/dbplatform/DbTypeMapTest.java b/src/test/java/com/avaje/ebean/config/dbplatform/DbTypeMapTest.java
index 3a0fc9055..abf0d6d6e 100644
--- a/src/test/java/com/avaje/ebean/config/dbplatform/DbTypeMapTest.java
+++ b/src/test/java/com/avaje/ebean/config/dbplatform/DbTypeMapTest.java
@@ -13,16 +13,16 @@ public class DbTypeMapTest {
PostgresPlatform pg = new PostgresPlatform();
DbTypeMap dbTypeMap = pg.getDbTypeMap();
- assertThat(dbTypeMap.lookup("clob").renderType(0,0)).isEqualTo("text");
- assertThat(dbTypeMap.lookup("CLOB").renderType(0, 0)).isEqualTo("text");
+ assertThat(dbTypeMap.lookup("clob", false).renderType(0,0)).isEqualTo("text");
+ assertThat(dbTypeMap.lookup("CLOB", false).renderType(0, 0)).isEqualTo("text");
- assertThat(dbTypeMap.lookup("varchar").renderType(20,0)).isEqualTo("varchar(20)");
+ assertThat(dbTypeMap.lookup("varchar", true).renderType(20,0)).isEqualTo("varchar(20)");
- assertThat(dbTypeMap.lookup("json").renderType(0,0)).isEqualTo("json");
- assertThat(dbTypeMap.lookup("jsonb").renderType(0,0)).isEqualTo("jsonb");
- assertThat(dbTypeMap.lookup("jsonclob").renderType(0,0)).isEqualTo("text");
- assertThat(dbTypeMap.lookup("jsonblob").renderType(0,0)).isEqualTo("bytea");
- assertThat(dbTypeMap.lookup("jsonvarchar").renderType(200,0)).isEqualTo("varchar(200)");
+ assertThat(dbTypeMap.lookup("json", false).renderType(0,0)).isEqualTo("json");
+ assertThat(dbTypeMap.lookup("jsonb", false).renderType(0,0)).isEqualTo("jsonb");
+ assertThat(dbTypeMap.lookup("jsonclob", false).renderType(0,0)).isEqualTo("text");
+ assertThat(dbTypeMap.lookup("jsonblob", false).renderType(0,0)).isEqualTo("bytea");
+ assertThat(dbTypeMap.lookup("jsonvarchar", false).renderType(200,0)).isEqualTo("varchar(200)");
}
@@ -31,7 +31,7 @@ public class DbTypeMapTest {
DbTypeMap dbTypeMap = DbTypeMap.logicalTypes();
DbType dbType = dbTypeMap.get(DbType.JSON);
- DbType json = dbTypeMap.lookup("json");
+ DbType json = dbTypeMap.lookup("json", false);
assertThat(dbType).isSameAs(json);
}
diff --git a/src/test/java/com/avaje/ebean/json/EJsonTests.java b/src/test/java/com/avaje/ebean/json/EJsonTests.java
index 366831bf1..239fa0eaa 100644
--- a/src/test/java/com/avaje/ebean/json/EJsonTests.java
+++ b/src/test/java/com/avaje/ebean/json/EJsonTests.java
@@ -246,4 +246,18 @@ public class EJsonTests {
}
+
+ @Test
+ public void parse_when_null() throws IOException {
+
+ Object nothing = EJson.parse((String) null);
+ Assert.assertNull(nothing);
+ }
+
+ @Test
+ public void parseList_when_null() throws IOException {
+
+ Object nothing = EJson.parseList((String) null);
+ Assert.assertNull(nothing);
+ }
}
diff --git a/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataSerializeTest.java b/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataSerializeTest.java
index 9d8907573..dc7eec116 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataSerializeTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/cache/CachedBeanDataSerializeTest.java
@@ -98,7 +98,7 @@ public class CachedBeanDataSerializeTest extends BaseTestCase {
TBytesOnly bean = new TBytesOnly();
bean.setId(42);
- bean.setContent(stringContent.getBytes("UTF8"));
+ bean.setContent(stringContent.getBytes("UTF-8"));
BeanDescriptor desc = getBeanDescriptor(TBytesOnly.class);
CachedBeanData extract = CachedBeanDataFromBean.extract(desc, (EntityBean)bean);
diff --git a/src/test/java/com/avaje/tests/json/TestDbJson_List.java b/src/test/java/com/avaje/tests/json/TestDbJson_List.java
new file mode 100644
index 000000000..34e7891f7
--- /dev/null
+++ b/src/test/java/com/avaje/tests/json/TestDbJson_List.java
@@ -0,0 +1,78 @@
+package com.avaje.tests.json;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.tests.model.json.EBasicJsonList;
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class TestDbJson_List extends BaseTestCase {
+
+ private EBasicJsonList bean = new EBasicJsonList();
+
+ private EBasicJsonList found;
+
+ @Test
+ public void insert() {
+
+ bean.setName("stuff");
+
+ List tags = bean.getTags();
+ tags.add("one");
+ tags.add("two");
+
+ Ebean.save(bean);
+
+ found = Ebean.find(EBasicJsonList.class, bean.getId());
+
+ assertThat(found.getTags()).containsExactly("one", "two");
+
+ json_parse_format();
+ update_when_notDirty();
+ update_when_dirty();
+ }
+
+ //@Test//(dependsOnMethods = "insert")
+ public void json_parse_format() {
+
+ String asJson = Ebean.json().toJson(found);
+ assertThat(asJson).contains("\"tags\":[\"one\",\"two\"]");
+ assertThat(asJson).contains("\"id\":");
+
+ EBasicJsonList fromJson = Ebean.json().toBean(EBasicJsonList.class, asJson);
+ assertEquals(found.getId(), fromJson.getId());
+ assertEquals(found.getId(), fromJson.getId());
+ assertEquals(found.getName(), fromJson.getName());
+ assertThat(fromJson.getTags()).containsExactly("one", "two");
+ }
+
+ //@Test//(dependsOnMethods = "insert")
+ public void update_when_notDirty() {
+
+ found.setName("mod");
+ LoggedSqlCollector.start();
+ Ebean.save(found);
+ List sql = LoggedSqlCollector.stop();
+
+ // we don't update the phone numbers (as they are not dirty)
+ assertThat(sql.get(0)).contains("update ebasic_json_list set name=?, version=? where");
+ }
+
+ public void update_when_dirty() {
+
+ //found.setName("modAgain");
+ found.getTags().add("three");
+
+ LoggedSqlCollector.start();
+ Ebean.save(found);
+ List sql = LoggedSqlCollector.stop();
+
+ // we don't update the phone numbers (as they are not dirty)
+ assertThat(sql.get(0)).contains("update ebasic_json_list set tags=?, version=? where");
+ }
+}
diff --git a/src/test/java/com/avaje/tests/model/json/EBasicJsonList.java b/src/test/java/com/avaje/tests/model/json/EBasicJsonList.java
new file mode 100644
index 000000000..cf694cb23
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/json/EBasicJsonList.java
@@ -0,0 +1,57 @@
+package com.avaje.tests.model.json;
+
+import com.avaje.ebean.annotation.DbJson;
+import com.avaje.ebean.annotation.DbJsonType;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+import java.util.ArrayList;
+import java.util.List;
+
+@Entity
+public class EBasicJsonList {
+
+ @Id
+ Long id;
+
+ String name;
+
+ @DbJson(length = 100, storage = DbJsonType.VARCHAR)
+ List tags = new ArrayList();
+
+ @Version
+ Long version;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public List getTags() {
+ return tags;
+ }
+
+ public void setTags(List tags) {
+ this.tags = tags;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+}
diff --git a/src/test/resources/logback-test.xml b/src/test/resources/logback-test.xml
index 5ecf16998..466673fd5 100644
--- a/src/test/resources/logback-test.xml
+++ b/src/test/resources/logback-test.xml
@@ -76,9 +76,9 @@
-
-
-
+
+
+