com.google.firebase.database.Query Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of firebase-admin Show documentation
Show all versions of firebase-admin Show documentation
This is the official Firebase Admin Java SDK. Build extraordinary native JVM apps in
minutes with Firebase. The Firebase platform can power your app’s backend, user
authentication, static hosting, and more.
/*
* Copyright 2017 Google Inc.
*
* Licensed 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 com.google.firebase.database;
import static com.google.firebase.database.utilities.Utilities.hardAssert;
import com.google.firebase.database.core.ChildEventRegistration;
import com.google.firebase.database.core.EventRegistration;
import com.google.firebase.database.core.Path;
import com.google.firebase.database.core.Repo;
import com.google.firebase.database.core.ValueEventRegistration;
import com.google.firebase.database.core.ZombieEventManager;
import com.google.firebase.database.core.view.QueryParams;
import com.google.firebase.database.core.view.QuerySpec;
import com.google.firebase.database.snapshot.BooleanNode;
import com.google.firebase.database.snapshot.ChildKey;
import com.google.firebase.database.snapshot.DoubleNode;
import com.google.firebase.database.snapshot.EmptyNode;
import com.google.firebase.database.snapshot.Index;
import com.google.firebase.database.snapshot.KeyIndex;
import com.google.firebase.database.snapshot.Node;
import com.google.firebase.database.snapshot.PathIndex;
import com.google.firebase.database.snapshot.PriorityIndex;
import com.google.firebase.database.snapshot.PriorityUtilities;
import com.google.firebase.database.snapshot.StringNode;
import com.google.firebase.database.snapshot.ValueIndex;
import com.google.firebase.database.utilities.Validation;
/**
* The Query class (and its subclass, {@link DatabaseReference}) are used for reading data.
* Listeners are attached, and they will be triggered when the corresponding data changes.
*
* Instances of Query are obtained by calling startAt(), endAt(), or limit() on a DatabaseReference.
*/
public class Query {
/**
* @hide
*/
protected final Repo repo;
/**
* @hide
*/
protected final Path path;
/**
* @hide
*/
protected final QueryParams params;
// we can't use params index, because the default query params have priority index set as
// default,
// but we don't want to allow multiple orderByPriority calls, so track them here
private final boolean orderByCalled;
Query(Repo repo, Path path, QueryParams params, boolean orderByCalled) throws DatabaseException {
hardAssert(params.isValid(), "Validation of queries failed.");
this.repo = repo;
this.path = path;
this.params = params;
this.orderByCalled = orderByCalled;
}
Query(Repo repo, Path path) {
this(repo, path, QueryParams.DEFAULT_PARAMS, false);
}
/**
* This method validates that key index has been called with the correct combination of
* parameters.
*/
private void validateQueryEndpoints(QueryParams params) {
if (params.getIndex().equals(KeyIndex.getInstance())) {
String message =
"You must use startAt(String value), endAt(String value) or "
+ "equalTo(String value) in combination with orderByKey(). Other type of values or "
+ "using the version with 2 parameters is not supported";
if (params.hasStart()) {
Node startNode = params.getIndexStartValue();
ChildKey startName = params.getIndexStartName();
if (startName != ChildKey.getMinName() || !(startNode instanceof StringNode)) {
throw new IllegalArgumentException(message);
}
}
if (params.hasEnd()) {
Node endNode = params.getIndexEndValue();
ChildKey endName = params.getIndexEndName();
if (endName != ChildKey.getMaxName() || !(endNode instanceof StringNode)) {
throw new IllegalArgumentException(message);
}
}
} else if (params.getIndex().equals(PriorityIndex.getInstance())) {
if ((params.hasStart() && !PriorityUtilities.isValidPriority(params.getIndexStartValue()))
|| (params.hasEnd() && !PriorityUtilities.isValidPriority(params.getIndexEndValue()))) {
throw new IllegalArgumentException(
"When using orderByPriority(), values provided to startAt(), "
+ "endAt(), or equalTo() must be valid priorities.");
}
}
}
/**
* This method validates that limit has been called with the correct combination or parameters.
*/
private void validateLimit(QueryParams params) {
if (params.hasStart() && params.hasEnd() && params.hasLimit() && !params.hasAnchoredLimit()) {
throw new IllegalArgumentException(
"Can't combine startAt(), endAt() and limit(). "
+ "Use limitToFirst() or limitToLast() instead");
}
}
/** This method validates that the equalTo call can be made. */
private void validateEqualToCall() {
if (params.hasStart()) {
throw new IllegalArgumentException("Can't call equalTo() and startAt() combined");
}
if (params.hasEnd()) {
throw new IllegalArgumentException("Can't call equalTo() and endAt() combined");
}
}
/** This method validates that only one order by call has been made. */
private void validateNoOrderByCall() {
if (this.orderByCalled) {
throw new IllegalArgumentException("You can't combine multiple orderBy calls!");
}
}
/**
* Add a listener for changes in the data at this location. Each time time the data changes, your
* listener will be called with an immutable snapshot of the data.
*
* @param listener The listener to be called with changes
* @return A reference to the listener provided. Save this to remove the listener later.
*/
public ValueEventListener addValueEventListener(ValueEventListener listener) {
addEventRegistration(new ValueEventRegistration(repo, listener, getSpec()));
return listener;
}
/**
* Add a listener for child events occurring at this location. When child locations are added,
* removed, changed, or moved, the listener will be triggered for the appropriate event.
*
* @param listener The listener to be called with changes
* @return A reference to the listener provided. Save this to remove the listener later.
*/
public ChildEventListener addChildEventListener(ChildEventListener listener) {
addEventRegistration(new ChildEventRegistration(repo, listener, getSpec()));
return listener;
}
/**
* Add a listener for a single change in the data at this location. This listener will be
* triggered once with the value of the data at the location.
*
* @param listener The listener to be called with the data
*/
public void addListenerForSingleValueEvent(final ValueEventListener listener) {
addEventRegistration(
new ValueEventRegistration(
repo,
new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
// Removing the event listener will also prevent any further calls into
// onDataChange
removeEventListener(this);
listener.onDataChange(snapshot);
}
@Override
public void onCancelled(DatabaseError error) {
listener.onCancelled(error);
}
},
getSpec()));
}
/**
* Remove the specified listener from this location.
*
* @param listener The listener to remove
*/
public void removeEventListener(final ValueEventListener listener) {
if (listener == null) {
throw new NullPointerException("listener must not be null");
}
removeEventRegistration(new ValueEventRegistration(repo, listener, getSpec()));
}
/**
* Remove the specified listener from this location.
*
* @param listener The listener to remove
*/
public void removeEventListener(final ChildEventListener listener) {
if (listener == null) {
throw new NullPointerException("listener must not be null");
}
removeEventRegistration(new ChildEventRegistration(repo, listener, getSpec()));
}
private void removeEventRegistration(final EventRegistration registration) {
ZombieEventManager.getInstance().zombifyForRemove(registration);
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.removeEventCallback(registration);
}
});
}
private void addEventRegistration(final EventRegistration listener) {
ZombieEventManager.getInstance().recordEventRegistration(listener);
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.addEventCallback(listener);
}
});
}
/**
* By calling {@code keepSynced(true)} on a location, the data for that location will
* automatically be downloaded and kept in sync, even when no listeners are attached for that
* location. Additionally, while a location is kept synced, it will not be evicted from the
* persistent disk cache.
*
* @param keepSynced Pass {@code true} to keep this location synchronized, pass {@code false} to
* stop synchronization.
* @since 2.3
*/
public void keepSynced(final boolean keepSynced) {
if (!this.path.isEmpty() && this.path.getFront().equals(ChildKey.getInfoKey())) {
throw new DatabaseException("Can't call keepSynced() on .info paths.");
}
repo.scheduleNow(
new Runnable() {
@Override
public void run() {
repo.keepSynced(getSpec(), keepSynced);
}
});
}
/* Removes all of the event listeners at this location */
/*public void removeAllEventListeners() {
Query.scheduleNow(new Runnable() {
@Override
public void run() {
repo.removeEventCallback(Query.this, null);
}
});
}*/
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(String value) {
return startAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(double value) {
return startAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to start at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query startAt(boolean value) {
return startAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key greater than or equal to the given key.
*
* @param value The priority to start at, inclusive
* @param key The key to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(String value, String key) {
Node node =
value != null ? new StringNode(value, PriorityUtilities.NullPriority()) : EmptyNode.Empty();
return startAt(node, key);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key greater than or equal to the given key.
*
* @param value The priority to start at, inclusive
* @param key The key name to start at, inclusive
* @return A Query with the new constraint
*/
public Query startAt(double value, String key) {
return startAt(new DoubleNode(value, PriorityUtilities.NullPriority()), key);
}
/**
* Create a query constrained to only return child nodes with a value greater than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key greater than or equal to the given key.
*
* @param value The priority to start at, inclusive
* @param key The key to start at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query startAt(boolean value, String key) {
return startAt(new BooleanNode(value, PriorityUtilities.NullPriority()), key);
}
private Query startAt(Node node, String key) {
Validation.validateNullableKey(key);
if (!(node.isLeafNode() || node.isEmpty())) {
throw new IllegalArgumentException("Can only use simple values for startAt()");
}
if (params.hasStart()) {
throw new IllegalArgumentException("Can't call startAt() or equalTo() multiple times");
}
ChildKey childKey = key != null ? ChildKey.fromString(key) : null;
QueryParams newParams = params.startAt(node, childKey);
validateLimit(newParams);
validateQueryEndpoints(newParams);
assert newParams.isValid();
return new Query(repo, path, newParams, orderByCalled);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(String value) {
return endAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(double value) {
return endAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default.
*
* @param value The value to end at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query endAt(boolean value) {
return endAt(value, null);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key key less than or equal to the given key.
*
* @param value The value to end at, inclusive
* @param key The key to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(String value, String key) {
Node node =
value != null ? new StringNode(value, PriorityUtilities.NullPriority()) : EmptyNode.Empty();
return endAt(node, key);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key less than or equal to the given key.
*
* @param value The value to end at, inclusive
* @param key The key to end at, inclusive
* @return A Query with the new constraint
*/
public Query endAt(double value, String key) {
return endAt(new DoubleNode(value, PriorityUtilities.NullPriority()), key);
}
/**
* Create a query constrained to only return child nodes with a value less than or equal to the
* given value, using the given orderBy directive or priority as default, and additionally only
* child nodes with a key less than or equal to the given key.
*
* @param value The value to end at, inclusive
* @param key The key to end at, inclusive
* @return A Query with the new constraint
* @since 2.0
*/
public Query endAt(boolean value, String key) {
return endAt(new BooleanNode(value, PriorityUtilities.NullPriority()), key);
}
private Query endAt(Node node, String key) {
Validation.validateNullableKey(key);
if (!(node.isLeafNode() || node.isEmpty())) {
throw new IllegalArgumentException("Can only use simple values for endAt()");
}
ChildKey childKey = key != null ? ChildKey.fromString(key) : null;
if (params.hasEnd()) {
throw new IllegalArgumentException("Can't call endAt() or equalTo() multiple times");
}
QueryParams newParams = params.endAt(node, childKey);
validateLimit(newParams);
validateQueryEndpoints(newParams);
assert newParams.isValid();
return new Query(repo, path, newParams, orderByCalled);
}
/**
* Create a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public Query equalTo(String value) {
validateEqualToCall();
return this.startAt(value).endAt(value);
}
/**
* Create a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
*/
public Query equalTo(double value) {
validateEqualToCall();
return this.startAt(value).endAt(value);
}
/**
* Create a query constrained to only return child nodes with the given value.
*
* @param value The value to query for
* @return A query with the new constraint
* @since 2.0
*/
public Query equalTo(boolean value) {
validateEqualToCall();
return this.startAt(value).endAt(value);
}
/**
* Create a query constrained to only return the child node with the given key and value. Note
* that there is at most one such child as names are unique.
*
* @param value The value to query for
* @param key The key of the child
* @return A query with the new constraint
*/
public Query equalTo(String value, String key) {
validateEqualToCall();
return this.startAt(value, key).endAt(value, key);
}
/**
* Create a query constrained to only return the child node with the given key and value. Note
* that there is at most one such child as keys are unique.
*
* @param value The value to query for
* @param key The key of the child
* @return A query with the new constraint
*/
public Query equalTo(double value, String key) {
validateEqualToCall();
return this.startAt(value, key).endAt(value, key);
}
/**
* Create a query constrained to only return the child node with the given key and value. Note
* that there is at most one such child as keys are unique.
*
* @param value The value to query for
* @param key The name of the child
* @return A query with the new constraint
*/
public Query equalTo(boolean value, String key) {
validateEqualToCall();
return this.startAt(value, key).endAt(value, key);
}
/**
* Create a query with limit and anchor it to the start of the window.
*
* @param limit The maximum number of child nodes to return
* @return A Query with the new constraint
* @since 2.0
*/
public Query limitToFirst(int limit) {
if (limit <= 0) {
throw new IllegalArgumentException("Limit must be a positive integer!");
}
if (params.hasLimit()) {
throw new IllegalArgumentException(
"Can't call limitToLast on query with previously set limit!");
}
return new Query(repo, path, params.limitToFirst(limit), orderByCalled);
}
/**
* Create a query with limit and anchor it to the end of the window.
*
* @param limit The maximum number of child nodes to return
* @return A Query with the new constraint
* @since 2.0
*/
public Query limitToLast(int limit) {
if (limit <= 0) {
throw new IllegalArgumentException("Limit must be a positive integer!");
}
if (params.hasLimit()) {
throw new IllegalArgumentException(
"Can't call limitToLast on query with previously set limit!");
}
return new Query(repo, path, params.limitToLast(limit), orderByCalled);
}
/**
* Create a query in which child nodes are ordered by the values of the specified path.
*
* @param path The path to the child node to use for sorting
* @return A Query with the new constraint
* @since 2.0
*/
public Query orderByChild(String path) {
if (path == null) {
throw new NullPointerException("Key can't be null");
}
if (path.equals("$key") || path.equals(".key")) {
throw new IllegalArgumentException(
"Can't use '" + path + "' as path, please use orderByKey() instead!");
}
if (path.equals("$priority") || path.equals(".priority")) {
throw new IllegalArgumentException(
"Can't use '" + path + "' as path, please use orderByPriority() instead!");
}
if (path.equals("$value") || path.equals(".value")) {
throw new IllegalArgumentException(
"Can't use '" + path + "' as path, please use orderByValue() instead!");
}
Validation.validatePathString(path);
validateNoOrderByCall();
Path indexPath = new Path(path);
if (indexPath.size() == 0) {
throw new IllegalArgumentException("Can't use empty path, use orderByValue() instead!");
}
Index index = new PathIndex(indexPath);
return new Query(repo, this.path, params.orderBy(index), true);
}
/**
* Create a query in which child nodes are ordered by their priorities.
*
* @return A Query with the new constraint
* @since 2.0
*/
public Query orderByPriority() {
validateNoOrderByCall();
QueryParams newParams = params.orderBy(PriorityIndex.getInstance());
validateQueryEndpoints(newParams);
return new Query(repo, path, newParams, true);
}
/**
* Create a query in which child nodes are ordered by their keys.
*
* @return A Query with the new constraint
* @since 2.0
*/
public Query orderByKey() {
validateNoOrderByCall();
QueryParams newParams = this.params.orderBy(KeyIndex.getInstance());
validateQueryEndpoints(newParams);
return new Query(repo, path, newParams, true);
}
/**
* Create a query in which nodes are ordered by their value
*
* @return A Query with the new constraint
* @since 2.2
*/
public Query orderByValue() {
validateNoOrderByCall();
return new Query(repo, path, params.orderBy(ValueIndex.getInstance()), true);
}
/**
* @return A DatabaseReference to this location
*/
public DatabaseReference getRef() {
return new DatabaseReference(repo, getPath());
}
// Need to hide these...
/**
* For internal use
*
* @hide
* @return The path to this location
*/
public Path getPath() {
return path;
}
/**
* For internal use
*
* @hide
* @return The repo
*/
public Repo getRepo() {
return repo;
}
/**
* For internal use
*
* @hide
* @return The constraints
*/
public QuerySpec getSpec() {
return new QuerySpec(path, params);
}
}