Skip to content

Commit 0d52495

Browse files
committed
Implement get for RTDB queries
1 parent 17506e4 commit 0d52495

File tree

5 files changed

+131
-15
lines changed

5 files changed

+131
-15
lines changed

firebase-database/src/androidTest/java/com/google/firebase/database/QueryTest.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,10 @@
2121
import static org.junit.Assert.assertTrue;
2222
import static org.junit.Assert.fail;
2323

24+
import androidx.annotation.NonNull;
2425
import androidx.test.ext.junit.runners.AndroidJUnit4;
26+
import com.google.android.gms.tasks.OnCompleteListener;
27+
import com.google.android.gms.tasks.Task;
2528
import com.google.firebase.database.core.DatabaseConfig;
2629
import com.google.firebase.database.core.Path;
2730
import com.google.firebase.database.core.RepoManager;
@@ -3401,6 +3404,25 @@ public void onComplete(DatabaseError error, DatabaseReference ref) {
34013404
IntegrationTestHelpers.waitFor(semaphore);
34023405
}
34033406

3407+
@Test
3408+
public void emptyQueryGet() throws DatabaseException, InterruptedException {
3409+
assertTrue(false);
3410+
DatabaseReference node = IntegrationTestHelpers.getRandomNode();
3411+
final Semaphore semaphore = new Semaphore(0);
3412+
node.get()
3413+
.addOnCompleteListener(
3414+
new OnCompleteListener<DataSnapshot>() {
3415+
@Override
3416+
public void onComplete(@NonNull Task<DataSnapshot> task) {
3417+
assertTrue(task.isSuccessful());
3418+
assertNotNull(task.getResult());
3419+
assertFalse(task.getResult().exists());
3420+
semaphore.release();
3421+
}
3422+
});
3423+
IntegrationTestHelpers.waitFor(semaphore);
3424+
}
3425+
34043426
@Test
34053427
public void querySnapshotChildrenRespectDefaultOrdering()
34063428
throws DatabaseException, ExecutionException, TimeoutException, TestFailure,

firebase-database/src/main/java/com/google/firebase/database/Query.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import androidx.annotation.Nullable;
2121
import androidx.annotation.RestrictTo;
2222
import com.google.android.gms.common.internal.Objects;
23+
import com.google.android.gms.tasks.Task;
2324
import com.google.firebase.database.core.ChildEventRegistration;
2425
import com.google.firebase.database.core.EventRegistration;
2526
import com.google.firebase.database.core.Path;
@@ -161,6 +162,15 @@ public ChildEventListener addChildEventListener(@NonNull ChildEventListener list
161162
return listener;
162163
}
163164

