From b197d8a637c7413c39d68a8053e0cb796182e766 Mon Sep 17 00:00:00 2001
From: Robin Bygrave
Date: Mon, 9 May 2016 22:38:51 +1200
Subject: [PATCH] #695 - ENH: Add @DbArray for storing scalar types (UUID,
Long, String) to Postgres ARRAY type
---
pom.xml | 2 +-
.../com/avaje/ebean/ExpressionFactory.java | 32 +++++
.../java/com/avaje/ebean/ExpressionList.java | 29 ++++
.../com/avaje/ebean/annotation/DbArray.java | 40 ++++++
.../ebean/config/dbplatform/DbTypeMap.java | 2 +
.../api/SpiExpressionRequest.java | 62 ++++-----
.../server/core/DbExpressionHandler.java | 26 ++++
.../server/core/InternalConfiguration.java | 8 +-
.../server/core/JsonExpressionHandler.java | 17 ---
.../server/core/NotSupportedDbExpression.java | 25 ++++
.../core/NotSupportedJsonExpression.java | 15 ---
.../server/core/OracleDbExpression.java | 33 +++++
.../server/core/OracleJsonExpression.java | 24 ----
.../server/core/PostgresCast.java | 52 ++++++++
.../server/core/PostgresJsonExpression.java | 55 ++++----
.../server/core/RelationalQueryRequest.java | 2 +-
.../server/deploy/BeanProperty.java | 2 +-
.../server/deploy/parse/AnnotationFields.java | 4 +
.../deploy/parse/DeployCreateProperties.java | 2 +
.../server/deploy/parse/DeployUtil.java | 28 ++++
.../expression/ArrayContainsExpression.java | 93 +++++++++++++
.../expression/ArrayIsEmptyExpression.java | 60 +++++++++
.../expression/DefaultExpressionFactory.java | 20 +++
.../expression/DefaultExpressionList.java | 24 ++++
.../expression/DefaultExpressionRequest.java | 6 +-
.../server/expression/JsonPathExpression.java | 2 +-
.../server/expression/JunctionExpression.java | 20 +++
.../ebeaninternal/server/persist/Binder.java | 23 ++--
.../server/persist/ExeCallableSql.java | 2 +-
.../server/persist/ExeOrmUpdate.java | 2 +-
.../server/persist/ExeUpdateSql.java | 2 +-
.../server/persist/dml/DmlHandler.java | 2 +-
.../ebeaninternal/server/query/CQuery.java | 2 +-
.../server/query/CQueryDelete.java | 2 +-
.../server/query/CQueryFetchIds.java | 2 +-
.../server/query/CQueryPlan.java | 5 +-
.../server/query/CQueryPredicates.java | 5 +-
.../server/query/CQueryRowCount.java | 2 +-
.../server/type/ArrayElementConverter.java | 79 +++++++++++
.../ebeaninternal/server/type/DataBind.java | 12 +-
.../server/type/DefaultTypeManager.java | 49 +++++--
.../server/type/ScalarTypeArrayList.java | 125 ++++++++++++++++++
.../server/type/ScalarTypeJsonCollection.java | 7 +-
.../server/type/ScalarTypeJsonList.java | 38 +++---
.../type/ScalarTypeJsonObjectMapper.java | 37 +++---
.../server/type/ScalarTypeJsonSet.java | 35 +++--
.../server/type/ScalarTypeUUIDBase.java | 2 +-
.../server/type/TypeManager.java | 6 +
.../docstore/api/mapping/DocPropertyType.java | 5 +
.../java/com/avaje/ebean/BaseTestCase.java | 5 +
.../expression/TDSpiExpressionRequest.java | 4 +-
.../avaje/tests/model/array/EArrayBean.java | 80 +++++++++++
.../tests/model/array/TestDbArray_basic.java | 113 ++++++++++++++++
53 files changed, 1102 insertions(+), 229 deletions(-)
create mode 100644 src/main/java/com/avaje/ebean/annotation/DbArray.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/DbExpressionHandler.java
delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedDbExpression.java
delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/OracleDbExpression.java
delete mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PostgresCast.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/ArrayContainsExpression.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/ArrayIsEmptyExpression.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java
create mode 100644 src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java
create mode 100644 src/test/java/com/avaje/tests/model/array/EArrayBean.java
create mode 100644 src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java
diff --git a/pom.xml b/pom.xml
index bad54c0ad..968c04176 100644
--- a/pom.xml
+++ b/pom.xml
@@ -9,7 +9,7 @@
org.avaje.ebeanorm
avaje-ebeanorm
- 7.10.2-SNAPSHOT
+ 7.11.1-SNAPSHOT
jar
avaje-ebeanorm
diff --git a/src/main/java/com/avaje/ebean/ExpressionFactory.java b/src/main/java/com/avaje/ebean/ExpressionFactory.java
index 89ab15535..264d87312 100644
--- a/src/main/java/com/avaje/ebean/ExpressionFactory.java
+++ b/src/main/java/com/avaje/ebean/ExpressionFactory.java
@@ -87,6 +87,38 @@ public interface ExpressionFactory {
*/
Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
+ /**
+ * Array contains all the given values.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ Expression arrayContains(String propertyName, Object... values);
+
+ /**
+ * Array does not contain the given values.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ Expression arrayNotContains(String propertyName, Object... values);
+
+ /**
+ * Array is empty - for the given array property.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ Expression arrayIsEmpty(String propertyName);
+
+ /**
+ * Array is not empty - for the given array property.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ Expression arrayIsNotEmpty(String propertyName);
+
/**
* Equal To - property equal to the given value.
*/
diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java
index 0d9c85c16..f468d698a 100644
--- a/src/main/java/com/avaje/ebean/ExpressionList.java
+++ b/src/main/java/com/avaje/ebean/ExpressionList.java
@@ -801,6 +801,35 @@ public interface ExpressionList {
*/
ExpressionList allEq(Map propertyMap);
+ /**
+ * Array property contains entries with the given values.
+ */
+ ExpressionList arrayContains(String propertyName, Object... values);
+
+ /**
+ * Array does not contain the given values.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ ExpressionList arrayNotContains(String propertyName, Object... values);
+
+ /**
+ * Array is empty - for the given array property.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ ExpressionList arrayIsEmpty(String propertyName);
+
+ /**
+ * Array is not empty - for the given array property.
+ *
+ * Array support is effectively limited to Postgres at this time.
+ *
+ */
+ ExpressionList arrayIsNotEmpty(String propertyName);
+
/**
* Add raw expression with a single parameter.
*
diff --git a/src/main/java/com/avaje/ebean/annotation/DbArray.java b/src/main/java/com/avaje/ebean/annotation/DbArray.java
new file mode 100644
index 000000000..5c81651d5
--- /dev/null
+++ b/src/main/java/com/avaje/ebean/annotation/DbArray.java
@@ -0,0 +1,40 @@
+package com.avaje.ebean.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Specify a collection property that will be stored into a DB ARRAY type.
+ *
+ * If the target database does not support ARRAY type (so not Postgres)
+ * then the collection will be stored in JSON format into a VARCHAR column.
+ *
+ *
+ * Example:
+ * {@code
+ *
+ * // Store as ARRAY of UUID on Postgres
+ * @DbArray
+ * List uids = new ArrayList<>();
+ *
+ * // Store as ARRAY on Postgres
+ * @DbArray
+ * List phoneNumbers = new ArrayList<>();
+ *
+ * // Store as ARRAY of INTEGER on Postgres
+ * @DbArray
+ * List someLongs = new ArrayList<>();
+ *
+ * }
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.FIELD)
+public @interface DbArray {
+
+ /**
+ * For VARCHAR storage specify the column length (defaults to 1000).
+ */
+ int length() default 0;
+}
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 9dbe17b2e..bbf138bdf 100644
--- a/src/main/java/com/avaje/ebean/config/dbplatform/DbTypeMap.java
+++ b/src/main/java/com/avaje/ebean/config/dbplatform/DbTypeMap.java
@@ -49,6 +49,7 @@ public class DbTypeMap {
lookup.put("TIME", Types.TIME);
lookup.put("TIMESTAMP", Types.TIMESTAMP);
+ lookup.put("ARRAY", Types.ARRAY);
lookup.put("UUID", DbType.UUID);
// Not standard java.sql.Types
@@ -105,6 +106,7 @@ public class DbTypeMap {
// DB native UUID support (H2 and Postgres)
put(DbType.UUID, new DbType("uuid"));
+ put(Types.ARRAY, new DbType("array"));
if (logicalTypes) {
// keep it logical for 2 layer DDL generation
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java
index 98069d29e..3e3d182df 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java
@@ -1,6 +1,6 @@
package com.avaje.ebeaninternal.api;
-import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
+import com.avaje.ebeaninternal.server.core.DbExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -12,49 +12,49 @@ import java.util.List;
public interface SpiExpressionRequest {
/**
- * Return the DB specific JSON expression handler.
+ * Return the DB specific handler for JSON and ARRAY expressions.
*/
- JsonExpressionHandler getJsonHandler();
+ DbExpressionHandler getDbPlatformHandler();
/**
* Parse the logical property name to the deployment name.
*/
String parseDeploy(String logicalProp);
- /**
- * Return the bean descriptor for the root type.
- */
- BeanDescriptor> getBeanDescriptor();
-
- /**
- * Return the associated QueryRequest.
- */
- SpiOrmQueryRequest> getQueryRequest();
-
- /**
- * Append to the expression sql.
- */
- SpiExpressionRequest append(String sql);
+ /**
+ * Return the bean descriptor for the root type.
+ */
+ BeanDescriptor> getBeanDescriptor();
+
+ /**
+ * Return the associated QueryRequest.
+ */
+ SpiOrmQueryRequest> getQueryRequest();
+
+ /**
+ * Append to the expression sql.
+ */
+ SpiExpressionRequest append(String sql);
/**
* Add an encryption key to bind to this request.
*/
void addBindEncryptKey(Object encryptKey);
- /**
- * Add a bind value to this request.
- */
- void addBindValue(Object bindValue);
-
- /**
- * Return the accumulated expression sql for all expressions in this request.
- */
- String getSql();
-
- /**
- * Return the ordered list of bind values for all expressions in this request.
- */
- List getBindValues();
+ /**
+ * Add a bind value to this request.
+ */
+ void addBindValue(Object bindValue);
+
+ /**
+ * Return the accumulated expression sql for all expressions in this request.
+ */
+ String getSql();
+
+ /**
+ * Return the ordered list of bind values for all expressions in this request.
+ */
+ List getBindValues();
/**
* Increments the parameter index and returns that value.
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/DbExpressionHandler.java b/src/main/java/com/avaje/ebeaninternal/server/core/DbExpressionHandler.java
new file mode 100644
index 000000000..0c1d4bb2b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/DbExpressionHandler.java
@@ -0,0 +1,26 @@
+package com.avaje.ebeaninternal.server.core;
+
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+import com.avaje.ebeaninternal.server.expression.Op;
+
+/**
+ * Adds the db platform specific json expression.
+ */
+public interface DbExpressionHandler {
+
+ /**
+ * Write the db platform specific json expression.
+ */
+ void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value);
+
+ /**
+ * Add SQL for ARRAY CONTAINS expression.
+ */
+ void arrayContains(SpiExpressionRequest request, String propName, boolean contains, Object... values);
+
+ /**
+ * Add SQL for ARRAY IS EMPTY expression.
+ */
+ void arrayIsEmpty(SpiExpressionRequest request, String propName, boolean empty);
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
index ed061bb83..214247867 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
@@ -235,7 +235,7 @@ public class InternalConfiguration {
*/
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform, DataTimeZone dataTimeZone) {
- JsonExpressionHandler jsonHandler = getJsonExpressionHandler(databasePlatform);
+ DbExpressionHandler jsonHandler = getDbExpressionHandler(databasePlatform);
DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
@@ -247,16 +247,16 @@ public class InternalConfiguration {
/**
* Return the JSON expression handler for the given database platform.
*/
- private JsonExpressionHandler getJsonExpressionHandler(DatabasePlatform databasePlatform) {
+ private DbExpressionHandler getDbExpressionHandler(DatabasePlatform databasePlatform) {
String name = databasePlatform.getName();
if ("postgres".equalsIgnoreCase(name)) {
return new PostgresJsonExpression();
}
if ("oracle".equalsIgnoreCase(name)) {
- return new OracleJsonExpression();
+ return new OracleDbExpression();
}
- return new NotSupportedJsonExpression();
+ return new NotSupportedDbExpression();
}
public JsonContext createJsonContext(SpiEbeanServer server) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java b/src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java
deleted file mode 100644
index 67ec32bcb..000000000
--- a/src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.avaje.ebeaninternal.server.core;
-
-import com.avaje.ebeaninternal.api.SpiExpressionRequest;
-import com.avaje.ebeaninternal.server.expression.Op;
-
-/**
- * Adds the db platform specific json expression.
- */
-public interface JsonExpressionHandler {
-
-
- /**
- * Write the db platform specific json expression.
- */
- void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value);
-
-}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedDbExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedDbExpression.java
new file mode 100644
index 000000000..17db357a5
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedDbExpression.java
@@ -0,0 +1,25 @@
+package com.avaje.ebeaninternal.server.core;
+
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+import com.avaje.ebeaninternal.server.expression.Op;
+
+/**
+ * Not supported JSON or ARRAY expression handler.
+ */
+public class NotSupportedDbExpression implements DbExpressionHandler {
+
+ @Override
+ public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
+ throw new RuntimeException("JSON expressions only supported on Postgres and Oracle");
+ }
+
+ @Override
+ public void arrayContains(SpiExpressionRequest request, String propName, boolean contains, Object... values) {
+ throw new RuntimeException("ARRAY expressions only supported on Postgres");
+ }
+
+ @Override
+ public void arrayIsEmpty(SpiExpressionRequest request, String propName, boolean empty) {
+ throw new RuntimeException("ARRAY expressions only supported on Postgres");
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java
deleted file mode 100644
index 840bd237f..000000000
--- a/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.avaje.ebeaninternal.server.core;
-
-import com.avaje.ebeaninternal.api.SpiExpressionRequest;
-import com.avaje.ebeaninternal.server.expression.Op;
-
-/**
- * Not supported JSON expression handler.
- */
-public class NotSupportedJsonExpression implements JsonExpressionHandler {
-
- @Override
- public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
- throw new RuntimeException("JSON expressions only supported on Postgres and Oracle");
- }
-}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OracleDbExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/OracleDbExpression.java
new file mode 100644
index 000000000..d3f73cc1e
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OracleDbExpression.java
@@ -0,0 +1,33 @@
+package com.avaje.ebeaninternal.server.core;
+
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+import com.avaje.ebeaninternal.server.expression.Op;
+
+/**
+ * Oracle JSON expression handler, ARRAY expressions not supported.
+ */
+public class OracleDbExpression implements DbExpressionHandler {
+
+ @Override
+ public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
+
+ if (operator == Op.EXISTS) {
+ request.append("json_exists(").append(propName).append(", '$.").append(path).append("')");
+ } else if (operator == Op.NOT_EXISTS) {
+ request.append("not json_exists(").append(propName).append(", '$.").append(path).append("')");
+ } else {
+ request.append("json_value(").append(propName).append(", '$.").append(path).append("')");
+ request.append(operator.bind());
+ }
+ }
+
+ @Override
+ public void arrayContains(SpiExpressionRequest request, String propName, boolean contains, Object... values) {
+ throw new IllegalStateException("ARRAY expressions not supported on Oracle");
+ }
+
+ @Override
+ public void arrayIsEmpty(SpiExpressionRequest request, String propName, boolean empty) {
+ throw new IllegalStateException("ARRAY expressions not supported on Oracle");
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java
deleted file mode 100644
index c4acf95a9..000000000
--- a/src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java
+++ /dev/null
@@ -1,24 +0,0 @@
-package com.avaje.ebeaninternal.server.core;
-
-import com.avaje.ebeaninternal.api.SpiExpressionRequest;
-import com.avaje.ebeaninternal.server.expression.Op;
-
-/**
- * Postgres JSON expression handler
- */
-public class OracleJsonExpression implements JsonExpressionHandler {
-
- @Override
- public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
-
- if (operator == Op.EXISTS) {
- request.append("json_exists(").append(propName).append(", '$.").append(path).append("')");
- } else if (operator == Op.NOT_EXISTS) {
- request.append("not json_exists(").append(propName).append(", '$.").append(path).append("')");
- } else {
- request.append("json_value(").append(propName).append(", '$.").append(path).append("')");
- request.append(operator.bind());
- }
-
- }
-}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PostgresCast.java b/src/main/java/com/avaje/ebeaninternal/server/core/PostgresCast.java
new file mode 100644
index 000000000..3aa592e6b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PostgresCast.java
@@ -0,0 +1,52 @@
+package com.avaje.ebeaninternal.server.core;
+
+/**
+ * Helper for determining type casting for JSON and ARRAY expressions.
+ */
+public class PostgresCast {
+
+ /**
+ * Postgres CAST the type if necessary.
+ *
+ * This is generally necessary for JSON expressions as text values always returned from the json operators used.
+ *
+ */
+ protected static String cast(Object value) {
+ return cast(value, false);
+ }
+
+ /**
+ * Postgres CAST the type if necessary additionally specify if DB ARRAY is used.
+ */
+ protected static String cast(Object value, boolean asArray) {
+
+ if (value == null) {
+ // for exists and not-exists expressions
+ return "";
+ }
+
+ if (isIntegerType(value)) {
+ return asArray ? "::integer[]" : "::integer";
+ }
+ if (isNumberType(value)) {
+ return asArray ? "::decimal[]" : "::decimal";
+ }
+ if (isBooleanType(value)) {
+ return asArray ? "::boolean[]" : "::boolean";
+ }
+
+ return "";
+ }
+
+ private static boolean isBooleanType(Object value) {
+ return (value instanceof Boolean);
+ }
+
+ private static boolean isIntegerType(Object value) {
+ return (value instanceof Integer) || (value instanceof Long);
+ }
+
+ private static boolean isNumberType(Object value) {
+ return (value instanceof Number);
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java
index c61f21f0a..3e102b132 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java
@@ -4,12 +4,12 @@ import com.avaje.ebeaninternal.api.SpiExpressionRequest;
import com.avaje.ebeaninternal.server.expression.Op;
/**
- * Postgres JSON expression handler
+ * Postgres JSON and ARRAY expression handler
*/
-public class PostgresJsonExpression implements JsonExpressionHandler {
+public class PostgresJsonExpression implements DbExpressionHandler {
@Override
- public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
+ public void json(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {
StringBuilder sb = new StringBuilder(50);
String[] paths = path.split("\\.");
@@ -29,43 +29,36 @@ public class PostgresJsonExpression implements JsonExpressionHandler {
sb.append("}')");
}
- request.append(castType(sb.toString(), value));
+ request.append(sb.toString());
+ request.append(PostgresCast.cast(value));
request.append(operator.bind());
}
- /**
- * Postgres CAST the type if necessary as text values always returned from the json operators used.
- */
- private String castType(String expression, Object value) {
+ @Override
+ public void arrayContains(SpiExpressionRequest request, String propName, boolean contains, Object... values) {
- if (value == null) {
- // for exists and not-exists expressions
- return expression;
+ if (!contains) {
+ request.append("not (");
}
-
- // Postgres cast of returned text value
- if (isIntegerType(value)) {
- return expression+"::INTEGER";
+ request.append(propName).append(" @> array[?");
+ for (int i = 1; i < values.length; i++) {
+ request.append(",?");
}
- if (isNumberType(value)) {
- return expression+"::DECIMAL";
+ request.append("]");
+ request.append(PostgresCast.cast(values[0], true));
+ if (!contains) {
+ request.append(")");
}
- if (isBooleanType(value)) {
- return expression+"::BOOLEAN";
- }
-
- return expression;
}
- private boolean isBooleanType(Object value) {
- return (value instanceof Boolean);
- }
+ @Override
+ public void arrayIsEmpty(SpiExpressionRequest request, String propName, boolean empty) {
- private boolean isIntegerType(Object value) {
- return (value instanceof Integer) || (value instanceof Long);
- }
-
- private boolean isNumberType(Object value) {
- return (value instanceof Number);
+ request.append("coalesce(cardinality(").append(propName).append("),0)");
+ if (empty) {
+ request.append(" = 0");
+ } else {
+ request.append(" <> 0");
+ }
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java
index 1eeac35ca..916f429cb 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/RelationalQueryRequest.java
@@ -235,7 +235,7 @@ public final class RelationalQueryRequest {
BindParams bindParams = query.getBindParams();
if (!bindParams.isEmpty()) {
- this.bindLog = binder.bind(bindParams, pstmt);
+ this.bindLog = binder.bind(bindParams, pstmt, conn);
}
if (isLogSql()) {
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 04aa3ec51..bf62fa46a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/deploy/BeanProperty.java
@@ -1367,7 +1367,7 @@ public class BeanProperty implements ElPropertyValue, Property {
DocPropertyType type = scalarType.getDocType();
DocPropertyOptions options = docOptions.copy();
- if (DocPropertyType.ENUM == type || isStringId(type)) {
+ if (DocPropertyType.UUID == type || DocPropertyType.ENUM == type ||isStringId(type)) {
options.setCode(true);
}
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 8b76eb7f6..ed04e512a 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
@@ -187,6 +187,10 @@ public class AnnotationFields extends AnnotationParser {
util.setDbJsonBType(prop, dbJsonB);
}
}
+ DbArray dbArray = get(prop, DbArray.class);
+ if (dbArray != null) {
+ util.setDbArray(prop, dbArray);
+ }
DocCode docCode = get(prop, DocCode.class);
if (docCode != null) {
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 8362fdcc5..23ab68dec 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
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.deploy.parse;
+import com.avaje.ebean.annotation.DbArray;
import com.avaje.ebean.annotation.DbHstore;
import com.avaje.ebean.annotation.DbJson;
import com.avaje.ebean.annotation.DbJsonB;
@@ -299,6 +300,7 @@ public class DeployCreateProperties {
private boolean isSpecialScalarType(Field field) {
return (field.getAnnotation(DbJson.class) != null)
|| (field.getAnnotation(DbJsonB.class) != null)
+ || (field.getAnnotation(DbArray.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 0d430f7e7..5887044bb 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
@@ -5,6 +5,7 @@ import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.PersistenceException;
+import com.avaje.ebean.annotation.DbArray;
import com.avaje.ebean.annotation.DbJson;
import com.avaje.ebean.annotation.DbJsonB;
import com.avaje.ebean.annotation.DbJsonType;
@@ -21,6 +22,7 @@ import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanProperty;
import com.avaje.ebeaninternal.server.deploy.meta.DeployBeanPropertyCompound;
import com.avaje.ebeaninternal.server.type.DataEncryptSupport;
import com.avaje.ebeaninternal.server.type.ScalarType;
+import com.avaje.ebeaninternal.server.type.ScalarTypeArrayList;
import com.avaje.ebeaninternal.server.type.ScalarTypeEnumStandard;
import com.avaje.ebeaninternal.server.type.SimpleAesEncryptor;
import com.avaje.ebeaninternal.server.type.TypeManager;
@@ -47,6 +49,8 @@ public class DeployUtil {
private static final int DEFAULT_JSON_VARCHAR_LENGTH = 3000;
+ private static final int DEFAULT_ARRAY_VARCHAR_LENGTH = 1000;
+
private final NamingConvention namingConvention;
private final TypeManager typeManager;
@@ -210,6 +214,30 @@ public class DeployUtil {
prop.setScalarType(scalarType);
}
+ /**
+ * Set the DbArray type (effectively Postgres only).
+ */
+ public void setDbArray(DeployBeanProperty prop, DbArray dbArray) {
+
+ Class> type = prop.getPropertyType();
+ ScalarType> scalarType = typeManager.getArrayScalarType(type, dbArray, prop.getGenericType());
+ if (scalarType == null) {
+ throw new RuntimeException("No ScalarType for @DbArray type for [" + prop.getFullBeanName()+ "]");
+ }
+ int dbType = scalarType.getJdbcType();
+ prop.setDbType(dbType);
+ prop.setScalarType(scalarType);
+ if (scalarType instanceof ScalarTypeArrayList) {
+ prop.setDbColumnDefn(((ScalarTypeArrayList)scalarType).getDbColumnDefn());
+ }
+ if (dbType == Types.VARCHAR) {
+ // determine the db column size
+ int dbLength = dbArray.length();
+ int columnLength = (dbLength > 0) ? dbLength : DEFAULT_ARRAY_VARCHAR_LENGTH;
+ prop.setDbLength(columnLength);
+ }
+ }
+
/**
* This property is marked as a Lob object.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ArrayContainsExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ArrayContainsExpression.java
new file mode 100644
index 000000000..a90c13cf7
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ArrayContainsExpression.java
@@ -0,0 +1,93 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+
+import java.io.IOException;
+
+/**
+ * Contains expression for ARRAY type.
+ */
+public class ArrayContainsExpression extends AbstractExpression {
+
+ private final boolean contains;
+
+ private final Object[] values;
+
+ protected ArrayContainsExpression(String propName, boolean contains, Object... values) {
+ super(propName);
+ this.contains = contains;
+ this.values = values;
+ if (values == null || values.length == 0) {
+ throw new IllegalArgumentException("values must not be null or empty");
+ }
+ }
+
+ @Override
+ public void writeDocQuery(DocQueryContext context) throws IOException {
+
+ if (values.length == 1) {
+ context.writeEqualTo(propName, values[0]);
+ } else {
+ if (contains) {
+ context.startBoolMust();
+ } else {
+ context.startBoolMustNot();
+ }
+ for (int i = 0; i < values.length; i++) {
+ context.writeEqualTo(propName, values[i]);
+ }
+ context.endBool();
+ }
+ }
+
+ @Override
+ public void queryPlanHash(HashQueryPlanBuilder builder) {
+ builder.add(ArrayContainsExpression.class).add(propName).add(contains);
+ builder.bind(values.length);
+ }
+
+ @Override
+ public int queryBindHash() {
+ int hc = values[0].hashCode();
+ for (int i = 1; i < values.length; i++) {
+ hc = hc * 31 + values[i].hashCode();
+ }
+ return hc;
+ }
+
+ @Override
+ public boolean isSameByPlan(SpiExpression other) {
+ if (!(other instanceof ArrayContainsExpression)) {
+ return false;
+ }
+ ArrayContainsExpression that = (ArrayContainsExpression) other;
+ return this.propName.equals(that.propName)
+ && this.contains == that.contains
+ && this.values.length == that.values.length;
+ }
+
+ @Override
+ public boolean isSameByBind(SpiExpression other) {
+ ArrayContainsExpression that = (ArrayContainsExpression) other;
+ for (int i = 0; i < this.values.length; i++) {
+ if (!this.values[i].equals(that.values[i])) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void addSql(SpiExpressionRequest request) {
+ request.getDbPlatformHandler().arrayContains(request, propName, contains, values);
+ }
+
+ @Override
+ public void addBindValues(SpiExpressionRequest request) {
+ for (Object value : values) {
+ request.addBindValue(value);
+ }
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ArrayIsEmptyExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ArrayIsEmptyExpression.java
new file mode 100644
index 000000000..741fcf26f
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ArrayIsEmptyExpression.java
@@ -0,0 +1,60 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+
+import java.io.IOException;
+
+/**
+ * IsEmpty expression for ARRAY type.
+ */
+public class ArrayIsEmptyExpression extends AbstractExpression {
+
+ private final boolean empty;
+
+ protected ArrayIsEmptyExpression(String propName, boolean empty) {
+ super(propName);
+ this.empty = empty;
+ }
+
+ @Override
+ public void writeDocQuery(DocQueryContext context) throws IOException {
+ context.writeExists(!empty, propName);
+ }
+
+ @Override
+ public void queryPlanHash(HashQueryPlanBuilder builder) {
+ builder.add(ArrayIsEmptyExpression.class).add(propName);
+ }
+
+ @Override
+ public int queryBindHash() {
+ return empty ? 0 : 31;
+ }
+
+ @Override
+ public boolean isSameByPlan(SpiExpression other) {
+ if (!(other instanceof ArrayIsEmptyExpression)) {
+ return false;
+ }
+ ArrayIsEmptyExpression that = (ArrayIsEmptyExpression) other;
+ return this.propName.equals(that.propName)
+ && this.empty == that.empty;
+ }
+
+ @Override
+ public boolean isSameByBind(SpiExpression other) {
+ return true;
+ }
+
+ @Override
+ public void addSql(SpiExpressionRequest request) {
+ request.getDbPlatformHandler().arrayIsEmpty(request, propName, empty);
+ }
+
+ @Override
+ public void addBindValues(SpiExpressionRequest request) {
+ // nothing to bind
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
index 87465295f..f8e6e5729 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
@@ -106,6 +106,26 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
return new JsonPathExpression(propertyName, path, lowerValue, upperValue);
}
+ @Override
+ public Expression arrayContains(String propertyName, Object... values) {
+ return new ArrayContainsExpression(propertyName, true, values);
+ }
+
+ @Override
+ public Expression arrayNotContains(String propertyName, Object... values) {
+ return new ArrayContainsExpression(propertyName, false, values);
+ }
+
+ @Override
+ public Expression arrayIsEmpty(String propertyName) {
+ return new ArrayIsEmptyExpression(propertyName, true);
+ }
+
+ @Override
+ public Expression arrayIsNotEmpty(String propertyName) {
+ return new ArrayIsEmptyExpression(propertyName, false);
+ }
+
/**
* Equal To - property equal to the given value.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
index 413e420ef..8fa03ca5a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionList.java
@@ -826,6 +826,30 @@ public class DefaultExpressionList implements SpiExpressionList {
return this;
}
+ @Override
+ public ExpressionList arrayContains(String propertyName, Object... elementValue) {
+ add(expr.arrayContains(propertyName, elementValue));
+ return this;
+ }
+
+ @Override
+ public ExpressionList arrayNotContains(String propertyName, Object... values) {
+ add(expr.arrayNotContains(propertyName, values));
+ return this;
+ }
+
+ @Override
+ public ExpressionList arrayIsEmpty(String propertyName) {
+ add(expr.arrayIsEmpty(propertyName));
+ return this;
+ }
+
+ @Override
+ public ExpressionList arrayIsNotEmpty(String propertyName) {
+ add(expr.arrayIsNotEmpty(propertyName));
+ return this;
+ }
+
@Override
public ExpressionList raw(String raw, Object value) {
add(expr.raw(raw, value));
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java
index c87126df4..76f157b43 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionRequest.java
@@ -6,7 +6,7 @@ import java.util.List;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
-import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
+import com.avaje.ebeaninternal.server.core.DbExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.DeployParser;
@@ -73,8 +73,8 @@ public class DefaultExpressionRequest implements SpiExpressionRequest {
}
@Override
- public JsonExpressionHandler getJsonHandler() {
- return binder.getJsonExpressionHandler();
+ public DbExpressionHandler getDbPlatformHandler() {
+ return binder.getDbExpressionHandler();
}
@Override
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
index f89905f59..778bbad18 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
@@ -108,7 +108,7 @@ class JsonPathExpression extends AbstractExpression {
public void addSql(SpiExpressionRequest request) {
// Use DB specific expression handling (Postgres and Oracle supported)
- request.getJsonHandler().addSql(request, propName, path, operator, value);
+ request.getDbPlatformHandler().json(request, propName, path, operator, value);
}
@Override
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
index 9b5e61a0a..27f445e83 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
@@ -462,6 +462,26 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression
return exprList.jsonBetween(propertyName, path, lowerValue, upperValue);
}
+ @Override
+ public ExpressionList arrayContains(String propertyName, Object... values) {
+ return exprList.arrayContains(propertyName, values);
+ }
+
+ @Override
+ public ExpressionList arrayNotContains(String propertyName, Object... values) {
+ return exprList.arrayNotContains(propertyName, values);
+ }
+
+ @Override
+ public ExpressionList arrayIsEmpty(String propertyName) {
+ return exprList.arrayIsEmpty(propertyName);
+ }
+
+ @Override
+ public ExpressionList arrayIsNotEmpty(String propertyName) {
+ return exprList.arrayIsNotEmpty(propertyName);
+ }
+
@Override
public ExpressionList ge(String propertyName, Object value) {
return exprList.ge(propertyName, value);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java
index 5c57c1066..2de9ee82a 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.persist;
import java.math.BigDecimal;
import java.sql.CallableStatement;
+import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Types;
@@ -12,7 +13,7 @@ import javax.persistence.PersistenceException;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebeaninternal.api.BindParams;
-import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
+import com.avaje.ebeaninternal.server.core.DbExpressionHandler;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.core.timezone.DataTimeZone;
import com.avaje.ebeaninternal.server.type.DataBind;
@@ -34,7 +35,7 @@ public class Binder {
private final boolean bindAsOfWithFromClause;
- private final JsonExpressionHandler jsonExpressionHandler;
+ private final DbExpressionHandler dbExpressionHandler;
private final DataTimeZone dataTimeZone;
@@ -42,12 +43,12 @@ public class Binder {
* Set the PreparedStatement with which to bind variables to.
*/
public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause,
- JsonExpressionHandler jsonExpressionHandler, DataTimeZone dataTimeZone) {
+ DbExpressionHandler dbExpressionHandler, DataTimeZone dataTimeZone) {
this.typeManager = typeManager;
this.asOfBindCount = asOfBindCount;
this.bindAsOfWithFromClause = bindAsOfWithFromClause;
- this.jsonExpressionHandler = jsonExpressionHandler;
+ this.dbExpressionHandler = dbExpressionHandler;
this.dataTimeZone = dataTimeZone;
}
@@ -101,8 +102,8 @@ public class Binder {
/**
* Bind the parameters to the preparedStatement returning the bind log.
*/
- public String bind(BindParams bindParams, PreparedStatement statement) throws SQLException {
- return bind(bindParams, new DataBind(dataTimeZone, statement));
+ public String bind(BindParams bindParams, PreparedStatement statement, Connection connection) throws SQLException {
+ return bind(bindParams, new DataBind(dataTimeZone, statement, connection));
}
/**
@@ -401,16 +402,16 @@ public class Binder {
}
/**
- * Return the JsonExpressionHandler specific to the database.
+ * Return the DB platform specific expression handler (for JSON and ARRAY types).
*/
- public JsonExpressionHandler getJsonExpressionHandler() {
- return jsonExpressionHandler;
+ public DbExpressionHandler getDbExpressionHandler() {
+ return dbExpressionHandler;
}
/**
* Create and return a DataBind for the statement.
*/
- public DataBind dataBind(PreparedStatement stmt) {
- return new DataBind(dataTimeZone, stmt);
+ public DataBind dataBind(PreparedStatement stmt, Connection connection) {
+ return new DataBind(dataTimeZone, stmt, connection);
}
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java
index 89435be6b..ec565bc9f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeCallableSql.java
@@ -95,7 +95,7 @@ public class ExeCallableSql {
String bindLog = null;
if (!bindParams.isEmpty()) {
- bindLog = binder.bind(bindParams, cstmt);
+ bindLog = binder.bind(bindParams, cstmt, t.getInternalConnection());
}
request.setBindLog(bindLog);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java
index 2b3480543..efd238f73 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeOrmUpdate.java
@@ -113,7 +113,7 @@ public class ExeOrmUpdate {
String bindLog = null;
if (!bindParams.isEmpty()) {
- bindLog = binder.bind(bindParams, pstmt);
+ bindLog = binder.bind(bindParams, pstmt, t.getInternalConnection());
}
request.setBindLog(bindLog);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
index 377ce08fa..67cb2ab4f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/ExeUpdateSql.java
@@ -100,7 +100,7 @@ public class ExeUpdateSql {
String bindLog = null;
if (!bindParams.isEmpty()) {
- bindLog = binder.bind(bindParams, pstmt);
+ bindLog = binder.bind(bindParams, pstmt, t.getInternalConnection());
}
request.setBindLog(bindLog);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java
index db0c8261a..b68776381 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/persist/dml/DmlHandler.java
@@ -78,7 +78,7 @@ public abstract class DmlHandler implements PersistHandler, BindableRequest {
* Bind to the statement returning the DataBind.
*/
protected DataBind bind(PreparedStatement stmt) {
- return new DataBind(persistRequest.getDataTimeZone(), stmt);
+ return new DataBind(persistRequest.getDataTimeZone(), stmt, transaction.getInternalConnection());
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
index e8f96c930..39972cd35 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQuery.java
@@ -335,7 +335,7 @@ public class CQuery implements DbReadContext, CancelableQuery {
pstmt.setFetchSize(query.getBufferFetchSizeHint());
}
- DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt);
+ DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt, conn);
bindLog = predicates.bind(dataBind);
// executeQuery
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java
index f99f4c6f8..5f005ad33 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryDelete.java
@@ -105,7 +105,7 @@ public class CQueryDelete {
pstmt.setQueryTimeout(query.getTimeout());
}
- bindLog = predicates.bind(pstmt);
+ bindLog = predicates.bind(pstmt, conn);
rowCount = pstmt.executeUpdate();
long exeNano = System.nanoTime() - startNano;
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java
index 7a7a3332e..31da74715 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryFetchIds.java
@@ -143,7 +143,7 @@ public class CQueryFetchIds {
pstmt.setQueryTimeout(query.getTimeout());
}
- bindLog = predicates.bind(pstmt);
+ bindLog = predicates.bind(pstmt, conn);
ResultSet rset = pstmt.executeQuery();
dataReader = new RsetDataReader(request.getDataTimeZone(), rset);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java
index 2764cfb22..72e7ea4ff 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPlan.java
@@ -15,6 +15,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.MessageDigest;
+import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -139,8 +140,8 @@ public class CQueryPlan {
/**
* Bind keys for encrypted properties if necessary returning the DataBind.
*/
- public DataBind bindEncryptedProperties(PreparedStatement stmt) throws SQLException {
- DataBind dataBind = new DataBind(dataTimeZone, stmt);
+ public DataBind bindEncryptedProperties(PreparedStatement stmt, Connection conn) throws SQLException {
+ DataBind dataBind = new DataBind(dataTimeZone, stmt, conn);
if (encryptedProps != null) {
for (int i = 0; i < encryptedProps.length; i++) {
String key = encryptedProps[i].getEncryptKey().getStringValue();
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java
index ef8ac205f..4b69efc4b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java
@@ -17,6 +17,7 @@ import com.avaje.ebeaninternal.server.expression.DefaultExpressionRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
@@ -126,8 +127,8 @@ public class CQueryPredicates {
this.idValue = query.getId();
}
- public String bind(PreparedStatement stmt) throws SQLException {
- return bind(binder.dataBind(stmt));
+ public String bind(PreparedStatement stmt, Connection connection) throws SQLException {
+ return bind(binder.dataBind(stmt, connection));
}
public String bind(DataBind dataBind) throws SQLException {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java
index 0a35be81f..9a8d81da1 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryRowCount.java
@@ -116,7 +116,7 @@ public class CQueryRowCount {
pstmt.setQueryTimeout(query.getTimeout());
}
- bindLog = predicates.bind(pstmt);
+ bindLog = predicates.bind(pstmt, conn);
rset = pstmt.executeQuery();
if (!rset.next()) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java b/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java
new file mode 100644
index 000000000..1065650ff
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ArrayElementConverter.java
@@ -0,0 +1,79 @@
+package com.avaje.ebeaninternal.server.type;
+
+import java.util.UUID;
+
+/**
+ * Type conversion for use with ScalarTypeArrayList.
+ */
+public interface ArrayElementConverter {
+
+ /**
+ * Convert the array element to the logical type.
+ */
+ T toElement(Object rawValue);
+
+ /**
+ * The UUID converter implementation.
+ */
+ ArrayElementConverter UUID = new UuidConverter();
+
+ /**
+ * The String converter implementation.
+ */
+ ArrayElementConverter STRING = new StringConverter();
+
+ /**
+ * The Long converter implementation.
+ */
+ ArrayElementConverter LONG = new LongConverter();
+
+ /**
+ * The Integer converter implementation.
+ */
+ ArrayElementConverter INTEGER = new IntegerConverter();
+
+ class LongConverter implements ArrayElementConverter {
+
+ @Override
+ public Long toElement(Object rawValue) {
+ if (rawValue instanceof Long) {
+ return (Long)rawValue;
+ } else {
+ return ((Number)rawValue).longValue();
+ }
+ }
+ }
+
+ class IntegerConverter implements ArrayElementConverter {
+
+ @Override
+ public Integer toElement(Object rawValue) {
+ if (rawValue instanceof Integer) {
+ return (Integer)rawValue;
+ } else {
+ return ((Number)rawValue).intValue();
+ }
+ }
+ }
+
+ /**
+ * String converter (noop based).
+ */
+ class StringConverter extends NoopConverter {
+ }
+
+ /**
+ * UUID converter (noop based).
+ */
+ class UuidConverter extends NoopConverter {
+ }
+
+ class NoopConverter implements ArrayElementConverter {
+
+ @Override
+ public T toElement(Object rawValue) {
+ return (T)rawValue;
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java b/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java
index 03a8c7d91..20616339b 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/DataBind.java
@@ -7,6 +7,8 @@ import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.math.BigDecimal;
+import java.sql.Array;
+import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Time;
@@ -19,13 +21,16 @@ public class DataBind {
private final PreparedStatement pstmt;
+ private final Connection connection;
+
private final StringBuilder bindLog = new StringBuilder();
private int pos;
- public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt) {
+ public DataBind(DataTimeZone dataTimeZone, PreparedStatement pstmt, Connection connection) {
this.dataTimeZone = dataTimeZone;
this.pstmt = pstmt;
+ this.connection = connection;
}
/**
@@ -156,4 +161,9 @@ public class DataBind {
pstmt.setCharacterStream(++pos, reader, content.length());
}
+ public void setArray(String arrayType, Object[] elements) throws SQLException {
+ Array array = connection.createArrayOf(arrayType, elements);
+ pstmt.setArray(++pos, array);
+ }
+
}
\ No newline at end of file
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 3da28dbd3..7c7e76203 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/DefaultTypeManager.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebean.annotation.DbArray;
import com.avaje.ebean.annotation.DbEnumType;
import com.avaje.ebean.annotation.DbEnumValue;
import com.avaje.ebean.annotation.EnumValue;
@@ -19,6 +20,7 @@ import com.avaje.ebeaninternal.server.type.reflect.KnownImmutable;
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundType;
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedCompoundTypeProperty;
import com.avaje.ebeaninternal.server.type.reflect.ReflectionBasedTypeBuilder;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.joda.time.DateMidnight;
@@ -357,22 +359,38 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
return reader;
}
+ @Override
+ public ScalarType> getArrayScalarType(Class> type, DbArray dbArray, Type genericType) {
+
+ if (type.equals(List.class)) {
+ if (postgres) {
+ Type valueType = getValueType(genericType);
+ return ScalarTypeArrayList.typeFor(valueType);
+ }
+ // fallback to JSON storage in VARCHAR column
+ return new ScalarTypeJsonList.Varchar(getDocType(getValueType(genericType)));
+ }
+ throw new IllegalStateException("Type ["+type+"] not supported for @DbArray");
+ }
+
@Override
public ScalarType> getJsonScalarType(Class> type, int dbType, int dbLength, Type genericType) {
if (type.equals(List.class)) {
+ DocPropertyType docType = getDocType(genericType);
if (isValueTypeSimple(genericType)) {
- return ScalarTypeJsonList.typeFor(postgres, dbType);
+ return ScalarTypeJsonList.typeFor(postgres, dbType, docType);
} else {
- return createJsonObjectMapperType(type, genericType, dbType);
+ return createJsonObjectMapperType(type, genericType, dbType, docType);
}
}
if (type.equals(Set.class)) {
+ DocPropertyType docType = getDocType(genericType);
if (isValueTypeSimple(genericType)) {
- return ScalarTypeJsonSet.typeFor(postgres, dbType);
+ return ScalarTypeJsonSet.typeFor(postgres, dbType, docType);
} else {
- return createJsonObjectMapperType(type, genericType, dbType);
+ return createJsonObjectMapperType(type, genericType, dbType, docType);
}
}
@@ -380,7 +398,7 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
if (isMapValueTypeObject(genericType)) {
return ScalarTypeJsonMap.typeFor(postgres, dbType);
} else {
- return createJsonObjectMapperType(type, genericType, dbType);
+ return createJsonObjectMapperType(type, genericType, dbType, DocPropertyType.OBJECT);
}
}
@@ -396,7 +414,17 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
}
}
- return createJsonObjectMapperType(type, type, dbType);
+ return createJsonObjectMapperType(type, type, dbType, DocPropertyType.OBJECT);
+ }
+
+ private DocPropertyType getDocType(Type genericType) {
+ if (genericType instanceof Class>) {
+ ScalarType> found = typeMap.get(genericType);
+ if (found != null) {
+ return found.getDocType();
+ }
+ }
+ return DocPropertyType.OBJECT;
}
/**
@@ -407,6 +435,11 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
return String.class.equals(typeArgs[0]) || Long.class.equals(typeArgs[0]);
}
+ private Type getValueType(Type genericType) {
+ Type[] typeArgs = ((ParameterizedType) genericType).getActualTypeArguments();
+ return typeArgs[0];
+ }
+
/**
* Return true if value parameter type of the map is Object.
*/
@@ -415,11 +448,11 @@ public final class DefaultTypeManager implements TypeManager, KnownImmutable {
return Object.class.equals(typeArgs[1]);
}
- private ScalarType> createJsonObjectMapperType(Class> type, Type genericType, int dbType) {
+ private ScalarType> createJsonObjectMapperType(Class> type, Type genericType, int dbType, DocPropertyType docType) {
if (objectMapper == null) {
throw new IllegalArgumentException("Type [" + type + "] unsupported for @DbJson mapping - Jackson ObjectMapper not present");
}
- return ScalarTypeJsonObjectMapper.createTypeFor(postgres, type, (ObjectMapper) objectMapper, genericType, dbType);
+ return ScalarTypeJsonObjectMapper.createTypeFor(postgres, type, (ObjectMapper) objectMapper, genericType, dbType, docType);
}
/**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java
new file mode 100644
index 000000000..73ce0c1c0
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeArrayList.java
@@ -0,0 +1,125 @@
+package com.avaje.ebeaninternal.server.type;
+
+
+import com.avaje.ebean.text.json.EJson;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+
+import javax.persistence.PersistenceException;
+import java.io.IOException;
+import java.lang.reflect.Type;
+import java.sql.Array;
+import java.sql.SQLException;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * Type mapped for DB ARRAY type (Postgres only effectively).
+ */
+public class ScalarTypeArrayList extends ScalarTypeJsonCollection {
+
+ private static ScalarTypeArrayList UUID = new ScalarTypeArrayList("uuid", DocPropertyType.UUID, ArrayElementConverter.UUID);
+
+ private static ScalarTypeArrayList LONG = new ScalarTypeArrayList("integer", DocPropertyType.LONG, ArrayElementConverter.LONG);
+
+ private static ScalarTypeArrayList INTEGER = new ScalarTypeArrayList("integer", DocPropertyType.INTEGER, ArrayElementConverter.INTEGER);
+
+ private static ScalarTypeArrayList STRING = new ScalarTypeArrayList("varchar", DocPropertyType.STRING, ArrayElementConverter.STRING);
+
+ /**
+ * Return the ScalarType to use based on the List's generic parameter type.
+ */
+ public static ScalarTypeArrayList typeFor(Type valueType) {
+ if (valueType.equals(UUID.class)) {
+ return UUID;
+ }
+ if (valueType.equals(Long.class)) {
+ return LONG;
+ }
+ if (valueType.equals(Integer.class)) {
+ return INTEGER;
+ }
+ if (valueType.equals(String.class)) {
+ return STRING;
+ }
+ throw new IllegalArgumentException("Type ["+valueType+"] not supported for @DbArray mapping");
+ }
+
+ private final String arrayType;
+
+ private final ArrayElementConverter converter;
+
+ public ScalarTypeArrayList(String arrayType, DocPropertyType docPropertyType, ArrayElementConverter converter) {
+ super(List.class, Types.ARRAY, docPropertyType);
+ this.arrayType = arrayType;
+ this.converter = converter;
+ }
+
+ @Override
+ public DocPropertyType getDocType() {
+ return docPropertyType;
+ }
+
+ /**
+ * Return the DB column definition for DDL generation.
+ */
+ public String getDbColumnDefn() {
+ return arrayType+"[]";
+ }
+
+ @SuppressWarnings("unchecked")
+ private List fromArray(Object[] array1) {
+ List list = new ArrayList();
+ for (Object element : array1) {
+ list.add(converter.toElement(element));
+ }
+ return new ModifyAwareList(list);
+ }
+
+ private Object[] toArray(List value) {
+ return value.toArray();
+ }
+
+ @Override
+ public List read(DataReader reader) throws SQLException {
+ Array array = reader.getArray();
+ return fromArray((Object[])array.getArray());
+ }
+
+ @Override
+ public void bind(DataBind bind, List value) throws SQLException {
+ bind.setArray(arrayType, toArray(value));
+ }
+
+ @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);
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java
index d37442385..27cf80165 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonCollection.java
@@ -8,8 +8,11 @@ import java.io.IOException;
abstract class ScalarTypeJsonCollection extends ScalarTypeBase {
- public ScalarTypeJsonCollection(Class type, int dbType) {
+ protected DocPropertyType docPropertyType;
+
+ public ScalarTypeJsonCollection(Class type, int dbType, DocPropertyType docPropertyType) {
super(type, false, dbType);
+ this.docPropertyType = docPropertyType;
}
/**
@@ -41,7 +44,7 @@ abstract class ScalarTypeJsonCollection extends ScalarTypeBase {
@Override
public DocPropertyType getDocType() {
- return DocPropertyType.LIST;
+ return docPropertyType;
}
@Override
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java
index 461e2fbd7..64155a6c6 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonList.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebean.text.json.EJson;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -15,32 +16,25 @@ 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();
-
/**
* Return the appropriate ScalarType based requested dbType and if Postgres.
*/
- public static ScalarType> typeFor(boolean postgres, int dbType) {
+ public static ScalarType> typeFor(boolean postgres, int dbType, DocPropertyType docType) {
if (postgres) {
switch (dbType) {
- case DbType.JSONB: return ScalarTypeJsonList.JSONB;
- case DbType.JSON: return ScalarTypeJsonList.JSON;
+ case DbType.JSONB: return new ScalarTypeJsonList.JsonB(docType);
+ case DbType.JSON: return new ScalarTypeJsonList.Json(docType);
}
}
- return ScalarTypeJsonList.VARCHAR;
+ return new ScalarTypeJsonList.Varchar(docType);
}
/**
* List mapped to DB VARCHAR.
*/
- private static class Varchar extends ScalarTypeJsonList.Base {
- public Varchar() {
- super(Types.VARCHAR);
+ public static class Varchar extends ScalarTypeJsonList.Base {
+ public Varchar(DocPropertyType docType) {
+ super(Types.VARCHAR, docType);
}
}
@@ -48,8 +42,8 @@ public class ScalarTypeJsonList {
* List mapped to Postgres JSON.
*/
private static class Json extends ScalarTypeJsonList.PgBase {
- public Json() {
- super(DbType.JSON, PostgresHelper.JSON_TYPE);
+ public Json(DocPropertyType docType) {
+ super(DbType.JSON, PostgresHelper.JSON_TYPE, docType);
}
}
@@ -57,8 +51,8 @@ public class ScalarTypeJsonList {
* List mapped to Postgres JSONB.
*/
private static class JsonB extends ScalarTypeJsonList.PgBase {
- public JsonB() {
- super(DbType.JSONB, PostgresHelper.JSONB_TYPE);
+ public JsonB(DocPropertyType docType) {
+ super(DbType.JSONB, PostgresHelper.JSONB_TYPE, docType);
}
}
@@ -67,8 +61,8 @@ public class ScalarTypeJsonList {
*/
private abstract static class Base extends ScalarTypeJsonCollection {
- public Base(int dbType) {
- super(List.class, dbType);
+ public Base(int dbType, DocPropertyType docType) {
+ super(List.class, dbType, docType);
}
@Override
@@ -134,8 +128,8 @@ public class ScalarTypeJsonList {
final String pgType;
- PgBase(int jdbcType, String pgType) {
- super(jdbcType);
+ PgBase(int jdbcType, String pgType, DocPropertyType docType) {
+ super(jdbcType, docType);
this.pgType = pgType;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
index aba059ef7..587be6fa7 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonObjectMapper.java
@@ -25,21 +25,16 @@ public class ScalarTypeJsonObjectMapper {
/**
* Create and return the appropriate ScalarType.
- *
- * @param postgres
- * @param type The field/property type
- * @param objectMapper The Jackson ObjectMapper to use for marshalling
- * @param genericType The generic type
- * @param dbType The DB storage type to use
*/
- public static ScalarType> createTypeFor(boolean postgres, Class> type, ObjectMapper objectMapper, Type genericType, int dbType) {
+ public static ScalarType> createTypeFor(boolean postgres, Class> type, ObjectMapper objectMapper,
+ Type genericType, int dbType, DocPropertyType docType) {
String pgType = getPostgresType(postgres, dbType);
if (Set.class.equals(type)) {
- return new OmSet(objectMapper, genericType, dbType, pgType);
+ return new OmSet(objectMapper, genericType, dbType, pgType, docType);
}
if (List.class.equals(type)) {
- return new OmList(objectMapper, genericType, dbType, pgType);
+ return new OmList(objectMapper, genericType, dbType, pgType, docType);
}
if (Map.class.equals(type)) {
return new OmMap(objectMapper, genericType, dbType, pgType);
@@ -63,7 +58,7 @@ public class ScalarTypeJsonObjectMapper {
private static class GenericObject extends Base {
public GenericObject(ObjectMapper objectMapper, Type type, int dbType, String pgType) {
- super(Object.class, objectMapper, type, dbType, pgType);
+ super(Object.class, objectMapper, type, dbType, pgType, DocPropertyType.OBJECT);
}
}
@@ -72,8 +67,8 @@ public class ScalarTypeJsonObjectMapper {
*/
private static class OmSet extends Base {
- public OmSet(ObjectMapper objectMapper, Type type, int dbType, String pgType) {
- super(Set.class, objectMapper, type, dbType, pgType);
+ public OmSet(ObjectMapper objectMapper, Type type, int dbType, String pgType, DocPropertyType docType) {
+ super(Set.class, objectMapper, type, dbType, pgType, docType);
}
@Override
@@ -89,8 +84,8 @@ public class ScalarTypeJsonObjectMapper {
*/
private static class OmList extends Base {
- public OmList(ObjectMapper objectMapper, Type type, int dbType, String pgType) {
- super(List.class, objectMapper, type, dbType, pgType);
+ public OmList(ObjectMapper objectMapper, Type type, int dbType, String pgType, DocPropertyType docType) {
+ super(List.class, objectMapper, type, dbType, pgType, docType);
}
@Override
@@ -107,7 +102,7 @@ public class ScalarTypeJsonObjectMapper {
private static class OmMap extends Base {
public OmMap(ObjectMapper objectMapper, Type type, int dbType, String pgType) {
- super(Map.class, objectMapper, type, dbType, pgType);
+ super(Map.class, objectMapper, type, dbType, pgType, DocPropertyType.OBJECT);
}
@Override
@@ -130,16 +125,15 @@ public class ScalarTypeJsonObjectMapper {
private final String pgType;
+ private final DocPropertyType docType;
+
/**
* Construct given the object mapper, property type and DB type for storage.
- *
- * @param objectMapper Jackson object mapper for JSON marshalling/unmarshalling
- * @param type The property type (ie. type of field with @DbJson)
- * @param dbType The DB type used for storage (JSON, JSONB, VARCHAR, CLOB or BLOB)
*/
- public Base(Class cls, ObjectMapper objectMapper, Type type, int dbType, String pgType) {
+ public Base(Class cls, ObjectMapper objectMapper, Type type, int dbType, String pgType, DocPropertyType docType) {
super(cls, false, dbType);
this.pgType = pgType;
+ this.docType = docType;
this.objectMapper = objectMapper;
this.javaType = objectMapper.getTypeFactory().constructType(type);
}
@@ -199,6 +193,7 @@ public class ScalarTypeJsonObjectMapper {
}
@Override
+ @SuppressWarnings("unchecked")
public T toBeanType(Object value) {
// no type conversion supported
return (T) value;
@@ -224,7 +219,7 @@ public class ScalarTypeJsonObjectMapper {
@Override
public DocPropertyType getDocType() {
- return DocPropertyType.OBJECT;
+ return docType;
}
@Override
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonSet.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonSet.java
index 1cf02d093..b35b352bd 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonSet.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeJsonSet.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.type;
import com.avaje.ebean.config.dbplatform.DbType;
import com.avaje.ebean.text.json.EJson;
+import com.avaje.ebeanservice.docstore.api.mapping.DocPropertyType;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
@@ -18,31 +19,25 @@ import java.util.Set;
*/
public class ScalarTypeJsonSet {
- private static final ScalarTypeJsonCollection VARCHAR = new ScalarTypeJsonSet.Varchar();
-
- private static final ScalarTypeJsonCollection JSON = new ScalarTypeJsonSet.Json();
-
- private static final ScalarTypeJsonCollection JSONB = new ScalarTypeJsonSet.JsonB();
-
/**
* Return the appropriate ScalarType for the requested dbType and Postgres.
*/
- public static ScalarType> typeFor(boolean postgres, int dbType) {
+ public static ScalarType> typeFor(boolean postgres, int dbType, DocPropertyType docPropertyType) {
if (postgres) {
switch (dbType) {
- case DbType.JSONB: return ScalarTypeJsonSet.JSONB;
- case DbType.JSON: return ScalarTypeJsonSet.JSON;
+ case DbType.JSONB: return new ScalarTypeJsonSet.JsonB(docPropertyType);
+ case DbType.JSON: return new ScalarTypeJsonSet.Json(docPropertyType);
}
}
- return ScalarTypeJsonSet.VARCHAR;
+ return new ScalarTypeJsonSet.Varchar(docPropertyType);
}
/**
* List mapped to DB VARCHAR.
*/
private static class Varchar extends ScalarTypeJsonSet.Base {
- public Varchar() {
- super(Types.VARCHAR);
+ public Varchar(DocPropertyType docPropertyType) {
+ super(Types.VARCHAR, docPropertyType);
}
}
@@ -50,8 +45,8 @@ public class ScalarTypeJsonSet {
* List mapped to Postgres JSON.
*/
private static class Json extends ScalarTypeJsonSet.PgBase {
- public Json() {
- super(DbType.JSON, PostgresHelper.JSON_TYPE);
+ public Json(DocPropertyType docPropertyType) {
+ super(DbType.JSON, PostgresHelper.JSON_TYPE, docPropertyType);
}
}
@@ -59,8 +54,8 @@ public class ScalarTypeJsonSet {
* List mapped to Postgres JSONB.
*/
private static class JsonB extends ScalarTypeJsonSet.PgBase {
- public JsonB() {
- super(DbType.JSONB, PostgresHelper.JSONB_TYPE);
+ public JsonB(DocPropertyType docPropertyType) {
+ super(DbType.JSONB, PostgresHelper.JSONB_TYPE, docPropertyType);
}
}
@@ -69,8 +64,8 @@ public class ScalarTypeJsonSet {
*/
private abstract static class Base extends ScalarTypeJsonCollection {
- public Base(int dbType) {
- super(Set.class, dbType);
+ public Base(int dbType, DocPropertyType docPropertyType) {
+ super(Set.class, dbType, docPropertyType);
}
@Override
@@ -142,8 +137,8 @@ public class ScalarTypeJsonSet {
final String pgType;
- PgBase(int jdbcType, String pgType) {
- super(jdbcType);
+ PgBase(int jdbcType, String pgType, DocPropertyType docPropertyType) {
+ super(jdbcType, docPropertyType);
this.pgType = pgType;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBase.java b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBase.java
index 10b0a199b..9c9a18bae 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBase.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/ScalarTypeUUIDBase.java
@@ -89,6 +89,6 @@ public abstract class ScalarTypeUUIDBase extends ScalarTypeBase {
@Override
public DocPropertyType getDocType() {
- return DocPropertyType.STRING;
+ return DocPropertyType.UUID;
}
}
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 bf7ed1b62..ac967797c 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/type/TypeManager.java
@@ -1,5 +1,6 @@
package com.avaje.ebeaninternal.server.type;
+import com.avaje.ebean.annotation.DbArray;
import com.avaje.ebeaninternal.server.type.reflect.CheckImmutableResponse;
import java.lang.reflect.Type;
@@ -66,4 +67,9 @@ public interface TypeManager {
*
*/
ScalarType> getJsonScalarType(Class> type, int dbType, int dbLength, Type genericType);
+
+ /**
+ * Return the ScalarType used to handle DB ARRAY.
+ */
+ ScalarType> getArrayScalarType(Class> type, DbArray dbArray, Type genericType);
}
diff --git a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java
index 48e826db2..83ab39c3d 100644
--- a/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java
+++ b/src/main/java/com/avaje/ebeanservice/docstore/api/mapping/DocPropertyType.java
@@ -10,6 +10,11 @@ public enum DocPropertyType {
*/
ENUM,
+ /**
+ * A UUID is a String Id implying it should not be analysed.
+ */
+ UUID,
+
/**
* String.
*/
diff --git a/src/test/java/com/avaje/ebean/BaseTestCase.java b/src/test/java/com/avaje/ebean/BaseTestCase.java
index c8b31599f..077591532 100644
--- a/src/test/java/com/avaje/ebean/BaseTestCase.java
+++ b/src/test/java/com/avaje/ebean/BaseTestCase.java
@@ -32,6 +32,11 @@ public class BaseTestCase {
return spi.getDatabasePlatform().getName().equals("h2");
}
+ public boolean isPostgres() {
+ SpiEbeanServer spi = (SpiEbeanServer)Ebean.getDefaultServer();
+ return spi.getDatabasePlatform().getName().equals("postgres");
+ }
+
/**
* Wait for the L2 cache to propagate changes post-commit.
*/
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/TDSpiExpressionRequest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/TDSpiExpressionRequest.java
index 478897e38..33f1f207d 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/expression/TDSpiExpressionRequest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/TDSpiExpressionRequest.java
@@ -1,7 +1,7 @@
package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebeaninternal.api.SpiExpressionRequest;
-import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
+import com.avaje.ebeaninternal.server.core.DbExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
@@ -22,7 +22,7 @@ public class TDSpiExpressionRequest implements SpiExpressionRequest {
}
@Override
- public JsonExpressionHandler getJsonHandler() {
+ public DbExpressionHandler getDbPlatformHandler() {
return null;
}
diff --git a/src/test/java/com/avaje/tests/model/array/EArrayBean.java b/src/test/java/com/avaje/tests/model/array/EArrayBean.java
new file mode 100644
index 000000000..a16670a8c
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/array/EArrayBean.java
@@ -0,0 +1,80 @@
+package com.avaje.tests.model.array;
+
+
+import com.avaje.ebean.annotation.DbArray;
+
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Version;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+
+@Entity
+public class EArrayBean {
+
+ @Id
+ Long id;
+
+ String name;
+
+ @DbArray(length = 300)
+ List phoneNumbers = new ArrayList();
+
+ @DbArray
+ List uids = new ArrayList();
+
+ @DbArray
+ List otherIds = 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 getPhoneNumbers() {
+ return phoneNumbers;
+ }
+
+ public void setPhoneNumbers(List phoneNumbers) {
+ this.phoneNumbers = phoneNumbers;
+ }
+
+ public List getUids() {
+ return uids;
+ }
+
+ public void setUids(List uids) {
+ this.uids = uids;
+ }
+
+ public List getOtherIds() {
+ return otherIds;
+ }
+
+ public void setOtherIds(List otherIds) {
+ this.otherIds = otherIds;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+}
diff --git a/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java b/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java
new file mode 100644
index 000000000..4ca0863e6
--- /dev/null
+++ b/src/test/java/com/avaje/tests/model/array/TestDbArray_basic.java
@@ -0,0 +1,113 @@
+package com.avaje.tests.model.array;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.Query;
+import org.avaje.ebeantest.LoggedSqlCollector;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.assertEquals;
+
+public class TestDbArray_basic extends BaseTestCase {
+
+ private EArrayBean bean = new EArrayBean();
+
+ private EArrayBean found;
+
+ @Test
+ public void insert() {
+
+ bean.setName("some stuff");
+
+ List phNumbers = bean.getPhoneNumbers();
+ phNumbers.add("4321");
+ phNumbers.add("9823");
+
+ bean.getUids().add(UUID.randomUUID());
+ bean.getUids().add(UUID.randomUUID());
+ bean.getOtherIds().add(95L);
+ bean.getOtherIds().add(96L);
+ bean.getOtherIds().add(97L);
+
+ Ebean.save(bean);
+
+ found = Ebean.find(EArrayBean.class, bean.getId());
+
+ assertThat(found.getPhoneNumbers()).containsExactly("4321", "9823");
+
+ if (isPostgres()) {
+ Query query = Ebean.find(EArrayBean.class)
+ .where()
+ .arrayContains("otherIds", 96L, 97L)
+ .arrayContains("uids", bean.getUids().get(0))
+ .arrayContains("phoneNumbers", "9823")
+ .arrayIsNotEmpty("phoneNumbers")
+ .query();
+
+ List list = query.findList();
+
+ assertThat(query.getGeneratedSql()).contains(" t0.other_ids @> array[?,?]::integer[] ");
+ assertThat(query.getGeneratedSql()).contains(" t0.uids @> array[?] ");
+ assertThat(query.getGeneratedSql()).contains(" t0.phone_numbers @> array[?] ");
+ assertThat(query.getGeneratedSql()).contains(" coalesce(cardinality(t0.phone_numbers),0) <> 0");
+ assertThat(list).hasSize(1);
+
+ query = Ebean.find(EArrayBean.class)
+ .where()
+ .arrayIsEmpty("otherIds")
+ .arrayNotContains("uids", bean.getUids().get(0))
+ .query();
+ query.findList();
+
+ assertThat(query.getGeneratedSql()).contains(" coalesce(cardinality(t0.other_ids),0) = 0");
+ assertThat(query.getGeneratedSql()).contains(" not (t0.uids @> array[?])");
+ }
+
+ 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("\"phoneNumbers\":[\"4321\",\"9823\"]");
+ assertThat(asJson).contains("\"id\":");
+
+ EArrayBean fromJson = Ebean.json().toBean(EArrayBean.class, asJson);
+ assertEquals(found.getId(), fromJson.getId());
+ assertEquals(found.getId(), fromJson.getId());
+ assertEquals(found.getName(), fromJson.getName());
+ assertThat(fromJson.getPhoneNumbers()).containsExactly("4321", "9823");
+ }
+
+ //@Test//(dependsOnMethods = "insert")
+ public void update_when_notDirty() {
+
+ found.setName("jack");
+ 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 earray_bean set name=?, version=? where");
+ }
+
+ //@Test//(dependsOnMethods = "update_when_notDirty")
+ public void update_when_dirty() {
+
+ found.getPhoneNumbers().add("9987");
+ found.getUids().add(UUID.randomUUID());
+
+ LoggedSqlCollector.start();
+ Ebean.save(found);
+ List sql = LoggedSqlCollector.stop();
+
+ assertThat(sql.get(0)).contains("update earray_bean set phone_numbers=?, uids=?, version=? where");
+ }
+}
\ No newline at end of file