Skip to content

Commit

Permalink
#417 - ENH: Add JSON expressions ... path exists and value at path eq…
Browse files Browse the repository at this point in the history
…uals value
  • Loading branch information
rbygrave committed Sep 21, 2015
1 parent 054f7dc commit 5726410
Show file tree
Hide file tree
Showing 18 changed files with 584 additions and 33 deletions.
40 changes: 40 additions & 0 deletions src/main/java/com/avaje/ebean/ExpressionFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,46 @@
*/
public interface ExpressionFactory {

/**
* Path exists - for the given path in a JSON document.
*/
Expression jsonExists(String propertyName, String path);

/**
* Path does not exist - for the given path in a JSON document.
*/
Expression jsonNotExists(String propertyName, String path);

/**
* Equal to - for the given path in a JSON document.
*/
Expression jsonEqualTo(String propertyName, String path, Object val);

/**
* Not Equal to - for the given path in a JSON document.
*/
Expression jsonNotEqualTo(String propertyName, String path, Object val);

/**
* Greater than - for the given path in a JSON document.
*/
Expression jsonGreaterThan(String propertyName, String path, Object val);

/**
* Greater than or equal to - for the given path in a JSON document.
*/
Expression jsonGreaterOrEqual(String propertyName, String path, Object val);

/**
* Less than - for the given path in a JSON document.
*/
Expression jsonLessThan(String propertyName, String path, Object val);

/**
* Less than or equal to - for the given path in a JSON document.
*/
Expression jsonLessOrEqualTo(String propertyName, String path, Object val);

/**
* Equal To - property equal to the given value.
*/
Expand Down
56 changes: 55 additions & 1 deletion src/main/java/com/avaje/ebean/ExpressionList.java
Original file line number Diff line number Diff line change
Expand Up @@ -345,12 +345,66 @@ public interface ExpressionList<T> extends Serializable {
*/
ExpressionList<T> where();

/**
* Path exists - for the given path in a JSON document.
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
*/
ExpressionList<T> jsonExists(String propertyName, String path);

/**
* Path does not exist - for the given path in a JSON document.
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
*/
ExpressionList<T> jsonNotExists(String propertyName, String path);

/**
* Equal to expression for the value at the given path in the JSON document.
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
* @param value the value used to test equality against the document path's value
*/
ExpressionList<T> jsonEqualTo(String propertyName, String path, Object value);

/**
* Not Equal to - for the given path in a JSON document.
*
* @param propertyName the property that holds a JSON document
* @param path the nested path in the JSON document in dot notation
* @param value the value used to test equality against the document path's value
*/
ExpressionList<T> jsonNotEqualTo(String propertyName, String path, Object value);

/**
* Greater than - for the given path in a JSON document.
*/
ExpressionList<T> jsonGreaterThan(String propertyName, String path, Object val);

/**
* Greater than or equal to - for the given path in a JSON document.
*/
ExpressionList<T> jsonGreaterOrEqual(String propertyName, String path, Object val);

/**
* Less than - for the given path in a JSON document.
*/
ExpressionList<T> jsonLessThan(String propertyName, String path, Object val);

/**
* Less than or equal to - for the given path in a JSON document.
*/
ExpressionList<T> jsonLessOrEqualTo(String propertyName, String path, Object val);

/**
* Add an Expression to the list.
* <p>
* This returns the list so that add() can be chained.
* </p>
*
*
* <pre>{@code
*
* Query<Customer> query = Ebean.find(Customer.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.ArrayList;

import com.avaje.ebeaninternal.server.core.JsonExpressionHandler;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;

Expand All @@ -10,6 +11,11 @@
*/
public interface SpiExpressionRequest {

/**
* Return the DB specific JSON expression handler.
*/
JsonExpressionHandler getJsonHander();

/**
* Parse the logical property name to the deployment name.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,29 @@ public ReadAuditPrepare getReadAuditPrepare() {
* For 'As Of' queries return the number of bind variables per predicate.
*/
private Binder getBinder(TypeManager typeManager, DatabasePlatform databasePlatform) {

JsonExpressionHandler jsonHandler = getJsonExpressionHandler(databasePlatform);

DbHistorySupport historySupport = databasePlatform.getHistorySupport();
if (historySupport == null) {
return new Binder(typeManager, 0, false);
return new Binder(typeManager, 0, false, jsonHandler);
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause(), jsonHandler);
}

/**
* Return the JSON expression handler for the given database platform.
*/
private JsonExpressionHandler getJsonExpressionHandler(DatabasePlatform databasePlatform) {

String name = databasePlatform.getName();
if ("postgres".equalsIgnoreCase(name)) {
return new PostgresJsonExpression();
}
if ("oracle".equalsIgnoreCase(name)) {
return new OracleJsonExpression();
}
return new Binder(typeManager, historySupport.getBindCount(), historySupport.isBindWithFromClause());
return new NotSupportedJsonExpression();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
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);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
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");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
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());
}

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
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 PostgresJsonExpression implements JsonExpressionHandler {

@Override
public void addSql(SpiExpressionRequest request, String propName, String path, Op operator, Object value) {

StringBuilder sb = new StringBuilder(50);
String[] paths = path.split("\\.");
if (paths.length == 1) {
// (t0.content ->> 'title') = 'Some value'
sb.append("(").append(propName).append(" ->> '").append(path).append("')");

} else {
// (t0.content #>> '{path,inner}') = 'Some value'
sb.append("(").append(propName).append(" #>> '{");
for (int i = 0; i < paths.length; i++) {
if (i > 0) {
sb.append(",");
}
sb.append(paths[i]);
}
sb.append("}')");
}

request.append(castType(sb.toString(), 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) {

if (value == null) {
// for exists and not-exists expressions
return expression;
}

// Postgres cast of returned text value
if (isIntegerType(value)) {
return expression+"::INTEGER";
}
if (isNumberType(value)) {
return expression+"::DECIMAL";
}
if (isBooleanType(value)) {
return expression+"::BOOLEAN";
}

return expression;
}

private boolean isBooleanType(Object value) {
return (value instanceof Boolean);
}

private boolean isIntegerType(Object value) {
return (value instanceof Integer) || (value instanceof Long);
}

private boolean isNumberType(Object value) {
return (value instanceof Number);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ private ArrayList<SpiExpression> buildExpressions(BeanQueryRequest<?> request) {
// exclude the zero values typically to weed out
// primitive int and long that initialise to 0
if (includeZeros || !isZero(value)) {
list.add(new SimpleExpression(propName, SimpleExpression.Op.EQ, value));
list.add(new SimpleExpression(propName, Op.EQ, value));
}
}
}
Expand Down
Loading

0 comments on commit 5726410

Please sign in to comment.