mirror of
https://github.com/ebean-orm/ebean.git
synced 2026-10-24 15:55:55 +00:00
#695 - ENH: Add @DbArray for storing scalar types (UUID, Long, String) to Postgres ARRAY type
This commit is contained in:
@@ -9,7 +9,7 @@
|
||||
|
||||
<groupId>org.avaje.ebeanorm</groupId>
|
||||
<artifactId>avaje-ebeanorm</artifactId>
|
||||
<version>7.10.2-SNAPSHOT</version>
|
||||
<version>7.11.1-SNAPSHOT</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>avaje-ebeanorm</name>
|
||||
|
||||
@@ -87,6 +87,38 @@ public interface ExpressionFactory {
|
||||
*/
|
||||
Expression jsonBetween(String propertyName, String path, Object lowerValue, Object upperValue);
|
||||
|
||||
/**
|
||||
* Array contains all the given values.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
Expression arrayContains(String propertyName, Object... values);
|
||||
|
||||
/**
|
||||
* Array does not contain the given values.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
Expression arrayNotContains(String propertyName, Object... values);
|
||||
|
||||
/**
|
||||
* Array is empty - for the given array property.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
Expression arrayIsEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Array is not empty - for the given array property.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
Expression arrayIsNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Equal To - property equal to the given value.
|
||||
*/
|
||||
|
||||
@@ -801,6 +801,35 @@ public interface ExpressionList<T> {
|
||||
*/
|
||||
ExpressionList<T> allEq(Map<String, Object> propertyMap);
|
||||
|
||||
/**
|
||||
* Array property contains entries with the given values.
|
||||
*/
|
||||
ExpressionList<T> arrayContains(String propertyName, Object... values);
|
||||
|
||||
/**
|
||||
* Array does not contain the given values.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> arrayNotContains(String propertyName, Object... values);
|
||||
|
||||
/**
|
||||
* Array is empty - for the given array property.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> arrayIsEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Array is not empty - for the given array property.
|
||||
* <p>
|
||||
* Array support is effectively limited to Postgres at this time.
|
||||
* </p>
|
||||
*/
|
||||
ExpressionList<T> arrayIsNotEmpty(String propertyName);
|
||||
|
||||
/**
|
||||
* Add raw expression with a single parameter.
|
||||
* <p>
|
||||
|
||||
@@ -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.
|
||||
* <p>
|
||||
* 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.
|
||||
* </p>
|
||||
*
|
||||
* <h3>Example:</h3>
|
||||
* <pre>{@code
|
||||
*
|
||||
* // Store as ARRAY of UUID on Postgres
|
||||
* @DbArray
|
||||
* List<UUID> uids = new ArrayList<>();
|
||||
*
|
||||
* // Store as ARRAY on Postgres
|
||||
* @DbArray
|
||||
* List<String> phoneNumbers = new ArrayList<>();
|
||||
*
|
||||
* // Store as ARRAY of INTEGER on Postgres
|
||||
* @DbArray
|
||||
* List<Long> someLongs = new ArrayList<>();
|
||||
*
|
||||
* }</pre>
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.FIELD)
|
||||
public @interface DbArray {
|
||||
|
||||
/**
|
||||
* For VARCHAR storage specify the column length (defaults to 1000).
|
||||
*/
|
||||
int length() default 0;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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<Object> 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<Object> getBindValues();
|
||||
|
||||
/**
|
||||
* Increments the parameter index and returns that value.
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
* <p>
|
||||
* This is generally necessary for JSON expressions as text values always returned from the json operators used.
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -826,6 +826,30 @@ public class DefaultExpressionList<T> implements SpiExpressionList<T> {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayContains(String propertyName, Object... elementValue) {
|
||||
add(expr.arrayContains(propertyName, elementValue));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayNotContains(String propertyName, Object... values) {
|
||||
add(expr.arrayNotContains(propertyName, values));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayIsEmpty(String propertyName) {
|
||||
add(expr.arrayIsEmpty(propertyName));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayIsNotEmpty(String propertyName) {
|
||||
add(expr.arrayIsNotEmpty(propertyName));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> raw(String raw, Object value) {
|
||||
add(expr.raw(raw, value));
|
||||
|
||||
+3
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -462,6 +462,26 @@ class JunctionExpression<T> implements SpiJunction<T>, SpiExpression, Expression
|
||||
return exprList.jsonBetween(propertyName, path, lowerValue, upperValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayContains(String propertyName, Object... values) {
|
||||
return exprList.arrayContains(propertyName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayNotContains(String propertyName, Object... values) {
|
||||
return exprList.arrayNotContains(propertyName, values);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayIsEmpty(String propertyName) {
|
||||
return exprList.arrayIsEmpty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> arrayIsNotEmpty(String propertyName) {
|
||||
return exprList.arrayIsNotEmpty(propertyName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ExpressionList<T> ge(String propertyName, Object value) {
|
||||
return exprList.ge(propertyName, value);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -335,7 +335,7 @@ public class CQuery<T> implements DbReadContext, CancelableQuery {
|
||||
pstmt.setFetchSize(query.getBufferFetchSizeHint());
|
||||
}
|
||||
|
||||
DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt);
|
||||
DataBind dataBind = queryPlan.bindEncryptedProperties(pstmt, conn);
|
||||
bindLog = predicates.bind(dataBind);
|
||||
|
||||
// executeQuery
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.avaje.ebeaninternal.server.type;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Type conversion for use with ScalarTypeArrayList.
|
||||
*/
|
||||
public interface ArrayElementConverter<T> {
|
||||
|
||||
/**
|
||||
* 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<Long> {
|
||||
|
||||
@Override
|
||||
public Long toElement(Object rawValue) {
|
||||
if (rawValue instanceof Long) {
|
||||
return (Long)rawValue;
|
||||
} else {
|
||||
return ((Number)rawValue).longValue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IntegerConverter implements ArrayElementConverter<Integer> {
|
||||
|
||||
@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<String> {
|
||||
}
|
||||
|
||||
/**
|
||||
* UUID converter (noop based).
|
||||
*/
|
||||
class UuidConverter extends NoopConverter<UUID> {
|
||||
}
|
||||
|
||||
class NoopConverter<T> implements ArrayElementConverter<T> {
|
||||
|
||||
@Override
|
||||
public T toElement(Object rawValue) {
|
||||
return (T)rawValue;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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<List> {
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,8 +8,11 @@ import java.io.IOException;
|
||||
|
||||
abstract class ScalarTypeJsonCollection<T> extends ScalarTypeBase<T> {
|
||||
|
||||
public ScalarTypeJsonCollection(Class<T> type, int dbType) {
|
||||
protected DocPropertyType docPropertyType;
|
||||
|
||||
public ScalarTypeJsonCollection(Class<T> type, int dbType, DocPropertyType docPropertyType) {
|
||||
super(type, false, dbType);
|
||||
this.docPropertyType = docPropertyType;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,7 +44,7 @@ abstract class ScalarTypeJsonCollection<T> extends ScalarTypeBase<T> {
|
||||
|
||||
@Override
|
||||
public DocPropertyType getDocType() {
|
||||
return DocPropertyType.LIST;
|
||||
return docPropertyType;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -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<List> VARCHAR = new ScalarTypeJsonList.Varchar();
|
||||
|
||||
public static final ScalarTypeJsonCollection<List> JSON = new ScalarTypeJsonList.Json();
|
||||
|
||||
public static final ScalarTypeJsonCollection<List> 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<List> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<Object> {
|
||||
|
||||
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<Set> {
|
||||
|
||||
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<List> {
|
||||
|
||||
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<Map> {
|
||||
|
||||
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<T> cls, ObjectMapper objectMapper, Type type, int dbType, String pgType) {
|
||||
public Base(Class<T> 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
|
||||
|
||||
@@ -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<Set> VARCHAR = new ScalarTypeJsonSet.Varchar();
|
||||
|
||||
private static final ScalarTypeJsonCollection<Set> JSON = new ScalarTypeJsonSet.Json();
|
||||
|
||||
private static final ScalarTypeJsonCollection<Set> 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<Set> {
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -89,6 +89,6 @@ public abstract class ScalarTypeUUIDBase extends ScalarTypeBase<UUID> {
|
||||
|
||||
@Override
|
||||
public DocPropertyType getDocType() {
|
||||
return DocPropertyType.STRING;
|
||||
return DocPropertyType.UUID;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
* </p>
|
||||
*/
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,11 @@ public enum DocPropertyType {
|
||||
*/
|
||||
ENUM,
|
||||
|
||||
/**
|
||||
* A UUID is a String Id implying it should not be analysed.
|
||||
*/
|
||||
UUID,
|
||||
|
||||
/**
|
||||
* String.
|
||||
*/
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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<String> phoneNumbers = new ArrayList<String>();
|
||||
|
||||
@DbArray
|
||||
List<UUID> uids = new ArrayList<UUID>();
|
||||
|
||||
@DbArray
|
||||
List<Long> otherIds = new ArrayList<Long>();
|
||||
|
||||
@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<String> getPhoneNumbers() {
|
||||
return phoneNumbers;
|
||||
}
|
||||
|
||||
public void setPhoneNumbers(List<String> phoneNumbers) {
|
||||
this.phoneNumbers = phoneNumbers;
|
||||
}
|
||||
|
||||
public List<UUID> getUids() {
|
||||
return uids;
|
||||
}
|
||||
|
||||
public void setUids(List<UUID> uids) {
|
||||
this.uids = uids;
|
||||
}
|
||||
|
||||
public List<Long> getOtherIds() {
|
||||
return otherIds;
|
||||
}
|
||||
|
||||
public void setOtherIds(List<Long> otherIds) {
|
||||
this.otherIds = otherIds;
|
||||
}
|
||||
|
||||
public Long getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(Long version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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<EArrayBean> query = Ebean.find(EArrayBean.class)
|
||||
.where()
|
||||
.arrayContains("otherIds", 96L, 97L)
|
||||
.arrayContains("uids", bean.getUids().get(0))
|
||||
.arrayContains("phoneNumbers", "9823")
|
||||
.arrayIsNotEmpty("phoneNumbers")
|
||||
.query();
|
||||
|
||||
List<EArrayBean> 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<String> 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<String> sql = LoggedSqlCollector.stop();
|
||||
|
||||
assertThat(sql.get(0)).contains("update earray_bean set phone_numbers=?, uids=?, version=? where");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user