diff --git a/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java b/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java new file mode 100644 index 000000000..00ad8fadf --- /dev/null +++ b/ebean-querybean/src/main/java/io/ebean/typequery/QueryBean.java @@ -0,0 +1,1680 @@ +package io.ebean.typequery; + +import io.avaje.lang.Nullable; +import io.ebean.*; +import io.ebean.search.MultiMatch; +import io.ebean.search.TextCommonTerms; +import io.ebean.search.TextQueryString; +import io.ebean.search.TextSimple; +import io.ebean.text.PathProperties; + +import javax.sql.DataSource; +import java.sql.Connection; +import java.sql.Timestamp; +import java.util.*; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.stream.Stream; + +/** + * Query bean for strongly typed query construction and execution. + *

+ * For each entity bean querybean-generator generates a query bean that implements QueryBean. + * + *

Example - usage of QCustomer

+ *
{@code
+ *
+ *  Date fiveDaysAgo = ...
+ *
+ *  List customers =
+ *      new QCustomer()
+ *        .name.ilike("rob")
+ *        .status.equalTo(Customer.Status.GOOD)
+ *        .registered.after(fiveDaysAgo)
+ *        .contacts.email.endsWith("@foo.com")
+ *        .orderBy()
+ *          .name.asc()
+ *          .registered.desc()
+ *        .findList();
+ *
+ * }
+ *

+ *

Resulting SQL where

+ *
{@code
+ *
+ *   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 the entity bean type (normal entity bean type e.g. Customer) + * @param the specific query bean type (e.g. QCustomer) + */ +public interface QueryBean { + + /** + * Return the fetch group. + */ + FetchGroup buildFetchGroup(); + + /** + * Return a copy of the query bean. + */ + R copy(); + + /** + * Return the underlying query. + *

+ * 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(). + */ + Query query(); + + /** + * Explicitly set a comma delimited list of the properties to fetch on the + * 'main' root level entity bean (aka partial object). Note that '*' means all + * properties. + *

+ * You use {@link #fetch(String, String)} to specify specific properties to fetch + * on other non-root level paths of the object graph. + *

+ *

{@code
+   *
+   * List customers =
+   *     new QCustomer()
+   *     // Only fetch the customer id, name and status.
+   *     // This is described as a "Partial Object"
+   *     .select("name, status")
+   *     .name.ilike("rob%")
+   *     .findList();
+   *
+   * }
+ * + * @param properties the properties to fetch for this bean (* = all properties). + */ + R select(String properties); + + /** + * Set a FetchGroup to control what part of the object graph is loaded. + *

+ * FetchGroup is immutable and threadsafe. We expect to create and store + * FetchGroup to a static final field and reuse the instance. + *

+ * FetchGroup is an alternative to using select() and fetch() providing a nice + * clean separation between what a query should load and the query predicates. + * + *

{@code
+   *
+   * // immutable threadsafe
+   *
+   * static final FetchGroup fetchGroup =
+   *   QCustomer.forFetchGroup()
+   *     .shippingAddress.fetch()
+   *     .contacts.fetch()
+   *     .buildFetchGroup();
+   *
+   * List customers = new QCustomer()
+   *   .select(fetchGroup)
+   *   .findList();
+   *
+   * }
+ * + * + *
{@code
+   *
+   * static final FetchGroup fetchGroup =
+   *   FetchGroup.of(Customer.class)
+   *     .select("name, status")
+   *     .fetch("contacts", "firstName, lastName, email")
+   *     .build();
+   *
+   * List customers = new QCustomer()
+   *   .select(fetchGroup)
+   *   .findList();
+   *
+   * }
+ */ + R select(FetchGroup fetchGroup); + + /** + * Specify the properties to be loaded on the 'main' root level entity bean. + *

+ * The resulting entities with be "partially loaded" aka partial objects. + *

+ * Alternatively we can use a {@link #select(FetchGroup)} to specify all properties + * to load on all parts of the graph. + * + *

{@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 customers =
+   *     new QCustomer()
+   *       // specify the parts of the graph we want to load
+   *       .select(cust.id, cust.name)
+   *       .contacts.fetch(contact.firstName, contact.lastName, contact.email)
+   *
+   *       // predicates
+   *       .id.gt(1)
+   *       .findList();
+   *
+   * }
+ * + * @param properties the list of properties to fetch + */ + @SuppressWarnings("unchecked") + R select(TQProperty... properties); + + /** + * Specify the properties to be loaded on the 'main' root level entity bean + * also allowing for functions to be used like {@link StdOperators#max(Query.Property)}. + * + * @param properties the list of properties to fetch + */ + R select(Query.Property... properties); + + /** + * Specify a path to load including all its properties. + * + *
{@code
+   *
+   * List customers =
+   *     new QCustomer()
+   *     // eager fetch the contacts
+   *     .fetch("contacts")
+   *     .findList();
+   *
+   * }
+ * + * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean. + */ + R fetch(String path); + + /** + * Specify a path to load including all its properties using a "query join". + * + *
{@code
+   *
+   * List customers =
+   *     new QCustomer()
+   *     // eager fetch the contacts using a "query join"
+   *     .fetchQuery("contacts")
+   *     .findList();
+   *
+   * }
+ * + * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean. + */ + R fetchQuery(String path); + + /** + * Specify a path to load from L2 cache including all its properties. + * + *
{@code
+   *
+   * List orders =
+   *     new QOrder()
+   *     // eager fetch the customer using L2 cache
+   *     .fetchCache("customer")
+   *     .findList();
+   *
+   * }
+ * + * @param path the property path to load from L2 cache. + */ + R fetchCache(String path); + + /** + * Specify a path and properties to load using a "query join". + * + *
{@code
+   *
+   * List customers =
+   *     new QCustomer()
+   *     // eager fetch contacts using a "query join"
+   *     .fetchQuery("contacts", "email, firstName, lastName")
+   *     .findList();
+   *
+   * }
+ * + * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean. + * @param properties the properties to load for this path. + */ + R fetchQuery(String path, String properties); + + /** + * Specify a path and properties to load from L2 cache. + * + *
{@code
+   *
+   * List orders =
+   *     new QOrder()
+   *     // eager fetch the customer using L2 cache
+   *     .fetchCache("customer", "name,status")
+   *     .findList();
+   *
+   * }
+ * + * @param path the property path to load from L2 cache. + * @param properties the properties to load for this path. + */ + R fetchCache(String path, String properties); + + /** + * Specify a path to fetch with its specific properties to include + * (aka partial object). + *

+ * 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. + *

+ *

{@code
+   *
+   * // query orders...
+   * List orders =
+   *     new QOrder()
+   *       .fetch("customer", "name, phoneNumber")
+   *       .fetch("customer.billingAddress", "*")
+   *       .findList();
+   *
+   * }
+ *

+ * If columns is null or "*" then all columns/properties for that path are fetched. + * + *

{@code
+   *
+   * List customers =
+   *     new QCustomer()
+   *     .select("name, status")
+   *     .fetch("contacts", "firstName,lastName,email")
+   *     .findList();
+   *
+   * }
+ * + * @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). + */ + R fetch(String path, String properties); + + /** + * 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 customers =
+   *     new QCustomer()
+   *     .select("name, status")
+   *     .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
+   *     .findList();
+   *
+   * }
+ */ + R fetch(String path, String properties, FetchConfig fetchConfig); + + /** + * 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 customers =
+   *     new QCustomer()
+   *       // lazy fetch contacts with a batch size of 100
+   *       .fetch("contacts", new FetchConfig().lazy(100))
+   *       .findList();
+   *
+   * }
+ */ + R fetch(String path, FetchConfig fetchConfig); + + /** + * Apply the path properties replacing the select and fetch clauses. + *

+ * This is typically used when the PathProperties is applied to both the query and the JSON output. + */ + R apply(PathProperties pathProperties); + + /** + * Apply changes to the query conditional on the supplied predicate. + *

+ * Typically, the changes are extra predicates etc. + * + * @param predicate The predicate which when true the changes are applied + * @param apply The changes to apply to the query + */ + R alsoIf(BooleanSupplier predicate, Consumer apply); + + /** + * Perform an 'As of' query using history tables to return the object graph + * as of a time in the past. + *

+ * To perform this query the DB must have underlying history tables. + * + * @param asOf the date time in the past at which you want to view the data + */ + R asOf(Timestamp asOf); + + /** + * Execute the query against the draft set of tables. + */ + R asDraft(); + + /** + * Execute the query including soft deleted rows. + */ + R setIncludeSoftDeletes(); + + /** + * Add an expression to the WHERE or HAVING clause. + */ + R add(Expression expression); + + /** + * Set root table alias. + */ + R alias(String alias); + + /** + * Set the maximum number of rows to return in the query. + * + * @param maxRows the maximum number of rows to return in the query. + */ + R setMaxRows(int maxRows); + + /** + * Set the first row to return for this query. + * + * @param firstRow the first row to include in the query result. + */ + R setFirstRow(int firstRow); + + /** + * Execute the query allowing properties with invalid JSON to be collected and not fail the query. + *

{@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 errors = server().getBeanState(bean).getLoadErrors();
+   *
+   *   // If this map is not empty tell we have invalid JSON
+   *   // and should try and fix the JSON content or inform the user
+   *
+   * }
+ */ + R setAllowLoadErrors(); + + /** + * Explicitly specify whether to use AutoTune for this query. + *

+ * 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. + */ + R setAutoTune(boolean autoTune); + + /** + * A hint which for JDBC translates to the Statement.fetchSize(). + *

+ * 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. + */ + R setBufferFetchSizeHint(int fetchSize); + + /** + * Set whether this query uses DISTINCT. + */ + R setDistinct(boolean distinct); + + /** + * Set the index(es) to search for a document store which uses partitions. + *

+ * For example, when executing a query against ElasticSearch with daily indexes we can + * explicitly specify the indexes to search against. + * + *

{@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")
+   *
+   * }
+ *

+ * 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. + *

+ *
{@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 + */ + R setDocIndexName(String indexName); + + /** + * Restrict the query to only return subtypes of the given inherit type. + *
{@code
+   *
+   *   List animals =
+   *     new QAnimal()
+   *       .name.startsWith("Fluffy")
+   *       .setInheritType(Cat.class)
+   *       .findList();
+   *
+   * }
+ */ + R setInheritType(Class type); + + /** + * Set the base table to use for this query. + *

+ * Typically this is used when a table has partitioning and we wish to specify a specific + * partition/table to query against. + *

+ *
{@code
+   *
+   *   QOrder()
+   *   .setBaseTable("order_2019_05")
+   *   .status.equalTo(Status.NEW)
+   *   .findList();
+   *
+   * }
+ */ + R setBaseTable(String baseTable); + + /** + * Execute the query with the given lock type and WAIT. + *

+ * Note that forUpdate() is the same as + * withLock(LockType.UPDATE). + *

+ * Provides us with the ability to explicitly use Postgres + * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. + */ + R withLock(Query.LockType lockType); + + /** + * Execute the query with the given lock type and lock wait. + *

+ * Note that forUpdateNoWait() is the same as + * withLock(LockType.UPDATE, LockWait.NOWAIT). + *

+ * Provides us with the ability to explicitly use Postgres + * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. + */ + R withLock(Query.LockType lockType, Query.LockWait lockWait); + + /** + * Add EXISTS sub-query predicate. + */ + R exists(Query subQuery); + + /** + * Add NOT EXISTS sub-query predicate. + */ + R notExists(Query subQuery); + + /** + * EXISTS using a SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + R exists(String sqlSubQuery, Object... bindValues); + + /** + * Not EXISTS using a SQL SubQuery. + * + * @param sqlSubQuery The SQL SubQuery + * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. + */ + R notExists(String sqlSubQuery, Object... bindValues); + + /** + * Execute using "for update" clause which results in the DB locking the record. + */ + R forUpdate(); + + /** + * Execute using "for update" clause with "no wait" option. + *

+ * This is typically a Postgres and Oracle only option at this stage. + */ + R forUpdateNoWait(); + + /** + * Execute using "for update" clause with "skip locked" option. + *

+ * This is typically a Postgres and Oracle only option at this stage. + */ + R forUpdateSkipLocked(); + + /** + * Return this query as an UpdateQuery. + * + *

{@code
+   *
+   *   int rows =
+   *     new QCustomer()
+   *     .name.startsWith("Rob")
+   *     .organisation.id.equalTo(42)
+   *     .asUpdate()
+   *       .set("active", false)
+   *       .update()
+   *
+   * }
+ * + * @return This query as an UpdateQuery + */ + UpdateQuery asUpdate(); + + /** + * Convert the query to a DTO bean query. + *

+ * We effectively use the underlying ORM query to build the SQL and then execute + * and map it into DTO beans. + */ + DtoQuery asDto(Class dtoClass); + + /** + * Set the Id value to query. This is used with findOne(). + *

+ * You can use this to have further control over the query. For example adding fetch joins. + *

+ *

{@code
+   *
+   * Order order =
+   *   new QOrder()
+   *     .setId(1)
+   *     .fetch("details")
+   *     .findOne();
+   *
+   * // the order details were eagerly fetched
+   * List details = order.getDetails();
+   *
+   * }
+ */ + R setId(Object id); + + /** + * Set a list of Id values to match. + *

+ *

{@code
+   *
+   * List orders =
+   *   new QOrder()
+   *     .setIdIn(42, 43, 44)
+   *     .findList();
+   *
+   * }
+ */ + R setIdIn(Object... ids); + + /** + * Set a collection of Id values to match. + *

+ *

{@code
+   *
+   * Collection ids = ...
+   *
+   * List orders =
+   *   new QOrder()
+   *     .setIdIn(ids)
+   *     .findList();
+   *
+   * }
+ */ + R setIdIn(Collection ids); + + /** + * Set a label on the query. + *

+ * 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. + */ + R setLabel(String label); + + /** + * Set a SQL query hint. + *

+ * This results in an inline comment that immediately follows + * after the select keyword in the form: {@code /*+ hint *\/ } + */ + R setHint(String hint); + + /** + * Set the profile location. + *

+ * This is typically set automatically via enhancement when profile location enhancement + * is turned on. It is generally not set by application code. + */ + R setProfileLocation(ProfileLocation profileLocation); + + /** + * Set the default lazy loading batch size to use. + *

+ * 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 + */ + R setLazyLoadBatchSize(int lazyLoadBatchSize); + + /** + * Set the property to use as keys for a map. + *

+ * If no property is set then the id property is used. + *

+ *

{@code
+   *
+   * // Assuming sku is unique for products...
+   *
+   * Map productMap =
+   *     new QProduct()
+   *     // use sku for keys...
+   *     .setMapKey("sku")
+   *     .findMap();
+   *
+   * }
+ * + * @param mapKey the property to use as keys for a map. + */ + R setMapKey(String mapKey); + + /** + * Specify the PersistenceContextScope to use for this query. + *

+ * When this is not set the 'default' configured on {@link io.ebean.config.DatabaseConfig#setPersistenceContextScope(PersistenceContextScope)} + * is used - this value defaults to {@link 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. + */ + R setPersistenceContextScope(PersistenceContextScope scope); + + /** + * Set RawSql to use for this query. + */ + R setRawSql(RawSql rawSql); + + /** + * When set to true when you want the returned beans to be read only. + */ + R setReadOnly(boolean readOnly); + + /** + * 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. + */ + R setUseCache(boolean useCache); + + /** + * Set the mode to use the bean cache when executing this query. + *

+ * 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. + */ + R setBeanCacheMode(CacheMode beanCacheMode); + + /** + * Set to true if this query should execute against the doc store. + *

+ * When setting this you may also consider disabling lazy loading. + */ + R setUseDocStore(boolean useDocStore); + + /** + * Set true if you want to disable lazy loading. + *

+ * That is, once the object graph is returned further lazy loading is disabled. + */ + R setDisableLazyLoading(boolean disableLazyLoading); + + /** + * Disable read auditing for this query. + *

+ * 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. + */ + R setDisableReadAuditing(); + + /** + * Set this to true to use the query cache. + */ + R setUseQueryCache(boolean useCache); + + /** + * Set the {@link CacheMode} to use the query for executing this query. + */ + R setUseQueryCache(CacheMode cacheMode); + + /** + * Set a timeout on this query. + *

+ * 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. + * + * @param secs the query timeout limit in seconds. Zero means there is no limit. + */ + R setTimeout(int secs); + + /** + * Returns the set of properties or paths that are unknown (do not map to known properties or paths). + *

+ * Validate the query checking the where and orderBy expression paths to confirm if + * they represent valid properties or paths for the given bean type. + */ + Set validate(); + + /** + * Add raw expression with no 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. + *

+ *

{@code
+   *
+   *   raw("orderQty < shipQty")
+   *
+   * }
+ * + *

Subquery example:

+ *
{@code
+   *
+   *   .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
+   *
+   * }
+ */ + R raw(String rawExpression); + + /** + * Add raw expression with an array of parameters. + *

+ * 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. + */ + R raw(String rawExpression, Object... bindValues); + + /** + * Only add the raw expression if the values is not null or empty. + *

+ * 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. + * + *

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();
+   *
+   * }
+ *

+ * Note that we need to cast the Postgres array for UUID types like: + *

+ *
{@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. + */ + R rawOrEmpty(String raw, Collection values); + + /** + * Add raw expression with a single parameter. + *

+ * 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. + *

+ *

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)
+   *
+   * }
+ */ + R raw(String rawExpression, Object bindValue); + + /** + * In expression using multiple columns. + */ + R inTuples(InTuples inTuples); + + /** + * 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 orders =
+   *          new QOrder()
+   *            .customer.name.ilike("rob")
+   *            .orderBy()
+   *              .customer.name.asc()
+   *              .orderDate.asc()
+   *            .findList();
+   *
+   * }
+ */ + R orderBy(); + + /** + * @deprecated migrate to {@link #orderBy()}. + */ + @Deprecated(since = "13.19", forRemoval = true) + R order(); + + /** + * 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. + */ + R orderBy(String orderByClause); + + /** + * @deprecated migrate to {@link #orderBy(String)} + */ + @Deprecated(since = "13.19", forRemoval = true) + R order(String orderByClause); + + /** + * Begin a list of expressions added by 'OR'. + *

+ * Use endOr() or endJunction() to stop added to OR and 'pop' to the parent expression list. + *

+ *

Example

+ *

+ * This example uses an 'OR' expression list with an inner 'AND' expression list. + *

+ *
{@code
+   *
+   *    List customers =
+   *          new QCustomer()
+   *            .status.equalTo(Customer.Status.GOOD)
+   *            .or()
+   *              .id.greaterThan(1000)
+   *              .and()
+   *                .name.startsWith("super")
+   *                .registered.after(fiveDaysAgo)
+   *              .endAnd()
+   *            .endOr()
+   *            .orderBy().id.desc()
+   *            .findList();
+   *
+   * }
+ *

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)
+   *
+   * }
+ */ + R or(); + + /** + * Begin a list of expressions added by 'AND'. + *

+ * 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. + * + *

Example

+ *

+ * This example uses an 'OR' expression list with an inner 'AND' expression list. + *

+ *
{@code
+   *
+   *    List customers =
+   *          new QCustomer()
+   *            .status.equalTo(Customer.Status.GOOD)
+   *            .or() // OUTER 'OR'
+   *              .id.greaterThan(1000)
+   *              .and()  // NESTED 'AND' expression list
+   *                .name.startsWith("super")
+   *                .registered.after(fiveDaysAgo)
+   *                .endAnd()
+   *              .endOr()
+   *            .orderBy().id.desc()
+   *            .findList();
+   *
+   * }
+ *

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)
+   *
+   * }
+ */ + R and(); + + /** + * Begin a list of expressions added by NOT. + *

+ * Use endNot() or endJunction() to stop added to NOT and 'pop' to the parent expression list. + */ + R not(); + + /** + * Begin a list of expressions added by MUST. + *

+ * This automatically makes this query a document store query. + *

+ * Use endJunction() to stop added to MUST and 'pop' to the parent expression list. + */ + R must(); + + /** + * Begin a list of expressions added by MUST NOT. + *

+ * This automatically makes this query a document store query. + *

+ * Use endJunction() to stop added to MUST NOT and 'pop' to the parent expression list. + */ + R mustNot(); + + /** + * Begin a list of expressions added by SHOULD. + *

+ * This automatically makes this query a document store query. + *

+ * Use endJunction() to stop added to SHOULD and 'pop' to the parent expression list. + */ + R should(); + + /** + * End a list of expressions added by 'OR'. + */ + R endJunction(); + + /** + * End OR junction - synonym for endJunction(). + */ + R endOr(); + + /** + * End AND junction - synonym for endJunction(). + */ + R endAnd(); + + /** + * End NOT junction - synonym for endJunction(). + */ + R endNot(); + + /** + * Add expression after this to the WHERE 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. + */ + R where(); + + /** + * Begin added expressions to the 'Text' expression list. + *

+ * 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'. + */ + R text(); + + /** + * Add a Text Multi-match expression (document store only). + *

+ * This automatically makes the query a document store query. + */ + R multiMatch(String query, MultiMatch multiMatch); + + /** + * Add a Text Multi-match expression (document store only). + *

+ * This automatically makes the query a document store query. + */ + R multiMatch(String query, String... properties); + + /** + * Add a Text common terms expression (document store only). + *

+ * This automatically makes the query a document store query. + */ + R textCommonTerms(String query, TextCommonTerms options); + + /** + * Add a Text simple expression (document store only). + *

+ * This automatically makes the query a document store query. + */ + R textSimple(String query, TextSimple options); + + /** + * Add a Text query string expression (document store only). + *

+ * This automatically makes the query a document store query. + */ + R textQueryString(String query, TextQueryString options); + + /** + * Execute the query using the given transaction. + */ + R usingTransaction(Transaction transaction); + + /** + * Execute the query using the given connection. + */ + R usingConnection(Connection connection); + + /** + * Ensure that the master DataSource is used if there is a read only data source + * being used (that is using a read replica database potentially with replication lag). + *

+ * When the database is configured with a read-only DataSource via + * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then + * by default when a query is run without an active transaction, it uses the read-only data + * source. We we use {@code usingMaster()} to instead ensure that the query is executed + * against the master data source. + */ + R usingMaster(); + + /** + * Execute the query returning true if a row is found. + *

+ * 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. + * + *

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 + */ + boolean exists(); + + /** + * Execute the query returning either a single bean or null (if no matching + * bean is found). + *

+ * 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. + *

+ *

{@code
+   *
+   * // assuming the sku of products is unique...
+   * Product product =
+   *     new QProduct()
+   *         .sku.equalTo("aa113")
+   *         .findOne();
+   * ...
+   * }
+ *

+ * It is also useful with finding objects by their id when you want to specify + * further join information to optimise the query. + *

+ *

{@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 details = order.getDetails();
+   * ...
+   * }
+ */ + @Nullable + T findOne(); + + /** + * Execute the query returning an optional bean. + */ + Optional findOneOrEmpty(); + + /** + * Execute the query returning the list of objects. + *

+ * This query will execute against the EbeanServer that was used to create it. + *

+ *

{@code
+   *
+   * List customers =
+   *     new QCustomer()
+   *       .name.ilike("rob%")
+   *       .findList();
+   *
+   * }
+ * + * @see Query#findList() + */ + List findList(); + + /** + * Execute the query returning the result as a Stream. + *

+ * Note that this can support very large queries iterating + * any number of results. To do so internally it can use + * multiple persistence contexts. + *

+ *
{@code
+   *
+   *  // use try with resources to ensure Stream is closed
+   *
+   *  try (Stream stream = query.findStream()) {
+   *    stream
+   *    .map(...)
+   *    .collect(...);
+   *  }
+   *
+   * }
+ */ + Stream findStream(); + + /** + * Execute the query returning the set of objects. + *

+ * This query will execute against the EbeanServer that was used to create it. + *

+ *

{@code
+   *
+   * Set customers =
+   *     new QCustomer()
+   *       .name.ilike("rob%")
+   *       .findSet();
+   *
+   * }
+ * + * @see Query#findSet() + */ + Set findSet(); + + /** + * Execute the query returning the list of Id's. + *

+ * This query will execute against the EbeanServer that was used to create it. + * + * @see Query#findIds() + */ + List findIds(); + + /** + * Execute the query returning a map of the objects. + *

+ * 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. + *

+ *

{@code
+   *
+   * Map map =
+   *   new QProduct()
+   *     .sku.asMapKey()
+   *     .findMap();
+   *
+   * }
+ * + * @see Query#findMap() + */ + Map findMap(); + + /** + * Execute the query iterating over the results. + *

+ * 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. + *

+ *
{@code
+   *
+   *  Query query =
+   *    new QCustomer()
+   *     .status.equalTo(Customer.Status.NEW)
+   *     .orderBy()
+   *       id.asc()
+   *     .query();
+   *
+   *  try (QueryIterator it = query.findIterate()) {
+   *    while (it.hasNext()) {
+   *      Customer customer = it.next();
+   *      // do something with customer ...
+   *    }
+   *  }
+   *
+   * }
+ */ + QueryIterator findIterate(); + + /** + * Execute the query returning a list of values for a single property. + *

+ *

Example

+ *
{@code
+   *
+   *  List names =
+   *    new QCustomer()
+   *      .setDistinct(true)
+   *      .select(name)
+   *      .findSingleAttributeList();
+   *
+   * }
+ * + * @return the list of values for the selected property + */ +
List findSingleAttributeList(); + + /** + * Execute the query returning a single value or null for a single property. + *

+ *

Example

+ *
{@code
+   *
+   *  LocalDate maxDate =
+   *    new QCustomer()
+   *      .select("max(startDate)")
+   *      .findSingleAttribute();
+   *
+   * }
+ * + * @return a single value or null for the selected property + */ + @Nullable +
A findSingleAttribute(); + + /** + * Execute the query returning a single optional attribute value. + *

+ *

Example

+ *
{@code
+   *
+   *  Optional maybeName =
+   *    new QCustomer()
+   *      .select(name)
+   *      .id.eq(42)
+   *      .status.eq(NEW)
+   *      .findSingleAttributeOrEmpty();
+   *
+   * }
+ * + * @return an optional value for the selected property + */ +
Optional findSingleAttributeOrEmpty(); + + /** + * Execute the query processing the beans one at a time. + *

+ * 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 Consumer interface which is better suited to use with closures. + * + *

{@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. + */ + void findEach(Consumer consumer); + + /** + * Execute findEach streaming query batching the results for consuming. + *

+ * This query execution will stream the results and is suited to consuming + * large numbers of results from the database. + *

+ * Typically, we use this batch consumer when we want to do further processing on + * the beans and want to do that processing in batch form, for example - 100 at + * a time. + * + * @param batch The number of beans processed in the batch + * @param consumer Process the batch of beans + */ + void findEach(int batch, Consumer> consumer); + + /** + * Execute the query using callbacks to a visitor to process the resulting + * beans one at a time. + *

+ * This method is functionally equivalent to findIterate() but instead of using an + * iterator uses the Predicate interface which is better suited to use with closures. + * + *

{@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. + */ + void findEachWhile(Predicate consumer); + + /** + * Return versions of a @History entity 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. + */ + List> findVersions(); + + /** + * Return versions of a @History entity bean between a start and end timestamp. + *

+ * 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. + */ + List> findVersionsBetween(Timestamp start, Timestamp end); + + /** + * Return the count of entities this query should return. + *

+ * This is the number of 'top level' or 'root level' entities. + */ + int findCount(); + + /** + * Execute find row count query in a background thread. + *

+ * 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). + * + * @return a Future object for the row count query + */ + FutureRowCount findFutureCount(); + + /** + * Execute find Id's query in a background thread. + *

+ * 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). + * + * @return a Future object for the list of Id's + */ + FutureIds findFutureIds(); + + /** + * Execute find list query in a background thread. + *

+ * 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. + * + * @return a Future object for the list result of the query + */ + FutureList findFutureList(); + + /** + * Return a PagedList for this query using firstRow and maxRows. + *

+ * 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. + *

+ *

{@code
+   *
+   *  PagedList pagedList =
+   *    new QOrder()
+   *       .setFirstRow(50)
+   *       .setMaxRows(20)
+   *       .findPagedList();
+   *
+   *       // fetch the total row count in the background
+   *       pagedList.loadRowCount();
+   *
+   *       List orders = pagedList.getList();
+   *       int totalRowCount = pagedList.getTotalRowCount();
+   *
+   * }
+ * + * @return The PagedList + */ + PagedList findPagedList(); + + /** + * Execute as a delete query deleting the 'root level' beans that match the predicates + * in the query. + *

+ * Note that if the query includes joins then the generated delete statement may not be + * optimal depending on the database platform. + * + * @return the number of beans/rows that were deleted. + */ + int delete(); + + /** + * Return the sql that was generated for executing this query. + *

+ * This is only available after the query has been executed and provided only + * for informational purposes. + */ + String getGeneratedSql(); + + /** + * Return the type of beans being queried. + */ + Class getBeanType(); + + /** + * Return the expression list that has been built for this query. + */ + ExpressionList getExpressionList(); + + /** + * Start adding expressions to the having clause when using @Aggregation properties. + * + *

{@code
+   *
+   *   new QMachineUse()
+   *   // where ...
+   *   .date.inRange(fromDate, toDate)
+   *
+   *   .having()
+   *   .sumHours.greaterThan(1)
+   *   .findList()
+   *
+   *   // The sumHours property uses @Aggregation
+   *   // e.g. @Aggregation("sum(hours)")
+   *
+   * }
+ */ + R having(); + + /** + * Return the underlying having clause to typically when using dynamic aggregation formula. + *

+ * Note that after this we no longer have the query bean so typically we use this right + * at the end of the query. + * + *

{@code
+   *
+   *  // sum(distanceKms) ... is a "dynamic formula"
+   *  // so we use havingClause() for it like:
+   *
+   *  List machineUse =
+   *
+   *    new QMachineUse()
+   *      .select("machine, sum(fuelUsed), sum(distanceKms)")
+   *
+   *      // where ...
+   *      .date.greaterThan(LocalDate.now().minusDays(7))
+   *
+   *      .havingClause()
+   *        .gt("sum(distanceKms)", 2)
+   *        .findList();
+   *
+   * }
+ */ + ExpressionList havingClause(); +} 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 6d414db9d..73941a6e3 100644 --- a/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java +++ b/ebean-querybean/src/main/java/io/ebean/typequery/TQRootBean.java @@ -12,7 +12,6 @@ import io.ebean.text.PathProperties; import io.ebeaninternal.api.SpiQueryFetch; import io.ebeaninternal.server.util.ArrayStack; -import javax.sql.DataSource; import java.sql.Connection; import java.sql.Timestamp; import java.util.*; @@ -25,9 +24,7 @@ import java.util.stream.Stream; * Base root query bean providing common features for all root query beans. *

