diff --git a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java index 1b67c6cc2..d8857acbe 100644 --- a/ebean-api/src/main/java/io/ebean/ExpressionFactory.java +++ b/ebean-api/src/main/java/io/ebean/ExpressionFactory.java @@ -36,6 +36,11 @@ import java.util.Map; */ public interface ExpressionFactory { + /** + * Return a new ExpressionList. + */ + ExpressionList expressionList(); + /** * Path exists - for the given path in a JSON document. */ 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 6b9900ba3..2cb77ece6 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 @@ -36,6 +36,11 @@ public class DefaultExpressionFactory implements SpiExpressionFactory { return "sql"; } + @Override + public ExpressionList expressionList() { + return new DefaultExpressionList<>(this); + } + @Override public Expression textMatch(String propertyName, String search, Match options) { return new TextMatchExpression(propertyName, search, options); 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 0830aa566..49e7a7144 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 @@ -56,6 +56,10 @@ public class DefaultExpressionList implements SpiExpressionList { this.parentExprList = parentExprList; } + protected DefaultExpressionList(ExpressionFactory expr) { + this(null, expr, null, new ArrayList<>()); + } + private DefaultExpressionList() { this(null, null, null, new ArrayList<>()); } @@ -112,10 +116,8 @@ public class DefaultExpressionList implements SpiExpressionList { *

* If this is the Top level "text" expressions then it detects if explicit or implicit Bool Should, Must etc is required * to wrap the expressions. - *

*

* If implicit Bool is required SHOULD is used. - *

*/ @Override public void writeDocQuery(DocQueryContext context) throws IOException { diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml index 8013ed74a..397317af1 100644 --- a/ebean-querybean/pom.xml +++ b/ebean-querybean/pom.xml @@ -107,7 +107,7 @@ true - io.ebean.tile:enhancement:13.17.1 + io.ebean.tile:enhancement:13.21.1-beta 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 78224d9c5..8a4360117 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -136,6 +136,13 @@ public abstract class TQRootBean { this.query = null; } + /** Construct for FilterMany */ + protected TQRootBean(ExpressionList filter) { + this.query = null; + this.whereStack = new ArrayStack<>(); + whereStack.push(filter); + } + /** * Return the fetch group. */ diff --git a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java index 658ccd8aa..aa3975a23 100644 --- a/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java +++ b/ebean-querybean/src/test/java/org/querytest/QCustomerTest.java @@ -4,10 +4,11 @@ import io.ebean.*; import io.ebean.annotation.Transactional; import io.ebean.test.LoggedSql; import io.ebean.types.Inet; -import org.example.domain.*; +import org.example.domain.Address; +import org.example.domain.Country; +import org.example.domain.Customer; import org.example.domain.otherpackage.PhoneNumber; import org.example.domain.otherpackage.ValidEmail; -import org.example.domain.query.QAnimal; import org.example.domain.query.QContact; import org.example.domain.query.QCustomer; import org.junit.jupiter.api.Disabled; @@ -26,7 +27,9 @@ import static io.ebean.StdOperators.*; import static org.assertj.core.api.Assertions.assertThat; import static org.example.domain.query.QAddress.Alias.country; import static org.example.domain.query.QAddress.Alias.line1; +import static org.example.domain.query.QContact.Alias.firstName; import static org.example.domain.query.QContact.Alias.lastName; +import static org.example.domain.query.QCustomer.Alias.*; import static org.example.domain.query.QCustomer.Alias.billingAddress; import static org.junit.jupiter.api.Assertions.assertNotNull; @@ -161,8 +164,8 @@ public class QCustomerTest { @Test void equalTo_byProperty() { Query query = new QCustomer() - .select(QCustomer.Alias.id) - .billingAddress.city.eq(QCustomer.Alias.shippingAddress.city) + .select(id) + .billingAddress.city.eq(shippingAddress.city) .query(); query.findList(); @@ -178,8 +181,8 @@ public class QCustomerTest { @Test void notEqual_byProperty() { Query query = new QCustomer() - .select(QCustomer.Alias.id) - .billingAddress.city.ne(QCustomer.Alias.shippingAddress.city) + .select(id) + .billingAddress.city.ne(shippingAddress.city) .query(); query.findList(); @@ -246,6 +249,37 @@ public class QCustomerTest { .findList(); } + static final FetchGroup FGCustomerContacts = QCustomer.forFetchGroup() + .select(name) + .contacts.fetch(firstName, lastName) + .buildFetchGroup(); + + @Test + void filterMany() { + + var q = new QCustomer() + .select(FGCustomerContacts) + .contacts.filterMany(contacts -> contacts + .firstName.startsWith("r") + .email.isNotNull()) + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).isEqualTo("select t0.id, t0.name, t1.id, t1.first_name, t1.last_name from be_customer t0 left join be_contact t1 on t1.customer_id = t0.id where t1.first_name like ? escape'|' and t1.email is not null order by t0.id"); + } + + @Test + void filterManySingle() { + + var q = new QCustomer() + .select(FGCustomerContacts) + .contacts.filterMany(c -> c.firstName.startsWith("r")) + .query(); + + q.findList(); + assertThat(q.getGeneratedSql()).isEqualTo("select t0.id, t0.name, t1.id, t1.first_name, t1.last_name from be_customer t0 left join be_contact t1 on t1.customer_id = t0.id where t1.first_name like ? escape'|' order by t0.id"); + } + @Test public void testIdIn() { @@ -277,10 +311,10 @@ public class QCustomerTest { @Test public void usingMaster() { - new QCustomer() - .registered.isNull() - .usingMaster() - .findList(); + new QCustomer() + .registered.isNull() + .usingMaster() + .findList(); } @Test @@ -555,7 +589,7 @@ public class QCustomerTest { assertContains(new QCustomer().registered.lt(new Date()).query(), " where t0.registered < ?"); assertContains(new QCustomer().registered.before(new Date()).query(), " where t0.registered < ?"); assertContains(new QCustomer().registered.lessThan(new Date()).query(), " where t0.registered < ?"); - assertContains(new QCustomer().whenCreated.lt(QCustomer.Alias.whenUpdated).query(), " where t0.when_created < t0.when_updated"); + assertContains(new QCustomer().whenCreated.lt(whenUpdated).query(), " where t0.when_created < t0.when_updated"); } @Test @@ -563,7 +597,7 @@ public class QCustomerTest { assertContains(new QCustomer().registered.le(new Date()).query(), " where t0.registered <= ?"); assertContains(new QCustomer().registered.lessOrEqualTo(new Date()).query(), " where t0.registered <= ?"); - assertContains(new QCustomer().whenCreated.le(QCustomer.Alias.whenUpdated).query(), " where t0.when_created <= t0.when_updated"); + assertContains(new QCustomer().whenCreated.le(whenUpdated).query(), " where t0.when_created <= t0.when_updated"); } @Test @@ -571,7 +605,7 @@ public class QCustomerTest { assertContains(new QCustomer().registered.after(new Date()).query(), " where t0.registered > ?"); assertContains(new QCustomer().registered.gt(new Date()).query(), " where t0.registered > ?"); - assertContains(new QCustomer().whenCreated.gt(QCustomer.Alias.whenUpdated).query(), " where t0.when_created > t0.when_updated"); + assertContains(new QCustomer().whenCreated.gt(whenUpdated).query(), " where t0.when_created > t0.when_updated"); assertContains(new QCustomer().registered.greaterThan(new Date()).query(), " where t0.registered > ?"); } @@ -581,7 +615,7 @@ public class QCustomerTest { assertContains(new QCustomer().registered.ge(new Date()).query(), " where t0.registered >= ?"); assertContains(new QCustomer().registered.greaterOrEqualTo(new Date()).query(), " where t0.registered >= ?"); - assertContains(new QCustomer().whenCreated.ge(QCustomer.Alias.whenUpdated).query(), " where t0.when_created >= t0.when_updated"); + assertContains(new QCustomer().whenCreated.ge(whenUpdated).query(), " where t0.when_created >= t0.when_updated"); } private void assertContains(Query query, String match) { @@ -598,8 +632,6 @@ public class QCustomerTest { .findList(); } - - @Test public void select_assocManyToOne() { @@ -788,16 +820,16 @@ public class QCustomerTest { .findList(); new QCustomer() - .add(in(QCustomer.Alias.name, List.of("foo", "bar"))) - .add(eq(QCustomer.Alias.currentInet, Inet.of("127.0.0.1"))) + .add(in(name, List.of("foo", "bar"))) + .add(eq(currentInet, Inet.of("127.0.0.1"))) .findList(); new QCustomer() //.add(gt(sum(QCustomer.Alias.version), 45)) - .add(eq(QCustomer.Alias.version, 45L)) - .add(eq(QCustomer.Alias.name, "junk")) - .add(eq(QCustomer.Alias.registered, new Date())) - .add(eq(QCustomer.Alias.whenUpdated, new Timestamp(System.currentTimeMillis()))) + .add(eq(version, 45L)) + .add(eq(name, "junk")) + .add(eq(registered, new Date())) + .add(eq(whenUpdated, new Timestamp(System.currentTimeMillis()))) .findList(); } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java index b456dbeb6..bf506d4e0 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/Constants.java @@ -21,9 +21,7 @@ interface Constants { String DBNAME = "io.ebean.annotation.DbName"; String TQROOTBEAN = "io.ebean.typequery.TQRootBean"; - String TQASSOC = "io.ebean.typequery.TQAssoc"; String TQASSOCBEAN = "io.ebean.typequery.TQAssocBean"; - String TQPROPERTY = "io.ebean.typequery.TQProperty"; String TYPEQUERYBEAN = "io.ebean.typequery.TypeQueryBean"; String DATABASE = "io.ebean.Database"; String DB = "io.ebean.DB"; @@ -37,4 +35,7 @@ interface Constants { String AVAJE_LANG_NULLABLE = "io.avaje.lang.Nullable"; String JAVA_COLLECTION = "java.util.Collection"; + String EXPRESSIONLIST = "io.ebean.ExpressionList"; + String EXPR = "io.ebean.Expr"; + String CONSUMER = "java.util.function.Consumer"; } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java index 416d09af9..631eb4d3f 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/ProcessingContext.java @@ -361,20 +361,8 @@ class ProcessingContext implements Constants { */ private PropertyType createPropertyTypeAssoc(String fullName) { String[] split = Split.split(fullName); - String propertyName = "QAssoc" + split[1]; - String packageName = packageAppend(split[0]); - return new PropertyTypeAssoc(propertyName, packageName); - } - - /** - * Prepend the package to the suffix taking null into account. - */ - private String packageAppend(String origPackage) { - if (origPackage == null) { - return "query.assoc"; - } else { - return origPackage + "." + "query.assoc"; - } + String propertyName = "Q" + split[1] + ".Assoc"; + return new PropertyTypeAssoc(propertyName); } /** diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java index e52f5a3e3..9579042a3 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/Processor.java @@ -114,7 +114,6 @@ public class Processor extends AbstractProcessor implements Constants { try { SimpleQueryBeanWriter beanWriter = new SimpleQueryBeanWriter((TypeElement) element, processingContext); beanWriter.writeRootBean(); - beanWriter.writeAssocBean(); } catch (Throwable e) { e.printStackTrace(); processingContext.logError(element, "Error generating query beans: " + e); diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyMeta.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyMeta.java index 6634f5b21..33ae06697 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyMeta.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyMeta.java @@ -31,14 +31,12 @@ class PropertyMeta { } void writeFieldDefn(Append writer, String shortName, boolean assoc) { - writer.append(" public "); writer.append(getTypeDefn(shortName, assoc)); writer.append(" ").append(name).append(";"); } void writeFieldAliasDefn(Append writer, String shortName) { - writer.append(" public static "); writer.append(getTypeDefn(shortName, false)); writer.append(" ").append(name).append(" = _alias.").append(name).append(";"); diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeAssoc.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeAssoc.java index c9b912886..6e6847c86 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeAssoc.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/PropertyTypeAssoc.java @@ -7,20 +7,13 @@ import java.util.Set; */ class PropertyTypeAssoc extends PropertyType { - /** - * The package name for this associated query bean. - */ - private final String assocPackage; - /** * Construct given the associated bean type name and package. * * @param qAssocTypeName the associated bean type name. - * @param assocPackage the associated bean package. */ - PropertyTypeAssoc(String qAssocTypeName, String assocPackage) { + PropertyTypeAssoc(String qAssocTypeName) { super(qAssocTypeName); - this.assocPackage = assocPackage; } /** @@ -28,7 +21,7 @@ class PropertyTypeAssoc extends PropertyType { */ @Override void addImports(Set allImports) { - allImports.add(assocPackage + "." + propertyType); + // do nothing } } diff --git a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java index b3d2d723d..8ad6f9783 100644 --- a/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java +++ b/querybean-generator/src/main/java/io/ebean/querybean/generator/SimpleQueryBeanWriter.java @@ -8,7 +8,6 @@ import javax.tools.JavaFileObject; import java.io.IOException; import java.io.Writer; import java.util.ArrayList; -import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -29,10 +28,8 @@ class SimpleQueryBeanWriter { private final String beanFullName; private final boolean isEntity; private final boolean embeddable; - private boolean writingAssocBean; private String destPackage; - private String origDestPackage; private String shortName; private final String shortInnerName; private final String origShortName; @@ -80,16 +77,24 @@ class SimpleQueryBeanWriter { private void gatherPropertyDetails() { importTypes.add(Constants.GENERATED); importTypes.add(beanFullName); + importTypes.add(Constants.TQASSOCBEAN); importTypes.add(Constants.TQROOTBEAN); importTypes.add(Constants.TYPEQUERYBEAN); importTypes.add(Constants.DATABASE); importTypes.add(Constants.FETCHGROUP); importTypes.add(Constants.QUERY); importTypes.add(Constants.TRANSACTION); + importTypes.add(Constants.CONSUMER); + importTypes.add(Constants.EXPR); + importTypes.add(Constants.EXPRESSIONLIST); if (implementsInterface != null) { implementsInterfaceFullName = implementsInterface.getQualifiedName().toString(); boolean nested = implementsInterface.getNestingKind().isNested(); implementsInterfaceShortName = Util.shortName(nested, implementsInterfaceFullName); + + importTypes.add(Constants.AVAJE_LANG_NULLABLE); + importTypes.add(Constants.JAVA_COLLECTION); + importTypes.add(implementsInterfaceFullName); } if (dbName != null) { importTypes.add(Constants.DB); @@ -101,7 +106,6 @@ class SimpleQueryBeanWriter { * Recursively add properties from the inheritance hierarchy. *

* Includes properties from mapped super classes and usual inheritance. - *

*/ private void addClassProperties() { for (VariableElement field : processingContext.allFields(element)) { @@ -131,87 +135,17 @@ class SimpleQueryBeanWriter { writeFields(); writeConstructors(); writeStaticAliasClass(); + writeAssocClass(); writeClassEnd(); writer.close(); } } - /** - * Write the type query assoc bean. - */ - void writeAssocBean() throws IOException { - writingAssocBean = true; - origDestPackage = destPackage; - destPackage = destPackage + ".assoc"; - shortName = "Assoc" + shortName; - - prepareAssocBeanImports(); - - writer = new Append(createFileWriter()); - - writePackage(); - writeImports(); - writeClass(); - writeFields(); - writeConstructors(); - writeClassEnd(); - - writer.close(); - } - - /** - * Prepare the imports for writing assoc bean. - */ - private void prepareAssocBeanImports() { - importTypes.remove(Constants.DB); - importTypes.remove(Constants.TQROOTBEAN); - importTypes.remove(Constants.DATABASE); - importTypes.remove(Constants.FETCHGROUP); - importTypes.remove(Constants.QUERY); - if (embeddable) { - importTypes.add(Constants.TQASSOC); - } else { - importTypes.add(Constants.TQASSOCBEAN); - } - if (isEntity()) { - importTypes.add(Constants.TQPROPERTY); - importTypes.add(origDestPackage + ".Q" + origShortName); - if (implementsInterface != null) { - importTypes.add(Constants.AVAJE_LANG_NULLABLE); - importTypes.add(Constants.JAVA_COLLECTION); - importTypes.add(implementsInterfaceFullName); - } - } - - // remove imports for the same package - Iterator importsIterator = importTypes.iterator(); - String checkImportStart = destPackage + ".QAssoc"; - while (importsIterator.hasNext()) { - String importType = importsIterator.next(); - if (importType.startsWith(checkImportStart)) { - importsIterator.remove(); - } - } - } - /** * Write constructors. */ private void writeConstructors() { - if (writingAssocBean) { - writeAssocBeanFetch(); - writeAssocBeanConstructor(); - } else { - writeRootBeanConstructor(); - } - } - - /** - * Write the constructors for 'root' type query bean. - */ - private void writeRootBeanConstructor() { - writer.eol(); writer.append(" /**").eol(); writer.append(" * Return a query bean used to build a FetchGroup.").eol(); @@ -240,9 +174,7 @@ class SimpleQueryBeanWriter { writer.eol(); String name = (dbName == null) ? "default" : dbName; - writer.append(" /**").eol(); - writer.append(" * Construct using the %s Database.", name).eol(); - writer.append(" */").eol(); + writer.append(" /** Construct using the %s Database */", name).eol(); writer.append(" public Q%s() {", shortName).eol(); if (dbName == null) { writer.append(" super(%s.class);", shortName).eol(); @@ -252,9 +184,7 @@ class SimpleQueryBeanWriter { writer.append(" }").eol(); writer.eol(); - writer.append(" /**").eol(); - writer.append(" * Construct with a given transaction.", name).eol(); - writer.append(" */").eol(); + writer.append(" /** Construct with a given transaction */").eol(); writer.append(" public Q%s(Transaction transaction) {", shortName).eol(); if (dbName == null) { writer.append(" super(%s.class, transaction);", shortName).eol(); @@ -264,29 +194,122 @@ class SimpleQueryBeanWriter { writer.append(" }").eol(); writer.eol(); - writer.append(" /**").eol(); - writer.append(" * Construct with a given Database.").eol(); - writer.append(" */").eol(); + writer.append(" /** Construct with a given Database */").eol(); writer.append(" public Q%s(Database database) {", shortName).eol(); writer.append(" super(%s.class, database);", shortName).eol(); writer.append(" }").eol(); writer.eol(); writer.eol(); - writer.append(" /**").eol(); - writer.append(" * Construct for Alias.").eol(); - writer.append(" */").eol(); + writer.append(" /** Private constructor for Alias */").eol(); writer.append(" private Q%s(boolean dummy) {", shortName).eol(); writer.append(" super(dummy);").eol(); writer.append(" }").eol(); writer.eol(); - writer.append(" /**").eol(); - writer.append(" * Private constructor for FetchGroup building.").eol(); - writer.append(" */").eol(); + writer.append(" /** Private constructor for FetchGroup building */").eol(); writer.append(" private Q%s(Query<%s> fetchGroupQuery) {", shortName, shortName).eol(); writer.append(" super(fetchGroupQuery);").eol(); writer.append(" }").eol(); + + writer.eol(); + writer.append(" /** Private constructor for filterMany */").eol(); + writer.append(" private Q%s(ExpressionList<%s> filter) {", shortName, shortName).eol(); + writer.append(" super(filter);").eol(); + writer.append(" }").eol(); + } + + /** + * Write all the fields. + */ + private void writeFields() { + for (PropertyMeta property : properties) { + property.writeFieldDefn(writer, shortName, false); + writer.eol(); + } + writer.eol(); + } + + private void writeClass() { + writer.append("/**").eol(); + writer.append(" * Query bean for %s.", shortName).eol(); + writer.append(" * ").eol(); + writer.append(" * THIS IS A GENERATED OBJECT, DO NOT MODIFY THIS CLASS.").eol(); + writer.append(" */").eol(); + writer.append(Constants.AT_GENERATED).eol(); + writer.append(Constants.AT_TYPEQUERYBEAN).eol(); + writer.append("public final class Q%s extends TQRootBean<%1$s,Q%1$s> {", shortName).eol(); + writer.eol(); + } + + private void writeAlias() { + writer.append(" private static final Q%s _alias = new Q%1$s(true);", shortName).eol().eol(); + + writer.append(" /**").eol(); + writer.append(" * Return the shared 'Alias' instance used to provide properties to ").eol(); + writer.append(" * select() and fetch() ").eol(); + writer.append(" */").eol(); + writer.append(" public static Q%s alias() {", shortName).eol(); + writer.append(" return _alias;").eol(); + writer.append(" }").eol(); + writer.eol(); + } + + private void writeStaticAliasClass() { + writer.eol(); + writer.append(" /**").eol(); + writer.append(" * Provides static properties to use in select() and fetch() ").eol(); + writer.append(" * clauses of a query. Typically referenced via static imports. ").eol(); + writer.append(" */").eol(); + writer.append(" ").append(Constants.AT_GENERATED).eol(); + writer.append(" public static final class Alias {").eol(); + for (PropertyMeta property : properties) { + property.writeFieldAliasDefn(writer, shortName); + writer.eol(); + } + writer.append(" }").eol(); + } + + private void writeAssocClass() { + writer.eol(); + writer.append(" /** Association query bean */").eol(); + writer.append(" ").append(Constants.AT_GENERATED).eol(); + writer.append(" ").append(Constants.AT_TYPEQUERYBEAN).eol(); + if (embeddable) { + writer.append(" public static final class Assoc extends TQAssoc<%s,R> {", shortName, shortInnerName).eol(); + } else { + writer.append(" public static final class Assoc extends TQAssocBean<%s,R,Q%s> {", shortName, shortInnerName, origShortName).eol(); + } + for (PropertyMeta property : properties) { + writer.append(" "); + property.writeFieldDefn(writer, shortName, true); + writer.eol(); + } + writer.eol(); + writeAssocBeanConstructor(); + writeAssocFilterMany(); + writeAssocBeanFetch(); + writer.append(" }").eol(); + } + + private void writeAssocFilterMany() { + writer.append(" public final R filterMany(Consumer apply) {", shortName).eol(); + writer.append(" final ExpressionList list = Expr.factory().expressionList();", shortName).eol(); + writer.append(" final var qb = new Q%s(list);", shortName).eol(); + writer.append(" apply.accept(qb);").eol(); + writer.append(" expr().filterMany(_name).addAll(list);").eol(); + writer.append(" return _root;").eol(); + writer.append(" }").eol(); + } + + private void writeAssocBeanConstructor() { + writer.append(" public Assoc(String name, R root) {").eol(); + writer.append(" super(name, root);").eol(); + writer.append(" }").eol().eol(); + + writer.append(" public Assoc(String name, R root, String prefix) {").eol(); + writer.append(" super(name, root, prefix);").eol(); + writer.append(" }").eol(); } private void writeAssocBeanFetch() { @@ -320,101 +343,6 @@ class SimpleQueryBeanWriter { writer.eol(); } - private void writeAssocBeanFetch(String fetchType, String comment) { - writer.append(" /**").eol(); - writer.append(" * ").append(comment).eol(); - writer.append(" */").eol(); - writer.append(" @SafeVarargs @SuppressWarnings(\"varargs\")").eol(); - writer.append(" public final R fetch%s(TQProperty... properties) {", fetchType, origShortName).eol(); - writer.append(" return fetch%sProperties(properties);", fetchType).eol(); - writer.append(" }").eol(); - writer.eol(); - } - - /** - * Write constructor for 'assoc' type query bean. - */ - private void writeAssocBeanConstructor() { - writer.append(" public Q%s(String name, R root) {", shortName).eol(); - writer.append(" super(name, root);").eol(); - writer.append(" }").eol().eol(); - - writer.append(" public Q%s(String name, R root, String prefix) {", shortName).eol(); - writer.append(" super(name, root, prefix);").eol(); - writer.append(" }").eol(); - } - - /** - * Write all the fields. - */ - private void writeFields() { - for (PropertyMeta property : properties) { - property.writeFieldDefn(writer, shortName, writingAssocBean); - writer.eol(); - } - writer.eol(); - } - - /** - * Write the class definition. - */ - private void writeClass() { - if (writingAssocBean) { - writer.append("/**").eol(); - writer.append(" * Association query bean for %s.", shortName).eol(); - writer.append(" * ").eol(); - writer.append(" * THIS IS A GENERATED OBJECT, DO NOT MODIFY THIS CLASS.").eol(); - writer.append(" */").eol(); - writer.append(Constants.AT_GENERATED).eol(); - writer.append(Constants.AT_TYPEQUERYBEAN).eol(); - if (embeddable) { - writer.append("public final class Q%s extends TQAssoc<%s,R> {", shortName, shortInnerName).eol(); - } else { - writer.append("public final class Q%s extends TQAssocBean<%s,R,Q%s> {", shortName, shortInnerName, origShortName).eol(); - } - } else { - writer.append("/**").eol(); - writer.append(" * Query bean for %s.", shortName).eol(); - writer.append(" * ").eol(); - writer.append(" * THIS IS A GENERATED OBJECT, DO NOT MODIFY THIS CLASS.").eol(); - writer.append(" */").eol(); - writer.append(Constants.AT_GENERATED).eol(); - writer.append(Constants.AT_TYPEQUERYBEAN).eol(); - writer.append("public final class Q%s extends TQRootBean<%1$s,Q%1$s> {", shortName).eol(); - } - - writer.eol(); - } - - private void writeAlias() { - if (!writingAssocBean) { - writer.append(" private static final Q%s _alias = new Q%1$s(true);", shortName).eol().eol(); - - writer.append(" /**").eol(); - writer.append(" * Return the shared 'Alias' instance used to provide properties to ").eol(); - writer.append(" * select() and fetch() ").eol(); - writer.append(" */").eol(); - writer.append(" public static Q%s alias() {", shortName).eol(); - writer.append(" return _alias;").eol(); - writer.append(" }").eol(); - writer.eol(); - } - } - - private void writeStaticAliasClass() { - writer.eol(); - writer.append(" /**").eol(); - writer.append(" * Provides static properties to use in select() and fetch() ").eol(); - writer.append(" * clauses of a query. Typically referenced via static imports. ").eol(); - writer.append(" */").eol(); - writer.append(" public static class Alias {").eol(); - for (PropertyMeta property : properties) { - property.writeFieldAliasDefn(writer, shortName); - writer.eol(); - } - writer.append(" }").eol(); - } - private void writeClassEnd() { writer.append("}").eol(); } @@ -433,7 +361,6 @@ class SimpleQueryBeanWriter { writer.append("package %s;", destPackage).eol().eol(); } - private Writer createFileWriter() throws IOException { JavaFileObject jfo = processingContext.createWriter(destPackage + "." + "Q" + shortName, element); return jfo.openWriter();