Skip to content

Commit bae22e0

Browse files
feat: ability to serialize Query to Proto (#241)
### Feature Add new methods `toProto` and `fromProto` to Query, allowing serialization to and deserialization from RunQueryRequest. ### Refactoring FieldFilter in Query has had a `fromProto` defined on it. Properties in FieldFilter and corresponding subclasses have been refactored to use proto types internally rather than previously using high level api types. Add a new interface FirestoreRpcContext which defines all the methods needed by model classes that were only provided by FirestoreImpl. FirestoreImpl now implements FirestoreRpcContext, and the new interface methods are all the same implementation as before. All dependency on FirestoreImpl from the following classes has been replaced with FirestoreRpcContext: * CollectionReference * DocumentReference * DocumentSnapshot * Query * QueryDocumentSnapshot Motivation for this change is due to the fact that we want to expose a new method on Query `fromProto` to allow a Query to be loaded from a proto. However, Query requires additional context & access to be able execute queries and thus has a dependency on a Firestore instance as well as those methods now defined in FirestoreRpcContext. Due to the constraints of Java's type system, the constraint on the instance passed to `fromProto` is runtime checked versus compile time checked. The method signature accepts any `Firestore` instance, but we assert that the instance is also a `FirestoreRpcContext`. This isn't ideal as it is a runtime error when not satisfied. However, Firestore is annotated @InternalExtensionOnly so we have policy control that the instance returned from FirestoreOptions will always also implement FirestoreRpcContext. A test has been added to verify that a proxy instance of FirestoreImpl could satisfy the compile and runtime checks present such that use should allow the code to work with java ee container based approaches using dynamic class proxies for things like instrumentation and dependency injection. As part of the change, the constructors for the above mentioned model classes have had their scope narrowed to package private from protected. The classes themselves are now also annotated with @InternalExtensionOnly along with the note in their Javadoc stating the policy. clirr rules have been added to ignore the constructor changes in the model classes. Co-authored-by: Sebastian Schmidt <mrschmidt@google.com> Co-authored-by: BenWhitehead <BenWhitehead@users.noreply.github.com>
1 parent bde9643 commit bae22e0

File tree

12 files changed

+553
-162
lines changed

12 files changed

+553
-162
lines changed

google-cloud-firestore/clirr-ignored-differences.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,17 @@
2323
<className>com/google/cloud/firestore/Firestore</className>
2424
<method>com.google.api.core.ApiFuture runAsyncTransaction(*)</method>
2525
</difference>
26+
27+
<difference>
28+
<differenceType>7005</differenceType>
29+
<className>com/google/cloud/firestore/*</className>
30+
<method>*(com.google.cloud.firestore.FirestoreImpl, *)</method>
31+
<to>*(com.google.cloud.firestore.FirestoreRpcContext, *)</to>
32+
</difference>
33+
<difference>
34+
<differenceType>7009</differenceType>
35+
<className>com/google/cloud/firestore/*</className>
36+
<method>*(com.google.cloud.firestore.FirestoreImpl, *)</method>
37+
</difference>
38+
2639
</differences>

google-cloud-firestore/src/main/java/com/google/cloud/firestore/CollectionReference.java

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import com.google.api.core.ApiFunction;
2020
import com.google.api.core.ApiFuture;
2121
import com.google.api.core.ApiFutures;
22+
import com.google.api.core.InternalExtensionOnly;
2223
import com.google.api.gax.rpc.ApiException;
2324
import com.google.api.gax.rpc.ApiExceptions;
25+
import com.google.api.gax.rpc.UnaryCallable;
26+
import com.google.cloud.firestore.spi.v1.FirestoreRpc;
2427
import com.google.cloud.firestore.v1.FirestoreClient.ListDocumentsPagedResponse;
2528
import com.google.common.base.Preconditions;
2629
import com.google.common.util.concurrent.MoreExecutors;
@@ -40,16 +43,17 @@
4043
* test mocks. Subclassing is not supported in production code and new SDK releases may break code
4144
* that does so.
4245
*/
46+
@InternalExtensionOnly
4347
public class CollectionReference extends Query {
4448

4549
/**
4650
* Creates a CollectionReference from a complete collection path.
4751
*
48-
* @param firestore The Firestore client.
52+
* @param rpcContext The Firestore client.
4953
* @param collectionPath The Path of this collection.
5054
*/
51-
protected CollectionReference(FirestoreImpl firestore, ResourcePath collectionPath) {
52-
super(firestore, collectionPath);
55+
CollectionReference(FirestoreRpcContext<?> rpcContext, ResourcePath collectionPath) {
56+
super(rpcContext, collectionPath);
5357
}
5458

5559
/**
@@ -71,7 +75,7 @@ public String getId() {
7175
@Nullable
7276
public DocumentReference getParent() {
7377
ResourcePath parent = options.getParentPath();
74-
return parent.isDocument() ? new DocumentReference(firestore, parent) : null;
78+
return parent.isDocument() ? new DocumentReference(rpcContext, parent) : null;
7579
}
7680

7781
/**
@@ -109,7 +113,7 @@ public DocumentReference document(@Nonnull String childPath) {
109113
Preconditions.checkArgument(
110114
documentPath.isDocument(),
111115
String.format("Path should point to a Document Reference: %s", getPath()));
112-
return new DocumentReference(firestore, documentPath);
116+
return new DocumentReference(rpcContext, documentPath);
113117
}
114118

115119
/**
@@ -133,10 +137,12 @@ public Iterable<DocumentReference> listDocuments() {
133137
final ListDocumentsPagedResponse response;
134138

135139
try {
136-
response =
137-
ApiExceptions.callAndTranslateApiException(
138-
firestore.sendRequest(
139-
request.build(), firestore.getClient().listDocumentsPagedCallable()));
140+
FirestoreRpc client = rpcContext.getClient();
141+
UnaryCallable<ListDocumentsRequest, ListDocumentsPagedResponse> callable =
142+
client.listDocumentsPagedCallable();
143+
ListDocumentsRequest build = request.build();
144+
ApiFuture<ListDocumentsPagedResponse> future = rpcContext.sendRequest(build, callable);
145+
response = ApiExceptions.callAndTranslateApiException(future);
140146
} catch (ApiException exception) {
141147
throw FirestoreException.apiException(exception);
142148
}

google-cloud-firestore/src/main/java/com/google/cloud/firestore/DocumentReference.java

Lines changed: 32 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import com.google.api.core.ApiFunction;
2020
import com.google.api.core.ApiFuture;
2121
import com.google.api.core.ApiFutures;
22+
import com.google.api.core.InternalExtensionOnly;
2223
import com.google.api.gax.rpc.ApiException;
2324
import com.google.api.gax.rpc.ApiExceptions;
2425
import com.google.cloud.firestore.v1.FirestoreClient.ListCollectionIdsPagedResponse;
@@ -42,15 +43,16 @@
4243
* test mocks. Subclassing is not supported in production code and new SDK releases may break code
4344
* that does so.
4445
*/
46+
@InternalExtensionOnly
4547
public class DocumentReference {
4648

4749
private final ResourcePath path;
48-
private final FirestoreImpl firestore;
50+
private final FirestoreRpcContext<?> rpcContext;
4951

50-
protected DocumentReference(
51-
FirestoreImpl firestore, ResourcePath path) { // Elevated access level for mocking.
52+
DocumentReference(
53+
FirestoreRpcContext<?> rpcContext, ResourcePath path) { // Elevated access level for mocking.
5254
this.path = path;
53-
this.firestore = firestore;
55+
this.rpcContext = rpcContext;
5456
}
5557

5658
/*
@@ -60,7 +62,7 @@ protected DocumentReference(
6062
*/
6163
@Nonnull
6264
public Firestore getFirestore() {
63-
return firestore;
65+
return rpcContext.getFirestore();
6466
}
6567

6668
/**
@@ -102,7 +104,7 @@ String getName() {
102104
*/
103105
@Nonnull
104106
public CollectionReference getParent() {
105-
return new CollectionReference(firestore, path.getParent());
107+
return new CollectionReference(rpcContext, path.getParent());
106108
}
107109

108110
/**
@@ -114,7 +116,7 @@ public CollectionReference getParent() {
114116
*/
115117
@Nonnull
116118
public CollectionReference collection(@Nonnull String collectionPath) {
117-
return new CollectionReference(firestore, path.append(collectionPath));
119+
return new CollectionReference(rpcContext, path.append(collectionPath));
118120
}
119121

120122
/**
@@ -144,7 +146,7 @@ public T apply(List<T> results) {
144146
*/
145147
@Nonnull
146148
public ApiFuture<WriteResult> create(@Nonnull Map<String, Object> fields) {
147-
WriteBatch writeBatch = firestore.batch();
149+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
148150
return extractFirst(writeBatch.create(this, fields).commit());
149151
}
150152

@@ -157,7 +159,7 @@ public ApiFuture<WriteResult> create(@Nonnull Map<String, Object> fields) {
157159
*/
158160
@Nonnull
159161
public ApiFuture<WriteResult> create(@Nonnull Object pojo) {
160-
WriteBatch writeBatch = firestore.batch();
162+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
161163
return extractFirst(writeBatch.create(this, pojo).commit());
162164
}
163165

@@ -170,7 +172,7 @@ public ApiFuture<WriteResult> create(@Nonnull Object pojo) {
170172
*/
171173
@Nonnull
172174
public ApiFuture<WriteResult> set(@Nonnull Map<String, Object> fields) {
173-
WriteBatch writeBatch = firestore.batch();
175+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
174176
return extractFirst(writeBatch.set(this, fields).commit());
175177
}
176178

@@ -186,7 +188,7 @@ public ApiFuture<WriteResult> set(@Nonnull Map<String, Object> fields) {
186188
@Nonnull
187189
public ApiFuture<WriteResult> set(
188190
@Nonnull Map<String, Object> fields, @Nonnull SetOptions options) {
189-
WriteBatch writeBatch = firestore.batch();
191+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
190192
return extractFirst(writeBatch.set(this, fields, options).commit());
191193
}
192194

@@ -199,7 +201,7 @@ public ApiFuture<WriteResult> set(
199201
*/
200202
@Nonnull
201203
public ApiFuture<WriteResult> set(@Nonnull Object pojo) {
202-
WriteBatch writeBatch = firestore.batch();
204+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
203205
return extractFirst(writeBatch.set(this, pojo).commit());
204206
}
205207

@@ -214,7 +216,7 @@ public ApiFuture<WriteResult> set(@Nonnull Object pojo) {
214216
*/
215217
@Nonnull
216218
public ApiFuture<WriteResult> set(@Nonnull Object pojo, @Nonnull SetOptions options) {
217-
WriteBatch writeBatch = firestore.batch();
219+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
218220
return extractFirst(writeBatch.set(this, pojo, options).commit());
219221
}
220222

@@ -227,7 +229,7 @@ public ApiFuture<WriteResult> set(@Nonnull Object pojo, @Nonnull SetOptions opti
227229
*/
228230
@Nonnull
229231
public ApiFuture<WriteResult> update(@Nonnull Map<String, Object> fields) {
230-
WriteBatch writeBatch = firestore.batch();
232+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
231233
return extractFirst(writeBatch.update(this, fields).commit());
232234
}
233235

@@ -241,7 +243,7 @@ public ApiFuture<WriteResult> update(@Nonnull Map<String, Object> fields) {
241243
*/
242244
@Nonnull
243245
public ApiFuture<WriteResult> update(@Nonnull Map<String, Object> fields, Precondition options) {
244-
WriteBatch writeBatch = firestore.batch();
246+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
245247
return extractFirst(writeBatch.update(this, fields, options).commit());
246248
}
247249

@@ -257,7 +259,7 @@ public ApiFuture<WriteResult> update(@Nonnull Map<String, Object> fields, Precon
257259
@Nonnull
258260
public ApiFuture<WriteResult> update(
259261
@Nonnull String field, @Nullable Object value, Object... moreFieldsAndValues) {
260-
WriteBatch writeBatch = firestore.batch();
262+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
261263
return extractFirst(writeBatch.update(this, field, value, moreFieldsAndValues).commit());
262264
}
263265

@@ -273,7 +275,7 @@ public ApiFuture<WriteResult> update(
273275
@Nonnull
274276
public ApiFuture<WriteResult> update(
275277
@Nonnull FieldPath fieldPath, @Nullable Object value, Object... moreFieldsAndValues) {
276-
WriteBatch writeBatch = firestore.batch();
278+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
277279
return extractFirst(writeBatch.update(this, fieldPath, value, moreFieldsAndValues).commit());
278280
}
279281

@@ -293,7 +295,7 @@ public ApiFuture<WriteResult> update(
293295
@Nonnull String field,
294296
@Nullable Object value,
295297
Object... moreFieldsAndValues) {
296-
WriteBatch writeBatch = firestore.batch();
298+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
297299
return extractFirst(
298300
writeBatch.update(this, options, field, value, moreFieldsAndValues).commit());
299301
}
@@ -314,7 +316,7 @@ public ApiFuture<WriteResult> update(
314316
@Nonnull FieldPath fieldPath,
315317
@Nullable Object value,
316318
Object... moreFieldsAndValues) {
317-
WriteBatch writeBatch = firestore.batch();
319+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
318320
return extractFirst(
319321
writeBatch.update(this, options, fieldPath, value, moreFieldsAndValues).commit());
320322
}
@@ -327,7 +329,7 @@ public ApiFuture<WriteResult> update(
327329
*/
328330
@Nonnull
329331
public ApiFuture<WriteResult> delete(@Nonnull Precondition options) {
330-
WriteBatch writeBatch = firestore.batch();
332+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
331333
return extractFirst(writeBatch.delete(this, options).commit());
332334
}
333335

@@ -338,7 +340,7 @@ public ApiFuture<WriteResult> delete(@Nonnull Precondition options) {
338340
*/
339341
@Nonnull
340342
public ApiFuture<WriteResult> delete() {
341-
WriteBatch writeBatch = firestore.batch();
343+
WriteBatch writeBatch = rpcContext.getFirestore().batch();
342344
return extractFirst(writeBatch.delete(this).commit());
343345
}
344346

@@ -351,7 +353,7 @@ public ApiFuture<WriteResult> delete() {
351353
*/
352354
@Nonnull
353355
public ApiFuture<DocumentSnapshot> get() {
354-
return extractFirst(firestore.getAll(this));
356+
return extractFirst(rpcContext.getFirestore().getAll(this));
355357
}
356358

357359
/**
@@ -364,7 +366,8 @@ public ApiFuture<DocumentSnapshot> get() {
364366
*/
365367
@Nonnull
366368
public ApiFuture<DocumentSnapshot> get(FieldMask fieldMask) {
367-
return extractFirst(firestore.getAll(new DocumentReference[] {this}, fieldMask));
369+
return extractFirst(
370+
rpcContext.getFirestore().getAll(new DocumentReference[] {this}, fieldMask));
368371
}
369372

370373
/**
@@ -382,8 +385,8 @@ public Iterable<CollectionReference> listCollections() {
382385
try {
383386
response =
384387
ApiExceptions.callAndTranslateApiException(
385-
firestore.sendRequest(
386-
request.build(), firestore.getClient().listCollectionIdsPagedCallable()));
388+
rpcContext.sendRequest(
389+
request.build(), rpcContext.getClient().listCollectionIdsPagedCallable()));
387390
} catch (ApiException exception) {
388391
throw FirestoreException.apiException(exception);
389392
}
@@ -456,7 +459,7 @@ public void onEvent(
456459
}
457460
listener.onEvent(
458461
DocumentSnapshot.fromMissing(
459-
firestore, DocumentReference.this, value.getReadTime()),
462+
rpcContext, DocumentReference.this, value.getReadTime()),
460463
null);
461464
}
462465
});
@@ -471,7 +474,7 @@ public void onEvent(
471474
@Nonnull
472475
public ListenerRegistration addSnapshotListener(
473476
@Nonnull EventListener<DocumentSnapshot> listener) {
474-
return addSnapshotListener(firestore.getClient().getExecutor(), listener);
477+
return addSnapshotListener(rpcContext.getClient().getExecutor(), listener);
475478
}
476479

477480
ResourcePath getResourcePath() {
@@ -498,11 +501,11 @@ public boolean equals(Object obj) {
498501
return false;
499502
}
500503
DocumentReference that = (DocumentReference) obj;
501-
return Objects.equals(path, that.path) && Objects.equals(firestore, that.firestore);
504+
return Objects.equals(path, that.path) && Objects.equals(rpcContext, that.rpcContext);
502505
}
503506

504507
@Override
505508
public int hashCode() {
506-
return Objects.hash(path, firestore);
509+
return Objects.hash(path, rpcContext);
507510
}
508511
}

0 commit comments

Comments
 (0)