* For each entity bean querybean-generator generates a query bean that extends TQRootBean. - *

* - *

*

Example - usage of QCustomer

*
{@code
  *
@@ -61,7 +58,7 @@ import java.util.stream.Stream;
  * @param  the specific root query bean type (e.g. QCustomer)
  */
 @NonNullApi
-public abstract class TQRootBean {
+public abstract class TQRootBean implements QueryBean {
 
   /**
    * The underlying query.
@@ -146,132 +143,29 @@ public abstract class TQRootBean {
     whereStack.push(filter);
   }
 
-  /**
-   * Return the fetch group.
-   */
+  @Override
   public FetchGroup buildFetchGroup() {
     return ((SpiFetchGroupQuery) query()).buildFetchGroup();
   }
 
-  /**
-   * Return a copy of the query bean.
-   */
-  public abstract R copy();
-
-  /**
-   * Return the underlying query.
-   * 

- * 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(). - *

- */ + @Override public Query query() { return query; } - /** - * Explicitly set a comma delimited list of the properties to fetch on the - * 'main' root level entity bean (aka partial object). Note that '*' means all - * properties. - *

- * You use {@link #fetch(String, String)} to specify specific properties to fetch - * on other non-root level paths of the object graph. - *

- *

- *

{@code
-   *
-   * List customers =
-   *     new QCustomer()
-   *     // Only fetch the customer id, name and status.
-   *     // This is described as a "Partial Object"
-   *     .select("name, status")
-   *     .name.ilike("rob%")
-   *     .findList();
-   *
-   * }
- * - * @param properties the properties to fetch for this bean (* = all properties). - */ + @Override public R select(String properties) { query.select(properties); return root; } - /** - * Set a FetchGroup to control what part of the object graph is loaded. - *

- * FetchGroup is immutable and threadsafe. We expect to create and store - * FetchGroup to a static final field and reuse the instance. - *

- * FetchGroup is an alternative to using select() and fetch() providing a nice - * clean separation between what a query should load and the query predicates. - * - *

{@code
-   *
-   * // immutable threadsafe
-   *
-   * static final FetchGroup fetchGroup =
-   *   QCustomer.forFetchGroup()
-   *     .shippingAddress.fetch()
-   *     .contacts.fetch()
-   *     .buildFetchGroup();
-   *
-   * List customers = new QCustomer()
-   *   .select(fetchGroup)
-   *   .findList();
-   *
-   * }
- * - * - *
{@code
-   *
-   * static final FetchGroup fetchGroup =
-   *   FetchGroup.of(Customer.class)
-   *     .select("name, status")
-   *     .fetch("contacts", "firstName, lastName, email")
-   *     .build();
-   *
-   * List customers = new QCustomer()
-   *   .select(fetchGroup)
-   *   .findList();
-   *
-   * }
- */ + @Override public R select(FetchGroup fetchGroup) { query.select(fetchGroup); return root; } - /** - * Specify the properties to be loaded on the 'main' root level entity bean. - *

- * The resulting entities with be "partially loaded" aka partial objects. - *

- * Alternatively we can use a {@link #select(FetchGroup)} to specify all properties - * to load on all parts of the graph. - * - *

{@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 customers =
-   *     new QCustomer()
-   *       // specify the parts of the graph we want to load
-   *       .select(cust.id, cust.name)
-   *       .contacts.fetch(contact.firstName, contact.lastName, contact.email)
-   *
-   *       // predicates
-   *       .id.gt(1)
-   *       .findList();
-   *
-   * }
- * - * @param properties the list of properties to fetch - */ + @Override @SafeVarargs public final R select(TQProperty... properties) { ((SpiQueryFetch) query).selectProperties(properties(properties)); @@ -286,223 +180,67 @@ public abstract class TQRootBean { return props; } - /** - * Specify the properties to be loaded on the 'main' root level entity bean - * also allowing for functions to be used like {@link StdOperators#max(Query.Property)}. - * - * @param properties the list of properties to fetch - */ + @Override public final R select(Query.Property... properties) { ((SpiQueryFetch) query).selectProperties(properties(properties)); return root; } - /** - * Specify a path to load including all its properties. - * - *
{@code
-   *
-   * List customers =
-   *     new QCustomer()
-   *     // eager fetch the contacts
-   *     .fetch("contacts")
-   *     .findList();
-   *
-   * }
- * - * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean. - */ + @Override public final R fetch(String path) { query.fetch(path); return root; } - /** - * Specify a path to load including all its properties using a "query join". - * - *
{@code
-   *
-   * List customers =
-   *     new QCustomer()
-   *     // eager fetch the contacts using a "query join"
-   *     .fetchQuery("contacts")
-   *     .findList();
-   *
-   * }
- * - * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean. - */ + @Override public final R fetchQuery(String path) { query.fetchQuery(path); return root; } - /** - * Specify a path to load from L2 cache including all its properties. - * - *
{@code
-   *
-   * List orders =
-   *     new QOrder()
-   *     // eager fetch the customer using L2 cache
-   *     .fetchCache("customer")
-   *     .findList();
-   *
-   * }
- * - * @param path the property path to load from L2 cache. - */ + @Override public final R fetchCache(String path) { query.fetchCache(path); return root; } - /** - * Specify a path and properties to load using a "query join". - * - *
{@code
-   *
-   * List customers =
-   *     new QCustomer()
-   *     // eager fetch contacts using a "query join"
-   *     .fetchQuery("contacts", "email, firstName, lastName")
-   *     .findList();
-   *
-   * }
- * - * @param path the property path of an associated (OneToOne, OneToMany, ManyToOne or ManyToMany) bean. - * @param properties the properties to load for this path. - */ + @Override public final R fetchQuery(String path, String properties) { query.fetchQuery(path, properties); return root; } - /** - * Specify a path and properties to load from L2 cache. - * - *
{@code
-   *
-   * List orders =
-   *     new QOrder()
-   *     // eager fetch the customer using L2 cache
-   *     .fetchCache("customer", "name,status")
-   *     .findList();
-   *
-   * }
- * - * @param path the property path to load from L2 cache. - * @param properties the properties to load for this path. - */ + @Override public final 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). - *

- * 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. - *

- *

- *

{@code
-   *
-   * // query orders...
-   * List orders =
-   *     new QOrder()
-   *       .fetch("customer", "name, phoneNumber")
-   *       .fetch("customer.billingAddress", "*")
-   *       .findList();
-   *
-   * }
- *

- * If columns is null or "*" then all columns/properties for that path are fetched. - *

- * - *
{@code
-   *
-   * List customers =
-   *     new QCustomer()
-   *     .select("name, status")
-   *     .fetch("contacts", "firstName,lastName,email")
-   *     .findList();
-   *
-   * }
- * - * @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). - */ + @Override public final 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 customers =
-   *     new QCustomer()
-   *     .select("name, status")
-   *     .fetch("contacts", "firstName,lastName,email", new FetchConfig().lazy(10))
-   *     .findList();
-   *
-   * }
- */ + @Override public final 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 customers =
-   *     new QCustomer()
-   *       // lazy fetch contacts with a batch size of 100
-   *       .fetch("contacts", new FetchConfig().lazy(100))
-   *       .findList();
-   *
-   * }
- */ + @Override public final R fetch(String path, FetchConfig fetchConfig) { query.fetch(path, fetchConfig); return root; } - /** - * Apply the path properties replacing the select and fetch clauses. - *

- * This is typically used when the PathProperties is applied to both the query and the JSON output. - *

- */ + @Override public final R apply(PathProperties pathProperties) { query.apply(pathProperties); return root; } - /** - * Apply changes to the query conditional on the supplied predicate. - *

- * Typically, the changes are extra predicates etc. - * - * @param predicate The predicate which when true the changes are applied - * @param apply The changes to apply to the query - */ + @Override public final R alsoIf(BooleanSupplier predicate, Consumer apply) { if (predicate.getAsBoolean()) { apply.accept(root); @@ -510,929 +248,364 @@ public abstract class TQRootBean { return root; } - /** - * Perform an 'As of' query using history tables to return the object graph - * as of a time in the past. - *

- * To perform this query the DB must have underlying history tables. - *

- * - * @param asOf the date time in the past at which you want to view the data - */ + @Override public final R asOf(Timestamp asOf) { query.asOf(asOf); return root; } - /** - * Execute the query against the draft set of tables. - */ + @Override public final R asDraft() { query.asDraft(); return root; } - /** - * Execute the query including soft deleted rows. - */ + @Override public final R setIncludeSoftDeletes() { query.setIncludeSoftDeletes(); return root; } - /** - * Add an expression to the WHERE or HAVING clause. - */ + @Override public final R add(Expression expression) { peekExprList().add(expression); return root; } - /** - * Set root table alias. - */ + @Override public final R alias(String alias) { query.alias(alias); return root; } - /** - * Set the maximum number of rows to return in the query. - * - * @param maxRows the maximum number of rows to return in the query. - */ + @Override public final R setMaxRows(int maxRows) { query.setMaxRows(maxRows); return root; } - /** - * Set the first row to return for this query. - * - * @param firstRow the first row to include in the query result. - */ + @Override public final R setFirstRow(int firstRow) { query.setFirstRow(firstRow); return root; } - /** - * Execute the query allowing properties with invalid JSON to be collected and not fail the query. - *
{@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 errors = server().getBeanState(bean).getLoadErrors();
-   *
-   *   // If this map is not empty tell we have invalid JSON
-   *   // and should try and fix the JSON content or inform the user
-   *
-   * }
- */ + @Override public final R setAllowLoadErrors() { query.setAllowLoadErrors(); return root; } - /** - * Explicitly specify whether to use AutoTune for this query. - *

- * 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. - *

- */ + @Override public final R setAutoTune(boolean autoTune) { query.setAutoTune(autoTune); return root; } - /** - * A hint which for JDBC translates to the Statement.fetchSize(). - *

- * 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. - *

- */ + @Override public final R setBufferFetchSizeHint(int fetchSize) { query.setBufferFetchSizeHint(fetchSize); return root; } - /** - * Set whether this query uses DISTINCT. - */ + @Override public final R setDistinct(boolean distinct) { query.setDistinct(distinct); return root; } - /** - * Set the index(es) to search for a document store which uses partitions. - *

- * For example, when executing a query against ElasticSearch with daily indexes we can - * explicitly specify the indexes to search against. - *

- *
{@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")
-   *
-   * }
- *

- * 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. - *

- *
{@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 - */ + @Override public final R setDocIndexName(String indexName) { query.setDocIndexName(indexName); return root; } - /** - * Restrict the query to only return subtypes of the given inherit type. - *
{@code
-   *
-   *   List animals =
-   *     new QAnimal()
-   *       .name.startsWith("Fluffy")
-   *       .setInheritType(Cat.class)
-   *       .findList();
-   *
-   * }
- */ + @Override public final R setInheritType(Class type) { query.setInheritType(type); return root; } - /** - * Set the base table to use for this query. - *

- * Typically this is used when a table has partitioning and we wish to specify a specific - * partition/table to query against. - *

- *
{@code
-   *
-   *   QOrder()
-   *   .setBaseTable("order_2019_05")
-   *   .status.equalTo(Status.NEW)
-   *   .findList();
-   *
-   * }
- */ + @Override public final R setBaseTable(String baseTable) { query.setBaseTable(baseTable); return root; } - /** - * Execute the query with the given lock type and WAIT. - *

- * Note that forUpdate() is the same as - * withLock(LockType.UPDATE). - *

- * Provides us with the ability to explicitly use Postgres - * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. - */ + @Override public final R withLock(Query.LockType lockType) { query.withLock(lockType); return root; } - /** - * Execute the query with the given lock type and lock wait. - *

- * Note that forUpdateNoWait() is the same as - * withLock(LockType.UPDATE, LockWait.NOWAIT). - *

- * Provides us with the ability to explicitly use Postgres - * SHARE, KEY SHARE, NO KEY UPDATE and UPDATE row locks. - */ + @Override public final R withLock(Query.LockType lockType, Query.LockWait lockWait) { query.withLock(lockType, lockWait); return root; } - /** - * Add EXISTS sub-query predicate. - */ + @Override public final R exists(Query subQuery) { peekExprList().exists(subQuery); return root; } - /** - * Add NOT EXISTS sub-query predicate. - */ + @Override public final R notExists(Query subQuery) { peekExprList().notExists(subQuery); return root; } - /** - * EXISTS using a SQL SubQuery. - * - * @param sqlSubQuery The SQL SubQuery - * @param bindValues Optional bind values if the SubQuery uses {@code ? } bind values. - */ + @Override public final R exists(String sqlSubQuery, Object... bindValues) { peekExprList().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. - */ + @Override public final R notExists(String sqlSubQuery, Object... bindValues) { peekExprList().notExists(sqlSubQuery, bindValues); return root; } - /** - * Execute using "for update" clause which results in the DB locking the record. - */ + @Override public final R forUpdate() { query.forUpdate(); return root; } - /** - * Execute using "for update" clause with "no wait" option. - *

- * This is typically a Postgres and Oracle only option at this stage. - */ + @Override public final R forUpdateNoWait() { query.forUpdateNoWait(); return root; } - /** - * Execute using "for update" clause with "skip locked" option. - *

- * This is typically a Postgres and Oracle only option at this stage. - *

- */ + @Override public final R forUpdateSkipLocked() { query.forUpdateSkipLocked(); return root; } - /** - * Return this query as an UpdateQuery. - * - *
{@code
-   *
-   *   int rows =
-   *     new QCustomer()
-   *     .name.startsWith("Rob")
-   *     .organisation.id.equalTo(42)
-   *     .asUpdate()
-   *       .set("active", false)
-   *       .update()
-   *
-   * }
- * - * @return This query as an UpdateQuery - */ + @Override public final UpdateQuery asUpdate() { return query.asUpdate(); } - /** - * Convert the query to a DTO bean query. - *

- * We effectively use the underlying ORM query to build the SQL and then execute - * and map it into DTO beans. - */ + @Override public final DtoQuery asDto(Class dtoClass) { return query.asDto(dtoClass); } - /** - * Set the Id value to query. This is used with findOne(). - *

- * You can use this to have further control over the query. For example adding - * fetch joins. - *

- *

- *

{@code
-   *
-   * Order order =
-   *   new QOrder()
-   *     .setId(1)
-   *     .fetch("details")
-   *     .findOne();
-   *
-   * // the order details were eagerly fetched
-   * List details = order.getDetails();
-   *
-   * }
- */ + @Override public final R setId(Object id) { query.setId(id); return root; } - /** - * Set a list of Id values to match. - *

- *

{@code
-   *
-   * List orders =
-   *   new QOrder()
-   *     .setIdIn(42, 43, 44)
-   *     .findList();
-   *
-   * }
- */ + @Override public final R setIdIn(Object... ids) { query.where().idIn(ids); return root; } - /** - * Set a collection of Id values to match. - *

- *

{@code
-   *
-   * Collection ids = ...
-   *
-   * List orders =
-   *   new QOrder()
-   *     .setIdIn(ids)
-   *     .findList();
-   *
-   * }
- */ + @Override public final R setIdIn(Collection ids) { query.where().idIn(ids); return root; } - /** - * Set a label on the query. - *

- * 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. - *

- */ + @Override public final R setLabel(String label) { query.setLabel(label); return root; } - /** - * Set a SQL query hint. - *

- * This results in an inline comment that immediately follows - * after the select keyword in the form: {@code /*+ hint *\/ } - */ + @Override public final R setHint(String hint) { query.setHint(hint); return root; } - /** - * Set the profile location. - *

- * This is typically set automatically via enhancement when profile location enhancement - * is turned on. It is generally not set by application code. - *

- */ + @Override public final R setProfileLocation(ProfileLocation profileLocation) { query.setProfileLocation(profileLocation); return root; } - /** - * Set the default lazy loading batch size to use. - *

- * 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 - */ + @Override public final R setLazyLoadBatchSize(int lazyLoadBatchSize) { query.setLazyLoadBatchSize(lazyLoadBatchSize); return root; } - /** - * Set the property to use as keys for a map. - *

- * If no property is set then the id property is used. - *

- *

- *

{@code
-   *
-   * // Assuming sku is unique for products...
-   *
-   * Map productMap =
-   *     new QProduct()
-   *     // use sku for keys...
-   *     .setMapKey("sku")
-   *     .findMap();
-   *
-   * }
- * - * @param mapKey the property to use as keys for a map. - */ + @Override public final R setMapKey(String mapKey) { query.setMapKey(mapKey); return root; } - /** - * Specify the PersistenceContextScope to use for this query. - *

- * When this is not set the 'default' configured on {@link io.ebean.config.DatabaseConfig#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. - */ + @Override public final R setPersistenceContextScope(PersistenceContextScope scope) { query.setPersistenceContextScope(scope); return root; } - /** - * Set RawSql to use for this query. - */ + @Override public final R setRawSql(RawSql rawSql) { query.setRawSql(rawSql); return root; } - /** - * When set to true when you want the returned beans to be read only. - */ + @Override public final 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. - */ + @Override public final R setUseCache(boolean useCache) { query.setUseCache(useCache); return root; } - /** - * Set the mode to use the bean cache when executing this query. - *

- * 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. - */ + @Override public final R setBeanCacheMode(CacheMode beanCacheMode) { query.setBeanCacheMode(beanCacheMode); return root; } - /** - * Set to true if this query should execute against the doc store. - *

- * When setting this you may also consider disabling lazy loading. - */ + @Override public final R setUseDocStore(boolean useDocStore) { query.setUseDocStore(useDocStore); return root; } - /** - * Set true if you want to disable lazy loading. - *

- * That is, once the object graph is returned further lazy loading is disabled. - */ + @Override public final R setDisableLazyLoading(boolean disableLazyLoading) { query.setDisableLazyLoading(disableLazyLoading); return root; } - /** - * Disable read auditing for this query. - *

- * 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. - */ + @Override public final R setDisableReadAuditing() { query.setDisableReadAuditing(); return root; } - /** - * Set this to true to use the query cache. - */ + @Override public final R setUseQueryCache(boolean useCache) { query.setUseQueryCache(useCache); return root; } - /** - * Set the {@link CacheMode} to use the query for executing this query. - */ + @Override public final R setUseQueryCache(CacheMode cacheMode) { query.setUseQueryCache(cacheMode); return root; } - /** - * Set a timeout on this query. - *

- * 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. - * - * @param secs the query timeout limit in seconds. Zero means there is no limit. - */ + @Override public final R setTimeout(int secs) { query.setTimeout(secs); return root; } - /** - * Returns the set of properties or paths that are unknown (do not map to known properties or paths). - *

- * Validate the query checking the where and orderBy expression paths to confirm if - * they represent valid properties or paths for the given bean type. - */ + @Override public final Set validate() { return query.validate(); } - /** - * Add raw expression with no 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. - *

- *

{@code
-   *
-   *   raw("orderQty < shipQty")
-   *
-   * }
- * - *

Subquery example:

- *
{@code
-   *
-   *   .raw("t0.customer_id in (select customer_id from customer_group where group_id = any(?::uuid[]))", groupIds)
-   *
-   * }
- */ + @Override public final R raw(String rawExpression) { peekExprList().raw(rawExpression); return root; } - /** - * Add raw expression with an array of parameters. - *

- * 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. - */ + @Override public final R raw(String rawExpression, Object... bindValues) { peekExprList().raw(rawExpression, bindValues); return root; } - /** - * Only add the raw expression if the values is not null or empty. - *

- * 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. - *

- *

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();
-   *
-   * }
- *

- * Note that we need to cast the Postgres array for UUID types like: - *

- *
{@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. - */ + @Override public final R rawOrEmpty(String raw, Collection values) { peekExprList().rawOrEmpty(raw, values); return root; } - /** - * Add raw expression with a single parameter. - *

- * 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. - *

- *

- *

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)
-   *
-   * }
- */ + @Override public final R raw(String rawExpression, Object bindValue) { peekExprList().raw(rawExpression, bindValue); return root; } - /** - * In expression using multiple columns. - */ + @Override public final R inTuples(InTuples inTuples) { peekExprList().inTuples(inTuples); 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 orders =
-   *          new QOrder()
-   *            .customer.name.ilike("rob")
-   *            .orderBy()
-   *              .customer.name.asc()
-   *              .orderDate.asc()
-   *            .findList();
-   *
-   * }
- */ + @Override public final 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; } - /** - * @deprecated migrate to {@link #orderBy()}. - */ + @Override @Deprecated(since = "13.19", forRemoval = true) public final R order() { 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. - */ + @Override public final R orderBy(String orderByClause) { query.orderBy(orderByClause); return root; } - /** - * @deprecated migrate to {@link #orderBy(String)} - */ + @Override @Deprecated(since = "13.19", forRemoval = true) public final R order(String orderByClause) { return orderBy(orderByClause); } - /** - * Begin a list of expressions added by 'OR'. - *

- * Use endOr() or endJunction() to stop added to OR and 'pop' to the parent expression list. - *

- *

- *

Example

- *

- * This example uses an 'OR' expression list with an inner 'AND' expression list. - *

- *
{@code
-   *
-   *    List customers =
-   *          new QCustomer()
-   *            .status.equalTo(Customer.Status.GOOD)
-   *            .or()
-   *              .id.greaterThan(1000)
-   *              .and()
-   *                .name.startsWith("super")
-   *                .registered.after(fiveDaysAgo)
-   *              .endAnd()
-   *            .endOr()
-   *            .orderBy().id.desc()
-   *            .findList();
-   *
-   * }
- *

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)
-   *
-   * }
- */ + @Override public final R or() { pushExprList(peekExprList().or()); return root; } - /** - * Begin a list of expressions added by 'AND'. - *

- * 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. - *

- *

Example

- *

- * This example uses an 'OR' expression list with an inner 'AND' expression list. - *

- *
{@code
-   *
-   *    List customers =
-   *          new QCustomer()
-   *            .status.equalTo(Customer.Status.GOOD)
-   *            .or() // OUTER 'OR'
-   *              .id.greaterThan(1000)
-   *              .and()  // NESTED 'AND' expression list
-   *                .name.startsWith("super")
-   *                .registered.after(fiveDaysAgo)
-   *                .endAnd()
-   *              .endOr()
-   *            .orderBy().id.desc()
-   *            .findList();
-   *
-   * }
- *

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)
-   *
-   * }
- */ + @Override public final R and() { pushExprList(peekExprList().and()); return root; } - /** - * Begin a list of expressions added by NOT. - *

- * Use endNot() or endJunction() to stop added to NOT and 'pop' to the parent expression list. - */ + @Override public final R not() { pushExprList(peekExprList().not()); return root; } - /** - * Begin a list of expressions added by MUST. - *

- * This automatically makes this query a document store query. - *

- * Use endJunction() to stop added to MUST and 'pop' to the parent expression list. - */ + @Override public final R must() { pushExprList(peekExprList().must()); return root; } - /** - * Begin a list of expressions added by MUST NOT. - *

- * This automatically makes this query a document store query. - *

- * Use endJunction() to stop added to MUST NOT and 'pop' to the parent expression list. - */ + @Override public final R mustNot() { return pushExprList(peekExprList().mustNot()); } - /** - * Begin a list of expressions added by SHOULD. - *

- * This automatically makes this query a document store query. - *

- * Use endJunction() to stop added to SHOULD and 'pop' to the parent expression list. - */ + @Override public final R should() { return pushExprList(peekExprList().should()); } - /** - * End a list of expressions added by 'OR'. - */ + @Override public final R endJunction() { if (textMode) { textStack.pop(); @@ -1442,23 +615,17 @@ public abstract class TQRootBean { return root; } - /** - * End OR junction - synonym for endJunction(). - */ + @Override public final R endOr() { return endJunction(); } - /** - * End AND junction - synonym for endJunction(). - */ + @Override public final R endAnd() { return endJunction(); } - /** - * End NOT junction - synonym for endJunction(). - */ + @Override public final R endNot() { return endJunction(); } @@ -1475,628 +642,199 @@ public abstract class TQRootBean { return root; } - /** - * Add expression after this to the WHERE 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. - */ + @Override public final R where() { textMode = false; return root; } - /** - * Begin added expressions to the 'Text' expression list. - *

- * 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'. - */ + @Override public final R text() { textMode = true; return root; } - /** - * Add a Text Multi-match expression (document store only). - *

- * This automatically makes the query a document store query. - */ + @Override public final R multiMatch(String query, MultiMatch multiMatch) { peekExprList().multiMatch(query, multiMatch); return root; } - /** - * Add a Text Multi-match expression (document store only). - *

- * This automatically makes the query a document store query. - */ + @Override public final R multiMatch(String query, String... properties) { peekExprList().multiMatch(query, properties); return root; } - /** - * Add a Text common terms expression (document store only). - *

- * This automatically makes the query a document store query. - */ + @Override public final R textCommonTerms(String query, TextCommonTerms options) { peekExprList().textCommonTerms(query, options); return root; } - /** - * Add a Text simple expression (document store only). - *

- * This automatically makes the query a document store query. - */ + @Override public final R textSimple(String query, TextSimple options) { peekExprList().textSimple(query, options); return root; } - /** - * Add a Text query string expression (document store only). - *

- * This automatically makes the query a document store query. - */ + @Override public final R textQueryString(String query, TextQueryString options) { peekExprList().textQueryString(query, options); return root; } - /** - * Execute the query using the given transaction. - */ + @Override public final R usingTransaction(Transaction transaction) { query.usingTransaction(transaction); return root; } - /** - * Execute the query using the given connection. - */ + @Override public final R usingConnection(Connection connection) { query.usingConnection(connection); return root; } - /** - * Ensure that the master DataSource is used if there is a read only data source - * being used (that is using a read replica database potentially with replication lag). - *

- * When the database is configured with a read-only DataSource via - * say {@link io.ebean.config.DatabaseConfig#setReadOnlyDataSource(DataSource)} then - * by default when a query is run without an active transaction, it uses the read-only data - * source. We we use {@code usingMaster()} to instead ensure that the query is executed - * against the master data source. - */ + @Override public final R usingMaster() { query.usingMaster(); return root; } - /** - * Execute the query returning true if a row is found. - *

- * 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. - * - *

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 - */ + @Override public final boolean exists() { return query.exists(); } - /** - * Execute the query returning either a single bean or null (if no matching - * bean is found). - *

- * 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. - *

- *

{@code
-   *
-   * // assuming the sku of products is unique...
-   * Product product =
-   *     new QProduct()
-   *         .sku.equalTo("aa113")
-   *         .findOne();
-   * ...
-   * }
- *

- * It is also useful with finding objects by their id when you want to specify - * further join information to optimise the query. - *

- *

{@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 details = order.getDetails();
-   * ...
-   * }
- */ + @Override @Nullable public final T findOne() { return query.findOne(); } - /** - * Execute the query returning an optional bean. - */ + @Override public final Optional findOneOrEmpty() { return query.findOneOrEmpty(); } - /** - * Execute the query returning the list of objects. - *

- * This query will execute against the EbeanServer that was used to create it. - *

- *

{@code
-   *
-   * List customers =
-   *     new QCustomer()
-   *       .name.ilike("rob%")
-   *       .findList();
-   *
-   * }
- * - * @see Query#findList() - */ + @Override public final List findList() { return query.findList(); } - /** - * Execute the query returning the result as a Stream. - *

- * Note that this can support very large queries iterating - * any number of results. To do so internally it can use - * multiple persistence contexts. - *

- *
{@code
-   *
-   *  // use try with resources to ensure Stream is closed
-   *
-   *  try (Stream stream = query.findStream()) {
-   *    stream
-   *    .map(...)
-   *    .collect(...);
-   *  }
-   *
-   * }
- */ + @Override public final Stream findStream() { return query.findStream(); } - /** - * Execute the query returning the set of objects. - *

- * This query will execute against the EbeanServer that was used to create it. - *

- *

{@code
-   *
-   * Set customers =
-   *     new QCustomer()
-   *       .name.ilike("rob%")
-   *       .findSet();
-   *
-   * }
- * - * @see Query#findSet() - */ + @Override public final Set findSet() { return query.findSet(); } - /** - * Execute the query returning the list of Id's. - *

- * This query will execute against the EbeanServer that was used to create it. - * - * @see Query#findIds() - */ + @Override public final List findIds() { return query.findIds(); } - /** - * Execute the query returning a map of the objects. - *

- * 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. - *

- *

{@code
-   *
-   * Map map =
-   *   new QProduct()
-   *     .sku.asMapKey()
-   *     .findMap();
-   *
-   * }
- * - * @see Query#findMap() - */ + @Override public final Map findMap() { return query.findMap(); } - /** - * Execute the query iterating over the results. - *

- * 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. - *

- *
{@code
-   *
-   *  Query query =
-   *    new QCustomer()
-   *     .status.equalTo(Customer.Status.NEW)
-   *     .orderBy()
-   *       id.asc()
-   *     .query();
-   *
-   *  try (QueryIterator it = query.findIterate()) {
-   *    while (it.hasNext()) {
-   *      Customer customer = it.next();
-   *      // do something with customer ...
-   *    }
-   *  }
-   *
-   * }
- */ + @Override public final QueryIterator findIterate() { return query.findIterate(); } - /** - * Execute the query returning a list of values for a single property. - *

- *

Example

- *
{@code
-   *
-   *  List names =
-   *    new QCustomer()
-   *      .setDistinct(true)
-   *      .select(name)
-   *      .findSingleAttributeList();
-   *
-   * }
- * - * @return the list of values for the selected property - */ + @Override public final
List findSingleAttributeList() { return query.findSingleAttributeList(); } - /** - * Execute the query returning a single value or null for a single property. - *

- *

Example

- *
{@code
-   *
-   *  LocalDate maxDate =
-   *    new QCustomer()
-   *      .select("max(startDate)")
-   *      .findSingleAttribute();
-   *
-   * }
- * - * @return a single value or null for the selected property - */ + @Override @Nullable public final
A findSingleAttribute() { return query.findSingleAttribute(); } - /** - * Execute the query returning a single optional attribute value. - *

- *

Example

- *
{@code
-   *
-   *  Optional maybeName =
-   *    new QCustomer()
-   *      .select(name)
-   *      .id.eq(42)
-   *      .status.eq(NEW)
-   *      .findSingleAttributeOrEmpty();
-   *
-   * }
- * - * @return an optional value for the selected property - */ + @Override public final
Optional findSingleAttributeOrEmpty() { return query.findSingleAttributeOrEmpty(); } - /** - * Execute the query processing the beans one at a time. - *

- * 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 Consumer interface which is better suited to use with closures. - * - *

{@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. - */ + @Override public final void findEach(Consumer consumer) { query.findEach(consumer); } - /** - * Execute findEach streaming query batching the results for consuming. - *

- * This query execution will stream the results and is suited to consuming - * large numbers of results from the database. - *

- * Typically we use this batch consumer when we want to do further processing on - * the beans and want to do that processing in batch form, for example - 100 at - * a time. - * - * @param batch The number of beans processed in the batch - * @param consumer Process the batch of beans - */ + @Override public final void findEach(int batch, Consumer> consumer) { query.findEach(batch, consumer); } - /** - * Execute the query using callbacks to a visitor to process the resulting - * beans one at a time. - *

- * This method is functionally equivalent to findIterate() but instead of using an - * iterator uses the Predicate interface which is better suited to use with closures. - * - *

{@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. - */ + @Override public final void findEachWhile(Predicate consumer) { query.findEachWhile(consumer); } - /** - * Return versions of a @History entity 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. - */ + @Override public final List> findVersions() { return query.findVersions(); } - /** - * Return versions of a @History entity bean between a start and end timestamp. - *

- * 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. - */ + @Override public final List> findVersionsBetween(Timestamp start, Timestamp end) { return query.findVersionsBetween(start, end); } - /** - * Return the count of entities this query should return. - *

- * This is the number of 'top level' or 'root level' entities. - */ + @Override public final int findCount() { return query.findCount(); } - /** - * Execute find row count query in a background thread. - *

- * 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). - * - * @return a Future object for the row count query - */ + @Override public final FutureRowCount findFutureCount() { return query.findFutureCount(); } - /** - * Execute find Id's query in a background thread. - *

- * 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). - * - * @return a Future object for the list of Id's - */ + @Override public final FutureIds findFutureIds() { return query.findFutureIds(); } - /** - * Execute find list query in a background thread. - *

- * 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. - * - * @return a Future object for the list result of the query - */ + @Override public final FutureList findFutureList() { return query.findFutureList(); } - /** - * Return a PagedList for this query using firstRow and maxRows. - *

- * 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. - *

- *

{@code
-   *
-   *  PagedList pagedList =
-   *    new QOrder()
-   *       .setFirstRow(50)
-   *       .setMaxRows(20)
-   *       .findPagedList();
-   *
-   *       // fetch the total row count in the background
-   *       pagedList.loadRowCount();
-   *
-   *       List orders = pagedList.getList();
-   *       int totalRowCount = pagedList.getTotalRowCount();
-   *
-   * }
- * - * @return The PagedList - */ + @Override public final PagedList findPagedList() { return query.findPagedList(); } - /** - * Execute as a delete query deleting the 'root level' beans that match the predicates - * in the query. - *

- * Note that if the query includes joins then the generated delete statement may not be - * optimal depending on the database platform. - * - * @return the number of beans/rows that were deleted. - */ + @Override public final int delete() { return query.delete(); } - /** - * Return the sql that was generated for executing this query. - *

- * This is only available after the query has been executed and provided only - * for informational purposes. - */ + @Override public final String getGeneratedSql() { return query.getGeneratedSql(); } - /** - * Return the type of beans being queried. - */ + @Override public final Class getBeanType() { return query.getBeanType(); } - /** - * Return the expression list that has been built for this query. - */ + @Override public final ExpressionList getExpressionList() { return query.where(); } - /** - * Start adding expressions to the having clause when using @Aggregation properties. - * - *

{@code
-   *
-   *   new QMachineUse()
-   *   // where ...
-   *   .date.inRange(fromDate, toDate)
-   *
-   *   .having()
-   *   .sumHours.greaterThan(1)
-   *   .findList()
-   *
-   *   // The sumHours property uses @Aggregation
-   *   // e.g. @Aggregation("sum(hours)")
-   *
-   * }
- */ + @Override public final R having() { if (whereStack == null) { whereStack = new ArrayStack<>(); @@ -2107,31 +845,7 @@ public abstract class TQRootBean { return root; } - /** - * Return the underlying having clause to typically when using dynamic aggregation formula. - *

- * Note that after this we no longer have the query bean so typically we use this right - * at the end of the query. - * - *

{@code
-   *
-   *  // sum(distanceKms) ... is a "dynamic formula"
-   *  // so we use havingClause() for it like:
-   *
-   *  List machineUse =
-   *
-   *    new QMachineUse()
-   *      .select("machine, sum(fuelUsed), sum(distanceKms)")
-   *
-   *      // where ...
-   *      .date.greaterThan(LocalDate.now().minusDays(7))
-   *
-   *      .havingClause()
-   *        .gt("sum(distanceKms)", 2)
-   *        .findList();
-   *
-   * }
- */ + @Override public final ExpressionList havingClause() { return query.having(); }