Skip to content

Commit

Permalink
Merge pull request #3473 from ebean-orm/feature/3455-query-beans-impr…
Browse files Browse the repository at this point in the history
…ovement

#3455 - Improve query beans such that filterMany() expressions are only on ToMany relationships
  • Loading branch information
rbygrave authored Sep 17, 2024
2 parents acbce35 + a2e66bc commit 5095583
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 100 deletions.
2 changes: 1 addition & 1 deletion ebean-api/src/main/resources/META-INF/ebean-version.mf
Original file line number Diff line number Diff line change
@@ -1 +1 @@
ebean-version: 143
ebean-version: 145
85 changes: 10 additions & 75 deletions ebean-querybean/src/main/java/io/ebean/typequery/TQAssocBean.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.ebean.typequery;

import io.ebean.Expr;
import io.ebean.ExpressionList;
import io.ebean.FetchConfig;
import io.ebean.FetchGroup;
Expand Down Expand Up @@ -188,102 +189,36 @@ private Set<String> properties(TQProperty<?, ?>... props) {
return set;
}


/**
* Apply a filter when fetching these beans.
*/
public final R filterMany(ExpressionList<T> filter) {
protected final R _filterMany(ExpressionList<T> filter) {
@SuppressWarnings("unchecked")
ExpressionList<T> expressionList = (ExpressionList<T>) expr().filterMany(_name);
expressionList.addAll(filter);
return _root;
}

/**
* @deprecated for removal - migrate to {@link #filterManyRaw(String, Object...)}.
* <p>
* Apply a filter when fetching these beans.
* <p>
* The expressions can use any valid Ebean expression and contain
* placeholders for bind values using <code>?</code> or <code>?1</code> style.
* </p>
*
* <pre>{@code
*
* new QCustomer()
* .name.startsWith("Postgres")
* .contacts.filterMany("firstName istartsWith ?", "Rob")
* .findList();
*
* }</pre>
*
* <pre>{@code
*
* new QCustomer()
* .name.startsWith("Postgres")
* .contacts.filterMany("whenCreated inRange ? to ?", startDate, endDate)
* .findList();
*
* }</pre>
*
* @param expressions The expressions including and, or, not etc with ? and ?1 bind params.
* @param params The bind parameter values
*/
@Deprecated(forRemoval = true)
public final R filterMany(String expressions, Object... params) {
protected final R _filterMany(String expressions, Object... params) {
expr().filterMany(_name, expressions, params);
return _root;
}

/**
* Add filter expressions for the many path. The expressions can include SQL functions if
* desired and the property names are translated to column names.
* <p>
* The expressions can contain placeholders for bind values using <code>?</code> or <code>?1</code> style.
*
* <pre>{@code
*
* new QCustomer()
* .name.startsWith("Shrek")
* .contacts.filterManyRaw("status = ? and firstName like ?", Contact.Status.NEW, "Rob%")
* .findList();
*
* }</pre>
*
* @param rawExpressions The raw expressions which can include ? and ?1 style bind parameter placeholders
* @param params The parameter values to bind
*/
public final R filterManyRaw(String rawExpressions, Object... params) {
protected final R _filterManyRaw(String rawExpressions, Object... params) {
expr().filterManyRaw(_name, rawExpressions, params);
return _root;
}

/**
* Is empty for a collection property.
* <p>
* This effectively adds a not exists sub-query on the collection property.
* </p>
* <p>
* This expression only works on OneToMany and ManyToMany properties.
* </p>
*/
public final R isEmpty() {
protected final R _isEmpty() {
expr().isEmpty(_name);
return _root;
}

/**
* Is not empty for a collection property.
* <p>
* This effectively adds an exists sub-query on the collection property.
* </p>
* <p>
* This expression only works on OneToMany and ManyToMany properties.
* </p>
*/
public final R isNotEmpty() {
protected final R _isNotEmpty() {
expr().isNotEmpty(_name);
return _root;
}

protected final <S> ExpressionList<S> _newExpressionList() {
return Expr.factory().expressionList();
}

}
94 changes: 94 additions & 0 deletions ebean-querybean/src/main/java/io/ebean/typequery/TQAssocMany.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package io.ebean.typequery;

import io.ebean.ExpressionList;

/**
* Expressions for Query Bean ToMany relationships.
*
* @param <T> The entity bean type
* @param <R> The root query bean type
*/
public interface TQAssocMany<T, R, QB> {

/**
* Filter the beans fetched for this relationship.
*
* @param filter The filter to apply
*/
R filterMany(java.util.function.Consumer<QB> filter);

/**
* Filter the beans fetched for this relationship.
*/
R filterMany(ExpressionList<T> filter);

/**
* @param expressions The expressions including and, or, not etc with ? and ?1 bind params.
* @param params The bind parameter values
* @deprecated for removal - migrate to {@link #filterManyRaw(String, Object...)}.
* <p>
* Apply a filter when fetching these beans.
* <p>
* The expressions can use any valid Ebean expression and contain
* placeholders for bind values using <code>?</code> or <code>?1</code> style.
* </p>
*
* <pre>{@code
*
* new QCustomer()
* .name.startsWith("Postgres")
* .contacts.filterMany("firstName istartsWith ?", "Rob")
* .findList();
*
* }</pre>
*
* <pre>{@code
*
* new QCustomer()
* .name.startsWith("Postgres")
* .contacts.filterMany("whenCreated inRange ? to ?", startDate, endDate)
* .findList();
*
* }</pre>
*/
@Deprecated(forRemoval = true)
R filterMany(String expressions, Object... params);

/**
* Add filter expressions for the many path. The expressions can include SQL functions if
* desired and the property names are translated to column names.
* <p>
* The expressions can contain placeholders for bind values using <code>?</code> or <code>?1</code> style.
*
* <pre>{@code
*
* new QCustomer()
* .name.startsWith("Shrek")
* .contacts.filterManyRaw("status = ? and firstName like ?", Contact.Status.NEW, "Rob%")
* .findList();
*
* }</pre>
*
* @param rawExpressions The raw expressions which can include ? and ?1 style bind parameter placeholders
* @param params The parameter values to bind
*/
R filterManyRaw(String rawExpressions, Object... params);

/**
* Is empty for a collection property.
* <p>
* This effectively adds a not exists sub-query on the collection property.
* <p>
* This expression only works on OneToMany and ManyToMany properties.
*/
R isEmpty();

/**
* Is not empty for a collection property.
* <p>
* This effectively adds an exists sub-query on the collection property.
* <p>
* This expression only works on OneToMany and ManyToMany properties.
*/
R isNotEmpty();
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ void findInTuples() {
var query = new QContact()
.firstName.isNotNull()
.email.isNotNull()
.others.filterMany(o -> o.something.gt(43))
.inTuples(inTuples)
.query();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ interface Constants {
String AT_TYPEQUERYBEAN = "@io.ebean.typequery.TypeQueryBean(\"v1\")";
String GENERATED = "io.ebean.typequery.Generated";

String ONE_TO_MANY = "jakarta.persistence.OneToMany";
String MANY_TO_MANY = "jakarta.persistence.ManyToMany";
String MAPPED_SUPERCLASS = "jakarta.persistence.MappedSuperclass";
String DISCRIMINATOR_VALUE = "jakarta.persistence.DiscriminatorValue";
String INHERITANCE = "jakarta.persistence.Inheritance";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,10 @@ private static boolean dbArrayField(Element field) {
return hasAnnotations(field, DBARRAY);
}

private static boolean dbToMany(Element field) {
return hasAnnotations(field, ONE_TO_MANY, MANY_TO_MANY);
}

/**
* Escape the type (e.g. java.lang.String) from the TypeMirror toString().
*/
Expand All @@ -239,6 +243,7 @@ private String trimAnnotations(String type) {
}

PropertyType getPropertyType(VariableElement field) {
boolean toMany = dbToMany(field);
if (dbJsonField(field)) {
return propertyTypeMap.getDbJsonType();
}
Expand Down Expand Up @@ -278,13 +283,15 @@ PropertyType getPropertyType(VariableElement field) {
if (targetEntity != null) {
final TypeElement element = elementUtils.getTypeElement(targetEntity);
if (isEntityOrEmbedded(element)) {
return createPropertyTypeAssoc(typeDef(element.asType()));
boolean embeddable = isEmbeddable(element);
return createPropertyTypeAssoc(embeddable, toMany, typeDef(element.asType()));
}
}

if (isEntityOrEmbedded(fieldType)) {
// public QAssocContact<QCustomer> contacts;
return createPropertyTypeAssoc(typeDef(typeMirror));
boolean embeddable = isEmbeddable(fieldType);
return createPropertyTypeAssoc(embeddable, toMany, typeDef(typeMirror));
}

final PropertyType result;
Expand Down Expand Up @@ -322,16 +329,19 @@ private boolean typeInstanceOf(final TypeMirror typeMirror, final CharSequence d
}

private PropertyType createManyTypeAssoc(VariableElement field, DeclaredType declaredType) {
boolean toMany = dbToMany(field);
List<? extends TypeMirror> typeArguments = declaredType.getTypeArguments();
if (typeArguments.size() == 1) {
Element argElement = typeUtils.asElement(typeArguments.get(0));
if (isEntityOrEmbedded(argElement)) {
return createPropertyTypeAssoc(typeDef(argElement.asType()));
boolean embeddable = isEmbeddable(argElement);
return createPropertyTypeAssoc(embeddable, toMany, typeDef(argElement.asType()));
}
} else if (typeArguments.size() == 2) {
Element argElement = typeUtils.asElement(typeArguments.get(1));
if (isEntityOrEmbedded(argElement)) {
return createPropertyTypeAssoc(typeDef(argElement.asType()));
boolean embeddable = isEmbeddable(argElement);
return createPropertyTypeAssoc(embeddable, toMany, typeDef(argElement.asType()));
}
}
return null;
Expand Down Expand Up @@ -359,7 +369,7 @@ private static Object readTargetEntityFromAnnotation(AnnotationMirror mirror) {
/**
* Create the QAssoc PropertyType.
*/
private PropertyType createPropertyTypeAssoc(String fullName) {
private PropertyType createPropertyTypeAssoc(boolean embeddable, boolean toMany, String fullName) {
TypeElement typeElement = elementUtils.getTypeElement(fullName);
String type;
if (typeElement.getNestingKind().isNested()) {
Expand All @@ -368,8 +378,9 @@ private PropertyType createPropertyTypeAssoc(String fullName) {
type = typeElement.getQualifiedName().toString();
}

String suffix = toMany ? "Many" : embeddable ? "": "One";
String[] split = Split.split(type);
String propertyName = "Q" + split[1] + ".Assoc";
String propertyName = "Q" + split[1] + ".Assoc" + suffix;
String importName = split[0] + ".query.Q" + split[1];
return new PropertyTypeAssoc(propertyName, importName);
}
Expand Down
Loading

0 comments on commit 5095583

Please sign in to comment.