From 0a61852711d07bcb4249e66dba9e14461ed0b2ab Mon Sep 17 00:00:00 2001 From: Rob Bygrave Date: Thu, 3 Nov 2022 00:22:48 +1300 Subject: [PATCH] #2847 - Add support for Sub-query EQ, NE, LT, LE, GT, GE expressions --- .../main/java/io/ebean/ExpressionFactory.java | 30 ++++++ .../main/java/io/ebean/ExpressionList.java | 30 ++++++ .../autotune/service/BaseQueryTuner.java | 2 +- .../java/io/ebeaninternal/api/SpiQuery.java | 6 +- .../expression/DefaultExpressionFactory.java | 50 +++++++--- .../expression/DefaultExpressionList.java | 39 ++++++-- .../server/expression/JunctionExpression.java | 30 ++++++ ...xpression.java => SubQueryExpression.java} | 47 +++++---- .../server/query/SqlTreeBuilder.java | 2 +- .../DefaultExpressionFactoryTest.java | 10 +- .../expression/InQueryExpressionTest.java | 7 +- .../server/expression/NoopExpressionTest.java | 4 +- .../io/ebean/typequery/PBaseCompareable.java | 45 +++++++++ .../io/ebean/typequery/PBaseValueEqual.java | 26 ++++- .../test/java/org/querytest/QOrderTest.java | 97 +++++++++++++++++++ .../java/org/tests/query/TestQueryIsNull.java | 2 +- .../tests/query/TestQueryNullAssocOne.java | 6 +- .../org/tests/query/other/TestSelfParent.java | 2 +- 18 files changed, 373 insertions(+), 62 deletions(-) rename ebean-core/src/main/java/io/ebeaninternal/server/expression/{InQueryExpression.java => SubQueryExpression.java} (66%) diff --git a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java index d8f047908..da84e2836 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java @@ -117,6 +117,11 @@ public interface ExpressionFactory { */ Expression arrayIsNotEmpty(String propertyName); + /** + * Equal To the result of a sub-query. + */ + Expression eq(String propertyName, Query subQuery); + /** * Equal To - property equal to the given value. */ @@ -127,6 +132,11 @@ public interface ExpressionFactory { */ Expression eqOrNull(String propertyName, Object value); + /** + * Not Equal To the result of a sub-query. + */ + Expression ne(String propertyName, Query subQuery); + /** * Not Equal To - property not equal to the given value. */ @@ -224,11 +234,21 @@ public interface ExpressionFactory { */ Expression geOrNull(String propertyName, Object value); + /** + * Greater Than the result of a sub-query. + */ + Expression gt(String propertyName, Query subQuery); + /** * Greater Than - property greater than the given value. */ Expression gt(String propertyName, Object value); + /** + * Greater Than or Equal to the result of a sub-query. + */ + Expression ge(String propertyName, Query subQuery); + /** * Greater Than or Equal to - property greater than or equal to the given * value. @@ -251,11 +271,21 @@ public interface ExpressionFactory { */ Expression leOrNull(String propertyName, Object value); + /** + * Less Than the result of a sub-query. + */ + Expression lt(String propertyName, Query subQuery); + /** * Less Than - property less than the given value. */ Expression lt(String propertyName, Object value); + /** + * Less Than or Equal to the result of a sub-query. + */ + Expression le(String propertyName, Query subQuery); + /** * Less Than or Equal to - property less than or equal to the given value. */ diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index 74426fe95..f02ccd44e 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -830,6 +830,11 @@ public interface ExpressionList { */ ExpressionList addAll(ExpressionList exprList); + /** + * Equal To the result of a sub-query. + */ + ExpressionList eq(String propertyName, Query subQuery); + /** * Equal To - property is equal to a given value. */ @@ -854,6 +859,11 @@ public interface ExpressionList { */ ExpressionList eqOrNull(String propertyName, Object value); + /** + * Not Equal To the result of a sub-query. + */ + ExpressionList ne(String propertyName, Query subQuery); + /** * Not Equal To - property not equal to the given value. */ @@ -925,6 +935,11 @@ public interface ExpressionList { */ ExpressionList betweenProperties(String lowProperty, String highProperty, Object value); + /** + * Greater Than the result of a sub-query. + */ + ExpressionList gt(String propertyName, Query subQuery); + /** * Greater Than - property greater than the given value. */ @@ -940,12 +955,22 @@ public interface ExpressionList { */ ExpressionList geOrNull(String propertyName, Object value); + /** + * Greater Than or Equal to the result of a sub-query. + */ + ExpressionList ge(String propertyName, Query subQuery); + /** * Greater Than or Equal to - property greater than or equal to the given * value. */ ExpressionList ge(String propertyName, Object value); + /** + * Less Than the result of a sub-query. + */ + ExpressionList lt(String propertyName, Query subQuery); + /** * Less Than - property less than the given value. */ @@ -961,6 +986,11 @@ public interface ExpressionList { */ ExpressionList leOrNull(String propertyName, Object value); + /** + * Less Than or Equal to the result of a sub-query. + */ + ExpressionList le(String propertyName, Query subQuery); + /** * Less Than or Equal to - property less than or equal to the given value. */ diff --git a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java index b2282b468..99677970a 100644 --- a/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java +++ b/ebean-autotune/src/main/java/io/ebeaninternal/server/autotune/service/BaseQueryTuner.java @@ -148,7 +148,7 @@ public class BaseQueryTuner { case UPDATE: case DELETE: case SQ_EXISTS: - case SQ_IN: + case SQ_EX: return false; default: // not using autoTune when explicitly loading the l2 bean cache diff --git a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java index 76e7cad99..bbe36c9b9 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java +++ b/ebean-core/src/main/java/io/ebeaninternal/api/SpiQuery.java @@ -103,14 +103,14 @@ public interface SpiQuery extends Query, SpiQueryFetch, TxnProfileEventCod COUNT(FIND_COUNT, "findCount"), /** - * A subquery used as part of an exists where clause. + * A sub-query used as part of an exists where clause. */ SQ_EXISTS(FIND_SUBQUERY, "sqExists", false, false), /** - * A subquery used as part of an in where clause. + * A sub-query expression used as part of where clause. */ - SQ_IN(FIND_SUBQUERY, "sqIn", false, false), + SQ_EX(FIND_SUBQUERY, "sqEx", false, false), /** * Delete query. diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java index 9ce606961..b6cc327db 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionFactory.java @@ -1,21 +1,11 @@ package io.ebeaninternal.server.expression; -import io.ebean.ExampleExpression; -import io.ebean.Expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.Junction; -import io.ebean.LikeType; -import io.ebean.Pairs; -import io.ebean.Query; +import io.ebean.*; import io.ebean.bean.EntityBean; -import io.ebean.search.Match; -import io.ebean.search.MultiMatch; -import io.ebean.search.TextCommonTerms; -import io.ebean.search.TextQueryString; -import io.ebean.search.TextSimple; +import io.ebean.search.*; import io.ebeaninternal.api.SpiExpressionFactory; import io.ebeaninternal.api.SpiQuery; +import io.ebeaninternal.server.expression.SubQueryExpression.SQOp; import io.ebeaninternal.server.grammer.EqlParser; import java.util.Arrays; @@ -157,6 +147,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { EqlParser.parseWhere(expressions, list, this, params); } + @Override + public Expression eq(String propertyName, Query subQuery) { + return new SubQueryExpression(SQOp.EQ, propertyName, (SpiQuery) subQuery); + } + /** * Equal To - property equal to the given value. */ @@ -173,6 +168,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return or(eq(propertyName, value), isNull(propertyName)); } + @Override + public Expression ne(String propertyName, Query subQuery) { + return new SubQueryExpression(SQOp.NE, propertyName, (SpiQuery) subQuery); + } + /** * Not Equal To - property not equal to the given value. */ @@ -258,6 +258,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return new BetweenPropertyExpression(lowProperty, highProperty, value); } + @Override + public Expression gt(String propertyName, Query subQuery) { + return new SubQueryExpression(SQOp.GT, propertyName, (SpiQuery) subQuery); + } + /** * Greater Than - property greater than the given value. */ @@ -279,6 +284,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return or(ge(propertyName, value), isNull(propertyName)); } + @Override + public Expression ge(String propertyName, Query subQuery) { + return new SubQueryExpression(SQOp.GE, propertyName, (SpiQuery) subQuery); + } + /** * Greater Than or Equal to - property greater than or equal to the given * value. @@ -301,6 +311,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return or(le(propertyName, value), isNull(propertyName)); } + @Override + public Expression lt(String propertyName, Query subQuery) { + return new SubQueryExpression(SQOp.LT, propertyName, (SpiQuery) subQuery); + } + /** * Less Than - property less than the given value. */ @@ -309,6 +324,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return new SimpleExpression(propertyName, Op.LT, value); } + @Override + public Expression le(String propertyName, Query subQuery) { + return new SubQueryExpression(SQOp.LE, propertyName, (SpiQuery) subQuery); + } + /** * Less Than or Equal to - property less than or equal to the given value. */ @@ -465,7 +485,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression in(String propertyName, Query subQuery) { - return new InQueryExpression(propertyName, (SpiQuery) subQuery, false); + return new SubQueryExpression(SQOp.IN, propertyName, (SpiQuery) subQuery); } /** @@ -507,7 +527,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression notIn(String propertyName, Query subQuery) { - return new InQueryExpression(propertyName, (SpiQuery) subQuery, true); + return new SubQueryExpression(SQOp.NOTIN, propertyName, (SpiQuery) subQuery); } /** diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java index 408cdc4b8..3759ce9b0 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/DefaultExpressionList.java @@ -788,6 +788,11 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.bitwiseAnd(propertyName, flags, match)); } + @Override + public ExpressionList eq(String propertyName, Query subQuery) { + return add(expr.eq(propertyName, subQuery)); + } + @Override public ExpressionList eq(String propertyName, Object value) { return add(expr.eq(propertyName, value)); @@ -813,6 +818,11 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.ine(propertyName, value)); } + @Override + public ExpressionList ne(String propertyName, Query subQuery) { + return add(expr.ne(propertyName, subQuery)); + } + @Override public ExpressionList ne(String propertyName, Object value) { return add(expr.ne(propertyName, value)); @@ -863,10 +873,19 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.endsWith(propertyName, value)); } + @Override + public ExpressionList ge(String propertyName, Query subQuery) { + return add(expr.ge(propertyName, subQuery)); + } + @Override public ExpressionList ge(String propertyName, Object value) { - add(expr.ge(propertyName, value)); - return this; + return add(expr.ge(propertyName, value)); + } + + @Override + public ExpressionList gt(String propertyName, Query subQuery) { + return add(expr.gt(propertyName, subQuery)); } @Override @@ -876,14 +895,12 @@ public class DefaultExpressionList implements SpiExpressionList { @Override public ExpressionList gtOrNull(String propertyName, Object value) { - add(expr.gtOrNull(propertyName, value)); - return this; + return add(expr.gtOrNull(propertyName, value)); } @Override public ExpressionList geOrNull(String propertyName, Object value) { - add(expr.geOrNull(propertyName, value)); - return this; + return add(expr.geOrNull(propertyName, value)); } @Override @@ -999,6 +1016,11 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.istartsWith(propertyName, value)); } + @Override + public ExpressionList le(String propertyName, Query subQuery) { + return add(expr.le(propertyName, subQuery)); + } + @Override public ExpressionList le(String propertyName, Object value) { return add(expr.le(propertyName, value)); @@ -1019,6 +1041,11 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.like(propertyName, value)); } + @Override + public ExpressionList lt(String propertyName, Query subQuery) { + return add(expr.lt(propertyName, subQuery)); + } + @Override public ExpressionList lt(String propertyName, Object value) { return add(expr.lt(propertyName, value)); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java index d3f968c22..3e202e99e 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/JunctionExpression.java @@ -309,6 +309,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.endsWith(propertyName, value); } + @Override + public ExpressionList eq(String propertyName, Query subQuery) { + return exprList.eq(propertyName, subQuery); + } + @Override public ExpressionList eq(String propertyName, Object value) { return exprList.eq(propertyName, value); @@ -642,11 +647,21 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.bitwiseNot(propertyName, flags); } + @Override + public ExpressionList ge(String propertyName, Query subQuery) { + return exprList.ge(propertyName, subQuery); + } + @Override public ExpressionList ge(String propertyName, Object value) { return exprList.ge(propertyName, value); } + @Override + public ExpressionList gt(String propertyName, Query subQuery) { + return exprList.gt(propertyName, subQuery); + } + @Override public ExpressionList gt(String propertyName, Object value) { return exprList.gt(propertyName, value); @@ -787,6 +802,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.istartsWith(propertyName, value); } + @Override + public ExpressionList le(String propertyName, Query subQuery) { + return exprList.le(propertyName, subQuery); + } + @Override public ExpressionList le(String propertyName, Object value) { return exprList.le(propertyName, value); @@ -797,6 +817,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.like(propertyName, value); } + @Override + public ExpressionList lt(String propertyName, Query subQuery) { + return exprList.lt(propertyName, subQuery); + } + @Override public ExpressionList lt(String propertyName, Object value) { return exprList.lt(propertyName, value); @@ -812,6 +837,11 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.leOrNull(propertyName, value); } + @Override + public ExpressionList ne(String propertyName, Query subQuery) { + return exprList.ne(propertyName, subQuery); + } + @Override public ExpressionList ne(String propertyName, Object value) { return exprList.ne(propertyName, value); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java similarity index 66% rename from ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java rename to ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java index 66c7fcaa3..8a2ae7c80 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/InQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java @@ -8,32 +8,47 @@ import io.ebeaninternal.server.query.CQuery; import java.util.List; /** - * In expression using a sub query. + * Sub-Query expression. */ -final class InQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression { +final class SubQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression { - private final boolean not; + enum SQOp { + EQ(" = "), + NE(" <> "), + GT(" > "), + GE(" >= "), + LT(" < "), + LE(" <= "), + IN(" in "), + NOTIN(" not in "); + final String expression; + SQOp(String expression) { + this.expression = expression; + } + } + + private final SQOp op; private final SpiQuery subQuery; private List bindParams; private String sql; - InQueryExpression(String propertyName, SpiQuery subQuery, boolean not) { + SubQueryExpression(SQOp op, String propertyName, SpiQuery subQuery) { super(propertyName); + this.op = op; this.subQuery = subQuery; - this.not = not; } - InQueryExpression(String propertyName, boolean not, String sql, List bindParams) { + SubQueryExpression(SQOp op, String propertyName, String sql, List bindParams) { super(propertyName); + this.op = op; this.subQuery = null; - this.not = not; this.sql = sql; this.bindParams = bindParams; } @Override public SpiExpression copy() { - return subQuery == null ? this : new InQueryExpression(propName, subQuery.copy(), not); + return subQuery == null ? this : new SubQueryExpression(op, propName, subQuery.copy()); } @Override @@ -55,8 +70,8 @@ final class InQueryExpression extends AbstractExpression implements UnsupportedD @Override public void queryPlanHash(StringBuilder builder) { - builder.append("InQuery[").append(propName) - .append(" not:").append(not).append(" sql:").append(sql) + builder.append("SubQuery[").append(propName).append(op.expression) + .append(" sql:").append(sql) .append(" ?:").append(bindParams.size()).append("]"); } @@ -65,7 +80,7 @@ final class InQueryExpression extends AbstractExpression implements UnsupportedD */ private CQuery compileSubQuery(BeanQueryRequest queryRequest) { SpiEbeanServer ebeanServer = (SpiEbeanServer) queryRequest.database(); - return ebeanServer.compileQuery(Type.SQ_IN, subQuery, queryRequest.transaction()); + return ebeanServer.compileQuery(Type.SQ_EX, subQuery, queryRequest.transaction()); } @Override @@ -75,13 +90,7 @@ final class InQueryExpression extends AbstractExpression implements UnsupportedD @Override public void addSql(SpiExpressionRequest request) { - request.append(propName); - if (not) { - request.append(" not"); - } - request.append(" in ("); - request.append(sql); - request.append(")"); + request.append(propName).append(op.expression).append("(").append(sql).append(")"); } @Override @@ -93,7 +102,7 @@ final class InQueryExpression extends AbstractExpression implements UnsupportedD @Override public boolean isSameByBind(SpiExpression other) { - InQueryExpression that = (InQueryExpression) other; + SubQueryExpression that = (SubQueryExpression) other; if (this.bindParams.size() != that.bindParams.size()) { return false; } diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java index 412c74fc8..fd7bbfeaf 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/query/SqlTreeBuilder.java @@ -86,7 +86,7 @@ public final class SqlTreeBuilder { this.disableLazyLoad = query.isDisableLazyLoading(); this.readOnly = Boolean.TRUE.equals(query.isReadOnly()); this.subQuery = Type.SQ_EXISTS == query.getType() - || Type.SQ_IN == query.getType() + || Type.SQ_EX == query.getType() || Type.ID_LIST == query.getType() || Type.DELETE == query.getType() || query.isCountDistinct(); diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java index 47be406be..57b7f2a50 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/expression/DefaultExpressionFactoryTest.java @@ -35,7 +35,7 @@ public class DefaultExpressionFactoryTest { public void testEq() { DefaultExpressionFactory factory = new DefaultExpressionFactory(false, false); - Expression expression = factory.eq("name", null); + Expression expression = factory.eq("name", (String)null); assertThat(expression).isInstanceOf(NullExpression.class); assertThat(toQueryPlanHash(expression)).isEqualTo("Null[name]"); } @@ -44,7 +44,7 @@ public class DefaultExpressionFactoryTest { public void testNe() { DefaultExpressionFactory factory = new DefaultExpressionFactory(false, false); - Expression expression = factory.ne("name", null); + Expression expression = factory.ne("name", (String)null); assertThat(expression).isInstanceOf(NullExpression.class); assertThat(toQueryPlanHash(expression)).isEqualTo("NotNull[name]"); } @@ -71,7 +71,7 @@ public class DefaultExpressionFactoryTest { public void testEq_with_equalsWithNullAsNoop() { DefaultExpressionFactory factory = new DefaultExpressionFactory(true, false); - Expression expression = factory.eq("name", null); + Expression expression = factory.eq("name", (String)null); assertThat(expression).isInstanceOf(NoopExpression.class); } @@ -79,7 +79,7 @@ public class DefaultExpressionFactoryTest { public void testNe_with_equalsWithNullAsNoop() { DefaultExpressionFactory factory = new DefaultExpressionFactory(true, false); - Expression expression = factory.ne("name", null); + Expression expression = factory.ne("name", (String)null); assertThat(expression).isInstanceOf(NoopExpression.class); } @@ -87,7 +87,7 @@ public class DefaultExpressionFactoryTest { public void testIeq_with_equalsWithNullAsNoop() { DefaultExpressionFactory factory = new DefaultExpressionFactory(true, false); - Expression expression = factory.ieq("name", null); + Expression expression = factory.ieq("name", (String)null); assertThat(expression).isInstanceOf(NoopExpression.class); } } diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/expression/InQueryExpressionTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/expression/InQueryExpressionTest.java index b8d9fc451..48adbdd1d 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/expression/InQueryExpressionTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/expression/InQueryExpressionTest.java @@ -12,14 +12,15 @@ import static org.mockito.Mockito.verify; public class InQueryExpressionTest extends BaseExpressionTest { - private InQueryExpression exp(String propertyName, boolean not, String sql, Object... bindValues) { - return new InQueryExpression(propertyName, not, sql, Arrays.asList(bindValues)); + private SubQueryExpression exp(String propertyName, boolean not, String sql, Object... bindValues) { + var op = not ? SubQueryExpression.SQOp.NOTIN : SubQueryExpression.SQOp.IN; + return new SubQueryExpression(op, propertyName, sql, Arrays.asList(bindValues)); } @Test void copy_subQuery_expectNewInstance() { SpiQuery subQuery = mock(SpiQuery.class); - var orig = new InQueryExpression("name", subQuery, false); + var orig = new SubQueryExpression(SubQueryExpression.SQOp.IN, "name", subQuery); SpiExpression copy = orig.copy(); assertThat(copy).isNotSameAs(orig); verify(subQuery).copy(); diff --git a/ebean-core/src/test/java/io/ebeaninternal/server/expression/NoopExpressionTest.java b/ebean-core/src/test/java/io/ebeaninternal/server/expression/NoopExpressionTest.java index fd5548b1c..3987becaa 100644 --- a/ebean-core/src/test/java/io/ebeaninternal/server/expression/NoopExpressionTest.java +++ b/ebean-core/src/test/java/io/ebeaninternal/server/expression/NoopExpressionTest.java @@ -27,9 +27,9 @@ public class NoopExpressionTest extends BaseExpressionTest { initTables(); Query query = DB.find(Customer.class) .select("id") - .where().eq("name", null) + .where().eq("name", (String) null) .add(NoopExpression.INSTANCE) - .ne("status", null) + .ne("status", (String) null) .query(); query.findList(); diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java index 75b279619..cc50bd0d0 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java @@ -1,6 +1,8 @@ package io.ebean.typequery; +import io.ebean.Query; + /** * Base property for all comparable types. * @@ -301,4 +303,47 @@ public class PBaseCompareable extends PBaseValueEqual { return _root; } + /** + * Property is Less Than or Equal To the result of a sub-query. + * + * @param subQuery value provided by a subQuery + * @return the root query bean instance + */ + public final R le(Query subQuery) { + expr().le(_name, subQuery); + return _root; + } + + /** + * Property is Less Than the result of a sub-query. + * + * @param subQuery value provided by a subQuery + * @return the root query bean instance + */ + public final R lt(Query subQuery) { + expr().lt(_name, subQuery); + return _root; + } + + /** + * Property is Greater Than or Equal To the result of a sub-query. + * + * @param subQuery value provided by a subQuery + * @return the root query bean instance + */ + public final R ge(Query subQuery) { + expr().ge(_name, subQuery); + return _root; + } + + /** + * Property is Greater Than the result of a sub-query. + * + * @param subQuery value provided by a subQuery + * @return the root query bean instance + */ + public final R gt(Query subQuery) { + expr().gt(_name, subQuery); + return _root; + } } diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java index 7d4edc0e9..58474a371 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java @@ -281,7 +281,7 @@ public abstract class PBaseValueEqual extends TQPropertyBase { } /** - * Is in the result of a subquery. Synonym for in(). + * Is in the result of a sub-query. Synonym for in(). * * @param subQuery values provided by a subQuery * @return the root query bean instance @@ -291,7 +291,7 @@ public abstract class PBaseValueEqual extends TQPropertyBase { } /** - * Is NOT in the result of a subquery. + * Is NOT in the result of a sub-query. * * @param subQuery values provided by a subQuery * @return the root query bean instance @@ -300,4 +300,26 @@ public abstract class PBaseValueEqual extends TQPropertyBase { expr().notIn(_name, subQuery); return _root; } + + /** + * Property is equal to the result of a sub-query. + * + * @param subQuery value provided by a subQuery + * @return the root query bean instance + */ + public final R eq(Query subQuery) { + expr().eq(_name, subQuery); + return _root; + } + + /** + * Property is not equal to the result of a sub-query. + * + * @param subQuery value provided by a subQuery + * @return the root query bean instance + */ + public final R ne(Query subQuery) { + expr().ne(_name, subQuery); + return _root; + } } diff --git a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java index 3edfc9310..e8563d879 100644 --- a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java @@ -4,6 +4,7 @@ import io.ebean.DB; import io.ebean.FetchGroup; import io.ebean.Query; import io.ebean.test.LoggedSql; +import io.ebean.typequery.TQProperty; import org.example.domain.Customer; import org.example.domain.Order; import org.example.domain.OrderDetail; @@ -217,6 +218,96 @@ public class QOrderTest { } + @Test + void geSubQuery() { + var subQuery = new QOrder() + .select(StdFunctions.max(QOrder.Alias.orderDate)) + .query(); + + var query = new QCustomer() + .select(QCustomer.Alias.id) + .registered.ge(subQuery) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered >= (select max(t0.order_date) from o_order t0)"); + } + + @Test + void gtSubQuery() { + var subQuery = new QOrder() + .select("max(orderDate)") + .query(); + + var query = new QCustomer() + .select(QCustomer.Alias.id) + .registered.gt(subQuery) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered > (select max(t0.order_date) from o_order t0)"); + } + + @Test + void leSubQuery() { + var subQuery = new QOrder() + .select("max(orderDate)") + .query(); + + var query = new QCustomer() + .select(QCustomer.Alias.id) + .registered.le(subQuery) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered <= (select max(t0.order_date) from o_order t0)"); + } + + @Test + void ltSubQuery() { + var subQuery = new QOrder() + .select("max(orderDate)") + .query(); + + var query = new QCustomer() + .select(QCustomer.Alias.id) + .registered.lt(subQuery) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered < (select max(t0.order_date) from o_order t0)"); + } + + @Test + void eqSubQuery() { + var subQuery = new QOrder() + .select("max(orderDate)") + .query(); + + var query = new QCustomer() + .select(QCustomer.Alias.id) + .registered.eq(subQuery) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered = (select max(t0.order_date) from o_order t0)"); + } + + @Test + void neSubQuery() { + var subQuery = new QOrder() + .select(StdFunctions.max(QOrder.Alias.orderDate)) + .query(); + + var query = new QCustomer() + .select(QCustomer.Alias.id) + .registered.ne(subQuery) + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered <> (select max(t0.order_date) from o_order t0)"); + } + @Test void propertyCompare() { Query query = new QOrder() @@ -283,4 +374,10 @@ public class QOrderTest { order.setCustomer(customer); order.save(); } + + static class StdFunctions { + static String max(TQProperty prop) { + return "max(" + prop + ")"; + } + } } diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java b/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java index a47084131..99f417300 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryIsNull.java @@ -28,7 +28,7 @@ public class TestQueryIsNull extends BaseTestCase { ResetBasicData.reset(); Query query = DB.find(Order.class).select("id, status") - .where().eq("status", null).query(); + .where().eq("status", (String) null).query(); query.findList(); assertThat(query.getGeneratedSql()).isEqualTo("select t0.id, t0.status from o_order t0 where t0.status is null"); diff --git a/ebean-test/src/test/java/org/tests/query/TestQueryNullAssocOne.java b/ebean-test/src/test/java/org/tests/query/TestQueryNullAssocOne.java index 1fde35127..e3e365533 100644 --- a/ebean-test/src/test/java/org/tests/query/TestQueryNullAssocOne.java +++ b/ebean-test/src/test/java/org/tests/query/TestQueryNullAssocOne.java @@ -1,8 +1,8 @@ package org.tests.query; -import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Query; +import io.ebean.xtest.BaseTestCase; import org.junit.jupiter.api.Test; import org.tests.model.basic.Order; import org.tests.model.basic.ResetBasicData; @@ -19,13 +19,13 @@ public class TestQueryNullAssocOne extends BaseTestCase { ResetBasicData.reset(); - Query q0 = DB.find(Order.class).where().eq("customer", null).query(); + Query q0 = DB.find(Order.class).where().eq("customer", (String) null).query(); List orders = q0.findList(); assertNotNull(orders); assertTrue(q0.getGeneratedSql().contains("where t0.kcustomer_id is null")); - Query q1 = DB.find(Order.class).where().eq("customer.id", null).query(); + Query q1 = DB.find(Order.class).where().eq("customer.id", (String) null).query(); List o1 = q1.findList(); assertTrue(o1.size() == orders.size()); diff --git a/ebean-test/src/test/java/org/tests/query/other/TestSelfParent.java b/ebean-test/src/test/java/org/tests/query/other/TestSelfParent.java index 9aa5c761a..9215b112c 100644 --- a/ebean-test/src/test/java/org/tests/query/other/TestSelfParent.java +++ b/ebean-test/src/test/java/org/tests/query/other/TestSelfParent.java @@ -38,7 +38,7 @@ public class TestSelfParent extends BaseTestCase { DB.save(child21); DB.save(child22); - List roots = DB.find(SelfParent.class).where().eq("parent", null).findList(); + List roots = DB.find(SelfParent.class).where().eq("parent", (String) null).findList(); assertEquals(1, roots.size());