(type, wrapper);
+ } else {
+ return wrapper;
+ }
+ }
+
/**
* Write being aware if it is the Top level "text" expressions.
*
@@ -94,6 +116,7 @@ public class DefaultExpressionList implements SpiExpressionList {
// this is a Top level "text" expressions so we may need to wrap in Bool SHOULD etc.
if (list.isEmpty()) throw new IllegalStateException("empty expression list?");
+ if (allDocNestedPath!=null) context.startNested(allDocNestedPath);
int size = list.size();
SpiExpression first = list.get(0);
@@ -124,11 +147,13 @@ public class DefaultExpressionList implements SpiExpressionList {
if (implicitBool || explicitBool) {
context.endBoolGroup();
}
+ if (allDocNestedPath!=null) context.endNested();
}
}
public void writeDocQuery(DocQueryContext context, SpiExpression idEquals) throws IOException {
+ if (allDocNestedPath!=null) context.startNested(allDocNestedPath);
int size = list.size();
if (size == 1 && idEquals == null) {
// only 1 expression - skip bool
@@ -147,6 +172,7 @@ public class DefaultExpressionList implements SpiExpressionList {
}
context.endBool();
}
+ if (allDocNestedPath!=null) context.endNested();
}
@Override
@@ -975,4 +1001,30 @@ public class DefaultExpressionList implements SpiExpressionList {
return junction(Junction.Type.MUST_NOT);
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ // effectively handled by JunctionExpression
+ return null;
+ }
+
+ /**
+ * Set the nested path that all contained expressions share.
+ */
+ public void setAllDocNested(String allDocNestedPath) {
+ this.allDocNestedPath = allDocNestedPath;
+ }
+
+ /**
+ * Replace the underlying expression list with one organised by nested path.
+ */
+ public void setUnderlying(List groupedByNesting) {
+ this.list = groupedByNesting;
+ }
+
+ /**
+ * Prepare expressions for document store nested path handling.
+ */
+ public void prepareDocNested(BeanDescriptor beanDescriptor) {
+ PrepareDocNested.prepare(this, beanDescriptor);
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java b/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java
index 123efb830..c2b9b620f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/DocQueryContext.java
@@ -147,4 +147,14 @@ public interface DocQueryContext {
* Return the expression path for the given property path.
*/
ExpressionPath getExpressionPath(String propName);
+
+ /**
+ * Start nested path expressions.
+ */
+ void startNested(String nestedPath) throws IOException;
+
+ /**
+ * End nested path expressions.
+ */
+ void endNested() throws IOException;
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java
index ec937239d..a34110abf 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/ExistsQueryExpression.java
@@ -118,6 +118,11 @@ class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreExpress
return true;
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return null;
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins whereManyJoins) {
// Nothing to do for exists expression
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java
index 73692a5f5..c74767df2 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdExpression.java
@@ -25,6 +25,11 @@ class IdExpression extends NonPrepareExpression implements SpiExpression {
context.writeId(value);
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return null;
+ }
+
/**
* Always returns false.
*/
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java
index b9c7e09e5..3c153b9ad 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/IdInExpression.java
@@ -22,6 +22,11 @@ public class IdInExpression extends NonPrepareExpression {
this.idList = idList;
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return null;
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
}
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 47f1631f0..55fa7ce29 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/JunctionExpression.java
@@ -39,7 +39,7 @@ import java.util.Set;
*/
class JunctionExpression implements SpiJunction, SpiExpression, ExpressionList {
- private final DefaultExpressionList exprList;
+ protected final DefaultExpressionList exprList;
protected final Junction.Type type;
@@ -790,4 +790,17 @@ class JunctionExpression implements SpiJunction, SpiExpression, Expression
public ExpressionList endNot() {
return endJunction();
}
+
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+
+ PrepareDocNested.prepare(exprList, desc, type);
+ String nestedPath = exprList.allDocNestedPath;
+ if (nestedPath != null) {
+ // push the nestedPath up to parent
+ exprList.setAllDocNested(null);
+ return nestedPath;
+ }
+ return null;
+ }
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java
index b94bfb1d3..3efb7cb65 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/LogicExpression.java
@@ -45,9 +45,9 @@ abstract class LogicExpression implements SpiExpression {
}
}
- protected final SpiExpression expOne;
+ protected SpiExpression expOne;
- protected final SpiExpression expTwo;
+ protected SpiExpression expTwo;
private final String joinType;
@@ -67,6 +67,27 @@ abstract class LogicExpression implements SpiExpression {
context.endBool();
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+
+ String pathOne = expOne.nestedPath(desc);
+ String pathTwo = expTwo.nestedPath(desc);
+
+ if (pathOne == null && pathTwo == null) {
+ return null;
+ }
+ if (pathOne != null && pathOne.equals(pathTwo)) {
+ return pathOne;
+ }
+ if (pathOne != null) {
+ expOne = new NestedPathWrapperExpression(pathOne, expOne);
+ }
+ if (pathTwo != null) {
+ expTwo = new NestedPathWrapperExpression(pathTwo, expTwo);
+ }
+ return null;
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
expOne.containsMany(desc, manyWhereJoin);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NestedPathWrapperExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NestedPathWrapperExpression.java
new file mode 100644
index 000000000..92a730a20
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NestedPathWrapperExpression.java
@@ -0,0 +1,93 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.event.BeanQueryRequest;
+import com.avaje.ebeaninternal.api.HashQueryPlanBuilder;
+import com.avaje.ebeaninternal.api.ManyWhereJoins;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.ebeaninternal.api.SpiExpressionRequest;
+import com.avaje.ebeaninternal.api.SpiExpressionValidation;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+
+import java.io.IOException;
+
+/**
+ * Wraps a single expression with nestedPath for document queries.
+ */
+class NestedPathWrapperExpression implements SpiExpression {
+
+ protected final String nestedPath;
+
+ protected final SpiExpression delegate;
+
+ NestedPathWrapperExpression(String nestedPath, SpiExpression delegate) {
+ this.nestedPath = nestedPath;
+ this.delegate = delegate;
+ }
+
+ @Override
+ public void writeDocQuery(DocQueryContext context) throws IOException {
+ context.startNested(nestedPath);
+ delegate.writeDocQuery(context);
+ context.endNested();
+ }
+
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return nestedPath;
+ }
+
+ @Override
+ public void containsMany(BeanDescriptor> desc, ManyWhereJoins whereManyJoins) {
+ delegate.containsMany(desc, whereManyJoins);
+ }
+
+ @Override
+ public void prepareExpression(BeanQueryRequest> request) {
+ delegate.prepareExpression(request);
+ }
+
+ @Override
+ public void queryPlanHash(HashQueryPlanBuilder builder) {
+ delegate.queryPlanHash(builder);
+ }
+
+ @Override
+ public int queryBindHash() {
+ return delegate.queryBindHash();
+ }
+
+ @Override
+ public boolean isSameByPlan(SpiExpression other) {
+ if (other instanceof NestedPathWrapperExpression) {
+ NestedPathWrapperExpression that = (NestedPathWrapperExpression)other;
+ return nestedPath.equals(that.nestedPath)
+ && delegate.isSameByPlan(that.delegate);
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isSameByBind(SpiExpression other) {
+ return delegate.isSameByBind(other);
+ }
+
+ @Override
+ public void addSql(SpiExpressionRequest request) {
+ delegate.addSql(request);
+ }
+
+ @Override
+ public void addBindValues(SpiExpressionRequest request) {
+ delegate.addBindValues(request);
+ }
+
+ @Override
+ public void validate(SpiExpressionValidation validation) {
+ delegate.validate(validation);
+ }
+
+ @Override
+ public SpiExpression copyForPlanKey() {
+ return new NestedPathWrapperExpression(nestedPath, delegate.copyForPlanKey());
+ }
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java
index 1c9bc39fa..e771e7916 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NoopExpression.java
@@ -26,6 +26,11 @@ class NoopExpression implements SpiExpression {
public void writeDocQuery(DocQueryContext context) throws IOException {
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return null;
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins whereManyJoins) {
// nothing to do
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java
index 5e4b1c09a..4024a43c9 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/NotExpression.java
@@ -34,6 +34,11 @@ final class NotExpression implements SpiExpression {
return new NotExpression(exp.copyForPlanKey());
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return exp.nestedPath(desc);
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
exp.containsMany(desc, manyWhereJoin);
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/PrepareDocNested.java b/src/main/java/com/avaje/ebeaninternal/server/expression/PrepareDocNested.java
new file mode 100644
index 000000000..ee917f830
--- /dev/null
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/PrepareDocNested.java
@@ -0,0 +1,163 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.Junction;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Prepare nested path expressions for
+ */
+class PrepareDocNested {
+
+ /**
+ * Prepare the top level expressions for nested path handling.
+ */
+ static void prepare(DefaultExpressionList> expressions, BeanDescriptor> beanDescriptor) {
+ new PrepareDocNested(expressions, beanDescriptor, null).process();
+ }
+
+ /**
+ * Prepare the Junction expressions for nested path handling.
+ */
+ static void prepare(DefaultExpressionList> expressions, BeanDescriptor> beanDescriptor, Junction.Type type) {
+ new PrepareDocNested(expressions, beanDescriptor, type).process();
+ }
+
+ enum Mode {
+ NONE,
+ SINGLE,
+ MIXED
+ }
+
+ private final Junction.Type type;
+ private final DefaultExpressionList> original;
+ private final BeanDescriptor> beanDescriptor;
+ private final List origUnderlying;
+ private final int origSize;
+
+ private boolean hasNesting;
+ private boolean hasMixedNesting;
+ private String firstNestedPath;
+
+
+ PrepareDocNested(DefaultExpressionList> original, BeanDescriptor> beanDescriptor, Junction.Type type) {
+ this.type = type;
+ this.beanDescriptor = beanDescriptor;
+ this.original = original;
+ this.origUnderlying = original.getUnderlyingList();
+ this.origSize = origUnderlying.size();
+ }
+
+ void process() {
+
+ PrepareDocNested.Mode mode = determineMode();
+ if (mode == PrepareDocNested.Mode.SINGLE) {
+ original.setAllDocNested(firstNestedPath);
+
+ } else if (mode == PrepareDocNested.Mode.MIXED) {
+ original.setUnderlying(group());
+ }
+
+ }
+
+ /**
+ * Reorganise the flat list of expressions into a tree grouping expressions by nested path.
+ *
+ * Returns the new top level list of expressions.
+ */
+ private List group() {
+
+ Map groups = new LinkedHashMap();
+
+ // organise expressions by nestedPath
+ for (int i = 0; i < origSize; i++) {
+ SpiExpression expr = origUnderlying.get(i);
+ String nestedPath = expr.nestedPath(beanDescriptor);
+ Group group = groups.get(nestedPath);
+ if (group == null) {
+ group = new Group(nestedPath);
+ groups.put(nestedPath, group);
+ }
+ group.list.add(expr);
+ }
+
+ List newList = new ArrayList();
+ Collection values = groups.values();
+ for (Group group : values) {
+ group.addTo(newList);
+ }
+ return newList;
+ }
+
+ /**
+ * Determined the nested path mode.
+ */
+ private Mode determineMode() {
+
+ if (!hasNesting()) {
+ // no nested paths at all
+ return Mode.NONE;
+ }
+ if (!hasMixedNesting) {
+ // single nested path for all expressions
+ return Mode.SINGLE;
+ }
+ // mixed nested paths to underlying expression list needs re-organising by nested path
+ return Mode.MIXED;
+ }
+
+ /**
+ * Return true if the expressions have nested paths.
+ */
+ private boolean hasNesting() {
+
+ for (int i = 0; i < origSize; i++) {
+ SpiExpression expr = origUnderlying.get(i);
+ String nestedPath = expr.nestedPath(beanDescriptor);
+ if (nestedPath == null) {
+ hasMixedNesting = true;
+
+ } if (nestedPath != null) {
+ hasNesting = true;
+ if (firstNestedPath == null) {
+ firstNestedPath = nestedPath;
+ } else if (hasMixedNesting || !firstNestedPath.equals(nestedPath)) {
+ hasMixedNesting = true;
+ return true;
+ }
+ }
+ }
+
+ return hasNesting;
+ }
+
+
+ /**
+ * List of SpiExpression grouped by nested path.
+ */
+ class Group {
+
+ final String nestedPath;
+
+ final List list = new ArrayList();
+
+ Group(String nestedPath) {
+ this.nestedPath = nestedPath;
+ }
+
+ void addTo(List newList) {
+ if (nestedPath == null) {
+ newList.addAll(list);
+ } else {
+ newList.add(original.wrap(list, nestedPath, type));
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java
index bbb48f21c..3632086cf 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/expression/RawExpression.java
@@ -25,6 +25,11 @@ class RawExpression extends NonPrepareExpression {
context.writeRaw(sql, values);
}
+ @Override
+ public String nestedPath(BeanDescriptor> desc) {
+ return null;
+ }
+
@Override
public void containsMany(BeanDescriptor> desc, ManyWhereJoins manyWhereJoin) {
diff --git a/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java b/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java
index 8d9e66cbb..77f82d039 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/query/SplitName.java
@@ -1,9 +1,15 @@
package com.avaje.ebeaninternal.server.query;
+/**
+ * Helper for dot notation property paths.
+ */
public class SplitName {
private static final char PERIOD = '.';
+ /**
+ * Add the two name sections together in dot notation.
+ */
public static String add(String prefix, String name) {
if (prefix != null) {
return prefix + "." + name;
@@ -38,10 +44,20 @@ public class SplitName {
}
}
+ /**
+ * Return the name split by last.
+ */
public static String[] split(String name) {
return split(name, true);
}
+ /**
+ * Return the first part of the name.
+ */
+ public static String begin(String name) {
+ return splitBegin(name)[0];
+ }
+
public static String[] splitBegin(String name) {
return split(name, false);
}
diff --git a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
index 983076e55..aea04264f 100644
--- a/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
+++ b/src/main/java/com/avaje/ebeaninternal/server/querydefn/DefaultOrmQuery.java
@@ -484,6 +484,16 @@ public class DefaultOrmQuery implements SpiQuery {
}
}
+ @Override
+ public void prepareDocNested() {
+ if (textExpressions != null) {
+ textExpressions.prepareDocNested(beanDescriptor);
+ }
+ if (whereExpressions != null) {
+ whereExpressions.prepareDocNested(beanDescriptor);
+ }
+ }
+
/**
* Setup to be a delete query.
*/
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java
index 742a6ebd0..099584e0f 100644
--- a/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/LogicExpressionTest.java
@@ -2,6 +2,7 @@ package com.avaje.ebeaninternal.server.expression;
import com.avaje.ebean.Expr;
import com.avaje.ebean.Expression;
+import com.avaje.tests.model.basic.Order;
import org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
@@ -75,4 +76,54 @@ public class LogicExpressionTest extends BaseExpressionTest {
assertThat(and(eq("a", 10), eq("b", 10))
.isSameByBind(and(eq("a", 10), eq("c", 20)))).isFalse();
}
+
+ @Test
+ public void nestedPath_when_notNested() {
+
+ LogicExpression and = and(eq("orderDate", 10), eq("shipDate", 10));
+
+ and.nestedPath(getBeanDescriptor(Order.class));
+
+ assertThat(and.expOne).isInstanceOf(SimpleExpression.class);
+ assertThat(and.expTwo).isInstanceOf(SimpleExpression.class);
+ }
+
+ @Test
+ public void nestedPath_when_nestedSame() {
+
+ LogicExpression and = and(eq("details.orderQty", 10), eq("details.unitPrice", 10));
+
+ String path = and.nestedPath(getBeanDescriptor(Order.class));
+
+ assertThat(path).isEqualTo("details");
+ assertThat(and.expOne).isInstanceOf(SimpleExpression.class);
+ assertThat(and.expTwo).isInstanceOf(SimpleExpression.class);
+ }
+
+ @Test
+ public void nestedPath_when_nestedDifferent() {
+
+ LogicExpression and = and(eq("details.orderQty", 10), eq("shipments.shipTime", 10));
+
+ String path = and.nestedPath(getBeanDescriptor(Order.class));
+
+ assertThat(path).isNull();
+ assertThat(and.expOne).isInstanceOf(NestedPathWrapperExpression.class);
+ assertThat(((NestedPathWrapperExpression)and.expOne).nestedPath).isEqualTo("details");
+ assertThat(and.expTwo).isInstanceOf(NestedPathWrapperExpression.class);
+ assertThat(((NestedPathWrapperExpression)and.expTwo).nestedPath).isEqualTo("shipments");
+ }
+
+ @Test
+ public void nestedPath_when_oneNested() {
+
+ LogicExpression and = and(eq("details.orderQty", 10), eq("orderDate", 10));
+
+ String path = and.nestedPath(getBeanDescriptor(Order.class));
+
+ assertThat(path).isNull();
+ assertThat(and.expOne).isInstanceOf(NestedPathWrapperExpression.class);
+ assertThat(((NestedPathWrapperExpression)and.expOne).nestedPath).isEqualTo("details");
+ assertThat(and.expTwo).isInstanceOf(SimpleExpression.class);
+ }
}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/expression/PrepareDocNestedTest.java b/src/test/java/com/avaje/ebeaninternal/server/expression/PrepareDocNestedTest.java
new file mode 100644
index 000000000..977bd01aa
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/expression/PrepareDocNestedTest.java
@@ -0,0 +1,148 @@
+package com.avaje.ebeaninternal.server.expression;
+
+import com.avaje.ebean.BaseTestCase;
+import com.avaje.ebean.Ebean;
+import com.avaje.ebean.ExpressionList;
+import com.avaje.ebeaninternal.api.SpiExpression;
+import com.avaje.tests.model.basic.Order;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+public class PrepareDocNestedTest extends BaseTestCase {
+ @Test
+ public void prepare() throws Exception {
+
+ ExpressionList where = Ebean.find(Order.class)
+ .where()
+ .gt("details.orderQty", 1)
+ .query().where();
+
+
+ DefaultExpressionList> exp = (DefaultExpressionList>)where;
+ PrepareDocNested.prepare(exp, getBeanDescriptor(Order.class));
+
+ List underlyingList = exp.getUnderlyingList();
+ assertEquals(underlyingList.size(), 1);
+ assertEquals(exp.allDocNestedPath, "details");
+ }
+
+ @Test
+ public void prepare_when_multipleOfSamePath() throws Exception {
+
+ ExpressionList where = Ebean.find(Order.class)
+ .where()
+ .gt("details.orderQty", 1)
+ .gt("details.unitPrice", 1)
+ .query().where();
+
+
+ DefaultExpressionList> exp = (DefaultExpressionList>)where;
+ PrepareDocNested.prepare(exp, getBeanDescriptor(Order.class));
+
+ List underlyingList = exp.getUnderlyingList();
+ assertEquals(underlyingList.size(), 2);
+ assertEquals(exp.allDocNestedPath, "details");
+ }
+
+ @Test
+ public void prepare_when_mixed() throws Exception {
+
+ ExpressionList where = Ebean.find(Order.class)
+ .where()
+ .gt("customer.id", 1)
+ .gt("details.orderQty", 1)
+ .gt("details.unitPrice", 1)
+ .query().where();
+
+
+ DefaultExpressionList> exp = (DefaultExpressionList>)where;
+ PrepareDocNested.prepare(exp, getBeanDescriptor(Order.class));
+
+ List underlyingList = exp.getUnderlyingList();
+ assertEquals(underlyingList.size(), 2);
+ assertNull(exp.allDocNestedPath);
+
+ DefaultExpressionList> second = (DefaultExpressionList>)underlyingList.get(1);
+ assertEquals(second.allDocNestedPath, "details");
+ }
+
+ @Test
+ public void prepare_when_nestedJunction() throws Exception {
+
+ ExpressionList where = Ebean.find(Order.class)
+ .where()
+ .not()
+ .gt("customer.id", 1)
+ .gt("details.orderQty", 1)
+ .gt("details.unitPrice", 1)
+ .query().where();
+
+ DefaultExpressionList> exp = (DefaultExpressionList>)where;
+ PrepareDocNested.prepare(exp, getBeanDescriptor(Order.class));
+
+ List underlyingList = exp.getUnderlyingList();
+ assertEquals(underlyingList.size(), 1);
+ assertNull(exp.allDocNestedPath);
+
+ JunctionExpression> junction = (JunctionExpression>)underlyingList.get(0);
+ List junctionUnderlying = junction.exprList.getUnderlyingList();
+ JunctionExpression> nestedNestedPath = (JunctionExpression)junctionUnderlying.get(1);
+ assertEquals(nestedNestedPath.exprList.allDocNestedPath, "details");
+ }
+
+ @Test
+ public void prepare_when_nestedMultiple() throws Exception {
+
+ ExpressionList where = Ebean.find(Order.class)
+ .where()
+ .isNotNull("shipments.shipTime")
+ .gt("details.orderQty", 1)
+ .gt("details.unitPrice", 1)
+ .query().where();
+
+ DefaultExpressionList> exp = (DefaultExpressionList>)where;
+ PrepareDocNested.prepare(exp, getBeanDescriptor(Order.class));
+
+ List underlyingList = exp.getUnderlyingList();
+ assertEquals(underlyingList.size(), 2);
+ assertNull(exp.allDocNestedPath);
+
+ DefaultExpressionList> shipExpr = (DefaultExpressionList>)underlyingList.get(0);
+ assertEquals(shipExpr.allDocNestedPath, "shipments");
+
+ DefaultExpressionList> detailsExpr = (DefaultExpressionList>)underlyingList.get(1);
+ assertEquals(detailsExpr.allDocNestedPath, "details");
+ }
+
+
+ @Test
+ public void prepare_when_manyMixed() throws Exception {
+
+ ExpressionList where = Ebean.find(Order.class)
+ .where()
+ .gt("customer.id", 1) // 0
+ .isNotNull("shipments.shipTime") // shipments 0
+ .isNotNull("status") // 1
+ .gt("details.orderQty", 1) // details 0
+ .isNotNull("orderDate") // 2
+ .gt("details.unitPrice", 1) // details 1
+ .query().where();
+
+ DefaultExpressionList> exp = (DefaultExpressionList>)where;
+ PrepareDocNested.prepare(exp, getBeanDescriptor(Order.class));
+
+ List underlyingList = exp.getUnderlyingList();
+ assertEquals(underlyingList.size(), 5);
+ assertNull(exp.allDocNestedPath);
+
+ DefaultExpressionList> shipExpr = (DefaultExpressionList>)underlyingList.get(3);
+ assertEquals(shipExpr.allDocNestedPath, "shipments");
+
+ DefaultExpressionList> detailsExpr = (DefaultExpressionList>)underlyingList.get(4);
+ assertEquals(detailsExpr.allDocNestedPath, "details");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/avaje/ebeaninternal/server/query/SplitNameTest.java b/src/test/java/com/avaje/ebeaninternal/server/query/SplitNameTest.java
new file mode 100644
index 000000000..b5070b11d
--- /dev/null
+++ b/src/test/java/com/avaje/ebeaninternal/server/query/SplitNameTest.java
@@ -0,0 +1,82 @@
+package com.avaje.ebeaninternal.server.query;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class SplitNameTest {
+
+ @Test
+ public void add() throws Exception {
+
+ assertEquals(SplitName.add("a","b"), "a.b");
+ assertEquals(SplitName.add("a","b.c"), "a.b.c");
+ }
+
+ @Test
+ public void count() throws Exception {
+
+ assertEquals(SplitName.count("a"), 0);
+ assertEquals(SplitName.count("a.b"), 1);
+ assertEquals(SplitName.count("a.b.c"), 2);
+ assertEquals(SplitName.count("a.b.c.foo"), 3);
+ }
+
+ @Test
+ public void parent() throws Exception {
+ assertNull(SplitName.parent("a"));
+ assertEquals(SplitName.parent("a.b"), "a");
+ assertEquals(SplitName.parent("a.b.c"), "a.b");
+ assertNull(SplitName.parent(null));
+ }
+
+ @Test
+ public void split() throws Exception {
+
+ String[] split = SplitName.split("a.b.c");
+ assertEquals(split[0], "a.b");
+ assertEquals(split[1], "c");
+ }
+
+ @Test
+ public void begin_when_one() throws Exception {
+
+ assertEquals(SplitName.begin("a"), "a");
+ }
+
+ @Test
+ public void begin_when_both() throws Exception {
+
+ assertEquals(SplitName.begin("a.b"), "a");
+ }
+
+ @Test
+ public void begin_when_multi() throws Exception {
+
+ assertEquals(SplitName.begin("a.b.c"), "a");
+ }
+
+ @Test
+ public void splitBegin_when_both() throws Exception {
+
+ String[] split = SplitName.splitBegin("a.b");
+ assertEquals(split[0], "a");
+ assertEquals(split[1], "b");
+ }
+
+ @Test
+ public void splitBegin_when_bothPlus() throws Exception {
+
+ String[] split = SplitName.splitBegin("a.b.c");
+ assertEquals(split[0], "a");
+ assertEquals(split[1], "b.c");
+ }
+
+ @Test
+ public void splitBegin_when_one() throws Exception {
+
+ String[] split = SplitName.splitBegin("a");
+ assertEquals(split[0], "a");
+ assertNull(split[1]);
+ }
+}
\ No newline at end of file