165+
/**
166+
* Get the server value for this query, updating cache and raising events if successful. If not
167+
* connected, fall back to a locally-cached value.
168+
*/
169+
@NonNull
170+
Task<DataSnapshot> get() {
171+
return repo.getValue(this);
172+
}
173+
164174
/**
165175
* Add a listener for a single change in the data at this location. This listener will be
166176
* triggered once with the value of the data at the location.

firebase-database/src/main/java/com/google/firebase/database/connection/PersistentConnection.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
package com.google.firebase.database.connection;
1616

17+
import com.google.android.gms.tasks.Task;
1718
import java.util.List;
1819
import java.util.Map;
1920

@@ -56,6 +57,10 @@ void listen(
5657

5758
void unlisten(List<String> path, Map<String, Object> queryParams);
5859

60+
// Get
61+
62+
Task<Object> get(List<String> path, Map<String, Object> queryParams);
63+
5964
// Writes
6065

6166
void purgeOutstandingWrites();

firebase-database/src/main/java/com/google/firebase/database/connection/PersistentConnectionImpl.java

Lines changed: 58 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616

1717
import static com.google.firebase.database.connection.ConnectionUtils.hardAssert;
1818

19+
import com.google.android.gms.tasks.Task;
20+
import com.google.android.gms.tasks.TaskCompletionSource;
1921
import com.google.firebase.database.connection.util.RetryHelper;
2022
import com.google.firebase.database.logging.LogWrapper;
2123
import com.google.firebase.database.util.GAuthToken;
@@ -37,23 +39,23 @@ private interface ConnectionRequestCallback {
3739
void onResponse(Map<String, Object> response);
3840
}
3941

40-
private static class ListenQuerySpec {
42+
private static class QuerySpec {
4143
private final List<String> path;
4244
private final Map<String, Object> queryParams;
4345

44-
public ListenQuerySpec(List<String> path, Map<String, Object> queryParams) {
46+
public QuerySpec(List<String> path, Map<String, Object> queryParams) {
4547
this.path = path;
4648
this.queryParams = queryParams;
4749
}
4850

4951
@Override
5052
public boolean equals(Object o) {
5153
if (this == o) return true;
52-
if (!(o instanceof ListenQuerySpec)) {
54+
if (!(o instanceof QuerySpec)) {
5355
return false;
5456
}
5557

56-
ListenQuerySpec that = (ListenQuerySpec) o;
58+
QuerySpec that = (QuerySpec) o;
5759

5860
if (!path.equals(that.path)) {
5961
return false;
@@ -76,13 +78,13 @@ public String toString() {
7678

7779
private static class OutstandingListen {
7880
private final RequestResultCallback resultCallback;
79-
private final ListenQuerySpec query;
81+
private final QuerySpec query;
8082
private final ListenHashProvider hashFunction;
8183
private final Long tag;
8284

8385
private OutstandingListen(
8486
RequestResultCallback callback,
85-
ListenQuerySpec query,
87+
QuerySpec query,
8688
Long tag,
8789
ListenHashProvider hashFunction) {
8890
this.resultCallback = callback;
@@ -91,7 +93,7 @@ private OutstandingListen(
9193
this.tag = tag;
9294
}
9395

94-
public ListenQuerySpec getQuery() {
96+
public QuerySpec getQuery() {
9597
return query;
9698
}
9799

@@ -256,7 +258,7 @@ private enum ConnectionState {
256258
private List<OutstandingDisconnect> onDisconnectRequestQueue;
257259
private Map<Long, OutstandingPut> outstandingPuts;
258260

259-
private Map<ListenQuerySpec, OutstandingListen> listens;
261+
private Map<QuerySpec, OutstandingListen> listens;
260262
private String authToken;
261263
private boolean forceAuthTokenRefresh;
262264
private final ConnectionContext context;
@@ -281,7 +283,7 @@ public PersistentConnectionImpl(
281283
this.executorService = context.getExecutorService();
282284
this.authTokenProvider = context.getAuthTokenProvider();
283285
this.hostInfo = info;
284-
this.listens = new HashMap<ListenQuerySpec, OutstandingListen>();
286+
this.listens = new HashMap<QuerySpec, OutstandingListen>();
285287
this.requestCBHash = new HashMap<Long, ConnectionRequestCallback>();
286288
this.outstandingPuts = new HashMap<Long, OutstandingPut>();
287289
this.onDisconnectRequestQueue = new ArrayList<OutstandingDisconnect>();
@@ -328,7 +330,7 @@ public void listen(
328330
ListenHashProvider currentHashFn,
329331
Long tag,
330332
RequestResultCallback listener) {
331-
ListenQuerySpec query = new ListenQuerySpec(path, queryParams);
333+
QuerySpec query = new QuerySpec(path, queryParams);
332334
if (logger.logsDebug()) logger.debug("Listening on " + query);
333335
// TODO: Fix this somehow?
334336
// hardAssert(query.isDefault() || !query.loadsAllData(), "listen() called for non-default but
@@ -344,6 +346,21 @@ public void listen(
344346
doIdleCheck();
345347
}
346348

349+
@Override
350+
public Task<Object> get(List<String> path, Map<String, Object> queryParams) {
351+
QuerySpec query = new QuerySpec(path, queryParams);
352+
TaskCompletionSource<Object> source = new TaskCompletionSource<>();
353+
Task<Object> task;
354+
if (connected()) {
355+
task = sendGet(query, source);
356+
} else {
357+
source.setException(new Exception("Client offline!"));
358+
task = source.getTask();
359+
}
360+
doIdleCheck();
361+
return task;
362+
}
363+
347364
@Override
348365
public void initialize() {
349366
this.tryScheduleReconnect();
@@ -456,7 +473,7 @@ public void onKill(String reason) {
456473

457474
@Override
458475
public void unlisten(List<String> path, Map<String, Object> queryParams) {
459-
ListenQuerySpec query = new ListenQuerySpec(path, queryParams);
476+
QuerySpec query = new QuerySpec(path, queryParams);
460477
if (logger.logsDebug()) logger.debug("unlistening on " + query);
461478

462479
// TODO: fix this by understanding query params?
@@ -731,7 +748,7 @@ private void sendUnlisten(OutstandingListen listen) {
731748
sendAction(REQUEST_ACTION_QUERY_UNLISTEN, request, null);
732749
}
733750

734-
private OutstandingListen removeListen(ListenQuerySpec query) {
751+
private OutstandingListen removeListen(QuerySpec query) {
735752
if (logger.logsDebug()) logger.debug("removing query " + query);
736753
if (!listens.containsKey(query)) {
737754
if (logger.logsDebug())
@@ -749,8 +766,8 @@ private OutstandingListen removeListen(ListenQuerySpec query) {
749766
private Collection<OutstandingListen> removeListens(List<String> path) {
750767
if (logger.logsDebug()) logger.debug("removing all listens at path " + path);
751768
List<OutstandingListen> removedListens = new ArrayList<OutstandingListen>();
752-
for (Map.Entry<ListenQuerySpec, OutstandingListen> entry : listens.entrySet()) {
753-
ListenQuerySpec query = entry.getKey();
769+
for (Map.Entry<QuerySpec, OutstandingListen> entry : listens.entrySet()) {
770+
QuerySpec query = entry.getKey();
754771
OutstandingListen listen = entry.getValue();
755772
if (query.path.equals(path)) {
756773
removedListens.add(listen);
@@ -1049,6 +1066,32 @@ public void onResponse(Map<String, Object> response) {
10491066
});
10501067
}
10511068

1069+
private Task<Object> sendGet(final QuerySpec query, TaskCompletionSource<Object> source) {
1070+
Map<String, Object> request = new HashMap<String, Object>();
1071+
request.put(REQUEST_PATH, ConnectionUtils.pathToString(query.path));
1072+
request.put(REQUEST_QUERIES, query.queryParams);
1073+
sendAction(
1074+
REQUEST_ACTION_QUERY,
1075+
request,
1076+
new ConnectionRequestCallback() {
1077+
@Override
1078+
public void onResponse(Map<String, Object> response) {
1079+
String status = (String) response.get(REQUEST_STATUS);
1080+
if (status.equals("ok")) {
1081+
String pathString = (String) response.get(SERVER_DATA_UPDATE_PATH);
1082+
List<String> path = ConnectionUtils.stringToPath(pathString);
1083+
Object body = response.get(SERVER_DATA_UPDATE_BODY);
1084+
Long tagNumber = ConnectionUtils.longFromObject(response.get(SERVER_DATA_TAG));
1085+
delegate.onDataUpdate(path, body, /*isMerge=*/ false, /*tagNumber=*/ tagNumber);
1086+
source.setResult(body);
1087+
} else {
1088+
source.setException(new Exception((String) response.get(SERVER_DATA_UPDATE_BODY)));
1089+
}
1090+
}
1091+
});
1092+
return source.getTask();
1093+
}
1094+
10521095
private void sendListen(final OutstandingListen listen) {
10531096
Map<String, Object> request = new HashMap<String, Object>();
10541097
request.put(REQUEST_PATH, ConnectionUtils.pathToString(listen.getQuery().path));
@@ -1136,7 +1179,7 @@ public void onResponse(Map<String, Object> response) {
11361179
}
11371180

11381181
@SuppressWarnings("unchecked")
1139-
private void warnOnListenerWarnings(List<String> warnings, ListenQuerySpec query) {
1182+
private void warnOnListenerWarnings(List<String> warnings, QuerySpec query) {
11401183
if (warnings.contains("no_index")) {
11411184
String indexSpec = "\".indexOn\": \"" + query.queryParams.get("i") + '\"';
11421185
logger.warn(

firebase-database/src/main/java/com/google/firebase/database/core/Repo.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,18 @@
1616

1717
import static com.google.firebase.database.core.utilities.Utilities.hardAssert;
1818

19+
import androidx.annotation.NonNull;
20+
import com.google.android.gms.tasks.Continuation;
21+
import com.google.android.gms.tasks.Task;
22+
import com.google.android.gms.tasks.TaskCompletionSource;
1923
import com.google.firebase.database.DataSnapshot;
2024
import com.google.firebase.database.DatabaseError;
2125
import com.google.firebase.database.DatabaseException;
2226
import com.google.firebase.database.DatabaseReference;
2327
import com.google.firebase.database.FirebaseDatabase;
2428
import com.google.firebase.database.InternalHelpers;
2529
import com.google.firebase.database.MutableData;
30+
import com.google.firebase.database.Query;
2631
import com.google.firebase.database.Transaction;
2732
import com.google.firebase.database.ValueEventListener;
2833
import com.google.firebase.database.annotations.NotNull;
@@ -462,6 +467,37 @@ public void onRequestResult(String optErrorCode, String optErrorMessage) {
462467
this.rerunTransactions(affectedPath);
463468
}
464469

470+
public Task<DataSnapshot> getValue(Query query) {
471+
return connection
472+
.get(query.getPath().asList(), query.getSpec().getParams().getWireProtocolParams())
473+
.continueWithTask(
474+
new Continuation<Object, Task<DataSnapshot>>() {
475+
@Override
476+
public Task<DataSnapshot> then(@NonNull Task<Object> task) throws Exception {
477+
TaskCompletionSource<DataSnapshot> source = new TaskCompletionSource<>();
478+
if (!task.isSuccessful()) {
479+
Node cached =
480+
serverSyncTree.calcCompleteEventCache(query.getPath(), new ArrayList<>());
481+
if (cached.isEmpty()) {
482+
source.setException(new Exception("Client offline with empty cache!"));
483+
} else {
484+
source.setResult(
485+
InternalHelpers.createDataSnapshot(
486+
query.getRef(), IndexedNode.from(cached, query.getSpec().getIndex())));
487+
}
488+
} else {
489+
Node serverNode = NodeUtilities.NodeFromJSON(task.getResult());
490+
postEvents(serverSyncTree.applyServerOverwrite(query.getPath(), serverNode));
491+
source.setResult(
492+
InternalHelpers.createDataSnapshot(
493+
query.getRef(),
494+
IndexedNode.from(serverNode, query.getSpec().getIndex())));
495+
}
496+
return source.getTask();
497+
}
498+
});
499+
}
500+
465501
public void updateChildren(
466502
final Path path,
467503
CompoundWrite updates,

0 commit comments

Comments
 (0)