/*****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
****************************************************************/
package org.apache.cayenne.query;
import org.apache.cayenne.DataRow;
import org.apache.cayenne.ObjectContext;
import org.apache.cayenne.exp.Expression;
import org.apache.cayenne.exp.ExpressionFactory;
import org.apache.cayenne.exp.Property;
import org.apache.cayenne.map.DbEntity;
import org.apache.cayenne.map.EntityResolver;
import org.apache.cayenne.map.ObjEntity;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* A selecting query providing chainable API. This is an alternative to
* {@link SelectQuery} when you want to use a fluent API. For example, the following
* is a convenient way to return a record:
* <pre>
* {@code
* Artist a = ObjectSelect
* .query(Artist.class)
* .where(Artist.NAME.eq("Picasso"))
* .selectOne(context);
* }
* </pre>
*
* @since 4.0
*/
public class ObjectSelect<T> extends FluentSelect<T> {
private static final long serialVersionUID = -156124021150949227L;
protected boolean fetchingDataRows;
/**
* Creates a ObjectSelect that selects objects of a given persistent class.
*/
public static <T> ObjectSelect<T> query(Class<T> entityType) {
return new ObjectSelect<T>().entityType(entityType);
}
/**
* Creates a ObjectSelect that selects objects of a given persistent class
* and uses provided expression for its qualifier.
*/
public static <T> ObjectSelect<T> query(Class<T> entityType, Expression expression) {
return new ObjectSelect<T>().entityType(entityType).where(expression);
}
/**
* Creates a ObjectSelect that selects objects of a given persistent class
* and uses provided expression for its qualifier.
*/
public static <T> ObjectSelect<T> query(Class<T> entityType, Expression expression, List<Ordering> orderings) {
return new ObjectSelect<T>().entityType(entityType).where(expression).orderBy(orderings);
}
/**
* Creates a ObjectSelect that fetches data for an {@link ObjEntity}
* determined from a provided class.
*/
public static ObjectSelect<DataRow> dataRowQuery(Class<?> entityType) {
return query(entityType).fetchDataRows();
}
/**
* Creates a ObjectSelect that fetches data for an {@link ObjEntity}
* determined from a provided class and uses provided expression for its
* qualifier.
*/
public static ObjectSelect<DataRow> dataRowQuery(Class<?> entityType, Expression expression) {
return query(entityType).fetchDataRows().where(expression);
}
/**
* Creates a ObjectSelect that fetches data for {@link ObjEntity} determined
* from provided "entityName", but fetches the result of a provided type.
* This factory method is most often used with generic classes that by
* themselves are not enough to resolve the entity to fetch.
*/
public static <T> ObjectSelect<T> query(Class<T> resultType, String entityName) {
return new ObjectSelect<T>().entityName(entityName);
}
/**
* Creates a ObjectSelect that fetches DataRows for a {@link DbEntity}
* determined from provided "dbEntityName".
*/
public static ObjectSelect<DataRow> dbQuery(String dbEntityName) {
return new ObjectSelect<DataRow>().fetchDataRows().dbEntityName(dbEntityName);
}
/**
* Creates a ObjectSelect that fetches DataRows for a {@link DbEntity}
* determined from provided "dbEntityName" and uses provided expression for
* its qualifier.
*
* @return this object
*/
public static ObjectSelect<DataRow> dbQuery(String dbEntityName, Expression expression) {
return new ObjectSelect<DataRow>().fetchDataRows().dbEntityName(dbEntityName).where(expression);
}
/**
* Creates a ColumnSelect that will fetch single property that can be resolved
* against a given {@link ObjEntity} class.
*
* @param entityType base persistent class that will be used as a root for this query
* @param column single column to select
*/
public static <E> ColumnSelect<E> columnQuery(Class<?> entityType, Property<E> column) {
return new ColumnSelect<>().entityType(entityType).column(column);
}
/**
* Creates a ColumnSelect that will fetch multiple columns of a given {@link ObjEntity}
*
* @param entityType base persistent class that will be used as a root for this query
* @param firstColumn column to select
* @param otherColumns columns to select
*/
public static ColumnSelect<Object[]> columnQuery(Class<?> entityType, Property<?> firstColumn, Property<?>... otherColumns) {
return new ColumnSelect<Object[]>().entityType(entityType).columns(firstColumn, otherColumns);
}
protected ObjectSelect() {
}
/**
* Translates self to a SelectQuery.
*/
@SuppressWarnings({"deprecation", "unchecked"})
@Override
protected Query createReplacementQuery(EntityResolver resolver) {
SelectQuery<?> replacement = (SelectQuery<?>) super.createReplacementQuery(resolver);
replacement.setFetchingDataRows(fetchingDataRows);
return replacement;
}
/**
* Sets the type of the entity to fetch without changing the return type of
* the query.
*
* @return this object
*/
public ObjectSelect<T> entityType(Class<?> entityType) {
return resetEntity(entityType, null, null);
}
/**
* Sets the {@link ObjEntity} name to fetch without changing the return type
* of the query. This form is most often used for generic entities that
* don't map to a distinct class.
*
* @return this object
*/
public ObjectSelect<T> entityName(String entityName) {
return resetEntity(null, entityName, null);
}
/**
* Sets the {@link DbEntity} name to fetch without changing the return type
* of the query. This form is most often used for generic entities that
* don't map to a distinct class.
*
* @return this object
*/
public ObjectSelect<T> dbEntityName(String dbEntityName) {
return resetEntity(null, null, dbEntityName);
}
private ObjectSelect<T> resetEntity(Class<?> entityType, String entityName, String dbEntityName) {
this.entityType = entityType;
this.entityName = entityName;
this.dbEntityName = dbEntityName;
return this;
}
/**
* Appends a qualifier expression of this query. An equivalent to
* {@link #and(Expression...)} that can be used a syntactic sugar.
*
* @return this object
*/
public ObjectSelect<T> where(Expression expression) {
return and(expression);
}
/**
* Appends a qualifier expression of this query, using provided expression
* String and an array of position parameters. This is an equivalent to
* calling "and".
*
* @return this object
*/
public ObjectSelect<T> where(String expressionString, Object... parameters) {
return and(ExpressionFactory.exp(expressionString, parameters));
}
/**
* AND's provided expressions to the existing WHERE clause expression.
*
* @return this object
*/
public ObjectSelect<T> and(Expression... expressions) {
if (expressions == null || expressions.length == 0) {
return this;
}
return and(Arrays.asList(expressions));
}
/**
* AND's provided expressions to the existing WHERE clause expression.
*
* @return this object
*/
public ObjectSelect<T> and(Collection<Expression> expressions) {
if (expressions == null || expressions.isEmpty()) {
return this;
}
Collection<Expression> all;
if (where != null) {
all = new ArrayList<>(expressions.size() + 1);
all.add(where);
all.addAll(expressions);
} else {
all = expressions;
}
where = ExpressionFactory.and(all);
return this;
}
/**
* OR's provided expressions to the existing WHERE clause expression.
*
* @return this object
*/
public ObjectSelect<T> or(Expression... expressions) {
if (expressions == null || expressions.length == 0) {
return this;
}
return or(Arrays.asList(expressions));
}
/**
* OR's provided expressions to the existing WHERE clause expression.
*
* @return this object
*/
public ObjectSelect<T> or(Collection<Expression> expressions) {
if (expressions == null || expressions.isEmpty()) {
return this;
}
Collection<Expression> all;
if (where != null) {
all = new ArrayList<>(expressions.size() + 1);
all.add(where);
all.addAll(expressions);
} else {
all = expressions;
}
where = ExpressionFactory.or(all);
return this;
}
/**
* Add an ascending ordering on the given property. If there is already an ordering
* on this query then add this ordering with a lower priority.
*
* @param property the property to sort on
* @return this object
*/
public ObjectSelect<T> orderBy(String property) {
return orderBy(new Ordering(property));
}
/**
* Add an ordering on the given property. If there is already an ordering
* on this query then add this ordering with a lower priority.
*
* @param property the property to sort on
* @param sortOrder the direction of the ordering
* @return this object
*/
public ObjectSelect<T> orderBy(String property, SortOrder sortOrder) {
return orderBy(new Ordering(property, sortOrder));
}
/**
* Add one or more orderings to this query.
*
* @return this object
*/
public ObjectSelect<T> orderBy(Ordering... orderings) {
if (orderings == null) {
return this;
}
if (this.orderings == null) {
this.orderings = new ArrayList<>(orderings.length);
}
Collections.addAll(this.orderings, orderings);
return this;
}
/**
* Adds a list of orderings to this query.
*
* @return this object
*/
public ObjectSelect<T> orderBy(Collection<Ordering> orderings) {
if (orderings == null) {
return this;
}
if (this.orderings == null) {
this.orderings = new ArrayList<>(orderings.size());
}
this.orderings.addAll(orderings);
return this;
}
/**
* Merges prefetch into the query prefetch tree.
*
* @return this object
*/
public ObjectSelect<T> prefetch(PrefetchTreeNode prefetch) {
if (prefetch == null) {
return this;
}
if (prefetches == null) {
prefetches = new PrefetchTreeNode();
}
prefetches.merge(prefetch);
return this;
}
/**
* Merges a prefetch path with specified semantics into the query prefetch
* tree.
*
* @return this object
*/
public ObjectSelect<T> prefetch(String path, int semantics) {
if (path == null) {
return this;
}
if (prefetches == null) {
prefetches = new PrefetchTreeNode();
}
prefetches.addPath(path).setSemantics(semantics);
return this;
}
/**
* Resets query fetch limit - a parameter that defines max number of objects
* that should be ever be fetched from the database.
*/
@SuppressWarnings("unchecked")
public ObjectSelect<T> limit(int fetchLimit) {
if (this.limit != fetchLimit) {
this.limit = fetchLimit;
this.replacementQuery = null;
}
return this;
}
/**
* Resets query fetch offset - a parameter that defines how many objects
* should be skipped when reading data from the database.
*/
public ObjectSelect<T> offset(int fetchOffset) {
if (this.offset != fetchOffset) {
this.offset = fetchOffset;
this.replacementQuery = null;
}
return this;
}
/**
* Resets query page size. A non-negative page size enables query result
* pagination that saves memory and processing time for large lists if only
* parts of the result are ever going to be accessed.
*/
public ObjectSelect<T> pageSize(int pageSize) {
if (this.pageSize != pageSize) {
this.pageSize = pageSize;
this.replacementQuery = null;
}
return this;
}
/**
* Sets fetch size of the PreparedStatement generated for this query. Only
* non-negative values would change the default size.
*
* @see Statement#setFetchSize(int)
*/
public ObjectSelect<T> statementFetchSize(int size) {
if (this.statementFetchSize != size) {
this.statementFetchSize = size;
this.replacementQuery = null;
}
return this;
}
public ObjectSelect<T> cacheStrategy(QueryCacheStrategy strategy) {
if (this.cacheStrategy != strategy) {
this.cacheStrategy = strategy;
this.replacementQuery = null;
}
if(this.cacheGroup != null) {
this.cacheGroup = null;
this.replacementQuery = null;
}
return this;
}
public ObjectSelect<T> cacheStrategy(QueryCacheStrategy strategy, String cacheGroup) {
return cacheStrategy(strategy).cacheGroup(cacheGroup);
}
public ObjectSelect<T> cacheGroup(String cacheGroup) {
this.cacheGroup = cacheGroup;
this.replacementQuery = null;
return this;
}
/**
* Instructs Cayenne to look for query results in the "local" cache when
* running the query. This is a short-hand notation for:
* <p>
* <pre>
* query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
* </pre>
*/
public ObjectSelect<T> localCache(String cacheGroup) {
return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE, cacheGroup);
}
/**
* Instructs Cayenne to look for query results in the "local" cache when
* running the query. This is a short-hand notation for:
* <p>
* <pre>
* query.cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
* </pre>
*/
public ObjectSelect<T> localCache() {
return cacheStrategy(QueryCacheStrategy.LOCAL_CACHE);
}
/**
* Instructs Cayenne to look for query results in the "shared" cache when
* running the query. This is a short-hand notation for:
* <p>
* <pre>
* query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
* </pre>
*/
public ObjectSelect<T> sharedCache(String cacheGroup) {
return cacheStrategy(QueryCacheStrategy.SHARED_CACHE, cacheGroup);
}
/**
* Instructs Cayenne to look for query results in the "shared" cache when
* running the query. This is a short-hand notation for:
* <p>
* <pre>
* query.cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
* </pre>
*/
public ObjectSelect<T> sharedCache() {
return cacheStrategy(QueryCacheStrategy.SHARED_CACHE);
}
/**
* Forces query to fetch DataRows. This automatically changes whatever
* result type was set previously to "DataRow".
*
* @return this object
*/
@SuppressWarnings("unchecked")
public ObjectSelect<DataRow> fetchDataRows() {
this.fetchingDataRows = true;
return (ObjectSelect<DataRow>) this;
}
/**
* <p>Select only specific properties.</p>
* <p>Can be any properties that can be resolved against root entity type
* (root entity's properties, function call expressions, properties of relationships, etc).</p>
* <p>
* <pre>
* {@code
* List<Object[]> columns = ObjectSelect.query(Artist.class)
* .columns(Artist.ARTIST_NAME, Artist.DATE_OF_BIRTH)
* .select(context);
* }
* </pre>
*
* @param properties array of properties to select
* @see ObjectSelect#column(Property)
*/
public ColumnSelect<Object[]> columns(Property<?> firstProperty, Property<?>... properties) {
return new ColumnSelect<>(this).columns(firstProperty, properties);
}
/**
* <p>Select one specific property.</p>
* <p>Can be any property that can be resolved against root entity type
* (root entity's property, function call expression, property of relationships, etc)</p>
* <p>If you need several columns use {@link ObjectSelect#columns(Property, Property[])} method.</p>
* <p>
* <pre>
* {@code
* List<String> names = ObjectSelect.query(Artist.class)
* .column(Artist.ARTIST_NAME)
* .select(context);
* }
* </pre>
* </p>
* @param property single property to select
* @see ObjectSelect#columns(Property, Property[])
*/
public <E> ColumnSelect<E> column(Property<E> property) {
return new ColumnSelect<>(this).column(property);
}
/**
* Select COUNT(*)
* @see ObjectSelect#column(Property)
*/
public ColumnSelect<Long> count() {
return column(Property.COUNT);
}
/**
* <p>Select COUNT(property)</p>
* <p>Can return different result than COUNT(*) as it will count only non null values</p>
* @see ObjectSelect#count()
* @see ObjectSelect#column(Property)
*/
public ColumnSelect<Long> count(Property<?> property) {
return column(property.count());
}
/**
* <p>Select minimum value of property</p>
* @see ObjectSelect#column(Property)
*/
public <E> ColumnSelect<E> min(Property<E> property) {
return column(property.min());
}
/**
* <p>Select maximum value of property</p>
* @see ObjectSelect#column(Property)
*/
public <E> ColumnSelect<E> max(Property<E> property) {
return column(property.max());
}
/**
* <p>Select average value of property</p>
* @see ObjectSelect#column(Property)
*/
public <E> ColumnSelect<E> avg(Property<E> property) {
return column(property.avg());
}
/**
* <p>Select sum of values</p>
* @see ObjectSelect#column(Property)
*/
public <E extends Number> ColumnSelect<E> sum(Property<E> property) {
return column(property.sum());
}
/**
* <p>Quick way to select count of records</p>
* <p>Usage:
* <pre>
* {@code
* long count = ObjectSelect.query(Artist.class)
* .where(Artist.ARTIST_NAME.like("a%"))
* .selectCount(context);
* }
* </pre>
* </p>
* @param context to perform query
* @return count of rows
*/
public long selectCount(ObjectContext context) {
return count().selectOne(context);
}
@Override
public T selectFirst(ObjectContext context) {
return context.selectFirst(limit(1));
}
public boolean isFetchingDataRows() {
return fetchingDataRows;
}
}