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); + } +}