From 5726410f6f9fafd04954cf5ee07ac1c5ace9b41b Mon Sep 17 00:00:00 2001 From: Robin Bygrave Date: Mon, 21 Sep 2015 16:20:14 +1200 Subject: [PATCH] #417 - ENH: Add JSON expressions ... path exists and value at path equals value --- .../com/avaje/ebean/ExpressionFactory.java | 40 +++++++++++ .../java/com/avaje/ebean/ExpressionList.java | 56 ++++++++++++++- .../api/SpiExpressionRequest.java | 6 ++ .../server/core/InternalConfiguration.java | 22 +++++- .../server/core/JsonExpressionHandler.java | 17 +++++ .../core/NotSupportedJsonExpression.java | 15 ++++ .../server/core/OracleJsonExpression.java | 24 +++++++ .../server/core/PostgresJsonExpression.java | 71 ++++++++++++++++++ .../expression/DefaultExampleExpression.java | 2 +- .../expression/DefaultExpressionFactory.java | 52 +++++++++++--- .../server/expression/JsonPathExpression.java | 69 ++++++++++++++++++ .../server/expression/JunctionExpression.java | 64 +++++++++++++++++ .../ebeaninternal/server/expression/Op.java | 62 ++++++++++++++++ .../server/expression/SimpleExpression.java | 14 ---- .../ebeaninternal/server/persist/Binder.java | 13 +++- .../server/query/CQueryPredicates.java | 6 +- .../util/DefaultExpressionList.java | 72 +++++++++++++++++++ .../util/DefaultExpressionRequest.java | 12 +++- 18 files changed, 584 insertions(+), 33 deletions(-) create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java create mode 100644 src/main/java/com/avaje/ebeaninternal/server/expression/Op.java diff --git a/src/main/java/com/avaje/ebean/ExpressionFactory.java b/src/main/java/com/avaje/ebean/ExpressionFactory.java index 4d9a5e2fc..72b07e117 100644 --- a/src/main/java/com/avaje/ebean/ExpressionFactory.java +++ b/src/main/java/com/avaje/ebean/ExpressionFactory.java @@ -36,6 +36,46 @@ import java.util.Map; */ 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. */ diff --git a/src/main/java/com/avaje/ebean/ExpressionList.java b/src/main/java/com/avaje/ebean/ExpressionList.java index f546afa84..3f7692bb1 100644 --- a/src/main/java/com/avaje/ebean/ExpressionList.java +++ b/src/main/java/com/avaje/ebean/ExpressionList.java @@ -345,12 +345,66 @@ public interface ExpressionList extends Serializable { */ ExpressionList 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 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 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 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 jsonNotEqualTo(String propertyName, String path, Object value); + + /** + * Greater than - for the given path in a JSON document. + */ + ExpressionList jsonGreaterThan(String propertyName, String path, Object val); + + /** + * Greater than or equal to - for the given path in a JSON document. + */ + ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val); + + /** + * Less than - for the given path in a JSON document. + */ + ExpressionList jsonLessThan(String propertyName, String path, Object val); + + /** + * Less than or equal to - for the given path in a JSON document. + */ + ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val); + /** * Add an Expression to the list. *

* This returns the list so that add() can be chained. *

