Add support for raw SQL SubQuery expressions IN,EQ,NE,GT,GE,LT,LE,EXISTS,Not IN, Not EXISTS

For these expressions the SQL SubQuery is not parsed by ebean and
just used as is.
This commit is contained in:
Rob Bygrave
2023-03-24 22:38:52 +13:00
parent a68665a37e
commit 79fd689e9a
16 changed files with 844 additions and 113 deletions
@@ -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.
*/
@@ -1141,6 +1141,94 @@ public interface ExpressionList<T> {
*/
ExpressionList<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> 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<T> leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues);
/**
* In - using a subQuery.
*/
@@ -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);
}
/**
@@ -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<T> implements SpiExpressionList<T> {
return add(expr.inPairs(pairs));
}
@Override
public ExpressionList<T> exists(String sqlSubQuery, Object... bindValues) {
return add(expr.exists(sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> notExists(String sqlSubQuery, Object... bindValues) {
return add(expr.notExists(sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> inSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.inSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> notInSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.notInSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> eqSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.eqSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> neSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.neSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> gtSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.gtSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> geSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.geSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> ltSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.ltSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return add(expr.leSubQuery(propertyName, sqlSubQuery, bindValues));
}
@Override
public ExpressionList<T> in(String propertyName, Query<?> subQuery) {
return add(expr.in(propertyName, subQuery));
@@ -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
@@ -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
}
}
@@ -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<T> implements SpiJunction<T>, SpiExpression, Expr
return exprList.in(propertyName, subQuery);
}
@Override
public ExpressionList<T> exists(String sqlSubQuery, Object... bindValues) {
return exprList.exists(sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> notExists(String sqlSubQuery, Object... bindValues) {
return exprList.notExists(sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> inSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.inSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> notInSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.notInSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> eqSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.eqSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> neSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.neSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> gtSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.gtSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> geSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.geSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> ltSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.ltSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> leSubQuery(String propertyName, String sqlSubQuery, Object... bindValues) {
return exprList.leSubQuery(propertyName, sqlSubQuery, bindValues);
}
@Override
public ExpressionList<T> notIn(String propertyName, Collection<?> values) {
return exprList.notIn(propertyName, values);
@@ -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<Object> 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<Object> bindParams) {
SubQueryExpression(SubQueryOp op, String propertyName, String sql, List<Object> bindParams) {
super(propertyName);
this.op = op;
this.subQuery = null;
@@ -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;
}
}
@@ -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);
}
}
@@ -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();
@@ -413,4 +413,48 @@ public abstract class PBaseComparable<R, T> extends PBaseValueEqual<R, T> {
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;
}
}
@@ -301,6 +301,50 @@ public abstract class PBaseValueEqual<R, T> extends TQPropertyBase<R, T> {
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.
*
@@ -733,6 +733,28 @@ public abstract class TQRootBean<T, R> {
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.
*/
@@ -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<Order> 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<Order> 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<OrderDetail> subQuery = new QOrderDetail()
@@ -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<String> 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<String> 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 = ?)");
}
}