From 12fc017aae4aaded38c3c8a2f4bbfacaacd1af82 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:16:09 -0400 Subject: [PATCH 01/48] Update protos to googleapis HEAD at https://github.com/googleapis/googleapis/commit/13f9b8908d84fa85c7573223aac3ac900f1cca27 --- .../proto/google/firestore/v1/common.proto | 8 +- .../proto/google/firestore/v1/document.proto | 6 +- .../proto/google/firestore/v1/firestore.proto | 313 +++++++++++++----- .../src/proto/google/firestore/v1/query.proto | 92 ++--- .../src/proto/google/firestore/v1/write.proto | 16 +- 5 files changed, 289 insertions(+), 146 deletions(-) diff --git a/firebase-firestore/src/proto/google/firestore/v1/common.proto b/firebase-firestore/src/proto/google/firestore/v1/common.proto index 670cb41739b..588a9b81b79 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/common.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/common.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,11 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; option csharp_namespace = "Google.Cloud.Firestore.V1"; @@ -27,7 +25,7 @@ option java_outer_classname = "CommonProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A set of field paths on a document. // Used to restrict a get or update operation on a document to a subset of its @@ -49,7 +47,7 @@ message Precondition { bool exists = 1; // When set, the target document must exist and have been last updated at - // that time. + // that time. Timestamp must be microsecond aligned. google.protobuf.Timestamp update_time = 2; } } diff --git a/firebase-firestore/src/proto/google/firestore/v1/document.proto b/firebase-firestore/src/proto/google/firestore/v1/document.proto index 268947856a8..41283588eb8 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/document.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/document.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,11 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/type/latlng.proto"; @@ -29,7 +27,7 @@ option java_outer_classname = "DocumentProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A Firestore document. // diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index d425edf9e0f..17c94ecb4e6 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,14 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/firestore/v1/query.proto"; @@ -31,27 +32,25 @@ option go_package = "google.golang.org/genproto/googleapis/firestore/v1;firestor option java_multiple_files = true; option java_outer_classname = "FirestoreProto"; option java_package = "com.google.firestore.v1"; -option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; + // Specification of the Firestore API. // The Cloud Firestore service. // -// This service exposes several types of comparable timestamps: -// -// * `create_time` - The time at which a document was created. Changes only -// when a document is deleted, then re-created. Increases in a strict -// monotonic fashion. -// * `update_time` - The time at which a document was last updated. Changes -// every time a document is modified. Does not change when a write results -// in no modifications. Increases in a strict monotonic fashion. -// * `read_time` - The time at which a particular state was observed. Used -// to denote a consistent snapshot of the database or the time at which a -// Document was observed to not exist. -// * `commit_time` - The time at which the writes in a transaction were -// committed. Any read with an equal or greater `read_time` is guaranteed -// to see the effects of the transaction. +// Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL +// document database that simplifies storing, syncing, and querying data for +// your mobile, web, and IoT apps at global scale. Its client libraries provide +// live synchronization and offline support, while its security features and +// integrations with Firebase and Google Cloud Platform (GCP) accelerate +// building truly serverless apps. service Firestore { + option (google.api.default_host) = "firestore.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/datastore"; + // Gets a single document. rpc GetDocument(GetDocumentRequest) returns (Document) { option (google.api.http) = { @@ -63,14 +62,9 @@ service Firestore { rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}" - }; - } - - // Creates a new document. - rpc CreateDocument(CreateDocumentRequest) returns (Document) { - option (google.api.http) = { - post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" - body: "document" + additional_bindings { + get: "/v1/{parent=projects/*/databases/*/documents}/{collection_id}" + } }; } @@ -80,6 +74,7 @@ service Firestore { patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}" body: "document" }; + option (google.api.method_signature) = "document,update_mask"; } // Deletes a document. @@ -87,6 +82,7 @@ service Firestore { option (google.api.http) = { delete: "/v1/{name=projects/*/databases/*/documents/*/**}" }; + option (google.api.method_signature) = "name"; } // Gets multiple documents. @@ -106,6 +102,7 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction" body: "*" }; + option (google.api.method_signature) = "database"; } // Commits a transaction, while optionally updating documents. @@ -114,6 +111,7 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:commit" body: "*" }; + option (google.api.method_signature) = "database,writes"; } // Rolls back a transaction. @@ -122,6 +120,7 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:rollback" body: "*" }; + option (google.api.method_signature) = "database,transaction"; } // Runs a query. @@ -136,6 +135,20 @@ service Firestore { }; } + // Partitions a query by returning partition cursors that can be used to run + // the query in parallel. The returned partition cursors are split points that + // can be used by RunQuery as starting/end points for the query results. + rpc PartitionQuery(PartitionQueryRequest) returns (PartitionQueryResponse) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery" + body: "*" + additional_bindings { + post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery" + body: "*" + } + }; + } + // Streams batches of document updates and deletes, in order. rpc Write(stream WriteRequest) returns (stream WriteResponse) { option (google.api.http) = { @@ -162,14 +175,39 @@ service Firestore { body: "*" } }; + option (google.api.method_signature) = "parent"; + } + + // Applies a batch of write operations. + // + // The BatchWrite method does not apply the write operations atomically + // and can apply them out of order. Method does not allow more than one write + // per document. Each write succeeds or fails independently. See the + // [BatchWriteResponse][google.firestore.v1.BatchWriteResponse] for the success status of each write. + // + // If you require an atomically applied set of writes, use + // [Commit][google.firestore.v1.Firestore.Commit] instead. + rpc BatchWrite(BatchWriteRequest) returns (BatchWriteResponse) { + option (google.api.http) = { + post: "/v1/{database=projects/*/databases/*}/documents:batchWrite" + body: "*" + }; + } + + // Creates a new document. + rpc CreateDocument(CreateDocumentRequest) returns (Document) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" + body: "document" + }; } } // The request for [Firestore.GetDocument][google.firestore.v1.Firestore.GetDocument]. message GetDocumentRequest { - // The resource name of the Document to get. In the format: + // Required. The resource name of the Document to get. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1; + string name = 1 [(google.api.field_behavior) = REQUIRED]; // The fields to return. If not set, returns all fields. // @@ -184,24 +222,24 @@ message GetDocumentRequest { bytes transaction = 3; // Reads the version of the document at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 5; } } // The request for [Firestore.ListDocuments][google.firestore.v1.Firestore.ListDocuments]. message ListDocumentsRequest { - // The parent resource name. In the format: + // Required. The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; - // The collection ID, relative to `parent`, to list. For example: `chatrooms` + // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms` // or `messages`. - string collection_id = 2; + string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; // The maximum number of documents to return. int32 page_size = 3; @@ -225,7 +263,7 @@ message ListDocumentsRequest { bytes transaction = 8; // Reads documents as they were at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 10; } @@ -250,21 +288,21 @@ message ListDocumentsResponse { // The request for [Firestore.CreateDocument][google.firestore.v1.Firestore.CreateDocument]. message CreateDocumentRequest { - // The parent resource. For example: + // Required. The parent resource. For example: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/chatrooms/{chatroom_id}` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; - // The collection ID, relative to `parent`, to list. For example: `chatrooms`. - string collection_id = 2; + // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms`. + string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; // The client-assigned document ID to use for this document. // // Optional. If not specified, an ID will be assigned by the service. string document_id = 3; - // The document to create. `name` must not be set. - Document document = 4; + // Required. The document to create. `name` must not be set. + Document document = 4 [(google.api.field_behavior) = REQUIRED]; // The fields to return. If not set, returns all fields. // @@ -275,9 +313,9 @@ message CreateDocumentRequest { // The request for [Firestore.UpdateDocument][google.firestore.v1.Firestore.UpdateDocument]. message UpdateDocumentRequest { - // The updated document. + // Required. The updated document. // Creates the document if it does not already exist. - Document document = 1; + Document document = 1 [(google.api.field_behavior) = REQUIRED]; // The fields to update. // None of the field paths in the mask may contain a reserved name. @@ -301,9 +339,9 @@ message UpdateDocumentRequest { // The request for [Firestore.DeleteDocument][google.firestore.v1.Firestore.DeleteDocument]. message DeleteDocumentRequest { - // The resource name of the Document to delete. In the format: + // Required. The resource name of the Document to delete. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1; + string name = 1 [(google.api.field_behavior) = REQUIRED]; // An optional precondition on the document. // The request will fail if this is set and not met by the target document. @@ -312,9 +350,9 @@ message DeleteDocumentRequest { // The request for [Firestore.BatchGetDocuments][google.firestore.v1.Firestore.BatchGetDocuments]. message BatchGetDocumentsRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The names of the documents to retrieve. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. @@ -341,7 +379,7 @@ message BatchGetDocumentsRequest { TransactionOptions new_transaction = 5; // Reads documents as they were at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -373,9 +411,9 @@ message BatchGetDocumentsResponse { // The request for [Firestore.BeginTransaction][google.firestore.v1.Firestore.BeginTransaction]. message BeginTransactionRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The options for the transaction. // Defaults to a read-write transaction. @@ -390,9 +428,9 @@ message BeginTransactionResponse { // The request for [Firestore.Commit][google.firestore.v1.Firestore.Commit]. message CommitRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The writes to apply. // @@ -411,29 +449,30 @@ message CommitResponse { // request. repeated WriteResult write_results = 1; - // The time at which the commit occurred. + // The time at which the commit occurred. Any read with an equal or greater + // `read_time` is guaranteed to see the effects of the commit. google.protobuf.Timestamp commit_time = 2; } // The request for [Firestore.Rollback][google.firestore.v1.Firestore.Rollback]. message RollbackRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; - // The transaction to roll back. - bytes transaction = 2; + // Required. The transaction to roll back. + bytes transaction = 2 [(google.api.field_behavior) = REQUIRED]; } // The request for [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery]. message RunQueryRequest { - // The parent resource name. In the format: + // Required. The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; // The query to run. oneof query_type { @@ -444,7 +483,9 @@ message RunQueryRequest { // The consistency mode for this transaction. // If not set, defaults to strong consistency. oneof consistency_selector { - // Reads documents in a transaction. + // Run the query within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. bytes transaction = 5; // Starts a new transaction and reads the documents. @@ -454,7 +495,7 @@ message RunQueryRequest { TransactionOptions new_transaction = 6; // Reads documents as they were at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -467,8 +508,7 @@ message RunQueryResponse { // If set, no other fields will be set in this response. bytes transaction = 2; - // A query result. - // Not set when reporting partial progress. + // A query result, not set when reporting partial progress. Document document = 1; // The time at which the document was read. This may be monotonically @@ -483,6 +523,94 @@ message RunQueryResponse { // The number of results that have been skipped due to an offset between // the last response and the current response. int32 skipped_results = 4; + + // The continuation mode for the query. If present, it indicates the current + // query response stream has finished. This can be set with or without a + // `document` present, but when set, no more results are returned. + oneof continuation_selector { + // If present, Firestore has completely finished the request and no more + // documents will be returned. + bool done = 6; + } +} + +// The request for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. +message PartitionQueryRequest { + // Required. The parent resource name. In the format: + // `projects/{project_id}/databases/{database_id}/documents`. + // Document resource names are not supported; only database resource names + // can be specified. + string parent = 1 [(google.api.field_behavior) = REQUIRED]; + + // The query to partition. + oneof query_type { + // A structured query. + // Query must specify collection with all descendants and be ordered by name + // ascending. Other filters, order bys, limits, offsets, and start/end + // cursors are not supported. + StructuredQuery structured_query = 2; + } + + // The desired maximum number of partition points. + // The partitions may be returned across multiple pages of results. + // The number must be positive. The actual number of partitions + // returned may be fewer. + // + // For example, this may be set to one fewer than the number of parallel + // queries to be run, or in running a data pipeline job, one fewer than the + // number of workers or compute instances available. + int64 partition_count = 3; + + // The `next_page_token` value returned from a previous call to + // PartitionQuery that may be used to get an additional set of results. + // There are no ordering guarantees between sets of results. Thus, using + // multiple sets of results will require merging the different result sets. + // + // For example, two subsequent calls using a page_token may return: + // + // * cursor B, cursor M, cursor Q + // * cursor A, cursor U, cursor W + // + // To obtain a complete result set ordered with respect to the results of the + // query supplied to PartitionQuery, the results sets should be merged: + // cursor A, cursor B, cursor M, cursor Q, cursor U, cursor W + string page_token = 4; + + // The maximum number of partitions to return in this call, subject to + // `partition_count`. + // + // For example, if `partition_count` = 10 and `page_size` = 8, the first call + // to PartitionQuery will return up to 8 partitions and a `next_page_token` + // if more results exist. A second call to PartitionQuery will return up to + // 2 partitions, to complete the total of 10 specified in `partition_count`. + int32 page_size = 5; +} + +// The response for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. +message PartitionQueryResponse { + // Partition results. + // Each partition is a split point that can be used by RunQuery as a starting + // or end point for the query results. The RunQuery requests must be made with + // the same query supplied to this PartitionQuery request. The partition + // cursors will be ordered according to same ordering as the results of the + // query supplied to PartitionQuery. + // + // For example, if a PartitionQuery request returns partition cursors A and B, + // running the following three queries will return the entire result set of + // the original query: + // + // * query, end_at A + // * query, start_at A, end_at B + // * query, start_at B + // + // An empty result may indicate that the query has too few results to be + // partitioned. + repeated Cursor partitions = 1; + + // A page token that may be used to request an additional set of results, up + // to the number specified by `partition_count` in the PartitionQuery request. + // If blank, there are no more results. + string next_page_token = 2; } // The request for [Firestore.Write][google.firestore.v1.Firestore.Write]. @@ -496,10 +624,10 @@ message RunQueryResponse { // given token, then a response containing only an up-to-date token, to use in // the next request. message WriteRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. // This is only required in the first message. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The ID of the write stream to resume. // This may only be set in the first message. When left empty, a new write @@ -552,15 +680,16 @@ message WriteResponse { // request. repeated WriteResult write_results = 3; - // The time at which the commit occurred. + // The time at which the commit occurred. Any read with an equal or greater + // `read_time` is guaranteed to see the effects of the write. google.protobuf.Timestamp commit_time = 4; } // A request for [Firestore.Listen][google.firestore.v1.Firestore.Listen] message ListenRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The supported target changes. oneof target_change { @@ -654,14 +783,8 @@ message Target { google.protobuf.Timestamp read_time = 11; } - // A client provided target ID. - // - // If not set, the server will assign an ID for the target. - // - // Used for resuming a target without changing IDs. The IDs can either be - // client-assigned or be server-assigned in a previous stream. All targets - // with client provided IDs must be added before adding a target that needs - // a server-assigned id. + // The target ID that identifies the target on the stream. Must be a positive + // number and non-zero. int32 target_id = 5; // If the target should be removed once it is current and consistent. @@ -706,11 +829,7 @@ message TargetChange { // // If empty, the change applies to all targets. // - // For `target_change_type=ADD`, the order of the target IDs matches the order - // of the requests to add the targets. This allows clients to unambiguously - // associate server-assigned target IDs with added targets. - // - // For other states, the order of the target IDs is not defined. + // The order of the target IDs is not defined. repeated int32 target_ids = 2; // The error that resulted in this change, if applicable. @@ -737,11 +856,11 @@ message TargetChange { // The request for [Firestore.ListCollectionIds][google.firestore.v1.Firestore.ListCollectionIds]. message ListCollectionIdsRequest { - // The parent document. In the format: + // Required. The parent document. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; // The maximum number of results to return. int32 page_size = 2; @@ -759,3 +878,35 @@ message ListCollectionIdsResponse { // A page token that may be used to continue the list. string next_page_token = 2; } + +// The request for [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. +message BatchWriteRequest { + // Required. The database name. In the format: + // `projects/{project_id}/databases/{database_id}`. + string database = 1 [(google.api.field_behavior) = REQUIRED]; + + // The writes to apply. + // + // Method does not apply writes atomically and does not guarantee ordering. + // Each write succeeds or fails independently. You cannot write to the same + // document more than once per request. + repeated Write writes = 2; + + // Labels associated with this batch write. + map labels = 3; +} + +// The response from [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. +message BatchWriteResponse { + // The result of applying the writes. + // + // This i-th write result corresponds to the i-th write in the + // request. + repeated WriteResult write_results = 1; + + // The status of applying the writes. + // + // This i-th write status corresponds to the i-th write in the + // request. + repeated google.rpc.Status status = 2; +} diff --git a/firebase-firestore/src/proto/google/firestore/v1/query.proto b/firebase-firestore/src/proto/google/firestore/v1/query.proto index 25d53238130..58163eb62cf 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/query.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/query.proto @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,12 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/wrappers.proto"; @@ -28,7 +27,7 @@ option java_outer_classname = "QueryProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A Firestore query. message StructuredQuery { @@ -66,7 +65,7 @@ message StructuredQuery { // Unspecified. This value must not be used. OPERATOR_UNSPECIFIED = 0; - // The results are required to satisfy each of the combined filters. + // Documents are required to satisfy all of the combined filters. AND = 1; } @@ -74,7 +73,10 @@ message StructuredQuery { Operator op = 1; // The list of filters to combine. - // Must contain at least one filter. + // + // Requires: + // + // * At least one filter is present. repeated Filter filters = 2; } @@ -113,7 +115,7 @@ message StructuredQuery { // * That `field` come first in `order_by`. GREATER_THAN_OR_EQUAL = 4; - // The given `field` is equal to the given `value`.. + // The given `field` is equal to the given `value`. EQUAL = 5; // The given `field` is not equal to the given `value`. @@ -132,7 +134,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) + // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. IN = 8; // The given `field` is an array that contains any of the values in the @@ -141,7 +143,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) + // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. ARRAY_CONTAINS_ANY = 9; // The value of the `field` is not in the given array. @@ -169,30 +171,30 @@ message StructuredQuery { message UnaryFilter { // A unary operator. enum Operator { - // Unspecified. This value must not be used. - OPERATOR_UNSPECIFIED = 0; - - // The given `field` is equal to `NaN`. - IS_NAN = 2; - - // The given `field` is equal to `NULL`. - IS_NULL = 3; - - // The given `field` is not equal to `NaN`. - // - // Requires: - // - // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NAN = 4; - - // The given `field` is not equal to `NULL`. - // - // Requires: - // - // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NULL = 5; + // Unspecified. This value must not be used. + OPERATOR_UNSPECIFIED = 0; + + // The given `field` is equal to `NaN`. + IS_NAN = 2; + + // The given `field` is equal to `NULL`. + IS_NULL = 3; + + // The given `field` is not equal to `NaN`. + // + // Requires: + // + // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NAN = 4; + + // The given `field` is not equal to `NULL`. + // + // Requires: + // + // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NULL = 5; } // The unary operator to apply. @@ -214,6 +216,18 @@ message StructuredQuery { Direction direction = 2; } + // A sort direction. + enum Direction { + // Unspecified. + DIRECTION_UNSPECIFIED = 0; + + // Ascending. + ASCENDING = 1; + + // Descending. + DESCENDING = 2; + } + // A reference to a field, such as `max(messages.time) as max_time`. message FieldReference { string field_path = 2; @@ -228,18 +242,6 @@ message StructuredQuery { repeated FieldReference fields = 2; } - // A sort direction. - enum Direction { - // Unspecified. - DIRECTION_UNSPECIFIED = 0; - - // Ascending. - ASCENDING = 1; - - // Descending. - DESCENDING = 2; - } - // The projection to return. Projection select = 1; diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index 59b2dafe4fd..c17e96ad9c1 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,11 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/timestamp.proto"; @@ -29,7 +27,7 @@ option java_outer_classname = "WriteProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A write on a document. message Write { @@ -42,12 +40,7 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; - // The name of a document on which to verify the `current_document` - // precondition. - // This only requires read access to the document. - string verify = 5; - - // Applies a tranformation to a document. + // Applies a transformation to a document. DocumentTransform transform = 6; } @@ -86,7 +79,8 @@ message DocumentTransform { SERVER_VALUE_UNSPECIFIED = 0; // The time at which the server processed the request, with millisecond - // precision. + // precision. If used on multiple fields (same or different documents) in + // a transaction, all the fields will get the same server timestamp. REQUEST_TIME = 1; } From 3f3a8237cc48d8af4e73efe80b7f070b0a1e4e5d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:23:17 -0400 Subject: [PATCH 02/48] write.proto: add back string verify = 5 --- firebase-firestore/src/proto/google/firestore/v1/write.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index c17e96ad9c1..6d0141e41a0 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -40,6 +40,11 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; + // The name of a document on which to verify the `current_document` + // precondition. + // This only requires read access to the document. + string verify = 5; + // Applies a transformation to a document. DocumentTransform transform = 6; } From d8dab8e0df8ec70de78639e0f8fd7785bc734121 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:25:23 -0400 Subject: [PATCH 03/48] Update protos to googleapis HEAD of the preview branch at https://github.com/googleapis/googleapis/commit/77a0a97816f53e83753fe5a1d93722b762436114 --- .../firestore/v1/aggregation_result.proto | 42 ++++++++++ .../proto/google/firestore/v1/firestore.proto | 80 +++++++++++++++++++ .../src/proto/google/firestore/v1/query.proto | 52 ++++++++++++ .../src/proto/google/firestore/v1/write.proto | 5 -- 4 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto diff --git a/firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto b/firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto new file mode 100644 index 00000000000..538e3fef5e4 --- /dev/null +++ b/firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto @@ -0,0 +1,42 @@ +// Copyright 2022 Google LLC +// +// 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. + +syntax = "proto3"; + +package google.firestore.v1; + +import "google/firestore/v1/document.proto"; + +option csharp_namespace = "Google.Cloud.Firestore.V1"; +option go_package = "google.golang.org/genproto/googleapis/firestore/v1;firestore"; +option java_multiple_files = true; +option java_outer_classname = "AggregationResultProto"; +option java_package = "com.google.firestore.v1"; +option objc_class_prefix = "GCFS"; +option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; + +// The result of a single bucket from a Firestore aggregation query. +// +// The keys of `aggregate_fields` are the same for all results in an aggregation +// query, unlike document queries which can have different fields present for +// each result. +message AggregationResult { + // The result of the aggregation functions, ex: `COUNT(*) AS total_docs`. + // + // The key is the [alias][google.firestore.v1.StructuredAggregationQuery.Aggregation.alias] + // assigned to the aggregation function on input and the size of this map + // equals the number of aggregation functions in the query. + map aggregate_fields = 2; +} diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index 17c94ecb4e6..69787e8289a 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -19,6 +19,7 @@ package google.firestore.v1; import "google/api/annotations.proto"; import "google/api/client.proto"; import "google/api/field_behavior.proto"; +import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/firestore/v1/query.proto"; @@ -135,6 +136,29 @@ service Firestore { }; } + // Runs an aggregation query. + // + // Rather than producing [Document][google.firestore.v1.Document] results like [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery], + // this API allows running an aggregation to produce a series of + // [AggregationResult][google.firestore.v1.AggregationResult] server-side. + // + // High-Level Example: + // + // ``` + // -- Return the number of documents in table given a filter. + // SELECT COUNT(*) FROM ( SELECT * FROM k where a = true ); + // ``` + rpc RunAggregationQuery(RunAggregationQueryRequest) returns (stream RunAggregationQueryResponse) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery" + body: "*" + additional_bindings { + post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery" + body: "*" + } + }; + } + // Partitions a query by returning partition cursors that can be used to run // the query in parallel. The returned partition cursors are split points that // can be used by RunQuery as starting/end points for the query results. @@ -534,6 +558,62 @@ message RunQueryResponse { } } +// The request for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. +message RunAggregationQueryRequest { + // Required. The parent resource name. In the format: + // `projects/{project_id}/databases/{database_id}/documents` or + // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + // For example: + // `projects/my-project/databases/my-database/documents` or + // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + string parent = 1 [(google.api.field_behavior) = REQUIRED]; + + // The query to run. + oneof query_type { + // An aggregation query. + StructuredAggregationQuery structured_aggregation_query = 2; + } + + // The consistency mode for the query, defaults to strong consistency. + oneof consistency_selector { + // Run the aggregation within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. + bytes transaction = 4; + + // Starts a new transaction as part of the query, defaulting to read-only. + // + // The new transaction ID will be returned as the first response in the + // stream. + TransactionOptions new_transaction = 5; + + // Executes the query at the given timestamp. + // + // Requires: + // + // * Cannot be more than 270 seconds in the past. + google.protobuf.Timestamp read_time = 6; + } +} + +// The response for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. +message RunAggregationQueryResponse { + // A single aggregation result. + // + // Not present when reporting partial progress or when the query produced + // zero results. + AggregationResult result = 1; + + // The transaction that was started as part of this request. + // + // Only present on the first response when the request requested to start + // a new transaction. + bytes transaction = 2; + + // The time at which the aggregate value is valid for. + google.protobuf.Timestamp read_time = 3; +} + // The request for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. message PartitionQueryRequest { // Required. The parent resource name. In the format: diff --git a/firebase-firestore/src/proto/google/firestore/v1/query.proto b/firebase-firestore/src/proto/google/firestore/v1/query.proto index 58163eb62cf..4c6250fa780 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/query.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/query.proto @@ -290,6 +290,58 @@ message StructuredQuery { google.protobuf.Int32Value limit = 5; } +// Firestore query for running an aggregation over a [StructuredQuery][google.firestore.v1.StructuredQuery]. +message StructuredAggregationQuery { + // Defines a aggregation that produces a single result. + message Aggregation { + // Count of documents that match the query. + // + // The `COUNT(*)` aggregation function operates on the entire document + // so it does not require a field reference. + message Count { + // Optional. Optional constraint on the maximum number of documents to count. + // + // This provides a way to set an upper bound on the number of documents + // to scan, limiting latency and cost. + // + // High-Level Example: + // + // ``` + // SELECT COUNT_UP_TO(1000) FROM ( SELECT * FROM k ); + // ``` + // + // Requires: + // + // * Must be greater than zero when present. + int32 up_to = 1 [(google.api.field_behavior) = OPTIONAL]; + } + + // The type of aggregation to perform, required. + oneof operator { + // Count aggregator. + Count count = 1; + } + + // Required. The name of the field to store the result of the aggregation into. + // + // Requires: + // + // * Must be present. + // * Must be unique across all aggregation aliases. + // * Conform to existing [document field name][google.firestore.v1.Document.fields] limitations. + string alias = 7 [(google.api.field_behavior) = REQUIRED]; + } + + // The base query to aggregate over. + oneof query_type { + // Nested structured query. + StructuredQuery structured_query = 1; + } + + // Optional. Series of aggregations to apply on top of the `structured_query`. + repeated Aggregation aggregations = 3 [(google.api.field_behavior) = OPTIONAL]; +} + // A position in a query result set. message Cursor { // The values that represent a position, in the order they appear in diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index 6d0141e41a0..c17e96ad9c1 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -40,11 +40,6 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; - // The name of a document on which to verify the `current_document` - // precondition. - // This only requires read access to the document. - string verify = 5; - // Applies a transformation to a document. DocumentTransform transform = 6; } From 3ffc859318e8df597544dc16fddf200b2d2926d0 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:23:17 -0400 Subject: [PATCH 04/48] write.proto: add back string verify = 5 --- firebase-firestore/src/proto/google/firestore/v1/write.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index c17e96ad9c1..6d0141e41a0 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -40,6 +40,11 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; + // The name of a document on which to verify the `current_document` + // precondition. + // This only requires read access to the document. + string verify = 5; + // Applies a transformation to a document. DocumentTransform transform = 6; } From f5b5ec4955fbedbe0217897b33d833b6ab0e3bf9 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 15:06:11 -0400 Subject: [PATCH 05/48] Add API skeleton to aggregate queries and COUNT --- .../firebase/firestore/ktx/Firestore.kt | 31 +- .../firebase/firestore/AggregateDemo.java | 97 ++++++ .../firebase/firestore/AggregateField.java | 301 ++++++++++++++++++ .../firebase/firestore/AggregateQuery.java | 81 +++++ .../firestore/AggregateQuerySnapshot.java | 33 ++ .../firebase/firestore/AggregateSnapshot.java | 157 +++++++++ .../firebase/firestore/AggregateSource.java | 19 ++ .../firebase/firestore/GroupByQuery.java | 175 ++++++++++ .../firestore/GroupByQuerySnapshot.java | 74 +++++ .../firebase/firestore/GroupBySource.java | 19 ++ .../firebase/firestore/GroupChange.java | 57 ++++ .../firebase/firestore/GroupSnapshot.java | 221 +++++++++++++ .../com/google/firebase/firestore/Query.java | 24 ++ 13 files changed, 1283 insertions(+), 6 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java diff --git a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt index ec2ee225d41..fa72f33ca2f 100644 --- a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt +++ b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt @@ -18,12 +18,7 @@ import androidx.annotation.Keep import com.google.firebase.FirebaseApp import com.google.firebase.components.Component import com.google.firebase.components.ComponentRegistrar -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.FieldPath -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.FirebaseFirestoreSettings -import com.google.firebase.firestore.QueryDocumentSnapshot -import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.* import com.google.firebase.ktx.Firebase import com.google.firebase.platforminfo.LibraryVersionComponent @@ -154,6 +149,30 @@ fun firestoreSettings(init: FirebaseFirestoreSettings.Builder.() -> Unit): Fireb return builder.build() } +inline fun GroupSnapshot.getField(field: String): T? = get(field, T::class.java) + +inline fun GroupSnapshot.getField( + field: String, + serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior +): T? = + get(field, T::class.java, serverTimestampBehavior) + +inline fun GroupSnapshot.getField(fieldPath: FieldPath): T? = get(fieldPath, T::class.java) + +inline fun GroupSnapshot.getField( + fieldPath: FieldPath, + serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior +): T? = + get(fieldPath, T::class.java, serverTimestampBehavior) + +inline fun AggregateSnapshot.getField(field: AggregateField): T? = get(field, T::class.java) + +inline fun AggregateSnapshot.getField( + field: AggregateField, + serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior +): T? = + get(field, T::class.java, serverTimestampBehavior) + internal const val LIBRARY_NAME: String = "fire-fst-ktx" /** @suppress */ diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java new file mode 100644 index 00000000000..fae409b128d --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java @@ -0,0 +1,97 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import static com.google.firebase.firestore.AggregateField.count; + +import android.app.Activity; +import androidx.annotation.NonNull; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +final class AggregateDemo { + + private final FirebaseFirestore db; + + AggregateDemo(@NonNull FirebaseFirestore db) { + this.db = db; + } + + void countBasicQuery() { + AggregateQuery query = db.collection("users").count(); + AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); + assertEqual(snapshot.get(count()), 50); + } + + void countLimitNumRowsScanned() { + AggregateQuery query = db.collection("users").limit(11).count(); + AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); + assertEqual(snapshot.get(count()), 11); + } + + void countUpTo() { + AggregateQuery query = db.collection("users").aggregate(count().upTo(11)); + AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); + assertEqual(snapshot.get(count()), 11); + } + + void countRealTimeUpdates() throws InterruptedException { + AggregateQuery query = db.collection("users").aggregate(count()); + + Lock lock = new ReentrantLock(); + Condition condition = lock.newCondition(); + + ListenerRegistration registration = + query + .listen() + .startDirectFromServer( + (snapshot, error) -> { + assertNull(error); + assertNotNull(snapshot); + assertEqual(snapshot.get(count()), 50); + assertFalse(snapshot.getMetadata().isFromCache()); + assertFalse(snapshot.getMetadata().hasPendingWrites()); + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + }); + + lock.lock(); + try { + condition.await(); + } finally { + lock.unlock(); + } + + registration.remove(); + } + + private static void assertEqual(Object o1, Object o2) {} + + private static void assertNull(Object o) {} + + private static void assertFalse(Object o) {} + + private static void assertNotNull(Object o) { + if (o == null) { + throw new NullPointerException(); + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java new file mode 100644 index 00000000000..d599059be16 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -0,0 +1,301 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Objects; + +public abstract class AggregateField { + + private AggregateField() {} + + @NonNull + public static CountAggregateField count() { + return new CountAggregateField(); + } + + @NonNull + public static MinAggregateField min(@NonNull String field) { + return min(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static MinAggregateField min(@NonNull FieldPath field) { + return new MinAggregateField(field); + } + + @NonNull + public static MaxAggregateField max(@NonNull String field) { + return max(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static MaxAggregateField max(@NonNull FieldPath field) { + return new MaxAggregateField(field); + } + + @NonNull + public static AverageAggregateField average(@NonNull String field) { + return average(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static AverageAggregateField average(@NonNull FieldPath field) { + return new AverageAggregateField(field); + } + + @NonNull + public static SumAggregateField sum(@NonNull String field) { + return sum(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static SumAggregateField sum(@NonNull FieldPath field) { + return new SumAggregateField(field); + } + + @NonNull + public static FirstAggregateField first(@NonNull String field) { + return first(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static FirstAggregateField first(@NonNull FieldPath field) { + return new FirstAggregateField(field); + } + + @NonNull + public static LastAggregateField last(@NonNull String field) { + return last(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static LastAggregateField last(@NonNull FieldPath field) { + return new LastAggregateField(field); + } + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); + + @Override + public abstract String toString(); + + public static final class CountAggregateField extends AggregateField { + + @Nullable private Integer upTo; + + CountAggregateField() {} + + CountAggregateField(@Nullable Integer upTo) { + this.upTo = upTo; + } + + public CountAggregateField upTo(int upTo) { + if (upTo < 0) { + throw new IllegalArgumentException("upTo==" + upTo); + } + return new CountAggregateField(upTo); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + CountAggregateField other = (CountAggregateField) obj; + return Objects.equals(upTo, other.upTo); + } + + @Override + public int hashCode() { + return Objects.hash("COUNT", upTo); + } + + @Override + public String toString() { + if (upTo == null) { + return "COUNT"; + } else { + return "COUNT(upTo=" + upTo + ")"; + } + } + } + + public static final class MinAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + MinAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((MinAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "MIN(" + field.toString() + ")"; + } + } + + public static final class MaxAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + MaxAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((MaxAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "MAX(" + field.toString() + ")"; + } + } + + public static final class AverageAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + AverageAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((AverageAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "AVERAGE(" + field.toString() + ")"; + } + } + + public static final class SumAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + SumAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((SumAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "SUM(" + field.toString() + ")"; + } + } + + public static final class FirstAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + FirstAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((FirstAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "FIRST(" + field.toString() + ")"; + } + } + + public static final class LastAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + LastAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((LastAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "LAST(" + field.toString() + ")"; + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java new file mode 100644 index 00000000000..e8ec4b91fbc --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -0,0 +1,81 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import android.app.Activity; +import androidx.annotation.NonNull; +import com.google.android.gms.tasks.Task; +import java.util.concurrent.Executor; + +public class AggregateQuery { + + AggregateQuery() {} + + @NonNull + public Query getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get(@NonNull AggregateSource source) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenConfig listen() { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(Object obj) { + throw new RuntimeException("not implemented"); + } + + public static final class ListenConfig { + + private ListenConfig() {} + + @NonNull + public ListenConfig executeCallbacksOn(@NonNull Executor executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenConfig scopeTo(@NonNull Activity executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenConfig includeMetadataOnlyChanges(boolean includeMetadataOnlyChanges) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenerRegistration startDirectFromServer( + @NonNull EventListener listener) { + throw new RuntimeException("not implemented"); + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java new file mode 100644 index 00000000000..2a786c0b478 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -0,0 +1,33 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import javax.annotation.Nonnull; + +public class AggregateQuerySnapshot extends AggregateSnapshot { + + AggregateQuerySnapshot() {} + + @Nonnull + public AggregateQuery getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public SnapshotMetadata getMetadata() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java new file mode 100644 index 00000000000..cd9bad6a978 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java @@ -0,0 +1,157 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; +import java.util.Date; +import java.util.Map; +import javax.annotation.Nonnull; + +public class AggregateSnapshot { + + AggregateSnapshot() {} + + @NonNull + public Map getAggregations() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Map getAggregations( + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + public boolean contains(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get( + @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get(@NonNull AggregateField field, @NonNull Class valueType) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get( + @NonNull AggregateField field, + @NonNull Class valueType, + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + // Overload get() specifically for COUNT since it has a well-defined type (i.e. long). + @Nullable + public Long get(@Nonnull AggregateField.CountAggregateField field) { + throw new RuntimeException("not implemented"); + } + + // Overload get() specifically for SUM since it has a well-defined type (i.e. double). + @Nullable + public Double get(@Nonnull AggregateField.SumAggregateField field) { + throw new RuntimeException("not implemented"); + } + + // Overload get() specifically for AVERAGE since it has a well-defined type (i.e. double). + @Nullable + public Double get(@Nonnull AggregateField.AverageAggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Boolean getBoolean(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Double getDouble(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public String getString(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Long getLong(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate( + @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp( + @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Blob getBlob(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public GeoPoint getGeoPoint(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public DocumentReference getDocumentReference(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object obj) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public String toString() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java new file mode 100644 index 00000000000..dd119c59e59 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -0,0 +1,19 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +public enum AggregateSource { + SERVER_DIRECT, +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java new file mode 100644 index 00000000000..073f134fe6c --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java @@ -0,0 +1,175 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import android.app.Activity; +import androidx.annotation.NonNull; +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.Query.Direction; +import java.util.concurrent.Executor; + +public class GroupByQuery { + + GroupByQuery() {} + + @NonNull + public Query getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get(@NonNull GroupBySource source) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public AggregateQuery.ListenConfig listen() { + throw new RuntimeException("not implemented"); + } + + // Note: Specifying an empty list of aggregates, or not invoking this method at all, is equivalent + // to an SQL "DISTINCT" operator. + @NonNull + public GroupByQuery aggregate(@NonNull AggregateField... fields) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupLimit(long maxGroups) { + throw new RuntimeException("not implemented"); + } + + // Question: Do we want to support group-by "limitToLast" queries? In the Query class this is + // implemented entirely client side by issuing the requested query with inverted order-by. We + // would need to verify at runtime that the underlying query has the correct order-by clause and + // possibly invert first/last aggregations to maintain their expected semantics. + @NonNull + public GroupByQuery groupLimitToLast(long maxGroups) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAt(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAt(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAfter(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAfter(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndAt(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndAt(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndBefore(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndBefore(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull String groupByField) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull AggregateField aggregateField) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull String groupByField, @NonNull Direction direction) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField, @NonNull Direction direction) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy( + @NonNull AggregateField aggregateField, @NonNull Direction direction) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(Object obj) { + throw new RuntimeException("not implemented"); + } + + public static final class ListenConfig { + + private ListenConfig() {} + + @NonNull + public AggregateQuery.ListenConfig executeCallbacksOn(@NonNull Executor executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public AggregateQuery.ListenConfig scopeTo(@NonNull Activity executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public AggregateQuery.ListenConfig includeMetadataOnlyChanges( + boolean includeMetadataOnlyChanges) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenerRegistration startDirectFromServer( + @NonNull EventListener listener) { + throw new RuntimeException("not implemented"); + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java new file mode 100644 index 00000000000..95c26827ca9 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java @@ -0,0 +1,74 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Iterator; +import java.util.List; +import javax.annotation.Nonnull; + +public class GroupByQuerySnapshot implements Iterable { + + GroupByQuerySnapshot() {} + + @Nonnull + public GroupByQuery getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public SnapshotMetadata getMetadata() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public List getGroups() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public List getGroupChanges() { + throw new RuntimeException("not implemented"); + } + + public List getGroupChanges(@NonNull MetadataChanges metadataChanges) { + throw new RuntimeException("not implemented"); + } + + public boolean isEmpty() { + throw new RuntimeException("not implemented"); + } + + public int size() { + throw new RuntimeException("not implemented"); + } + + @Override + @NonNull + public Iterator iterator() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object obj) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java new file mode 100644 index 00000000000..c472cc28bf7 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java @@ -0,0 +1,19 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +public enum GroupBySource { + SERVER_DIRECT, +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java new file mode 100644 index 00000000000..1e322290526 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java @@ -0,0 +1,57 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class GroupChange { + + public enum Type { + ADDED, + MODIFIED, + REMOVED + } + + GroupChange() {} + + @NonNull + public Type getType() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupSnapshot getGroup() { + throw new RuntimeException("not implemented"); + } + + public int getOldIndex() { + throw new RuntimeException("not implemented"); + } + + public int getNewIndex() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object object) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java new file mode 100644 index 00000000000..3ceda427472 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java @@ -0,0 +1,221 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; +import java.util.Date; +import java.util.Map; + +public class GroupSnapshot extends AggregateSnapshot { + + @NonNull + public Map getFields() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Map getFields( + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + public boolean contains(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + public boolean contains(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get( + @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get( + @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get(@NonNull FieldPath field, @NonNull Class valueType) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get(@NonNull String field, @NonNull Class valueType) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get( + @NonNull FieldPath field, + @NonNull Class valueType, + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get( + @NonNull String field, + @NonNull Class valueType, + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Boolean getBoolean(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Boolean getBoolean(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Double getDouble(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Double getDouble(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public String getString(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public String getString(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Long getLong(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Long getLong(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate( + @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate( + @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp( + @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp( + @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Blob getBlob(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Blob getBlob(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public GeoPoint getGeoPoint(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public GeoPoint getGeoPoint(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public DocumentReference getDocumentReference(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public DocumentReference getDocumentReference(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object obj) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public String toString() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index ff895f3bca1..77073211d5f 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1223,6 +1223,30 @@ private void validateHasExplicitOrderByForLimitToLast() { } } + @NonNull + public AggregateQuery aggregate( + @NonNull AggregateField field, @NonNull AggregateField... fields) { + throw new RuntimeException("not implemented"); + } + + // A convenience method for just getting the count of a query. + // This method also helps with visibility of the "count" feature, since "aggregate" is + // less obvious what you can do with it. + @NonNull + public AggregateQuery count() { + return aggregate(AggregateField.count()); + } + + @NonNull + public GroupByQuery groupBy(@NonNull String field1, @NonNull String... fields) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupBy(@NonNull FieldPath field1, @NonNull FieldPath... fields) { + throw new RuntimeException("not implemented"); + } + @Override public boolean equals(Object o) { if (this == o) { From 3112d79ffe29a7cc181edab50a0e0389fecf77a2 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 15:47:09 -0400 Subject: [PATCH 06/48] Datastore.java: runCountQuery() added --- .../firebase/firestore/remote/Datastore.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index 46323e881b9..570add6f2d8 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -14,6 +14,7 @@ package com.google.firebase.firestore.remote; +import static com.google.firebase.firestore.util.Assert.hardAssert; import static com.google.firebase.firestore.util.Util.exceptionFromStatus; import android.content.Context; @@ -25,17 +26,25 @@ import com.google.firebase.firestore.auth.CredentialsProvider; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.core.DatabaseInfo; +import com.google.firebase.firestore.core.Target; import com.google.firebase.firestore.model.DocumentKey; import com.google.firebase.firestore.model.MutableDocument; import com.google.firebase.firestore.model.SnapshotVersion; import com.google.firebase.firestore.model.mutation.Mutation; import com.google.firebase.firestore.model.mutation.MutationResult; import com.google.firebase.firestore.util.AsyncQueue; +import com.google.firestore.v1.AggregationResult; import com.google.firestore.v1.BatchGetDocumentsRequest; import com.google.firestore.v1.BatchGetDocumentsResponse; import com.google.firestore.v1.CommitRequest; import com.google.firestore.v1.CommitResponse; import com.google.firestore.v1.FirestoreGrpc; +import com.google.firestore.v1.RunAggregationQueryRequest; +import com.google.firestore.v1.RunAggregationQueryResponse; +import com.google.firestore.v1.RunQueryRequest; +import com.google.firestore.v1.StructuredAggregationQuery; +import com.google.firestore.v1.Value; + import io.grpc.Status; import java.util.ArrayList; import java.util.Arrays; @@ -215,6 +224,45 @@ public void onClose(Status status) { return completionSource.getTask(); } + public Task runCountQuery(Target queryTarget) { + com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = serializer.encodeQueryTarget(queryTarget); + + StructuredAggregationQuery.Builder structuredAggregationQuery = StructuredAggregationQuery.newBuilder(); + structuredAggregationQuery.setStructuredQuery(encodedQueryTarget.getStructuredQuery()); + + StructuredAggregationQuery.Aggregation.Builder aggregation = StructuredAggregationQuery.Aggregation.newBuilder(); + aggregation.setCount(StructuredAggregationQuery.Aggregation.Count.getDefaultInstance()); + aggregation.setAlias("zzyzx_agg_alias_count"); + structuredAggregationQuery.addAggregations(aggregation); + + RunAggregationQueryRequest.Builder request = RunAggregationQueryRequest.newBuilder(); + request.setParent(encodedQueryTarget.getParent()); + request.setStructuredAggregationQuery(structuredAggregationQuery); + + return channel.runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) + .continueWith( + workerQueue.getExecutor(), + task -> { + if (!task.isSuccessful()) { + if (task.getException() instanceof FirebaseFirestoreException + && ((FirebaseFirestoreException) task.getException()).getCode() + == FirebaseFirestoreException.Code.UNAUTHENTICATED) { + channel.invalidateToken(); + } + throw task.getException(); + } + RunAggregationQueryResponse response = task.getResult(); + + AggregationResult aggregationResult = response.getResult(); + Map aggregateFieldsByAlias = aggregationResult.getAggregateFieldsMap(); + hardAssert(aggregateFieldsByAlias.size() == 1, "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); + Value countValue = aggregateFieldsByAlias.get("zzyzx_agg_alias_count"); + hardAssert(countValue != null, "countValue == null"); + hardAssert(countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); + return countValue.getIntegerValue(); + }); + } + /** * Determines whether the given status has an error code that represents a permanent error when * received in response to a non-write operation. From 728f4cb573bbb400442b0ec1bd1deca28bdd0b3d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 16:22:47 -0400 Subject: [PATCH 07/48] wire up count queries --- .../firebase/firestore/AggregateQuery.java | 78 +++++++------------ .../firestore/AggregateQuerySnapshot.java | 18 ++--- .../com/google/firebase/firestore/Query.java | 21 +---- .../firebase/firestore/core/CountQuery.java | 33 ++++++++ .../firestore/core/FirestoreClient.java | 7 +- .../firestore/remote/RemoteStore.java | 2 + 6 files changed, 80 insertions(+), 79 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index e8ec4b91fbc..fa54137993a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -16,66 +16,46 @@ import android.app.Activity; import androidx.annotation.NonNull; + +import com.google.android.gms.tasks.Continuation; import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.firebase.firestore.core.CountQuery; +import com.google.firebase.firestore.util.Executors; + import java.util.concurrent.Executor; -public class AggregateQuery { +public final class AggregateQuery { - AggregateQuery() {} + private final Query query; - @NonNull - public Query getQuery() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Task get() { - throw new RuntimeException("not implemented"); + AggregateQuery(@NonNull Query query, @NonNull AggregateField aggregateField) { + this.query = query; + if (! (aggregateField instanceof AggregateField.CountAggregateField)) { + throw new IllegalArgumentException("unsupported aggregateField: " + aggregateField); + } } @NonNull - public Task get(@NonNull AggregateSource source) { - throw new RuntimeException("not implemented"); + public Query getQuery() { + return query; } @NonNull - public ListenConfig listen() { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(Object obj) { - throw new RuntimeException("not implemented"); + public Task get() { + CountQuery countQuery = new CountQuery(query.firestore.getClient().getDatastore(), query.query); + TaskCompletionSource tcs = new TaskCompletionSource<>(); + + countQuery.run().continueWith(Executors.DIRECT_EXECUTOR, task -> { + if (task.isSuccessful()) { + tcs.setResult(new AggregateQuerySnapshot(task.getResult())); + } else { + tcs.setException(task.getException()); + } + return null; + }); + + return tcs.getTask(); } - public static final class ListenConfig { - - private ListenConfig() {} - - @NonNull - public ListenConfig executeCallbacksOn(@NonNull Executor executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenConfig scopeTo(@NonNull Activity executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenConfig includeMetadataOnlyChanges(boolean includeMetadataOnlyChanges) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenerRegistration startDirectFromServer( - @NonNull EventListener listener) { - throw new RuntimeException("not implemented"); - } - } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 2a786c0b478..6f9da9aea8f 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -15,19 +15,19 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; -import javax.annotation.Nonnull; +import androidx.annotation.Nullable; -public class AggregateQuerySnapshot extends AggregateSnapshot { +public class AggregateQuerySnapshot { - AggregateQuerySnapshot() {} + private final long count; - @Nonnull - public AggregateQuery getQuery() { - throw new RuntimeException("not implemented"); + AggregateQuerySnapshot(long count) { + this.count = count; } - @NonNull - public SnapshotMetadata getMetadata() { - throw new RuntimeException("not implemented"); + @Nullable + public Long get(@NonNull AggregateField.CountAggregateField field) { + return count; } + } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index 77073211d5f..fc28cb9aee0 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1223,28 +1223,9 @@ private void validateHasExplicitOrderByForLimitToLast() { } } - @NonNull - public AggregateQuery aggregate( - @NonNull AggregateField field, @NonNull AggregateField... fields) { - throw new RuntimeException("not implemented"); - } - - // A convenience method for just getting the count of a query. - // This method also helps with visibility of the "count" feature, since "aggregate" is - // less obvious what you can do with it. @NonNull public AggregateQuery count() { - return aggregate(AggregateField.count()); - } - - @NonNull - public GroupByQuery groupBy(@NonNull String field1, @NonNull String... fields) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupBy(@NonNull FieldPath field1, @NonNull FieldPath... fields) { - throw new RuntimeException("not implemented"); + return new AggregateQuery(this, AggregateField.count()); } @Override diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java new file mode 100644 index 00000000000..c50fd5fb5a7 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java @@ -0,0 +1,33 @@ +// Copyright 2022 Google LLC +// +// 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.firestore.core; + +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.remote.Datastore; + +public class CountQuery { + private final Datastore datastore; + private final Query query; + + public CountQuery(Datastore datastore, Query query) { + this.datastore = datastore; + this.query = query; + } + + public Task run() { + return datastore.runCountQuery(query.toTarget()); + } + +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 464cd5913f9..20d5d96da6c 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -70,6 +70,7 @@ public final class FirestoreClient { private final BundleSerializer bundleSerializer; private final GrpcMetadataProvider metadataProvider; + private Datastore datastore; private Persistence persistence; private LocalStore localStore; private RemoteStore remoteStore; @@ -255,7 +256,7 @@ private void initialize(Context context, User user, FirebaseFirestoreSettings se // completes. Logger.debug(LOG_TAG, "Initializing. user=%s", user.getUid()); - Datastore datastore = + this.datastore = new Datastore( databaseInfo, asyncQueue, authProvider, appCheckProvider, context, metadataProvider); ComponentProvider.Configuration configuration = @@ -302,6 +303,10 @@ public void loadBundle(InputStream bundleData, LoadBundleTask resultTask) { asyncQueue.enqueueAndForget(() -> syncEngine.loadBundle(bundleReader, resultTask)); } + public Datastore getDatastore() { + return datastore; + } + public Task getNamedQuery(String queryName) { verifyNotTerminated(); TaskCompletionSource completionSource = new TaskCompletionSource<>(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index 2af8508957d..45c909c838e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -19,6 +19,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.firebase.database.collection.ImmutableSortedSet; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.core.CountQuery; import com.google.firebase.firestore.core.OnlineState; import com.google.firebase.firestore.core.Transaction; import com.google.firebase.firestore.local.LocalStore; From ec2be899ec41e5c2ded97ba106da855ee0ab125d Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 16:32:22 -0400 Subject: [PATCH 08/48] delete group-by stuff, since it's not needed for this experiment --- .../firebase/firestore/AggregateDemo.java | 97 -------- .../firebase/firestore/GroupByQuery.java | 175 -------------- .../firestore/GroupByQuerySnapshot.java | 74 ------ .../firebase/firestore/GroupBySource.java | 19 -- .../firebase/firestore/GroupChange.java | 57 ----- .../firebase/firestore/GroupSnapshot.java | 221 ------------------ 6 files changed, 643 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java deleted file mode 100644 index fae409b128d..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import static com.google.firebase.firestore.AggregateField.count; - -import android.app.Activity; -import androidx.annotation.NonNull; -import java.util.concurrent.Executors; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -final class AggregateDemo { - - private final FirebaseFirestore db; - - AggregateDemo(@NonNull FirebaseFirestore db) { - this.db = db; - } - - void countBasicQuery() { - AggregateQuery query = db.collection("users").count(); - AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); - assertEqual(snapshot.get(count()), 50); - } - - void countLimitNumRowsScanned() { - AggregateQuery query = db.collection("users").limit(11).count(); - AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); - assertEqual(snapshot.get(count()), 11); - } - - void countUpTo() { - AggregateQuery query = db.collection("users").aggregate(count().upTo(11)); - AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); - assertEqual(snapshot.get(count()), 11); - } - - void countRealTimeUpdates() throws InterruptedException { - AggregateQuery query = db.collection("users").aggregate(count()); - - Lock lock = new ReentrantLock(); - Condition condition = lock.newCondition(); - - ListenerRegistration registration = - query - .listen() - .startDirectFromServer( - (snapshot, error) -> { - assertNull(error); - assertNotNull(snapshot); - assertEqual(snapshot.get(count()), 50); - assertFalse(snapshot.getMetadata().isFromCache()); - assertFalse(snapshot.getMetadata().hasPendingWrites()); - lock.lock(); - try { - condition.signalAll(); - } finally { - lock.unlock(); - } - }); - - lock.lock(); - try { - condition.await(); - } finally { - lock.unlock(); - } - - registration.remove(); - } - - private static void assertEqual(Object o1, Object o2) {} - - private static void assertNull(Object o) {} - - private static void assertFalse(Object o) {} - - private static void assertNotNull(Object o) { - if (o == null) { - throw new NullPointerException(); - } - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java deleted file mode 100644 index 073f134fe6c..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import android.app.Activity; -import androidx.annotation.NonNull; -import com.google.android.gms.tasks.Task; -import com.google.firebase.firestore.Query.Direction; -import java.util.concurrent.Executor; - -public class GroupByQuery { - - GroupByQuery() {} - - @NonNull - public Query getQuery() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Task get() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Task get(@NonNull GroupBySource source) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public AggregateQuery.ListenConfig listen() { - throw new RuntimeException("not implemented"); - } - - // Note: Specifying an empty list of aggregates, or not invoking this method at all, is equivalent - // to an SQL "DISTINCT" operator. - @NonNull - public GroupByQuery aggregate(@NonNull AggregateField... fields) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupLimit(long maxGroups) { - throw new RuntimeException("not implemented"); - } - - // Question: Do we want to support group-by "limitToLast" queries? In the Query class this is - // implemented entirely client side by issuing the requested query with inverted order-by. We - // would need to verify at runtime that the underlying query has the correct order-by clause and - // possibly invert first/last aggregations to maintain their expected semantics. - @NonNull - public GroupByQuery groupLimitToLast(long maxGroups) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAt(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAt(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAfter(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAfter(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndAt(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndAt(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndBefore(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndBefore(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull String groupByField) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull AggregateField aggregateField) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull String groupByField, @NonNull Direction direction) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField, @NonNull Direction direction) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy( - @NonNull AggregateField aggregateField, @NonNull Direction direction) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(Object obj) { - throw new RuntimeException("not implemented"); - } - - public static final class ListenConfig { - - private ListenConfig() {} - - @NonNull - public AggregateQuery.ListenConfig executeCallbacksOn(@NonNull Executor executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public AggregateQuery.ListenConfig scopeTo(@NonNull Activity executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public AggregateQuery.ListenConfig includeMetadataOnlyChanges( - boolean includeMetadataOnlyChanges) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenerRegistration startDirectFromServer( - @NonNull EventListener listener) { - throw new RuntimeException("not implemented"); - } - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java deleted file mode 100644 index 95c26827ca9..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import java.util.Iterator; -import java.util.List; -import javax.annotation.Nonnull; - -public class GroupByQuerySnapshot implements Iterable { - - GroupByQuerySnapshot() {} - - @Nonnull - public GroupByQuery getQuery() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public SnapshotMetadata getMetadata() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public List getGroups() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public List getGroupChanges() { - throw new RuntimeException("not implemented"); - } - - public List getGroupChanges(@NonNull MetadataChanges metadataChanges) { - throw new RuntimeException("not implemented"); - } - - public boolean isEmpty() { - throw new RuntimeException("not implemented"); - } - - public int size() { - throw new RuntimeException("not implemented"); - } - - @Override - @NonNull - public Iterator iterator() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java deleted file mode 100644 index c472cc28bf7..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -public enum GroupBySource { - SERVER_DIRECT, -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java deleted file mode 100644 index 1e322290526..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class GroupChange { - - public enum Type { - ADDED, - MODIFIED, - REMOVED - } - - GroupChange() {} - - @NonNull - public Type getType() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupSnapshot getGroup() { - throw new RuntimeException("not implemented"); - } - - public int getOldIndex() { - throw new RuntimeException("not implemented"); - } - - public int getNewIndex() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object object) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java deleted file mode 100644 index 3ceda427472..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.firebase.Timestamp; -import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; -import java.util.Date; -import java.util.Map; - -public class GroupSnapshot extends AggregateSnapshot { - - @NonNull - public Map getFields() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Map getFields( - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - public boolean contains(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - public boolean contains(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get( - @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get( - @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get(@NonNull FieldPath field, @NonNull Class valueType) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get(@NonNull String field, @NonNull Class valueType) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get( - @NonNull FieldPath field, - @NonNull Class valueType, - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get( - @NonNull String field, - @NonNull Class valueType, - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Boolean getBoolean(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Boolean getBoolean(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Double getDouble(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Double getDouble(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public String getString(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public String getString(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Long getLong(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Long getLong(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate( - @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate( - @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp( - @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp( - @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Blob getBlob(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Blob getBlob(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public GeoPoint getGeoPoint(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public GeoPoint getGeoPoint(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public DocumentReference getDocumentReference(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public DocumentReference getDocumentReference(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public String toString() { - throw new RuntimeException("not implemented"); - } -} From 3f2a5399fa68f289ddf41d572034c9f429eb1f67 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:21:27 -0400 Subject: [PATCH 09/48] CountTest.java added --- .../google/firebase/firestore/CountTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java new file mode 100644 index 00000000000..a49851d09f1 --- /dev/null +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -0,0 +1,53 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollectionWithDocs; +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor; +import static com.google.firebase.firestore.testutil.TestUtil.map; +import static org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.firebase.firestore.AggregateField; +import com.google.firebase.firestore.AggregateQuerySnapshot; +import com.google.firebase.firestore.testutil.IntegrationTestUtil; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class CountTest { + + @After + public void tearDown() { + IntegrationTestUtil.tearDown(); + } + + @Test + public void count() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + + AggregateQuerySnapshot snapshot = waitFor(collection.count().get()); + assertEquals(Long.valueOf(3), snapshot.get(AggregateField.count())); + } + +} From dccef124437da4c090f9cccdefcc8f3440ae6939 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:31:43 -0400 Subject: [PATCH 10/48] Revert changes to Firestore.kt, since this prototype only uses java --- .../firebase/firestore/ktx/Firestore.kt | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt index fa72f33ca2f..ec2ee225d41 100644 --- a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt +++ b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt @@ -18,7 +18,12 @@ import androidx.annotation.Keep import com.google.firebase.FirebaseApp import com.google.firebase.components.Component import com.google.firebase.components.ComponentRegistrar -import com.google.firebase.firestore.* +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FieldPath +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.FirebaseFirestoreSettings +import com.google.firebase.firestore.QueryDocumentSnapshot +import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.ktx.Firebase import com.google.firebase.platforminfo.LibraryVersionComponent @@ -149,30 +154,6 @@ fun firestoreSettings(init: FirebaseFirestoreSettings.Builder.() -> Unit): Fireb return builder.build() } -inline fun GroupSnapshot.getField(field: String): T? = get(field, T::class.java) - -inline fun GroupSnapshot.getField( - field: String, - serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior -): T? = - get(field, T::class.java, serverTimestampBehavior) - -inline fun GroupSnapshot.getField(fieldPath: FieldPath): T? = get(fieldPath, T::class.java) - -inline fun GroupSnapshot.getField( - fieldPath: FieldPath, - serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior -): T? = - get(fieldPath, T::class.java, serverTimestampBehavior) - -inline fun AggregateSnapshot.getField(field: AggregateField): T? = get(field, T::class.java) - -inline fun AggregateSnapshot.getField( - field: AggregateField, - serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior -): T? = - get(field, T::class.java, serverTimestampBehavior) - internal const val LIBRARY_NAME: String = "fire-fst-ktx" /** @suppress */ From 20b6f6133ac9c27dd4633ceb5f4beb5944aef975 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:32:04 -0400 Subject: [PATCH 11/48] AggregateField.java: remove everything except COUNT --- .../firebase/firestore/AggregateField.java | 266 +----------------- 1 file changed, 4 insertions(+), 262 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index d599059be16..4137f1111f0 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -16,7 +16,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Objects; public abstract class AggregateField { @@ -27,275 +26,18 @@ public static CountAggregateField count() { return new CountAggregateField(); } - @NonNull - public static MinAggregateField min(@NonNull String field) { - return min(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static MinAggregateField min(@NonNull FieldPath field) { - return new MinAggregateField(field); - } - - @NonNull - public static MaxAggregateField max(@NonNull String field) { - return max(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static MaxAggregateField max(@NonNull FieldPath field) { - return new MaxAggregateField(field); - } - - @NonNull - public static AverageAggregateField average(@NonNull String field) { - return average(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static AverageAggregateField average(@NonNull FieldPath field) { - return new AverageAggregateField(field); - } - - @NonNull - public static SumAggregateField sum(@NonNull String field) { - return sum(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static SumAggregateField sum(@NonNull FieldPath field) { - return new SumAggregateField(field); - } - - @NonNull - public static FirstAggregateField first(@NonNull String field) { - return first(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static FirstAggregateField first(@NonNull FieldPath field) { - return new FirstAggregateField(field); - } - - @NonNull - public static LastAggregateField last(@NonNull String field) { - return last(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static LastAggregateField last(@NonNull FieldPath field) { - return new LastAggregateField(field); - } - - @Override - public abstract boolean equals(Object obj); - - @Override - public abstract int hashCode(); - - @Override - public abstract String toString(); - public static final class CountAggregateField extends AggregateField { - @Nullable private Integer upTo; + @Nullable + private Integer upTo; - CountAggregateField() {} + CountAggregateField() { + } CountAggregateField(@Nullable Integer upTo) { this.upTo = upTo; } - public CountAggregateField upTo(int upTo) { - if (upTo < 0) { - throw new IllegalArgumentException("upTo==" + upTo); - } - return new CountAggregateField(upTo); - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - CountAggregateField other = (CountAggregateField) obj; - return Objects.equals(upTo, other.upTo); - } - - @Override - public int hashCode() { - return Objects.hash("COUNT", upTo); - } - - @Override - public String toString() { - if (upTo == null) { - return "COUNT"; - } else { - return "COUNT(upTo=" + upTo + ")"; - } - } - } - - public static final class MinAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - MinAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((MinAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "MIN(" + field.toString() + ")"; - } - } - - public static final class MaxAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - MaxAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((MaxAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "MAX(" + field.toString() + ")"; - } - } - - public static final class AverageAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - AverageAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((AverageAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "AVERAGE(" + field.toString() + ")"; - } - } - - public static final class SumAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - SumAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((SumAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "SUM(" + field.toString() + ")"; - } - } - - public static final class FirstAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - FirstAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((FirstAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "FIRST(" + field.toString() + ")"; - } } - public static final class LastAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - LastAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((LastAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "LAST(" + field.toString() + ")"; - } - } } From 4eee46cb2a4e6500b40850fe188b1ce876996b06 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:34:15 -0400 Subject: [PATCH 12/48] AggregateSnapshot.java deleted, since it's not used in this prototype --- .../firebase/firestore/AggregateSnapshot.java | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java deleted file mode 100644 index cd9bad6a978..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.firebase.Timestamp; -import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; -import java.util.Date; -import java.util.Map; -import javax.annotation.Nonnull; - -public class AggregateSnapshot { - - AggregateSnapshot() {} - - @NonNull - public Map getAggregations() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Map getAggregations( - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - public boolean contains(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get( - @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get(@NonNull AggregateField field, @NonNull Class valueType) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get( - @NonNull AggregateField field, - @NonNull Class valueType, - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - // Overload get() specifically for COUNT since it has a well-defined type (i.e. long). - @Nullable - public Long get(@Nonnull AggregateField.CountAggregateField field) { - throw new RuntimeException("not implemented"); - } - - // Overload get() specifically for SUM since it has a well-defined type (i.e. double). - @Nullable - public Double get(@Nonnull AggregateField.SumAggregateField field) { - throw new RuntimeException("not implemented"); - } - - // Overload get() specifically for AVERAGE since it has a well-defined type (i.e. double). - @Nullable - public Double get(@Nonnull AggregateField.AverageAggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Boolean getBoolean(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Double getDouble(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public String getString(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Long getLong(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate( - @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp( - @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Blob getBlob(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public GeoPoint getGeoPoint(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public DocumentReference getDocumentReference(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public String toString() { - throw new RuntimeException("not implemented"); - } -} From 41ce0a833e4386018d812ccf3dc7f6e94423f6c5 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:48:23 -0400 Subject: [PATCH 13/48] revert a bunch of other unnecssary changes --- .../firebase/firestore/AggregateSource.java | 19 -- .../firestore/remote/RemoteStore.java | 2 - .../proto/google/firestore/v1/common.proto | 8 +- .../proto/google/firestore/v1/document.proto | 6 +- .../proto/google/firestore/v1/firestore.proto | 315 +++++------------- .../src/proto/google/firestore/v1/query.proto | 99 +++--- .../src/proto/google/firestore/v1/write.proto | 11 +- 7 files changed, 145 insertions(+), 315 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java deleted file mode 100644 index dd119c59e59..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -public enum AggregateSource { - SERVER_DIRECT, -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index 45c909c838e..2af8508957d 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -19,8 +19,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.firebase.database.collection.ImmutableSortedSet; -import com.google.firebase.firestore.Query; -import com.google.firebase.firestore.core.CountQuery; import com.google.firebase.firestore.core.OnlineState; import com.google.firebase.firestore.core.Transaction; import com.google.firebase.firestore.local.LocalStore; diff --git a/firebase-firestore/src/proto/google/firestore/v1/common.proto b/firebase-firestore/src/proto/google/firestore/v1/common.proto index 588a9b81b79..670cb41739b 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/common.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/common.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; +import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; option csharp_namespace = "Google.Cloud.Firestore.V1"; @@ -25,7 +27,7 @@ option java_outer_classname = "CommonProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A set of field paths on a document. // Used to restrict a get or update operation on a document to a subset of its @@ -47,7 +49,7 @@ message Precondition { bool exists = 1; // When set, the target document must exist and have been last updated at - // that time. Timestamp must be microsecond aligned. + // that time. google.protobuf.Timestamp update_time = 2; } } diff --git a/firebase-firestore/src/proto/google/firestore/v1/document.proto b/firebase-firestore/src/proto/google/firestore/v1/document.proto index 41283588eb8..268947856a8 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/document.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/document.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; +import "google/api/annotations.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/type/latlng.proto"; @@ -27,7 +29,7 @@ option java_outer_classname = "DocumentProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A Firestore document. // diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index 69787e8289a..dda4721596c 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,14 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; import "google/api/annotations.proto"; -import "google/api/client.proto"; -import "google/api/field_behavior.proto"; import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; @@ -33,25 +32,27 @@ option go_package = "google.golang.org/genproto/googleapis/firestore/v1;firestor option java_multiple_files = true; option java_outer_classname = "FirestoreProto"; option java_package = "com.google.firestore.v1"; +option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; - // Specification of the Firestore API. // The Cloud Firestore service. // -// Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL -// document database that simplifies storing, syncing, and querying data for -// your mobile, web, and IoT apps at global scale. Its client libraries provide -// live synchronization and offline support, while its security features and -// integrations with Firebase and Google Cloud Platform (GCP) accelerate -// building truly serverless apps. +// This service exposes several types of comparable timestamps: +// +// * `create_time` - The time at which a document was created. Changes only +// when a document is deleted, then re-created. Increases in a strict +// monotonic fashion. +// * `update_time` - The time at which a document was last updated. Changes +// every time a document is modified. Does not change when a write results +// in no modifications. Increases in a strict monotonic fashion. +// * `read_time` - The time at which a particular state was observed. Used +// to denote a consistent snapshot of the database or the time at which a +// Document was observed to not exist. +// * `commit_time` - The time at which the writes in a transaction were +// committed. Any read with an equal or greater `read_time` is guaranteed +// to see the effects of the transaction. service Firestore { - option (google.api.default_host) = "firestore.googleapis.com"; - option (google.api.oauth_scopes) = - "https://www.googleapis.com/auth/cloud-platform," - "https://www.googleapis.com/auth/datastore"; - // Gets a single document. rpc GetDocument(GetDocumentRequest) returns (Document) { option (google.api.http) = { @@ -63,9 +64,14 @@ service Firestore { rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}" - additional_bindings { - get: "/v1/{parent=projects/*/databases/*/documents}/{collection_id}" - } + }; + } + + // Creates a new document. + rpc CreateDocument(CreateDocumentRequest) returns (Document) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" + body: "document" }; } @@ -75,7 +81,6 @@ service Firestore { patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}" body: "document" }; - option (google.api.method_signature) = "document,update_mask"; } // Deletes a document. @@ -83,7 +88,6 @@ service Firestore { option (google.api.http) = { delete: "/v1/{name=projects/*/databases/*/documents/*/**}" }; - option (google.api.method_signature) = "name"; } // Gets multiple documents. @@ -103,7 +107,6 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction" body: "*" }; - option (google.api.method_signature) = "database"; } // Commits a transaction, while optionally updating documents. @@ -112,7 +115,6 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:commit" body: "*" }; - option (google.api.method_signature) = "database,writes"; } // Rolls back a transaction. @@ -121,7 +123,6 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:rollback" body: "*" }; - option (google.api.method_signature) = "database,transaction"; } // Runs a query. @@ -159,20 +160,6 @@ service Firestore { }; } - // Partitions a query by returning partition cursors that can be used to run - // the query in parallel. The returned partition cursors are split points that - // can be used by RunQuery as starting/end points for the query results. - rpc PartitionQuery(PartitionQueryRequest) returns (PartitionQueryResponse) { - option (google.api.http) = { - post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery" - body: "*" - additional_bindings { - post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery" - body: "*" - } - }; - } - // Streams batches of document updates and deletes, in order. rpc Write(stream WriteRequest) returns (stream WriteResponse) { option (google.api.http) = { @@ -199,39 +186,14 @@ service Firestore { body: "*" } }; - option (google.api.method_signature) = "parent"; - } - - // Applies a batch of write operations. - // - // The BatchWrite method does not apply the write operations atomically - // and can apply them out of order. Method does not allow more than one write - // per document. Each write succeeds or fails independently. See the - // [BatchWriteResponse][google.firestore.v1.BatchWriteResponse] for the success status of each write. - // - // If you require an atomically applied set of writes, use - // [Commit][google.firestore.v1.Firestore.Commit] instead. - rpc BatchWrite(BatchWriteRequest) returns (BatchWriteResponse) { - option (google.api.http) = { - post: "/v1/{database=projects/*/databases/*}/documents:batchWrite" - body: "*" - }; - } - - // Creates a new document. - rpc CreateDocument(CreateDocumentRequest) returns (Document) { - option (google.api.http) = { - post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" - body: "document" - }; } } // The request for [Firestore.GetDocument][google.firestore.v1.Firestore.GetDocument]. message GetDocumentRequest { - // Required. The resource name of the Document to get. In the format: + // The resource name of the Document to get. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1 [(google.api.field_behavior) = REQUIRED]; + string name = 1; // The fields to return. If not set, returns all fields. // @@ -246,24 +208,24 @@ message GetDocumentRequest { bytes transaction = 3; // Reads the version of the document at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 5; } } // The request for [Firestore.ListDocuments][google.firestore.v1.Firestore.ListDocuments]. message ListDocumentsRequest { - // Required. The parent resource name. In the format: + // The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; - // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms` + // The collection ID, relative to `parent`, to list. For example: `chatrooms` // or `messages`. - string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; + string collection_id = 2; // The maximum number of documents to return. int32 page_size = 3; @@ -287,7 +249,7 @@ message ListDocumentsRequest { bytes transaction = 8; // Reads documents as they were at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 10; } @@ -312,21 +274,21 @@ message ListDocumentsResponse { // The request for [Firestore.CreateDocument][google.firestore.v1.Firestore.CreateDocument]. message CreateDocumentRequest { - // Required. The parent resource. For example: + // The parent resource. For example: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/chatrooms/{chatroom_id}` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; - // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms`. - string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; + // The collection ID, relative to `parent`, to list. For example: `chatrooms`. + string collection_id = 2; // The client-assigned document ID to use for this document. // // Optional. If not specified, an ID will be assigned by the service. string document_id = 3; - // Required. The document to create. `name` must not be set. - Document document = 4 [(google.api.field_behavior) = REQUIRED]; + // The document to create. `name` must not be set. + Document document = 4; // The fields to return. If not set, returns all fields. // @@ -337,9 +299,9 @@ message CreateDocumentRequest { // The request for [Firestore.UpdateDocument][google.firestore.v1.Firestore.UpdateDocument]. message UpdateDocumentRequest { - // Required. The updated document. + // The updated document. // Creates the document if it does not already exist. - Document document = 1 [(google.api.field_behavior) = REQUIRED]; + Document document = 1; // The fields to update. // None of the field paths in the mask may contain a reserved name. @@ -363,9 +325,9 @@ message UpdateDocumentRequest { // The request for [Firestore.DeleteDocument][google.firestore.v1.Firestore.DeleteDocument]. message DeleteDocumentRequest { - // Required. The resource name of the Document to delete. In the format: + // The resource name of the Document to delete. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1 [(google.api.field_behavior) = REQUIRED]; + string name = 1; // An optional precondition on the document. // The request will fail if this is set and not met by the target document. @@ -374,9 +336,9 @@ message DeleteDocumentRequest { // The request for [Firestore.BatchGetDocuments][google.firestore.v1.Firestore.BatchGetDocuments]. message BatchGetDocumentsRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The names of the documents to retrieve. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. @@ -403,7 +365,7 @@ message BatchGetDocumentsRequest { TransactionOptions new_transaction = 5; // Reads documents as they were at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -435,9 +397,9 @@ message BatchGetDocumentsResponse { // The request for [Firestore.BeginTransaction][google.firestore.v1.Firestore.BeginTransaction]. message BeginTransactionRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The options for the transaction. // Defaults to a read-write transaction. @@ -452,9 +414,9 @@ message BeginTransactionResponse { // The request for [Firestore.Commit][google.firestore.v1.Firestore.Commit]. message CommitRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The writes to apply. // @@ -473,30 +435,29 @@ message CommitResponse { // request. repeated WriteResult write_results = 1; - // The time at which the commit occurred. Any read with an equal or greater - // `read_time` is guaranteed to see the effects of the commit. + // The time at which the commit occurred. google.protobuf.Timestamp commit_time = 2; } // The request for [Firestore.Rollback][google.firestore.v1.Firestore.Rollback]. message RollbackRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; - // Required. The transaction to roll back. - bytes transaction = 2 [(google.api.field_behavior) = REQUIRED]; + // The transaction to roll back. + bytes transaction = 2; } // The request for [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery]. message RunQueryRequest { - // Required. The parent resource name. In the format: + // The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; // The query to run. oneof query_type { @@ -507,9 +468,7 @@ message RunQueryRequest { // The consistency mode for this transaction. // If not set, defaults to strong consistency. oneof consistency_selector { - // Run the query within an already active transaction. - // - // The value here is the opaque transaction ID to execute the query in. + // Reads documents in a transaction. bytes transaction = 5; // Starts a new transaction and reads the documents. @@ -519,7 +478,7 @@ message RunQueryRequest { TransactionOptions new_transaction = 6; // Reads documents as they were at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -532,7 +491,8 @@ message RunQueryResponse { // If set, no other fields will be set in this response. bytes transaction = 2; - // A query result, not set when reporting partial progress. + // A query result. + // Not set when reporting partial progress. Document document = 1; // The time at which the document was read. This may be monotonically @@ -547,15 +507,6 @@ message RunQueryResponse { // The number of results that have been skipped due to an offset between // the last response and the current response. int32 skipped_results = 4; - - // The continuation mode for the query. If present, it indicates the current - // query response stream has finished. This can be set with or without a - // `document` present, but when set, no more results are returned. - oneof continuation_selector { - // If present, Firestore has completely finished the request and no more - // documents will be returned. - bool done = 6; - } } // The request for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. @@ -566,7 +517,7 @@ message RunAggregationQueryRequest { // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; // The query to run. oneof query_type { @@ -614,85 +565,6 @@ message RunAggregationQueryResponse { google.protobuf.Timestamp read_time = 3; } -// The request for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. -message PartitionQueryRequest { - // Required. The parent resource name. In the format: - // `projects/{project_id}/databases/{database_id}/documents`. - // Document resource names are not supported; only database resource names - // can be specified. - string parent = 1 [(google.api.field_behavior) = REQUIRED]; - - // The query to partition. - oneof query_type { - // A structured query. - // Query must specify collection with all descendants and be ordered by name - // ascending. Other filters, order bys, limits, offsets, and start/end - // cursors are not supported. - StructuredQuery structured_query = 2; - } - - // The desired maximum number of partition points. - // The partitions may be returned across multiple pages of results. - // The number must be positive. The actual number of partitions - // returned may be fewer. - // - // For example, this may be set to one fewer than the number of parallel - // queries to be run, or in running a data pipeline job, one fewer than the - // number of workers or compute instances available. - int64 partition_count = 3; - - // The `next_page_token` value returned from a previous call to - // PartitionQuery that may be used to get an additional set of results. - // There are no ordering guarantees between sets of results. Thus, using - // multiple sets of results will require merging the different result sets. - // - // For example, two subsequent calls using a page_token may return: - // - // * cursor B, cursor M, cursor Q - // * cursor A, cursor U, cursor W - // - // To obtain a complete result set ordered with respect to the results of the - // query supplied to PartitionQuery, the results sets should be merged: - // cursor A, cursor B, cursor M, cursor Q, cursor U, cursor W - string page_token = 4; - - // The maximum number of partitions to return in this call, subject to - // `partition_count`. - // - // For example, if `partition_count` = 10 and `page_size` = 8, the first call - // to PartitionQuery will return up to 8 partitions and a `next_page_token` - // if more results exist. A second call to PartitionQuery will return up to - // 2 partitions, to complete the total of 10 specified in `partition_count`. - int32 page_size = 5; -} - -// The response for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. -message PartitionQueryResponse { - // Partition results. - // Each partition is a split point that can be used by RunQuery as a starting - // or end point for the query results. The RunQuery requests must be made with - // the same query supplied to this PartitionQuery request. The partition - // cursors will be ordered according to same ordering as the results of the - // query supplied to PartitionQuery. - // - // For example, if a PartitionQuery request returns partition cursors A and B, - // running the following three queries will return the entire result set of - // the original query: - // - // * query, end_at A - // * query, start_at A, end_at B - // * query, start_at B - // - // An empty result may indicate that the query has too few results to be - // partitioned. - repeated Cursor partitions = 1; - - // A page token that may be used to request an additional set of results, up - // to the number specified by `partition_count` in the PartitionQuery request. - // If blank, there are no more results. - string next_page_token = 2; -} - // The request for [Firestore.Write][google.firestore.v1.Firestore.Write]. // // The first request creates a stream, or resumes an existing one from a token. @@ -704,10 +576,10 @@ message PartitionQueryResponse { // given token, then a response containing only an up-to-date token, to use in // the next request. message WriteRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. // This is only required in the first message. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The ID of the write stream to resume. // This may only be set in the first message. When left empty, a new write @@ -760,16 +632,15 @@ message WriteResponse { // request. repeated WriteResult write_results = 3; - // The time at which the commit occurred. Any read with an equal or greater - // `read_time` is guaranteed to see the effects of the write. + // The time at which the commit occurred. google.protobuf.Timestamp commit_time = 4; } // A request for [Firestore.Listen][google.firestore.v1.Firestore.Listen] message ListenRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The supported target changes. oneof target_change { @@ -863,8 +734,14 @@ message Target { google.protobuf.Timestamp read_time = 11; } - // The target ID that identifies the target on the stream. Must be a positive - // number and non-zero. + // A client provided target ID. + // + // If not set, the server will assign an ID for the target. + // + // Used for resuming a target without changing IDs. The IDs can either be + // client-assigned or be server-assigned in a previous stream. All targets + // with client provided IDs must be added before adding a target that needs + // a server-assigned id. int32 target_id = 5; // If the target should be removed once it is current and consistent. @@ -909,7 +786,11 @@ message TargetChange { // // If empty, the change applies to all targets. // - // The order of the target IDs is not defined. + // For `target_change_type=ADD`, the order of the target IDs matches the order + // of the requests to add the targets. This allows clients to unambiguously + // associate server-assigned target IDs with added targets. + // + // For other states, the order of the target IDs is not defined. repeated int32 target_ids = 2; // The error that resulted in this change, if applicable. @@ -936,11 +817,11 @@ message TargetChange { // The request for [Firestore.ListCollectionIds][google.firestore.v1.Firestore.ListCollectionIds]. message ListCollectionIdsRequest { - // Required. The parent document. In the format: + // The parent document. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; // The maximum number of results to return. int32 page_size = 2; @@ -958,35 +839,3 @@ message ListCollectionIdsResponse { // A page token that may be used to continue the list. string next_page_token = 2; } - -// The request for [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. -message BatchWriteRequest { - // Required. The database name. In the format: - // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; - - // The writes to apply. - // - // Method does not apply writes atomically and does not guarantee ordering. - // Each write succeeds or fails independently. You cannot write to the same - // document more than once per request. - repeated Write writes = 2; - - // Labels associated with this batch write. - map labels = 3; -} - -// The response from [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. -message BatchWriteResponse { - // The result of applying the writes. - // - // This i-th write result corresponds to the i-th write in the - // request. - repeated WriteResult write_results = 1; - - // The status of applying the writes. - // - // This i-th write status corresponds to the i-th write in the - // request. - repeated google.rpc.Status status = 2; -} diff --git a/firebase-firestore/src/proto/google/firestore/v1/query.proto b/firebase-firestore/src/proto/google/firestore/v1/query.proto index 4c6250fa780..d76642c4c21 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/query.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/query.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2019 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,12 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; -import "google/api/field_behavior.proto"; +import "google/api/annotations.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/wrappers.proto"; @@ -27,7 +28,7 @@ option java_outer_classname = "QueryProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A Firestore query. message StructuredQuery { @@ -65,7 +66,7 @@ message StructuredQuery { // Unspecified. This value must not be used. OPERATOR_UNSPECIFIED = 0; - // Documents are required to satisfy all of the combined filters. + // The results are required to satisfy each of the combined filters. AND = 1; } @@ -73,10 +74,7 @@ message StructuredQuery { Operator op = 1; // The list of filters to combine. - // - // Requires: - // - // * At least one filter is present. + // Must contain at least one filter. repeated Filter filters = 2; } @@ -115,7 +113,7 @@ message StructuredQuery { // * That `field` come first in `order_by`. GREATER_THAN_OR_EQUAL = 4; - // The given `field` is equal to the given `value`. + // The given `field` is equal to the given `value`.. EQUAL = 5; // The given `field` is not equal to the given `value`. @@ -134,7 +132,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. + // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) IN = 8; // The given `field` is an array that contains any of the values in the @@ -143,7 +141,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. + // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) ARRAY_CONTAINS_ANY = 9; // The value of the `field` is not in the given array. @@ -171,30 +169,30 @@ message StructuredQuery { message UnaryFilter { // A unary operator. enum Operator { - // Unspecified. This value must not be used. - OPERATOR_UNSPECIFIED = 0; - - // The given `field` is equal to `NaN`. - IS_NAN = 2; - - // The given `field` is equal to `NULL`. - IS_NULL = 3; - - // The given `field` is not equal to `NaN`. - // - // Requires: - // - // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NAN = 4; - - // The given `field` is not equal to `NULL`. - // - // Requires: - // - // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NULL = 5; + // Unspecified. This value must not be used. + OPERATOR_UNSPECIFIED = 0; + + // The given `field` is equal to `NaN`. + IS_NAN = 2; + + // The given `field` is equal to `NULL`. + IS_NULL = 3; + + // The given `field` is not equal to `NaN`. + // + // Requires: + // + // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NAN = 4; + + // The given `field` is not equal to `NULL`. + // + // Requires: + // + // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NULL = 5; } // The unary operator to apply. @@ -216,18 +214,6 @@ message StructuredQuery { Direction direction = 2; } - // A sort direction. - enum Direction { - // Unspecified. - DIRECTION_UNSPECIFIED = 0; - - // Ascending. - ASCENDING = 1; - - // Descending. - DESCENDING = 2; - } - // A reference to a field, such as `max(messages.time) as max_time`. message FieldReference { string field_path = 2; @@ -242,6 +228,18 @@ message StructuredQuery { repeated FieldReference fields = 2; } + // A sort direction. + enum Direction { + // Unspecified. + DIRECTION_UNSPECIFIED = 0; + + // Ascending. + ASCENDING = 1; + + // Descending. + DESCENDING = 2; + } + // The projection to return. Projection select = 1; @@ -290,7 +288,6 @@ message StructuredQuery { google.protobuf.Int32Value limit = 5; } -// Firestore query for running an aggregation over a [StructuredQuery][google.firestore.v1.StructuredQuery]. message StructuredAggregationQuery { // Defines a aggregation that produces a single result. message Aggregation { @@ -313,7 +310,7 @@ message StructuredAggregationQuery { // Requires: // // * Must be greater than zero when present. - int32 up_to = 1 [(google.api.field_behavior) = OPTIONAL]; + int32 up_to = 1; } // The type of aggregation to perform, required. @@ -329,7 +326,7 @@ message StructuredAggregationQuery { // * Must be present. // * Must be unique across all aggregation aliases. // * Conform to existing [document field name][google.firestore.v1.Document.fields] limitations. - string alias = 7 [(google.api.field_behavior) = REQUIRED]; + string alias = 7; } // The base query to aggregate over. @@ -339,7 +336,7 @@ message StructuredAggregationQuery { } // Optional. Series of aggregations to apply on top of the `structured_query`. - repeated Aggregation aggregations = 3 [(google.api.field_behavior) = OPTIONAL]; + repeated Aggregation aggregations = 3; } // A position in a query result set. diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index 6d0141e41a0..59b2dafe4fd 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; +import "google/api/annotations.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/timestamp.proto"; @@ -27,7 +29,7 @@ option java_outer_classname = "WriteProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A write on a document. message Write { @@ -45,7 +47,7 @@ message Write { // This only requires read access to the document. string verify = 5; - // Applies a transformation to a document. + // Applies a tranformation to a document. DocumentTransform transform = 6; } @@ -84,8 +86,7 @@ message DocumentTransform { SERVER_VALUE_UNSPECIFIED = 0; // The time at which the server processed the request, with millisecond - // precision. If used on multiple fields (same or different documents) in - // a transaction, all the fields will get the same server timestamp. + // precision. REQUEST_TIME = 1; } From 931c8a60aa5288961e5767fbfc862a1b9fd4c173 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 23:02:51 -0400 Subject: [PATCH 14/48] Collapse CountQuery.java into AggregateQuery.java for simplicity --- .../firebase/firestore/AggregateQuery.java | 12 +++---- .../firebase/firestore/core/CountQuery.java | 33 ------------------- 2 files changed, 5 insertions(+), 40 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index fa54137993a..11188ce9f69 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -14,17 +14,13 @@ package com.google.firebase.firestore; -import android.app.Activity; import androidx.annotation.NonNull; -import com.google.android.gms.tasks.Continuation; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.firebase.firestore.core.CountQuery; +import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.util.Executors; -import java.util.concurrent.Executor; - public final class AggregateQuery { private final Query query; @@ -43,10 +39,12 @@ public Query getQuery() { @NonNull public Task get() { - CountQuery countQuery = new CountQuery(query.firestore.getClient().getDatastore(), query.query); + Datastore datastore = query.firestore.getClient().getDatastore(); TaskCompletionSource tcs = new TaskCompletionSource<>(); - countQuery.run().continueWith(Executors.DIRECT_EXECUTOR, task -> { + datastore + .runCountQuery(query.query.toTarget()) + .continueWith(Executors.DIRECT_EXECUTOR, task -> { if (task.isSuccessful()) { tcs.setResult(new AggregateQuerySnapshot(task.getResult())); } else { diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java deleted file mode 100644 index c50fd5fb5a7..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore.core; - -import com.google.android.gms.tasks.Task; -import com.google.firebase.firestore.remote.Datastore; - -public class CountQuery { - private final Datastore datastore; - private final Query query; - - public CountQuery(Datastore datastore, Query query) { - this.datastore = datastore; - this.query = query; - } - - public Task run() { - return datastore.runCountQuery(query.toTarget()); - } - -} From 57aa89b7c0c11f16df21ff3953578166fd4e59cd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 23:04:31 -0400 Subject: [PATCH 15/48] /gradlew :firebase-firestore:googleJavaFormat --- .../google/firebase/firestore/CountTest.java | 5 ---- .../firebase/firestore/AggregateField.java | 8 ++----- .../firebase/firestore/AggregateQuery.java | 22 ++++++++--------- .../firestore/AggregateQuerySnapshot.java | 1 - .../firebase/firestore/remote/Datastore.java | 24 ++++++++++++------- 5 files changed, 28 insertions(+), 32 deletions(-) diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index a49851d09f1..78cf1e94fe9 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -20,11 +20,7 @@ import static org.junit.Assert.assertEquals; import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.firebase.firestore.AggregateField; -import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.testutil.IntegrationTestUtil; - import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,5 +45,4 @@ public void count() { AggregateQuerySnapshot snapshot = waitFor(collection.count().get()); assertEquals(Long.valueOf(3), snapshot.get(AggregateField.count())); } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index 4137f1111f0..c13f785dc61 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -28,16 +28,12 @@ public static CountAggregateField count() { public static final class CountAggregateField extends AggregateField { - @Nullable - private Integer upTo; + @Nullable private Integer upTo; - CountAggregateField() { - } + CountAggregateField() {} CountAggregateField(@Nullable Integer upTo) { this.upTo = upTo; } - } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 11188ce9f69..1c6820705a9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -15,7 +15,6 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; - import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.remote.Datastore; @@ -27,7 +26,7 @@ public final class AggregateQuery { AggregateQuery(@NonNull Query query, @NonNull AggregateField aggregateField) { this.query = query; - if (! (aggregateField instanceof AggregateField.CountAggregateField)) { + if (!(aggregateField instanceof AggregateField.CountAggregateField)) { throw new IllegalArgumentException("unsupported aggregateField: " + aggregateField); } } @@ -44,16 +43,17 @@ public Task get() { datastore .runCountQuery(query.query.toTarget()) - .continueWith(Executors.DIRECT_EXECUTOR, task -> { - if (task.isSuccessful()) { - tcs.setResult(new AggregateQuerySnapshot(task.getResult())); - } else { - tcs.setException(task.getException()); - } - return null; - }); + .continueWith( + Executors.DIRECT_EXECUTOR, + task -> { + if (task.isSuccessful()) { + tcs.setResult(new AggregateQuerySnapshot(task.getResult())); + } else { + tcs.setException(task.getException()); + } + return null; + }); return tcs.getTask(); } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 6f9da9aea8f..dceff564bea 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -29,5 +29,4 @@ public class AggregateQuerySnapshot { public Long get(@NonNull AggregateField.CountAggregateField field) { return count; } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index 570add6f2d8..ffdce24be93 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -41,10 +41,8 @@ import com.google.firestore.v1.FirestoreGrpc; import com.google.firestore.v1.RunAggregationQueryRequest; import com.google.firestore.v1.RunAggregationQueryResponse; -import com.google.firestore.v1.RunQueryRequest; import com.google.firestore.v1.StructuredAggregationQuery; import com.google.firestore.v1.Value; - import io.grpc.Status; import java.util.ArrayList; import java.util.Arrays; @@ -225,12 +223,15 @@ public void onClose(Status status) { } public Task runCountQuery(Target queryTarget) { - com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = serializer.encodeQueryTarget(queryTarget); + com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = + serializer.encodeQueryTarget(queryTarget); - StructuredAggregationQuery.Builder structuredAggregationQuery = StructuredAggregationQuery.newBuilder(); + StructuredAggregationQuery.Builder structuredAggregationQuery = + StructuredAggregationQuery.newBuilder(); structuredAggregationQuery.setStructuredQuery(encodedQueryTarget.getStructuredQuery()); - StructuredAggregationQuery.Aggregation.Builder aggregation = StructuredAggregationQuery.Aggregation.newBuilder(); + StructuredAggregationQuery.Aggregation.Builder aggregation = + StructuredAggregationQuery.Aggregation.newBuilder(); aggregation.setCount(StructuredAggregationQuery.Aggregation.Count.getDefaultInstance()); aggregation.setAlias("zzyzx_agg_alias_count"); structuredAggregationQuery.addAggregations(aggregation); @@ -239,13 +240,14 @@ public Task runCountQuery(Target queryTarget) { request.setParent(encodedQueryTarget.getParent()); request.setStructuredAggregationQuery(structuredAggregationQuery); - return channel.runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) + return channel + .runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) .continueWith( workerQueue.getExecutor(), task -> { if (!task.isSuccessful()) { if (task.getException() instanceof FirebaseFirestoreException - && ((FirebaseFirestoreException) task.getException()).getCode() + && ((FirebaseFirestoreException) task.getException()).getCode() == FirebaseFirestoreException.Code.UNAUTHENTICATED) { channel.invalidateToken(); } @@ -255,10 +257,14 @@ public Task runCountQuery(Target queryTarget) { AggregationResult aggregationResult = response.getResult(); Map aggregateFieldsByAlias = aggregationResult.getAggregateFieldsMap(); - hardAssert(aggregateFieldsByAlias.size() == 1, "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); + hardAssert( + aggregateFieldsByAlias.size() == 1, + "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); Value countValue = aggregateFieldsByAlias.get("zzyzx_agg_alias_count"); hardAssert(countValue != null, "countValue == null"); - hardAssert(countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); + hardAssert( + countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, + "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); return countValue.getIntegerValue(); }); } From e95066c2ca94d3c7fd1848781ef8260a4d8d6fd4 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 23:06:21 -0400 Subject: [PATCH 16/48] AggregateField.java: remove CountAggregateField.upTo, since it's never used in this prototype --- .../com/google/firebase/firestore/AggregateField.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index c13f785dc61..7406d2fd5dc 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -15,7 +15,6 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; public abstract class AggregateField { @@ -27,13 +26,6 @@ public static CountAggregateField count() { } public static final class CountAggregateField extends AggregateField { - - @Nullable private Integer upTo; - CountAggregateField() {} - - CountAggregateField(@Nullable Integer upTo) { - this.upTo = upTo; - } } } From 20a7e10e5ae7ede9973bd8d27e7eec934bb1f201 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:16:09 -0400 Subject: [PATCH 17/48] Update protos to googleapis HEAD at https://github.com/googleapis/googleapis/commit/13f9b8908d84fa85c7573223aac3ac900f1cca27 --- .../proto/google/firestore/v1/common.proto | 8 +- .../proto/google/firestore/v1/document.proto | 6 +- .../proto/google/firestore/v1/firestore.proto | 313 +++++++++++++----- .../src/proto/google/firestore/v1/query.proto | 92 ++--- .../src/proto/google/firestore/v1/write.proto | 16 +- 5 files changed, 289 insertions(+), 146 deletions(-) diff --git a/firebase-firestore/src/proto/google/firestore/v1/common.proto b/firebase-firestore/src/proto/google/firestore/v1/common.proto index 670cb41739b..588a9b81b79 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/common.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/common.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,11 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; option csharp_namespace = "Google.Cloud.Firestore.V1"; @@ -27,7 +25,7 @@ option java_outer_classname = "CommonProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A set of field paths on a document. // Used to restrict a get or update operation on a document to a subset of its @@ -49,7 +47,7 @@ message Precondition { bool exists = 1; // When set, the target document must exist and have been last updated at - // that time. + // that time. Timestamp must be microsecond aligned. google.protobuf.Timestamp update_time = 2; } } diff --git a/firebase-firestore/src/proto/google/firestore/v1/document.proto b/firebase-firestore/src/proto/google/firestore/v1/document.proto index 268947856a8..41283588eb8 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/document.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/document.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,11 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/type/latlng.proto"; @@ -29,7 +27,7 @@ option java_outer_classname = "DocumentProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A Firestore document. // diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index d425edf9e0f..17c94ecb4e6 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,14 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; import "google/api/annotations.proto"; +import "google/api/client.proto"; +import "google/api/field_behavior.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/firestore/v1/query.proto"; @@ -31,27 +32,25 @@ option go_package = "google.golang.org/genproto/googleapis/firestore/v1;firestor option java_multiple_files = true; option java_outer_classname = "FirestoreProto"; option java_package = "com.google.firestore.v1"; -option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; + // Specification of the Firestore API. // The Cloud Firestore service. // -// This service exposes several types of comparable timestamps: -// -// * `create_time` - The time at which a document was created. Changes only -// when a document is deleted, then re-created. Increases in a strict -// monotonic fashion. -// * `update_time` - The time at which a document was last updated. Changes -// every time a document is modified. Does not change when a write results -// in no modifications. Increases in a strict monotonic fashion. -// * `read_time` - The time at which a particular state was observed. Used -// to denote a consistent snapshot of the database or the time at which a -// Document was observed to not exist. -// * `commit_time` - The time at which the writes in a transaction were -// committed. Any read with an equal or greater `read_time` is guaranteed -// to see the effects of the transaction. +// Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL +// document database that simplifies storing, syncing, and querying data for +// your mobile, web, and IoT apps at global scale. Its client libraries provide +// live synchronization and offline support, while its security features and +// integrations with Firebase and Google Cloud Platform (GCP) accelerate +// building truly serverless apps. service Firestore { + option (google.api.default_host) = "firestore.googleapis.com"; + option (google.api.oauth_scopes) = + "https://www.googleapis.com/auth/cloud-platform," + "https://www.googleapis.com/auth/datastore"; + // Gets a single document. rpc GetDocument(GetDocumentRequest) returns (Document) { option (google.api.http) = { @@ -63,14 +62,9 @@ service Firestore { rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}" - }; - } - - // Creates a new document. - rpc CreateDocument(CreateDocumentRequest) returns (Document) { - option (google.api.http) = { - post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" - body: "document" + additional_bindings { + get: "/v1/{parent=projects/*/databases/*/documents}/{collection_id}" + } }; } @@ -80,6 +74,7 @@ service Firestore { patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}" body: "document" }; + option (google.api.method_signature) = "document,update_mask"; } // Deletes a document. @@ -87,6 +82,7 @@ service Firestore { option (google.api.http) = { delete: "/v1/{name=projects/*/databases/*/documents/*/**}" }; + option (google.api.method_signature) = "name"; } // Gets multiple documents. @@ -106,6 +102,7 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction" body: "*" }; + option (google.api.method_signature) = "database"; } // Commits a transaction, while optionally updating documents. @@ -114,6 +111,7 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:commit" body: "*" }; + option (google.api.method_signature) = "database,writes"; } // Rolls back a transaction. @@ -122,6 +120,7 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:rollback" body: "*" }; + option (google.api.method_signature) = "database,transaction"; } // Runs a query. @@ -136,6 +135,20 @@ service Firestore { }; } + // Partitions a query by returning partition cursors that can be used to run + // the query in parallel. The returned partition cursors are split points that + // can be used by RunQuery as starting/end points for the query results. + rpc PartitionQuery(PartitionQueryRequest) returns (PartitionQueryResponse) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery" + body: "*" + additional_bindings { + post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery" + body: "*" + } + }; + } + // Streams batches of document updates and deletes, in order. rpc Write(stream WriteRequest) returns (stream WriteResponse) { option (google.api.http) = { @@ -162,14 +175,39 @@ service Firestore { body: "*" } }; + option (google.api.method_signature) = "parent"; + } + + // Applies a batch of write operations. + // + // The BatchWrite method does not apply the write operations atomically + // and can apply them out of order. Method does not allow more than one write + // per document. Each write succeeds or fails independently. See the + // [BatchWriteResponse][google.firestore.v1.BatchWriteResponse] for the success status of each write. + // + // If you require an atomically applied set of writes, use + // [Commit][google.firestore.v1.Firestore.Commit] instead. + rpc BatchWrite(BatchWriteRequest) returns (BatchWriteResponse) { + option (google.api.http) = { + post: "/v1/{database=projects/*/databases/*}/documents:batchWrite" + body: "*" + }; + } + + // Creates a new document. + rpc CreateDocument(CreateDocumentRequest) returns (Document) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" + body: "document" + }; } } // The request for [Firestore.GetDocument][google.firestore.v1.Firestore.GetDocument]. message GetDocumentRequest { - // The resource name of the Document to get. In the format: + // Required. The resource name of the Document to get. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1; + string name = 1 [(google.api.field_behavior) = REQUIRED]; // The fields to return. If not set, returns all fields. // @@ -184,24 +222,24 @@ message GetDocumentRequest { bytes transaction = 3; // Reads the version of the document at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 5; } } // The request for [Firestore.ListDocuments][google.firestore.v1.Firestore.ListDocuments]. message ListDocumentsRequest { - // The parent resource name. In the format: + // Required. The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; - // The collection ID, relative to `parent`, to list. For example: `chatrooms` + // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms` // or `messages`. - string collection_id = 2; + string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; // The maximum number of documents to return. int32 page_size = 3; @@ -225,7 +263,7 @@ message ListDocumentsRequest { bytes transaction = 8; // Reads documents as they were at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 10; } @@ -250,21 +288,21 @@ message ListDocumentsResponse { // The request for [Firestore.CreateDocument][google.firestore.v1.Firestore.CreateDocument]. message CreateDocumentRequest { - // The parent resource. For example: + // Required. The parent resource. For example: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/chatrooms/{chatroom_id}` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; - // The collection ID, relative to `parent`, to list. For example: `chatrooms`. - string collection_id = 2; + // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms`. + string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; // The client-assigned document ID to use for this document. // // Optional. If not specified, an ID will be assigned by the service. string document_id = 3; - // The document to create. `name` must not be set. - Document document = 4; + // Required. The document to create. `name` must not be set. + Document document = 4 [(google.api.field_behavior) = REQUIRED]; // The fields to return. If not set, returns all fields. // @@ -275,9 +313,9 @@ message CreateDocumentRequest { // The request for [Firestore.UpdateDocument][google.firestore.v1.Firestore.UpdateDocument]. message UpdateDocumentRequest { - // The updated document. + // Required. The updated document. // Creates the document if it does not already exist. - Document document = 1; + Document document = 1 [(google.api.field_behavior) = REQUIRED]; // The fields to update. // None of the field paths in the mask may contain a reserved name. @@ -301,9 +339,9 @@ message UpdateDocumentRequest { // The request for [Firestore.DeleteDocument][google.firestore.v1.Firestore.DeleteDocument]. message DeleteDocumentRequest { - // The resource name of the Document to delete. In the format: + // Required. The resource name of the Document to delete. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1; + string name = 1 [(google.api.field_behavior) = REQUIRED]; // An optional precondition on the document. // The request will fail if this is set and not met by the target document. @@ -312,9 +350,9 @@ message DeleteDocumentRequest { // The request for [Firestore.BatchGetDocuments][google.firestore.v1.Firestore.BatchGetDocuments]. message BatchGetDocumentsRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The names of the documents to retrieve. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. @@ -341,7 +379,7 @@ message BatchGetDocumentsRequest { TransactionOptions new_transaction = 5; // Reads documents as they were at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -373,9 +411,9 @@ message BatchGetDocumentsResponse { // The request for [Firestore.BeginTransaction][google.firestore.v1.Firestore.BeginTransaction]. message BeginTransactionRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The options for the transaction. // Defaults to a read-write transaction. @@ -390,9 +428,9 @@ message BeginTransactionResponse { // The request for [Firestore.Commit][google.firestore.v1.Firestore.Commit]. message CommitRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The writes to apply. // @@ -411,29 +449,30 @@ message CommitResponse { // request. repeated WriteResult write_results = 1; - // The time at which the commit occurred. + // The time at which the commit occurred. Any read with an equal or greater + // `read_time` is guaranteed to see the effects of the commit. google.protobuf.Timestamp commit_time = 2; } // The request for [Firestore.Rollback][google.firestore.v1.Firestore.Rollback]. message RollbackRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; - // The transaction to roll back. - bytes transaction = 2; + // Required. The transaction to roll back. + bytes transaction = 2 [(google.api.field_behavior) = REQUIRED]; } // The request for [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery]. message RunQueryRequest { - // The parent resource name. In the format: + // Required. The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; // The query to run. oneof query_type { @@ -444,7 +483,9 @@ message RunQueryRequest { // The consistency mode for this transaction. // If not set, defaults to strong consistency. oneof consistency_selector { - // Reads documents in a transaction. + // Run the query within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. bytes transaction = 5; // Starts a new transaction and reads the documents. @@ -454,7 +495,7 @@ message RunQueryRequest { TransactionOptions new_transaction = 6; // Reads documents as they were at the given time. - // This may not be older than 60 seconds. + // This may not be older than 270 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -467,8 +508,7 @@ message RunQueryResponse { // If set, no other fields will be set in this response. bytes transaction = 2; - // A query result. - // Not set when reporting partial progress. + // A query result, not set when reporting partial progress. Document document = 1; // The time at which the document was read. This may be monotonically @@ -483,6 +523,94 @@ message RunQueryResponse { // The number of results that have been skipped due to an offset between // the last response and the current response. int32 skipped_results = 4; + + // The continuation mode for the query. If present, it indicates the current + // query response stream has finished. This can be set with or without a + // `document` present, but when set, no more results are returned. + oneof continuation_selector { + // If present, Firestore has completely finished the request and no more + // documents will be returned. + bool done = 6; + } +} + +// The request for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. +message PartitionQueryRequest { + // Required. The parent resource name. In the format: + // `projects/{project_id}/databases/{database_id}/documents`. + // Document resource names are not supported; only database resource names + // can be specified. + string parent = 1 [(google.api.field_behavior) = REQUIRED]; + + // The query to partition. + oneof query_type { + // A structured query. + // Query must specify collection with all descendants and be ordered by name + // ascending. Other filters, order bys, limits, offsets, and start/end + // cursors are not supported. + StructuredQuery structured_query = 2; + } + + // The desired maximum number of partition points. + // The partitions may be returned across multiple pages of results. + // The number must be positive. The actual number of partitions + // returned may be fewer. + // + // For example, this may be set to one fewer than the number of parallel + // queries to be run, or in running a data pipeline job, one fewer than the + // number of workers or compute instances available. + int64 partition_count = 3; + + // The `next_page_token` value returned from a previous call to + // PartitionQuery that may be used to get an additional set of results. + // There are no ordering guarantees between sets of results. Thus, using + // multiple sets of results will require merging the different result sets. + // + // For example, two subsequent calls using a page_token may return: + // + // * cursor B, cursor M, cursor Q + // * cursor A, cursor U, cursor W + // + // To obtain a complete result set ordered with respect to the results of the + // query supplied to PartitionQuery, the results sets should be merged: + // cursor A, cursor B, cursor M, cursor Q, cursor U, cursor W + string page_token = 4; + + // The maximum number of partitions to return in this call, subject to + // `partition_count`. + // + // For example, if `partition_count` = 10 and `page_size` = 8, the first call + // to PartitionQuery will return up to 8 partitions and a `next_page_token` + // if more results exist. A second call to PartitionQuery will return up to + // 2 partitions, to complete the total of 10 specified in `partition_count`. + int32 page_size = 5; +} + +// The response for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. +message PartitionQueryResponse { + // Partition results. + // Each partition is a split point that can be used by RunQuery as a starting + // or end point for the query results. The RunQuery requests must be made with + // the same query supplied to this PartitionQuery request. The partition + // cursors will be ordered according to same ordering as the results of the + // query supplied to PartitionQuery. + // + // For example, if a PartitionQuery request returns partition cursors A and B, + // running the following three queries will return the entire result set of + // the original query: + // + // * query, end_at A + // * query, start_at A, end_at B + // * query, start_at B + // + // An empty result may indicate that the query has too few results to be + // partitioned. + repeated Cursor partitions = 1; + + // A page token that may be used to request an additional set of results, up + // to the number specified by `partition_count` in the PartitionQuery request. + // If blank, there are no more results. + string next_page_token = 2; } // The request for [Firestore.Write][google.firestore.v1.Firestore.Write]. @@ -496,10 +624,10 @@ message RunQueryResponse { // given token, then a response containing only an up-to-date token, to use in // the next request. message WriteRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. // This is only required in the first message. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The ID of the write stream to resume. // This may only be set in the first message. When left empty, a new write @@ -552,15 +680,16 @@ message WriteResponse { // request. repeated WriteResult write_results = 3; - // The time at which the commit occurred. + // The time at which the commit occurred. Any read with an equal or greater + // `read_time` is guaranteed to see the effects of the write. google.protobuf.Timestamp commit_time = 4; } // A request for [Firestore.Listen][google.firestore.v1.Firestore.Listen] message ListenRequest { - // The database name. In the format: + // Required. The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1; + string database = 1 [(google.api.field_behavior) = REQUIRED]; // The supported target changes. oneof target_change { @@ -654,14 +783,8 @@ message Target { google.protobuf.Timestamp read_time = 11; } - // A client provided target ID. - // - // If not set, the server will assign an ID for the target. - // - // Used for resuming a target without changing IDs. The IDs can either be - // client-assigned or be server-assigned in a previous stream. All targets - // with client provided IDs must be added before adding a target that needs - // a server-assigned id. + // The target ID that identifies the target on the stream. Must be a positive + // number and non-zero. int32 target_id = 5; // If the target should be removed once it is current and consistent. @@ -706,11 +829,7 @@ message TargetChange { // // If empty, the change applies to all targets. // - // For `target_change_type=ADD`, the order of the target IDs matches the order - // of the requests to add the targets. This allows clients to unambiguously - // associate server-assigned target IDs with added targets. - // - // For other states, the order of the target IDs is not defined. + // The order of the target IDs is not defined. repeated int32 target_ids = 2; // The error that resulted in this change, if applicable. @@ -737,11 +856,11 @@ message TargetChange { // The request for [Firestore.ListCollectionIds][google.firestore.v1.Firestore.ListCollectionIds]. message ListCollectionIdsRequest { - // The parent document. In the format: + // Required. The parent document. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1; + string parent = 1 [(google.api.field_behavior) = REQUIRED]; // The maximum number of results to return. int32 page_size = 2; @@ -759,3 +878,35 @@ message ListCollectionIdsResponse { // A page token that may be used to continue the list. string next_page_token = 2; } + +// The request for [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. +message BatchWriteRequest { + // Required. The database name. In the format: + // `projects/{project_id}/databases/{database_id}`. + string database = 1 [(google.api.field_behavior) = REQUIRED]; + + // The writes to apply. + // + // Method does not apply writes atomically and does not guarantee ordering. + // Each write succeeds or fails independently. You cannot write to the same + // document more than once per request. + repeated Write writes = 2; + + // Labels associated with this batch write. + map labels = 3; +} + +// The response from [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. +message BatchWriteResponse { + // The result of applying the writes. + // + // This i-th write result corresponds to the i-th write in the + // request. + repeated WriteResult write_results = 1; + + // The status of applying the writes. + // + // This i-th write status corresponds to the i-th write in the + // request. + repeated google.rpc.Status status = 2; +} diff --git a/firebase-firestore/src/proto/google/firestore/v1/query.proto b/firebase-firestore/src/proto/google/firestore/v1/query.proto index 25d53238130..58163eb62cf 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/query.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/query.proto @@ -1,4 +1,4 @@ -// Copyright 2019 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,12 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; +import "google/api/field_behavior.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/wrappers.proto"; @@ -28,7 +27,7 @@ option java_outer_classname = "QueryProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A Firestore query. message StructuredQuery { @@ -66,7 +65,7 @@ message StructuredQuery { // Unspecified. This value must not be used. OPERATOR_UNSPECIFIED = 0; - // The results are required to satisfy each of the combined filters. + // Documents are required to satisfy all of the combined filters. AND = 1; } @@ -74,7 +73,10 @@ message StructuredQuery { Operator op = 1; // The list of filters to combine. - // Must contain at least one filter. + // + // Requires: + // + // * At least one filter is present. repeated Filter filters = 2; } @@ -113,7 +115,7 @@ message StructuredQuery { // * That `field` come first in `order_by`. GREATER_THAN_OR_EQUAL = 4; - // The given `field` is equal to the given `value`.. + // The given `field` is equal to the given `value`. EQUAL = 5; // The given `field` is not equal to the given `value`. @@ -132,7 +134,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) + // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. IN = 8; // The given `field` is an array that contains any of the values in the @@ -141,7 +143,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) + // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. ARRAY_CONTAINS_ANY = 9; // The value of the `field` is not in the given array. @@ -169,30 +171,30 @@ message StructuredQuery { message UnaryFilter { // A unary operator. enum Operator { - // Unspecified. This value must not be used. - OPERATOR_UNSPECIFIED = 0; - - // The given `field` is equal to `NaN`. - IS_NAN = 2; - - // The given `field` is equal to `NULL`. - IS_NULL = 3; - - // The given `field` is not equal to `NaN`. - // - // Requires: - // - // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NAN = 4; - - // The given `field` is not equal to `NULL`. - // - // Requires: - // - // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NULL = 5; + // Unspecified. This value must not be used. + OPERATOR_UNSPECIFIED = 0; + + // The given `field` is equal to `NaN`. + IS_NAN = 2; + + // The given `field` is equal to `NULL`. + IS_NULL = 3; + + // The given `field` is not equal to `NaN`. + // + // Requires: + // + // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NAN = 4; + + // The given `field` is not equal to `NULL`. + // + // Requires: + // + // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NULL = 5; } // The unary operator to apply. @@ -214,6 +216,18 @@ message StructuredQuery { Direction direction = 2; } + // A sort direction. + enum Direction { + // Unspecified. + DIRECTION_UNSPECIFIED = 0; + + // Ascending. + ASCENDING = 1; + + // Descending. + DESCENDING = 2; + } + // A reference to a field, such as `max(messages.time) as max_time`. message FieldReference { string field_path = 2; @@ -228,18 +242,6 @@ message StructuredQuery { repeated FieldReference fields = 2; } - // A sort direction. - enum Direction { - // Unspecified. - DIRECTION_UNSPECIFIED = 0; - - // Ascending. - ASCENDING = 1; - - // Descending. - DESCENDING = 2; - } - // The projection to return. Projection select = 1; diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index 59b2dafe4fd..c17e96ad9c1 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -1,4 +1,4 @@ -// Copyright 2018 Google LLC. +// Copyright 2022 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,13 +11,11 @@ // 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. -// syntax = "proto3"; package google.firestore.v1; -import "google/api/annotations.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/timestamp.proto"; @@ -29,7 +27,7 @@ option java_outer_classname = "WriteProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; - +option ruby_package = "Google::Cloud::Firestore::V1"; // A write on a document. message Write { @@ -42,12 +40,7 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; - // The name of a document on which to verify the `current_document` - // precondition. - // This only requires read access to the document. - string verify = 5; - - // Applies a tranformation to a document. + // Applies a transformation to a document. DocumentTransform transform = 6; } @@ -86,7 +79,8 @@ message DocumentTransform { SERVER_VALUE_UNSPECIFIED = 0; // The time at which the server processed the request, with millisecond - // precision. + // precision. If used on multiple fields (same or different documents) in + // a transaction, all the fields will get the same server timestamp. REQUEST_TIME = 1; } From 92c5218d59ca86ffad8a4a250253ccc4b4818101 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:23:17 -0400 Subject: [PATCH 18/48] write.proto: add back string verify = 5 --- firebase-firestore/src/proto/google/firestore/v1/write.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index c17e96ad9c1..6d0141e41a0 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -40,6 +40,11 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; + // The name of a document on which to verify the `current_document` + // precondition. + // This only requires read access to the document. + string verify = 5; + // Applies a transformation to a document. DocumentTransform transform = 6; } From 5130d1682f0145a9cbe2c36b2f93e41f40a5cb8f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:25:23 -0400 Subject: [PATCH 19/48] Update protos to googleapis HEAD of the preview branch at https://github.com/googleapis/googleapis/commit/77a0a97816f53e83753fe5a1d93722b762436114 --- .../firestore/v1/aggregation_result.proto | 42 ++++++++++ .../proto/google/firestore/v1/firestore.proto | 80 +++++++++++++++++++ .../src/proto/google/firestore/v1/query.proto | 52 ++++++++++++ .../src/proto/google/firestore/v1/write.proto | 5 -- 4 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto diff --git a/firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto b/firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto new file mode 100644 index 00000000000..538e3fef5e4 --- /dev/null +++ b/firebase-firestore/src/proto/google/firestore/v1/aggregation_result.proto @@ -0,0 +1,42 @@ +// Copyright 2022 Google LLC +// +// 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. + +syntax = "proto3"; + +package google.firestore.v1; + +import "google/firestore/v1/document.proto"; + +option csharp_namespace = "Google.Cloud.Firestore.V1"; +option go_package = "google.golang.org/genproto/googleapis/firestore/v1;firestore"; +option java_multiple_files = true; +option java_outer_classname = "AggregationResultProto"; +option java_package = "com.google.firestore.v1"; +option objc_class_prefix = "GCFS"; +option php_namespace = "Google\\Cloud\\Firestore\\V1"; +option ruby_package = "Google::Cloud::Firestore::V1"; + +// The result of a single bucket from a Firestore aggregation query. +// +// The keys of `aggregate_fields` are the same for all results in an aggregation +// query, unlike document queries which can have different fields present for +// each result. +message AggregationResult { + // The result of the aggregation functions, ex: `COUNT(*) AS total_docs`. + // + // The key is the [alias][google.firestore.v1.StructuredAggregationQuery.Aggregation.alias] + // assigned to the aggregation function on input and the size of this map + // equals the number of aggregation functions in the query. + map aggregate_fields = 2; +} diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index 17c94ecb4e6..69787e8289a 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -19,6 +19,7 @@ package google.firestore.v1; import "google/api/annotations.proto"; import "google/api/client.proto"; import "google/api/field_behavior.proto"; +import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/firestore/v1/query.proto"; @@ -135,6 +136,29 @@ service Firestore { }; } + // Runs an aggregation query. + // + // Rather than producing [Document][google.firestore.v1.Document] results like [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery], + // this API allows running an aggregation to produce a series of + // [AggregationResult][google.firestore.v1.AggregationResult] server-side. + // + // High-Level Example: + // + // ``` + // -- Return the number of documents in table given a filter. + // SELECT COUNT(*) FROM ( SELECT * FROM k where a = true ); + // ``` + rpc RunAggregationQuery(RunAggregationQueryRequest) returns (stream RunAggregationQueryResponse) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents}:runAggregationQuery" + body: "*" + additional_bindings { + post: "/v1/{parent=projects/*/databases/*/documents/*/**}:runAggregationQuery" + body: "*" + } + }; + } + // Partitions a query by returning partition cursors that can be used to run // the query in parallel. The returned partition cursors are split points that // can be used by RunQuery as starting/end points for the query results. @@ -534,6 +558,62 @@ message RunQueryResponse { } } +// The request for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. +message RunAggregationQueryRequest { + // Required. The parent resource name. In the format: + // `projects/{project_id}/databases/{database_id}/documents` or + // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. + // For example: + // `projects/my-project/databases/my-database/documents` or + // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` + string parent = 1 [(google.api.field_behavior) = REQUIRED]; + + // The query to run. + oneof query_type { + // An aggregation query. + StructuredAggregationQuery structured_aggregation_query = 2; + } + + // The consistency mode for the query, defaults to strong consistency. + oneof consistency_selector { + // Run the aggregation within an already active transaction. + // + // The value here is the opaque transaction ID to execute the query in. + bytes transaction = 4; + + // Starts a new transaction as part of the query, defaulting to read-only. + // + // The new transaction ID will be returned as the first response in the + // stream. + TransactionOptions new_transaction = 5; + + // Executes the query at the given timestamp. + // + // Requires: + // + // * Cannot be more than 270 seconds in the past. + google.protobuf.Timestamp read_time = 6; + } +} + +// The response for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. +message RunAggregationQueryResponse { + // A single aggregation result. + // + // Not present when reporting partial progress or when the query produced + // zero results. + AggregationResult result = 1; + + // The transaction that was started as part of this request. + // + // Only present on the first response when the request requested to start + // a new transaction. + bytes transaction = 2; + + // The time at which the aggregate value is valid for. + google.protobuf.Timestamp read_time = 3; +} + // The request for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. message PartitionQueryRequest { // Required. The parent resource name. In the format: diff --git a/firebase-firestore/src/proto/google/firestore/v1/query.proto b/firebase-firestore/src/proto/google/firestore/v1/query.proto index 58163eb62cf..4c6250fa780 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/query.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/query.proto @@ -290,6 +290,58 @@ message StructuredQuery { google.protobuf.Int32Value limit = 5; } +// Firestore query for running an aggregation over a [StructuredQuery][google.firestore.v1.StructuredQuery]. +message StructuredAggregationQuery { + // Defines a aggregation that produces a single result. + message Aggregation { + // Count of documents that match the query. + // + // The `COUNT(*)` aggregation function operates on the entire document + // so it does not require a field reference. + message Count { + // Optional. Optional constraint on the maximum number of documents to count. + // + // This provides a way to set an upper bound on the number of documents + // to scan, limiting latency and cost. + // + // High-Level Example: + // + // ``` + // SELECT COUNT_UP_TO(1000) FROM ( SELECT * FROM k ); + // ``` + // + // Requires: + // + // * Must be greater than zero when present. + int32 up_to = 1 [(google.api.field_behavior) = OPTIONAL]; + } + + // The type of aggregation to perform, required. + oneof operator { + // Count aggregator. + Count count = 1; + } + + // Required. The name of the field to store the result of the aggregation into. + // + // Requires: + // + // * Must be present. + // * Must be unique across all aggregation aliases. + // * Conform to existing [document field name][google.firestore.v1.Document.fields] limitations. + string alias = 7 [(google.api.field_behavior) = REQUIRED]; + } + + // The base query to aggregate over. + oneof query_type { + // Nested structured query. + StructuredQuery structured_query = 1; + } + + // Optional. Series of aggregations to apply on top of the `structured_query`. + repeated Aggregation aggregations = 3 [(google.api.field_behavior) = OPTIONAL]; +} + // A position in a query result set. message Cursor { // The values that represent a position, in the order they appear in diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index 6d0141e41a0..c17e96ad9c1 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -40,11 +40,6 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; - // The name of a document on which to verify the `current_document` - // precondition. - // This only requires read access to the document. - string verify = 5; - // Applies a transformation to a document. DocumentTransform transform = 6; } From 9c3a3364e4c756186bd238f8f6a46b8bd2313b10 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Thu, 2 Jun 2022 16:23:17 -0400 Subject: [PATCH 20/48] write.proto: add back string verify = 5 --- firebase-firestore/src/proto/google/firestore/v1/write.proto | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index c17e96ad9c1..6d0141e41a0 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -40,6 +40,11 @@ message Write { // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. string delete = 2; + // The name of a document on which to verify the `current_document` + // precondition. + // This only requires read access to the document. + string verify = 5; + // Applies a transformation to a document. DocumentTransform transform = 6; } From 74e6d14bf4b6754f4989d72ee0b3395bd5df6d92 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 15:06:11 -0400 Subject: [PATCH 21/48] Add API skeleton to aggregate queries and COUNT --- .../firebase/firestore/ktx/Firestore.kt | 31 +- .../firebase/firestore/AggregateDemo.java | 97 ++++++ .../firebase/firestore/AggregateField.java | 301 ++++++++++++++++++ .../firebase/firestore/AggregateQuery.java | 81 +++++ .../firestore/AggregateQuerySnapshot.java | 33 ++ .../firebase/firestore/AggregateSnapshot.java | 157 +++++++++ .../firebase/firestore/AggregateSource.java | 19 ++ .../firebase/firestore/GroupByQuery.java | 175 ++++++++++ .../firestore/GroupByQuerySnapshot.java | 74 +++++ .../firebase/firestore/GroupBySource.java | 19 ++ .../firebase/firestore/GroupChange.java | 57 ++++ .../firebase/firestore/GroupSnapshot.java | 221 +++++++++++++ .../com/google/firebase/firestore/Query.java | 24 ++ 13 files changed, 1283 insertions(+), 6 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java diff --git a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt index ec2ee225d41..fa72f33ca2f 100644 --- a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt +++ b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt @@ -18,12 +18,7 @@ import androidx.annotation.Keep import com.google.firebase.FirebaseApp import com.google.firebase.components.Component import com.google.firebase.components.ComponentRegistrar -import com.google.firebase.firestore.DocumentSnapshot -import com.google.firebase.firestore.FieldPath -import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.FirebaseFirestoreSettings -import com.google.firebase.firestore.QueryDocumentSnapshot -import com.google.firebase.firestore.QuerySnapshot +import com.google.firebase.firestore.* import com.google.firebase.ktx.Firebase import com.google.firebase.platforminfo.LibraryVersionComponent @@ -154,6 +149,30 @@ fun firestoreSettings(init: FirebaseFirestoreSettings.Builder.() -> Unit): Fireb return builder.build() } +inline fun GroupSnapshot.getField(field: String): T? = get(field, T::class.java) + +inline fun GroupSnapshot.getField( + field: String, + serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior +): T? = + get(field, T::class.java, serverTimestampBehavior) + +inline fun GroupSnapshot.getField(fieldPath: FieldPath): T? = get(fieldPath, T::class.java) + +inline fun GroupSnapshot.getField( + fieldPath: FieldPath, + serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior +): T? = + get(fieldPath, T::class.java, serverTimestampBehavior) + +inline fun AggregateSnapshot.getField(field: AggregateField): T? = get(field, T::class.java) + +inline fun AggregateSnapshot.getField( + field: AggregateField, + serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior +): T? = + get(field, T::class.java, serverTimestampBehavior) + internal const val LIBRARY_NAME: String = "fire-fst-ktx" /** @suppress */ diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java new file mode 100644 index 00000000000..fae409b128d --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java @@ -0,0 +1,97 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import static com.google.firebase.firestore.AggregateField.count; + +import android.app.Activity; +import androidx.annotation.NonNull; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +final class AggregateDemo { + + private final FirebaseFirestore db; + + AggregateDemo(@NonNull FirebaseFirestore db) { + this.db = db; + } + + void countBasicQuery() { + AggregateQuery query = db.collection("users").count(); + AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); + assertEqual(snapshot.get(count()), 50); + } + + void countLimitNumRowsScanned() { + AggregateQuery query = db.collection("users").limit(11).count(); + AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); + assertEqual(snapshot.get(count()), 11); + } + + void countUpTo() { + AggregateQuery query = db.collection("users").aggregate(count().upTo(11)); + AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); + assertEqual(snapshot.get(count()), 11); + } + + void countRealTimeUpdates() throws InterruptedException { + AggregateQuery query = db.collection("users").aggregate(count()); + + Lock lock = new ReentrantLock(); + Condition condition = lock.newCondition(); + + ListenerRegistration registration = + query + .listen() + .startDirectFromServer( + (snapshot, error) -> { + assertNull(error); + assertNotNull(snapshot); + assertEqual(snapshot.get(count()), 50); + assertFalse(snapshot.getMetadata().isFromCache()); + assertFalse(snapshot.getMetadata().hasPendingWrites()); + lock.lock(); + try { + condition.signalAll(); + } finally { + lock.unlock(); + } + }); + + lock.lock(); + try { + condition.await(); + } finally { + lock.unlock(); + } + + registration.remove(); + } + + private static void assertEqual(Object o1, Object o2) {} + + private static void assertNull(Object o) {} + + private static void assertFalse(Object o) {} + + private static void assertNotNull(Object o) { + if (o == null) { + throw new NullPointerException(); + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java new file mode 100644 index 00000000000..d599059be16 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -0,0 +1,301 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Objects; + +public abstract class AggregateField { + + private AggregateField() {} + + @NonNull + public static CountAggregateField count() { + return new CountAggregateField(); + } + + @NonNull + public static MinAggregateField min(@NonNull String field) { + return min(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static MinAggregateField min(@NonNull FieldPath field) { + return new MinAggregateField(field); + } + + @NonNull + public static MaxAggregateField max(@NonNull String field) { + return max(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static MaxAggregateField max(@NonNull FieldPath field) { + return new MaxAggregateField(field); + } + + @NonNull + public static AverageAggregateField average(@NonNull String field) { + return average(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static AverageAggregateField average(@NonNull FieldPath field) { + return new AverageAggregateField(field); + } + + @NonNull + public static SumAggregateField sum(@NonNull String field) { + return sum(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static SumAggregateField sum(@NonNull FieldPath field) { + return new SumAggregateField(field); + } + + @NonNull + public static FirstAggregateField first(@NonNull String field) { + return first(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static FirstAggregateField first(@NonNull FieldPath field) { + return new FirstAggregateField(field); + } + + @NonNull + public static LastAggregateField last(@NonNull String field) { + return last(FieldPath.fromDotSeparatedPath(field)); + } + + @NonNull + public static LastAggregateField last(@NonNull FieldPath field) { + return new LastAggregateField(field); + } + + @Override + public abstract boolean equals(Object obj); + + @Override + public abstract int hashCode(); + + @Override + public abstract String toString(); + + public static final class CountAggregateField extends AggregateField { + + @Nullable private Integer upTo; + + CountAggregateField() {} + + CountAggregateField(@Nullable Integer upTo) { + this.upTo = upTo; + } + + public CountAggregateField upTo(int upTo) { + if (upTo < 0) { + throw new IllegalArgumentException("upTo==" + upTo); + } + return new CountAggregateField(upTo); + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + CountAggregateField other = (CountAggregateField) obj; + return Objects.equals(upTo, other.upTo); + } + + @Override + public int hashCode() { + return Objects.hash("COUNT", upTo); + } + + @Override + public String toString() { + if (upTo == null) { + return "COUNT"; + } else { + return "COUNT(upTo=" + upTo + ")"; + } + } + } + + public static final class MinAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + MinAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((MinAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "MIN(" + field.toString() + ")"; + } + } + + public static final class MaxAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + MaxAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((MaxAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "MAX(" + field.toString() + ")"; + } + } + + public static final class AverageAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + AverageAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((AverageAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "AVERAGE(" + field.toString() + ")"; + } + } + + public static final class SumAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + SumAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((SumAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "SUM(" + field.toString() + ")"; + } + } + + public static final class FirstAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + FirstAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((FirstAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "FIRST(" + field.toString() + ")"; + } + } + + public static final class LastAggregateField extends AggregateField { + + @NonNull private FieldPath field; + + LastAggregateField(@NonNull FieldPath field) { + this.field = field; + } + + @Override + public boolean equals(Object obj) { + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + return field.equals(((LastAggregateField) obj).field); + } + + @Override + public int hashCode() { + return toString().hashCode(); + } + + @Override + public String toString() { + return "LAST(" + field.toString() + ")"; + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java new file mode 100644 index 00000000000..e8ec4b91fbc --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -0,0 +1,81 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import android.app.Activity; +import androidx.annotation.NonNull; +import com.google.android.gms.tasks.Task; +import java.util.concurrent.Executor; + +public class AggregateQuery { + + AggregateQuery() {} + + @NonNull + public Query getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get(@NonNull AggregateSource source) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenConfig listen() { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(Object obj) { + throw new RuntimeException("not implemented"); + } + + public static final class ListenConfig { + + private ListenConfig() {} + + @NonNull + public ListenConfig executeCallbacksOn(@NonNull Executor executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenConfig scopeTo(@NonNull Activity executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenConfig includeMetadataOnlyChanges(boolean includeMetadataOnlyChanges) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenerRegistration startDirectFromServer( + @NonNull EventListener listener) { + throw new RuntimeException("not implemented"); + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java new file mode 100644 index 00000000000..2a786c0b478 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -0,0 +1,33 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import javax.annotation.Nonnull; + +public class AggregateQuerySnapshot extends AggregateSnapshot { + + AggregateQuerySnapshot() {} + + @Nonnull + public AggregateQuery getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public SnapshotMetadata getMetadata() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java new file mode 100644 index 00000000000..cd9bad6a978 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java @@ -0,0 +1,157 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; +import java.util.Date; +import java.util.Map; +import javax.annotation.Nonnull; + +public class AggregateSnapshot { + + AggregateSnapshot() {} + + @NonNull + public Map getAggregations() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Map getAggregations( + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + public boolean contains(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get( + @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get(@NonNull AggregateField field, @NonNull Class valueType) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get( + @NonNull AggregateField field, + @NonNull Class valueType, + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + // Overload get() specifically for COUNT since it has a well-defined type (i.e. long). + @Nullable + public Long get(@Nonnull AggregateField.CountAggregateField field) { + throw new RuntimeException("not implemented"); + } + + // Overload get() specifically for SUM since it has a well-defined type (i.e. double). + @Nullable + public Double get(@Nonnull AggregateField.SumAggregateField field) { + throw new RuntimeException("not implemented"); + } + + // Overload get() specifically for AVERAGE since it has a well-defined type (i.e. double). + @Nullable + public Double get(@Nonnull AggregateField.AverageAggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Boolean getBoolean(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Double getDouble(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public String getString(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Long getLong(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate( + @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp( + @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Blob getBlob(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public GeoPoint getGeoPoint(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public DocumentReference getDocumentReference(@NonNull AggregateField field) { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object obj) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public String toString() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java new file mode 100644 index 00000000000..dd119c59e59 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -0,0 +1,19 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +public enum AggregateSource { + SERVER_DIRECT, +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java new file mode 100644 index 00000000000..073f134fe6c --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java @@ -0,0 +1,175 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import android.app.Activity; +import androidx.annotation.NonNull; +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.Query.Direction; +import java.util.concurrent.Executor; + +public class GroupByQuery { + + GroupByQuery() {} + + @NonNull + public Query getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Task get(@NonNull GroupBySource source) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public AggregateQuery.ListenConfig listen() { + throw new RuntimeException("not implemented"); + } + + // Note: Specifying an empty list of aggregates, or not invoking this method at all, is equivalent + // to an SQL "DISTINCT" operator. + @NonNull + public GroupByQuery aggregate(@NonNull AggregateField... fields) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupLimit(long maxGroups) { + throw new RuntimeException("not implemented"); + } + + // Question: Do we want to support group-by "limitToLast" queries? In the Query class this is + // implemented entirely client side by issuing the requested query with inverted order-by. We + // would need to verify at runtime that the underlying query has the correct order-by clause and + // possibly invert first/last aggregations to maintain their expected semantics. + @NonNull + public GroupByQuery groupLimitToLast(long maxGroups) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAt(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAt(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAfter(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupStartAfter(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndAt(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndAt(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndBefore(Object... fieldValues) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupEndBefore(@NonNull GroupSnapshot snapshot) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull String groupByField) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull AggregateField aggregateField) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull String groupByField, @NonNull Direction direction) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField, @NonNull Direction direction) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupOrderBy( + @NonNull AggregateField aggregateField, @NonNull Direction direction) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(Object obj) { + throw new RuntimeException("not implemented"); + } + + public static final class ListenConfig { + + private ListenConfig() {} + + @NonNull + public AggregateQuery.ListenConfig executeCallbacksOn(@NonNull Executor executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public AggregateQuery.ListenConfig scopeTo(@NonNull Activity executor) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public AggregateQuery.ListenConfig includeMetadataOnlyChanges( + boolean includeMetadataOnlyChanges) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public ListenerRegistration startDirectFromServer( + @NonNull EventListener listener) { + throw new RuntimeException("not implemented"); + } + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java new file mode 100644 index 00000000000..95c26827ca9 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java @@ -0,0 +1,74 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import java.util.Iterator; +import java.util.List; +import javax.annotation.Nonnull; + +public class GroupByQuerySnapshot implements Iterable { + + GroupByQuerySnapshot() {} + + @Nonnull + public GroupByQuery getQuery() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public SnapshotMetadata getMetadata() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public List getGroups() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public List getGroupChanges() { + throw new RuntimeException("not implemented"); + } + + public List getGroupChanges(@NonNull MetadataChanges metadataChanges) { + throw new RuntimeException("not implemented"); + } + + public boolean isEmpty() { + throw new RuntimeException("not implemented"); + } + + public int size() { + throw new RuntimeException("not implemented"); + } + + @Override + @NonNull + public Iterator iterator() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object obj) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java new file mode 100644 index 00000000000..c472cc28bf7 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java @@ -0,0 +1,19 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +public enum GroupBySource { + SERVER_DIRECT, +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java new file mode 100644 index 00000000000..1e322290526 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java @@ -0,0 +1,57 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class GroupChange { + + public enum Type { + ADDED, + MODIFIED, + REMOVED + } + + GroupChange() {} + + @NonNull + public Type getType() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupSnapshot getGroup() { + throw new RuntimeException("not implemented"); + } + + public int getOldIndex() { + throw new RuntimeException("not implemented"); + } + + public int getNewIndex() { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object object) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java new file mode 100644 index 00000000000..3ceda427472 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java @@ -0,0 +1,221 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import com.google.firebase.Timestamp; +import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; +import java.util.Date; +import java.util.Map; + +public class GroupSnapshot extends AggregateSnapshot { + + @NonNull + public Map getFields() { + throw new RuntimeException("not implemented"); + } + + @NonNull + public Map getFields( + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + public boolean contains(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + public boolean contains(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get( + @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Object get( + @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get(@NonNull FieldPath field, @NonNull Class valueType) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get(@NonNull String field, @NonNull Class valueType) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get( + @NonNull FieldPath field, + @NonNull Class valueType, + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public T get( + @NonNull String field, + @NonNull Class valueType, + @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Boolean getBoolean(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Boolean getBoolean(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Double getDouble(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Double getDouble(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public String getString(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public String getString(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Long getLong(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Long getLong(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate( + @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Date getDate( + @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp( + @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Timestamp getTimestamp( + @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Blob getBlob(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public Blob getBlob(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public GeoPoint getGeoPoint(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public GeoPoint getGeoPoint(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public DocumentReference getDocumentReference(@NonNull FieldPath field) { + throw new RuntimeException("not implemented"); + } + + @Nullable + public DocumentReference getDocumentReference(@NonNull String field) { + throw new RuntimeException("not implemented"); + } + + @Override + public boolean equals(@Nullable Object obj) { + throw new RuntimeException("not implemented"); + } + + @Override + public int hashCode() { + throw new RuntimeException("not implemented"); + } + + @Override + public String toString() { + throw new RuntimeException("not implemented"); + } +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index 9978ec0b3e5..274a2210dfd 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1223,6 +1223,30 @@ private void validateHasExplicitOrderByForLimitToLast() { } } + @NonNull + public AggregateQuery aggregate( + @NonNull AggregateField field, @NonNull AggregateField... fields) { + throw new RuntimeException("not implemented"); + } + + // A convenience method for just getting the count of a query. + // This method also helps with visibility of the "count" feature, since "aggregate" is + // less obvious what you can do with it. + @NonNull + public AggregateQuery count() { + return aggregate(AggregateField.count()); + } + + @NonNull + public GroupByQuery groupBy(@NonNull String field1, @NonNull String... fields) { + throw new RuntimeException("not implemented"); + } + + @NonNull + public GroupByQuery groupBy(@NonNull FieldPath field1, @NonNull FieldPath... fields) { + throw new RuntimeException("not implemented"); + } + @Override public boolean equals(Object o) { if (this == o) { From 4155ac6be94e49e54fcec8286955e59a25a40d92 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 15:47:09 -0400 Subject: [PATCH 22/48] Datastore.java: runCountQuery() added --- .../firebase/firestore/remote/Datastore.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index 46323e881b9..570add6f2d8 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -14,6 +14,7 @@ package com.google.firebase.firestore.remote; +import static com.google.firebase.firestore.util.Assert.hardAssert; import static com.google.firebase.firestore.util.Util.exceptionFromStatus; import android.content.Context; @@ -25,17 +26,25 @@ import com.google.firebase.firestore.auth.CredentialsProvider; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.core.DatabaseInfo; +import com.google.firebase.firestore.core.Target; import com.google.firebase.firestore.model.DocumentKey; import com.google.firebase.firestore.model.MutableDocument; import com.google.firebase.firestore.model.SnapshotVersion; import com.google.firebase.firestore.model.mutation.Mutation; import com.google.firebase.firestore.model.mutation.MutationResult; import com.google.firebase.firestore.util.AsyncQueue; +import com.google.firestore.v1.AggregationResult; import com.google.firestore.v1.BatchGetDocumentsRequest; import com.google.firestore.v1.BatchGetDocumentsResponse; import com.google.firestore.v1.CommitRequest; import com.google.firestore.v1.CommitResponse; import com.google.firestore.v1.FirestoreGrpc; +import com.google.firestore.v1.RunAggregationQueryRequest; +import com.google.firestore.v1.RunAggregationQueryResponse; +import com.google.firestore.v1.RunQueryRequest; +import com.google.firestore.v1.StructuredAggregationQuery; +import com.google.firestore.v1.Value; + import io.grpc.Status; import java.util.ArrayList; import java.util.Arrays; @@ -215,6 +224,45 @@ public void onClose(Status status) { return completionSource.getTask(); } + public Task runCountQuery(Target queryTarget) { + com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = serializer.encodeQueryTarget(queryTarget); + + StructuredAggregationQuery.Builder structuredAggregationQuery = StructuredAggregationQuery.newBuilder(); + structuredAggregationQuery.setStructuredQuery(encodedQueryTarget.getStructuredQuery()); + + StructuredAggregationQuery.Aggregation.Builder aggregation = StructuredAggregationQuery.Aggregation.newBuilder(); + aggregation.setCount(StructuredAggregationQuery.Aggregation.Count.getDefaultInstance()); + aggregation.setAlias("zzyzx_agg_alias_count"); + structuredAggregationQuery.addAggregations(aggregation); + + RunAggregationQueryRequest.Builder request = RunAggregationQueryRequest.newBuilder(); + request.setParent(encodedQueryTarget.getParent()); + request.setStructuredAggregationQuery(structuredAggregationQuery); + + return channel.runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) + .continueWith( + workerQueue.getExecutor(), + task -> { + if (!task.isSuccessful()) { + if (task.getException() instanceof FirebaseFirestoreException + && ((FirebaseFirestoreException) task.getException()).getCode() + == FirebaseFirestoreException.Code.UNAUTHENTICATED) { + channel.invalidateToken(); + } + throw task.getException(); + } + RunAggregationQueryResponse response = task.getResult(); + + AggregationResult aggregationResult = response.getResult(); + Map aggregateFieldsByAlias = aggregationResult.getAggregateFieldsMap(); + hardAssert(aggregateFieldsByAlias.size() == 1, "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); + Value countValue = aggregateFieldsByAlias.get("zzyzx_agg_alias_count"); + hardAssert(countValue != null, "countValue == null"); + hardAssert(countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); + return countValue.getIntegerValue(); + }); + } + /** * Determines whether the given status has an error code that represents a permanent error when * received in response to a non-write operation. From 6b34e445904a3b097d8d7a89478f3e6f8ea8fc5c Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 16:22:47 -0400 Subject: [PATCH 23/48] wire up count queries --- .../firebase/firestore/AggregateQuery.java | 78 +++++++------------ .../firestore/AggregateQuerySnapshot.java | 18 ++--- .../com/google/firebase/firestore/Query.java | 21 +---- .../firebase/firestore/core/CountQuery.java | 33 ++++++++ .../firestore/core/FirestoreClient.java | 7 +- .../firestore/remote/RemoteStore.java | 2 + 6 files changed, 80 insertions(+), 79 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index e8ec4b91fbc..fa54137993a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -16,66 +16,46 @@ import android.app.Activity; import androidx.annotation.NonNull; + +import com.google.android.gms.tasks.Continuation; import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.firebase.firestore.core.CountQuery; +import com.google.firebase.firestore.util.Executors; + import java.util.concurrent.Executor; -public class AggregateQuery { +public final class AggregateQuery { - AggregateQuery() {} + private final Query query; - @NonNull - public Query getQuery() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Task get() { - throw new RuntimeException("not implemented"); + AggregateQuery(@NonNull Query query, @NonNull AggregateField aggregateField) { + this.query = query; + if (! (aggregateField instanceof AggregateField.CountAggregateField)) { + throw new IllegalArgumentException("unsupported aggregateField: " + aggregateField); + } } @NonNull - public Task get(@NonNull AggregateSource source) { - throw new RuntimeException("not implemented"); + public Query getQuery() { + return query; } @NonNull - public ListenConfig listen() { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(Object obj) { - throw new RuntimeException("not implemented"); + public Task get() { + CountQuery countQuery = new CountQuery(query.firestore.getClient().getDatastore(), query.query); + TaskCompletionSource tcs = new TaskCompletionSource<>(); + + countQuery.run().continueWith(Executors.DIRECT_EXECUTOR, task -> { + if (task.isSuccessful()) { + tcs.setResult(new AggregateQuerySnapshot(task.getResult())); + } else { + tcs.setException(task.getException()); + } + return null; + }); + + return tcs.getTask(); } - public static final class ListenConfig { - - private ListenConfig() {} - - @NonNull - public ListenConfig executeCallbacksOn(@NonNull Executor executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenConfig scopeTo(@NonNull Activity executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenConfig includeMetadataOnlyChanges(boolean includeMetadataOnlyChanges) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenerRegistration startDirectFromServer( - @NonNull EventListener listener) { - throw new RuntimeException("not implemented"); - } - } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 2a786c0b478..6f9da9aea8f 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -15,19 +15,19 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; -import javax.annotation.Nonnull; +import androidx.annotation.Nullable; -public class AggregateQuerySnapshot extends AggregateSnapshot { +public class AggregateQuerySnapshot { - AggregateQuerySnapshot() {} + private final long count; - @Nonnull - public AggregateQuery getQuery() { - throw new RuntimeException("not implemented"); + AggregateQuerySnapshot(long count) { + this.count = count; } - @NonNull - public SnapshotMetadata getMetadata() { - throw new RuntimeException("not implemented"); + @Nullable + public Long get(@NonNull AggregateField.CountAggregateField field) { + return count; } + } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index 274a2210dfd..61e5e6681d2 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1223,28 +1223,9 @@ private void validateHasExplicitOrderByForLimitToLast() { } } - @NonNull - public AggregateQuery aggregate( - @NonNull AggregateField field, @NonNull AggregateField... fields) { - throw new RuntimeException("not implemented"); - } - - // A convenience method for just getting the count of a query. - // This method also helps with visibility of the "count" feature, since "aggregate" is - // less obvious what you can do with it. @NonNull public AggregateQuery count() { - return aggregate(AggregateField.count()); - } - - @NonNull - public GroupByQuery groupBy(@NonNull String field1, @NonNull String... fields) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupBy(@NonNull FieldPath field1, @NonNull FieldPath... fields) { - throw new RuntimeException("not implemented"); + return new AggregateQuery(this, AggregateField.count()); } @Override diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java new file mode 100644 index 00000000000..c50fd5fb5a7 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java @@ -0,0 +1,33 @@ +// Copyright 2022 Google LLC +// +// 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.firestore.core; + +import com.google.android.gms.tasks.Task; +import com.google.firebase.firestore.remote.Datastore; + +public class CountQuery { + private final Datastore datastore; + private final Query query; + + public CountQuery(Datastore datastore, Query query) { + this.datastore = datastore; + this.query = query; + } + + public Task run() { + return datastore.runCountQuery(query.toTarget()); + } + +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 464cd5913f9..20d5d96da6c 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -70,6 +70,7 @@ public final class FirestoreClient { private final BundleSerializer bundleSerializer; private final GrpcMetadataProvider metadataProvider; + private Datastore datastore; private Persistence persistence; private LocalStore localStore; private RemoteStore remoteStore; @@ -255,7 +256,7 @@ private void initialize(Context context, User user, FirebaseFirestoreSettings se // completes. Logger.debug(LOG_TAG, "Initializing. user=%s", user.getUid()); - Datastore datastore = + this.datastore = new Datastore( databaseInfo, asyncQueue, authProvider, appCheckProvider, context, metadataProvider); ComponentProvider.Configuration configuration = @@ -302,6 +303,10 @@ public void loadBundle(InputStream bundleData, LoadBundleTask resultTask) { asyncQueue.enqueueAndForget(() -> syncEngine.loadBundle(bundleReader, resultTask)); } + public Datastore getDatastore() { + return datastore; + } + public Task getNamedQuery(String queryName) { verifyNotTerminated(); TaskCompletionSource completionSource = new TaskCompletionSource<>(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index 2af8508957d..45c909c838e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -19,6 +19,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.firebase.database.collection.ImmutableSortedSet; +import com.google.firebase.firestore.Query; +import com.google.firebase.firestore.core.CountQuery; import com.google.firebase.firestore.core.OnlineState; import com.google.firebase.firestore.core.Transaction; import com.google.firebase.firestore.local.LocalStore; From 8dded2cc119ea3223810b3f2eb163321a9568f6f Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 16:32:22 -0400 Subject: [PATCH 24/48] delete group-by stuff, since it's not needed for this experiment --- .../firebase/firestore/AggregateDemo.java | 97 -------- .../firebase/firestore/GroupByQuery.java | 175 -------------- .../firestore/GroupByQuerySnapshot.java | 74 ------ .../firebase/firestore/GroupBySource.java | 19 -- .../firebase/firestore/GroupChange.java | 57 ----- .../firebase/firestore/GroupSnapshot.java | 221 ------------------ 6 files changed, 643 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java deleted file mode 100644 index fae409b128d..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateDemo.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import static com.google.firebase.firestore.AggregateField.count; - -import android.app.Activity; -import androidx.annotation.NonNull; -import java.util.concurrent.Executors; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -final class AggregateDemo { - - private final FirebaseFirestore db; - - AggregateDemo(@NonNull FirebaseFirestore db) { - this.db = db; - } - - void countBasicQuery() { - AggregateQuery query = db.collection("users").count(); - AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); - assertEqual(snapshot.get(count()), 50); - } - - void countLimitNumRowsScanned() { - AggregateQuery query = db.collection("users").limit(11).count(); - AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); - assertEqual(snapshot.get(count()), 11); - } - - void countUpTo() { - AggregateQuery query = db.collection("users").aggregate(count().upTo(11)); - AggregateQuerySnapshot snapshot = query.get(AggregateSource.SERVER_DIRECT).getResult(); - assertEqual(snapshot.get(count()), 11); - } - - void countRealTimeUpdates() throws InterruptedException { - AggregateQuery query = db.collection("users").aggregate(count()); - - Lock lock = new ReentrantLock(); - Condition condition = lock.newCondition(); - - ListenerRegistration registration = - query - .listen() - .startDirectFromServer( - (snapshot, error) -> { - assertNull(error); - assertNotNull(snapshot); - assertEqual(snapshot.get(count()), 50); - assertFalse(snapshot.getMetadata().isFromCache()); - assertFalse(snapshot.getMetadata().hasPendingWrites()); - lock.lock(); - try { - condition.signalAll(); - } finally { - lock.unlock(); - } - }); - - lock.lock(); - try { - condition.await(); - } finally { - lock.unlock(); - } - - registration.remove(); - } - - private static void assertEqual(Object o1, Object o2) {} - - private static void assertNull(Object o) {} - - private static void assertFalse(Object o) {} - - private static void assertNotNull(Object o) { - if (o == null) { - throw new NullPointerException(); - } - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java deleted file mode 100644 index 073f134fe6c..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuery.java +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import android.app.Activity; -import androidx.annotation.NonNull; -import com.google.android.gms.tasks.Task; -import com.google.firebase.firestore.Query.Direction; -import java.util.concurrent.Executor; - -public class GroupByQuery { - - GroupByQuery() {} - - @NonNull - public Query getQuery() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Task get() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Task get(@NonNull GroupBySource source) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public AggregateQuery.ListenConfig listen() { - throw new RuntimeException("not implemented"); - } - - // Note: Specifying an empty list of aggregates, or not invoking this method at all, is equivalent - // to an SQL "DISTINCT" operator. - @NonNull - public GroupByQuery aggregate(@NonNull AggregateField... fields) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupLimit(long maxGroups) { - throw new RuntimeException("not implemented"); - } - - // Question: Do we want to support group-by "limitToLast" queries? In the Query class this is - // implemented entirely client side by issuing the requested query with inverted order-by. We - // would need to verify at runtime that the underlying query has the correct order-by clause and - // possibly invert first/last aggregations to maintain their expected semantics. - @NonNull - public GroupByQuery groupLimitToLast(long maxGroups) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAt(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAt(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAfter(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupStartAfter(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndAt(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndAt(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndBefore(Object... fieldValues) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupEndBefore(@NonNull GroupSnapshot snapshot) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull String groupByField) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull AggregateField aggregateField) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull String groupByField, @NonNull Direction direction) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy(@NonNull FieldPath groupByField, @NonNull Direction direction) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupByQuery groupOrderBy( - @NonNull AggregateField aggregateField, @NonNull Direction direction) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(Object obj) { - throw new RuntimeException("not implemented"); - } - - public static final class ListenConfig { - - private ListenConfig() {} - - @NonNull - public AggregateQuery.ListenConfig executeCallbacksOn(@NonNull Executor executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public AggregateQuery.ListenConfig scopeTo(@NonNull Activity executor) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public AggregateQuery.ListenConfig includeMetadataOnlyChanges( - boolean includeMetadataOnlyChanges) { - throw new RuntimeException("not implemented"); - } - - @NonNull - public ListenerRegistration startDirectFromServer( - @NonNull EventListener listener) { - throw new RuntimeException("not implemented"); - } - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java deleted file mode 100644 index 95c26827ca9..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupByQuerySnapshot.java +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import java.util.Iterator; -import java.util.List; -import javax.annotation.Nonnull; - -public class GroupByQuerySnapshot implements Iterable { - - GroupByQuerySnapshot() {} - - @Nonnull - public GroupByQuery getQuery() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public SnapshotMetadata getMetadata() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public List getGroups() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public List getGroupChanges() { - throw new RuntimeException("not implemented"); - } - - public List getGroupChanges(@NonNull MetadataChanges metadataChanges) { - throw new RuntimeException("not implemented"); - } - - public boolean isEmpty() { - throw new RuntimeException("not implemented"); - } - - public int size() { - throw new RuntimeException("not implemented"); - } - - @Override - @NonNull - public Iterator iterator() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java deleted file mode 100644 index c472cc28bf7..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupBySource.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -public enum GroupBySource { - SERVER_DIRECT, -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java deleted file mode 100644 index 1e322290526..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupChange.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -public class GroupChange { - - public enum Type { - ADDED, - MODIFIED, - REMOVED - } - - GroupChange() {} - - @NonNull - public Type getType() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public GroupSnapshot getGroup() { - throw new RuntimeException("not implemented"); - } - - public int getOldIndex() { - throw new RuntimeException("not implemented"); - } - - public int getNewIndex() { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object object) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java deleted file mode 100644 index 3ceda427472..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/GroupSnapshot.java +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.firebase.Timestamp; -import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; -import java.util.Date; -import java.util.Map; - -public class GroupSnapshot extends AggregateSnapshot { - - @NonNull - public Map getFields() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Map getFields( - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - public boolean contains(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - public boolean contains(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get( - @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get( - @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get(@NonNull FieldPath field, @NonNull Class valueType) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get(@NonNull String field, @NonNull Class valueType) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get( - @NonNull FieldPath field, - @NonNull Class valueType, - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get( - @NonNull String field, - @NonNull Class valueType, - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Boolean getBoolean(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Boolean getBoolean(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Double getDouble(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Double getDouble(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public String getString(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public String getString(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Long getLong(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Long getLong(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate( - @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate( - @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp( - @NonNull FieldPath field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp( - @NonNull String field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Blob getBlob(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Blob getBlob(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public GeoPoint getGeoPoint(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public GeoPoint getGeoPoint(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public DocumentReference getDocumentReference(@NonNull FieldPath field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public DocumentReference getDocumentReference(@NonNull String field) { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public String toString() { - throw new RuntimeException("not implemented"); - } -} From 12b5cd1268f5c74a98fedda3496a740a73735630 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:21:27 -0400 Subject: [PATCH 25/48] CountTest.java added --- .../google/firebase/firestore/CountTest.java | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java new file mode 100644 index 00000000000..a49851d09f1 --- /dev/null +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -0,0 +1,53 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollectionWithDocs; +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor; +import static com.google.firebase.firestore.testutil.TestUtil.map; +import static org.junit.Assert.assertEquals; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.google.firebase.firestore.AggregateField; +import com.google.firebase.firestore.AggregateQuerySnapshot; +import com.google.firebase.firestore.testutil.IntegrationTestUtil; + +import org.junit.After; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +public class CountTest { + + @After + public void tearDown() { + IntegrationTestUtil.tearDown(); + } + + @Test + public void count() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + + AggregateQuerySnapshot snapshot = waitFor(collection.count().get()); + assertEquals(Long.valueOf(3), snapshot.get(AggregateField.count())); + } + +} From 77beb18c322336407c2dada5425db294affda6cd Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:31:43 -0400 Subject: [PATCH 26/48] Revert changes to Firestore.kt, since this prototype only uses java --- .../firebase/firestore/ktx/Firestore.kt | 31 ++++--------------- 1 file changed, 6 insertions(+), 25 deletions(-) diff --git a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt index fa72f33ca2f..ec2ee225d41 100644 --- a/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt +++ b/firebase-firestore/ktx/src/main/kotlin/com/google/firebase/firestore/ktx/Firestore.kt @@ -18,7 +18,12 @@ import androidx.annotation.Keep import com.google.firebase.FirebaseApp import com.google.firebase.components.Component import com.google.firebase.components.ComponentRegistrar -import com.google.firebase.firestore.* +import com.google.firebase.firestore.DocumentSnapshot +import com.google.firebase.firestore.FieldPath +import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.FirebaseFirestoreSettings +import com.google.firebase.firestore.QueryDocumentSnapshot +import com.google.firebase.firestore.QuerySnapshot import com.google.firebase.ktx.Firebase import com.google.firebase.platforminfo.LibraryVersionComponent @@ -149,30 +154,6 @@ fun firestoreSettings(init: FirebaseFirestoreSettings.Builder.() -> Unit): Fireb return builder.build() } -inline fun GroupSnapshot.getField(field: String): T? = get(field, T::class.java) - -inline fun GroupSnapshot.getField( - field: String, - serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior -): T? = - get(field, T::class.java, serverTimestampBehavior) - -inline fun GroupSnapshot.getField(fieldPath: FieldPath): T? = get(fieldPath, T::class.java) - -inline fun GroupSnapshot.getField( - fieldPath: FieldPath, - serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior -): T? = - get(fieldPath, T::class.java, serverTimestampBehavior) - -inline fun AggregateSnapshot.getField(field: AggregateField): T? = get(field, T::class.java) - -inline fun AggregateSnapshot.getField( - field: AggregateField, - serverTimestampBehavior: DocumentSnapshot.ServerTimestampBehavior -): T? = - get(field, T::class.java, serverTimestampBehavior) - internal const val LIBRARY_NAME: String = "fire-fst-ktx" /** @suppress */ From bcf0bcfaa2dc51017c10a9c10c1d230828643441 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:32:04 -0400 Subject: [PATCH 27/48] AggregateField.java: remove everything except COUNT --- .../firebase/firestore/AggregateField.java | 266 +----------------- 1 file changed, 4 insertions(+), 262 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index d599059be16..4137f1111f0 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -16,7 +16,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import java.util.Objects; public abstract class AggregateField { @@ -27,275 +26,18 @@ public static CountAggregateField count() { return new CountAggregateField(); } - @NonNull - public static MinAggregateField min(@NonNull String field) { - return min(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static MinAggregateField min(@NonNull FieldPath field) { - return new MinAggregateField(field); - } - - @NonNull - public static MaxAggregateField max(@NonNull String field) { - return max(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static MaxAggregateField max(@NonNull FieldPath field) { - return new MaxAggregateField(field); - } - - @NonNull - public static AverageAggregateField average(@NonNull String field) { - return average(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static AverageAggregateField average(@NonNull FieldPath field) { - return new AverageAggregateField(field); - } - - @NonNull - public static SumAggregateField sum(@NonNull String field) { - return sum(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static SumAggregateField sum(@NonNull FieldPath field) { - return new SumAggregateField(field); - } - - @NonNull - public static FirstAggregateField first(@NonNull String field) { - return first(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static FirstAggregateField first(@NonNull FieldPath field) { - return new FirstAggregateField(field); - } - - @NonNull - public static LastAggregateField last(@NonNull String field) { - return last(FieldPath.fromDotSeparatedPath(field)); - } - - @NonNull - public static LastAggregateField last(@NonNull FieldPath field) { - return new LastAggregateField(field); - } - - @Override - public abstract boolean equals(Object obj); - - @Override - public abstract int hashCode(); - - @Override - public abstract String toString(); - public static final class CountAggregateField extends AggregateField { - @Nullable private Integer upTo; + @Nullable + private Integer upTo; - CountAggregateField() {} + CountAggregateField() { + } CountAggregateField(@Nullable Integer upTo) { this.upTo = upTo; } - public CountAggregateField upTo(int upTo) { - if (upTo < 0) { - throw new IllegalArgumentException("upTo==" + upTo); - } - return new CountAggregateField(upTo); - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - CountAggregateField other = (CountAggregateField) obj; - return Objects.equals(upTo, other.upTo); - } - - @Override - public int hashCode() { - return Objects.hash("COUNT", upTo); - } - - @Override - public String toString() { - if (upTo == null) { - return "COUNT"; - } else { - return "COUNT(upTo=" + upTo + ")"; - } - } - } - - public static final class MinAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - MinAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((MinAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "MIN(" + field.toString() + ")"; - } - } - - public static final class MaxAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - MaxAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((MaxAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "MAX(" + field.toString() + ")"; - } - } - - public static final class AverageAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - AverageAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((AverageAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "AVERAGE(" + field.toString() + ")"; - } - } - - public static final class SumAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - SumAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((SumAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "SUM(" + field.toString() + ")"; - } - } - - public static final class FirstAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - FirstAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((FirstAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "FIRST(" + field.toString() + ")"; - } } - public static final class LastAggregateField extends AggregateField { - - @NonNull private FieldPath field; - - LastAggregateField(@NonNull FieldPath field) { - this.field = field; - } - - @Override - public boolean equals(Object obj) { - if (obj == null || obj.getClass() != this.getClass()) { - return false; - } - return field.equals(((LastAggregateField) obj).field); - } - - @Override - public int hashCode() { - return toString().hashCode(); - } - - @Override - public String toString() { - return "LAST(" + field.toString() + ")"; - } - } } From 9aef43ab7caac1d5883b14345896dc06d2b228eb Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:34:15 -0400 Subject: [PATCH 28/48] AggregateSnapshot.java deleted, since it's not used in this prototype --- .../firebase/firestore/AggregateSnapshot.java | 157 ------------------ 1 file changed, 157 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java deleted file mode 100644 index cd9bad6a978..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSnapshot.java +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import com.google.firebase.Timestamp; -import com.google.firebase.firestore.DocumentSnapshot.ServerTimestampBehavior; -import java.util.Date; -import java.util.Map; -import javax.annotation.Nonnull; - -public class AggregateSnapshot { - - AggregateSnapshot() {} - - @NonNull - public Map getAggregations() { - throw new RuntimeException("not implemented"); - } - - @NonNull - public Map getAggregations( - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - public boolean contains(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Object get( - @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get(@NonNull AggregateField field, @NonNull Class valueType) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public T get( - @NonNull AggregateField field, - @NonNull Class valueType, - @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - // Overload get() specifically for COUNT since it has a well-defined type (i.e. long). - @Nullable - public Long get(@Nonnull AggregateField.CountAggregateField field) { - throw new RuntimeException("not implemented"); - } - - // Overload get() specifically for SUM since it has a well-defined type (i.e. double). - @Nullable - public Double get(@Nonnull AggregateField.SumAggregateField field) { - throw new RuntimeException("not implemented"); - } - - // Overload get() specifically for AVERAGE since it has a well-defined type (i.e. double). - @Nullable - public Double get(@Nonnull AggregateField.AverageAggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Boolean getBoolean(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Double getDouble(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public String getString(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Long getLong(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Date getDate( - @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Timestamp getTimestamp( - @NonNull AggregateField field, @NonNull ServerTimestampBehavior serverTimestampBehavior) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public Blob getBlob(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public GeoPoint getGeoPoint(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Nullable - public DocumentReference getDocumentReference(@NonNull AggregateField field) { - throw new RuntimeException("not implemented"); - } - - @Override - public boolean equals(@Nullable Object obj) { - throw new RuntimeException("not implemented"); - } - - @Override - public int hashCode() { - throw new RuntimeException("not implemented"); - } - - @Override - public String toString() { - throw new RuntimeException("not implemented"); - } -} From 01420e4ee76fc235bc898a5ff8839b7752c2ebed Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 22:48:23 -0400 Subject: [PATCH 29/48] revert a bunch of other unnecssary changes --- .../firebase/firestore/AggregateSource.java | 19 -- .../firestore/remote/RemoteStore.java | 2 - .../proto/google/firestore/v1/common.proto | 8 +- .../proto/google/firestore/v1/document.proto | 6 +- .../proto/google/firestore/v1/firestore.proto | 315 +++++------------- .../src/proto/google/firestore/v1/query.proto | 99 +++--- .../src/proto/google/firestore/v1/write.proto | 11 +- 7 files changed, 145 insertions(+), 315 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java deleted file mode 100644 index dd119c59e59..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -public enum AggregateSource { - SERVER_DIRECT, -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index 45c909c838e..2af8508957d 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -19,8 +19,6 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.firebase.database.collection.ImmutableSortedSet; -import com.google.firebase.firestore.Query; -import com.google.firebase.firestore.core.CountQuery; import com.google.firebase.firestore.core.OnlineState; import com.google.firebase.firestore.core.Transaction; import com.google.firebase.firestore.local.LocalStore; diff --git a/firebase-firestore/src/proto/google/firestore/v1/common.proto b/firebase-firestore/src/proto/google/firestore/v1/common.proto index 588a9b81b79..670cb41739b 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/common.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/common.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; +import "google/api/annotations.proto"; import "google/protobuf/timestamp.proto"; option csharp_namespace = "Google.Cloud.Firestore.V1"; @@ -25,7 +27,7 @@ option java_outer_classname = "CommonProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A set of field paths on a document. // Used to restrict a get or update operation on a document to a subset of its @@ -47,7 +49,7 @@ message Precondition { bool exists = 1; // When set, the target document must exist and have been last updated at - // that time. Timestamp must be microsecond aligned. + // that time. google.protobuf.Timestamp update_time = 2; } } diff --git a/firebase-firestore/src/proto/google/firestore/v1/document.proto b/firebase-firestore/src/proto/google/firestore/v1/document.proto index 41283588eb8..268947856a8 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/document.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/document.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; +import "google/api/annotations.proto"; import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/type/latlng.proto"; @@ -27,7 +29,7 @@ option java_outer_classname = "DocumentProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A Firestore document. // diff --git a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto index 69787e8289a..dda4721596c 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/firestore.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/firestore.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,14 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; import "google/api/annotations.proto"; -import "google/api/client.proto"; -import "google/api/field_behavior.proto"; import "google/firestore/v1/aggregation_result.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; @@ -33,25 +32,27 @@ option go_package = "google.golang.org/genproto/googleapis/firestore/v1;firestor option java_multiple_files = true; option java_outer_classname = "FirestoreProto"; option java_package = "com.google.firestore.v1"; +option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; - // Specification of the Firestore API. // The Cloud Firestore service. // -// Cloud Firestore is a fast, fully managed, serverless, cloud-native NoSQL -// document database that simplifies storing, syncing, and querying data for -// your mobile, web, and IoT apps at global scale. Its client libraries provide -// live synchronization and offline support, while its security features and -// integrations with Firebase and Google Cloud Platform (GCP) accelerate -// building truly serverless apps. +// This service exposes several types of comparable timestamps: +// +// * `create_time` - The time at which a document was created. Changes only +// when a document is deleted, then re-created. Increases in a strict +// monotonic fashion. +// * `update_time` - The time at which a document was last updated. Changes +// every time a document is modified. Does not change when a write results +// in no modifications. Increases in a strict monotonic fashion. +// * `read_time` - The time at which a particular state was observed. Used +// to denote a consistent snapshot of the database or the time at which a +// Document was observed to not exist. +// * `commit_time` - The time at which the writes in a transaction were +// committed. Any read with an equal or greater `read_time` is guaranteed +// to see the effects of the transaction. service Firestore { - option (google.api.default_host) = "firestore.googleapis.com"; - option (google.api.oauth_scopes) = - "https://www.googleapis.com/auth/cloud-platform," - "https://www.googleapis.com/auth/datastore"; - // Gets a single document. rpc GetDocument(GetDocumentRequest) returns (Document) { option (google.api.http) = { @@ -63,9 +64,14 @@ service Firestore { rpc ListDocuments(ListDocumentsRequest) returns (ListDocumentsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/databases/*/documents/*/**}/{collection_id}" - additional_bindings { - get: "/v1/{parent=projects/*/databases/*/documents}/{collection_id}" - } + }; + } + + // Creates a new document. + rpc CreateDocument(CreateDocumentRequest) returns (Document) { + option (google.api.http) = { + post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" + body: "document" }; } @@ -75,7 +81,6 @@ service Firestore { patch: "/v1/{document.name=projects/*/databases/*/documents/*/**}" body: "document" }; - option (google.api.method_signature) = "document,update_mask"; } // Deletes a document. @@ -83,7 +88,6 @@ service Firestore { option (google.api.http) = { delete: "/v1/{name=projects/*/databases/*/documents/*/**}" }; - option (google.api.method_signature) = "name"; } // Gets multiple documents. @@ -103,7 +107,6 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:beginTransaction" body: "*" }; - option (google.api.method_signature) = "database"; } // Commits a transaction, while optionally updating documents. @@ -112,7 +115,6 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:commit" body: "*" }; - option (google.api.method_signature) = "database,writes"; } // Rolls back a transaction. @@ -121,7 +123,6 @@ service Firestore { post: "/v1/{database=projects/*/databases/*}/documents:rollback" body: "*" }; - option (google.api.method_signature) = "database,transaction"; } // Runs a query. @@ -159,20 +160,6 @@ service Firestore { }; } - // Partitions a query by returning partition cursors that can be used to run - // the query in parallel. The returned partition cursors are split points that - // can be used by RunQuery as starting/end points for the query results. - rpc PartitionQuery(PartitionQueryRequest) returns (PartitionQueryResponse) { - option (google.api.http) = { - post: "/v1/{parent=projects/*/databases/*/documents}:partitionQuery" - body: "*" - additional_bindings { - post: "/v1/{parent=projects/*/databases/*/documents/*/**}:partitionQuery" - body: "*" - } - }; - } - // Streams batches of document updates and deletes, in order. rpc Write(stream WriteRequest) returns (stream WriteResponse) { option (google.api.http) = { @@ -199,39 +186,14 @@ service Firestore { body: "*" } }; - option (google.api.method_signature) = "parent"; - } - - // Applies a batch of write operations. - // - // The BatchWrite method does not apply the write operations atomically - // and can apply them out of order. Method does not allow more than one write - // per document. Each write succeeds or fails independently. See the - // [BatchWriteResponse][google.firestore.v1.BatchWriteResponse] for the success status of each write. - // - // If you require an atomically applied set of writes, use - // [Commit][google.firestore.v1.Firestore.Commit] instead. - rpc BatchWrite(BatchWriteRequest) returns (BatchWriteResponse) { - option (google.api.http) = { - post: "/v1/{database=projects/*/databases/*}/documents:batchWrite" - body: "*" - }; - } - - // Creates a new document. - rpc CreateDocument(CreateDocumentRequest) returns (Document) { - option (google.api.http) = { - post: "/v1/{parent=projects/*/databases/*/documents/**}/{collection_id}" - body: "document" - }; } } // The request for [Firestore.GetDocument][google.firestore.v1.Firestore.GetDocument]. message GetDocumentRequest { - // Required. The resource name of the Document to get. In the format: + // The resource name of the Document to get. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1 [(google.api.field_behavior) = REQUIRED]; + string name = 1; // The fields to return. If not set, returns all fields. // @@ -246,24 +208,24 @@ message GetDocumentRequest { bytes transaction = 3; // Reads the version of the document at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 5; } } // The request for [Firestore.ListDocuments][google.firestore.v1.Firestore.ListDocuments]. message ListDocumentsRequest { - // Required. The parent resource name. In the format: + // The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; - // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms` + // The collection ID, relative to `parent`, to list. For example: `chatrooms` // or `messages`. - string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; + string collection_id = 2; // The maximum number of documents to return. int32 page_size = 3; @@ -287,7 +249,7 @@ message ListDocumentsRequest { bytes transaction = 8; // Reads documents as they were at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 10; } @@ -312,21 +274,21 @@ message ListDocumentsResponse { // The request for [Firestore.CreateDocument][google.firestore.v1.Firestore.CreateDocument]. message CreateDocumentRequest { - // Required. The parent resource. For example: + // The parent resource. For example: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/chatrooms/{chatroom_id}` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; - // Required. The collection ID, relative to `parent`, to list. For example: `chatrooms`. - string collection_id = 2 [(google.api.field_behavior) = REQUIRED]; + // The collection ID, relative to `parent`, to list. For example: `chatrooms`. + string collection_id = 2; // The client-assigned document ID to use for this document. // // Optional. If not specified, an ID will be assigned by the service. string document_id = 3; - // Required. The document to create. `name` must not be set. - Document document = 4 [(google.api.field_behavior) = REQUIRED]; + // The document to create. `name` must not be set. + Document document = 4; // The fields to return. If not set, returns all fields. // @@ -337,9 +299,9 @@ message CreateDocumentRequest { // The request for [Firestore.UpdateDocument][google.firestore.v1.Firestore.UpdateDocument]. message UpdateDocumentRequest { - // Required. The updated document. + // The updated document. // Creates the document if it does not already exist. - Document document = 1 [(google.api.field_behavior) = REQUIRED]; + Document document = 1; // The fields to update. // None of the field paths in the mask may contain a reserved name. @@ -363,9 +325,9 @@ message UpdateDocumentRequest { // The request for [Firestore.DeleteDocument][google.firestore.v1.Firestore.DeleteDocument]. message DeleteDocumentRequest { - // Required. The resource name of the Document to delete. In the format: + // The resource name of the Document to delete. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. - string name = 1 [(google.api.field_behavior) = REQUIRED]; + string name = 1; // An optional precondition on the document. // The request will fail if this is set and not met by the target document. @@ -374,9 +336,9 @@ message DeleteDocumentRequest { // The request for [Firestore.BatchGetDocuments][google.firestore.v1.Firestore.BatchGetDocuments]. message BatchGetDocumentsRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The names of the documents to retrieve. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. @@ -403,7 +365,7 @@ message BatchGetDocumentsRequest { TransactionOptions new_transaction = 5; // Reads documents as they were at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -435,9 +397,9 @@ message BatchGetDocumentsResponse { // The request for [Firestore.BeginTransaction][google.firestore.v1.Firestore.BeginTransaction]. message BeginTransactionRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The options for the transaction. // Defaults to a read-write transaction. @@ -452,9 +414,9 @@ message BeginTransactionResponse { // The request for [Firestore.Commit][google.firestore.v1.Firestore.Commit]. message CommitRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The writes to apply. // @@ -473,30 +435,29 @@ message CommitResponse { // request. repeated WriteResult write_results = 1; - // The time at which the commit occurred. Any read with an equal or greater - // `read_time` is guaranteed to see the effects of the commit. + // The time at which the commit occurred. google.protobuf.Timestamp commit_time = 2; } // The request for [Firestore.Rollback][google.firestore.v1.Firestore.Rollback]. message RollbackRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; - // Required. The transaction to roll back. - bytes transaction = 2 [(google.api.field_behavior) = REQUIRED]; + // The transaction to roll back. + bytes transaction = 2; } // The request for [Firestore.RunQuery][google.firestore.v1.Firestore.RunQuery]. message RunQueryRequest { - // Required. The parent resource name. In the format: + // The parent resource name. In the format: // `projects/{project_id}/databases/{database_id}/documents` or // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; // The query to run. oneof query_type { @@ -507,9 +468,7 @@ message RunQueryRequest { // The consistency mode for this transaction. // If not set, defaults to strong consistency. oneof consistency_selector { - // Run the query within an already active transaction. - // - // The value here is the opaque transaction ID to execute the query in. + // Reads documents in a transaction. bytes transaction = 5; // Starts a new transaction and reads the documents. @@ -519,7 +478,7 @@ message RunQueryRequest { TransactionOptions new_transaction = 6; // Reads documents as they were at the given time. - // This may not be older than 270 seconds. + // This may not be older than 60 seconds. google.protobuf.Timestamp read_time = 7; } } @@ -532,7 +491,8 @@ message RunQueryResponse { // If set, no other fields will be set in this response. bytes transaction = 2; - // A query result, not set when reporting partial progress. + // A query result. + // Not set when reporting partial progress. Document document = 1; // The time at which the document was read. This may be monotonically @@ -547,15 +507,6 @@ message RunQueryResponse { // The number of results that have been skipped due to an offset between // the last response and the current response. int32 skipped_results = 4; - - // The continuation mode for the query. If present, it indicates the current - // query response stream has finished. This can be set with or without a - // `document` present, but when set, no more results are returned. - oneof continuation_selector { - // If present, Firestore has completely finished the request and no more - // documents will be returned. - bool done = 6; - } } // The request for [Firestore.RunAggregationQuery][google.firestore.v1.Firestore.RunAggregationQuery]. @@ -566,7 +517,7 @@ message RunAggregationQueryRequest { // For example: // `projects/my-project/databases/my-database/documents` or // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; // The query to run. oneof query_type { @@ -614,85 +565,6 @@ message RunAggregationQueryResponse { google.protobuf.Timestamp read_time = 3; } -// The request for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. -message PartitionQueryRequest { - // Required. The parent resource name. In the format: - // `projects/{project_id}/databases/{database_id}/documents`. - // Document resource names are not supported; only database resource names - // can be specified. - string parent = 1 [(google.api.field_behavior) = REQUIRED]; - - // The query to partition. - oneof query_type { - // A structured query. - // Query must specify collection with all descendants and be ordered by name - // ascending. Other filters, order bys, limits, offsets, and start/end - // cursors are not supported. - StructuredQuery structured_query = 2; - } - - // The desired maximum number of partition points. - // The partitions may be returned across multiple pages of results. - // The number must be positive. The actual number of partitions - // returned may be fewer. - // - // For example, this may be set to one fewer than the number of parallel - // queries to be run, or in running a data pipeline job, one fewer than the - // number of workers or compute instances available. - int64 partition_count = 3; - - // The `next_page_token` value returned from a previous call to - // PartitionQuery that may be used to get an additional set of results. - // There are no ordering guarantees between sets of results. Thus, using - // multiple sets of results will require merging the different result sets. - // - // For example, two subsequent calls using a page_token may return: - // - // * cursor B, cursor M, cursor Q - // * cursor A, cursor U, cursor W - // - // To obtain a complete result set ordered with respect to the results of the - // query supplied to PartitionQuery, the results sets should be merged: - // cursor A, cursor B, cursor M, cursor Q, cursor U, cursor W - string page_token = 4; - - // The maximum number of partitions to return in this call, subject to - // `partition_count`. - // - // For example, if `partition_count` = 10 and `page_size` = 8, the first call - // to PartitionQuery will return up to 8 partitions and a `next_page_token` - // if more results exist. A second call to PartitionQuery will return up to - // 2 partitions, to complete the total of 10 specified in `partition_count`. - int32 page_size = 5; -} - -// The response for [Firestore.PartitionQuery][google.firestore.v1.Firestore.PartitionQuery]. -message PartitionQueryResponse { - // Partition results. - // Each partition is a split point that can be used by RunQuery as a starting - // or end point for the query results. The RunQuery requests must be made with - // the same query supplied to this PartitionQuery request. The partition - // cursors will be ordered according to same ordering as the results of the - // query supplied to PartitionQuery. - // - // For example, if a PartitionQuery request returns partition cursors A and B, - // running the following three queries will return the entire result set of - // the original query: - // - // * query, end_at A - // * query, start_at A, end_at B - // * query, start_at B - // - // An empty result may indicate that the query has too few results to be - // partitioned. - repeated Cursor partitions = 1; - - // A page token that may be used to request an additional set of results, up - // to the number specified by `partition_count` in the PartitionQuery request. - // If blank, there are no more results. - string next_page_token = 2; -} - // The request for [Firestore.Write][google.firestore.v1.Firestore.Write]. // // The first request creates a stream, or resumes an existing one from a token. @@ -704,10 +576,10 @@ message PartitionQueryResponse { // given token, then a response containing only an up-to-date token, to use in // the next request. message WriteRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. // This is only required in the first message. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The ID of the write stream to resume. // This may only be set in the first message. When left empty, a new write @@ -760,16 +632,15 @@ message WriteResponse { // request. repeated WriteResult write_results = 3; - // The time at which the commit occurred. Any read with an equal or greater - // `read_time` is guaranteed to see the effects of the write. + // The time at which the commit occurred. google.protobuf.Timestamp commit_time = 4; } // A request for [Firestore.Listen][google.firestore.v1.Firestore.Listen] message ListenRequest { - // Required. The database name. In the format: + // The database name. In the format: // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; + string database = 1; // The supported target changes. oneof target_change { @@ -863,8 +734,14 @@ message Target { google.protobuf.Timestamp read_time = 11; } - // The target ID that identifies the target on the stream. Must be a positive - // number and non-zero. + // A client provided target ID. + // + // If not set, the server will assign an ID for the target. + // + // Used for resuming a target without changing IDs. The IDs can either be + // client-assigned or be server-assigned in a previous stream. All targets + // with client provided IDs must be added before adding a target that needs + // a server-assigned id. int32 target_id = 5; // If the target should be removed once it is current and consistent. @@ -909,7 +786,11 @@ message TargetChange { // // If empty, the change applies to all targets. // - // The order of the target IDs is not defined. + // For `target_change_type=ADD`, the order of the target IDs matches the order + // of the requests to add the targets. This allows clients to unambiguously + // associate server-assigned target IDs with added targets. + // + // For other states, the order of the target IDs is not defined. repeated int32 target_ids = 2; // The error that resulted in this change, if applicable. @@ -936,11 +817,11 @@ message TargetChange { // The request for [Firestore.ListCollectionIds][google.firestore.v1.Firestore.ListCollectionIds]. message ListCollectionIdsRequest { - // Required. The parent document. In the format: + // The parent document. In the format: // `projects/{project_id}/databases/{database_id}/documents/{document_path}`. // For example: // `projects/my-project/databases/my-database/documents/chatrooms/my-chatroom` - string parent = 1 [(google.api.field_behavior) = REQUIRED]; + string parent = 1; // The maximum number of results to return. int32 page_size = 2; @@ -958,35 +839,3 @@ message ListCollectionIdsResponse { // A page token that may be used to continue the list. string next_page_token = 2; } - -// The request for [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. -message BatchWriteRequest { - // Required. The database name. In the format: - // `projects/{project_id}/databases/{database_id}`. - string database = 1 [(google.api.field_behavior) = REQUIRED]; - - // The writes to apply. - // - // Method does not apply writes atomically and does not guarantee ordering. - // Each write succeeds or fails independently. You cannot write to the same - // document more than once per request. - repeated Write writes = 2; - - // Labels associated with this batch write. - map labels = 3; -} - -// The response from [Firestore.BatchWrite][google.firestore.v1.Firestore.BatchWrite]. -message BatchWriteResponse { - // The result of applying the writes. - // - // This i-th write result corresponds to the i-th write in the - // request. - repeated WriteResult write_results = 1; - - // The status of applying the writes. - // - // This i-th write status corresponds to the i-th write in the - // request. - repeated google.rpc.Status status = 2; -} diff --git a/firebase-firestore/src/proto/google/firestore/v1/query.proto b/firebase-firestore/src/proto/google/firestore/v1/query.proto index 4c6250fa780..d76642c4c21 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/query.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/query.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2019 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,12 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; -import "google/api/field_behavior.proto"; +import "google/api/annotations.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/wrappers.proto"; @@ -27,7 +28,7 @@ option java_outer_classname = "QueryProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A Firestore query. message StructuredQuery { @@ -65,7 +66,7 @@ message StructuredQuery { // Unspecified. This value must not be used. OPERATOR_UNSPECIFIED = 0; - // Documents are required to satisfy all of the combined filters. + // The results are required to satisfy each of the combined filters. AND = 1; } @@ -73,10 +74,7 @@ message StructuredQuery { Operator op = 1; // The list of filters to combine. - // - // Requires: - // - // * At least one filter is present. + // Must contain at least one filter. repeated Filter filters = 2; } @@ -115,7 +113,7 @@ message StructuredQuery { // * That `field` come first in `order_by`. GREATER_THAN_OR_EQUAL = 4; - // The given `field` is equal to the given `value`. + // The given `field` is equal to the given `value`.. EQUAL = 5; // The given `field` is not equal to the given `value`. @@ -134,7 +132,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. + // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) IN = 8; // The given `field` is an array that contains any of the values in the @@ -143,7 +141,7 @@ message StructuredQuery { // Requires: // // * That `value` is a non-empty `ArrayValue` with at most 10 values. - // * No other `IN` or `ARRAY_CONTAINS_ANY` or `NOT_IN`. + // * No other `IN` or `ARRAY_CONTAINS_ANY`. (-- or `NOT_IN` --) ARRAY_CONTAINS_ANY = 9; // The value of the `field` is not in the given array. @@ -171,30 +169,30 @@ message StructuredQuery { message UnaryFilter { // A unary operator. enum Operator { - // Unspecified. This value must not be used. - OPERATOR_UNSPECIFIED = 0; - - // The given `field` is equal to `NaN`. - IS_NAN = 2; - - // The given `field` is equal to `NULL`. - IS_NULL = 3; - - // The given `field` is not equal to `NaN`. - // - // Requires: - // - // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NAN = 4; - - // The given `field` is not equal to `NULL`. - // - // Requires: - // - // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. - // * That `field` comes first in the `order_by`. - IS_NOT_NULL = 5; + // Unspecified. This value must not be used. + OPERATOR_UNSPECIFIED = 0; + + // The given `field` is equal to `NaN`. + IS_NAN = 2; + + // The given `field` is equal to `NULL`. + IS_NULL = 3; + + // The given `field` is not equal to `NaN`. + // + // Requires: + // + // * No other `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NAN = 4; + + // The given `field` is not equal to `NULL`. + // + // Requires: + // + // * A single `NOT_EQUAL`, `NOT_IN`, `IS_NOT_NULL`, or `IS_NOT_NAN`. + // * That `field` comes first in the `order_by`. + IS_NOT_NULL = 5; } // The unary operator to apply. @@ -216,18 +214,6 @@ message StructuredQuery { Direction direction = 2; } - // A sort direction. - enum Direction { - // Unspecified. - DIRECTION_UNSPECIFIED = 0; - - // Ascending. - ASCENDING = 1; - - // Descending. - DESCENDING = 2; - } - // A reference to a field, such as `max(messages.time) as max_time`. message FieldReference { string field_path = 2; @@ -242,6 +228,18 @@ message StructuredQuery { repeated FieldReference fields = 2; } + // A sort direction. + enum Direction { + // Unspecified. + DIRECTION_UNSPECIFIED = 0; + + // Ascending. + ASCENDING = 1; + + // Descending. + DESCENDING = 2; + } + // The projection to return. Projection select = 1; @@ -290,7 +288,6 @@ message StructuredQuery { google.protobuf.Int32Value limit = 5; } -// Firestore query for running an aggregation over a [StructuredQuery][google.firestore.v1.StructuredQuery]. message StructuredAggregationQuery { // Defines a aggregation that produces a single result. message Aggregation { @@ -313,7 +310,7 @@ message StructuredAggregationQuery { // Requires: // // * Must be greater than zero when present. - int32 up_to = 1 [(google.api.field_behavior) = OPTIONAL]; + int32 up_to = 1; } // The type of aggregation to perform, required. @@ -329,7 +326,7 @@ message StructuredAggregationQuery { // * Must be present. // * Must be unique across all aggregation aliases. // * Conform to existing [document field name][google.firestore.v1.Document.fields] limitations. - string alias = 7 [(google.api.field_behavior) = REQUIRED]; + string alias = 7; } // The base query to aggregate over. @@ -339,7 +336,7 @@ message StructuredAggregationQuery { } // Optional. Series of aggregations to apply on top of the `structured_query`. - repeated Aggregation aggregations = 3 [(google.api.field_behavior) = OPTIONAL]; + repeated Aggregation aggregations = 3; } // A position in a query result set. diff --git a/firebase-firestore/src/proto/google/firestore/v1/write.proto b/firebase-firestore/src/proto/google/firestore/v1/write.proto index 6d0141e41a0..59b2dafe4fd 100644 --- a/firebase-firestore/src/proto/google/firestore/v1/write.proto +++ b/firebase-firestore/src/proto/google/firestore/v1/write.proto @@ -1,4 +1,4 @@ -// Copyright 2022 Google LLC +// Copyright 2018 Google LLC. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -11,11 +11,13 @@ // 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. +// syntax = "proto3"; package google.firestore.v1; +import "google/api/annotations.proto"; import "google/firestore/v1/common.proto"; import "google/firestore/v1/document.proto"; import "google/protobuf/timestamp.proto"; @@ -27,7 +29,7 @@ option java_outer_classname = "WriteProto"; option java_package = "com.google.firestore.v1"; option objc_class_prefix = "GCFS"; option php_namespace = "Google\\Cloud\\Firestore\\V1"; -option ruby_package = "Google::Cloud::Firestore::V1"; + // A write on a document. message Write { @@ -45,7 +47,7 @@ message Write { // This only requires read access to the document. string verify = 5; - // Applies a transformation to a document. + // Applies a tranformation to a document. DocumentTransform transform = 6; } @@ -84,8 +86,7 @@ message DocumentTransform { SERVER_VALUE_UNSPECIFIED = 0; // The time at which the server processed the request, with millisecond - // precision. If used on multiple fields (same or different documents) in - // a transaction, all the fields will get the same server timestamp. + // precision. REQUEST_TIME = 1; } From 965144b4322ab091b46e0e48a06af7a3aa17ab80 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 23:02:51 -0400 Subject: [PATCH 30/48] Collapse CountQuery.java into AggregateQuery.java for simplicity --- .../firebase/firestore/AggregateQuery.java | 12 +++---- .../firebase/firestore/core/CountQuery.java | 33 ------------------- 2 files changed, 5 insertions(+), 40 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index fa54137993a..11188ce9f69 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -14,17 +14,13 @@ package com.google.firebase.firestore; -import android.app.Activity; import androidx.annotation.NonNull; -import com.google.android.gms.tasks.Continuation; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.firebase.firestore.core.CountQuery; +import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.util.Executors; -import java.util.concurrent.Executor; - public final class AggregateQuery { private final Query query; @@ -43,10 +39,12 @@ public Query getQuery() { @NonNull public Task get() { - CountQuery countQuery = new CountQuery(query.firestore.getClient().getDatastore(), query.query); + Datastore datastore = query.firestore.getClient().getDatastore(); TaskCompletionSource tcs = new TaskCompletionSource<>(); - countQuery.run().continueWith(Executors.DIRECT_EXECUTOR, task -> { + datastore + .runCountQuery(query.query.toTarget()) + .continueWith(Executors.DIRECT_EXECUTOR, task -> { if (task.isSuccessful()) { tcs.setResult(new AggregateQuerySnapshot(task.getResult())); } else { diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java deleted file mode 100644 index c50fd5fb5a7..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/CountQuery.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore.core; - -import com.google.android.gms.tasks.Task; -import com.google.firebase.firestore.remote.Datastore; - -public class CountQuery { - private final Datastore datastore; - private final Query query; - - public CountQuery(Datastore datastore, Query query) { - this.datastore = datastore; - this.query = query; - } - - public Task run() { - return datastore.runCountQuery(query.toTarget()); - } - -} From 4aa3cebe702c5c09611447e9f35c12ef3816ab2e Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 23:04:31 -0400 Subject: [PATCH 31/48] /gradlew :firebase-firestore:googleJavaFormat --- .../google/firebase/firestore/CountTest.java | 5 ---- .../firebase/firestore/AggregateField.java | 8 ++----- .../firebase/firestore/AggregateQuery.java | 22 ++++++++--------- .../firestore/AggregateQuerySnapshot.java | 1 - .../firebase/firestore/remote/Datastore.java | 24 ++++++++++++------- 5 files changed, 28 insertions(+), 32 deletions(-) diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index a49851d09f1..78cf1e94fe9 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -20,11 +20,7 @@ import static org.junit.Assert.assertEquals; import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.google.firebase.firestore.AggregateField; -import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.testutil.IntegrationTestUtil; - import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; @@ -49,5 +45,4 @@ public void count() { AggregateQuerySnapshot snapshot = waitFor(collection.count().get()); assertEquals(Long.valueOf(3), snapshot.get(AggregateField.count())); } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index 4137f1111f0..c13f785dc61 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -28,16 +28,12 @@ public static CountAggregateField count() { public static final class CountAggregateField extends AggregateField { - @Nullable - private Integer upTo; + @Nullable private Integer upTo; - CountAggregateField() { - } + CountAggregateField() {} CountAggregateField(@Nullable Integer upTo) { this.upTo = upTo; } - } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 11188ce9f69..1c6820705a9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -15,7 +15,6 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; - import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.remote.Datastore; @@ -27,7 +26,7 @@ public final class AggregateQuery { AggregateQuery(@NonNull Query query, @NonNull AggregateField aggregateField) { this.query = query; - if (! (aggregateField instanceof AggregateField.CountAggregateField)) { + if (!(aggregateField instanceof AggregateField.CountAggregateField)) { throw new IllegalArgumentException("unsupported aggregateField: " + aggregateField); } } @@ -44,16 +43,17 @@ public Task get() { datastore .runCountQuery(query.query.toTarget()) - .continueWith(Executors.DIRECT_EXECUTOR, task -> { - if (task.isSuccessful()) { - tcs.setResult(new AggregateQuerySnapshot(task.getResult())); - } else { - tcs.setException(task.getException()); - } - return null; - }); + .continueWith( + Executors.DIRECT_EXECUTOR, + task -> { + if (task.isSuccessful()) { + tcs.setResult(new AggregateQuerySnapshot(task.getResult())); + } else { + tcs.setException(task.getException()); + } + return null; + }); return tcs.getTask(); } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 6f9da9aea8f..dceff564bea 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -29,5 +29,4 @@ public class AggregateQuerySnapshot { public Long get(@NonNull AggregateField.CountAggregateField field) { return count; } - } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index 570add6f2d8..ffdce24be93 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -41,10 +41,8 @@ import com.google.firestore.v1.FirestoreGrpc; import com.google.firestore.v1.RunAggregationQueryRequest; import com.google.firestore.v1.RunAggregationQueryResponse; -import com.google.firestore.v1.RunQueryRequest; import com.google.firestore.v1.StructuredAggregationQuery; import com.google.firestore.v1.Value; - import io.grpc.Status; import java.util.ArrayList; import java.util.Arrays; @@ -225,12 +223,15 @@ public void onClose(Status status) { } public Task runCountQuery(Target queryTarget) { - com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = serializer.encodeQueryTarget(queryTarget); + com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = + serializer.encodeQueryTarget(queryTarget); - StructuredAggregationQuery.Builder structuredAggregationQuery = StructuredAggregationQuery.newBuilder(); + StructuredAggregationQuery.Builder structuredAggregationQuery = + StructuredAggregationQuery.newBuilder(); structuredAggregationQuery.setStructuredQuery(encodedQueryTarget.getStructuredQuery()); - StructuredAggregationQuery.Aggregation.Builder aggregation = StructuredAggregationQuery.Aggregation.newBuilder(); + StructuredAggregationQuery.Aggregation.Builder aggregation = + StructuredAggregationQuery.Aggregation.newBuilder(); aggregation.setCount(StructuredAggregationQuery.Aggregation.Count.getDefaultInstance()); aggregation.setAlias("zzyzx_agg_alias_count"); structuredAggregationQuery.addAggregations(aggregation); @@ -239,13 +240,14 @@ public Task runCountQuery(Target queryTarget) { request.setParent(encodedQueryTarget.getParent()); request.setStructuredAggregationQuery(structuredAggregationQuery); - return channel.runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) + return channel + .runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) .continueWith( workerQueue.getExecutor(), task -> { if (!task.isSuccessful()) { if (task.getException() instanceof FirebaseFirestoreException - && ((FirebaseFirestoreException) task.getException()).getCode() + && ((FirebaseFirestoreException) task.getException()).getCode() == FirebaseFirestoreException.Code.UNAUTHENTICATED) { channel.invalidateToken(); } @@ -255,10 +257,14 @@ public Task runCountQuery(Target queryTarget) { AggregationResult aggregationResult = response.getResult(); Map aggregateFieldsByAlias = aggregationResult.getAggregateFieldsMap(); - hardAssert(aggregateFieldsByAlias.size() == 1, "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); + hardAssert( + aggregateFieldsByAlias.size() == 1, + "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); Value countValue = aggregateFieldsByAlias.get("zzyzx_agg_alias_count"); hardAssert(countValue != null, "countValue == null"); - hardAssert(countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); + hardAssert( + countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, + "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); return countValue.getIntegerValue(); }); } From df0542401829437838e4869a50b82c8ec93947b4 Mon Sep 17 00:00:00 2001 From: Denver Coneybeare Date: Fri, 3 Jun 2022 23:06:21 -0400 Subject: [PATCH 32/48] AggregateField.java: remove CountAggregateField.upTo, since it's never used in this prototype --- .../com/google/firebase/firestore/AggregateField.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index c13f785dc61..7406d2fd5dc 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -15,7 +15,6 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; -import androidx.annotation.Nullable; public abstract class AggregateField { @@ -27,13 +26,6 @@ public static CountAggregateField count() { } public static final class CountAggregateField extends AggregateField { - - @Nullable private Integer upTo; - CountAggregateField() {} - - CountAggregateField(@Nullable Integer upTo) { - this.upTo = upTo; - } } } From 8d640fe82cb2e99a1a4b4011a65e139afaee5472 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 28 Jun 2022 11:28:11 -0400 Subject: [PATCH 33/48] Temp checkin --- .../firestore/core/FirestoreClient.java | 10 +++++ .../firestore/core/OnlineQueryRunner.java | 41 +++++++++++++++++++ .../firebase/firestore/core/SyncEngine.java | 6 +++ 3 files changed, 57 insertions(+) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 20d5d96da6c..f25559e88f1 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -21,6 +21,8 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; +import com.google.firebase.firestore.AggregateQuery; +import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException.Code; @@ -238,6 +240,14 @@ public Task transaction( () -> syncEngine.transaction(asyncQueue, options, updateFunction)); } + public Task runCountQuery(AggregateQuery query) { + this.verifyNotTerminated(); + return AsyncQueue.callTask( + asyncQueue.getExecutor(), + () -> syncEngine.runCountQuery(asyncQueue, query)); + } + + /** * Returns a task resolves when all the pending writes at the time when this method is called * received server acknowledgement. An acknowledgement can be either acceptance or rejections. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java new file mode 100644 index 00000000000..9bacc0c53fc --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java @@ -0,0 +1,41 @@ +package com.google.firebase.firestore.core; + +import androidx.annotation.NonNull; + +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.firebase.firestore.AggregateQuery; +import com.google.firebase.firestore.AggregateQuerySnapshot; +import com.google.firebase.firestore.remote.RemoteStore; +import com.google.firebase.firestore.util.AsyncQueue; +import com.google.firebase.firestore.util.ExponentialBackoff; + +public class OnlineQueryRunner { + private AsyncQueue asyncQueue; + private RemoteStore remoteStore; + + private int attemptsRemaining = 1; + + private ExponentialBackoff backoff; + private TaskCompletionSource taskSource = new TaskCompletionSource<>(); + + public OnlineQueryRunner(AsyncQueue asyncQueue, RemoteStore remoteStore) { + backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_TRANSACTION); + this.asyncQueue = asyncQueue; + this.remoteStore = remoteStore; + } + + public Task run(AggregateQuery query) { + runWithBackoff(query); + return taskSource.getTask(); + } + + private void runWithBackoff(AggregateQuery query) { + attemptsRemaining -= 1; + backoff.backoffAndRun( + () -> { + remoteStore.getDatastore().run(query); + }); + } + +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 452d7907c76..02b9ce9d48a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -23,6 +23,8 @@ import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.database.collection.ImmutableSortedMap; import com.google.firebase.database.collection.ImmutableSortedSet; +import com.google.firebase.firestore.AggregateQuery; +import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.LoadBundleTask; import com.google.firebase.firestore.LoadBundleTaskProgress; @@ -314,6 +316,10 @@ public Task transaction( return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } + public Task runCountQuery(AsyncQueue asyncQueue, AggregateQuery query) { + return new OnlineQueryRunner(asyncQueue, remoteStore).run(query); + } + /** Called by FirestoreClient to notify us of a new remote event. */ @Override public void handleRemoteEvent(RemoteEvent event) { From af28b4ce638aedf22d3a746e096ea448a5e30375 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 7 Jul 2022 13:49:35 -0400 Subject: [PATCH 34/48] It runs now --- .../google/firebase/firestore/CountTest.java | 69 ++++++++++++++++++- .../firebase/firestore/AggregateField.java | 4 +- .../firebase/firestore/AggregateQuery.java | 32 +++++++-- .../firestore/AggregateQuerySnapshot.java | 22 +++++- .../firebase/firestore/AggregateSource.java | 19 +++++ .../firestore/core/FirestoreClient.java | 8 +-- .../firestore/core/OnlineQueryRunner.java | 37 +++++++--- .../firebase/firestore/core/SyncEngine.java | 6 +- .../firebase/firestore/remote/Datastore.java | 10 +-- .../firestore/remote/RemoteStore.java | 8 ++- 10 files changed, 174 insertions(+), 41 deletions(-) create mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index 78cf1e94fe9..8138a25210d 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -15,6 +15,7 @@ package com.google.firebase.firestore; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollectionWithDocs; +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testFirestore; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor; import static com.google.firebase.firestore.testutil.TestUtil.map; import static org.junit.Assert.assertEquals; @@ -34,7 +35,7 @@ public void tearDown() { } @Test - public void count() { + public void testCanRunCount() { CollectionReference collection = testCollectionWithDocs( map( @@ -42,7 +43,69 @@ public void count() { "b", map("k", "b"), "c", map("k", "c"))); - AggregateQuerySnapshot snapshot = waitFor(collection.count().get()); - assertEquals(Long.valueOf(3), snapshot.get(AggregateField.count())); + AggregateQuerySnapshot snapshot = + waitFor(collection.count().get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(3), snapshot.getCount()); + } + + @Test + public void testCanRunCountWithFilters() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + + AggregateQuerySnapshot snapshot = + waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(1), snapshot.getCount()); + } + + @Test + public void testCanRunCountWithFiltersAndLimits() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "a"), + "c", map("k", "a"), + "d", map("k", "d"))); + + AggregateQuerySnapshot snapshot = + waitFor( + collection.whereEqualTo("k", "a").limit(2).count().get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(2), snapshot.getCount()); + + snapshot = + waitFor( + collection + .whereEqualTo("k", "a") + .limitToLast(2) + .count() + .get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(2), snapshot.getCount()); + + snapshot = + waitFor( + collection + .whereEqualTo("k", "d") + .limitToLast(1000) + .count() + .get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(1), snapshot.getCount()); + } + + @Test + public void testCanRunCountOnNonExistentCollection() { + CollectionReference collection = testFirestore().collection("random-coll"); + + AggregateQuerySnapshot snapshot = + waitFor(collection.count().get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(0), snapshot.getCount()); + + snapshot = + waitFor(collection.whereEqualTo("k", 100).count().get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(0), snapshot.getCount()); } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index 7406d2fd5dc..328900917fc 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -16,7 +16,7 @@ import androidx.annotation.NonNull; -public abstract class AggregateField { +abstract class AggregateField { private AggregateField() {} @@ -25,7 +25,7 @@ public static CountAggregateField count() { return new CountAggregateField(); } - public static final class CountAggregateField extends AggregateField { + static final class CountAggregateField extends AggregateField { CountAggregateField() {} } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 1c6820705a9..33e1932e142 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -17,8 +17,8 @@ import androidx.annotation.NonNull; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.util.Executors; +import java.util.Objects; public final class AggregateQuery { @@ -37,15 +37,15 @@ public Query getQuery() { } @NonNull - public Task get() { - Datastore datastore = query.firestore.getClient().getDatastore(); + public Task get(AggregateSource source) { TaskCompletionSource tcs = new TaskCompletionSource<>(); - - datastore - .runCountQuery(query.query.toTarget()) + query + .firestore + .getClient() + .runCountQuery(query.query) .continueWith( Executors.DIRECT_EXECUTOR, - task -> { + (task) -> { if (task.isSuccessful()) { tcs.setResult(new AggregateQuerySnapshot(task.getResult())); } else { @@ -56,4 +56,22 @@ public Task get() { return tcs.getTask(); } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AggregateQuery that = (AggregateQuery) o; + return query.equals(that.query); + } + + @Override + public int hashCode() { + return Objects.hash(query); + } + + @Override + public String toString() { + return "AggregateQuery{" + "query=" + query + '}'; + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index dceff564bea..1351c6637ba 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -14,8 +14,8 @@ package com.google.firebase.firestore; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import java.util.Objects; public class AggregateQuerySnapshot { @@ -26,7 +26,25 @@ public class AggregateQuerySnapshot { } @Nullable - public Long get(@NonNull AggregateField.CountAggregateField field) { + public Long getCount() { return count; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + AggregateQuerySnapshot that = (AggregateQuerySnapshot) o; + return count == that.count; + } + + @Override + public int hashCode() { + return Objects.hash(count); + } + + @Override + public String toString() { + return "AggregateQuerySnapshot{" + "count=" + count + '}'; + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java new file mode 100644 index 00000000000..dd119c59e59 --- /dev/null +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -0,0 +1,19 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +public enum AggregateSource { + SERVER_DIRECT, +} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index f25559e88f1..a87d5ecc774 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -21,8 +21,6 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.android.gms.tasks.Tasks; -import com.google.firebase.firestore.AggregateQuery; -import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.EventListener; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException.Code; @@ -240,14 +238,12 @@ public Task transaction( () -> syncEngine.transaction(asyncQueue, options, updateFunction)); } - public Task runCountQuery(AggregateQuery query) { + public Task runCountQuery(Query query) { this.verifyNotTerminated(); return AsyncQueue.callTask( - asyncQueue.getExecutor(), - () -> syncEngine.runCountQuery(asyncQueue, query)); + asyncQueue.getExecutor(), () -> syncEngine.runCountQuery(asyncQueue, query)); } - /** * Returns a task resolves when all the pending writes at the time when this method is called * received server acknowledgement. An acknowledgement can be either acceptance or rejections. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java index 9bacc0c53fc..e140f70943d 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java @@ -1,23 +1,20 @@ package com.google.firebase.firestore.core; -import androidx.annotation.NonNull; - +import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.firebase.firestore.AggregateQuery; -import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.remote.RemoteStore; import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.ExponentialBackoff; -public class OnlineQueryRunner { +public class OnlineQueryRunner { private AsyncQueue asyncQueue; private RemoteStore remoteStore; private int attemptsRemaining = 1; private ExponentialBackoff backoff; - private TaskCompletionSource taskSource = new TaskCompletionSource<>(); + private TaskCompletionSource taskSource = new TaskCompletionSource<>(); public OnlineQueryRunner(AsyncQueue asyncQueue, RemoteStore remoteStore) { backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_TRANSACTION); @@ -25,17 +22,35 @@ public OnlineQueryRunner(AsyncQueue asyncQueue, RemoteStore remoteStore) { this.remoteStore = remoteStore; } - public Task run(AggregateQuery query) { + public Task runCountQuery(Query query) { runWithBackoff(query); return taskSource.getTask(); } - private void runWithBackoff(AggregateQuery query) { + private void runWithBackoff(Query query) { attemptsRemaining -= 1; backoff.backoffAndRun( - () -> { - remoteStore.getDatastore().run(query); - }); + () -> { + remoteStore + .runCountQuery(query) + .addOnCompleteListener( + asyncQueue.getExecutor(), + (OnCompleteListener) + task -> { + if (task.isSuccessful()) { + taskSource.setResult(task.getResult()); + } else { + handleError(task, query); + } + }); + }); } + private void handleError(Task result, Query query) { + if (attemptsRemaining > 0) { + runWithBackoff(query); + } else { + taskSource.setException(result.getException()); + } + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 02b9ce9d48a..56d03b04810 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -23,8 +23,6 @@ import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.database.collection.ImmutableSortedMap; import com.google.firebase.database.collection.ImmutableSortedSet; -import com.google.firebase.firestore.AggregateQuery; -import com.google.firebase.firestore.AggregateQuerySnapshot; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.LoadBundleTask; import com.google.firebase.firestore.LoadBundleTaskProgress; @@ -316,8 +314,8 @@ public Task transaction( return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } - public Task runCountQuery(AsyncQueue asyncQueue, AggregateQuery query) { - return new OnlineQueryRunner(asyncQueue, remoteStore).run(query); + public Task runCountQuery(AsyncQueue asyncQueue, Query query) { + return new OnlineQueryRunner(asyncQueue, remoteStore).runCountQuery(query); } /** Called by FirestoreClient to notify us of a new remote event. */ diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index ffdce24be93..c77a8bf5f5e 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -26,7 +26,7 @@ import com.google.firebase.firestore.auth.CredentialsProvider; import com.google.firebase.firestore.auth.User; import com.google.firebase.firestore.core.DatabaseInfo; -import com.google.firebase.firestore.core.Target; +import com.google.firebase.firestore.core.Query; import com.google.firebase.firestore.model.DocumentKey; import com.google.firebase.firestore.model.MutableDocument; import com.google.firebase.firestore.model.SnapshotVersion; @@ -222,9 +222,9 @@ public void onClose(Status status) { return completionSource.getTask(); } - public Task runCountQuery(Target queryTarget) { + public Task runCountQuery(Query query) { com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = - serializer.encodeQueryTarget(queryTarget); + serializer.encodeQueryTarget(query.toTarget()); StructuredAggregationQuery.Builder structuredAggregationQuery = StructuredAggregationQuery.newBuilder(); @@ -233,7 +233,7 @@ public Task runCountQuery(Target queryTarget) { StructuredAggregationQuery.Aggregation.Builder aggregation = StructuredAggregationQuery.Aggregation.newBuilder(); aggregation.setCount(StructuredAggregationQuery.Aggregation.Count.getDefaultInstance()); - aggregation.setAlias("zzyzx_agg_alias_count"); + aggregation.setAlias("count_alias"); structuredAggregationQuery.addAggregations(aggregation); RunAggregationQueryRequest.Builder request = RunAggregationQueryRequest.newBuilder(); @@ -260,7 +260,7 @@ public Task runCountQuery(Target queryTarget) { hardAssert( aggregateFieldsByAlias.size() == 1, "aggregateFieldsByAlias.size()==" + aggregateFieldsByAlias.size()); - Value countValue = aggregateFieldsByAlias.get("zzyzx_agg_alias_count"); + Value countValue = aggregateFieldsByAlias.get("count_alias"); hardAssert(countValue != null, "countValue == null"); hardAssert( countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index 2af8508957d..e74adbd5a8f 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -18,8 +18,10 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import com.google.android.gms.tasks.Task; import com.google.firebase.database.collection.ImmutableSortedSet; import com.google.firebase.firestore.core.OnlineState; +import com.google.firebase.firestore.core.Query; import com.google.firebase.firestore.core.Transaction; import com.google.firebase.firestore.local.LocalStore; import com.google.firebase.firestore.local.QueryPurpose; @@ -50,7 +52,7 @@ * RemoteStore handles all interaction with the backend through a simple, clean interface. This * class is not thread safe and should be only called from the worker AsyncQueue. */ -public final class RemoteStore implements WatchChangeAggregator.TargetMetadataProvider { +public final class RemoteStore implements WatchChangeAggregator.TargetMetadataProvider { /** The maximum number of pending writes to allow. TODO: Negotiate this value with the backend. */ private static final int MAX_PENDING_WRITES = 10; @@ -747,4 +749,8 @@ public ImmutableSortedSet getRemoteKeysForTarget(int targetId) { public TargetData getTargetDataForTarget(int targetId) { return this.listenTargets.get(targetId); } + + public Task runCountQuery(Query query) { + return datastore.runCountQuery(query); + } } From e748b6a925cee938117d15dc5fb99791eb295cfa Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 7 Jul 2022 13:57:35 -0400 Subject: [PATCH 35/48] Format and lint --- .../google/firebase/firestore/AggregateQuery.java | 2 +- .../firebase/firestore/core/OnlineQueryRunner.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 33e1932e142..26aa76ff62a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -37,7 +37,7 @@ public Query getQuery() { } @NonNull - public Task get(AggregateSource source) { + public Task get(@NonNull AggregateSource source) { TaskCompletionSource tcs = new TaskCompletionSource<>(); query .firestore diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java index e140f70943d..057b9d17b3f 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java @@ -1,3 +1,17 @@ +// Copyright 2022 Google LLC +// +// 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.firestore.core; import com.google.android.gms.tasks.OnCompleteListener; From 3efce02a6d472c7a8f4912650ee6312069f1e3c7 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Thu, 7 Jul 2022 15:11:28 -0400 Subject: [PATCH 36/48] Undo change --- .../google/firebase/firestore/core/FirestoreClient.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index a87d5ecc774..2b19c8977b7 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -70,7 +70,6 @@ public final class FirestoreClient { private final BundleSerializer bundleSerializer; private final GrpcMetadataProvider metadataProvider; - private Datastore datastore; private Persistence persistence; private LocalStore localStore; private RemoteStore remoteStore; @@ -262,7 +261,7 @@ private void initialize(Context context, User user, FirebaseFirestoreSettings se // completes. Logger.debug(LOG_TAG, "Initializing. user=%s", user.getUid()); - this.datastore = + Datastore datastore = new Datastore( databaseInfo, asyncQueue, authProvider, appCheckProvider, context, metadataProvider); ComponentProvider.Configuration configuration = @@ -309,10 +308,6 @@ public void loadBundle(InputStream bundleData, LoadBundleTask resultTask) { asyncQueue.enqueueAndForget(() -> syncEngine.loadBundle(bundleReader, resultTask)); } - public Datastore getDatastore() { - return datastore; - } - public Task getNamedQuery(String queryName) { verifyNotTerminated(); TaskCompletionSource completionSource = new TaskCompletionSource<>(); From 8d9866284b83b2f10d356fbc05ceec7c3a02c842 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Fri, 8 Jul 2022 14:50:15 -0400 Subject: [PATCH 37/48] Public doc and changelog --- firebase-firestore/CHANGELOG.md | 2 ++ .../google/firebase/firestore/AggregateField.java | 8 ++++++++ .../google/firebase/firestore/AggregateQuery.java | 12 ++++++++++++ .../google/firebase/firestore/AggregateSource.java | 7 +++++++ .../java/com/google/firebase/firestore/Query.java | 6 ++++++ 5 files changed, 35 insertions(+) diff --git a/firebase-firestore/CHANGELOG.md b/firebase-firestore/CHANGELOG.md index 2fa979cebc0..c087b195c70 100644 --- a/firebase-firestore/CHANGELOG.md +++ b/firebase-firestore/CHANGELOG.md @@ -3,6 +3,8 @@ by opting into a release at [go/firebase-android-release](http:go/firebase-android-release) (Googlers only). # Unreleased +- [feature] Add COUNT queries that go to server directly for the result, without applying local + documents and mutations. # 24.2.0 - [feature] Added `TransactionOptions` to control how many times a transaction diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index 328900917fc..f9ddca84835 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -16,10 +16,18 @@ import androidx.annotation.NonNull; +/** + * Represents which field to aggregate on for a {@link AggregateQuery}, and what type of + * aggregations to perform. + */ abstract class AggregateField { private AggregateField() {} + /** + * Returns a {@link CountAggregateField} which counts the number of documents matching the {@code + * AggregateQuery}. + */ @NonNull public static CountAggregateField count() { return new CountAggregateField(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 26aa76ff62a..0ece4066741 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -20,8 +20,13 @@ import com.google.firebase.firestore.util.Executors; import java.util.Objects; +/** + * A {@code AggregateQuery} computes some aggregation statistics from the result set of a base + * {@link Query}. + */ public final class AggregateQuery { + // The base query. private final Query query; AggregateQuery(@NonNull Query query, @NonNull AggregateField aggregateField) { @@ -31,11 +36,18 @@ public final class AggregateQuery { } } + /** Returns the base {@link Query} for this aggregate query. */ @NonNull public Query getQuery() { return query; } + /** + * Executes the aggregate query and returns the results as a {@code AggregateQuerySnapshot}. + * + * @param source A value to configure the get behavior. + * @return A Task that will be resolved with the results of the {@code AggregateQuery}. + */ @NonNull public Task get(@NonNull AggregateSource source) { TaskCompletionSource tcs = new TaskCompletionSource<>(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java index dd119c59e59..fa1c4bed6c4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -14,6 +14,13 @@ package com.google.firebase.firestore; +/** Configures the behavior of {@code get()} calls on {@link AggregateQuery}. */ public enum AggregateSource { + /** + * Reach to the Firestore backend and surface the result verbatim, that is no local documents or + * mutations in the SDK cache will be included in the surfaced result. + * + *

