Skip to content

Commit 89c05fa

Browse files
committed
Initial implementation of sum and average support. Includes function for performing general aggregations.
1 parent d93f595 commit 89c05fa

File tree

18 files changed

+951
-190
lines changed

18 files changed

+951
-190
lines changed

common/api-review/firestore-lite.api.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn
1818
};
1919

2020
// @public
21-
export class AggregateField<T> {
22-
type: string;
21+
export class AggregateField<R> {
22+
constructor(aggregateType: AggregateType, methodName: string, field?: string | FieldPath);
23+
// (undocumented)
24+
readonly aggregateType: AggregateType;
25+
readonly type = "AggregateField";
2326
}
2427

2528
// @public
26-
export type AggregateFieldType = AggregateField<number>;
29+
export type AggregateFieldType = ReturnType<typeof count> | ReturnType<typeof sum> | ReturnType<typeof average>;
2730

2831
// @public
2932
export class AggregateQuerySnapshot<T extends AggregateSpec> {
@@ -46,12 +49,18 @@ export type AggregateSpecData<T extends AggregateSpec> = {
4649
[P in keyof T]: T[P] extends AggregateField<infer U> ? U : never;
4750
};
4851

52+
// @public
53+
export type AggregateType = 'avg' | 'count' | 'sum';
54+
4955
// @public
5056
export function arrayRemove(...elements: unknown[]): FieldValue;
5157

5258
// @public
5359
export function arrayUnion(...elements: unknown[]): FieldValue;
5460

61+
// @public
62+
export function average(field: string | FieldPath): AggregateField<number | null>;
63+
5564
// @public
5665
export class Bytes {
5766
static fromBase64String(base64: string): Bytes;
@@ -92,6 +101,9 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por
92101
mockUserToken?: EmulatorMockTokenOptions | string;
93102
}): void;
94103

104+
// @public
105+
export function count(): AggregateField<number>;
106+
95107
// @public
96108
export function deleteDoc(reference: DocumentReference<unknown>): Promise<void>;
97109

@@ -198,6 +210,9 @@ export class GeoPoint {
198210
};
199211
}
200212

213+
// @public
214+
export function getAggregate<T extends AggregateSpec>(query: Query<unknown>, aggregateSpec: T): Promise<AggregateQuerySnapshot<T>>;
215+
201216
// @public
202217
export function getCount(query: Query<unknown>): Promise<AggregateQuerySnapshot<{
203218
count: AggregateField<number>;
@@ -362,6 +377,9 @@ export function startAt(snapshot: DocumentSnapshot<unknown>): QueryStartAtConstr
362377
// @public
363378
export function startAt(...fieldValues: unknown[]): QueryStartAtConstraint;
364379

380+
// @public
381+
export function sum(field: string | FieldPath): AggregateField<number>;
382+
365383
// @public
366384
export function terminate(firestore: Firestore): Promise<void>;
367385

common/api-review/firestore.api.md

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn
1818
};
1919

2020
// @public
21-
export class AggregateField<T> {
22-
type: string;
21+
export class AggregateField<R> {
22+
constructor(aggregateType: AggregateType, methodName: string, field?: string | FieldPath);
23+
// (undocumented)
24+
readonly aggregateType: AggregateType;
25+
readonly type = "AggregateField";
2326
}
2427

2528
// @public
26-
export type AggregateFieldType = AggregateField<number>;
29+
export type AggregateFieldType = ReturnType<typeof count> | ReturnType<typeof sum> | ReturnType<typeof average>;
2730

2831
// @public
2932
export class AggregateQuerySnapshot<T extends AggregateSpec> {
@@ -46,12 +49,18 @@ export type AggregateSpecData<T extends AggregateSpec> = {
4649
[P in keyof T]: T[P] extends AggregateField<infer U> ? U : never;
4750
};
4851

52+
// @public
53+
export type AggregateType = 'avg' | 'count' | 'sum';
54+
4955
// @public
5056
export function arrayRemove(...elements: unknown[]): FieldValue;
5157

5258
// @public
5359
export function arrayUnion(...elements: unknown[]): FieldValue;
5460

61+
// @public
62+
export function average(field: string | FieldPath): AggregateField<number | null>;
63+
5564
// @public
5665
export class Bytes {
5766
static fromBase64String(base64: string): Bytes;
@@ -98,6 +107,9 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por
98107
mockUserToken?: EmulatorMockTokenOptions | string;
99108
}): void;
100109

110+
// @public
111+
export function count(): AggregateField<number>;
112+
101113
// @public
102114
export function deleteDoc(reference: DocumentReference<unknown>): Promise<void>;
103115

@@ -238,6 +250,9 @@ export class GeoPoint {
238250
};
239251
}
240252

