Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.parse.ParseQuery Maven / Gradle / Ivy
Go to download
A library that gives you access to the powerful Parse cloud platform from your Android app.
/*
* Copyright (c) 2015-present, Parse, LLC.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.parse;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.regex.Pattern;
import bolts.Continuation;
import bolts.Task;
import bolts.TaskCompletionSource;
/**
* The {@code ParseQuery} class defines a query that is used to fetch {@link ParseObject}s. The most
* common use case is finding all objects that match a query through the {@link #findInBackground()}
* method, using a {@link FindCallback}. For example, this sample code fetches all objects of class
* {@code "MyClass"}. It calls a different function depending on whether the fetch succeeded or not.
*
*
* ParseQuery<ParseObject> query = ParseQuery.getQuery("MyClass");
* query.findInBackground(new FindCallback<ParseObject>() {
* public void done(List<ParseObject> objects, ParseException e) {
* if (e == null) {
* objectsWereRetrievedSuccessfully(objects);
* } else {
* objectRetrievalFailed();
* }
* }
* }
*
*
* A {@code ParseQuery} can also be used to retrieve a single object whose id is known, through the
* {@link #getInBackground(String)} method, using a {@link GetCallback}. For example, this
* sample code fetches an object of class {@code "MyClass"} and id {@code myId}. It calls
* a different function depending on whether the fetch succeeded or not.
*
*
* ParseQuery<ParseObject> query = ParseQuery.getQuery("MyClass");
* query.getInBackground(myId, new GetCallback<ParseObject>() {
* public void done(ParseObject object, ParseException e) {
* if (e == null) {
* objectWasRetrievedSuccessfully(object);
* } else {
* objectRetrievalFailed();
* }
* }
* }
*
*
* A {@code ParseQuery} can also be used to count the number of objects that match the query without
* retrieving all of those objects. For example, this sample code counts the number of objects of
* the class {@code "MyClass"}.
*
*
* ParseQuery<ParseObject> query = ParseQuery.getQuery("MyClass");
* query.countInBackground(new CountCallback() {
* public void done(int count, ParseException e) {
* if (e == null) {
* objectsWereCounted(count);
* } else {
* objectCountFailed();
* }
* }
* }
*
*
* Using the callback methods is usually preferred because the network operation will not block the
* calling thread. However, in some cases it may be easier to use the {@link #find()},
* {@link #get(String)} or {@link #count()} calls, which do block the calling thread. For example,
* if your application has already spawned a background task to perform work, that background task
* could use the blocking calls and avoid the code complexity of callbacks.
*/
public class ParseQuery {
private static ParseQueryController getQueryController() {
return ParseCorePlugins.getInstance().getQueryController();
}
private static ParseObjectSubclassingController getSubclassingController() {
return ParseCorePlugins.getInstance().getSubclassingController();
}
/**
* Constraints for a {@code ParseQuery}'s where clause. A map of field names to constraints. The
* values can either be actual values to compare with for equality, or instances of
* {@link KeyConstraints}.
*/
@SuppressWarnings("serial")
/* package */ static class QueryConstraints extends HashMap {
public QueryConstraints() {
super();
}
public QueryConstraints(Map extends String, ?> map) {
super(map);
}
}
/**
* Constraints for a particular field in a query. If this is used, it's a may where the keys are
* special operators, such as $greaterThan or $nin. The values are the actual values to compare
* against.
*/
@SuppressWarnings("serial")
/* package */ static class KeyConstraints extends HashMap {
}
/**
* Constraint for a $relatedTo query.
*/
/* package */ static class RelationConstraint {
private String key;
private ParseObject object;
public RelationConstraint(String key, ParseObject object) {
if (key == null || object == null) {
throw new IllegalArgumentException("Arguments must not be null.");
}
this.key = key;
this.object = object;
}
public String getKey() {
return key;
}
public ParseObject getObject() {
return object;
}
public ParseRelation getRelation() {
return object.getRelation(key);
}
/**
* Encodes the constraint in a format appropriate for including in the query.
*/
public JSONObject encode(ParseEncoder objectEncoder) {
JSONObject json = new JSONObject();
try {
json.put("key", key);
json.put("object", objectEncoder.encodeRelatedObject(object));
} catch (JSONException e) {
// This can never happen.
throw new RuntimeException(e);
}
return json;
}
}
/**
* Constructs a query that is the {@code or} of the given queries.
*
* @param queries
* The list of {@code ParseQuery}s to 'or' together
* @return A {@code ParseQuery} that is the 'or' of the passed in queries
*/
public static ParseQuery or(List> queries) {
if (queries.isEmpty()) {
throw new IllegalArgumentException("Can't take an or of an empty list of queries");
}
List> builders = new ArrayList<>();
for (ParseQuery query : queries) {
builders.add(query.getBuilder());
}
return new ParseQuery<>(State.Builder.or(builders));
}
/**
* Creates a new query for the given {@link ParseObject} subclass type. A default query with no
* further parameters will retrieve all {@link ParseObject}s of the provided class.
*
* @param subclass
* The {@link ParseObject} subclass type to retrieve.
* @return A new {@code ParseQuery}.
*/
public static ParseQuery getQuery(Class subclass) {
return new ParseQuery<>(subclass);
}
/**
* Creates a new query for the given class name. A default query with no further parameters will
* retrieve all {@link ParseObject}s of the provided class name.
*
* @param className
* The name of the class to retrieve {@link ParseObject}s for.
* @return A new {@code ParseQuery}.
*/
public static ParseQuery getQuery(String className) {
return new ParseQuery<>(className);
}
/**
* Constructs a query for {@link ParseUser}s.
*
* @deprecated Please use {@link ParseUser#getQuery()} instead.
*/
@Deprecated
public static ParseQuery getUserQuery() {
return ParseUser.getQuery();
}
/**
* {@code CachePolicy} specifies different caching policies that could be used with
* {@link ParseQuery}.
*
* This lets you show data when the user's device is offline, or when the app has just started and
* network requests have not yet had time to complete. Parse takes care of automatically flushing
* the cache when it takes up too much space.
*
* Note: Cache policy can only be set when Local Datastore is not enabled.
*
* @see com.parse.ParseQuery
*/
public enum CachePolicy {
/**
* The query does not load from the cache or save results to the cache.
*
* This is the default cache policy.
*/
IGNORE_CACHE,
/**
* The query only loads from the cache, ignoring the network.
*
* If there are no cached results, this causes a {@link ParseException#CACHE_MISS}.
*/
CACHE_ONLY,
/**
* The query does not load from the cache, but it will save results to the cache.
*/
NETWORK_ONLY,
/**
* The query first tries to load from the cache, but if that fails, it loads results from the
* network.
*
* If there are no cached results, this causes a {@link ParseException#CACHE_MISS}.
*/
CACHE_ELSE_NETWORK,
/**
* The query first tries to load from the network, but if that fails, it loads results from the
* cache.
*
* If there are no cached results, this causes a {@link ParseException#CACHE_MISS}.
*/
NETWORK_ELSE_CACHE,
/**
* The query first loads from the cache, then loads from the network.
* The callback will be called twice - first with the cached results, then with the network
* results. Since it returns two results at different times, this cache policy cannot be used
* with synchronous or task methods.
*/
// TODO(grantland): Remove this and come up with a different solution, since it breaks our
// "callbacks get called at most once" paradigm. (v2)
CACHE_THEN_NETWORK
}
private static void throwIfLDSEnabled() {
throwIfLDSEnabled(false);
}
private static void throwIfLDSDisabled() {
throwIfLDSEnabled(true);
}
private static void throwIfLDSEnabled(boolean enabled) {
boolean ldsEnabled = Parse.isLocalDatastoreEnabled();
if (enabled && !ldsEnabled) {
throw new IllegalStateException("Method requires Local Datastore. " +
"Please refer to `Parse#enableLocalDatastore(Context)`.");
}
if (!enabled && ldsEnabled) {
throw new IllegalStateException("Unsupported method when Local Datastore is enabled.");
}
}
/* package */ static class State {
/* package */ static class Builder {
// TODO(grantland): Convert mutable parameter to immutable t6941155
public static Builder or(List> builders) {
if (builders.isEmpty()) {
throw new IllegalArgumentException("Can't take an or of an empty list of queries");
}
String className = null;
List constraints = new ArrayList<>();
for (Builder builder : builders) {
if (className != null && !builder.className.equals(className)) {
throw new IllegalArgumentException(
"All of the queries in an or query must be on the same class ");
}
if (builder.limit >= 0) {
throw new IllegalArgumentException("Cannot have limits in sub queries of an 'OR' query");
}
if (builder.skip > 0) {
throw new IllegalArgumentException("Cannot have skips in sub queries of an 'OR' query");
}
if (!builder.order.isEmpty()) {
throw new IllegalArgumentException("Cannot have an order in sub queries of an 'OR' query");
}
if (!builder.includes.isEmpty()) {
throw new IllegalArgumentException("Cannot have an include in sub queries of an 'OR' query");
}
if (builder.selectedKeys != null) {
throw new IllegalArgumentException(
"Cannot have an selectKeys in sub queries of an 'OR' query");
}
className = builder.className;
constraints.add(builder.where);
}
return new State.Builder(className)
.whereSatifiesAnyOf(constraints);
}
private final String className;
private final QueryConstraints where = new QueryConstraints();
private final Set includes = new HashSet<>();
// This is nullable since we allow unset selectedKeys as well as no selectedKeys
private Set selectedKeys;
private int limit = -1; // negative limits mean, do not send a limit
private int skip = 0; // negative skip means do not send a skip
private List order = new ArrayList<>();
private final Map extraOptions = new HashMap<>();
// TODO(grantland): Move out of State
private boolean trace;
// Query Caching
private CachePolicy cachePolicy = CachePolicy.IGNORE_CACHE;
private long maxCacheAge = Long.MAX_VALUE; // 292 million years should be enough not to cause issues
// LDS
private boolean isFromLocalDatastore = false;
private String pinName;
private boolean ignoreACLs;
public Builder(String className) {
this.className = className;
}
public Builder(Class subclass) {
this(getSubclassingController().getClassName(subclass));
}
public Builder(State state) {
className = state.className();
where.putAll(state.constraints());
includes.addAll(state.includes());
selectedKeys = state.selectedKeys() != null ? new HashSet(state.selectedKeys()) : null;
limit = state.limit();
skip = state.skip();
order.addAll(state.order());
extraOptions.putAll(state.extraOptions());
trace = state.isTracingEnabled();
cachePolicy = state.cachePolicy();
maxCacheAge = state.maxCacheAge();
isFromLocalDatastore = state.isFromLocalDatastore();
pinName = state.pinName();
ignoreACLs = state.ignoreACLs();
}
public Builder(Builder builder) {
className = builder.className;
where.putAll(builder.where);
includes.addAll(builder.includes);
selectedKeys = builder.selectedKeys != null ? new HashSet(builder.selectedKeys) : null;
limit = builder.limit;
skip = builder.skip;
order.addAll(builder.order);
extraOptions.putAll(builder.extraOptions);
trace = builder.trace;
cachePolicy = builder.cachePolicy;
maxCacheAge = builder.maxCacheAge;
isFromLocalDatastore = builder.isFromLocalDatastore;
pinName = builder.pinName;
ignoreACLs = builder.ignoreACLs;
}
public String getClassName() {
return className;
}
//region Where Constraints
/**
* Add a constraint to the query that requires a particular key's value to be equal to the
* provided value.
*
* @param key
* The key to check.
* @param value
* The value that the {@link ParseObject} must contain.
* @return this, so you can chain this call.
*/
// TODO(grantland): Add typing
public Builder whereEqualTo(String key, Object value) {
where.put(key, value);
return this;
}
// TODO(grantland): Convert mutable parameter to immutable t6941155
public Builder whereDoesNotMatchKeyInQuery(String key, String keyInQuery, Builder> builder) {
Map condition = new HashMap<>();
condition.put("key", keyInQuery);
condition.put("query", builder);
return addConditionInternal(key, "$dontSelect", Collections.unmodifiableMap(condition));
}
// TODO(grantland): Convert mutable parameter to immutable t6941155
public Builder whereMatchesKeyInQuery(String key, String keyInQuery, Builder> builder) {
Map condition = new HashMap<>();
condition.put("key", keyInQuery);
condition.put("query", builder);
return addConditionInternal(key, "$select", Collections.unmodifiableMap(new HashMap<>(condition)));
}
// TODO(grantland): Convert mutable parameter to immutable t6941155
public Builder whereDoesNotMatchQuery(String key, Builder> builder) {
return addConditionInternal(key, "$notInQuery", builder);
}
// TODO(grantland): Convert mutable parameter to immutable t6941155
public Builder whereMatchesQuery(String key, Builder> builder) {
return addConditionInternal(key, "$inQuery", builder);
}
public Builder whereNear(String key, ParseGeoPoint point) {
return addCondition(key, "$nearSphere", point);
}
public Builder maxDistance(String key, double maxDistance) {
return addCondition(key, "$maxDistance", maxDistance);
}
public Builder whereWithin(String key, ParseGeoPoint southwest, ParseGeoPoint northeast) {
List array = new ArrayList<>();
array.add(southwest);
array.add(northeast);
Map> dictionary = new HashMap<>();
dictionary.put("$box", array);
return addCondition(key, "$within", dictionary);
}
public Builder addCondition(String key, String condition,
Collection extends Object> value) {
return addConditionInternal(key, condition, Collections.unmodifiableCollection(value));
}
// TODO(grantland): Add typing
public Builder addCondition(String key, String condition, Object value) {
return addConditionInternal(key, condition, value);
}
// Helper for condition queries.
private Builder addConditionInternal(String key, String condition, Object value) {
KeyConstraints whereValue = null;
// Check if we already have some of a condition
if (where.containsKey(key)) {
Object existingValue = where.get(key);
if (existingValue instanceof KeyConstraints) {
whereValue = (KeyConstraints) existingValue;
}
}
if (whereValue == null) {
whereValue = new KeyConstraints();
}
whereValue.put(condition, value);
where.put(key, whereValue);
return this;
}
// Used by ParseRelation
/* package */ Builder whereRelatedTo(ParseObject parent, String key) {
where.put("$relatedTo", new RelationConstraint(key, parent));
return this;
}
/**
* Add a constraint that a require matches any one of an array of {@code ParseQuery}s.
*
* The {@code ParseQuery}s passed cannot have any orders, skips, or limits set.
*
* @param constraints
* The array of queries to or
*
* @return this, so you can chain this call.
*/
private Builder whereSatifiesAnyOf(List constraints) {
where.put("$or", constraints);
return this;
}
// Used by getInBackground
/* package */ Builder whereObjectIdEquals(String objectId) {
where.clear();
where.put("objectId", objectId);
return this;
}
//endregion
//region Order
private Builder setOrder(String key) {
order.clear();
order.add(key);
return this;
}
private Builder addOrder(String key) {
order.add(key);
return this;
}
/**
* Sorts the results in ascending order by the given key.
*
* @param key
* The key to order by.
* @return this, so you can chain this call.
*/
public Builder orderByAscending(String key) {
return setOrder(key);
}
/**
* Also sorts the results in ascending order by the given key.
*
* The previous sort keys have precedence over this key.
*
* @param key
* The key to order by
* @return this, so you can chain this call.
*/
public Builder addAscendingOrder(String key) {
return addOrder(key);
}
/**
* Sorts the results in descending order by the given key.
*
* @param key
* The key to order by.
* @return this, so you can chain this call.
*/
public Builder orderByDescending(String key) {
return setOrder(String.format("-%s", key));
}
/**
* Also sorts the results in descending order by the given key.
*
* The previous sort keys have precedence over this key.
*
* @param key
* The key to order by
* @return this, so you can chain this call.
*/
public Builder addDescendingOrder(String key) {
return addOrder(String.format("-%s", key));
}
//endregion
//region Includes
/**
* Include nested {@link ParseObject}s for the provided key.
*
* You can use dot notation to specify which fields in the included object that are also fetched.
*
* @param key
* The key that should be included.
* @return this, so you can chain this call.
*/
public Builder include(String key) {
includes.add(key);
return this;
}
//endregion
/**
* Restrict the fields of returned {@link ParseObject}s to only include the provided keys.
*
* If this is called multiple times, then all of the keys specified in each of the calls will be
* included.
*
* Note: This option will be ignored when querying from the local datastore. This
* is done since all the keys will be in memory anyway and there will be no performance gain from
* removing them.
*
* @param keys
* The set of keys to include in the result.
* @return this, so you can chain this call.
*/
public Builder selectKeys(Collection keys) {
if (selectedKeys == null) {
selectedKeys = new HashSet<>();
}
selectedKeys.addAll(keys);
return this;
}
public int getLimit() {
return limit;
}
public Builder setLimit(int limit) {
this.limit = limit;
return this;
}
public int getSkip() {
return skip;
}
public Builder setSkip(int skip) {
this.skip = skip;
return this;
}
// Used by ParseRelation
/* package */ Builder redirectClassNameForKey(String key) {
extraOptions.put("redirectClassNameForKey", key);
return this;
}
public Builder setTracingEnabled(boolean trace) {
this.trace = trace;
return this;
}
public CachePolicy getCachePolicy() {
throwIfLDSEnabled();
return cachePolicy;
}
public Builder setCachePolicy(CachePolicy cachePolicy) {
throwIfLDSEnabled();
this.cachePolicy = cachePolicy;
return this;
}
public long getMaxCacheAge() {
throwIfLDSEnabled();
return maxCacheAge;
}
public Builder setMaxCacheAge(long maxCacheAge) {
throwIfLDSEnabled();
this.maxCacheAge = maxCacheAge;
return this;
}
public boolean isFromNetwork() {
throwIfLDSDisabled();
return !isFromLocalDatastore;
}
public Builder fromNetwork() {
throwIfLDSDisabled();
isFromLocalDatastore = false;
pinName = null;
return this;
}
public Builder fromLocalDatastore() {
return fromPin(null);
}
public boolean isFromLocalDatstore() {
return isFromLocalDatastore;
}
public Builder fromPin() {
return fromPin(ParseObject.DEFAULT_PIN);
}
public Builder fromPin(String pinName) {
throwIfLDSDisabled();
isFromLocalDatastore = true;
this.pinName = pinName;
return this;
}
public Builder ignoreACLs() {
throwIfLDSDisabled();
ignoreACLs = true;
return this;
}
public State build() {
if (!isFromLocalDatastore && ignoreACLs) {
throw new IllegalStateException("`ignoreACLs` cannot be combined with network queries");
}
return new State<>(this);
}
}
private final String className;
private final QueryConstraints where;
private final Set include;
private final Set selectedKeys;
private final int limit;
private final int skip;
private final List order;
private final Map extraOptions;
// TODO(grantland): Move out of State
private final boolean trace;
// Query Caching
private final CachePolicy cachePolicy;
private final long maxCacheAge;
// LDS
private final boolean isFromLocalDatastore;
private final String pinName;
private final boolean ignoreACLs;
private State(Builder builder) {
className = builder.className;
where = new QueryConstraints(builder.where);
include = Collections.unmodifiableSet(new HashSet<>(builder.includes));
selectedKeys = builder.selectedKeys != null
? Collections.unmodifiableSet(new HashSet<>(builder.selectedKeys))
: null;
limit = builder.limit;
skip = builder.skip;
order = Collections.unmodifiableList(new ArrayList<>(builder.order));
extraOptions = Collections.unmodifiableMap(new HashMap<>(builder.extraOptions));
trace = builder.trace;
cachePolicy = builder.cachePolicy;
maxCacheAge = builder.maxCacheAge;
isFromLocalDatastore = builder.isFromLocalDatastore;
pinName = builder.pinName;
ignoreACLs = builder.ignoreACLs;
}
public String className() {
return className;
}
public QueryConstraints constraints() {
return where;
}
public Set includes() {
return include;
}
public Set selectedKeys() {
return selectedKeys;
}
public int limit() {
return limit;
}
public int skip() {
return skip;
}
public List order() {
return order;
}
public Map extraOptions() {
return extraOptions;
}
public boolean isTracingEnabled() {
return trace;
}
public CachePolicy cachePolicy() {
return cachePolicy;
}
public long maxCacheAge() {
return maxCacheAge;
}
public boolean isFromLocalDatastore() {
return isFromLocalDatastore;
}
public String pinName() {
return pinName;
}
public boolean ignoreACLs() {
return ignoreACLs;
}
// Returns the query in JSON REST format for subqueries
/* package */ JSONObject toJSON(ParseEncoder encoder) {
JSONObject params = new JSONObject();
try {
params.put("className", className);
params.put("where", encoder.encode(where));
if (limit >= 0) {
params.put("limit", limit);
}
if (skip > 0) {
params.put("skip", skip);
}
if (!order.isEmpty()) {
params.put("order", ParseTextUtils.join(",", order));
}
if (!include.isEmpty()) {
params.put("include", ParseTextUtils.join(",", include));
}
if (selectedKeys != null) {
params.put("fields", ParseTextUtils.join(",", selectedKeys));
}
if (trace) {
params.put("trace", 1);
}
for (String key : extraOptions.keySet()) {
params.put(key, encoder.encode(extraOptions.get(key)));
}
} catch (JSONException e) {
throw new RuntimeException(e);
}
return params;
}
@Override
public String toString() {
return String.format(Locale.US, "%s[className=%s, where=%s, include=%s, " +
"selectedKeys=%s, limit=%s, skip=%s, order=%s, extraOptions=%s, " +
"cachePolicy=%s, maxCacheAge=%s, " +
"trace=%s]",
getClass().getName(),
className,
where,
include,
selectedKeys,
limit,
skip,
order,
extraOptions,
cachePolicy,
maxCacheAge,
trace);
}
}
private final State.Builder builder;
private ParseUser user;
private final Object lock = new Object();
private boolean isRunning = false;
private TaskCompletionSource cts;
/**
* Constructs a query for a {@link ParseObject} subclass type. A default query with no further
* parameters will retrieve all {@link ParseObject}s of the provided class.
*
* @param subclass
* The {@link ParseObject} subclass type to retrieve.
*/
public ParseQuery(Class subclass) {
this(getSubclassingController().getClassName(subclass));
}
/**
* Constructs a query. A default query with no further parameters will retrieve all
* {@link ParseObject}s of the provided class.
*
* @param theClassName
* The name of the class to retrieve {@link ParseObject}s for.
*/
public ParseQuery(String theClassName) {
this(new State.Builder(theClassName));
}
/**
* Constructs a copy of {@code query};
*
* @param query
* The query to copy.
*/
public ParseQuery(ParseQuery query) {
this(new State.Builder<>(query.getBuilder()));
user = query.user;
}
/* package */ ParseQuery(State.Builder builder) {
this.builder = builder;
}
/* package */ State.Builder getBuilder() {
return builder;
}
/**
* Sets the user to be used for this query.
*
*
* The query will use the user if set, otherwise it will read the current user.
*/
/* package for tests */ ParseQuery setUser(ParseUser user) {
this.user = user;
return this;
}
/**
* Returns the user used for the query. This user is used to filter results based on ACLs on the
* target objects. Can be {@code null} if the there is no current user or {@link #ignoreACLs} is
* enabled.
*/
/* package for tests */ Task getUserAsync(State state) {
if (state.ignoreACLs()) {
return Task.forResult(null);
}
if (user != null) {
return Task.forResult(user);
}
return ParseUser.getCurrentUserAsync();
}
private void checkIfRunning() {
checkIfRunning(false);
}
private void checkIfRunning(boolean grabLock) {
synchronized (lock) {
if (isRunning) {
throw new RuntimeException(
"This query has an outstanding network connection. You have to wait until it's done.");
} else if (grabLock) {
isRunning = true;
cts = Task.create();
}
}
}
/**
* Cancels the current network request (if one is running).
*/
//TODO (grantland): Deprecate and replace with CancellationTokens
public void cancel() {
synchronized (lock) {
if (cts != null) {
cts.trySetCancelled();
cts = null;
}
isRunning = false;
}
}
/**
* Retrieves a list of {@link ParseObject}s that satisfy this query.
*
* @return A list of all {@link ParseObject}s obeying the conditions set in this query.
* @throws ParseException
* Throws a {@link ParseException} if no object is found.
*
* @see ParseException#OBJECT_NOT_FOUND
*/
public List find() throws ParseException {
return ParseTaskUtils.wait(findInBackground());
}
/**
* Retrieves at most one {@link ParseObject} that satisfies this query.
*
* Note: This mutates the {@code ParseQuery}.
*
* @return A {@link ParseObject} obeying the conditions set in this query.
* @throws ParseException
* Throws a {@link ParseException} if no object is found.
*
* @see ParseException#OBJECT_NOT_FOUND
*/
public T getFirst() throws ParseException {
return ParseTaskUtils.wait(getFirstInBackground());
}
/**
* Change the caching policy of this query.
*
* Unsupported when Local Datastore is enabled.
*
* @return this, so you can chain this call.
*
* @see ParseQuery#fromLocalDatastore()
* @see ParseQuery#fromPin()
* @see ParseQuery#fromPin(String)
*/
public ParseQuery setCachePolicy(CachePolicy newCachePolicy) {
checkIfRunning();
builder.setCachePolicy(newCachePolicy);
return this;
}
/**
* @return the caching policy.
*/
public CachePolicy getCachePolicy() {
return builder.getCachePolicy();
}
/**
* Change the source of this query to the server.
*
* Requires Local Datastore to be enabled.
*
* @return this, so you can chain this call.
*
* @see ParseQuery#setCachePolicy(CachePolicy)
*/
/* package */ ParseQuery fromNetwork() {
checkIfRunning();
builder.fromNetwork();
return this;
}
/* package */ boolean isFromNetwork() {
return builder.isFromNetwork();
}
/**
* Change the source of this query to all pinned objects.
*
* Requires Local Datastore to be enabled.
*
* @return this, so you can chain this call.
*
* @see ParseQuery#setCachePolicy(CachePolicy)
*/
public ParseQuery fromLocalDatastore() {
builder.fromLocalDatastore();
return this;
}
/**
* Change the source of this query to the default group of pinned objects.
*
* Requires Local Datastore to be enabled.
*
* @return this, so you can chain this call.
*
* @see ParseObject#DEFAULT_PIN
* @see ParseQuery#setCachePolicy(CachePolicy)
*/
public ParseQuery fromPin() {
checkIfRunning();
builder.fromPin();
return this;
}
/**
* Change the source of this query to a specific group of pinned objects.
*
* Requires Local Datastore to be enabled.
*
* @param name
* the pinned group
* @return this, so you can chain this call.
*
* @see ParseQuery#setCachePolicy(CachePolicy)
*/
public ParseQuery fromPin(String name) {
checkIfRunning();
builder.fromPin(name);
return this;
}
/**
* Ignore ACLs when querying from the Local Datastore.
*
* This is particularly useful when querying for objects with Role based ACLs set on them.
*
* @return this, so you can chain this call.
*/
public ParseQuery ignoreACLs() {
checkIfRunning();
builder.ignoreACLs();
return this;
}
/**
* Sets the maximum age of cached data that will be considered in this query.
*
* @return this, so you can chain this call.
*/
public ParseQuery setMaxCacheAge(long maxAgeInMilliseconds) {
checkIfRunning();
builder.setMaxCacheAge(maxAgeInMilliseconds);
return this;
}
/**
* Gets the maximum age of cached data that will be considered in this query. The returned value
* is in milliseconds
*/
public long getMaxCacheAge() {
return builder.getMaxCacheAge();
}
/**
* Wraps a callable with checking that only one of these is running.
*/
private Task doWithRunningCheck(Callable> runnable) {
checkIfRunning(true);
Task task;
try {
task = runnable.call();
} catch (Exception e) {
task = Task.forError(e);
}
return task.continueWithTask(new Continuation>() {
@Override
public Task then(Task task) throws Exception {
synchronized (lock) {
isRunning = false;
if (cts != null) {
cts.trySetResult(null);
}
cts = null;
}
return task;
}
});
}
/**
* Retrieves a list of {@link ParseObject}s that satisfy this query from the source in a
* background thread.
*
* This is preferable to using {@link #find()}, unless your code is already running in a
* background thread.
*
* @return A {@link Task} that will be resolved when the find has completed.
*/
public Task> findInBackground() {
return findAsync(builder.build());
}
/**
* Retrieves a list of {@link ParseObject}s that satisfy this query from the source in a
* background thread.
*
* This is preferable to using {@link #find()}, unless your code is already running in a
* background thread.
*
* @param callback
* callback.done(objectList, e) is called when the find completes.
*/
public void findInBackground(final FindCallback callback) {
final State state = builder.build();
final Task> task;
if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK ||
state.isFromLocalDatastore()) {
task = findAsync(state);
} else {
task = doCacheThenNetwork(state, callback, new CacheThenNetworkCallable>>() {
@Override
public Task> call(State state, ParseUser user, Task cancellationToken) {
return findAsync(state, user, cancellationToken);
}
});
}
ParseTaskUtils.callbackOnMainThreadAsync(task, callback);
}
private Task> findAsync(final State state) {
return doWithRunningCheck(new Callable>>() {
@Override
public Task> call() throws Exception {
return getUserAsync(state).onSuccessTask(new Continuation>>() {
@Override
public Task> then(Task task) throws Exception {
final ParseUser user = task.getResult();
return findAsync(state, user, cts.getTask());
}
});
}
});
}
/* package */ Task> findAsync(State state, ParseUser user, Task cancellationToken) {
return ParseQuery.getQueryController().findAsync(state, user, cancellationToken);
}
/**
* Retrieves at most one {@link ParseObject} that satisfies this query from the source in a
* background thread.
*
* This is preferable to using {@link #getFirst()}, unless your code is already running in a
* background thread.
*
* Note: This mutates the {@code ParseQuery}.
*
* @return A {@link Task} that will be resolved when the get has completed.
*/
public Task getFirstInBackground() {
final State state = builder.setLimit(1)
.build();
return getFirstAsync(state);
}
/**
* Retrieves at most one {@link ParseObject} that satisfies this query from the source in a
* background thread.
*
* This is preferable to using {@link #getFirst()}, unless your code is already running in a
* background thread.
*
* Note: This mutates the {@code ParseQuery}.
*
* @param callback
* callback.done(object, e) is called when the find completes.
*/
public void getFirstInBackground(final GetCallback callback) {
final State state = builder.setLimit(1)
.build();
final Task task;
if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK ||
state.isFromLocalDatastore()) {
task = getFirstAsync(state);
} else {
task = doCacheThenNetwork(state, callback, new CacheThenNetworkCallable>() {
@Override
public Task call(State state, ParseUser user, Task cancellationToken) {
return getFirstAsync(state, user, cancellationToken);
}
});
}
ParseTaskUtils.callbackOnMainThreadAsync(task, callback);
}
private Task getFirstAsync(final State state) {
return doWithRunningCheck(new Callable>() {
@Override
public Task call() throws Exception {
return getUserAsync(state).onSuccessTask(new Continuation>() {
@Override
public Task then(Task task) throws Exception {
final ParseUser user = task.getResult();
return getFirstAsync(state, user, cts.getTask());
}
});
}
});
}
private Task getFirstAsync(State state, ParseUser user, Task cancellationToken) {
return ParseQuery.getQueryController().getFirstAsync(state, user, cancellationToken);
}
/**
* Counts the number of objects that match this query. This does not use caching.
*
* @throws ParseException
* Throws an exception when the network connection fails or when the query is invalid.
*/
public int count() throws ParseException {
return ParseTaskUtils.wait(countInBackground());
}
/**
* Counts the number of objects that match this query in a background thread. This does not use
* caching.
*
* @return A {@link Task} that will be resolved when the count has completed.
*/
public Task countInBackground() {
State.Builder copy = new State.Builder(builder);
final State state = copy.setLimit(0).build();
return countAsync(state);
}
/**
* Counts the number of objects that match this query in a background thread. This does not use
* caching.
*
* @param callback
* callback.done(count, e) will be called when the count completes.
*/
public void countInBackground(final CountCallback callback) {
State.Builder copy = new State.Builder(builder);
final State state = copy.setLimit(0).build();
// Hack to workaround CountCallback's non-uniform signature.
final ParseCallback2 c = callback != null
? new ParseCallback2() {
@Override
public void done(Integer integer, ParseException e) {
callback.done(e == null ? integer : -1, e);
}
}
: null;
final Task task;
if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK ||
state.isFromLocalDatastore()) {
task = countAsync(state);
} else {
task = doCacheThenNetwork(state, c, new CacheThenNetworkCallable>() {
@Override
public Task call(State state, ParseUser user, Task cancellationToken) {
return countAsync(state, user, cancellationToken);
}
});
}
ParseTaskUtils.callbackOnMainThreadAsync(task, c);
}
private Task countAsync(final State state) {
return doWithRunningCheck(new Callable>() {
@Override
public Task call() throws Exception {
return getUserAsync(state).onSuccessTask(new Continuation>() {
@Override
public Task then(Task task) throws Exception {
final ParseUser user = task.getResult();
return countAsync(state, user, cts.getTask());
}
});
}
});
}
private Task countAsync(State state, ParseUser user, Task cancellationToken) {
return ParseQuery.getQueryController().countAsync(state, user, cancellationToken);
}
/**
* Constructs a {@link ParseObject} whose id is already known by fetching data from the source.
*
* Note: This mutates the {@code ParseQuery}.
*
* @param objectId
* Object id of the {@link ParseObject} to fetch.
* @throws ParseException
* Throws an exception when there is no such object or when the network connection
* fails.
*
* @see ParseException#OBJECT_NOT_FOUND
*/
public T get(final String objectId) throws ParseException {
return ParseTaskUtils.wait(getInBackground(objectId));
}
/**
* Returns whether or not this query has a cached result.
*/
//TODO (grantland): should be done Async since it does disk i/o & calls through to current user
public boolean hasCachedResult() {
throwIfLDSEnabled();
// TODO(grantland): Is there a more efficient way to accomplish this rather than building a
// new state just to check it's cacheKey?
State state = builder.build();
ParseUser user = null;
try {
user = ParseTaskUtils.wait(getUserAsync(state));
} catch (ParseException e) {
// do nothing
}
String sessionToken = user != null ? user.getSessionToken() : null;
/*
* TODO: Once the count queries are cached, only return false when both queries miss in the
* cache.
*/
String raw = ParseKeyValueCache.loadFromKeyValueCache(
ParseRESTQueryCommand.findCommand(state, sessionToken).getCacheKey(), state.maxCacheAge()
);
return raw != null;
}
/**
* Removes the previously cached result for this query, forcing the next find() to hit the
* network. If there is no cached result for this query, then this is a no-op.
*/
//TODO (grantland): should be done Async since it does disk i/o & calls through to current user
public void clearCachedResult() {
throwIfLDSEnabled();
// TODO(grantland): Is there a more efficient way to accomplish this rather than building a
// new state just to check it's cacheKey?
State state = builder.build();
ParseUser user = null;
try {
user = ParseTaskUtils.wait(getUserAsync(state));
} catch (ParseException e) {
// do nothing
}
String sessionToken = user != null ? user.getSessionToken() : null;
// TODO: Once the count queries are cached, handle the cached results of the count query.
ParseKeyValueCache.clearFromKeyValueCache(
ParseRESTQueryCommand.findCommand(state, sessionToken).getCacheKey()
);
}
/**
* Clears the cached result for all queries.
*/
public static void clearAllCachedResults() {
throwIfLDSEnabled();
ParseKeyValueCache.clearKeyValueCacheDir();
}
/**
* Constructs a {@link ParseObject} whose id is already known by fetching data from the source in a
* background thread. This does not use caching.
*
* This is preferable to using the {@link ParseObject#createWithoutData(String, String)}, unless
* your code is already running in a background thread.
*
* @param objectId
* Object id of the {@link ParseObject} to fetch.
*
* @return A {@link Task} that is resolved when the fetch completes.
*/
// TODO(grantland): Why is this an instance method? Shouldn't this just be a static method since
// other parameters don't even make sense here?
// We'll need to add a version with CancellationToken if we do.
public Task getInBackground(final String objectId) {
final State state = builder.setSkip(-1)
.whereObjectIdEquals(objectId)
.build();
return getFirstAsync(state);
}
/**
* Constructs a {@link ParseObject} whose id is already known by fetching data from the source in
* a background thread. This does not use caching.
*
* This is preferable to using the {@link ParseObject#createWithoutData(String, String)}, unless
* your code is already running in a background thread.
*
* @param objectId
* Object id of the {@link ParseObject} to fetch.
* @param callback
* callback.done(object, e) will be called when the fetch completes.
*/
// TODO(grantland): Why is this an instance method? Shouldn't this just be a static method since
// other parameters don't even make sense here?
// We'll need to add a version with CancellationToken if we do.
public void getInBackground(final String objectId, final GetCallback callback) {
final State state = builder.setSkip(-1)
.whereObjectIdEquals(objectId)
.build();
final Task task;
if (state.cachePolicy() != CachePolicy.CACHE_THEN_NETWORK ||
state.isFromLocalDatastore()) {
task = getFirstAsync(state);
} else {
task = doCacheThenNetwork(state, callback, new CacheThenNetworkCallable>() {
@Override
public Task call(State state, ParseUser user, Task cancellationToken) {
return getFirstAsync(state, user, cancellationToken);
}
});
}
ParseTaskUtils.callbackOnMainThreadAsync(task, callback);
}
//region CACHE_THEN_NETWORK
/**
* Helper method for CACHE_THEN_NETWORK.
*
* Serially executes the {@code delegate} once in cache with the {@code} callback and then returns
* a task for the execution of the second {@code delegate} execution on the network for the caller
* to callback on.
*/
private Task doCacheThenNetwork(
final ParseQuery.State state,
final ParseCallback2 callback,
final CacheThenNetworkCallable> delegate) {
return doWithRunningCheck(new Callable>() {
@Override
public Task call() throws Exception {
return getUserAsync(state).onSuccessTask(new Continuation>() {
@Override
public Task then(Task task) throws Exception {
final ParseUser user = task.getResult();
final State cacheState = new State.Builder(state)
.setCachePolicy(CachePolicy.CACHE_ONLY)
.build();
final State networkState = new State.Builder(state)
.setCachePolicy(CachePolicy.NETWORK_ONLY)
.build();
Task executionTask = delegate.call(cacheState, user, cts.getTask());
executionTask = ParseTaskUtils.callbackOnMainThreadAsync(executionTask, callback);
return executionTask.continueWithTask(new Continuation>() {
@Override
public Task then(Task task) throws Exception {
if (task.isCancelled()) {
return task;
}
return delegate.call(networkState, user, cts.getTask());
}
});
}
});
}
});
}
private interface CacheThenNetworkCallable {
TResult call(ParseQuery.State state, ParseUser user, Task cancellationToken);
}
//endregion
/**
* Add a constraint to the query that requires a particular key's value to be equal to the
* provided value.
*
* @param key
* The key to check.
* @param value
* The value that the {@link ParseObject} must contain.
* @return this, so you can chain this call.
*/
public ParseQuery whereEqualTo(String key, Object value) {
checkIfRunning();
builder.whereEqualTo(key, value);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value to be less than the
* provided value.
*
* @param key
* The key to check.
* @param value
* The value that provides an upper bound.
* @return this, so you can chain this call.
*/
public ParseQuery whereLessThan(String key, Object value) {
checkIfRunning();
builder.addCondition(key, "$lt", value);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value to be not equal to the
* provided value.
*
* @param key
* The key to check.
* @param value
* The value that must not be equalled.
* @return this, so you can chain this call.
*/
public ParseQuery whereNotEqualTo(String key, Object value) {
checkIfRunning();
builder.addCondition(key, "$ne", value);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value to be greater than the
* provided value.
*
* @param key
* The key to check.
* @param value
* The value that provides an lower bound.
* @return this, so you can chain this call.
*/
public ParseQuery whereGreaterThan(String key, Object value) {
checkIfRunning();
builder.addCondition(key, "$gt", value);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value to be less than or equal
* to the provided value.
*
* @param key
* The key to check.
* @param value
* The value that provides an upper bound.
* @return this, so you can chain this call.
*/
public ParseQuery whereLessThanOrEqualTo(String key, Object value) {
checkIfRunning();
builder.addCondition(key, "$lte", value);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value to be greater than or
* equal to the provided value.
*
* @param key
* The key to check.
* @param value
* The value that provides an lower bound.
* @return this, so you can chain this call.
*/
public ParseQuery whereGreaterThanOrEqualTo(String key, Object value) {
checkIfRunning();
builder.addCondition(key, "$gte", value);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value to be contained in the
* provided list of values.
*
* @param key
* The key to check.
* @param values
* The values that will match.
* @return this, so you can chain this call.
*/
public ParseQuery whereContainedIn(String key, Collection extends Object> values) {
checkIfRunning();
builder.addCondition(key, "$in", values);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value match another
* {@code ParseQuery}.
*
* This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s.
* Add a constraint to the query that requires a particular key's value to contain every one of
* the provided list of values.
*
* @param key
* The key to check. This key's value must be an array.
* @param values
* The values that will match.
* @return this, so you can chain this call.
*/
public ParseQuery whereContainsAll(String key, Collection> values) {
checkIfRunning();
builder.addCondition(key, "$all", values);
return this;
}
/**
* Add a constraint to the query that requires a particular key's value match another
* {@code ParseQuery}.
*
* This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s.
*
* @param key
* The key to check.
* @param query
* The query that the value should match
* @return this, so you can chain this call.
*/
public ParseQuery whereMatchesQuery(String key, ParseQuery> query) {
checkIfRunning();
builder.whereMatchesQuery(key, query.getBuilder());
return this;
}
/**
* Add a constraint to the query that requires a particular key's value does not match another
* {@code ParseQuery}.
*
* This only works on keys whose values are {@link ParseObject}s or lists of {@link ParseObject}s.
*
* @param key
* The key to check.
* @param query
* The query that the value should not match
* @return this, so you can chain this call.
*/
public ParseQuery whereDoesNotMatchQuery(String key, ParseQuery> query) {
checkIfRunning();
builder.whereDoesNotMatchQuery(key, query.getBuilder());
return this;
}
/**
* Add a constraint to the query that requires a particular key's value matches a value for a key
* in the results of another {@code ParseQuery}.
*
* @param key
* The key whose value is being checked
* @param keyInQuery
* The key in the objects from the sub query to look in
* @param query
* The sub query to run
* @return this, so you can chain this call.
*/
public ParseQuery whereMatchesKeyInQuery(String key, String keyInQuery, ParseQuery> query) {
checkIfRunning();
builder.whereMatchesKeyInQuery(key, keyInQuery, query.getBuilder());
return this;
}
/**
* Add a constraint to the query that requires a particular key's value does not match any value
* for a key in the results of another {@code ParseQuery}.
*
* @param key
* The key whose value is being checked and excluded
* @param keyInQuery
* The key in the objects from the sub query to look in
* @param query
* The sub query to run
* @return this, so you can chain this call.
*/
public ParseQuery whereDoesNotMatchKeyInQuery(String key, String keyInQuery,
ParseQuery> query) {
checkIfRunning();
builder.whereDoesNotMatchKeyInQuery(key, keyInQuery, query.getBuilder());
return this;
}
/**
* Add a constraint to the query that requires a particular key's value not be contained in the
* provided list of values.
*
* @param key
* The key to check.
* @param values
* The values that will not match.
* @return this, so you can chain this call.
*/
public ParseQuery whereNotContainedIn(String key, Collection extends Object> values) {
checkIfRunning();
builder.addCondition(key, "$nin", values);
return this;
}
/**
* Add a proximity based constraint for finding objects with key point values near the point
* given.
*
* @param key
* The key that the {@link ParseGeoPoint} is stored in.
* @param point
* The reference {@link ParseGeoPoint} that is used.
* @return this, so you can chain this call.
*/
public ParseQuery whereNear(String key, ParseGeoPoint point) {
checkIfRunning();
builder.whereNear(key, point);
return this;
}
/**
* Add a proximity based constraint for finding objects with key point values near the point given
* and within the maximum distance given.
*
* Radius of earth used is {@code 3958.8} miles.
*
* @param key
* The key that the {@link ParseGeoPoint} is stored in.
* @param point
* The reference {@link ParseGeoPoint} that is used.
* @param maxDistance
* Maximum distance (in miles) of results to return.
* @return this, so you can chain this call.
*/
public ParseQuery whereWithinMiles(String key, ParseGeoPoint point, double maxDistance) {
checkIfRunning();
return whereWithinRadians(key, point, maxDistance / ParseGeoPoint.EARTH_MEAN_RADIUS_MILE);
}
/**
* Add a proximity based constraint for finding objects with key point values near the point given
* and within the maximum distance given.
*
* Radius of earth used is {@code 6371.0} kilometers.
*
* @param key
* The key that the {@link ParseGeoPoint} is stored in.
* @param point
* The reference {@link ParseGeoPoint} that is used.
* @param maxDistance
* Maximum distance (in kilometers) of results to return.
* @return this, so you can chain this call.
*/
public ParseQuery whereWithinKilometers(String key, ParseGeoPoint point, double maxDistance) {
checkIfRunning();
return whereWithinRadians(key, point, maxDistance / ParseGeoPoint.EARTH_MEAN_RADIUS_KM);
}
/**
* Add a proximity based constraint for finding objects with key point values near the point given
* and within the maximum distance given.
*
* @param key
* The key that the {@link ParseGeoPoint} is stored in.
* @param point
* The reference {@link ParseGeoPoint} that is used.
* @param maxDistance
* Maximum distance (in radians) of results to return.
* @return this, so you can chain this call.
*/
public ParseQuery whereWithinRadians(String key, ParseGeoPoint point, double maxDistance) {
checkIfRunning();
builder.whereNear(key, point)
.maxDistance(key, maxDistance);
return this;
}
/**
* Add a constraint to the query that requires a particular key's coordinates be contained within
* a given rectangular geographic bounding box.
*
* @param key
* The key to be constrained.
* @param southwest
* The lower-left inclusive corner of the box.
* @param northeast
* The upper-right inclusive corner of the box.
* @return this, so you can chain this call.
*/
public ParseQuery whereWithinGeoBox(
String key, ParseGeoPoint southwest, ParseGeoPoint northeast) {
checkIfRunning();
builder.whereWithin(key, southwest, northeast);
return this;
}
/**
* Add a regular expression constraint for finding string values that match the provided regular
* expression.
*
* This may be slow for large datasets.
*
* @param key
* The key that the string to match is stored in.
* @param regex
* The regular expression pattern to match.
* @return this, so you can chain this call.
*/
public ParseQuery whereMatches(String key, String regex) {
checkIfRunning();
builder.addCondition(key, "$regex", regex);
return this;
}
/**
* Add a regular expression constraint for finding string values that match the provided regular
* expression.
*
* This may be slow for large datasets.
*
* @param key
* The key that the string to match is stored in.
* @param regex
* The regular expression pattern to match.
* @param modifiers
* Any of the following supported PCRE modifiers:
* i
- Case insensitive search
* m
- Search across multiple lines of input
* @return this, so you can chain this call.
*/
public ParseQuery whereMatches(String key, String regex, String modifiers) {
checkIfRunning();
builder.addCondition(key, "$regex", regex);
if (modifiers.length() != 0) {
builder.addCondition(key, "$options", modifiers);
}
return this;
}
/**
* Add a constraint for finding string values that contain a provided string.
*
* This will be slow for large datasets.
*
* @param key
* The key that the string to match is stored in.
* @param substring
* The substring that the value must contain.
* @return this, so you can chain this call.
*/
public ParseQuery whereContains(String key, String substring) {
String regex = Pattern.quote(substring);
whereMatches(key, regex);
return this;
}
/**
* Add a constraint for finding string values that start with a provided string.
*
* This query will use the backend index, so it will be fast even for large datasets.
*
* @param key
* The key that the string to match is stored in.
* @param prefix
* The substring that the value must start with.
* @return this, so you can chain this call.
*/
public ParseQuery whereStartsWith(String key, String prefix) {
String regex = "^" + Pattern.quote(prefix);
whereMatches(key, regex);
return this;
}
/**
* Add a constraint for finding string values that end with a provided string.
*
* This will be slow for large datasets.
*
* @param key
* The key that the string to match is stored in.
* @param suffix
* The substring that the value must end with.
* @return this, so you can chain this call.
*/
public ParseQuery whereEndsWith(String key, String suffix) {
String regex = Pattern.quote(suffix) + "$";
whereMatches(key, regex);
return this;
}
/**
* Include nested {@link ParseObject}s for the provided key.
*
* You can use dot notation to specify which fields in the included object that are also fetched.
*
* @param key
* The key that should be included.
* @return this, so you can chain this call.
*/
public ParseQuery include(String key) {
checkIfRunning();
builder.include(key);
return this;
}
/**
* Restrict the fields of returned {@link ParseObject}s to only include the provided keys.
*
* If this is called multiple times, then all of the keys specified in each of the calls will be
* included.
*
* Note: This option will be ignored when querying from the local datastore. This
* is done since all the keys will be in memory anyway and there will be no performance gain from
* removing them.
*
* @param keys
* The set of keys to include in the result.
* @return this, so you can chain this call.
*/
public ParseQuery selectKeys(Collection keys) {
checkIfRunning();
builder.selectKeys(keys);
return this;
}
/**
* Add a constraint for finding objects that contain the given key.
*
* @param key
* The key that should exist.
*
* @return this, so you can chain this call.
*/
public ParseQuery whereExists(String key) {
checkIfRunning();
builder.addCondition(key, "$exists", true);
return this;
}
/**
* Add a constraint for finding objects that do not contain a given key.
*
* @param key
* The key that should not exist
*
* @return this, so you can chain this call.
*/
public ParseQuery whereDoesNotExist(String key) {
checkIfRunning();
builder.addCondition(key, "$exists", false);
return this;
}
/**
* Sorts the results in ascending order by the given key.
*
* @param key
* The key to order by.
* @return this, so you can chain this call.
*/
public ParseQuery orderByAscending(String key) {
checkIfRunning();
builder.orderByAscending(key);
return this;
}
/**
* Also sorts the results in ascending order by the given key.
*
* The previous sort keys have precedence over this key.
*
* @param key
* The key to order by
* @return this, so you can chain this call.
*/
public ParseQuery addAscendingOrder(String key) {
checkIfRunning();
builder.addAscendingOrder(key);
return this;
}
/**
* Sorts the results in descending order by the given key.
*
* @param key
* The key to order by.
* @return this, so you can chain this call.
*/
public ParseQuery orderByDescending(String key) {
checkIfRunning();
builder.orderByDescending(key);
return this;
}
/**
* Also sorts the results in descending order by the given key.
*
* The previous sort keys have precedence over this key.
*
* @param key
* The key to order by
* @return this, so you can chain this call.
*/
public ParseQuery addDescendingOrder(String key) {
checkIfRunning();
builder.addDescendingOrder(key);
return this;
}
/**
* Controls the maximum number of results that are returned.
*
* Setting a negative limit denotes retrieval without a limit. The default limit is {@code 100},
* with a maximum of {@code 1000} results being returned at a time.
*
* @param newLimit The new limit.
* @return this, so you can chain this call.
*/
public ParseQuery setLimit(int newLimit) {
checkIfRunning();
builder.setLimit(newLimit);
return this;
}
/**
* Accessor for the limit.
*/
public int getLimit() {
return builder.getLimit();
}
/**
* Controls the number of results to skip before returning any results.
*
* This is useful for pagination. Default is to skip zero results.
*
* @param newSkip The new skip
* @return this, so you can chain this call.
*/
public ParseQuery setSkip(int newSkip) {
checkIfRunning();
builder.setSkip(newSkip);
return this;
}
/**
* Accessor for the skip value.
*/
public int getSkip() {
return builder.getSkip();
}
/**
* Accessor for the class name.
*/
public String getClassName() {
return builder.getClassName();
}
/**
* Turn on performance tracing of finds.
*
* If performance tracing is already turned on this does nothing. In general you don't need to call trace.
*
* @return this, so you can chain this call.
*/
public ParseQuery setTrace(boolean shouldTrace) {
checkIfRunning();
builder.setTracingEnabled(shouldTrace);
return this;
}
}