Skip to content

Commit 0177c4f

Browse files
authored
Merge 1a0cce3 into 981e6f1
2 parents 981e6f1 + 1a0cce3 commit 0177c4f

File tree

8 files changed

+281
-70
lines changed

8 files changed

+281
-70
lines changed

common/api-review/firestore.api.md

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,18 +17,17 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn
1717
[K in keyof T & string as `${Prefix}.${K}`]+?: T[K];
1818
};
1919

20-
// @public (undocumented)
21-
export class AggregateQuery<T = DocumentData> {
22-
// (undocumented)
23-
readonly query: Query<T>;
20+
// @public
21+
export class AggregateQuery {
22+
readonly query: Query<unknown>;
2423
// (undocumented)
2524
readonly type = "AggregateQuery";
2625
}
2726

2827
// @public (undocumented)
2928
export function aggregateQueryEqual(left: AggregateQuery, right: AggregateQuery): boolean;
3029

31-
// @public (undocumented)
30+
// @public
3231
export class AggregateQuerySnapshot {
3332
// (undocumented)
3433
getCount(): number | null;
@@ -93,8 +92,8 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por
9392
mockUserToken?: EmulatorMockTokenOptions | string;
9493
}): void;
9594

96-
// @public (undocumented)
97-
export function countQuery(query: Query): AggregateQuery;
95+
// @public
96+
export function countQuery(query: Query<unknown>): AggregateQuery;
9897

9998
// @public
10099
export function deleteDoc(reference: DocumentReference<unknown>): Promise<void>;

packages/firestore/src/lite-api/aggregate.ts

Lines changed: 56 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,40 +15,89 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { DocumentData, Query, queryEqual } from './reference';
18+
import { Value } from '../protos/firestore_proto_api';
19+
import { invokeRunAggregationQueryRpc } from '../remote/datastore';
20+
import { hardAssert } from '../util/assert';
21+
import { cast } from '../util/input_validation';
1922

