CAY-2468 Support subqueries in Expression API

Project: http://git-wip-us.apache.org/repos/asf/cayenne/repo
Commit: http://git-wip-us.apache.org/repos/asf/cayenne/commit/60c55667
Tree: http://git-wip-us.apache.org/repos/asf/cayenne/tree/60c55667
Diff: http://git-wip-us.apache.org/repos/asf/cayenne/diff/60c55667

Branch: refs/heads/master
Commit: 60c55667568bffaf92f738289a6ca7b2f2b2bc68
Parents: 1f10c92
Author: Nikita Timofeev <stari...@gmail.com>
Authored: Wed Jan 9 11:43:12 2019 +0300
Committer: Nikita Timofeev <stari...@gmail.com>
Committed: Wed Jan 9 11:43:12 2019 +0300

----------------------------------------------------------------------
 .../java/org/apache/cayenne/exp/Expression.java |  18 +-
 .../apache/cayenne/exp/ExpressionFactory.java   |  54 ++++++
 .../cayenne/exp/parser/ASTEnclosingObject.java  |  71 ++++++++
 .../apache/cayenne/exp/parser/ASTExists.java    |  62 +++++++
 .../cayenne/exp/parser/ASTFullObject.java       |   8 +
 .../org/apache/cayenne/exp/parser/ASTIn.java    |   4 +-
 .../org/apache/cayenne/exp/parser/ASTNotIn.java |   4 +-
 .../apache/cayenne/exp/parser/ASTSubquery.java  |  95 +++++++++++
 .../cayenne/exp/parser/PatternMatchNode.java    |   7 +
 .../cayenne/exp/property/BaseProperty.java      |  22 +++
 .../cayenne/exp/property/DateProperty.java      |   8 +
 .../cayenne/exp/property/EntityProperty.java    |   9 +
 .../cayenne/exp/property/ListProperty.java      |   8 +
 .../cayenne/exp/property/MapProperty.java       |   7 +
 .../cayenne/exp/property/NumericProperty.java   |   8 +
 .../cayenne/exp/property/SetProperty.java       |   8 +
 .../cayenne/exp/property/StringProperty.java    |   8 +
 .../cayenne/query/ObjectSelect_SubqueryIT.java  | 167 +++++++++++++++++++
 18 files changed, 561 insertions(+), 7 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
index 569416c..0e099d7 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/Expression.java
@@ -19,10 +19,7 @@
 
 package org.apache.cayenne.exp;
 
-import static org.apache.cayenne.exp.ExpressionFactory.exp;
-
 import java.io.IOException;
-import java.io.PrintWriter;
 import java.io.Serializable;
 import java.util.Collection;
 import java.util.HashMap;