- * + * *
{@code
    *
    * Query query = Ebean.find(Customer.class);
diff --git a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java
index 883d6d5ba..3cd185513 100644
--- a/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java
+++ b/src/main/java/com/avaje/ebeaninternal/api/SpiExpressionRequest.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.api;
 
 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;
 
@@ -10,6 +11,11 @@ import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
  */
 public interface SpiExpressionRequest {
 
+  /**
+   * Return the DB specific JSON expression handler.
+   */
+  JsonExpressionHandler getJsonHander();
+
   /**
    * Parse the logical property name to the deployment name.
    */
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 8fad1abf6..9d40af426 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/InternalConfiguration.java
@@ -209,11 +209,29 @@ public class InternalConfiguration {
    * 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());
+    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 NotSupportedJsonExpression();
   }
 
   /**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java b/src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java
new file mode 100644
index 000000000..67ec32bcb
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/JsonExpressionHandler.java
@@ -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);
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java
new file mode 100644
index 000000000..840bd237f
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/NotSupportedJsonExpression.java
@@ -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");
+  }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java
new file mode 100644
index 000000000..c4acf95a9
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/OracleJsonExpression.java
@@ -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());
+    }
+
+  }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java b/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java
new file mode 100644
index 000000000..c61f21f0a
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/core/PostgresJsonExpression.java
@@ -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);
+  }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java
index ee2ffadf7..b357e44da 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExampleExpression.java
@@ -211,7 +211,7 @@ public class DefaultExampleExpression implements SpiExpression, ExampleExpressio
           // 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));
           }
         }
       }
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 42f8f7e05..c66eb28df 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DefaultExpressionFactory.java
@@ -1,9 +1,5 @@
 package com.avaje.ebeaninternal.server.expression;
 
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
 import com.avaje.ebean.ExampleExpression;
 import com.avaje.ebean.Expression;
 import com.avaje.ebean.ExpressionFactory;
@@ -15,6 +11,10 @@ import com.avaje.ebean.bean.EntityBean;
 import com.avaje.ebeaninternal.api.SpiExpressionFactory;
 import com.avaje.ebeaninternal.api.SpiQuery;
 
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Default Expression factory for creating standard expressions.
  */
@@ -36,6 +36,38 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
     return "sql";
   }
 
+  public Expression jsonExists(String propertyName, String path) {
+    return new JsonPathExpression(propertyName, path, Op.EXISTS, null);
+  }
+
+  public Expression jsonNotExists(String propertyName, String path) {
+    return new JsonPathExpression(propertyName, path, Op.NOT_EXISTS, null);
+  }
+
+  public Expression jsonEqualTo(String propertyName, String path, Object val) {
+    return new JsonPathExpression(propertyName, path, Op.EQ, val);
+  }
+
+  public Expression jsonNotEqualTo(String propertyName, String path, Object val) {
+    return new JsonPathExpression(propertyName, path, Op.NOT_EQ, val);
+  }
+
+  public Expression jsonGreaterThan(String propertyName, String path, Object val) {
+    return new JsonPathExpression(propertyName, path, Op.GT, val);
+  }
+
+  public Expression jsonGreaterOrEqual(String propertyName, String path, Object val) {
+    return new JsonPathExpression(propertyName, path, Op.GT_EQ, val);
+  }
+
+  public Expression jsonLessThan(String propertyName, String path, Object val) {
+    return new JsonPathExpression(propertyName, path, Op.LT, val);
+  }
+
+  public Expression jsonLessOrEqualTo(String propertyName, String path, Object val) {
+    return new JsonPathExpression(propertyName, path, Op.LT_EQ, val);
+  }
+
   /**
    * Equal To - property equal to the given value.
    */
@@ -43,7 +75,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
     if (value == null) {
       return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNull(propertyName);
     }
