Skip to content

Sum and average #6952

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 15 commits into from
Feb 9, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions common/api-review/firestore-lite.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn

// @public
export class AggregateField<T> {
type: string;
readonly type = "AggregateField";
}

// @public
export type AggregateFieldType = AggregateField<number>;
export type AggregateFieldType = AggregateField<number> | AggregateField<number | null>;

// @public
export class AggregateQuerySnapshot<T extends AggregateSpec> {
Expand Down
4 changes: 2 additions & 2 deletions common/api-review/firestore.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ export type AddPrefixToKeys<Prefix extends string, T extends Record<string, unkn

// @public
export class AggregateField<T> {
type: string;
readonly type = "AggregateField";
}

// @public
export type AggregateFieldType = AggregateField<number>;
export type AggregateFieldType = AggregateField<number> | AggregateField<number | null>;

// @public
export class AggregateQuerySnapshot<T extends AggregateSpec> {
Expand Down
9 changes: 7 additions & 2 deletions packages/firestore/lite/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,15 +29,20 @@ registerFirestore();

export {
aggregateQuerySnapshotEqual,
getCount
getCount,
getAggregate,
count,
sum,
average
} from '../src/lite-api/aggregate';

export {
AggregateField,
AggregateFieldType,
AggregateSpec,
AggregateSpecData,
AggregateQuerySnapshot
AggregateQuerySnapshot,
AggregateType
} from '../src/lite-api/aggregate_types';

export { FirestoreSettings as Settings } from '../src/lite-api/settings';
Expand Down
9 changes: 7 additions & 2 deletions packages/firestore/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@

export {
aggregateQuerySnapshotEqual,
getCountFromServer
getCountFromServer,
getAggregateFromServer,
count,
sum,
average
} from './api/aggregate';

export {
AggregateField,
AggregateFieldType,
AggregateSpec,
AggregateSpecData,
AggregateQuerySnapshot
AggregateQuerySnapshot,
AggregateType
} from './lite-api/aggregate_types';

export { FieldPath, documentId } from './api/field_path';
Expand Down
106 changes: 98 additions & 8 deletions packages/firestore/src/api/aggregate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,24 @@
* limitations under the License.
*/

import { Query } from '../api';
import { firestoreClientRunCountQuery } from '../core/firestore_client';
import {
AggregateField,
AggregateQuerySnapshot
} from '../lite-api/aggregate_types';
import { AggregateField, AggregateSpec, Query } from '../api';
import { AggregateImpl } from '../core/aggregate';
import { firestoreClientRunAggregateQuery } from '../core/firestore_client';
import { count } from '../lite-api/aggregate';
import { AggregateQuerySnapshot } from '../lite-api/aggregate_types';
import { ObjectValue } from '../model/object_value';
import { cast } from '../util/input_validation';
import { mapToArray } from '../util/obj';

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

export { aggregateQuerySnapshotEqual } from '../lite-api/aggregate';
export {
aggregateQuerySnapshotEqual,
count,
sum,
average
} from '../lite-api/aggregate';

/**
* Calculates the number of documents in the result set of the given query,
Expand All @@ -52,8 +58,92 @@ export { aggregateQuerySnapshotEqual } from '../lite-api/aggregate';
export function getCountFromServer(
query: Query<unknown>
): Promise<AggregateQuerySnapshot<{ count: AggregateField<number> }>> {
const countQuerySpec: { count: AggregateField<number> } = {
count: count()
};

return getAggregateFromServer(query, countQuerySpec);
}

/**
* Calculates the specified aggregations over the documents in the result
* set of the given query, without actually downloading the documents.
*
* Using this function to perform aggregations is efficient because only the
* final aggregation values, not the documents' data, is downloaded. This
* function can even perform aggregations if the documents if the result set
* would be prohibitively large to download entirely (e.g. thousands of documents).
*
* The result received from the server is presented, unaltered, without
* considering any local state. That is, documents in the local cache are not
* taken into consideration, neither are local modifications not yet
* synchronized with the server. Previously-downloaded results, if any, are not
* used: every request using this source necessarily involves a round trip to
* the server.
*
* @param query The query whose result set to aggregate over.
* @param aggregateSpec An `AggregateSpec` object that specifies the aggregates
* to perform over the result set. The AggregateSpec specifies aliases for each
* aggregate, which can be used to retrieve the aggregate result.
* @example
* ```typescript
* const aggregateSnapshot = await getAggregateFromServer(query, {
* countOfDocs: count(),
* totalHours: sum('hours'),
* averageScore: average('score')
* });
*
* const countOfDocs: number = aggregateSnapshot.data().countOfDocs;
* const totalHours: number = aggregateSnapshot.data().totalHours;
* const averageScore: number | null = aggregateSnapshot.data().averageScore;
* ```
* @internal TODO (sum/avg) remove when public
*/
export function getAggregateFromServer<T extends AggregateSpec>(
query: Query<unknown>,
aggregateSpec: T
): Promise<AggregateQuerySnapshot<T>> {
const firestore = cast(query.firestore, Firestore);
const client = ensureFirestoreConfigured(firestore);

const internalAggregates = mapToArray(aggregateSpec, (aggregate, alias) => {
// TODO (sum/avg) should alias validation be performed or should that be
// delegated to the backend?

return new AggregateImpl(
alias,
aggregate._aggregateType,
aggregate._internalFieldPath
);
});

// Run the aggregation and convert the results
return firestoreClientRunAggregateQuery(
client,
query._query,
internalAggregates
).then(aggregateResult =>
convertToAggregateQuerySnapshot(firestore, query, aggregateResult)
);
}

/**
* Converts the core aggregration result to an `AggregateQuerySnapshot`
* that can be returned to the consumer.
* @param query
* @param aggregateResult Core aggregation result
* @internal
*/
function convertToAggregateQuerySnapshot<T extends AggregateSpec>(
firestore: Firestore,
query: Query<unknown>,
aggregateResult: ObjectValue
): AggregateQuerySnapshot<T> {
const userDataWriter = new ExpUserDataWriter(firestore);
return firestoreClientRunCountQuery(client, query, userDataWriter);
const querySnapshot = new AggregateQuerySnapshot<T>(
query,
userDataWriter,
aggregateResult
);
return querySnapshot;
}
44 changes: 44 additions & 0 deletions packages/firestore/src/core/aggregate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/**
* @license
* Copyright 2023 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.
*/

import { FieldPath } from '../model/path';

/**
* Union type representing the aggregate type to be performed.
* @internal
*/
export type AggregateType = 'count' | 'avg' | 'sum';

/**
* Represents an Aggregate to be performed over a query result set.
*/
export interface Aggregate {
readonly fieldPath?: FieldPath;
readonly alias: string;
readonly aggregateType: AggregateType;
}

/**
* Concrete implementation of the Aggregate type.
*/
export class AggregateImpl implements Aggregate {
constructor(
readonly alias: string,
readonly aggregateType: AggregateType,
readonly fieldPath?: FieldPath
) {}
}
70 changes: 0 additions & 70 deletions packages/firestore/src/core/count_query_runner.ts

This file was deleted.

Loading