/*****************************************************************
* 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.ResultBatchIterator;
import org.apache.cayenne.ResultIterator;
import org.apache.cayenne.ResultIteratorCallback;
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.MapLoader;
import org.apache.cayenne.map.ObjEntity;
import org.apache.cayenne.map.Procedure;
import org.apache.cayenne.util.XMLEncoder;
import org.apache.cayenne.util.XMLSerializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* A query that selects persistent objects of a certain type or "raw data" (aka
* DataRows). Supports expression qualifier, multiple orderings and a number of
* other parameters that serve as runtime hints to Cayenne on how to optimize
* the fetch and result processing.
*/
public class SelectQuery<T> extends AbstractQuery implements ParameterizedQuery, XMLSerializable, Select<T> {
private static final long serialVersionUID = 5486418811888197559L;
public static final String DISTINCT_PROPERTY = "cayenne.SelectQuery.distinct";
public static final boolean DISTINCT_DEFAULT = false;
protected Expression qualifier;
protected List<Ordering> orderings;
protected boolean distinct;
/**
* @since 4.0
*/
protected Collection<Property<?>> columns;
/**
* @since 4.0
*/
protected Expression havingQualifier;
/**
* <p>Flag that indicates whether this query can return single value or
* it should always return some complex data (Object[] for now)</p>
* <p>Default value is <b>true</b></p>
* @since 4.0
*/
protected boolean canReturnScalarValue = true;
SelectQueryMetadata metaData = new SelectQueryMetadata();
/**
* Creates a SelectQuery that selects objects of a given persistent class.
*
* @param rootClass
* the Class of objects fetched by this query.
*
* @since 4.0
*/
public static <T> SelectQuery<T> query(Class<T> rootClass) {
return new SelectQuery<T>(rootClass);
}
/**
* Creates a SelectQuery that selects objects of a given persistent class
* that match supplied qualifier.
*
* @param rootClass
* the Class of objects fetched by this query.
* @param qualifier
* an Expression indicating which objects should be fetched.
*
* @since 4.0
*/
public static <T> SelectQuery<T> query(Class<T> rootClass, Expression qualifier) {
return new SelectQuery<T>(rootClass, qualifier);
}
/**
* Creates a SelectQuery that selects objects of a given persistent class
* that match supplied qualifier.
*
* @param rootClass
* the Class of objects fetched by this query.
* @param qualifier
* an Expression indicating which objects should be fetched.
* @param orderings
* defines how to order the results, may be null.
*
* @since 4.0
*/
public static <T> SelectQuery<T> query(Class<T> rootClass, Expression qualifier, List<? extends Ordering> orderings) {
return new SelectQuery<T>(rootClass, qualifier, orderings);
}
/**
* @since 4.0
*/
public static SelectQuery<DataRow> dataRowQuery(Class<?> rootClass) {
// create a query replica that would fetch DataRows
SelectQuery<DataRow> query = new SelectQuery<DataRow>();
query.setRoot(rootClass);
query.metaData.setFetchingDataRows(true);
return query;
}
/**
* Creates a SelectQuery that selects DataRows that correspond to a given
* persistent class that match supplied qualifier.
*
* @param rootClass
* the Class of objects that correspond to DataRows entity.
* @param qualifier
* an Expression indicating which objects should be fetched.
*
* @since 4.0
*/
public static SelectQuery<DataRow> dataRowQuery(Class<?> rootClass, Expression qualifier) {
SelectQuery<DataRow> query = dataRowQuery(rootClass);
query.setQualifier(qualifier);
return query;
}
/**
* @since 4.0
*/
public static SelectQuery<DataRow> dataRowQuery(Class<?> rootClass, Expression qualifier, List<Ordering> orderings) {
SelectQuery<DataRow> query = dataRowQuery(rootClass, qualifier);
query.addOrderings(orderings);
return query;
}
/** Creates an empty SelectQuery. */
public SelectQuery() {
}
/**
* Creates a SelectQuery with null qualifier, for the specifed ObjEntity
*
* @param root
* the ObjEntity this SelectQuery is for.
*/
public SelectQuery(ObjEntity root) {
this(root, null);
}
/**
* Creates a SelectQuery for the specified ObjEntity with the given
* qualifier.
*
* @param root
* the ObjEntity this SelectQuery is for.
* @param qualifier
* an Expression indicating which objects should be fetched
*/
public SelectQuery(ObjEntity root, Expression qualifier) {
this(root, qualifier, null);
}
/**
* Creates a SelectQuery for the specified ObjEntity with the given
* qualifier and orderings.
*
* @param root
* the ObjEntity this SelectQuery is for.
* @param qualifier
* an Expression indicating which objects should be fetched.
* @param orderings
* defines how to order the results, may be null.
* @since 3.1
*/
public SelectQuery(ObjEntity root, Expression qualifier, List<? extends Ordering> orderings) {
this();
this.init(root, qualifier);
addOrderings(orderings);
}
/**
* Creates a SelectQuery that selects all objects of a given persistent
* class.
*
* @param rootClass
* the Class of objects fetched by this query.
*/
public SelectQuery(Class<T> rootClass) {
this(rootClass, null);
}
/**
* Creates a SelectQuery that selects objects of a given persistent class
* that match supplied qualifier.
*
* @param rootClass
* the Class of objects fetched by this query.
* @param qualifier
* an Expression indicating which objects should be fetched.
*/
public SelectQuery(Class<T> rootClass, Expression qualifier) {
this(rootClass, qualifier, null);
}
/**
* Creates a SelectQuery that selects objects of a given persistent class
* that match supplied qualifier.
*
* @param rootClass
* the Class of objects fetched by this query.
* @param qualifier
* an Expression indicating which objects should be fetched.
* @param orderings
* defines how to order the results, may be null.
* @since 3.1
*/
public SelectQuery(Class<T> rootClass, Expression qualifier, List<? extends Ordering> orderings) {
init(rootClass, qualifier);
addOrderings(orderings);
}
/**
* Creates a SelectQuery for the specified DbEntity.
*
* @param root
* the DbEntity this SelectQuery is for.
* @since 1.1
*/
public SelectQuery(DbEntity root) {
this(root, null);
}
/**
* Creates a SelectQuery for the specified DbEntity with the given
* qualifier.
*
* @param root
* the DbEntity this SelectQuery is for.
* @param qualifier
* an Expression indicating which objects should be fetched.
* @since 1.1
*/
public SelectQuery(DbEntity root, Expression qualifier) {
this(root, qualifier, null);
}
/**
* Creates a SelectQuery for the specified DbEntity with the given qualifier
* and orderings.
*
* @param root
* the DbEntity this SelectQuery is for.
* @param qualifier
* an Expression indicating which objects should be fetched.
* @param orderings
* defines how to order the results, may be null.
* @since 3.1
*/
public SelectQuery(DbEntity root, Expression qualifier, List<? extends Ordering> orderings) {
this();
this.init(root, qualifier);
addOrderings(orderings);
}
/**
* Creates SelectQuery with <code>objEntityName</code> parameter.
*/
public SelectQuery(String objEntityName) {
this(objEntityName, null);
}
/**
* Creates SelectQuery with <code>objEntityName</code> and
* <code>qualifier</code> parameters.
*/
public SelectQuery(String objEntityName, Expression qualifier) {
this(objEntityName, qualifier, null);
}
/**
* Creates a SelectQuery that selects objects of a given persistent class
* that match supplied qualifier.
*
* @param objEntityName
* the name of the ObjEntity to fetch from.
* @param qualifier
* an Expression indicating which objects should be fetched.
* @param orderings
* defines how to order the results, may be null.
* @since 3.1
*/
public SelectQuery(String objEntityName, Expression qualifier, List<? extends Ordering> orderings) {
init(objEntityName, qualifier);
addOrderings(orderings);
}
private void init(Object root, Expression qualifier) {
this.setRoot(root);
this.setQualifier(qualifier);
}
@Override
public List<T> select(ObjectContext context) {
return context.select(this);
}
@Override
public T selectOne(ObjectContext context) {
return context.selectOne(this);
}
@Override
public T selectFirst(ObjectContext context) {
setFetchLimit(1);
return context.selectFirst(this);
}
@Override
public void iterate(ObjectContext context, ResultIteratorCallback<T> callback) {
context.iterate((Select<T>) this, callback);
}
@Override
public ResultIterator<T> iterator(ObjectContext context) {
return context.iterator(this);
}
@Override
public ResultBatchIterator<T> batchIterator(ObjectContext context, int size) {
return context.batchIterator(this, size);
}
/**
* @since 1.2
*/
@Override
public QueryMetadata getMetaData(EntityResolver resolver) {
metaData.resolve(root, resolver, this);
// must force DataRows if DbEntity is fetched
if (root instanceof DbEntity) {
QueryMetadataWrapper wrapper = new QueryMetadataWrapper(metaData);
wrapper.override(QueryMetadata.FETCHING_DATA_ROWS_PROPERTY, Boolean.TRUE);
return wrapper;
} else {
return metaData;
}
}
/**
* Routes itself and if there are any prefetches configured, creates
* prefetch queries and routes them as well.
*
* @since 1.2
*/
@Override
public void route(QueryRouter router, EntityResolver resolver, Query substitutedQuery) {
super.route(router, resolver, substitutedQuery);
// suppress prefetches for paginated queries.. instead prefetches will
// be resolved
// per row...
if (metaData.getPageSize() <= 0) {
routePrefetches(router, resolver);
}
}
/**
* Creates and routes extra disjoint prefetch queries.
*
* @since 1.2
*/
void routePrefetches(QueryRouter router, EntityResolver resolver) {
new SelectQueryPrefetchRouterAction().route(this, router, resolver);
}
/**
* Calls "makeSelect" on the visitor.
*
* @since 1.2
*/
@Override
public SQLAction createSQLAction(SQLActionVisitor visitor) {
return visitor.objectSelectAction(this);
}
/**
* Initializes query parameters using a set of properties.
*
* @since 1.1
*/
public void initWithProperties(Map<String, ?> properties) {
// must init defaults even if properties are empty
if (properties == null) {
properties = Collections.emptyMap();
}
Object distinct = properties.get(DISTINCT_PROPERTY);
// init ivars from properties
this.distinct = (distinct != null) ? "true".equalsIgnoreCase(distinct.toString()) : DISTINCT_DEFAULT;
metaData.initWithProperties(properties);
}
/**
* Prints itself as XML to the provided PrintWriter.
*
* @since 1.1
*/
public void encodeAsXML(XMLEncoder encoder) {
encoder.print("<query name=\"");
encoder.print(getName());
encoder.print("\" factory=\"");
encoder.print("org.apache.cayenne.map.SelectQueryBuilder");
String rootString = null;
String rootType = null;
if (root instanceof String) {
rootType = MapLoader.OBJ_ENTITY_ROOT;
rootString = root.toString();
} else if (root instanceof ObjEntity) {
rootType = MapLoader.OBJ_ENTITY_ROOT;
rootString = ((ObjEntity) root).getName();
} else if (root instanceof DbEntity) {
rootType = MapLoader.DB_ENTITY_ROOT;
rootString = ((DbEntity) root).getName();
} else if (root instanceof Procedure) {
rootType = MapLoader.PROCEDURE_ROOT;
rootString = ((Procedure) root).getName();
} else if (root instanceof Class<?>) {
rootType = MapLoader.JAVA_CLASS_ROOT;
rootString = ((Class<?>) root).getName();
}
if (rootType != null) {
encoder.print("\" root=\"");
encoder.print(rootType);
encoder.print("\" root-name=\"");
encoder.print(rootString);
}
encoder.println("\">");
encoder.indent(1);
// print properties
if (distinct != DISTINCT_DEFAULT) {
encoder.printProperty(DISTINCT_PROPERTY, distinct);
}
metaData.encodeAsXML(encoder);
// encode qualifier
if (qualifier != null) {
encoder.print("<qualifier>");
qualifier.encodeAsXML(encoder);
encoder.println("</qualifier>");
}
// encode orderings
if (orderings != null && !orderings.isEmpty()) {
for (Ordering ordering : orderings) {
ordering.encodeAsXML(encoder);
}
}
encoder.indent(-1);
encoder.println("</query>");
}
/**
* A shortcut for {@link #queryWithParameters(Map, boolean)}that prunes
* parts of qualifier that have no parameter value set.
*/
public SelectQuery<T> queryWithParameters(Map<String, ?> parameters) {
return queryWithParameters(parameters, true);
}
/**
* Returns a query built using this query as a prototype, using a set of
* parameters to build the qualifier.
*
* @see org.apache.cayenne.exp.Expression#expWithParameters(java.util.Map,
* boolean) parameter substitution.
*/
public SelectQuery<T> queryWithParameters(Map<String, ?> parameters, boolean pruneMissing) {
// create a query replica
SelectQuery<T> query = new SelectQuery<T>();
query.setDistinct(distinct);
query.metaData.copyFromInfo(this.metaData);
query.setRoot(root);
if (orderings != null) {
query.addOrderings(orderings);
}
// substitute qualifier parameters
if (qualifier != null) {
query.setQualifier(qualifier.params(parameters, pruneMissing));
}
return query;
}
/**
* Creates and returns a new SelectQuery built using this query as a
* prototype and substituting qualifier parameters with the values from the
* map.
*
* @since 1.1
*/
public SelectQuery<T> createQuery(Map<String, ?> parameters) {
return queryWithParameters(parameters);
}
/**
* Adds ordering specification to this query orderings.
*/
public void addOrdering(Ordering ordering) {
nonNullOrderings().add(ordering);
}
/**
* Adds a list of orderings.
*/
public void addOrderings(Collection<? extends Ordering> orderings) {
// If the supplied list of orderings is null, do not attempt to add
// to the collection (addAll() will NPE otherwise).
if (orderings != null) {
nonNullOrderings().addAll(orderings);
}
}
/**
* Adds ordering specification to this query orderings.
*
* @since 3.0
*/
public void addOrdering(String sortPathSpec, SortOrder order) {
addOrdering(new Ordering(sortPathSpec, order));
}
/**
* Removes ordering.
*
* @since 1.1
*/
public void removeOrdering(Ordering ordering) {
if (orderings != null) {
orderings.remove(ordering);
}
}
/**
* Returns a list of orderings used by this query.
*/
public List<Ordering> getOrderings() {
return (orderings != null) ? orderings : Collections.<Ordering> emptyList();
}
/**
* Clears all configured orderings.
*/
public void clearOrderings() {
orderings = null;
}
/**
* Returns true if this query returns distinct rows.
*/
public boolean isDistinct() {
return distinct;
}
/**
* Sets <code>distinct</code> property that determines whether this query
* returns distinct row.
*/
public void setDistinct(boolean distinct) {
this.distinct = distinct;
}
/**
* Sets <code>distinct</code> property that determines whether this query
* returns distinct row.
*/
public void setSuppressDistinct(boolean suppressDistinct) {
this.metaData.setSuppressingDistinct(suppressDistinct);
}
/**
* Adds one or more aliases for the qualifier expression path. Aliases serve
* to instruct Cayenne to generate separate sets of joins for overlapping
* paths, that maybe needed for complex conditions. An example of an
* <i>implicit</i> splits is this method:
* {@link ExpressionFactory#matchAllExp(String, Object...)}.
*
* @since 3.0
*/
public void aliasPathSplits(String path, String... aliases) {
metaData.addPathSplitAliases(path, aliases);
}
/**
* @since 1.2
*/
public PrefetchTreeNode getPrefetchTree() {
return metaData.getPrefetchTree();
}
/**
* @since 1.2
*/
public void setPrefetchTree(PrefetchTreeNode prefetchTree) {
metaData.setPrefetchTree(prefetchTree);
}
/**
* Adds a prefetch with specified relationship path to the query.
*
* @since 4.0
*/
public void addPrefetch(PrefetchTreeNode prefetchElement) {
metaData.mergePrefetch(prefetchElement);
}
/**
* Adds a prefetch with specified relationship path to the query.
*
* @since 1.2 signature changed to return created PrefetchTreeNode.
*/
public PrefetchTreeNode addPrefetch(String prefetchPath) {
return metaData.addPrefetch(prefetchPath, PrefetchTreeNode.UNDEFINED_SEMANTICS);
}
/**
* Clears all stored prefetch paths.
*/
public void clearPrefetches() {
metaData.clearPrefetches();
}
/**
* Removes prefetch.
*
* @since 1.1
*/
public void removePrefetch(String prefetchPath) {
metaData.removePrefetch(prefetchPath);
}
/**
* Returns <code>true</code> if this query should produce a list of data
* rows as opposed to DataObjects, <code>false</code> for DataObjects. This
* is a hint to QueryEngine executing this query.
*/
public boolean isFetchingDataRows() {
return (root instanceof DbEntity) || metaData.isFetchingDataRows();
}
/**
* Sets query result type. If <code>flag</code> parameter is
* <code>true</code>, then results will be in the form of data rows.
* <p>
* <i>Note that if the root of this query is a {@link DbEntity}, this
* setting has no effect, and data rows are always fetched. </i>
* </p>
*
* @deprecated since 4.0, use {@link #dataRowQuery(Class, Expression)} to
* create DataRow query instead.
*/
public void setFetchingDataRows(boolean flag) {
metaData.setFetchingDataRows(flag);
}
/**
* Returns the fetchOffset.
*
* @since 3.0
*/
public int getFetchOffset() {
return metaData.getFetchOffset();
}
/**
* Returns the fetchLimit.
*/
public int getFetchLimit() {
return metaData.getFetchLimit();
}
/**
* Sets the fetchLimit.
*/
public void setFetchLimit(int fetchLimit) {
this.metaData.setFetchLimit(fetchLimit);
}
/**
* @since 3.0
*/
public void setFetchOffset(int fetchOffset) {
this.metaData.setFetchOffset(fetchOffset);
}
/**
* Returns <code>pageSize</code> property. See setPageSize for more details.
*/
public int getPageSize() {
return metaData.getPageSize();
}
/**
* Sets <code>pageSize</code> property.
*
* By setting a page size, the Collection returned by performing a query
* will return <i>hollow</i> DataObjects. This is considerably faster and
* uses a tiny fraction of the memory compared to a non-paged query when
* large numbers of objects are returned in the result. When a hollow
* DataObject is accessed all DataObjects on the same page will be faulted
* into memory. There will be a small delay when faulting objects while the
* data is fetched from the data source, but otherwise you do not need to do
* anything special to access data in hollow objects. The first page is
* always faulted into memory immediately.
*
* @param pageSize
* The pageSize to set
*/
public void setPageSize(int pageSize) {
metaData.setPageSize(pageSize);
}
/**
* Returns a list that internally stores orderings, creating it on demand.
*
* @since 1.2
*/
List<Ordering> nonNullOrderings() {
if (orderings == null) {
orderings = new ArrayList<>(3);
}
return orderings;
}
/**
* Sets statement's fetch size (0 for default size)
*
* @since 3.0
*/
public void setStatementFetchSize(int size) {
metaData.setStatementFetchSize(size);
}
/**
* @return statement's fetch size
* @since 3.0
*/
public int getStatementFetchSize() {
return metaData.getStatementFetchSize();
}
/**
* Sets new query qualifier.
*/
public void setQualifier(Expression qualifier) {
this.qualifier = qualifier;
}
/**
* Returns query qualifier.
*/
public Expression getQualifier() {
return qualifier;
}
/**
* Adds specified qualifier to the existing qualifier joining it using
* "AND".
*/
public void andQualifier(Expression e) {
qualifier = (qualifier != null) ? qualifier.andExp(e) : e;
}
/**
* Adds specified qualifier to the existing qualifier joining it using "OR".
*/
public void orQualifier(Expression e) {
qualifier = (qualifier != null) ? qualifier.orExp(e) : e;
}
/**
* @since 4.0
* @see SelectQuery#setCanReturnScalarValue(boolean)
*/
public void setColumns(Collection<Property<?>> columns) {
this.columns = columns;
}
/**
* @since 4.0
*/
public void setColumns(Property<?>... columns) {
if(columns == null || columns.length == 0) {
return;
}
setColumns(Arrays.asList(columns));
}
/**
* <p>Flag that indicates whether this query can return single value or
* it should always return some complex data (Object[] for now)</p>
* <p>Default value is <b>true</b></p>
* @param canReturnScalarValue can this query return single value
* @since 4.0
* @see SelectQuery#setColumns
*/
public void setCanReturnScalarValue(boolean canReturnScalarValue) {
this.canReturnScalarValue = canReturnScalarValue;
}
/**
* @return can this query return single value
* @since 4.0
*/
public boolean canReturnScalarValue() {
return canReturnScalarValue;
}
/**
* @since 4.0
*/
public Collection<Property<?>> getColumns() {
return columns;
}
/**
* Sets new query HAVING qualifier.
* @since 4.0
*/
public void setHavingQualifier(Expression qualifier) {
this.havingQualifier = qualifier;
}
/**
* Returns query HAVING qualifier.
* @since 4.0
*/
public Expression getHavingQualifier() {
return havingQualifier;
}
/**
* Adds specified HAVING qualifier to the existing HAVING qualifier joining it using "AND".
* @since 4.0
*/
public void andHavingQualifier(Expression e) {
havingQualifier = (havingQualifier != null) ? havingQualifier.andExp(e) : e;
}
/**
* Adds specified HAVING qualifier to the existing HAVING qualifier joining it using "OR".
* @since 4.0
*/
public void orHavingQualifier(Expression e) {
havingQualifier = (havingQualifier != null) ? havingQualifier.orExp(e) : e;
}
@Override
protected BaseQueryMetadata getBaseMetaData() {
return metaData;
}
}