@@ -167,6 +164,21 @@ public abstract class Expression implements Serializable, 
XMLSerializable {
         */
        public static final int FULL_OBJECT = 47;
 
+       /**
+        * @since 4.2
+        */
+       public static final int ENCLOSING_OBJECT = 48;
+
+       /**
+        * @since 4.2
+        */
+       public static final int EXISTS = 49;
+
+       /**
+        * @since 4.2
+        */
+       public static final int SUBQUERY = 50;
+
        protected int type;
 
        /**

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
index 3ad5e77..ad9775e 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/ExpressionFactory.java
@@ -31,7 +31,9 @@ import org.apache.cayenne.exp.parser.ASTBitwiseRightShift;
 import org.apache.cayenne.exp.parser.ASTBitwiseXor;
 import org.apache.cayenne.exp.parser.ASTDbPath;
 import org.apache.cayenne.exp.parser.ASTDivide;
+import org.apache.cayenne.exp.parser.ASTEnclosingObject;
 import org.apache.cayenne.exp.parser.ASTEqual;
+import org.apache.cayenne.exp.parser.ASTExists;
 import org.apache.cayenne.exp.parser.ASTFalse;
 import org.apache.cayenne.exp.parser.ASTFullObject;
 import org.apache.cayenne.exp.parser.ASTGreater;
@@ -54,6 +56,7 @@ import org.apache.cayenne.exp.parser.ASTObjPath;
 import org.apache.cayenne.exp.parser.ASTOr;
 import org.apache.cayenne.exp.parser.ASTPath;
 import org.apache.cayenne.exp.parser.ASTScalar;
+import org.apache.cayenne.exp.parser.ASTSubquery;
 import org.apache.cayenne.exp.parser.ASTSubtract;
 import org.apache.cayenne.exp.parser.ASTTrue;
 import org.apache.cayenne.exp.parser.ExpressionParser;
@@ -61,6 +64,9 @@ import 
org.apache.cayenne.exp.parser.ExpressionParserTokenManager;
 import org.apache.cayenne.exp.parser.JavaCharStream;
 import org.apache.cayenne.exp.parser.SimpleNode;
 import org.apache.cayenne.map.Entity;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.query.SelectQuery;
 
 import java.io.Reader;
 import java.io.StringReader;
@@ -1257,6 +1263,13 @@ public class ExpressionFactory {
        }
 
        /**
+        * @since 4.2
+        */
+       public static Expression enclosingObjectExp(Expression exp) {
+               return new ASTEnclosingObject(exp);
+       }
+
+       /**
         * @since 4.0
         */
        public static Expression and(Collection<Expression> expressions) {
@@ -1343,4 +1356,45 @@ public class ExpressionFactory {
                        throw new ExpressionException("%s", th, message != null 
? message : "");
                }
        }
+
+       /**
+        * @since 4.2
+        */
+       public static Expression exists(SelectQuery<?> subQuery) {
+               return new ASTExists(new ASTSubquery(subQuery));
+       }
+
+       /**
+        * @since 4.2
+        */
+       public static Expression exists(ObjectSelect<?> subQuery) {
+               return new ASTExists(new ASTSubquery(subQuery));
+       }
+
+       /**
+        * @since 4.2
+        */
+       public static Expression exists(ColumnSelect<?> subQuery) {
+               return new ASTExists(new ASTSubquery(subQuery));
+       }
+
+       /**
+        * @since 4.2
+        */
+       public static Expression inExp(Expression exp, ColumnSelect<?> 
subQuery) {
+               if(!(exp instanceof SimpleNode)) {
+                       throw new IllegalArgumentException("exp should be 
instance of SimpleNode");
+               }
+               return new ASTIn((SimpleNode)exp, new ASTSubquery(subQuery));
+       }
+
+       /**
+        * @since 4.2
+        */
+       public static Expression notInExp(Expression exp, ColumnSelect<?> 
subQuery) {
+               if(!(exp instanceof SimpleNode)) {
+                       throw new IllegalArgumentException("exp should be 
instance of SimpleNode");
+               }
+               return new ASTNotIn((SimpleNode)exp, new ASTSubquery(subQuery));
+       }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
new file mode 100644
index 0000000..2789781
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTEnclosingObject.java
@@ -0,0 +1,71 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import java.io.IOException;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * @since 4.2
+ */
+public class ASTEnclosingObject extends SimpleNode {
+
+    public ASTEnclosingObject(Expression expression) {
+        this();
+        Node node = wrapChild(expression);
+        jjtAddChild(node, 0);
+        node.jjtSetParent(this);
+    }
+
+    public ASTEnclosingObject() {
+        this(0);
+    }
+
+    protected ASTEnclosingObject(int i) {
+        super(i);
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) throws Exception {
+        return o;
+    }
+
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        out.append("SUPER:");
+        super.appendAsString(out);
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTEnclosingObject(id);
+    }
+
+    @Override
+    public int getType() {
+        return Expression.ENCLOSING_OBJECT;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
new file mode 100644
index 0000000..9f06dc2
--- /dev/null
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTExists.java
@@ -0,0 +1,62 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import org.apache.cayenne.exp.Expression;
+
+/**
+ * @since 4.2
+ */
+public class ASTExists extends ConditionNode {
+
+    public ASTExists(ASTSubquery subquery) {
+        super(0);
+        jjtAddChild(subquery, 0);
+    }
+
+    ASTExists(int id) {
+        super(id);
+    }
+
+    @Override
+    protected int getRequiredChildrenCount() {
+        return 1;
+    }
+
+    @Override
+    protected Boolean evaluateSubNode(Object o, Object[] evaluatedChildren) 
throws Exception {
+        return null;
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        return null;
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTExists(id);
+    }
+
+    @Override
+    public int getType() {
+        return Expression.EXISTS;
+    }
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
index 25f6d97..da15668 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTFullObject.java
@@ -19,6 +19,8 @@
 
 package org.apache.cayenne.exp.parser;
 
+import java.io.IOException;
+
 import org.apache.cayenne.exp.Expression;
 
 /**
@@ -52,6 +54,12 @@ public class ASTFullObject extends SimpleNode {
     }
 
     @Override
+    public void appendAsString(Appendable out) throws IOException {
+        out.append(":FULL_OBJECT:");
+        super.appendAsString(out);
+    }
+
+    @Override
     public Expression shallowCopy() {
         return new ASTFullObject(id);
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
index d1801ce..000a4c6 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTIn.java
@@ -42,10 +42,10 @@ public class ASTIn extends ConditionNode {
                super(ExpressionParserTreeConstants.JJTIN);
        }
 
-       public ASTIn(SimpleNode path, ASTList list) {
+       public ASTIn(SimpleNode path, SimpleNode node) {
                super(ExpressionParserTreeConstants.JJTIN);
                jjtAddChild(path, 0);
-               jjtAddChild(list, 1);
+               jjtAddChild(node, 1);
                connectChildren();
        }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
index 4ce7d46..74761e2 100644
--- a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
+++ b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTNotIn.java
@@ -37,10 +37,10 @@ public class ASTNotIn extends ConditionNode {
         super(ExpressionParserTreeConstants.JJTNOTIN);
     }
 
-    public ASTNotIn(SimpleNode path, ASTList list) {
+    public ASTNotIn(SimpleNode path, SimpleNode node) {
         super(ExpressionParserTreeConstants.JJTNOTIN);
         jjtAddChild(path, 0);
-        jjtAddChild(list, 1);
+        jjtAddChild(node, 1);
         connectChildren();
     }
 

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
new file mode 100644
index 0000000..f157501
--- /dev/null
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/ASTSubquery.java
@@ -0,0 +1,95 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.exp.parser;
+
+import java.io.IOException;
+
+import org.apache.cayenne.ObjectContext;
+import org.apache.cayenne.Persistent;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.query.ColumnSelect;
+import org.apache.cayenne.access.translator.select.ColumnSelectWrapper;
+import org.apache.cayenne.query.ObjectSelect;
+import org.apache.cayenne.access.translator.select.ObjectSelectWrapper;
+import org.apache.cayenne.query.SelectQuery;
+import org.apache.cayenne.access.translator.select.SelectQueryWrapper;
+import org.apache.cayenne.access.translator.select.TranslatableQueryWrapper;
+
+/**
+ * @since 4.2
+ */
+public class ASTSubquery extends SimpleNode {
+
+    private final TranslatableQueryWrapper query;
+
+    public ASTSubquery(SelectQuery<?> query) {
+        this(new SelectQueryWrapper(query));
+    }
+
+    public ASTSubquery(ObjectSelect<?> query) {
+        this(new ObjectSelectWrapper(query));
+    }
+
+    public ASTSubquery(ColumnSelect<?> query) {
+        this(new ColumnSelectWrapper(query));
+    }
+
+    public ASTSubquery(TranslatableQueryWrapper query) {
+        super(0);
+        this.query = query;
+    }
+
+    @Override
+    protected String getExpressionOperator(int index) {
+        throw new UnsupportedOperationException();
+    }
+
+    @Override
+    protected Object evaluateNode(Object o) {
+        ObjectContext context;
+        if(o instanceof Persistent) {
+            context = ((Persistent) o).getObjectContext();
+        } else {
+            throw new UnsupportedOperationException("Can't evaluate subquery 
expression against non-persistent object");
+        }
+
+        return context.select(query.unwrap());
+    }
+
+    @Override
+    public Expression shallowCopy() {
+        return new ASTSubquery(query);
+    }
+
+    @Override
+    public int getType() {
+        return Expression.SUBQUERY;
+    }
+
+    @Override
+    public void appendAsString(Appendable out) throws IOException {
+        out.append("EXISTS");
+    }
+
+    public TranslatableQueryWrapper getQuery() {
+        return query;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
index 6fb7bff..f850443 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/parser/PatternMatchNode.java
@@ -85,6 +85,13 @@ public abstract class PatternMatchNode extends ConditionNode 
{
         escapeChar = value;
     }
 
+    /**
+     * @since 4.2
+     */
+    public boolean isIgnoringCase() {
+        return ignoringCase;
+    }
+
     protected boolean matchPattern(String string) {
         return (string != null) && getPattern().matcher(string).find();
     }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
index 1ce3836..fa16417 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/BaseProperty.java
@@ -28,6 +28,7 @@ import org.apache.cayenne.exp.Expression;
 import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.FunctionExpressionFactory;
 import org.apache.cayenne.exp.parser.ASTPath;
+import org.apache.cayenne.query.ColumnSelect;
 import org.apache.cayenne.query.Ordering;
 import org.apache.cayenne.query.Orderings;
 import org.apache.cayenne.query.SortOrder;
@@ -386,4 +387,25 @@ public class BaseProperty<E> implements Property<E> {
     public Expression nin(Collection<E> values) {
         return ExpressionFactory.notInExp(getExpression(), values);
     }
+
+    /**
+     * @return An expression for finding objects with values in the given 
subquery
+     */
+    public Expression in(ColumnSelect<? extends E> subquery) {
+        return ExpressionFactory.inExp(getExpression(), subquery);
+    }
+
+    /**
+     * @return An expression for finding objects with values not in the given 
subquery
+     */
+    public Expression nin(ColumnSelect<? extends E> subquery) {
+        return ExpressionFactory.notInExp(getExpression(), subquery);
+    }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public BaseProperty<E> enclosing() {
+        return 
PropertyFactory.createBase(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
index 8c32512..ab67541 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/DateProperty.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.exp.property;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.FunctionExpressionFactory;
 
 /**
@@ -137,4 +138,11 @@ public class DateProperty<E> extends BaseProperty<E> 
implements ComparableProper
     public DateProperty<E> min() {
         return 
PropertyFactory.createDate(FunctionExpressionFactory.minExp(getExpression()), 
getType());
     }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public DateProperty<E> enclosing() {
+        return 
PropertyFactory.createDate(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
index 41c225f..502e40b 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/EntityProperty.java
@@ -21,6 +21,7 @@ package org.apache.cayenne.exp.property;
 
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.parser.ASTPath;
 
 /**
@@ -70,4 +71,12 @@ public class EntityProperty<E extends Persistent> extends 
BaseProperty<E> implem
                 ? this
                 : PropertyFactory.createEntity(getName() + "+", getType());
     }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public EntityProperty<E> enclosing() {
+        return 
PropertyFactory.createEntity(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
index ded0084..861fa69 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/ListProperty.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.parser.ASTPath;
 
 /**
@@ -66,4 +67,11 @@ public class ListProperty<V extends Persistent> extends 
CollectionProperty<V, Li
                 ? this
                 : PropertyFactory.createList(getName() + "+", getEntityType());
     }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public ListProperty<V> enclosing() {
+        return PropertyFactory.createList(null, 
ExpressionFactory.enclosingObjectExp(getExpression()), getEntityType());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
index b9b24b3..bd03eff 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/MapProperty.java
@@ -232,4 +232,11 @@ public class MapProperty<K, V extends Persistent> extends 
BaseProperty<Map<K, V>
     protected Class<V> getEntityType() {
         return entityType;
     }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public MapProperty<K, V> enclosing() {
+        return PropertyFactory.createMap(null, 
ExpressionFactory.enclosingObjectExp(getExpression()),  getKeyType(), 
getEntityType());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
index 14e07d1..a1f67d9 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/NumericProperty.java
@@ -20,6 +20,7 @@
 package org.apache.cayenne.exp.property;
 
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.FunctionExpressionFactory;
 import org.apache.cayenne.exp.parser.ASTAdd;
 import org.apache.cayenne.exp.parser.ASTDivide;
@@ -183,4 +184,11 @@ public class NumericProperty<E extends Number> extends 
BaseProperty<E> implement
         return PropertyFactory.createNumeric(alias, this.getExpression(), 
this.getType());
     }
 
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public NumericProperty<E> enclosing() {
+        return 
PropertyFactory.createNumeric(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
index 12b1fd2..b6c5d47 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/SetProperty.java
@@ -23,6 +23,7 @@ import java.util.Set;
 
 import org.apache.cayenne.Persistent;
 import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
 import org.apache.cayenne.exp.parser.ASTPath;
 
 /**
@@ -62,4 +63,11 @@ public class SetProperty<V extends Persistent> extends 
CollectionProperty<V, Set
                 ? this
                 : PropertyFactory.createSet(getName() + "+", getEntityType());
     }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public SetProperty<V> enclosing() {
+        return PropertyFactory.createSet(null, 
ExpressionFactory.enclosingObjectExp(getExpression()), getEntityType());
+    }
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
index 87a6e0f..1c09822 100644
--- 
a/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
+++ 
b/cayenne-server/src/main/java/org/apache/cayenne/exp/property/StringProperty.java
@@ -301,4 +301,12 @@ public class StringProperty<E extends CharSequence> 
extends BaseProperty<E> {
     public StringProperty<E> alias(String alias) {
         return PropertyFactory.createString(alias, this.getExpression(), 
this.getType());
     }
+
+    /**
+     * @return property that will be translated relative to parent query
+     */
+    public StringProperty<E> enclosing() {
+        return 
PropertyFactory.createString(ExpressionFactory.enclosingObjectExp(getExpression()),
 getType());
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/cayenne/blob/60c55667/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
----------------------------------------------------------------------
diff --git 
a/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
 
b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
new file mode 100644
index 0000000..8a91131
--- /dev/null
+++ 
b/cayenne-server/src/test/java/org/apache/cayenne/query/ObjectSelect_SubqueryIT.java
@@ -0,0 +1,167 @@
+/*****************************************************************
+ *   Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ ****************************************************************/
+
+package org.apache.cayenne.query;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.cayenne.access.DataContext;
+import org.apache.cayenne.di.Inject;
+import org.apache.cayenne.exp.Expression;
+import org.apache.cayenne.exp.ExpressionFactory;
+import org.apache.cayenne.exp.property.PropertyFactory;
+import org.apache.cayenne.test.jdbc.DBHelper;
+import org.apache.cayenne.test.jdbc.TableHelper;
+import org.apache.cayenne.testdo.testmap.Artist;
+import org.apache.cayenne.testdo.testmap.Gallery;
+import org.apache.cayenne.testdo.testmap.Painting;
+import org.apache.cayenne.unit.di.server.CayenneProjects;
+import org.apache.cayenne.unit.di.server.ServerCase;
+import org.apache.cayenne.unit.di.server.UseServerRuntime;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @since 4.2
+ */
+@UseServerRuntime(CayenneProjects.TESTMAP_PROJECT)
+public class ObjectSelect_SubqueryIT extends ServerCase {
+
+    @Inject
+    DataContext context;
+
+    @Inject
+    private DBHelper dbHelper;
+
+    @Before
+    public void createArtistsDataSet() throws Exception {
+        TableHelper tArtist = new TableHelper(dbHelper, "ARTIST");
+        tArtist.setColumns("ARTIST_ID", "ARTIST_NAME", "DATE_OF_BIRTH");
+
+        long dateBase = System.currentTimeMillis() - 2 * 24 * 3600 * 1000;
+        for (int i = 1; i <= 20; i++) {
+            tArtist.insert(i, "artist" + i, new java.sql.Date(dateBase + 24 * 
3600 * 1000 * i));
+        }
+
+        TableHelper tGallery = new TableHelper(dbHelper, "GALLERY");
+        tGallery.setColumns("GALLERY_ID", "GALLERY_NAME");
+        tGallery.insert(1, "tate modern");
+
+        TableHelper tPaintings = new TableHelper(dbHelper, "PAINTING");
+        tPaintings.setColumns("PAINTING_ID", "PAINTING_TITLE", "ARTIST_ID", 
"GALLERY_ID");
+        for (int i = 1; i <= 20; i++) {
+            tPaintings.insert(i, "painting" + i, i % 5 + 1, 1);
+        }
+    }
+
+    @Test
+    public void selectQuery_simpleExists() {
+        long count = ObjectSelect.query(Artist.class)
+                
.where(ExpressionFactory.exists(SelectQuery.query(Painting.class, 
Painting.PAINTING_TITLE.like("painting%"))))
+                .selectCount(context);
+        assertEquals(20L, count);
+    }
+
+    @Test
+    public void selectQuery_existsWithExpressionFromParentQuery() {
+        Expression exp = 
Painting.TO_ARTIST.eq(PropertyFactory.createSelf(Artist.class).enclosing())
+                .andExp(Painting.PAINTING_TITLE.like("painting%"))
+                .andExp(Artist.ARTIST_NAME.enclosing().like("art%"));
+
+        SelectQuery<Painting> subQuery = SelectQuery.query(Painting.class, 
exp);
+        subQuery.setColumns(Painting.PAINTING_TITLE);
+
+        long count = ObjectSelect.query(Artist.class)
+                .where(ExpressionFactory.exists(subQuery))
+                .selectCount(context);
+        assertEquals(5L, count);
+    }
+
+    @Test
+    public void selectQuery_twoLevelExists() {
+        Expression exp = Painting.PAINTING_TITLE.like("painting%")
+                
.andExp(ExpressionFactory.exists(SelectQuery.query(Gallery.class)));
+        long count = ObjectSelect.query(Artist.class)
+                
.where(ExpressionFactory.exists(SelectQuery.query(Painting.class, exp)))
+                .selectCount(context);
+        assertEquals(20L, count);
+    }
+
+    @Test
+    public void selectQuery_twoLevelExistsWithExpressionFromParentQuery() {
+        Expression deepNestedExp = 
Artist.ARTIST_NAME.enclosing().enclosing().like("art%")
+                
.andExp(Painting.TO_GALLERY.enclosing().eq(PropertyFactory.createSelf(Gallery.class)));
+
+        Expression exp = Painting.PAINTING_TITLE.like("painting%")
+                
.andExp(ExpressionFactory.exists(SelectQuery.query(Gallery.class, 
deepNestedExp)))
+                
.andExp(Painting.TO_ARTIST.eq(PropertyFactory.createSelf(Artist.class).enclosing()));
+
+        long count = ObjectSelect.query(Artist.class)
+                
.where(ExpressionFactory.exists(SelectQuery.query(Painting.class, exp)))
+                .selectCount(context);
+        assertEquals(5L, count);
+    }
+
+    @Test
+    public void objectSelect_twoLevelExistsWithExpressionFromParentQuery() {
+        ObjectSelect<Gallery> deepSubquery = ObjectSelect.query(Gallery.class)
+                .where(Artist.ARTIST_NAME.enclosing().enclosing().like("art%"))
+                
.and(Painting.TO_GALLERY.enclosing().eq(PropertyFactory.createSelf(Gallery.class)));
+
+        ObjectSelect<Painting> subquery = ObjectSelect.query(Painting.class)
+                .where(Painting.PAINTING_TITLE.like("painting%"))
+                
.and(Painting.TO_ARTIST.eq(PropertyFactory.createSelf(Artist.class).enclosing()))
+                .and(ExpressionFactory.exists(deepSubquery));
+
+        long count = ObjectSelect.query(Artist.class)
+                .where(ExpressionFactory.exists(subquery))
+                .selectCount(context);
+
+        assertEquals(5L, count);
+    }
+
+    @Test
+    public void columnSelect_simpleInSubquery() {
+
+        ColumnSelect<String> subquery = ObjectSelect.columnQuery(Artist.class, 
Artist.ARTIST_NAME)
+                .where(Artist.DATE_OF_BIRTH.lt(new Date()));
+
+        long count = ObjectSelect.query(Painting.class)
+                .where(Painting.TO_ARTIST.dot(Artist.ARTIST_NAME).in(subquery))
+                .selectCount(context);
+
+        assertEquals(4L, count);
+    }
+
+    @Test
+    public void columnSelect_simpleNotInSubquery() {
+
+        ColumnSelect<String> subquery = ObjectSelect.columnQuery(Artist.class, 
Artist.ARTIST_NAME)
+                .where(Artist.DATE_OF_BIRTH.lt(new Date()));
+
+        long count = ObjectSelect.query(Painting.class)
+                
.where(Painting.TO_ARTIST.dot(Artist.ARTIST_NAME).nin(subquery))
+                .selectCount(context);
+
+        assertEquals(16L, count);
+    }
+}

Reply via email to