diff --git a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java index da84e2836..e81f81636 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java @@ -1,10 +1,6 @@ package io.ebean; -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 java.util.Collection; import java.util.Map; @@ -423,6 +419,94 @@ public interface ExpressionFactory { */ Expression inOrEmpty(String propertyName, Collection values); + /** + * EXISTS a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression exists(String sqlSubQuery, Object... bindValues); + + /** + * Not EXISTS a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression notExists(String sqlSubQuery, Object... bindValues); + + /** + * IN a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression inSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Not IN a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression notInSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression eqSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Not Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression neSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Greater Than or Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression geSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Greater Than a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression gtSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Less Than or Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Less Than a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + Expression ltSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + /** * Not In - property has a value in the array of values. */ diff --git a/ebean-api/src/main/java/io/ebean/ExpressionList.java b/ebean-api/src/main/java/io/ebean/ExpressionList.java index 211da6664..4d0a2fc18 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionList.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionList.java @@ -1141,6 +1141,94 @@ public interface ExpressionList { */ ExpressionList inPairs(Pairs pairs); + /** + * EXISTS a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList exists(String sqlSubQuery, Object... bindValues); + + /** + * Not EXISTS a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList notExists(String sqlSubQuery, Object... bindValues); + + /** + * IN a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList inSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Not IN a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList notInSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList eqSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Not Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList neSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Greater Than a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList gtSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Greater Than or Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList geSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Less Than a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList ltSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + + /** + * Less Than or Equal To a raw SQL SubQuery. + * + * @param propertyName The bean property + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + ExpressionList leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues); + /** * In - using a subQuery. */ 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 b6cc327db..1ad8a9014 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 @@ -5,7 +5,6 @@ import io.ebean.bean.EntityBean; 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; @@ -149,7 +148,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { @Override public Expression eq(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.EQ, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.EQ, propertyName, (SpiQuery) subQuery); } /** @@ -170,7 +169,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { @Override public Expression ne(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.NE, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.NE, propertyName, (SpiQuery) subQuery); } /** @@ -260,7 +259,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { @Override public Expression gt(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.GT, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.GT, propertyName, (SpiQuery) subQuery); } /** @@ -286,7 +285,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { @Override public Expression ge(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.GE, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.GE, propertyName, (SpiQuery) subQuery); } /** @@ -313,7 +312,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { @Override public Expression lt(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.LT, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.LT, propertyName, (SpiQuery) subQuery); } /** @@ -326,7 +325,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { @Override public Expression le(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.LE, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.LE, propertyName, (SpiQuery) subQuery); } /** @@ -480,12 +479,68 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return new InExpression(propertyName, values, false); } + /** + * In - using a subQuery. + */ + @Override + public Expression exists(String subQuery, Object... bindValues) { + return new ExistsSqlQueryExpression(false, subQuery, bindValues); + } + + /** + * In - using a subQuery. + */ + @Override + public Expression notExists(String subQuery, Object... bindValues) { + return new ExistsSqlQueryExpression(true, subQuery, bindValues); + } + /** * In - using a subQuery. */ @Override public Expression in(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.IN, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.IN, propertyName, (SpiQuery) subQuery); + } + + @Override + public Expression inSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.IN, propertyName, subQuery, bindValues); + } + + @Override + public Expression notInSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.NOTIN, propertyName, subQuery, bindValues); + } + + @Override + public Expression eqSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.EQ, propertyName, subQuery, bindValues); + } + + @Override + public Expression neSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.NE, propertyName, subQuery, bindValues); + } + + @Override + public Expression geSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.GE, propertyName, subQuery, bindValues); + } + + @Override + public Expression gtSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.GT, propertyName, subQuery, bindValues); + } + + @Override + public Expression leSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.LE, propertyName, subQuery, bindValues); + } + + @Override + public Expression ltSubQuery(String propertyName, String subQuery, Object... bindValues) { + return new SubQueryRawExpression(SubQueryOp.LT, propertyName, subQuery, bindValues); } /** @@ -527,7 +582,7 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { */ @Override public Expression notIn(String propertyName, Query subQuery) { - return new SubQueryExpression(SQOp.NOTIN, propertyName, (SpiQuery) subQuery); + return new SubQueryExpression(SubQueryOp.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 3e25abe1b..3a015e0fa 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 @@ -2,51 +2,16 @@ package io.ebeaninternal.server.expression; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; -import io.ebean.CacheMode; -import io.ebean.CountDistinctOrder; -import io.ebean.DtoQuery; -import io.ebean.Expression; -import io.ebean.ExpressionFactory; -import io.ebean.ExpressionList; -import io.ebean.FetchGroup; -import io.ebean.FetchPath; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.Junction; -import io.ebean.OrderBy; -import io.ebean.PagedList; -import io.ebean.Pairs; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.Transaction; -import io.ebean.UpdateQuery; -import io.ebean.Version; +import io.ebean.*; import io.ebean.event.BeanQueryRequest; -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.ebeaninternal.api.BindValuesKey; -import io.ebeaninternal.api.ManyWhereJoins; -import io.ebeaninternal.api.NaturalKeyQueryData; -import io.ebeaninternal.api.SpiExpression; -import io.ebeaninternal.api.SpiExpressionList; -import io.ebeaninternal.api.SpiExpressionRequest; -import io.ebeaninternal.api.SpiExpressionValidation; -import io.ebeaninternal.api.SpiJunction; +import io.ebean.search.*; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.deploy.BeanDescriptor; import java.io.IOException; import java.sql.Connection; import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -953,6 +918,57 @@ public class DefaultExpressionList implements SpiExpressionList { return add(expr.inPairs(pairs)); } + + @Override + public ExpressionList exists(String sqlSubQuery, Object... bindValues) { + return add(expr.exists(sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList notExists(String sqlSubQuery, Object... bindValues) { + return add(expr.notExists(sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList inSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.inSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList notInSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.notInSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList eqSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.eqSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList neSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.neSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList gtSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.gtSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList geSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.geSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList ltSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.ltSubQuery(propertyName, sqlSubQuery, bindValues)); + } + + @Override + public ExpressionList leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return add(expr.leSubQuery(propertyName, sqlSubQuery, bindValues)); + } + @Override public ExpressionList in(String propertyName, Query subQuery) { return add(expr.in(propertyName, subQuery)); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java index 30b40d53f..60b831dc7 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsQueryExpression.java @@ -93,9 +93,9 @@ final class ExistsQueryExpression implements SpiExpression, UnsupportedDocStoreE @Override public void addSql(SpiExpressionRequest request) { if (not) { - request.append(" not"); + request.append("not "); } - request.append(" exists (").parse(sql).append(")"); + request.append("exists (").parse(sql).append(")"); } @Override diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsSqlQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsSqlQueryExpression.java new file mode 100644 index 000000000..7f09de4c5 --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/ExistsSqlQueryExpression.java @@ -0,0 +1,106 @@ +package io.ebeaninternal.server.expression; + +import io.ebean.event.BeanQueryRequest; +import io.ebeaninternal.api.*; +import io.ebeaninternal.server.deploy.BeanDescriptor; + +import java.util.Arrays; + +final class ExistsSqlQueryExpression implements SpiExpression, UnsupportedDocStoreExpression { + + private final boolean not; + private final String subQuery; + private final Object[] bindParams; + + ExistsSqlQueryExpression(boolean not, String subQuery, Object[] bindParams) { + this.not = not; + this.subQuery = subQuery; + this.bindParams = bindParams; + } + + @Override + public void prefixProperty(String path) { + // do nothing + } + + @Override + public boolean naturalKey(NaturalKeyQueryData data) { + // can't use naturalKey cache + return false; + } + + @Override + public void simplify() { + // do nothing + } + + @Override + public void writeDocQuery(DocQueryContext context) { + throw new IllegalStateException("Not supported"); + } + + @Override + public Object getIdEqualTo(String idName) { + // always return null for this expression + return null; + } + + @Override + public void prepareExpression(BeanQueryRequest request) { + // do nothing + } + + @Override + public SpiExpression copyForPlanKey() { + return this; + } + + @Override + public void queryPlanHash(StringBuilder builder) { + builder.append("ExistsSqlQuery[").append(" not:").append(not); + builder.append(" sql:").append(subQuery).append(" ?:").append(bindParams.length).append("]"); + } + + @Override + public void queryBindKey(BindValuesKey key) { + for (Object value : bindParams) { + key.add(value); + } + } + + @Override + public void addSql(SpiExpressionRequest request) { + if (not) { + request.append("not "); + } + request.append("exists (").parse(subQuery).append(")"); + } + + @Override + public void addBindValues(SpiExpressionRequest request) { + for (Object bindParam : bindParams) { + request.addBindValue(bindParam); + } + } + + @Override + public boolean isSameByBind(SpiExpression other) { + final ExistsSqlQueryExpression that = (ExistsSqlQueryExpression) other; + return Arrays.equals(bindParams, that.bindParams); + } + + @Override + public String nestedPath(BeanDescriptor desc) { + return null; + } + + @Override + public void containsMany(BeanDescriptor desc, ManyWhereJoins whereManyJoins) { + // Nothing to do for exists expression + } + + @Override + public void validate(SpiExpressionValidation validation) { + // Nothing to do for exists expression + } +} 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 01ee2147f..87d6ae331 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 @@ -2,48 +2,16 @@ package io.ebeaninternal.server.expression; import io.avaje.lang.NonNullApi; import io.avaje.lang.Nullable; -import io.ebean.CacheMode; -import io.ebean.CountDistinctOrder; -import io.ebean.DtoQuery; -import io.ebean.Expression; -import io.ebean.ExpressionList; -import io.ebean.FetchGroup; -import io.ebean.FetchPath; -import io.ebean.FutureIds; -import io.ebean.FutureList; -import io.ebean.FutureRowCount; -import io.ebean.Junction; -import io.ebean.OrderBy; -import io.ebean.PagedList; -import io.ebean.Pairs; -import io.ebean.Query; -import io.ebean.QueryIterator; -import io.ebean.Transaction; -import io.ebean.UpdateQuery; -import io.ebean.Version; +import io.ebean.*; import io.ebean.event.BeanQueryRequest; -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.ebeaninternal.api.BindValuesKey; -import io.ebeaninternal.api.ManyWhereJoins; -import io.ebeaninternal.api.NaturalKeyQueryData; -import io.ebeaninternal.api.SpiExpression; -import io.ebeaninternal.api.SpiExpressionRequest; -import io.ebeaninternal.api.SpiExpressionValidation; -import io.ebeaninternal.api.SpiJunction; +import io.ebean.search.*; +import io.ebeaninternal.api.*; import io.ebeaninternal.server.deploy.BeanDescriptor; import java.io.IOException; import java.sql.Connection; import java.sql.Timestamp; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; +import java.util.*; import java.util.function.Consumer; import java.util.function.Predicate; @@ -762,6 +730,56 @@ final class JunctionExpression implements SpiJunction, SpiExpression, Expr return exprList.in(propertyName, subQuery); } + @Override + public ExpressionList exists(String sqlSubQuery, Object... bindValues) { + return exprList.exists(sqlSubQuery, bindValues); + } + + @Override + public ExpressionList notExists(String sqlSubQuery, Object... bindValues) { + return exprList.notExists(sqlSubQuery, bindValues); + } + + @Override + public ExpressionList inSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.inSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList notInSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.notInSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList eqSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.eqSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList neSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.neSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList gtSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.gtSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList geSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.geSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList ltSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.ltSubQuery(propertyName, sqlSubQuery, bindValues); + } + + @Override + public ExpressionList leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) { + return exprList.leSubQuery(propertyName, sqlSubQuery, bindValues); + } + @Override public ExpressionList notIn(String propertyName, Collection values) { return exprList.notIn(propertyName, values); diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java index 21fd33c8b..ff60d7479 100644 --- a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryExpression.java @@ -12,33 +12,18 @@ import java.util.List; */ final class SubQueryExpression extends AbstractExpression implements UnsupportedDocStoreExpression { - 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 SubQueryOp op; private final SpiQuery subQuery; private List bindParams; private String sql; - SubQueryExpression(SQOp op, String propertyName, SpiQuery subQuery) { + SubQueryExpression(SubQueryOp op, String propertyName, SpiQuery subQuery) { super(propertyName); this.op = op; this.subQuery = subQuery; } - SubQueryExpression(SQOp op, String propertyName, String sql, List bindParams) { + SubQueryExpression(SubQueryOp op, String propertyName, String sql, List bindParams) { super(propertyName); this.op = op; this.subQuery = null; diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryOp.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryOp.java new file mode 100644 index 000000000..2ea6a7f1a --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryOp.java @@ -0,0 +1,17 @@ +package io.ebeaninternal.server.expression; + +enum SubQueryOp { + EQ(" = "), + NE(" <> "), + GT(" > "), + GE(" >= "), + LT(" < "), + LE(" <= "), + IN(" in "), + NOTIN(" not in "); + final String expression; + + SubQueryOp(String expression) { + this.expression = expression; + } +} diff --git a/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryRawExpression.java b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryRawExpression.java new file mode 100644 index 000000000..007386d5b --- /dev/null +++ b/ebean-core/src/main/java/io/ebeaninternal/server/expression/SubQueryRawExpression.java @@ -0,0 +1,61 @@ +package io.ebeaninternal.server.expression; + +import io.ebeaninternal.api.BindValuesKey; +import io.ebeaninternal.api.SpiExpression; +import io.ebeaninternal.api.SpiExpressionRequest; + +import java.util.Arrays; + +/** + * Raw SQL based Sub-Query expression. + */ +final class SubQueryRawExpression extends AbstractExpression implements UnsupportedDocStoreExpression { + + private final SubQueryOp op; + private final String subQuery; + private final Object[] bindParams; + + SubQueryRawExpression(SubQueryOp op, String propertyName, String subQuery, Object[] bindParams) { + super(propertyName); + this.op = op; + this.subQuery = subQuery; + this.bindParams = bindParams; + } + + @Override + public void writeDocQuery(DocQueryContext context) { + throw new IllegalStateException("Not supported"); + } + + @Override + public void queryPlanHash(StringBuilder builder) { + builder.append("SubQueryRaw[").append(propName).append(op.expression) + .append(" subQuery:").append(subQuery) + .append(" ?:").append(bindParams.length).append("]"); + } + + @Override + public void queryBindKey(BindValuesKey key) { + for (Object value : bindParams) { + key.add(value); + } + } + + @Override + public void addSql(SpiExpressionRequest request) { + request.property(propName).append(op.expression).append("(").append(subQuery).append(")"); + } + + @Override + public void addBindValues(SpiExpressionRequest request) { + for (Object bindParam : bindParams) { + request.addBindValue(bindParam); + } + } + + @Override + public boolean isSameByBind(SpiExpression other) { + final SubQueryRawExpression that = (SubQueryRawExpression) other; + return Arrays.equals(bindParams, that.bindParams); + } +} 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 48adbdd1d..29e60ae13 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 @@ -13,14 +13,14 @@ import static org.mockito.Mockito.verify; public class InQueryExpressionTest extends BaseExpressionTest { private SubQueryExpression exp(String propertyName, boolean not, String sql, Object... bindValues) { - var op = not ? SubQueryExpression.SQOp.NOTIN : SubQueryExpression.SQOp.IN; + var op = not ? SubQueryOp.NOTIN : SubQueryOp.IN; return new SubQueryExpression(op, propertyName, sql, Arrays.asList(bindValues)); } @Test void copy_subQuery_expectNewInstance() { SpiQuery subQuery = mock(SpiQuery.class); - var orig = new SubQueryExpression(SubQueryExpression.SQOp.IN, "name", subQuery); + var orig = new SubQueryExpression(SubQueryOp.IN, "name", subQuery); SpiExpression copy = orig.copy(); assertThat(copy).isNotSameAs(orig); verify(subQuery).copy(); diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java index dfdac7cad..0514f7ada 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseComparable.java @@ -413,4 +413,48 @@ public abstract class PBaseComparable extends PBaseValueEqual { expr().gt(_name, subQuery); return _root; } + + /** + * Less Than or Equal To a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R leSubQuery(String sqlSubQuery, Object... bindValues) { + expr().leSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + + /** + * Less Than a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R ltSubQuery(String sqlSubQuery, Object... bindValues) { + expr().ltSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + + /** + * Greater Than or Equal To a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R geSubQuery(String sqlSubQuery, Object... bindValues) { + expr().geSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + + /** + * Greater Than a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R gtSubQuery(String sqlSubQuery, Object... bindValues) { + expr().gtSubQuery(_name, sqlSubQuery, bindValues); + 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 1f25c916e..de6d563bd 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseValueEqual.java @@ -301,6 +301,50 @@ public abstract class PBaseValueEqual extends TQPropertyBase { return _root; } + /** + * IN a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R inSubQuery(String sqlSubQuery, Object... bindValues) { + expr().inSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + + /** + * Not IN a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R notInSubQuery(String sqlSubQuery, Object... bindValues) { + expr().notInSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + + /** + * Equal To a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R eqSubQuery(String sqlSubQuery, Object... bindValues) { + expr().eqSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + + /** + * Not Equal To a raw SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R neSubQuery(String sqlSubQuery, Object... bindValues) { + expr().neSubQuery(_name, sqlSubQuery, bindValues); + return _root; + } + /** * Property is equal to the result of a sub-query. * diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java index 8ba6e51f7..847ff86b0 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -733,6 +733,28 @@ public abstract class TQRootBean { return root; } + /** + * EXISTS using a SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R exists(String sqlSubQuery, Object... bindValues) { + query().where().exists(sqlSubQuery, bindValues); + return root; + } + + /** + * Not EXISTS using a SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + public final R notExists(String sqlSubQuery, Object... bindValues) { + query().where().notExists(sqlSubQuery, bindValues); + return root; + } + /** * Execute using "for update" clause which results in the DB locking the record. */ diff --git a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java index 935327359..bcfa451ec 100644 --- a/ebean-querybean/src/test/java/org/querytest/QOrderTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QOrderTest.java @@ -268,6 +268,84 @@ public class QOrderTest { assertThat(sql).isEqualTo("select t0.id, t0.status from o_order t0 join be_customer t1 on t1.id = t0.customer_id where (coalesce(t1.version,0) > ? or t0.id < ?)"); } + @Test + void geSqlSubQuery() { + var c = QCustomer.alias(); + + var query = new QCustomer() + .select(c.id) + .registered.geSubQuery("select max(o.order_date) as foo from o_order o") + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered >= (select max(o.order_date) as foo from o_order o)"); + } + + @Test + void gtSqlSubQuery() { + var c = QCustomer.alias(); + + var query = new QCustomer() + .select(c.id) + .registered.gtSubQuery("select max(o.order_date) as foo from o_order o") + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered > (select max(o.order_date) as foo from o_order o)"); + } + + @Test + void leSqlSubQuery() { + var c = QCustomer.alias(); + + var query = new QCustomer() + .select(c.id) + .registered.leSubQuery("select max(o.order_date) as foo from o_order o") + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered <= (select max(o.order_date) as foo from o_order o)"); + } + + @Test + void ltSqlSubQuery() { + var c = QCustomer.alias(); + + var query = new QCustomer() + .select(c.id) + .registered.ltSubQuery("select max(o.order_date) as foo from o_order o") + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered < (select max(o.order_date) as foo from o_order o)"); + } + + @Test + void eqSqlSubQuery() { + var c = QCustomer.alias(); + + var query = new QCustomer() + .select(c.id) + .registered.eqSubQuery("select max(o.order_date) as foo from o_order o") + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered = (select max(o.order_date) as foo from o_order o)"); + } + + @Test + void neSqlSubQuery() { + var c = QCustomer.alias(); + + var query = new QCustomer() + .select(c.id) + .registered.neSubQuery("select max(o.order_date) as foo from o_order o") + .query(); + query.findList(); + + assertThat(query.getGeneratedSql()).contains("select t0.id from be_customer t0 where t0.registered <> (select max(o.order_date) as foo from o_order o)"); + } + @Test void geSubQuery() { var c = QCustomer.alias(); @@ -394,6 +472,30 @@ public class QOrderTest { assertThat(sql).contains(" exists (select 1 from o_order_detail od where od.order_qty > ? and od.id = t1.id)"); } + @Test + void sqlSubQueryExists() { + Query query = new QOrder() + .exists("select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id", 10) + .orderBy("orderDate").query(); + + query.findList(); + + String sql = query.getGeneratedSql(); + assertThat(sql).contains(" exists (select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id) order by t0.order_date"); + } + + @Test + void sqlSubQueryNotExists() { + Query query = new QOrder() + .notExists("select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id", 10) + .orderBy("orderDate").query(); + + query.findList(); + + String sql = query.getGeneratedSql(); + assertThat(sql).contains(" not exists (select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id) order by t0.order_date"); + } + @Test void subQueryNotExists() { Query subQuery = new QOrderDetail() diff --git a/ebean-test/src/test/java/org/tests/query/TestWhereRawClause.java b/ebean-test/src/test/java/org/tests/query/TestWhereRawClause.java index 7c2b36603..7de9f77f6 100644 --- a/ebean-test/src/test/java/org/tests/query/TestWhereRawClause.java +++ b/ebean-test/src/test/java/org/tests/query/TestWhereRawClause.java @@ -1,12 +1,12 @@ package org.tests.query; -import io.ebean.xtest.BaseTestCase; import io.ebean.DB; import io.ebean.Expr; import io.ebean.Query; -import io.ebean.xtest.ForPlatform; import io.ebean.annotation.Platform; import io.ebean.test.LoggedSql; +import io.ebean.xtest.BaseTestCase; +import io.ebean.xtest.ForPlatform; import org.junit.jupiter.api.Test; import org.tests.model.basic.Customer; import org.tests.model.basic.Order; @@ -187,4 +187,93 @@ public class TestWhereRawClause extends BaseTestCase { .findList(); } + + @Test + void testSqlSubQueryExists() { + ResetBasicData.reset(); + + LoggedSql.start(); + DB.find(Order.class) + .select("id") + .where() + .exists("select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id", 10) + .findList(); + + DB.find(Order.class) + .select("id") + .where() + .notExists("select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id", 10) + .findList(); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(2); + assertThat(sql.get(0)).contains("select t0.id from o_order t0 where exists (select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id)"); + assertThat(sql.get(1)).contains("select t0.id from o_order t0 where not exists (select 1 from o_order_detail od where od.order_qty > ? and od.order_id = t0.id)"); + } + + @Test + void testSqlSubQueryExpressions() { + ResetBasicData.reset(); + + LoggedSql.start(); + DB.find(Customer.class) + .select("name") + .where() + .inSubQuery("id", "select c.id as id from o_customer c") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .notInSubQuery("id", "select c.id as id from o_customer c") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .eqSubQuery("id", "select c.id as id from o_customer c where c.name = ?", "Rob") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .neSubQuery("id", "select c.id as id from o_customer c where c.name = ?", "Rob") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .gtSubQuery("id", "select c.id as id from o_customer c where c.name = ?", "Rob") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .geSubQuery("id", "select c.id as id from o_customer c where c.name = ?", "Rob") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .ltSubQuery("id", "select c.id as id from o_customer c where c.name = ?", "Rob") + .findList(); + + DB.find(Customer.class) + .select("name") + .where() + .leSubQuery("id", "select c.id as id from o_customer c where c.name = ? and 1 = ?", "Rob", 1) + .like("name", "R%") + .findList(); + + List sql = LoggedSql.stop(); + assertThat(sql).hasSize(8); + assertThat(sql.get(0)).contains("select t0.id, t0.name from o_customer t0 where t0.id in (select c.id as id from o_customer c)"); + assertThat(sql.get(1)).contains("select t0.id, t0.name from o_customer t0 where t0.id not in (select c.id as id from o_customer c)"); + assertThat(sql.get(2)).contains("select t0.id, t0.name from o_customer t0 where t0.id = (select c.id as id from o_customer c where c.name = ?)"); + assertThat(sql.get(3)).contains("select t0.id, t0.name from o_customer t0 where t0.id <> (select c.id as id from o_customer c where c.name = ?)"); + assertThat(sql.get(4)).contains("select t0.id, t0.name from o_customer t0 where t0.id > (select c.id as id from o_customer c where c.name = ?)"); + assertThat(sql.get(5)).contains("select t0.id, t0.name from o_customer t0 where t0.id >= (select c.id as id from o_customer c where c.name = ?)"); + assertThat(sql.get(6)).contains("select t0.id, t0.name from o_customer t0 where t0.id < (select c.id as id from o_customer c where c.name = ?)"); + assertThat(sql.get(7)).contains("select t0.id, t0.name from o_customer t0 where t0.id <= (select c.id as id from o_customer c where c.name = ? and 1 = ?)"); + } }