20-
export class AggregateQuery<T = DocumentData> {
23+
import { getDatastore } from './components';
24+
import { Firestore } from './database';
25+
import { Query, queryEqual } from './reference';
26+
import { LiteUserDataWriter } from './reference_impl';
27+
28+
/**
29+
* An `AggregateQuery` computes some aggregation statistics from the result set of
30+
* a base `Query`.
31+
*/
32+
export class AggregateQuery {
2133
readonly type = 'AggregateQuery';
22-
readonly query: Query<T>;
34+
/**
35+
* The query on which you called `countQuery` in order to get this `AggregateQuery`.
36+
*/
37+
readonly query: Query<unknown>;
2338

2439
/** @hideconstructor */
25-
constructor(query: Query<T>) {
40+
constructor(query: Query<unknown>) {
2641
this.query = query;
2742
}
2843
}
2944

45+
/**
46+
* An `AggregateQuerySnapshot` contains results of a `AggregateQuery`.
47+
*/
3048
export class AggregateQuerySnapshot {
3149
readonly type = 'AggregateQuerySnapshot';
3250
readonly query: AggregateQuery;
3351

3452
/** @hideconstructor */
35-
constructor(query: AggregateQuery, readonly _count: number) {
53+
constructor(query: AggregateQuery, private readonly _count: number) {
3654
this.query = query;
3755
}
3856

57+
/**
58+
* @returns The result of a document count aggregation. Returns null if no count aggregation is
59+
* available in the result.
60+
*/
3961
getCount(): number | null {
4062
return this._count;
4163
}
4264
}
4365

44-
export function countQuery(query: Query): AggregateQuery {
66+
/**
67+
* Creates an `AggregateQuery` counting the number of documents matching this query.
68+
*
69+
* @returns An `AggregateQuery` object that can be used to count the number of documents in
70+
* the result set of this query.
71+
*/
72+
export function countQuery(query: Query<unknown>): AggregateQuery {
4573
return new AggregateQuery(query);
4674
}
4775

4876
export function getAggregateFromServerDirect(
4977
query: AggregateQuery
5078
): Promise<AggregateQuerySnapshot> {
51-
return Promise.resolve(new AggregateQuerySnapshot(query, 42));
79+
const firestore = cast(query.query.firestore, Firestore);
80+
const datastore = getDatastore(firestore);
81+
const userDataWriter = new LiteUserDataWriter(firestore);
82+
83+
return invokeRunAggregationQueryRpc(datastore, query).then(result => {
84+
hardAssert(
85+
result[0] !== undefined,
86+
'Aggregation fields are missing from result.'
87+
);
88+
89+
const counts = Object.entries(result[0])
90+
.filter(([key, value]) => key === 'count_alias')
91+
.map(([key, value]) => userDataWriter.convertValue(value as Value));
92+
93+
const count = counts[0];
94+
hardAssert(
95+
typeof count === 'number',
96+
'Count aggeragte field value is not a number: ' + count
97+
);
98+
99+
return Promise.resolve(new AggregateQuerySnapshot(query, count));
100+
});
52101
}
53102

54103
export function aggregateQueryEqual(

packages/firestore/src/protos/firestore_proto_api.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,32 @@ export declare namespace firestoreV1ApiClientInterfaces {
334334
readTime?: string;
335335
skippedResults?: number;
336336
}
337+
interface RunAggregationQueryRequest {
338+
parent?: string;
339+
structuredAggregationQuery?: StructuredAggregationQuery;
340+
transaction?: string;
341+
newTransaction?: TransactionOptions;
342+
readTime?: string;
343+
}
344+
interface RunAggregationQueryResponse {
345+
result?: AggregationResult;
346+
transaction?: string;
347+
readTime?: string;
348+
}
349+
interface AggregationResult {
350+
aggregateFields?: ApiClientObjectMap<Value>;
351+
}
352+
interface StructuredAggregationQuery {
353+
structuredQuery?: StructuredQuery;
354+
aggregations?: Aggregation[];
355+
}
356+
interface Aggregation {
357+
count?: Count;
358+
alias?: string;
359+
}
360+
interface Count {
361+
upTo?: number;
362+
}
337363
interface Status {
338364
code?: number;
339365
message?: string;
@@ -479,6 +505,10 @@ export declare type RunQueryRequest =
479505
firestoreV1ApiClientInterfaces.RunQueryRequest;
480506
export declare type RunQueryResponse =
481507
firestoreV1ApiClientInterfaces.RunQueryResponse;
508+
export declare type RunAggregationQueryRequest =
509+
firestoreV1ApiClientInterfaces.RunAggregationQueryRequest;
510+
export declare type RunAggregationQueryResponse =
511+
firestoreV1ApiClientInterfaces.RunAggregationQueryResponse;
482512
export declare type Status = firestoreV1ApiClientInterfaces.Status;
483513
export declare type StructuredQuery =
484514
firestoreV1ApiClientInterfaces.StructuredQuery;

packages/firestore/src/remote/datastore.ts

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,18 @@
1818
import { CredentialsProvider } from '../api/credentials';
1919
import { User } from '../auth/user';
2020
import { Query, queryToTarget } from '../core/query';
21+
import { AggregateQuery } from '../lite-api/aggregate';
2122
import { Document } from '../model/document';
2223
import { DocumentKey } from '../model/document_key';
2324
import { Mutation } from '../model/mutation';
2425
import {
2526
BatchGetDocumentsRequest as ProtoBatchGetDocumentsRequest,
2627
BatchGetDocumentsResponse as ProtoBatchGetDocumentsResponse,
28+
RunAggregationQueryRequest as ProtoRunAggregationQueryRequest,
29+
RunAggregationQueryResponse as ProtoRunAggregationQueryResponse,
2730
RunQueryRequest as ProtoRunQueryRequest,
28-
RunQueryResponse as ProtoRunQueryResponse
31+
RunQueryResponse as ProtoRunQueryResponse,
32+
Value as ProtoValue
2933
} from '../protos/firestore_proto_api';
3034
import { debugAssert, debugCast, hardAssert } from '../util/assert';
3135
import { AsyncQueue } from '../util/async_queue';
@@ -45,7 +49,8 @@ import {
4549
JsonProtoSerializer,
4650
toMutation,
4751
toName,
48-
toQueryTarget
52+
toQueryTarget,
53+
toRunAggregationQueryRequest
4954
} from './serializer';
5055

5156
/**
@@ -232,6 +237,29 @@ export async function invokeRunQueryRpc(
232237
);
233238
}
234239

240+
export async function invokeRunAggregationQueryRpc(
241+
datastore: Datastore,
242+
aggregateQuery: AggregateQuery
243+
): Promise<ProtoValue[]> {
244+
const datastoreImpl = debugCast(datastore, DatastoreImpl);
245+
const request = toRunAggregationQueryRequest(
246+
datastoreImpl.serializer,
247+
queryToTarget(aggregateQuery.query._query)
248+
);
249+
const response = await datastoreImpl.invokeStreamingRPC<
250+
ProtoRunAggregationQueryRequest,
251+
ProtoRunAggregationQueryResponse
252+
>('RunAggregationQuery', request.parent!, {
253+
structuredAggregationQuery: request.structuredAggregationQuery
254+
});
255+
return (
256+
response
257+
// Omit RunAggregationQueryResponse that only contain readTimes.
258+
.filter(proto => !!proto.result)
259+
.map(proto => proto.result!.aggregateFields!)
260+
);
261+
}
262+
235263
export function newPersistentWriteStream(
236264
datastore: Datastore,
237265
queue: AsyncQueue,

packages/firestore/src/remote/rest_connection.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const RPC_NAME_URL_MAPPING: StringMap = {};
3737
RPC_NAME_URL_MAPPING['BatchGetDocuments'] = 'batchGet';
3838
RPC_NAME_URL_MAPPING['Commit'] = 'commit';
3939
RPC_NAME_URL_MAPPING['RunQuery'] = 'runQuery';
40+
RPC_NAME_URL_MAPPING['RunAggregationQuery'] = 'runAggregationQuery';
4041

4142
const RPC_URL_VERSION = 'v1';
4243

@@ -78,7 +79,6 @@ export abstract class RestConnection implements Connection {
7879

7980
const headers = {};
8081
this.modifyHeadersForRequest(headers, authToken, appCheckToken);
81-
8282
return this.performRPCRequest<Req, Resp>(rpcName, url, headers, req).then(
8383
response => {
8484
logDebug(LOG_TAG, 'Received: ', response);

packages/firestore/src/remote/serializer.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ import {
7777
OrderDirection as ProtoOrderDirection,
7878
Precondition as ProtoPrecondition,
7979
QueryTarget as ProtoQueryTarget,
80+
RunAggregationQueryRequest as ProtoRunAggregationQueryRequest,
8081
Status as ProtoStatus,
8182
Target as ProtoTarget,
8283
TargetChangeTargetChangeType as ProtoTargetChangeTargetChangeType,
@@ -852,6 +853,26 @@ export function toQueryTarget(
852853
return result;
853854
}
854855

856+
export function toRunAggregationQueryRequest(
857+
serializer: JsonProtoSerializer,
858+
target: Target
859+
): ProtoRunAggregationQueryRequest {
860+
const queryTarget = toQueryTarget(serializer, target);
861+
862+
return {
863+
structuredAggregationQuery: {
864+
aggregations: [
865+
{
866+
count: {},
867+
alias: 'count_alias'
868+
}
869+
],
870+
structuredQuery: queryTarget.structuredQuery
871+
},
872+
parent: queryTarget.parent
873+
};
874+
}
875+
855876
export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query {
856877
let path = fromQueryPath(target.parent!);
857878

packages/firestore/test/integration/api/count.test.ts

Lines changed: 0 additions & 53 deletions
This file was deleted.

0 commit comments

Comments
 (0)