-    return new SimpleExpression(propertyName, SimpleExpression.Op.EQ, value);
+    return new SimpleExpression(propertyName, Op.EQ, value);
   }
 
   /**
@@ -53,7 +85,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
     if (value == null) {
       return equalsWithNullAsNoop ? NoopExpression.INSTANCE : isNotNull(propertyName);
     }
-    return new SimpleExpression(propertyName, SimpleExpression.Op.NOT_EQ, value);
+    return new SimpleExpression(propertyName, Op.NOT_EQ, value);
   }
 
   /**
@@ -88,7 +120,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
    */
   public Expression gt(String propertyName, Object value) {
 
-    return new SimpleExpression(propertyName, SimpleExpression.Op.GT, value);
+    return new SimpleExpression(propertyName, Op.GT, value);
   }
 
   /**
@@ -97,7 +129,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
    */
   public Expression ge(String propertyName, Object value) {
 
-    return new SimpleExpression(propertyName, SimpleExpression.Op.GT_EQ, value);
+    return new SimpleExpression(propertyName, Op.GT_EQ, value);
   }
 
   /**
@@ -105,7 +137,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
    */
   public Expression lt(String propertyName, Object value) {
 
-    return new SimpleExpression(propertyName, SimpleExpression.Op.LT, value);
+    return new SimpleExpression(propertyName, Op.LT, value);
   }
 
   /**
@@ -113,7 +145,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory {
    */
   public Expression le(String propertyName, Object value) {
 
-    return new SimpleExpression(propertyName, SimpleExpression.Op.LT_EQ, value);
+    return new SimpleExpression(propertyName, Op.LT_EQ, value);
   }
 
   /**
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
new file mode 100644
index 000000000..5f3244c0b
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JsonPathExpression.java
@@ -0,0 +1,69 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.event.BeanQueryRequest;
+import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+
+/**
+ * Generally speaking tests the value at a given path in the JSON document.
+ * 

+ * Supports the usual operators (equal to, greater than etc). + *

+ *

+ * The value passed in is expected to be a valid JSON type so string, number, boolean. + *

+ */ +class JsonPathExpression extends AbstractExpression { + + /** + * The path in the JSON document in dot notation form. + */ + protected final String path; + + /** + * The expression operator. + */ + protected final Op operator; + + /** + * The bind value used to compare against the document path value. + */ + protected final Object value; + + JsonPathExpression(String propertyName, String path, Op operator, Object value) { + super(propertyName); + this.path = path; + this.operator = operator; + this.value = value; + } + + @Override + public void queryAutoTuneHash(HashQueryPlanBuilder builder) { + builder.add(JsonPathExpression.class).add(propName).add(path).add(operator); + } + + @Override + public void queryPlanHash(BeanQueryRequest request, HashQueryPlanBuilder builder) { + queryAutoTuneHash(builder); + } + + @Override + public int queryBindHash() { + return (value == null) ? 0 : value.hashCode(); + } + + @Override + public void addSql(SpiExpressionRequest request) { + + // Use DB specific expression handling (Postgres and Oracle supported) + request.getJsonHander().addSql(request, propName, path, operator, value); + } + + @Override + public void addBindValues(SpiExpressionRequest request) { + + if (value != null) { + request.addBindValue(value); + } + } +} 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 d4a6ee80b..71fdae0be 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java @@ -313,6 +313,70 @@ abstract class JunctionExpression implements Junction, SpiExpression, Expr return exprList.findUnique(); } + /** + * Path exists - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonExists(String propertyName, String path) { + return exprList.jsonExists(propertyName, path); + } + + /** + * Path does not exist - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonNotExists(String propertyName, String path){ + return exprList.jsonNotExists(propertyName, path); + } + + /** + * Equal to - for the value at the given path in the JSON document. + */ + @Override + public ExpressionList jsonEqualTo(String propertyName, String path, Object value){ + return exprList.jsonEqualTo(propertyName, path, value); + } + + /** + * Not Equal to - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonNotEqualTo(String propertyName, String path, Object val){ + return exprList.jsonNotEqualTo(propertyName, path, val); + } + + /** + * Greater than - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonGreaterThan(String propertyName, String path, Object val){ + return exprList.jsonGreaterThan(propertyName, path, val); + } + + /** + * Greater than or equal to - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val){ + return exprList.jsonGreaterOrEqual(propertyName, path, val); + } + + /** + * Less than - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonLessThan(String propertyName, String path, Object val){ + return exprList.jsonLessThan(propertyName, path, val); + } + + /** + * Less than or equal to - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val){ + return exprList.jsonLessOrEqualTo(propertyName, path, val); + } + @Override public ExpressionList ge(String propertyName, Object value) { return exprList.ge(propertyName, value); diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java b/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java new file mode 100644 index 000000000..bb41a1191 --- /dev/null +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/Op.java @@ -0,0 +1,62 @@ +package com.avaje.ebeaninternal.server.expression; + +/** + * Simple operators - equals, greater than, less than etc. + */ +public enum Op { + + /** + * Exists (JSON). + */ + EXISTS(" is not null "), + + /** + * Not Exists (JSON). + */ + NOT_EXISTS(" is null "), + + /** + * Equal to + */ + EQ(" = ? "), + + /** + * Not equal to. + */ + NOT_EQ(" <> ? "), + + /** + * + * Less than. + */ + + LT(" < ? "), + + /** + * Less than or equal to. + */ + LT_EQ(" <= ? "), + + /** + * Greater than. + */ + GT(" > ? "), + + /** + * Greater than or equal to. + */ + GT_EQ(" >= ? "); + + final String exp; + + Op(String exp) { + this.exp = exp; + } + + /** + * Return the bind expression include JDBC ? bind placeholder. + */ + public String bind() { + return exp; + } +} diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java index daf90920d..9d2257305 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java +++ b/src/main/java/com/avaje/ebeaninternal/server/expression/SimpleExpression.java @@ -10,20 +10,6 @@ public class SimpleExpression extends AbstractExpression { private static final long serialVersionUID = -382881395755603790L; - enum Op { - EQ(" = ? "), NOT_EQ(" <> ? "), LT(" < ? "), LT_EQ(" <= ? "), GT(" > ? "), GT_EQ(" >= ? "); - - final String exp; - - Op(String exp) { - this.exp = exp; - } - - public String bind() { - return exp; - } - } - private final Op type; private final Object 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 e2aeab3a5..c853ffc00 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java +++ b/src/main/java/com/avaje/ebeaninternal/server/persist/Binder.java @@ -10,6 +10,7 @@ import java.util.List; import javax.persistence.PersistenceException; import com.avaje.ebeaninternal.api.BindParams; +import com.avaje.ebeaninternal.server.core.JsonExpressionHandler; import com.avaje.ebeaninternal.server.core.Message; import com.avaje.ebeaninternal.server.type.DataBind; import com.avaje.ebeaninternal.server.type.ScalarType; @@ -30,13 +31,16 @@ public class Binder { private final boolean bindAsOfWithFromClause; + private final JsonExpressionHandler jsonExpressionHandler; + /** * Set the PreparedStatement with which to bind variables to. */ - public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause) { + public Binder(TypeManager typeManager, int asOfBindCount, boolean bindAsOfWithFromClause, JsonExpressionHandler jsonExpressionHandler) { this.typeManager = typeManager; this.asOfBindCount = asOfBindCount; this.bindAsOfWithFromClause = bindAsOfWithFromClause; + this.jsonExpressionHandler = jsonExpressionHandler; } /** @@ -375,4 +379,11 @@ public class Binder { return false; } } + + /** + * Return the JsonExpressionHandler specific to the database. + */ + public JsonExpressionHandler getJsonExpressionHandler() { + return jsonExpressionHandler; + } } 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 c0dd5851e..236d3ebd2 100644 --- a/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java +++ b/src/main/java/com/avaje/ebeaninternal/server/query/CQueryPredicates.java @@ -302,7 +302,7 @@ public class CQueryPredicates { SpiExpressionList whereExp = query.getWhereExpressions(); if (whereExp != null) { - DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser); + DefaultExpressionRequest whereReq = new DefaultExpressionRequest(request, deployParser, binder); whereExprBindValues = whereExp.buildBindValues(whereReq); if (buildSql) { whereExprSql = whereExp.buildSql(whereReq); @@ -314,7 +314,7 @@ public class CQueryPredicates { OrmQueryProperties chunk = query.getDetail().getChunk(manyProperty.getName(), false); SpiExpressionList filterMany = chunk.getFilterMany(); if (filterMany != null) { - DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser); + DefaultExpressionRequest filterReq = new DefaultExpressionRequest(request, deployParser, binder); filterManyExprBindValues = filterMany.buildBindValues(filterReq); if (buildSql) { filterManyExprSql = filterMany.buildSql(filterReq); @@ -325,7 +325,7 @@ public class CQueryPredicates { // having expression SpiExpressionList havingExpr = query.getHavingExpressions(); if (havingExpr != null) { - DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser); + DefaultExpressionRequest havingReq = new DefaultExpressionRequest(request, deployParser, binder); havingExprBindValues = havingExpr.buildBindValues(havingReq); if (buildSql) { havingExprSql = havingExpr.buildSql(havingReq); diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java index 1581948db..f59733a0a 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionList.java @@ -363,6 +363,78 @@ public class DefaultExpressionList implements SpiExpressionList { return hash; } + /** + * Path exists - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonExists(String propertyName, String path) { + add(expr.jsonExists(propertyName, path)); + return this; + } + + /** + * Path does not exist - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonNotExists(String propertyName, String path){ + add(expr.jsonNotExists(propertyName, path)); + return this; + } + + /** + * Equal to expression for the value at the given path in the JSON document. + */ + @Override + public ExpressionList jsonEqualTo(String propertyName, String path, Object value){ + add(expr.jsonEqualTo(propertyName, path, value)); + return this; + } + + /** + * Not Equal to - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonNotEqualTo(String propertyName, String path, Object val){ + add(expr.jsonNotEqualTo(propertyName, path, val)); + return this; + } + + /** + * Greater than - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonGreaterThan(String propertyName, String path, Object val){ + add(expr.jsonGreaterThan(propertyName, path, val)); + return this; + } + + /** + * Greater than or equal to - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonGreaterOrEqual(String propertyName, String path, Object val){ + add(expr.jsonGreaterOrEqual(propertyName, path, val)); + return this; + } + + /** + * Less than - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonLessThan(String propertyName, String path, Object val){ + add(expr.jsonLessThan(propertyName, path, val)); + return this; + } + + /** + * Less than or equal to - for the given path in a JSON document. + */ + @Override + public ExpressionList jsonLessOrEqualTo(String propertyName, String path, Object val){ + add(expr.jsonLessOrEqualTo(propertyName, path, val)); + return this; + } + @Override public ExpressionList eq(String propertyName, Object value) { add(expr.eq(propertyName, value)); diff --git a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java index 10e469a09..6a62aca31 100644 --- a/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java +++ b/src/main/java/com/avaje/ebeaninternal/util/DefaultExpressionRequest.java @@ -3,9 +3,11 @@ package com.avaje.ebeaninternal.util; import java.util.ArrayList; import com.avaje.ebeaninternal.api.SpiExpressionRequest; +import com.avaje.ebeaninternal.server.core.JsonExpressionHandler; import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest; import com.avaje.ebeaninternal.server.deploy.BeanDescriptor; import com.avaje.ebeaninternal.server.deploy.DeployParser; +import com.avaje.ebeaninternal.server.persist.Binder; public class DefaultExpressionRequest implements SpiExpressionRequest { @@ -19,18 +21,26 @@ public class DefaultExpressionRequest implements SpiExpressionRequest { private final DeployParser deployParser; + private final Binder binder; + private int paramIndex; - public DefaultExpressionRequest(SpiOrmQueryRequest queryRequest, DeployParser deployParser) { + public DefaultExpressionRequest(SpiOrmQueryRequest queryRequest, DeployParser deployParser, Binder binder) { this.queryRequest = queryRequest; this.beanDescriptor = queryRequest.getBeanDescriptor(); this.deployParser = deployParser; + this.binder = binder; } public DefaultExpressionRequest(BeanDescriptor beanDescriptor) { this.beanDescriptor = beanDescriptor; this.queryRequest = null; this.deployParser = null; + this.binder = null; + } + + public JsonExpressionHandler getJsonHander() { + return binder.getJsonExpressionHandler(); } public String parseDeploy(String logicalProp) {