diff --git a/ebean-querybean/README.md b/ebean-querybean/README.md
new file mode 100644
index 000000000..772855af0
--- /dev/null
+++ b/ebean-querybean/README.md
@@ -0,0 +1,4 @@
+# ebean-querybean
+Type safe query extension for Ebean ORM
+
+Refer to the documentation at https://ebean.io/docs/query/query-beans
diff --git a/ebean-querybean/pom.xml b/ebean-querybean/pom.xml
new file mode 100644
index 000000000..702fa3f55
--- /dev/null
+++ b/ebean-querybean/pom.xml
@@ -0,0 +1,92 @@
+
+
+ * Used by the agent to detect already enhanced type query beans to skip enhancement processing. + *
+ */ +@Target({ ElementType.TYPE }) +@Retention(RetentionPolicy.RUNTIME) +public @interface AlreadyEnhancedMarker { + +} diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PArray.java b/ebean-querybean/src/main/java/io/ebean/typequery/PArray.java new file mode 100644 index 000000000..61d077636 --- /dev/null +++ b/ebean-querybean/src/main/java/io/ebean/typequery/PArray.java @@ -0,0 +1,98 @@ +package io.ebean.typequery; + +/** + * Array property with E as the element type. + * + * @param+ *
{@code
+ *
+ * new QContact()
+ * .phoneNumbers.contains("4321")
+ * .findList();
+ *
+ * }
+ *
+ * @param values The values that should be contained in the array
+ */
+ @SafeVarargs
+ public final R contains(E... values) {
+ expr().arrayContains(_name, (Object[]) values);
+ return _root;
+ }
+
+ /**
+ * ARRAY does not contain the values.
+ * + *
{@code
+ *
+ * new QContact()
+ * .phoneNumbers.notContains("4321")
+ * .findList();
+ *
+ * }
+ *
+ * @param values The values that should not be contained in the array
+ */
+ @SafeVarargs
+ public final R notContains(E... values) {
+ expr().arrayNotContains(_name, (Object[]) values);
+ return _root;
+ }
+
+ /**
+ * ARRAY is empty.
+ * + *
{@code
+ *
+ * new QContact()
+ * .phoneNumbers.isEmpty()
+ * .findList();
+ *
+ * }
+ */
+ public R isEmpty() {
+ expr().arrayIsEmpty(_name);
+ return _root;
+ }
+
+ /**
+ * ARRAY is not empty.
+ * + *
{@code
+ *
+ * new QContact()
+ * .phoneNumbers.isNotEmpty()
+ * .findList();
+ *
+ * }
+ */
+ public R isNotEmpty() {
+ expr().arrayIsNotEmpty(_name);
+ return _root;
+ }
+
+}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java
new file mode 100644
index 000000000..c9488f63c
--- /dev/null
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PBaseCompareable.java
@@ -0,0 +1,237 @@
+package io.ebean.typequery;
+
+
+/**
+ * Base property for all comparable types.
+ *
+ * @param + * This is generally preferable over Between for date and datetime types + * as SQL Between is inclusive on the upper bound (<=) and generally we + * need the upper bound to be exclusive (<). + *
+ * + * @param lower the lower bind value (>=) + * @param upper the upper bind value (<) + * @return the root query bean instance + */ + public final R inRange(T lower, T upper) { + expr().inRange(_name, lower, upper); + return _root; + } + + /** + * Value in Range between 2 properties. + * + *{@code
+ *
+ * .startDate.inRangeWith(endDate, now)
+ *
+ * // which equates to
+ * startDate <= now and (endDate > now or endDate is null)
+ *
+ * }
+ *
+ *
+ * This is a convenience expression combining a number of simple expressions.
+ * The most common use of this could be called "effective dating" where 2 date or
+ * timestamp columns represent the date range in which
+ */
+ public final R inRangeWith(TQProperty
+ * This means the query will automatically execute against the document store (ElasticSearch).
+ *
+ * That is, only add the IN predicate if the values are not null or empty.
+ *
+ * Without this we typically need to code an
+ * This is a placeholder in the sense that currently it has no supported expressions.
+ *
+ * Type that is JSON content mapped to database types such as Postgres JSON/JSONB and otherwise Varchar,Clob and Blob.
+ *
+ * The expressions on this type are valid of Postgres and Oracle.
+ *
+ * The path can reference a nested property in the JSON document using dot notation -
+ * for example "documentMeta.score" where "score" is an embedded attribute of "documentMeta"
+ *
+ * This means the query will automatically execute against the document store (ElasticSearch).
+ *
+ * The expressions can use any valid Ebean expression and contain
+ * placeholders for bind values using
+ * This effectively adds a not exists sub-query on the collection property.
+ *
+ * This expression only works on OneToMany and ManyToMany properties.
+ *
+ * This effectively adds an exists sub-query on the collection property.
+ *
+ * This expression only works on OneToMany and ManyToMany properties.
+ *
+ * With code generation for each entity bean type a query bean is created that extends this.
+ *
+ * Provides common features for all root query beans
+ *
+ *
+ * These 'query beans' like QCustomer are generated using the
+ *
+ *
+ *
+ * Generally it is not expected that you will need to do this but typically use
+ * the find methods available on this 'root query bean' instance like findList().
+ *
+ * You use {@link #fetch(String, String)} to specify specific properties to fetch
+ * on other non-root level paths of the object graph.
+ *
+ *
+ * This is an alternative to using select() and fetch() providing a nice clean separation
+ * between what a query should load and the query predicates.
+ *
+ * The same as {@link #fetch(String, String)} with the fetchProperties as "*".
+ *
+ * When you specify a join this means that property (associated bean(s)) will
+ * be fetched and populated. If you specify "*" then all the properties of the
+ * associated bean will be fetched and populated. You can specify a comma
+ * delimited list of the properties of that associated bean which means that
+ * only those properties are fetched and populated resulting in a
+ * "Partial Object" - a bean that only has some of its properties populated.
+ *
+ *
+ * If columns is null or "*" then all columns/properties for that path are fetched.
+ *
+ *
+ *
+ * This is typically used when the PathProperties is applied to both the query and the JSON output.
+ *
+ * To perform this query the DB must have underlying history tables.
+ *
+ * If you do not call this method on a query the "Implicit AutoTune mode" is
+ * used to determine if AutoTune should be used for a given query.
+ *
+ * AutoTune can add additional fetch paths to the query and specify which
+ * properties are included for each path. If you have explicitly defined some
+ * fetch paths AutoTune will not remove them.
+ *
+ * Gives the JDBC driver a hint as to the number of rows that should be
+ * fetched from the database when more rows are needed for ResultSet.
+ *
+ * For example, when executing a query against ElasticSearch with daily indexes we can
+ * explicitly specify the indexes to search against.
+ *
+ * If the indexName is specified with ${daily} e.g. "logstash-${daily}" ... then we can use
+ * $today and $last-x as the search docIndexName like the examples below.
+ *
+ * Typically this is used when a table has partitioning and we wish to specify a specific
+ * partition/table to query against.
+ *
+ * This is typically a Postgres and Oracle only option at this stage.
+ *
+ * This is typically a Postgres and Oracle only option at this stage.
+ *
+ * We effectively use the underlying ORM query to build the SQL and then execute
+ * and map it into DTO beans.
+ */
+ public
+ * You can use this to have further control over the query. For example adding
+ * fetch joins.
+ *
+ *
+ *
+ * This label can be used to help identify query performance metrics but we can also use
+ * profile location enhancement on Finders so for some that would be a better option.
+ *
+ * This is typically set automatically via enhancement when profile location enhancement
+ * is turned on. It is generally not set by application code.
+ *
+ * When lazy loading is invoked on beans loaded by this query then this sets the
+ * batch size used to load those beans.
+ *
+ * @param lazyLoadBatchSize the number of beans to lazy load in a single batch
+ */
+ public R setLazyLoadBatchSize(int lazyLoadBatchSize) {
+ query.setLazyLoadBatchSize(lazyLoadBatchSize);
+ return root;
+ }
+
+ /**
+ * When set to true all the beans from this query are loaded into the bean
+ * cache.
+ */
+ public R setLoadBeanCache(boolean loadBeanCache) {
+ query.setLoadBeanCache(loadBeanCache);
+ return root;
+ }
+
+ /**
+ * Set the property to use as keys for a map.
+ *
+ * If no property is set then the id property is used.
+ *
+ *
+ * When this is not set the 'default' configured on {@link io.ebean.config.ServerConfig#setPersistenceContextScope(PersistenceContextScope)}
+ * is used - this value defaults to {@link io.ebean.PersistenceContextScope#TRANSACTION}.
+ *
+ * Note that the same persistence Context is used for subsequent lazy loading and query join queries.
+ *
+ * Note that #findEach uses a 'per object graph' PersistenceContext so this scope is ignored for
+ * queries executed as #findIterate, #findEach, #findEachWhile.
+ *
+ * @param scope The scope to use for this query and subsequent lazy loading.
+ */
+ public R setPersistenceContextScope(PersistenceContextScope scope) {
+ query.setPersistenceContextScope(scope);
+ return root;
+ }
+
+ /**
+ * Set RawSql to use for this query.
+ */
+ public R setRawSql(RawSql rawSql) {
+ query.setRawSql(rawSql);
+ return root;
+ }
+
+ /**
+ * When set to true when you want the returned beans to be read only.
+ */
+ public R setReadOnly(boolean readOnly) {
+ query.setReadOnly(readOnly);
+ return root;
+ }
+
+ /**
+ * Set this to true to use the bean cache.
+ *
+ * If the query result is in cache then by default this same instance is
+ * returned. In this sense it should be treated as a read only object graph.
+ *
+ * By default "find by id" and "find by natural key" will use the bean cache
+ * when bean caching is enabled. Setting this to false means that the query
+ * will not use the bean cache and instead hit the database.
+ *
+ * By default findList() with natural keys will not use the bean cache. In that
+ * case we need to explicitly use the bean cache.
+ *
+ * When setting this you may also consider disabling lazy loading.
+ *
+ * That is, once the object graph is returned further lazy loading is disabled.
+ *
+ * This is intended to be used when the query is not a user initiated query and instead
+ * part of the internal processing in an application to load a cache or document store etc.
+ * In these cases we don't want the query to be part of read auditing.
+ *
+ * This will typically result in a call to setQueryTimeout() on a
+ * preparedStatement. If the timeout occurs an exception will be thrown - this
+ * will be a SQLException wrapped up in a PersistenceException.
+ *
+ * Validate the query checking the where and orderBy expression paths to confirm if
+ * they represent valid properties or paths for the given bean type.
+ *
+ * When properties in the clause are fully qualified as table-column names
+ * then they are not translated. logical property name names (not fully
+ * qualified) will still be translated to their physical name.
+ *
+ *
+ * The raw expression should contain the same number of ? as there are
+ * parameters.
+ *
+ * When properties in the clause are fully qualified as table-column names
+ * then they are not translated. logical property name names (not fully
+ * qualified) will still be translated to their physical name.
+ *
+ * This is a pure convenience expression to make it nicer to deal with the pattern where we use
+ * raw() expression with a subquery and only want to add the subquery predicate when the collection
+ * of values is not empty.
+ *
+ * Note that we need to cast the Postgres array for UUID types like:
+ *
+ * The raw expression should contain a single ? at the location of the
+ * parameter.
+ *
+ * When properties in the clause are fully qualified as table-column names
+ * then they are not translated. logical property name names (not fully
+ * qualified) will still be translated to their physical name.
+ *
+ *
+ *
+ *
+ * This follows SQL syntax using commas between each property with the
+ * optional asc and desc keywords representing ascending and descending order
+ * respectively.
+ */
+ public R orderBy(String orderByClause) {
+ query.orderBy(orderByClause);
+ return root;
+ }
+
+ /**
+ * Set the full raw order by clause replacing the existing order by clause if there is one.
+ *
+ * This follows SQL syntax using commas between each property with the
+ * optional asc and desc keywords representing ascending and descending order
+ * respectively.
+ */
+ public R order(String orderByClause) {
+ query.order(orderByClause);
+ return root;
+ }
+
+ /**
+ * Begin a list of expressions added by 'OR'.
+ *
+ * Use endOr() or endJunction() to stop added to OR and 'pop' to the parent expression list.
+ *
+ *
+ * This example uses an 'OR' expression list with an inner 'AND' expression list.
+ *
+ * Use endAnd() or endJunction() to stop added to AND and 'pop' to the parent expression list.
+ *
+ * Note that typically the AND expression is only used inside an outer 'OR' expression.
+ * This is because the top level expression list defaults to an 'AND' expression list.
+ *
+ * This example uses an 'OR' expression list with an inner 'AND' expression list.
+ *
+ * Use endNot() or endJunction() to stop added to NOT and 'pop' to the parent expression list.
+ *
+ * This automatically makes this query a document store query.
+ *
+ * Use endJunction() to stop added to MUST and 'pop' to the parent expression list.
+ *
+ * This automatically makes this query a document store query.
+ *
+ * Use endJunction() to stop added to MUST NOT and 'pop' to the parent expression list.
+ *
+ * This automatically makes this query a document store query.
+ *
+ * Use endJunction() to stop added to SHOULD and 'pop' to the parent expression list.
+ *
+ * For queries against the normal database (not the doc store) this has no effect.
+ *
+ * This is intended for use with Document Store / ElasticSearch where expressions can be put into either
+ * the "query" section or the "filter" section of the query. Full text expressions like MATCH are in the
+ * "query" section but many expression can be in either - expressions after the where() are put into the
+ * "filter" section which means that they don't add to the relevance and are also cache-able.
+ *
+ * This automatically makes the query a document store query.
+ *
+ * For ElasticSearch expressions added to 'text' go into the ElasticSearch 'query context'
+ * and expressions added to 'where' go into the ElasticSearch 'filter context'.
+ *
+ * This automatically makes the query a document store query.
+ *
+ * This automatically makes the query a document store query.
+ *
+ * This automatically makes the query a document store query.
+ *
+ * This automatically makes the query a document store query.
+ *
+ * This automatically makes the query a document store query.
+ *
+ * The query is executed using max rows of 1 and will only select the id property.
+ * This method is really just a convenient way to optimise a query to perform a
+ * 'does a row exist in the db' check.
+ *
+ * If more than 1 row is found for this query then a PersistenceException is
+ * thrown.
+ *
+ * This is useful when your predicates dictate that your query should only
+ * return 0 or 1 results.
+ *
+ *
+ *
+ * It is also useful with finding objects by their id when you want to specify
+ * further join information to optimise the query.
+ *
+ *
+ * This query will execute against the EbeanServer that was used to create it.
+ *
+ *
+ * Note that this can support very large queries iterating
+ * any number of results. To do so internally it can use
+ * multiple persistence contexts.
+ *
+ * This query will execute against the EbeanServer that was used to create it.
+ *
+ *
+ * This query will execute against the EbeanServer that was used to create it.
+ *
+ * This query will execute against the EbeanServer that was used to create it.
+ *
+ * You can use setMapKey() or asMapKey() to specify the property to be used as keys
+ * on the map. If one is not specified then the id property is used.
+ *
+ *
+ * Note that findIterate (and findEach and findEachWhile) uses a "per graph"
+ * persistence context scope and adjusts jdbc fetch buffer size for large
+ * queries. As such it is better to use findList for small queries.
+ *
+ * Remember that with {@link QueryIterator} you must call {@link QueryIterator#close()}
+ * when you have finished iterating the results (typically in a finally block).
+ *
+ * findEach() and findEachWhile() are preferred to findIterate() as they ensure
+ * the jdbc statement and resultSet are closed at the end of the iteration.
+ *
+ * This query will execute against the EbeanServer that was used to create it.
+ *
+ *
+ *
+ * This method is appropriate to process very large query results as the
+ * beans are consumed one at a time and do not need to be held in memory
+ * (unlike #findList #findSet etc)
+ *
+ * Note that internally Ebean can inform the JDBC driver that it is expecting larger
+ * resultSet and specifically for MySQL this hint is required to stop it's JDBC driver
+ * from buffering the entire resultSet. As such, for smaller resultSets findList() is
+ * generally preferable.
+ *
+ * Compared with #findEachWhile this will always process all the beans where as
+ * #findEachWhile provides a way to stop processing the query result early before
+ * all the beans have been read.
+ *
+ * This method is functionally equivalent to findIterate() but instead of using an
+ * iterator uses the QueryEachConsumer (SAM) interface which is better suited to use
+ * with Java8 closures.
+ *
+ *
+ * This method is functionally equivalent to findIterate() but instead of using an
+ * iterator uses the QueryEachWhileConsumer (SAM) interface which is better suited to use
+ * with Java8 closures.
+ *
+ *
+ *
+ * Generally this query is expected to be a find by id or unique predicates query.
+ * It will execute the query against the history returning the versions of the bean.
+ *
+ * Generally this query is expected to be a find by id or unique predicates query.
+ * It will execute the query against the history returning the versions of the bean.
+ *
+ * This is the number of 'top level' or 'root level' entities.
+ *
+ * This returns a Future object which can be used to cancel, check the
+ * execution status (isDone etc) and get the value (with or without a
+ * timeout).
+ *
+ * This returns a Future object which can be used to cancel, check the
+ * execution status (isDone etc) and get the value (with or without a
+ * timeout).
+ *
+ * This query will execute in it's own PersistenceContext and using its own transaction.
+ * What that means is that it will not share any bean instances with other queries.
+ *
+ * The benefit of using this over findList() is that it provides functionality to get the
+ * total row count etc.
+ *
+ * If maxRows is not set on the query prior to calling findPagedList() then a
+ * PersistenceException is thrown.
+ *
+ *
+ * Note that if the query includes joins then the generated delete statement may not be
+ * optimal depending on the database platform.
+ *
+ * This is only available after the query has been executed and provided only
+ * for informational purposes.
+ *
+ * Note that after this we no longer have the query bean so typically we use this right
+ * at the end of the query.
+ *
+ * These are typically generated beans used to build queries using type safe query criteria.
+ *
+ * 'Query beans' like QCustomer are generated using the
+ * Extending Model to enable the 'active record' style.
+ *
+ *
+ * whenCreated and whenUpdated are generally useful for maintaining external search services (like
+ * elasticsearch) and audit.
+ */
+@MappedSuperclass
+public abstract class BaseModel extends Model {
+
+ @Id
+ Long id;
+
+ @Version
+ Long version;
+
+ @CreatedTimestamp
+ Timestamp whenCreated;
+
+ @UpdatedTimestamp
+ Timestamp whenUpdated;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getVersion() {
+ return version;
+ }
+
+ public void setVersion(Long version) {
+ this.version = version;
+ }
+
+ public Timestamp getWhenCreated() {
+ return whenCreated;
+ }
+
+ public void setWhenCreated(Timestamp whenCreated) {
+ this.whenCreated = whenCreated;
+ }
+
+ public Timestamp getWhenUpdated() {
+ return whenUpdated;
+ }
+
+ public void setWhenUpdated(Timestamp whenUpdated) {
+ this.whenUpdated = whenUpdated;
+ }
+
+}
diff --git a/ebean-querybean/src/test/java/org/example/domain/Contact.java b/ebean-querybean/src/test/java/org/example/domain/Contact.java
new file mode 100644
index 000000000..e47f66248
--- /dev/null
+++ b/ebean-querybean/src/test/java/org/example/domain/Contact.java
@@ -0,0 +1,110 @@
+package org.example.domain;
+
+import io.ebean.annotation.DbArray;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.ManyToOne;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Contact entity bean.
+ */
+@Entity
+@Table(name="be_contact")
+public class Contact extends BaseModel {
+
+ @DbArray
+ Listeq but uses the strong type as argument rather than String.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R equalToType(T value) {
+ expr().eq(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is not equal to. The same as ne but uses the strong type as argument rather than String.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R notEqualToType(T value) {
+ expr().ne(_name, value);
+ return _root;
+ }
+
+ // common string / expressions ------------
+
+ /**
+ * Case insensitive is equal to.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R ieq(String value) {
+ expr().ieq(_name, value);
+ return _root;
+ }
+
+ /**
+ * Case insensitive is equal to.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R iequalTo(String value) {
+ expr().ieq(_name, value);
+ return _root;
+ }
+
+ /**
+ * Like - include '%' and '_' placeholders as necessary.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R like(String value) {
+ expr().like(_name, value);
+ return _root;
+ }
+
+ /**
+ * Starts with - uses a like with '%' wildcard added to the end.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R startsWith(String value) {
+ expr().startsWith(_name, value);
+ return _root;
+ }
+
+ /**
+ * Ends with - uses a like with '%' wildcard added to the beginning.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R endsWith(String value) {
+ expr().endsWith(_name, value);
+ return _root;
+ }
+
+ /**
+ * Contains - uses a like with '%' wildcard added to the beginning and end.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R contains(String value) {
+ expr().contains(_name, value);
+ return _root;
+ }
+
+ /**
+ * Case insensitive like.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R ilike(String value) {
+ expr().ilike(_name, value);
+ return _root;
+ }
+
+ /**
+ * Case insensitive starts with.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R istartsWith(String value) {
+ expr().istartsWith(_name, value);
+ return _root;
+ }
+
+ /**
+ * Case insensitive ends with.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R iendsWith(String value) {
+ expr().iendsWith(_name, value);
+ return _root;
+ }
+
+ /**
+ * Case insensitive contains.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public R icontains(String value) {
+ expr().icontains(_name, value);
+ return _root;
+ }
+
+ /**
+ * Add a full text "Match" expression.
+ * findMap query.
+ *
+ * {@code
+ *
+ * Map
+ *
+ * @return the root query bean instance
+ */
+ public final R asMapKey() {
+ expr().setMapKey(_name);
+ return _root;
+ }
+
+ /**
+ * Is equal to or Null.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R equalToOrNull(T value) {
+ expr().eqOrNull(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is equal to.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R equalTo(T value) {
+ expr().eq(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is equal to.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R eq(T value) {
+ expr().eq(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is equal to or Null.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R eqOrNull(T value) {
+ expr().eqOrNull(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is not equal to.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R notEqualTo(T value) {
+ expr().ne(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is not equal to.
+ *
+ * @param value the equal to bind value
+ * @return the root query bean instance
+ */
+ public final R ne(T value) {
+ expr().ne(_name, value);
+ return _root;
+ }
+
+ /**
+ * Is in a list of values.
+ *
+ * @param values the list of values for the predicate
+ * @return the root query bean instance
+ */
+ @SafeVarargs
+ public final R in(T... values) {
+ expr().in(_name, (Object[]) values);
+ return _root;
+ }
+
+ /**
+ * In where null or empty values means that no predicate is added to the query.
+ * if block to only add
+ * the IN predicate if the collection is not empty like:
+ * Without inOrEmpty()
+ * {@code
+ *
+ * List
+ *
+ * Using inOrEmpty()
+ * {@code
+ *
+ * List
+ */
+ public final R inOrEmpty(Collection{@code
+ *
+ * new QSimpleDoc()
+ * .content.jsonExists("meta.title")
+ * .findList();
+ *
+ * }
+ *
+ * @param path the nested path in the JSON document in dot notation
+ */
+ public R jsonExists(String path) {
+ expr().jsonExists(_name, path);
+ return _root;
+ }
+
+ /**
+ * Path does not exist - for the given path in a JSON document.
+ *
+ * {@code
+ *
+ * new QSimpleDoc()
+ * .content.jsonNotExists("meta.title")
+ * .findList();
+ *
+ * }
+ *
+ * @param path the nested path in the JSON document in dot notation
+ */
+ public R jsonNotExists(String path) {
+ expr().jsonNotExists(_name, path);
+ return _root;
+ }
+
+ /**
+ * Value at the given JSON path is equal to the given value.
+ *
+ *
+ * {@code
+ *
+ * new QSimpleDoc()
+ * .content.jsonEqualTo("title", "Rob JSON in the DB")
+ * .findList();
+ *
+ * }
+ *
+ * {@code
+ *
+ * new QSimpleDoc()
+ * .content.jsonEqualTo("path.other", 34)
+ * .findList();
+ *
+ * }
+ *
+ * @param path the dot notation path in the JSON document
+ * @param value the equal to bind value
+ */
+ public R jsonEqualTo(String path, Object value) {
+ expr().jsonEqualTo(_name, path, value);
+ return _root;
+ }
+
+ /**
+ * Not Equal to - for the given path in a JSON document.
+ *
+ * @param path the nested path in the JSON document in dot notation
+ * @param value the value used to test equality against the document path's value
+ */
+ public R jsonNotEqualTo(String path, Object value) {
+ expr().jsonNotEqualTo(_name, path, value);
+ return _root;
+ }
+
+ /**
+ * Greater than - for the given path in a JSON document.
+ *
+ * @param path the nested path in the JSON document in dot notation
+ * @param value the value used to test against the document path's value
+ */
+ public R jsonGreaterThan(String path, Object value) {
+ expr().jsonGreaterThan(_name, path, value);
+ return _root;
+ }
+
+ /**
+ * Greater than or equal to - for the given path in a JSON document.
+ *
+ * @param path the nested path in the JSON document in dot notation
+ * @param value the value used to test against the document path's value
+ */
+ public R jsonGreaterOrEqual(String path, Object value) {
+ expr().jsonGreaterOrEqual(_name, path, value);
+ return _root;
+ }
+
+ /**
+ * Less than - for the given path in a JSON document.
+ *
+ * @param path the nested path in the JSON document in dot notation
+ * @param value the value used to test against the document path's value
+ */
+ public R jsonLessThan(String path, Object value) {
+ expr().jsonLessThan(_name, path, value);
+ return _root;
+ }
+
+ /**
+ * Less than or equal to - for the given path in a JSON document.
+ *
+ * @param path the nested path in the JSON document in dot notation
+ * @param value the value used to test against the document path's value
+ */
+ public R jsonLessOrEqualTo(String path, Object value) {
+ expr().jsonLessOrEqualTo(_name, path, value);
+ return _root;
+ }
+}
diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/PLocalDate.java b/ebean-querybean/src/main/java/io/ebean/typequery/PLocalDate.java
new file mode 100644
index 000000000..6ffd1285a
--- /dev/null
+++ b/ebean-querybean/src/main/java/io/ebean/typequery/PLocalDate.java
@@ -0,0 +1,28 @@
+package io.ebean.typequery;
+
+import java.time.LocalDate;
+
+/**
+ * LocalDate property.
+ *
+ * @param ? or ?1 style.
+ * {@code
+ *
+ * new QCustomer()
+ * .name.startsWith("Postgres")
+ * .contacts.filterMany("firstName istartsWith ?", "Rob")
+ * .findList();
+ *
+ * }
+ *
+ * {@code
+ *
+ * new QCustomer()
+ * .name.startsWith("Postgres")
+ * .contacts.filterMany("whenCreated inRange ? to ?", startDate, endDate)
+ * .findList();
+ *
+ * }
+ *
+ * @param expressions The expressions including and, or, not etc with ? and ?1 bind params.
+ * @param params The bind parameter values
+ */
+ public R filterMany(String expressions, Object... params) {
+ expr().filterMany(_name, expressions, params);
+ return _root;
+ }
+
+ /**
+ * Is empty for a collection property.
+ * Example - QCustomer extends TQRootBean
+ * avaje-ebeanorm-typequery-generator.
+ * {@code
+ *
+ * public class QCustomer extends TQRootBean
+ * Example - usage of QCustomer
+ * {@code
+ *
+ * Date fiveDaysAgo = ...
+ *
+ * List
+ * Resulting SQL where
+ * {@code sql
+ *
+ * where lower(t0.name) like ? and t0.status = ? and t0.registered > ? and u1.email like ?
+ * order by t0.name, t0.registered desc;
+ *
+ * --bind(rob,GOOD,Mon Jul 27 12:05:37 NZST 2015,%@foo.com)
+ * }
+ *
+ * @param {@code
+ *
+ * List
+ *
+ * @param properties the properties to fetch for this bean (* = all properties).
+ */
+ public R select(String properties) {
+ query.select(properties);
+ return root;
+ }
+
+ /**
+ * Set a FetchGroup to control what part of the object graph is loaded.
+ * {@code
+ *
+ * FetchGroup
+ */
+ public R select(FetchGroup{@code
+ *
+ * // alias for the customer properties in select()
+ * QCustomer cust = QCustomer.alias();
+ *
+ * // alias for the contact properties in contacts.fetch()
+ * QContact contact = QContact.alias();
+ *
+ * List
+ *
+ * @param properties the list of properties to fetch
+ */
+ @SafeVarargs
+ public final R select(TQProperty{@code
+ *
+ * List
+ *
+ * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean.
+ */
+ public R fetch(String path) {
+ query.fetch(path);
+ return root;
+ }
+
+ /**
+ * Specify a path to load including all its properties using a "query join".
+ *
+ * {@code
+ *
+ * List
+ *
+ * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean.
+ */
+ public R fetchQuery(String path) {
+ query.fetchQuery(path);
+ return root;
+ }
+
+ /**
+ * Specify a path to load from L2 cache including all its properties.
+ *
+ * {@code
+ *
+ * List
+ *
+ * @param path the property path to load from L2 cache.
+ */
+ public R fetchCache(String path) {
+ query.fetchCache(path);
+ return root;
+ }
+
+ /**
+ * Specify a path and properties to load using a "query join".
+ *
+ * {@code
+ *
+ * List
+ *
+ * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean.
+ * @param properties the properties to load for this path.
+ */
+ public R fetchQuery(String path, String properties) {
+ query.fetchQuery(path, properties);
+ return root;
+ }
+
+ /**
+ * Specify a path and properties to load from L2 cache.
+ *
+ * {@code
+ *
+ * List
+ *
+ * @param path the property path to load from L2 cache.
+ * @param properties the properties to load for this path.
+ */
+ public R fetchCache(String path, String properties) {
+ query.fetchCache(path, properties);
+ return root;
+ }
+
+ /**
+ * Specify a path to fetch with its specific properties to include
+ * (aka partial object).
+ * {@code
+ *
+ * // query orders...
+ * List
+ * {@code
+ *
+ * List
+ *
+ * @param path the path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean.
+ * @param properties properties of the associated bean that you want to include in the
+ * fetch (* means all properties, null also means all properties).
+ */
+ public R fetch(String path, String properties) {
+ query.fetch(path, properties);
+ return root;
+ }
+
+ /**
+ * Additionally specify a FetchConfig to use a separate query or lazy loading
+ * to load this path.
+ * {@code
+ *
+ * // fetch customers (their id, name and status)
+ * List
+ */
+ public R fetch(String path, String properties, FetchConfig fetchConfig) {
+ query.fetch(path, properties, fetchConfig);
+ return root;
+ }
+
+ /**
+ * Additionally specify a FetchConfig to specify a "query join" and or define
+ * the lazy loading query.
+ * {@code
+ *
+ * // fetch customers (their id, name and status)
+ * List
+ */
+ public R fetch(String path, FetchConfig fetchConfig) {
+ query.fetch(path, fetchConfig);
+ return root;
+ }
+
+ /**
+ * Apply the path properties replacing the select and fetch clauses.
+ * {@code
+ *
+ * // fetch a bean with JSON content
+ * EBasicJsonList bean= new QEBasicJsonList()
+ * .id.equalTo(42)
+ * .setAllowLoadErrors() // collect errors into bean state if we have invalid JSON
+ * .findOne();
+ *
+ *
+ * // get the invalid JSON errors from the bean state
+ * Map
+ */
+ public R setAllowLoadErrors() {
+ query.setAllowLoadErrors();
+ return root;
+ }
+
+ /**
+ * Explicitly specify whether to use AutoTune for this query.
+ * {@code
+ *
+ * // explicitly specify the indexes to search
+ * query.setDocIndexName("logstash-2016.11.5,logstash-2016.11.6")
+ *
+ * // search today's index
+ * query.setDocIndexName("$today")
+ *
+ * // search the last 3 days
+ * query.setDocIndexName("$last-3")
+ *
+ * }
+ * {@code
+ *
+ * // search today's index
+ * query.setDocIndexName("$today")
+ *
+ * // search the last 3 days
+ * query.setDocIndexName("$last-3")
+ *
+ * }
+ *
+ * @param indexName The index or indexes to search against
+ * @return This query
+ */
+ public R setDocIndexName(String indexName) {
+ query.setDocIndexName(indexName);
+ return root;
+ }
+
+ /**
+ * Restrict the query to only return subtypes of the given inherit type.
+ * {@code
+ *
+ * List
+ */
+ public R setInheritType(Class extends T> type) {
+ query.setInheritType(type);
+ return root;
+ }
+
+ /**
+ * Set the base table to use for this query.
+ * {@code
+ *
+ * QOrder()
+ * .setBaseTable("order_2019_05")
+ * .status.equalTo(Status.NEW)
+ * .findList();
+ *
+ * }
+ */
+ public R setBaseTable(String baseTable) {
+ query.setBaseTable(baseTable);
+ return root;
+ }
+
+ /**
+ * executed the select with "for update" which should lock the record "on read"
+ */
+ public R forUpdate() {
+ query.forUpdate();
+ return root;
+ }
+
+ /**
+ * Execute using "for update" clause with "no wait" option.
+ * {@code
+ *
+ * int rows =
+ * new QCustomer()
+ * .name.startsWith("Rob")
+ * .organisation.id.equalTo(42)
+ * .asUpdate()
+ * .set("active", false)
+ * .update()
+ *
+ * }
+ *
+ * @return This query as an UpdateQuery
+ */
+ public UpdateQuery{@code
+ *
+ * Order order =
+ * new QOrder()
+ * .setId(1)
+ * .fetch("details")
+ * .findOne();
+ *
+ * // the order details were eagerly fetched
+ * List
+ */
+ public R setId(Object id) {
+ query.setId(id);
+ return root;
+ }
+
+ /**
+ * Set a list of Id values to match.
+ * {@code
+ *
+ * List
+ */
+ public R setIdIn(Object... ids) {
+ query.where().idIn(ids);
+ return root;
+ }
+
+ /**
+ * Set a label on the query.
+ * {@code
+ *
+ * // Assuming sku is unique for products...
+ *
+ * Map
+ *
+ * @param mapKey the property to use as keys for a map.
+ */
+ public R setMapKey(String mapKey) {
+ query.setMapKey(mapKey);
+ return root;
+ }
+
+ /**
+ * Specify the PersistenceContextScope to use for this query.
+ * {@code
+ *
+ * raw("orderQty < shipQty")
+ *
+ * }
+ *
+ * Subquery example:
+ * {@code
+ *
+ * .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
+ *
+ * }
+ */
+ public R raw(String rawExpression) {
+ peekExprList().raw(rawExpression);
+ return root;
+ }
+
+ /**
+ * Add raw expression with an array of parameters.
+ * Without inOrEmpty()
+ * {@code
+ *
+ * QCustomer query = new QCustomer() // add some predicates
+ * .status.equalTo(Status.NEW);
+ *
+ * // common pattern - we can use rawOrEmpty() instead
+ * if (orderIds != null && !orderIds.isEmpty()) {
+ * query.raw("t0.customer_id in (select o.customer_id from orders o where o.id in (?1))", orderIds);
+ * }
+ *
+ * query.findList();
+ *
+ * }
+ *
+ * Using rawOrEmpty()
+ * Note that in the example below we use the ?1 bind parameter to get "parameter expansion"
+ * for each element in the collection.
+ *
+ * {@code
+ *
+ * new QCustomer()
+ * .status.equalTo(Status.NEW)
+ * // only add the expression if orderIds is not empty
+ * .rawOrEmpty("t0.customer_id in (select o.customer_id from orders o where o.id in (?1))", orderIds);
+ * .findList();
+ *
+ * }
+ *
+ * Postgres ANY
+ * With Postgres we would often use the SQL ANY expression and array parameter binding
+ * rather than IN.
+ *
+ * {@code
+ *
+ * new QCustomer()
+ * .status.equalTo(Status.NEW)
+ * .rawOrEmpty("t0.customer_id in (select o.customer_id from orders o where o.id = any(?))", orderIds);
+ * .findList();
+ *
+ * }
+ * {@code
+ *
+ * " ... = any(?::uuid[])"
+ *
+ * }
+ *
+ * @param raw The raw expression that is typically a subquery
+ * @param values The values which is typically a list or set of id values.
+ */
+ public R rawOrEmpty(String raw, Collection> values) {
+ peekExprList().rawOrEmpty(raw, values);
+ return root;
+ }
+
+ /**
+ * Add raw expression with a single parameter.
+ * Example:
+ * {@code
+ *
+ * // use a database function
+ * raw("add_days(orderDate, 10) < ?", someDate)
+ *
+ * }
+ *
+ * Subquery example:
+ * {@code
+ *
+ * .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
+ *
+ * }
+ */
+ public R raw(String rawExpression, Object bindValue) {
+ peekExprList().raw(rawExpression, bindValue);
+ return root;
+ }
+
+ /**
+ * Marker that can be used to indicate that the order by clause is defined after this.
+ * Example: order by customer name, order date
+ * {@code
+ * List
+ */
+ public R orderBy() {
+ // Yes this does not actually do anything! We include it because style wise it makes
+ // the query nicer to read and suggests that order by definitions are added after this
+ return root;
+ }
+
+ /**
+ * Marker that can be used to indicate that the order by clause is defined after this.
+ * Example: order by customer name, order date
+ * {@code
+ * List
+ */
+ public R order() {
+ // Yes this does not actually do anything! We include it because style wise it makes
+ // the query nicer to read and suggests that order by definitions are added after this
+ return root;
+ }
+
+ /**
+ * Set the full raw order by clause replacing the existing order by clause if there is one.
+ * Example
+ * {@code
+ *
+ * List
+ * Resulting SQL where clause
+ * {@code sql
+ *
+ * where t0.status = ? and (t0.id > ? or (t0.name like ? and t0.registered > ? ) )
+ * order by t0.id desc;
+ *
+ * --bind(GOOD,1000,super%,Wed Jul 22 00:00:00 NZST 2015)
+ *
+ * }
+ */
+ public R or() {
+ pushExprList(peekExprList().or());
+ return root;
+ }
+
+ /**
+ * Begin a list of expressions added by 'AND'.
+ * Example
+ * {@code
+ *
+ * List
+ * Resulting SQL where clause
+ * {@code sql
+ *
+ * where t0.status = ? and (t0.id > ? or (t0.name like ? and t0.registered > ? ) )
+ * order by t0.id desc;
+ *
+ * --bind(GOOD,1000,super%,Wed Jul 22 00:00:00 NZST 2015)
+ *
+ * }
+ */
+ public R and() {
+ pushExprList(peekExprList().and());
+ return root;
+ }
+
+ /**
+ * Begin a list of expressions added by NOT.
+ * Example using a query bean:
+ * {@code
+ *
+ * boolean userExists =
+ * new QContact()
+ * .email.equalTo("rob@foo.com")
+ * .exists();
+ *
+ * }
+ *
+ * Example:
+ * {@code
+ *
+ * boolean userExists = query()
+ * .where().eq("email", "rob@foo.com")
+ * .exists();
+ *
+ * }
+ *
+ * @return True if the query finds a matching row in the database
+ */
+ public boolean exists() {
+ return query.exists();
+ }
+
+ /**
+ * Execute the query returning either a single bean or null (if no matching
+ * bean is found).
+ * {@code
+ *
+ * // assuming the sku of products is unique...
+ * Product product =
+ * new QProduct()
+ * .sku.equalTo("aa113")
+ * .findOne();
+ * ...
+ * }
+ * {@code
+ *
+ * // Fetch order 42 and additionally fetch join its order details...
+ * Order order =
+ * new QOrder()
+ * .fetch("details") // eagerly load the order details
+ * .id.equalTo(42)
+ * .findOne();
+ *
+ * // the order details were eagerly loaded
+ * List
+ */
+ @Nullable
+ public T findOne() {
+ return query.findOne();
+ }
+
+ /**
+ * Execute the query returning an optional bean.
+ */
+ @Nonnull
+ public Optional{@code
+ *
+ * List
+ *
+ * @see Query#findList()
+ */
+ @Nonnull
+ public List{@code
+ *
+ * // use try with resources to ensure Stream is closed
+ *
+ * try (Stream
+ */
+ @Nonnull
+ public Stream{@code
+ *
+ * Set
+ *
+ * @see Query#findSet()
+ */
+ @Nonnull
+ public Set{@code
+ *
+ * Map
+ *
+ * @see Query#findMap()
+ */
+ @Nonnull
+ public {@code
+ *
+ * Query
+ */
+ @Nonnull
+ public QueryIteratorExample
+ * {@code
+ *
+ * List
+ *
+ * @return the list of values for the selected property
+ */
+ @Nonnull
+ public List findSingleAttributeList() {
+ return query.findSingleAttributeList();
+ }
+
+ /**
+ * Execute the query returning a single value for a single property.
+ * Example
+ * {@code
+ *
+ * LocalDate maxDate =
+ * new QCustomer()
+ * .select("max(startDate)")
+ * .findSingleAttribute();
+ *
+ * }
+ *
+ * @return the list of values for the selected property
+ */
+ public A findSingleAttribute() {
+ return query.findSingleAttribute();
+ }
+
+ /**
+ * Execute the query processing the beans one at a time.
+ * {@code
+ *
+ * new QCustomer()
+ * .status.equalTo(Status.NEW)
+ * .orderBy().id.asc()
+ * .findEach((Customer customer) -> {
+ *
+ * // do something with customer
+ * System.out.println("-- visit " + customer);
+ * });
+ *
+ * }
+ *
+ * @param consumer the consumer used to process the queried beans.
+ */
+ public void findEach(Consumer{@code
+ *
+ * new QCustomer()
+ * .status.equalTo(Status.NEW)
+ * .orderBy().id.asc()
+ * .findEachWhile((Customer customer) -> {
+ *
+ * // do something with customer
+ * System.out.println("-- visit " + customer);
+ *
+ * // return true to continue processing or false to stop
+ * return (customer.getId() < 40);
+ * });
+ *
+ * }
+ *
+ * @param consumer the consumer used to process the queried beans.
+ */
+ public void findEachWhile(Predicate{@code
+ *
+ * PagedList
+ *
+ * @return The PagedList
+ */
+ @Nonnull
+ public PagedList{@code
+ *
+ * new QMachineUse()
+ * // where ...
+ * .date.inRange(fromDate, toDate)
+ *
+ * .having()
+ * .sumHours.greaterThan(1)
+ * .findList()
+ *
+ * // The sumHours property uses @Aggregation
+ * // e.g. @Aggregation("sum(hours)")
+ *
+ * }
+ */
+ public R having() {
+ if (whereStack == null) {
+ whereStack = new ArrayStack<>();
+ }
+ // effectively putting having expression list onto stack
+ // such that expression now add to the having clause
+ whereStack.push(query.having());
+ return root;
+ }
+
+ /**
+ * Return the underlying having clause to typically when using dynamic aggregation formula.
+ * {@code
+ *
+ * // sum(distanceKms) ... is a "dynamic formula"
+ * // so we use havingClause() for it like:
+ *
+ * List
+ */
+ public ExpressionListavaje-ebeanorm-typequery-generator
+ * for each entity bean type and can then be used to build queries with type safe criteria.
+ * Example - usage of QCustomer
+ * {@code
+ *
+ * Date fiveDaysAgo = ...
+ *
+ * List
+ */
+package io.ebean.typequery;
\ No newline at end of file
diff --git a/ebean-querybean/src/test/java/io/ebean/typequery/PBooleanTest.java b/ebean-querybean/src/test/java/io/ebean/typequery/PBooleanTest.java
new file mode 100644
index 000000000..283603c5f
--- /dev/null
+++ b/ebean-querybean/src/test/java/io/ebean/typequery/PBooleanTest.java
@@ -0,0 +1,92 @@
+package io.ebean.typequery;
+
+import io.ebeaninternal.server.expression.DefaultExpressionList;
+import io.ebeaninternal.server.expression.SimpleExpression;
+import org.example.domain.Customer;
+import org.example.domain.query.QCustomer;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PBooleanTest {
+
+ PBoolean