Requires client to be online. + */ SERVER_DIRECT, } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index 61e5e6681d2..9f462dc39c9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1223,6 +1223,12 @@ private void validateHasExplicitOrderByForLimitToLast() { } } + /** + * Creates an {@link AggregateQuery} counting the number of documents matching this query. + * + * @return An {@link AggregateQuery} object that can be used to count the number of documents in + * the result set of this query. + */ @NonNull public AggregateQuery count() { return new AggregateQuery(this, AggregateField.count()); From 4d7da5ac67a5b8b86001e8248bf3b2d1994ab2dd Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Fri, 8 Jul 2022 14:57:31 -0400 Subject: [PATCH 38/48] api.txt --- firebase-firestore/api.txt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index ab8b3f4192c..15bbbd8e1cb 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -19,6 +19,19 @@ package com.google.firebase { package com.google.firebase.firestore { + public final class AggregateQuery { + method @NonNull public com.google.android.gms.tasks.Task get(@NonNull com.google.firebase.firestore.AggregateSource); + method @NonNull public com.google.firebase.firestore.Query getQuery(); + } + + public class AggregateQuerySnapshot { + method @Nullable public Long getCount(); + } + + public enum AggregateSource { + enum_constant public static final com.google.firebase.firestore.AggregateSource SERVER_DIRECT; + } + public class Blob implements java.lang.Comparable { method public int compareTo(@NonNull com.google.firebase.firestore.Blob); method @NonNull public static com.google.firebase.firestore.Blob fromBytes(@NonNull byte[]); @@ -290,6 +303,7 @@ package com.google.firebase.firestore { method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); + method @NonNull public com.google.firebase.firestore.AggregateQuery count(); method @NonNull public com.google.firebase.firestore.Query endAt(@NonNull com.google.firebase.firestore.DocumentSnapshot); method @NonNull public com.google.firebase.firestore.Query endAt(java.lang.Object...); method @NonNull public com.google.firebase.firestore.Query endBefore(@NonNull com.google.firebase.firestore.DocumentSnapshot); From c33a960bc5b11931910f14f90913d27a2d44d6e1 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 13 Jul 2022 16:37:57 -0400 Subject: [PATCH 39/48] Support network change and address feedback --- .../google/firebase/firestore/CountTest.java | 23 +++++++++++++++++++ .../firebase/firestore/AggregateQuery.java | 3 +-- .../firestore/AggregateQuerySnapshot.java | 11 +++++++++ .../firestore/core/OnlineQueryRunner.java | 6 +++-- .../firestore/core/TransactionRunner.java | 18 +++------------ .../firestore/remote/RemoteStore.java | 12 ++++++++-- .../firebase/firestore/util/AsyncQueue.java | 5 ++++ .../google/firebase/firestore/util/Util.java | 13 +++++++++++ 8 files changed, 70 insertions(+), 21 deletions(-) diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index 8138a25210d..9f3c1f4c085 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -17,8 +17,10 @@ import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollectionWithDocs; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testFirestore; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor; +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitForException; import static com.google.firebase.firestore.testutil.TestUtil.map; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.firebase.firestore.testutil.IntegrationTestUtil; @@ -108,4 +110,25 @@ public void testCanRunCountOnNonExistentCollection() { waitFor(collection.whereEqualTo("k", 100).count().get(AggregateSource.SERVER_DIRECT)); assertEquals(Long.valueOf(0), snapshot.getCount()); } + + @Test + public void testFailWithoutNetwork() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + waitFor(collection.getFirestore().disableNetwork()); + + Exception e = waitForException(collection.count().get(AggregateSource.SERVER_DIRECT)); + assertTrue(e instanceof FirebaseFirestoreException); + assertEquals( + FirebaseFirestoreException.Code.UNAVAILABLE, ((FirebaseFirestoreException) e).getCode()); + + waitFor(collection.getFirestore().enableNetwork()); + AggregateQuerySnapshot snapshot = + waitFor(collection.count().get(AggregateSource.SERVER_DIRECT)); + assertEquals(Long.valueOf(3), snapshot.getCount()); + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 0ece4066741..9f8d11fb518 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -18,7 +18,6 @@ import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.util.Executors; -import java.util.Objects; /** * A {@code AggregateQuery} computes some aggregation statistics from the result set of a base @@ -79,7 +78,7 @@ public boolean equals(Object o) { @Override public int hashCode() { - return Objects.hash(query); + return query.hashCode(); } @Override diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 1351c6637ba..23a6db82a87 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -17,6 +17,13 @@ import androidx.annotation.Nullable; import java.util.Objects; +/** + * A {@code AggregateQuerySnapshot} contains results of a {@link AggregateQuery}. + * + *

Subclassing Note: Cloud Firestore classes are not meant to be subclassed except for use + * in test mocks. Subclassing is not supported in production code and new SDK releases may break + * code that does so. + */ public class AggregateQuerySnapshot { private final long count; @@ -25,6 +32,10 @@ public class AggregateQuerySnapshot { this.count = count; } + /** + * @return The result of a document count aggregation. Returns null if no count aggregation is + * available in the result. + */ @Nullable public Long getCount() { return count; diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java index 057b9d17b3f..cb768b05ef2 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java @@ -14,6 +14,8 @@ package com.google.firebase.firestore.core; +import static com.google.firebase.firestore.util.Util.isRetryableBackendError; + import com.google.android.gms.tasks.OnCompleteListener; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; @@ -31,7 +33,7 @@ public class OnlineQueryRunner { private TaskCompletionSource taskSource = new TaskCompletionSource<>(); public OnlineQueryRunner(AsyncQueue asyncQueue, RemoteStore remoteStore) { - backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_TRANSACTION); + backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_ONLINE_QUERY); this.asyncQueue = asyncQueue; this.remoteStore = remoteStore; } @@ -61,7 +63,7 @@ private void runWithBackoff(Query query) { } private void handleError(Task result, Query query) { - if (attemptsRemaining > 0) { + if (attemptsRemaining > 0 && isRetryableBackendError(result.getException())) { runWithBackoff(query); } else { taskSource.setException(result.getException()); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java index 70a249cc804..4f972736ac8 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java @@ -14,12 +14,12 @@ package com.google.firebase.firestore.core; +import static com.google.firebase.firestore.util.Util.isRetryableBackendError; + import androidx.annotation.NonNull; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.TransactionOptions; -import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.remote.RemoteStore; import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.AsyncQueue.TimerId; @@ -86,22 +86,10 @@ private void runWithBackoff() { } private void handleTransactionError(Task task) { - if (attemptsRemaining > 0 && isRetryableTransactionError(task.getException())) { + if (attemptsRemaining > 0 && isRetryableBackendError(task.getException())) { runWithBackoff(); } else { taskSource.setException(task.getException()); } } - - private static boolean isRetryableTransactionError(Exception e) { - if (e instanceof FirebaseFirestoreException) { - // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and - // non-matching document versions with ABORTED. These errors should be retried. - FirebaseFirestoreException.Code code = ((FirebaseFirestoreException) e).getCode(); - return code == FirebaseFirestoreException.Code.ABORTED - || code == FirebaseFirestoreException.Code.FAILED_PRECONDITION - || !Datastore.isPermanentError(((FirebaseFirestoreException) e).getCode()); - } - return false; - } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index e74adbd5a8f..dd75609d223 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -19,7 +19,9 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; import com.google.firebase.database.collection.ImmutableSortedSet; +import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.core.OnlineState; import com.google.firebase.firestore.core.Query; import com.google.firebase.firestore.core.Transaction; @@ -52,7 +54,7 @@ * RemoteStore handles all interaction with the backend through a simple, clean interface. This * class is not thread safe and should be only called from the worker AsyncQueue. */ -public final class RemoteStore implements WatchChangeAggregator.TargetMetadataProvider { +public final class RemoteStore implements WatchChangeAggregator.TargetMetadataProvider { /** The maximum number of pending writes to allow. TODO: Negotiate this value with the backend. */ private static final int MAX_PENDING_WRITES = 10; @@ -751,6 +753,12 @@ public TargetData getTargetDataForTarget(int targetId) { } public Task runCountQuery(Query query) { - return datastore.runCountQuery(query); + if (canUseNetwork()) { + return datastore.runCountQuery(query); + } + + return Tasks.forException( + new FirebaseFirestoreException( + "Failed to get result from server.", FirebaseFirestoreException.Code.UNAVAILABLE)); } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java index c368022f07a..463ff9e64c6 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java @@ -77,6 +77,11 @@ public enum TimerId { * multiple of these may be in the queue at a given time. */ RETRY_TRANSACTION, + /** + * A timer used to retry queries sent to DFE. Since there can be multiple active queries, + * multiple of these may be in the queue at a given time. + */ + RETRY_ONLINE_QUERY, /** * A timer used to monitor when a connection attempt in gRPC is unsuccessful and retry * accordingly. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java index 6591db805c1..be6a7c490c0 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java @@ -22,6 +22,7 @@ import com.google.firebase.firestore.FieldPath; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException.Code; +import com.google.firebase.firestore.remote.Datastore; import com.google.protobuf.ByteString; import io.grpc.Status; import io.grpc.StatusException; @@ -137,6 +138,18 @@ public static Exception convertThrowableToException(Throwable t) { } } + public static boolean isRetryableBackendError(Exception e) { + if (e instanceof FirebaseFirestoreException) { + // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and + // non-matching document versions with ABORTED. These errors should be retried. + FirebaseFirestoreException.Code code = ((FirebaseFirestoreException) e).getCode(); + return code == FirebaseFirestoreException.Code.ABORTED + || code == FirebaseFirestoreException.Code.FAILED_PRECONDITION + || !Datastore.isPermanentError(((FirebaseFirestoreException) e).getCode()); + } + return false; + } + private static final Continuation VOID_ERROR_TRANSFORMER = task -> { if (task.isSuccessful()) { From e33fb81ee9f8be5705817ec89ddcacc562a95354 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 18 Jul 2022 11:44:43 -0400 Subject: [PATCH 40/48] Address more feedback --- firebase-firestore/api.txt | 2 +- .../google/firebase/firestore/CountTest.java | 82 ++++++++++++++++++- .../firebase/firestore/AggregateField.java | 2 +- .../firebase/firestore/AggregateQuery.java | 18 +++- .../firestore/AggregateQuerySnapshot.java | 2 +- .../firestore/core/FirestoreClient.java | 4 +- .../firestore/core/OnlineQueryRunner.java | 26 +++--- .../firebase/firestore/core/SyncEngine.java | 4 +- .../firestore/AggregateQueryTest.java | 34 ++++++++ 9 files changed, 150 insertions(+), 24 deletions(-) create mode 100644 firebase-firestore/src/test/java/com/google/firebase/firestore/AggregateQueryTest.java diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index 15bbbd8e1cb..d0b225013b7 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -19,7 +19,7 @@ package com.google.firebase { package com.google.firebase.firestore { - public final class AggregateQuery { + public class AggregateQuery { method @NonNull public com.google.android.gms.tasks.Task get(@NonNull com.google.firebase.firestore.AggregateSource); method @NonNull public com.google.firebase.firestore.Query getQuery(); } diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index 9f3c1f4c085..a1b1d698536 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -14,13 +14,16 @@ package com.google.firebase.firestore; +import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollection; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testCollectionWithDocs; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.testFirestore; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitFor; import static com.google.firebase.firestore.testutil.IntegrationTestUtil.waitForException; import static com.google.firebase.firestore.testutil.TestUtil.map; +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertNotEquals; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.firebase.firestore.testutil.IntegrationTestUtil; @@ -36,6 +39,29 @@ public void tearDown() { IntegrationTestUtil.tearDown(); } + @Test + public void testCountQueryEquals() { + CollectionReference coll1 = testCollection("foo"); + CollectionReference coll1_same = coll1.firestore.collection(coll1.getPath()); + Query query1 = coll1.document("bar").collection("baz").whereEqualTo("a", 1).limit(100); + Query query1_same = coll1.document("bar").collection("baz").whereEqualTo("a", 1).limit(100); + Query query2 = coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c"); + Query query2_same = coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c"); + + assertEquals(coll1, coll1_same); + assertEquals(query1, query1_same); + assertEquals(query2, query2_same); + + assertEquals(coll1.hashCode(), coll1_same.hashCode()); + assertEquals(query1.hashCode(), query1_same.hashCode()); + assertEquals(query2.hashCode(), query2_same.hashCode()); + + assertNotEquals(coll1, query1); + assertNotEquals(query1, query2); + assertNotEquals(coll1.hashCode(), query1.hashCode()); + assertNotEquals(query1.hashCode(), query2.hashCode()); + } + @Test public void testCanRunCount() { CollectionReference collection = @@ -64,6 +90,40 @@ public void testCanRunCountWithFilters() { assertEquals(Long.valueOf(1), snapshot.getCount()); } + @Test + public void testCanRunCollectionGroupQuery() { + FirebaseFirestore db = testFirestore(); + // Use .document() to get a random collection group name to use but ensure it starts with 'b' + // for predictable ordering. + String collectionGroup = "b" + db.collection("foo").document().getId(); + + String[] docPaths = + new String[] { + "abc/123/${collectionGroup}/cg-doc1", + "abc/123/${collectionGroup}/cg-doc2", + "${collectionGroup}/cg-doc3", + "${collectionGroup}/cg-doc4", + "def/456/${collectionGroup}/cg-doc5", + "${collectionGroup}/virtual-doc/nested-coll/not-cg-doc", + "x${collectionGroup}/not-cg-doc", + "${collectionGroup}x/not-cg-doc", + "abc/123/${collectionGroup}x/not-cg-doc", + "abc/123/x${collectionGroup}/not-cg-doc", + "abc/${collectionGroup}" + }; + WriteBatch batch = db.batch(); + for (String path : docPaths) { + batch.set(db.document(path.replace("${collectionGroup}", collectionGroup)), map("x", 1)); + } + waitFor(batch.commit()); + + AggregateQuerySnapshot snapshot = + waitFor(db.collectionGroup(collectionGroup).count().get(AggregateSource.SERVER_DIRECT)); + assertEquals( + Long.valueOf(5), // "cg-doc1", "cg-doc2", "cg-doc3", "cg-doc4", "cg-doc5", + snapshot.getCount()); + } + @Test public void testCanRunCountWithFiltersAndLimits() { CollectionReference collection = @@ -122,7 +182,7 @@ public void testFailWithoutNetwork() { waitFor(collection.getFirestore().disableNetwork()); Exception e = waitForException(collection.count().get(AggregateSource.SERVER_DIRECT)); - assertTrue(e instanceof FirebaseFirestoreException); + assertThat(e, instanceOf(FirebaseFirestoreException.class)); assertEquals( FirebaseFirestoreException.Code.UNAVAILABLE, ((FirebaseFirestoreException) e).getCode()); @@ -131,4 +191,22 @@ public void testFailWithoutNetwork() { waitFor(collection.count().get(AggregateSource.SERVER_DIRECT)); assertEquals(Long.valueOf(3), snapshot.getCount()); } + + @Test + public void testExponentialBackoffWorks() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + waitFor(collection.getFirestore().disableNetwork()); + + Exception e = + waitForException( + collection.count().get(AggregateSource.SERVER_DIRECT, /* maxAttempts */ 2)); + assertThat(e, instanceOf(FirebaseFirestoreException.class)); + assertEquals( + FirebaseFirestoreException.Code.UNAVAILABLE, ((FirebaseFirestoreException) e).getCode()); + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java index f9ddca84835..0d044010c3a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java @@ -25,7 +25,7 @@ abstract class AggregateField { private AggregateField() {} /** - * Returns a {@link CountAggregateField} which counts the number of documents matching the {@code + * Returns a {@link CountAggregateField} which counts the number of documents matching the {@link * AggregateQuery}. */ @NonNull diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 9f8d11fb518..06651af6673 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -15,15 +15,21 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; +import androidx.annotation.VisibleForTesting; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.util.Executors; +import com.google.firebase.firestore.util.Preconditions; /** * A {@code AggregateQuery} computes some aggregation statistics from the result set of a base * {@link Query}. + * + *

Subclassing Note: Cloud Firestore classes are not meant to be subclassed except for use + * in test mocks. Subclassing is not supported in production code and new SDK releases may break + * code that does so. */ -public final class AggregateQuery { +public class AggregateQuery { // The base query. private final Query query; @@ -49,11 +55,17 @@ public Query getQuery() { */ @NonNull public Task get(@NonNull AggregateSource source) { + return get(source, 1); + } + + @VisibleForTesting + Task get(@NonNull AggregateSource source, int maxAttempts) { + Preconditions.checkNotNull(source, "AggregateSource must not be null"); TaskCompletionSource tcs = new TaskCompletionSource<>(); query .firestore .getClient() - .runCountQuery(query.query) + .runCountQuery(query.query, maxAttempts) .continueWith( Executors.DIRECT_EXECUTOR, (task) -> { @@ -71,7 +83,7 @@ public Task get(@NonNull AggregateSource source) { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (o == null || !(o instanceof AggregateQuery)) return false; AggregateQuery that = (AggregateQuery) o; return query.equals(that.query); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 23a6db82a87..31be51457fc 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -44,7 +44,7 @@ public Long getCount() { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (o == null || !(o instanceof AggregateQuerySnapshot)) return false; AggregateQuerySnapshot that = (AggregateQuerySnapshot) o; return count == that.count; } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 2b19c8977b7..d39191b76f7 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -237,10 +237,10 @@ public Task transaction( () -> syncEngine.transaction(asyncQueue, options, updateFunction)); } - public Task runCountQuery(Query query) { + public Task runCountQuery(Query query, int maxAttempts) { this.verifyNotTerminated(); return AsyncQueue.callTask( - asyncQueue.getExecutor(), () -> syncEngine.runCountQuery(asyncQueue, query)); + asyncQueue.getExecutor(), () -> syncEngine.runCountQuery(asyncQueue, query, maxAttempts)); } /** diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java index cb768b05ef2..a5488f97138 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java @@ -23,27 +23,29 @@ import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.ExponentialBackoff; -public class OnlineQueryRunner { - private AsyncQueue asyncQueue; - private RemoteStore remoteStore; +public final class OnlineQueryRunner { + private final AsyncQueue asyncQueue; + private final RemoteStore remoteStore; - private int attemptsRemaining = 1; + private int attemptsRemaining; - private ExponentialBackoff backoff; - private TaskCompletionSource taskSource = new TaskCompletionSource<>(); + private final ExponentialBackoff backoff; - public OnlineQueryRunner(AsyncQueue asyncQueue, RemoteStore remoteStore) { + public OnlineQueryRunner( + Query query, AsyncQueue asyncQueue, RemoteStore remoteStore, int maxAttempts) { backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_ONLINE_QUERY); this.asyncQueue = asyncQueue; this.remoteStore = remoteStore; + this.attemptsRemaining = maxAttempts; } public Task runCountQuery(Query query) { - runWithBackoff(query); + TaskCompletionSource taskSource = new TaskCompletionSource<>(); + runWithBackoff(query, taskSource); return taskSource.getTask(); } - private void runWithBackoff(Query query) { + private void runWithBackoff(Query query, TaskCompletionSource taskSource) { attemptsRemaining -= 1; backoff.backoffAndRun( () -> { @@ -56,15 +58,15 @@ private void runWithBackoff(Query query) { if (task.isSuccessful()) { taskSource.setResult(task.getResult()); } else { - handleError(task, query); + handleError(task, query, taskSource); } }); }); } - private void handleError(Task result, Query query) { + private void handleError(Task result, Query query, TaskCompletionSource taskSource) { if (attemptsRemaining > 0 && isRetryableBackendError(result.getException())) { - runWithBackoff(query); + runWithBackoff(query, taskSource); } else { taskSource.setException(result.getException()); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 56d03b04810..6a5023d921d 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -314,8 +314,8 @@ public Task transaction( return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } - public Task runCountQuery(AsyncQueue asyncQueue, Query query) { - return new OnlineQueryRunner(asyncQueue, remoteStore).runCountQuery(query); + public Task runCountQuery(AsyncQueue asyncQueue, Query query, int maxAttempts) { + return new OnlineQueryRunner(query, asyncQueue, remoteStore, maxAttempts).runCountQuery(query); } /** Called by FirestoreClient to notify us of a new remote event. */ diff --git a/firebase-firestore/src/test/java/com/google/firebase/firestore/AggregateQueryTest.java b/firebase-firestore/src/test/java/com/google/firebase/firestore/AggregateQueryTest.java new file mode 100644 index 00000000000..fd7c9764517 --- /dev/null +++ b/firebase-firestore/src/test/java/com/google/firebase/firestore/AggregateQueryTest.java @@ -0,0 +1,34 @@ +// Copyright 2022 Google LLC +// +// 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.firestore; + +import static com.google.firebase.firestore.testutil.Assert.assertThrows; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; + +@RunWith(RobolectricTestRunner.class) +@Config(manifest = Config.NONE) +public class AggregateQueryTest { + + @Test + public void testSourceMustNotBeNull() { + assertThrows( + NullPointerException.class, + () -> TestUtil.collectionReference("foo/bar/baz").count().get(null)); + } +} From 89dc144d5acecc4ee1ef51e0a25d9c6470947811 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Tue, 19 Jul 2022 21:24:50 -0400 Subject: [PATCH 41/48] More --- .../google/firebase/firestore/CountTest.java | 53 ++++++++++++++++--- .../firebase/firestore/AggregateQuery.java | 7 +-- .../firestore/AggregateQuerySnapshot.java | 25 +++++---- .../firebase/firestore/AggregateSource.java | 2 +- ...unner.java => OnlineCountQueryRunner.java} | 25 ++++++--- .../firebase/firestore/core/SyncEngine.java | 2 +- 6 files changed, 86 insertions(+), 28 deletions(-) rename firebase-firestore/src/main/java/com/google/firebase/firestore/core/{OnlineQueryRunner.java => OnlineCountQueryRunner.java} (71%) diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index a1b1d698536..e6905b4cca8 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -43,23 +43,30 @@ public void tearDown() { public void testCountQueryEquals() { CollectionReference coll1 = testCollection("foo"); CollectionReference coll1_same = coll1.firestore.collection(coll1.getPath()); - Query query1 = coll1.document("bar").collection("baz").whereEqualTo("a", 1).limit(100); - Query query1_same = coll1.document("bar").collection("baz").whereEqualTo("a", 1).limit(100); - Query query2 = coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c"); - Query query2_same = coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c"); + AggregateQuery query1 = coll1.count(); + AggregateQuery query1_same = coll1_same.count(); + AggregateQuery query2 = + coll1.document("bar").collection("baz").whereEqualTo("a", 1).limit(100).count(); + AggregateQuery query2_same = + coll1.document("bar").collection("baz").whereEqualTo("a", 1).limit(100).count(); + AggregateQuery query3 = + coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c").count(); + AggregateQuery query3_same = + coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c").count(); - assertEquals(coll1, coll1_same); assertEquals(query1, query1_same); assertEquals(query2, query2_same); + assertEquals(query3, query3_same); - assertEquals(coll1.hashCode(), coll1_same.hashCode()); assertEquals(query1.hashCode(), query1_same.hashCode()); assertEquals(query2.hashCode(), query2_same.hashCode()); + assertEquals(query3.hashCode(), query3_same.hashCode()); - assertNotEquals(coll1, query1); + assertNotEquals(null, query1); assertNotEquals(query1, query2); - assertNotEquals(coll1.hashCode(), query1.hashCode()); + assertNotEquals(query2, query3); assertNotEquals(query1.hashCode(), query2.hashCode()); + assertNotEquals(query2.hashCode(), query3.hashCode()); } @Test @@ -90,6 +97,36 @@ public void testCanRunCountWithFilters() { assertEquals(Long.valueOf(1), snapshot.getCount()); } + @Test + public void testSnapshotEquals() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + + AggregateQuerySnapshot snapshot1 = + waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER_DIRECT)); + AggregateQuerySnapshot snapshot1_same = + waitFor(collection.whereEqualTo("k", "b").count().get(AggregateSource.SERVER_DIRECT)); + + AggregateQuerySnapshot snapshot2 = + waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER_DIRECT)); + waitFor(collection.document("d").set(map("k", "a"))); + AggregateQuerySnapshot snapshot2_different = + waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER_DIRECT)); + + assertEquals(snapshot1, snapshot1_same); + assertEquals(snapshot1.hashCode(), snapshot1_same.hashCode()); + assertEquals(snapshot1.getQuery(), collection.whereEqualTo("k", "b").count()); + + assertNotEquals(snapshot1, snapshot2); + assertNotEquals(snapshot1.hashCode(), snapshot2.hashCode()); + assertNotEquals(snapshot2, snapshot2_different); + assertNotEquals(snapshot2.hashCode(), snapshot2_different.hashCode()); + } + @Test public void testCanRunCollectionGroupQuery() { FirebaseFirestore db = testFirestore(); diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 06651af6673..35b70286f48 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -30,6 +30,7 @@ * code that does so. */ public class AggregateQuery { + private static final int DEFAULT_ATTEMPTS = 2; // The base query. private final Query query; @@ -55,7 +56,7 @@ public Query getQuery() { */ @NonNull public Task get(@NonNull AggregateSource source) { - return get(source, 1); + return get(source, DEFAULT_ATTEMPTS); } @VisibleForTesting @@ -70,7 +71,7 @@ Task get(@NonNull AggregateSource source, int maxAttempt Executors.DIRECT_EXECUTOR, (task) -> { if (task.isSuccessful()) { - tcs.setResult(new AggregateQuerySnapshot(task.getResult())); + tcs.setResult(new AggregateQuerySnapshot(this, task.getResult())); } else { tcs.setException(task.getException()); } @@ -95,6 +96,6 @@ public int hashCode() { @Override public String toString() { - return "AggregateQuery{" + "query=" + query + '}'; + return "AggregateQuery{" + "query=" + query.toString() + '}'; } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 31be51457fc..0bd0ed73372 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -14,6 +14,9 @@ package com.google.firebase.firestore; +import static com.google.firebase.firestore.util.Preconditions.checkNotNull; + +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import java.util.Objects; @@ -27,11 +30,20 @@ public class AggregateQuerySnapshot { private final long count; + private final AggregateQuery query; - AggregateQuerySnapshot(long count) { + AggregateQuerySnapshot(@NonNull AggregateQuery query, long count) { + checkNotNull(query); + this.query = query; this.count = count; } + /** @return The original {@link AggregateQuery} this snapshot is a result of. */ + @NonNull + public AggregateQuery getQuery() { + return query; + } + /** * @return The result of a document count aggregation. Returns null if no count aggregation is * available in the result. @@ -45,17 +57,12 @@ public Long getCount() { public boolean equals(Object o) { if (this == o) return true; if (o == null || !(o instanceof AggregateQuerySnapshot)) return false; - AggregateQuerySnapshot that = (AggregateQuerySnapshot) o; - return count == that.count; + AggregateQuerySnapshot snapshot = (AggregateQuerySnapshot) o; + return count == snapshot.count && query.equals(snapshot.query); } @Override public int hashCode() { - return Objects.hash(count); - } - - @Override - public String toString() { - return "AggregateQuerySnapshot{" + "count=" + count + '}'; + return Objects.hash(count, query); } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java index fa1c4bed6c4..79bf4214828 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -14,7 +14,7 @@ package com.google.firebase.firestore; -/** Configures the behavior of {@code get()} calls on {@link AggregateQuery}. */ +/** Configures the behavior of {@code get()} calls on {@link AggregateQuery#get}. */ public enum AggregateSource { /** * Reach to the Firestore backend and surface the result verbatim, that is no local documents or diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java similarity index 71% rename from firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java rename to firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java index a5488f97138..4ecd4f375ae 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineQueryRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java @@ -14,6 +14,7 @@ package com.google.firebase.firestore.core; +import static com.google.firebase.firestore.util.Assert.hardAssert; import static com.google.firebase.firestore.util.Util.isRetryableBackendError; import com.google.android.gms.tasks.OnCompleteListener; @@ -23,25 +24,37 @@ import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.ExponentialBackoff; -public final class OnlineQueryRunner { +/** + * This class creates a count query from a base query, and run the query directly against Firestore + * DBE, by passing Watch and local cache processing. + * + *

NOTE: Eventually, when there are more queries need online-only support, we can modify this + * class to take the actual queries (count, sum, min, max, etc), instead of limiting this to running + * count queries only. + */ +public final class OnlineCountQueryRunner { private final AsyncQueue asyncQueue; private final RemoteStore remoteStore; private int attemptsRemaining; private final ExponentialBackoff backoff; + private final Query baseQuery; + private final TaskCompletionSource taskSource; - public OnlineQueryRunner( - Query query, AsyncQueue asyncQueue, RemoteStore remoteStore, int maxAttempts) { + public OnlineCountQueryRunner( + Query baseQuery, AsyncQueue asyncQueue, RemoteStore remoteStore, int maxAttempts) { backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_ONLINE_QUERY); this.asyncQueue = asyncQueue; this.remoteStore = remoteStore; this.attemptsRemaining = maxAttempts; + this.baseQuery = baseQuery; + taskSource = new TaskCompletionSource<>(); } - public Task runCountQuery(Query query) { - TaskCompletionSource taskSource = new TaskCompletionSource<>(); - runWithBackoff(query, taskSource); + public Task run() { + hardAssert(!taskSource.getTask().isComplete(), "run() can only be called once."); + runWithBackoff(baseQuery, taskSource); return taskSource.getTask(); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 6a5023d921d..88e15471187 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -315,7 +315,7 @@ public Task transaction( } public Task runCountQuery(AsyncQueue asyncQueue, Query query, int maxAttempts) { - return new OnlineQueryRunner(query, asyncQueue, remoteStore, maxAttempts).runCountQuery(query); + return new OnlineCountQueryRunner(query, asyncQueue, remoteStore, maxAttempts).run(); } /** Called by FirestoreClient to notify us of a new remote event. */ From 2dff823aedc430cd84cfb08449275f569a8547f0 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Wed, 27 Jul 2022 10:54:32 -0400 Subject: [PATCH 42/48] More feedback --- .../google/firebase/firestore/CountTest.java | 25 +++++++++++-------- .../firebase/firestore/AggregateQuery.java | 5 ---- .../firebase/firestore/AggregateSource.java | 2 +- .../core/OnlineCountQueryRunner.java | 7 ++++-- 4 files changed, 21 insertions(+), 18 deletions(-) diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index e6905b4cca8..bc7f04bd961 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -23,7 +23,9 @@ import static org.hamcrest.CoreMatchers.instanceOf; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.firebase.firestore.testutil.IntegrationTestUtil; @@ -54,17 +56,18 @@ public void testCountQueryEquals() { AggregateQuery query3_same = coll1.document("bar").collection("baz").whereEqualTo("b", 1).orderBy("c").count(); - assertEquals(query1, query1_same); - assertEquals(query2, query2_same); - assertEquals(query3, query3_same); + assertTrue(query1.equals(query1_same)); + assertTrue(query2.equals(query2_same)); + assertTrue(query3.equals(query3_same)); assertEquals(query1.hashCode(), query1_same.hashCode()); assertEquals(query2.hashCode(), query2_same.hashCode()); assertEquals(query3.hashCode(), query3_same.hashCode()); - assertNotEquals(null, query1); - assertNotEquals(query1, query2); - assertNotEquals(query2, query3); + assertFalse(query1.equals(null)); + assertFalse(query1.equals("string")); + assertFalse(query1.equals(query2)); + assertFalse(query2.equals(query3)); assertNotEquals(query1.hashCode(), query2.hashCode()); assertNotEquals(query2.hashCode(), query3.hashCode()); } @@ -117,13 +120,15 @@ public void testSnapshotEquals() { AggregateQuerySnapshot snapshot2_different = waitFor(collection.whereEqualTo("k", "a").count().get(AggregateSource.SERVER_DIRECT)); - assertEquals(snapshot1, snapshot1_same); + assertTrue(snapshot1.equals(snapshot1_same)); assertEquals(snapshot1.hashCode(), snapshot1_same.hashCode()); - assertEquals(snapshot1.getQuery(), collection.whereEqualTo("k", "b").count()); + assertTrue(snapshot1.getQuery().equals(collection.whereEqualTo("k", "b").count())); - assertNotEquals(snapshot1, snapshot2); + assertFalse(snapshot1.equals(null)); + assertFalse(snapshot1.equals("string")); + assertFalse(snapshot1.equals(snapshot2)); assertNotEquals(snapshot1.hashCode(), snapshot2.hashCode()); - assertNotEquals(snapshot2, snapshot2_different); + assertFalse(snapshot2.equals(snapshot2_different)); assertNotEquals(snapshot2.hashCode(), snapshot2_different.hashCode()); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 35b70286f48..328ca09dbf1 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -93,9 +93,4 @@ public boolean equals(Object o) { public int hashCode() { return query.hashCode(); } - - @Override - public String toString() { - return "AggregateQuery{" + "query=" + query.toString() + '}'; - } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java index 79bf4214828..5f66bcdf7f9 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -14,7 +14,7 @@ package com.google.firebase.firestore; -/** Configures the behavior of {@code get()} calls on {@link AggregateQuery#get}. */ +/** Configures the behavior of {@link AggregateQuery#get}. */ public enum AggregateSource { /** * Reach to the Firestore backend and surface the result verbatim, that is no local documents or diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java index 4ecd4f375ae..c7698a10c4a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java @@ -23,10 +23,11 @@ import com.google.firebase.firestore.remote.RemoteStore; import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.ExponentialBackoff; +import java.util.concurrent.atomic.AtomicBoolean; /** * This class creates a count query from a base query, and run the query directly against Firestore - * DBE, by passing Watch and local cache processing. + * DBE, bypassing Watch and local cache processing. * *

NOTE: Eventually, when there are more queries need online-only support, we can modify this * class to take the actual queries (count, sum, min, max, etc), instead of limiting this to running @@ -41,6 +42,7 @@ public final class OnlineCountQueryRunner { private final ExponentialBackoff backoff; private final Query baseQuery; private final TaskCompletionSource taskSource; + private final AtomicBoolean hasRun; public OnlineCountQueryRunner( Query baseQuery, AsyncQueue asyncQueue, RemoteStore remoteStore, int maxAttempts) { @@ -50,10 +52,11 @@ public OnlineCountQueryRunner( this.attemptsRemaining = maxAttempts; this.baseQuery = baseQuery; taskSource = new TaskCompletionSource<>(); + hasRun = new AtomicBoolean(false); } public Task run() { - hardAssert(!taskSource.getTask().isComplete(), "run() can only be called once."); + hardAssert(!hasRun.getAndSet(true), "run() can only be called once."); runWithBackoff(baseQuery, taskSource); return taskSource.getTask(); } From d6e7098390e8615df25ca94290665ce2ba63b88d Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 15 Aug 2022 11:25:11 -0400 Subject: [PATCH 43/48] Remove retry --- .../google/firebase/firestore/CountTest.java | 18 ---- .../firebase/firestore/AggregateQuery.java | 10 +-- .../firestore/core/FirestoreClient.java | 7 +- .../core/OnlineCountQueryRunner.java | 90 ------------------- .../firebase/firestore/core/SyncEngine.java | 4 +- .../firebase/firestore/remote/Datastore.java | 10 ++- .../firestore/remote/RemoteStore.java | 15 ++-- 7 files changed, 20 insertions(+), 134 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index bc7f04bd961..ac573bebe09 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -233,22 +233,4 @@ public void testFailWithoutNetwork() { waitFor(collection.count().get(AggregateSource.SERVER_DIRECT)); assertEquals(Long.valueOf(3), snapshot.getCount()); } - - @Test - public void testExponentialBackoffWorks() { - CollectionReference collection = - testCollectionWithDocs( - map( - "a", map("k", "a"), - "b", map("k", "b"), - "c", map("k", "c"))); - waitFor(collection.getFirestore().disableNetwork()); - - Exception e = - waitForException( - collection.count().get(AggregateSource.SERVER_DIRECT, /* maxAttempts */ 2)); - assertThat(e, instanceOf(FirebaseFirestoreException.class)); - assertEquals( - FirebaseFirestoreException.Code.UNAVAILABLE, ((FirebaseFirestoreException) e).getCode()); - } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 328ca09dbf1..730bbbd821c 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -15,7 +15,6 @@ package com.google.firebase.firestore; import androidx.annotation.NonNull; -import androidx.annotation.VisibleForTesting; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.firestore.util.Executors; @@ -30,8 +29,6 @@ * code that does so. */ public class AggregateQuery { - private static final int DEFAULT_ATTEMPTS = 2; - // The base query. private final Query query; @@ -56,17 +53,12 @@ public Query getQuery() { */ @NonNull public Task get(@NonNull AggregateSource source) { - return get(source, DEFAULT_ATTEMPTS); - } - - @VisibleForTesting - Task get(@NonNull AggregateSource source, int maxAttempts) { Preconditions.checkNotNull(source, "AggregateSource must not be null"); TaskCompletionSource tcs = new TaskCompletionSource<>(); query .firestore .getClient() - .runCountQuery(query.query, maxAttempts) + .runCountQuery(query.query) .continueWith( Executors.DIRECT_EXECUTOR, (task) -> { diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index d39191b76f7..a4a5e3dbdb1 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -237,10 +237,11 @@ public Task transaction( () -> syncEngine.transaction(asyncQueue, options, updateFunction)); } - public Task runCountQuery(Query query, int maxAttempts) { + public Task runCountQuery(Query query) { this.verifyNotTerminated(); - return AsyncQueue.callTask( - asyncQueue.getExecutor(), () -> syncEngine.runCountQuery(asyncQueue, query, maxAttempts)); + final TaskCompletionSource result = new TaskCompletionSource<>(); + asyncQueue.enqueue(() -> syncEngine.runCountQuery(query, result)); + return result.getTask(); } /** diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java deleted file mode 100644 index c7698a10c4a..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/OnlineCountQueryRunner.java +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore.core; - -import static com.google.firebase.firestore.util.Assert.hardAssert; -import static com.google.firebase.firestore.util.Util.isRetryableBackendError; - -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.TaskCompletionSource; -import com.google.firebase.firestore.remote.RemoteStore; -import com.google.firebase.firestore.util.AsyncQueue; -import com.google.firebase.firestore.util.ExponentialBackoff; -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * This class creates a count query from a base query, and run the query directly against Firestore - * DBE, bypassing Watch and local cache processing. - * - *

NOTE: Eventually, when there are more queries need online-only support, we can modify this - * class to take the actual queries (count, sum, min, max, etc), instead of limiting this to running - * count queries only. - */ -public final class OnlineCountQueryRunner { - private final AsyncQueue asyncQueue; - private final RemoteStore remoteStore; - - private int attemptsRemaining; - - private final ExponentialBackoff backoff; - private final Query baseQuery; - private final TaskCompletionSource taskSource; - private final AtomicBoolean hasRun; - - public OnlineCountQueryRunner( - Query baseQuery, AsyncQueue asyncQueue, RemoteStore remoteStore, int maxAttempts) { - backoff = new ExponentialBackoff(asyncQueue, AsyncQueue.TimerId.RETRY_ONLINE_QUERY); - this.asyncQueue = asyncQueue; - this.remoteStore = remoteStore; - this.attemptsRemaining = maxAttempts; - this.baseQuery = baseQuery; - taskSource = new TaskCompletionSource<>(); - hasRun = new AtomicBoolean(false); - } - - public Task run() { - hardAssert(!hasRun.getAndSet(true), "run() can only be called once."); - runWithBackoff(baseQuery, taskSource); - return taskSource.getTask(); - } - - private void runWithBackoff(Query query, TaskCompletionSource taskSource) { - attemptsRemaining -= 1; - backoff.backoffAndRun( - () -> { - remoteStore - .runCountQuery(query) - .addOnCompleteListener( - asyncQueue.getExecutor(), - (OnCompleteListener) - task -> { - if (task.isSuccessful()) { - taskSource.setResult(task.getResult()); - } else { - handleError(task, query, taskSource); - } - }); - }); - } - - private void handleError(Task result, Query query, TaskCompletionSource taskSource) { - if (attemptsRemaining > 0 && isRetryableBackendError(result.getException())) { - runWithBackoff(query, taskSource); - } else { - taskSource.setException(result.getException()); - } - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 88e15471187..215c1bc5893 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -314,8 +314,8 @@ public Task transaction( return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } - public Task runCountQuery(AsyncQueue asyncQueue, Query query, int maxAttempts) { - return new OnlineCountQueryRunner(query, asyncQueue, remoteStore, maxAttempts).run(); + public void runCountQuery(Query query, TaskCompletionSource result) { + remoteStore.runCountQuery(query, result); } /** Called by FirestoreClient to notify us of a new remote event. */ diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index c77a8bf5f5e..f2904e34dd4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -222,7 +222,7 @@ public void onClose(Status status) { return completionSource.getTask(); } - public Task runCountQuery(Query query) { + public void runCountQuery(Query query, TaskCompletionSource result) { com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = serializer.encodeQueryTarget(query.toTarget()); @@ -240,7 +240,7 @@ public Task runCountQuery(Query query) { request.setParent(encodedQueryTarget.getParent()); request.setStructuredAggregationQuery(structuredAggregationQuery); - return channel + channel .runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) .continueWith( workerQueue.getExecutor(), @@ -251,8 +251,9 @@ public Task runCountQuery(Query query) { == FirebaseFirestoreException.Code.UNAUTHENTICATED) { channel.invalidateToken(); } - throw task.getException(); + result.setException(task.getException()); } + RunAggregationQueryResponse response = task.getResult(); AggregationResult aggregationResult = response.getResult(); @@ -265,7 +266,8 @@ public Task runCountQuery(Query query) { hardAssert( countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); - return countValue.getIntegerValue(); + result.setResult(countValue.getIntegerValue()); + return null; }); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index dd75609d223..ef583fe9fed 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -18,8 +18,7 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.google.android.gms.tasks.Task; -import com.google.android.gms.tasks.Tasks; +import com.google.android.gms.tasks.TaskCompletionSource; import com.google.firebase.database.collection.ImmutableSortedSet; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.core.OnlineState; @@ -752,13 +751,13 @@ public TargetData getTargetDataForTarget(int targetId) { return this.listenTargets.get(targetId); } - public Task runCountQuery(Query query) { + public void runCountQuery(Query query, TaskCompletionSource result) { if (canUseNetwork()) { - return datastore.runCountQuery(query); + datastore.runCountQuery(query, result); + } else { + result.setException( + new FirebaseFirestoreException( + "Failed to get result from server.", FirebaseFirestoreException.Code.UNAVAILABLE)); } - - return Tasks.forException( - new FirebaseFirestoreException( - "Failed to get result from server.", FirebaseFirestoreException.Code.UNAVAILABLE)); } } From 419f3fe7c3ae077ab8a81e67d978aa6903f76772 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 15 Aug 2022 12:04:13 -0400 Subject: [PATCH 44/48] Fix CI --- firebase-firestore/api.txt | 1 + .../com/google/firebase/firestore/core/FirestoreClient.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index d0b225013b7..b2bb627a317 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -26,6 +26,7 @@ package com.google.firebase.firestore { public class AggregateQuerySnapshot { method @Nullable public Long getCount(); + method @NonNull public com.google.firebase.firestore.AggregateQuery getQuery(); } public enum AggregateSource { diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index a4a5e3dbdb1..8488db07043 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -240,7 +240,7 @@ public Task transaction( public Task runCountQuery(Query query) { this.verifyNotTerminated(); final TaskCompletionSource result = new TaskCompletionSource<>(); - asyncQueue.enqueue(() -> syncEngine.runCountQuery(query, result)); + asyncQueue.enqueueAndForget(() -> syncEngine.runCountQuery(query, result)); return result.getTask(); } From ae618b411763597db6c461186345e058be85da91 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Sun, 21 Aug 2022 21:33:03 -0400 Subject: [PATCH 45/48] Hide this feature for now --- .../main/java/com/google/firebase/firestore/AggregateQuery.java | 2 +- .../com/google/firebase/firestore/AggregateQuerySnapshot.java | 2 +- .../java/com/google/firebase/firestore/AggregateSource.java | 2 +- .../src/main/java/com/google/firebase/firestore/Query.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index 730bbbd821c..ce47c12ca21 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -28,7 +28,7 @@ * in test mocks. Subclassing is not supported in production code and new SDK releases may break * code that does so. */ -public class AggregateQuery { +class AggregateQuery { // The base query. private final Query query; diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 0bd0ed73372..877456bf2e4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -27,7 +27,7 @@ * in test mocks. Subclassing is not supported in production code and new SDK releases may break * code that does so. */ -public class AggregateQuerySnapshot { +class AggregateQuerySnapshot { private final long count; private final AggregateQuery query; diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java index 5f66bcdf7f9..afb235df184 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateSource.java @@ -15,7 +15,7 @@ package com.google.firebase.firestore; /** Configures the behavior of {@link AggregateQuery#get}. */ -public enum AggregateSource { +enum AggregateSource { /** * Reach to the Firestore backend and surface the result verbatim, that is no local documents or * mutations in the SDK cache will be included in the surfaced result. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index 9f462dc39c9..6918932fb8c 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1230,7 +1230,7 @@ private void validateHasExplicitOrderByForLimitToLast() { * the result set of this query. */ @NonNull - public AggregateQuery count() { + AggregateQuery count() { return new AggregateQuery(this, AggregateField.count()); } From 3f3cb57d28b84a41e51d2c3cd73bc1951ef69303 Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Sun, 21 Aug 2022 21:48:17 -0400 Subject: [PATCH 46/48] Update API --- firebase-firestore/api.txt | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/firebase-firestore/api.txt b/firebase-firestore/api.txt index b2bb627a317..ab8b3f4192c 100644 --- a/firebase-firestore/api.txt +++ b/firebase-firestore/api.txt @@ -19,20 +19,6 @@ package com.google.firebase { package com.google.firebase.firestore { - public class AggregateQuery { - method @NonNull public com.google.android.gms.tasks.Task get(@NonNull com.google.firebase.firestore.AggregateSource); - method @NonNull public com.google.firebase.firestore.Query getQuery(); - } - - public class AggregateQuerySnapshot { - method @Nullable public Long getCount(); - method @NonNull public com.google.firebase.firestore.AggregateQuery getQuery(); - } - - public enum AggregateSource { - enum_constant public static final com.google.firebase.firestore.AggregateSource SERVER_DIRECT; - } - public class Blob implements java.lang.Comparable { method public int compareTo(@NonNull com.google.firebase.firestore.Blob); method @NonNull public static com.google.firebase.firestore.Blob fromBytes(@NonNull byte[]); @@ -304,7 +290,6 @@ package com.google.firebase.firestore { method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull java.util.concurrent.Executor, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); method @NonNull public com.google.firebase.firestore.ListenerRegistration addSnapshotListener(@NonNull android.app.Activity, @NonNull com.google.firebase.firestore.MetadataChanges, @NonNull com.google.firebase.firestore.EventListener); - method @NonNull public com.google.firebase.firestore.AggregateQuery count(); method @NonNull public com.google.firebase.firestore.Query endAt(@NonNull com.google.firebase.firestore.DocumentSnapshot); method @NonNull public com.google.firebase.firestore.Query endAt(java.lang.Object...); method @NonNull public com.google.firebase.firestore.Query endBefore(@NonNull com.google.firebase.firestore.DocumentSnapshot); From 2dc20b580a295c03e12a55b52b4e44101e9bd00d Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Mon, 22 Aug 2022 09:20:49 -0400 Subject: [PATCH 47/48] Only run tests with emulator --- .../java/com/google/firebase/firestore/CountTest.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index ac573bebe09..7fc39c45e57 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -30,12 +30,19 @@ import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.firebase.firestore.testutil.IntegrationTestUtil; import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @RunWith(AndroidJUnit4.class) public class CountTest { + @Before + public void setUp() { + // TODO(b/243368243): Remove this once backend is ready to support count. + org.junit.Assume.assumeTrue(BuildConfig.USE_EMULATOR_FOR_TESTS); + } + @After public void tearDown() { IntegrationTestUtil.tearDown(); From 7f090e267532fb67a486eba40a919310183b992a Mon Sep 17 00:00:00 2001 From: Wu-Hui Date: Fri, 26 Aug 2022 13:45:45 -0400 Subject: [PATCH 48/48] More feedback --- .../google/firebase/firestore/CountTest.java | 29 ++++++++++++++ .../firebase/firestore/AggregateField.java | 39 ------------------- .../firebase/firestore/AggregateQuery.java | 7 +--- .../firestore/AggregateQuerySnapshot.java | 2 +- .../com/google/firebase/firestore/Query.java | 2 +- .../firestore/core/FirestoreClient.java | 7 +++- .../firebase/firestore/core/SyncEngine.java | 4 +- .../firestore/core/TransactionRunner.java | 18 +++++++-- .../firebase/firestore/remote/Datastore.java | 9 ++--- .../firestore/remote/RemoteStore.java | 9 +++-- .../firebase/firestore/util/AsyncQueue.java | 5 --- .../google/firebase/firestore/util/Util.java | 13 ------- 12 files changed, 65 insertions(+), 79 deletions(-) delete mode 100644 firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java diff --git a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java index 7fc39c45e57..5dffb4a70c4 100644 --- a/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java +++ b/firebase-firestore/src/androidTest/java/com/google/firebase/firestore/CountTest.java @@ -107,6 +107,35 @@ public void testCanRunCountWithFilters() { assertEquals(Long.valueOf(1), snapshot.getCount()); } + @Test + public void testCanRunCountWithOrderBy() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"), + "d", map("absent", "d"))); + + AggregateQuerySnapshot snapshot = + waitFor(collection.orderBy("k").count().get(AggregateSource.SERVER_DIRECT)); + // "d" is filtered out because it is ordered by "k". + assertEquals(Long.valueOf(3), snapshot.getCount()); + } + + @Test + public void testTerminateDoesNotCrashWithFlyingCountQuery() { + CollectionReference collection = + testCollectionWithDocs( + map( + "a", map("k", "a"), + "b", map("k", "b"), + "c", map("k", "c"))); + + collection.orderBy("k").count().get(AggregateSource.SERVER_DIRECT); + waitFor(collection.firestore.terminate()); + } + @Test public void testSnapshotEquals() { CollectionReference collection = diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java deleted file mode 100644 index 0d044010c3a..00000000000 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateField.java +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright 2022 Google LLC -// -// 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.firestore; - -import androidx.annotation.NonNull; - -/** - * Represents which field to aggregate on for a {@link AggregateQuery}, and what type of - * aggregations to perform. - */ -abstract class AggregateField { - - private AggregateField() {} - - /** - * Returns a {@link CountAggregateField} which counts the number of documents matching the {@link - * AggregateQuery}. - */ - @NonNull - public static CountAggregateField count() { - return new CountAggregateField(); - } - - static final class CountAggregateField extends AggregateField { - CountAggregateField() {} - } -} diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java index ce47c12ca21..179bc6e1b2d 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuery.java @@ -32,11 +32,8 @@ class AggregateQuery { // The base query. private final Query query; - AggregateQuery(@NonNull Query query, @NonNull AggregateField aggregateField) { + AggregateQuery(@NonNull Query query) { this.query = query; - if (!(aggregateField instanceof AggregateField.CountAggregateField)) { - throw new IllegalArgumentException("unsupported aggregateField: " + aggregateField); - } } /** Returns the base {@link Query} for this aggregate query. */ @@ -76,7 +73,7 @@ public Task get(@NonNull AggregateSource source) { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || !(o instanceof AggregateQuery)) return false; + if (!(o instanceof AggregateQuery)) return false; AggregateQuery that = (AggregateQuery) o; return query.equals(that.query); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java index 877456bf2e4..0ec69134471 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/AggregateQuerySnapshot.java @@ -56,7 +56,7 @@ public Long getCount() { @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || !(o instanceof AggregateQuerySnapshot)) return false; + if (!(o instanceof AggregateQuerySnapshot)) return false; AggregateQuerySnapshot snapshot = (AggregateQuerySnapshot) o; return count == snapshot.count && query.equals(snapshot.query); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java index 6918932fb8c..125b93c53a4 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/Query.java @@ -1231,7 +1231,7 @@ private void validateHasExplicitOrderByForLimitToLast() { */ @NonNull AggregateQuery count() { - return new AggregateQuery(this, AggregateField.count()); + return new AggregateQuery(this); } @Override diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java index 8488db07043..e75868b0ab6 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/FirestoreClient.java @@ -240,7 +240,12 @@ public Task transaction( public Task runCountQuery(Query query) { this.verifyNotTerminated(); final TaskCompletionSource result = new TaskCompletionSource<>(); - asyncQueue.enqueueAndForget(() -> syncEngine.runCountQuery(query, result)); + asyncQueue.enqueueAndForget( + () -> + syncEngine + .runCountQuery(query) + .addOnSuccessListener(count -> result.setResult(count)) + .addOnFailureListener(e -> result.setException(e))); return result.getTask(); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java index 215c1bc5893..7513e2b9f27 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/SyncEngine.java @@ -314,8 +314,8 @@ public Task transaction( return new TransactionRunner(asyncQueue, remoteStore, options, updateFunction).run(); } - public void runCountQuery(Query query, TaskCompletionSource result) { - remoteStore.runCountQuery(query, result); + public Task runCountQuery(Query query) { + return remoteStore.runCountQuery(query); } /** Called by FirestoreClient to notify us of a new remote event. */ diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java index 4f972736ac8..70a249cc804 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/core/TransactionRunner.java @@ -14,12 +14,12 @@ package com.google.firebase.firestore.core; -import static com.google.firebase.firestore.util.Util.isRetryableBackendError; - import androidx.annotation.NonNull; import com.google.android.gms.tasks.Task; import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.TransactionOptions; +import com.google.firebase.firestore.remote.Datastore; import com.google.firebase.firestore.remote.RemoteStore; import com.google.firebase.firestore.util.AsyncQueue; import com.google.firebase.firestore.util.AsyncQueue.TimerId; @@ -86,10 +86,22 @@ private void runWithBackoff() { } private void handleTransactionError(Task task) { - if (attemptsRemaining > 0 && isRetryableBackendError(task.getException())) { + if (attemptsRemaining > 0 && isRetryableTransactionError(task.getException())) { runWithBackoff(); } else { taskSource.setException(task.getException()); } } + + private static boolean isRetryableTransactionError(Exception e) { + if (e instanceof FirebaseFirestoreException) { + // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and + // non-matching document versions with ABORTED. These errors should be retried. + FirebaseFirestoreException.Code code = ((FirebaseFirestoreException) e).getCode(); + return code == FirebaseFirestoreException.Code.ABORTED + || code == FirebaseFirestoreException.Code.FAILED_PRECONDITION + || !Datastore.isPermanentError(((FirebaseFirestoreException) e).getCode()); + } + return false; + } } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java index f2904e34dd4..110d56a58a3 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/Datastore.java @@ -222,7 +222,7 @@ public void onClose(Status status) { return completionSource.getTask(); } - public void runCountQuery(Query query, TaskCompletionSource result) { + public Task runCountQuery(Query query) { com.google.firestore.v1.Target.QueryTarget encodedQueryTarget = serializer.encodeQueryTarget(query.toTarget()); @@ -240,7 +240,7 @@ public void runCountQuery(Query query, TaskCompletionSource result) { request.setParent(encodedQueryTarget.getParent()); request.setStructuredAggregationQuery(structuredAggregationQuery); - channel + return channel .runRpc(FirestoreGrpc.getRunAggregationQueryMethod(), request.build()) .continueWith( workerQueue.getExecutor(), @@ -251,7 +251,7 @@ public void runCountQuery(Query query, TaskCompletionSource result) { == FirebaseFirestoreException.Code.UNAUTHENTICATED) { channel.invalidateToken(); } - result.setException(task.getException()); + throw task.getException(); } RunAggregationQueryResponse response = task.getResult(); @@ -266,8 +266,7 @@ public void runCountQuery(Query query, TaskCompletionSource result) { hardAssert( countValue.getValueTypeCase() == Value.ValueTypeCase.INTEGER_VALUE, "countValue.getValueTypeCase() == " + countValue.getValueTypeCase()); - result.setResult(countValue.getIntegerValue()); - return null; + return countValue.getIntegerValue(); }); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java index ef583fe9fed..3999f22a939 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/remote/RemoteStore.java @@ -18,7 +18,8 @@ import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import com.google.android.gms.tasks.TaskCompletionSource; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; import com.google.firebase.database.collection.ImmutableSortedSet; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.core.OnlineState; @@ -751,11 +752,11 @@ public TargetData getTargetDataForTarget(int targetId) { return this.listenTargets.get(targetId); } - public void runCountQuery(Query query, TaskCompletionSource result) { + public Task runCountQuery(Query query) { if (canUseNetwork()) { - datastore.runCountQuery(query, result); + return datastore.runCountQuery(query); } else { - result.setException( + return Tasks.forException( new FirebaseFirestoreException( "Failed to get result from server.", FirebaseFirestoreException.Code.UNAVAILABLE)); } diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java index 463ff9e64c6..c368022f07a 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/AsyncQueue.java @@ -77,11 +77,6 @@ public enum TimerId { * multiple of these may be in the queue at a given time. */ RETRY_TRANSACTION, - /** - * A timer used to retry queries sent to DFE. Since there can be multiple active queries, - * multiple of these may be in the queue at a given time. - */ - RETRY_ONLINE_QUERY, /** * A timer used to monitor when a connection attempt in gRPC is unsuccessful and retry * accordingly. diff --git a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java index be6a7c490c0..6591db805c1 100644 --- a/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java +++ b/firebase-firestore/src/main/java/com/google/firebase/firestore/util/Util.java @@ -22,7 +22,6 @@ import com.google.firebase.firestore.FieldPath; import com.google.firebase.firestore.FirebaseFirestoreException; import com.google.firebase.firestore.FirebaseFirestoreException.Code; -import com.google.firebase.firestore.remote.Datastore; import com.google.protobuf.ByteString; import io.grpc.Status; import io.grpc.StatusException; @@ -138,18 +137,6 @@ public static Exception convertThrowableToException(Throwable t) { } } - public static boolean isRetryableBackendError(Exception e) { - if (e instanceof FirebaseFirestoreException) { - // In transactions, the backend will fail outdated reads with FAILED_PRECONDITION and - // non-matching document versions with ABORTED. These errors should be retried. - FirebaseFirestoreException.Code code = ((FirebaseFirestoreException) e).getCode(); - return code == FirebaseFirestoreException.Code.ABORTED - || code == FirebaseFirestoreException.Code.FAILED_PRECONDITION - || !Datastore.isPermanentError(((FirebaseFirestoreException) e).getCode()); - } - return false; - } - private static final Continuation VOID_ERROR_TRANSFORMER = task -> { if (task.isSuccessful()) {