253+
// @public
254+
export function getAggregateFromServer<T extends AggregateSpec>(query: Query<unknown>, aggregateSpec: T): Promise<AggregateQuerySnapshot<T>>;
255+
241256
// @public
242257
export function getCountFromServer(query: Query<unknown>): Promise<AggregateQuerySnapshot<{
243258
count: AggregateField<number>;
@@ -533,6 +548,9 @@ export function startAt(snapshot: DocumentSnapshot<unknown>): QueryStartAtConstr
533548
// @public
534549
export function startAt(...fieldValues: unknown[]): QueryStartAtConstraint;
535550

551+
// @public
552+
export function sum(field: string | FieldPath): AggregateField<number>;
553+
536554
// @public
537555
export type TaskState = 'Error' | 'Running' | 'Success';
538556

packages/firestore/lite/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,20 @@ registerFirestore();
2929

3030
export {
3131
aggregateQuerySnapshotEqual,
32-
getCount
32+
getCount,
33+
getAggregate,
34+
count,
35+
sum,
36+
average
3337
} from '../src/lite-api/aggregate';
3438

3539
export {
3640
AggregateField,
3741
AggregateFieldType,
3842
AggregateSpec,
3943
AggregateSpecData,
40-
AggregateQuerySnapshot
44+
AggregateQuerySnapshot,
45+
AggregateType
4146
} from '../src/lite-api/aggregate_types';
4247

4348
export { FirestoreSettings as Settings } from '../src/lite-api/settings';

packages/firestore/src/api.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717

1818
export {
1919
aggregateQuerySnapshotEqual,
20-
getCountFromServer
20+
getCountFromServer,
21+
getAggregateFromServer,
22+
count,
23+
sum,
24+
average
2125
} from './api/aggregate';
2226

2327
export {

packages/firestore/src/api/aggregate.ts

Lines changed: 86 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,18 +15,27 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { Query } from '../api';
19-
import { firestoreClientRunCountQuery } from '../core/firestore_client';
18+
import { AggregateSpec, Query } from '../api';
19+
import { AggregateImpl } from '../core/aggregate';
20+
import { firestoreClientRunAggregateQuery } from '../core/firestore_client';
21+
import { count } from '../lite-api/aggregate';
2022
import {
2123
AggregateField,
2224
AggregateQuerySnapshot
2325
} from '../lite-api/aggregate_types';
26+
import { ObjectValue } from '../model/object_value';
2427
import { cast } from '../util/input_validation';
28+
import { mapToArray } from '../util/obj';
2529

2630
import { ensureFirestoreConfigured, Firestore } from './database';
2731
import { ExpUserDataWriter } from './reference_impl';
2832

29-
export { aggregateQuerySnapshotEqual } from '../lite-api/aggregate';
33+
export {
34+
aggregateQuerySnapshotEqual,
35+
count,
36+
sum,
37+
average
38+
} from '../lite-api/aggregate';
3039

3140
/**
3241
* Calculates the number of documents in the result set of the given query,
@@ -54,6 +63,79 @@ export function getCountFromServer(
5463
): Promise<AggregateQuerySnapshot<{ count: AggregateField<number> }>> {
5564
const firestore = cast(query.firestore, Firestore);
5665
const client = ensureFirestoreConfigured(firestore);
66+
67+
const countQuerySpec: { count: AggregateField<number> } = {
68+
count: count()
69+
};
70+
71+
const internalAggregates = mapToArray(countQuerySpec, (aggregate, alias) => {
72+
return new AggregateImpl(
73+
alias,
74+
aggregate.aggregateType,
75+
aggregate._internalFieldPath
76+
);
77+
});
78+
79+
return firestoreClientRunAggregateQuery(
80+
client,
81+
query._query,
82+
internalAggregates
83+
).then(aggregateResult =>
84+
convertToAggregateQuerySnapshot(
85+
firestore,
86+
query,
87+
countQuerySpec,
88+
aggregateResult
89+
)
90+
);
91+
}
92+
93+
/**
94+
* TODO
95+
* @param query
96+
* @param aggregateSpec
97+
*/
98+
export function getAggregateFromServer<T extends AggregateSpec>(
99+
query: Query<unknown>,
100+
aggregateSpec: T
101+
): Promise<AggregateQuerySnapshot<T>> {
102+
const firestore = cast(query.firestore, Firestore);
103+
const client = ensureFirestoreConfigured(firestore);
104+
105+
const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => {
106+
return new AggregateImpl(
107+
alias,
108+
aggregate.aggregateType,
109+
aggregate._internalFieldPath
110+
);
111+
});
112+
113+
return firestoreClientRunAggregateQuery(
114+
client,
115+
query._query,
116+
internalAggregates
117+
).then(aggregateResult =>
118+
convertToAggregateQuerySnapshot(
119+
firestore,
120+
query,
121+
aggregateSpec,
122+
aggregateResult
123+
)
124+
);
125+
}
126+
127+
function convertToAggregateQuerySnapshot<T extends AggregateSpec>(
128+
firestore: Firestore,
129+
query: Query<unknown>,
130+
ref: T,
131+
aggregateResult: ObjectValue
132+
): AggregateQuerySnapshot<T> {
57133
const userDataWriter = new ExpUserDataWriter(firestore);
58-
return firestoreClientRunCountQuery(client, query, userDataWriter);
134+
const querySnapshot = new AggregateQuerySnapshot<T>(
135+
query,
136+
firestore,
137+
userDataWriter,
138+
aggregateResult
139+
);
140+
return querySnapshot;
59141
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @license
3+
* Copyright 2023 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import { FieldPath } from '../model/path';
19+
20+
type AggregateType = 'count' | 'avg' | 'sum';
21+
22+
/**
23+
* TODO
24+
*/
25+
export interface Aggregate {
26+
readonly fieldPath?: FieldPath;
27+
readonly alias: string;
28+
readonly aggregateType: AggregateType;
29+
}
30+
31+
/**
32+
* TODO
33+
*/
34+
export class AggregateImpl implements Aggregate {
35+
constructor(
36+
readonly alias: string,
37+
readonly aggregateType: AggregateType,
38+
readonly fieldPath?: FieldPath
39+
) {}
40+
}
41+
42+
/**
43+
* TODO
44+
* @param alias
45+
* @param aggregateType
46+
* @param fieldPath
47+
*/
48+
export function newAggregate(
49+
alias: string,
50+
aggregateType: AggregateType,
51+
fieldPath?: FieldPath
52+
): Aggregate {
53+
return new AggregateImpl(alias, aggregateType, fieldPath);
54+
}

0 commit comments

Comments
 (0)