From ce5b2d46e84e2d9623df420670f38074a165bf4c Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Mon, 8 Aug 2022 12:05:45 -0700 Subject: [PATCH 01/24] upload initial code --- packages/firestore/src/lite-api/aggregate.ts | 61 ++++++++++++++++--- packages/firestore/src/lite-api/components.ts | 4 +- .../test/integration/api/count.test.ts | 27 ++++++-- 3 files changed, 79 insertions(+), 13 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 0afd82a5996..4ffb2334716 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -15,14 +15,26 @@ * limitations under the License. */ -import { DocumentData, Query, queryEqual } from './reference'; +import { cast } from '../util/input_validation'; +import { Query, queryEqual } from './reference'; +import { Firestore } from './database'; +import { getDatastore } from './components'; +import { LiteUserDataWriter } from './reference_impl'; +import { invokeRunQueryRpc } from '../remote/datastore'; +import { QueryDocumentSnapshot, QuerySnapshot } from './snapshot'; -export class AggregateQuery { +export class AggregateQuery { readonly type = 'AggregateQuery'; - readonly query: Query; + /** + * The query on which you called {@link countQuery} in order to get this + * `AggregateQuery`. + * Qury type is set to unknown to avoid error caused by quey type converter. + * might change it back to T after testing if the error do exist or not + */ + readonly query: Query; /** @hideconstructor */ - constructor(query: Query) { + constructor(query: Query) { this.query = query; } } @@ -32,23 +44,56 @@ export class AggregateQuerySnapshot { readonly query: AggregateQuery; /** @hideconstructor */ - constructor(query: AggregateQuery, readonly _count: number) { + constructor(query: AggregateQuery, readonly _count: number, readonly docs:QueryDocumentSnapshot[]) { this.query = query; } getCount(): number | null { return this._count; } + + getdocs(): Array> { + return [...this.docs]; + } + + get count(): number | null { + return this._count; + } + + compare(){ + //which one we prefer? + console.log(this.getCount()) + console.log(this.count) + } } -export function countQuery(query: Query): AggregateQuery { +export function countQuery(query: Query): AggregateQuery { return new AggregateQuery(query); } export function getAggregateFromServerDirect( - query: AggregateQuery + aggregateQuery: AggregateQuery ): Promise { - return Promise.resolve(new AggregateQuerySnapshot(query, 42)); + + // const firestore = cast(aggregateQuery.query.firestore, Firestore); + const datastore = getDatastore(aggregateQuery.query.firestore); + const userDataWriter = new LiteUserDataWriter(aggregateQuery.query.firestore); + console.log("===========",datastore) + + return invokeRunQueryRpc(datastore, aggregateQuery.query._query).then(result => { + const docs = result.map( + doc => + new QueryDocumentSnapshot( + aggregateQuery.query.firestore, + userDataWriter, + doc.key, + doc, + aggregateQuery.query.converter + ) + ); + + return Promise.resolve(new AggregateQuerySnapshot(aggregateQuery, 42, docs)); + }); } export function aggregateQueryEqual( diff --git a/packages/firestore/src/lite-api/components.ts b/packages/firestore/src/lite-api/components.ts index 8280d396b0b..ea61d9dcaba 100644 --- a/packages/firestore/src/lite-api/components.ts +++ b/packages/firestore/src/lite-api/components.ts @@ -84,9 +84,11 @@ export function getDatastore(firestore: FirestoreService): Datastore { connection, serializer ); - + console.log("datastoreInstances--------",datastoreInstances) datastoreInstances.set(firestore, datastore); } + console.log("datastoreInstances 2--------",datastoreInstances) + return datastoreInstances.get(firestore)!; } diff --git a/packages/firestore/test/integration/api/count.test.ts b/packages/firestore/test/integration/api/count.test.ts index 1a4bac61ced..e677b213ee0 100644 --- a/packages/firestore/test/integration/api/count.test.ts +++ b/packages/firestore/test/integration/api/count.test.ts @@ -16,18 +16,36 @@ */ import { expect } from 'chai'; + import { countQuery, getAggregateFromServerDirect, - query + query, + collection, + doc, + documentId, + endAt, + endBefore, + getDoc, + getDocs, + limit, + orderBy, + setDoc, + startAfter, + startAt, + Timestamp, + where } from '../util/firebase_export'; -import { apiDescribe, withTestCollection } from '../util/helpers'; + +import { apiDescribe, withTestCollection,toDataArray } from '../util/helpers'; apiDescribe('Aggregation COUNT query:', (persistence: boolean) => { it('empty collection count equals to 0', () => { const testDocs = {}; - return withTestCollection(persistence, testDocs, collection => { - const countQuery_ = countQuery(query(collection)); + return withTestCollection(persistence, testDocs, coll => { + const countQuery_ = countQuery(query(coll)); + + expect(countQuery_.type).to.equal("AggregateQuery"); return getAggregateFromServerDirect(countQuery_).then(snapshot => { expect(snapshot.getCount()).to.equal(0); }); @@ -45,6 +63,7 @@ apiDescribe('Aggregation COUNT query:', (persistence: boolean) => { }; return withTestCollection(persistence, testDocs, collection => { const countQuery_ = countQuery(query(collection)); + console.log("?",countQuery_); return getAggregateFromServerDirect(countQuery_).then(snapshot => { expect(snapshot.getCount()).to.equal(6); }); From 6fbb066eef5f94e0fe11a85e298e22d66ec33ac6 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Mon, 8 Aug 2022 15:45:16 -0700 Subject: [PATCH 02/24] upload current changes --- packages/firestore/src/lite-api/aggregate.ts | 43 +++++------ packages/firestore/src/remote/datastore.ts | 20 ++++++ .../test/integration/api/count.test.ts | 72 ------------------- 3 files changed, 42 insertions(+), 93 deletions(-) delete mode 100644 packages/firestore/test/integration/api/count.test.ts diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 4ffb2334716..20200d1e4c2 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -17,18 +17,18 @@ import { cast } from '../util/input_validation'; import { Query, queryEqual } from './reference'; -import { Firestore } from './database'; +import { Firestore } from './database'; import { getDatastore } from './components'; import { LiteUserDataWriter } from './reference_impl'; -import { invokeRunQueryRpc } from '../remote/datastore'; -import { QueryDocumentSnapshot, QuerySnapshot } from './snapshot'; +import { invokeRunAggregateQueryRpc } from '../remote/datastore'; +import { QueryDocumentSnapshot } from './snapshot'; export class AggregateQuery { readonly type = 'AggregateQuery'; - /** + /** * The query on which you called {@link countQuery} in order to get this * `AggregateQuery`. - * Qury type is set to unknown to avoid error caused by quey type converter. + * Query type is set to unknown to avoid error caused by query type converter. * might change it back to T after testing if the error do exist or not */ readonly query: Query; @@ -44,26 +44,22 @@ export class AggregateQuerySnapshot { readonly query: AggregateQuery; /** @hideconstructor */ - constructor(query: AggregateQuery, readonly _count: number, readonly docs:QueryDocumentSnapshot[]) { + constructor(query: AggregateQuery, readonly _count: number) { this.query = query; } + //which one we prefer? method or accessor? getCount(): number | null { return this._count; } - getdocs(): Array> { - return [...this.docs]; - } - get count(): number | null { return this._count; } - compare(){ - //which one we prefer? - console.log(this.getCount()) - console.log(this.count) + compare() { + console.log(this.getCount()); + console.log(this.count); } } @@ -74,13 +70,15 @@ export function countQuery(query: Query): AggregateQuery { export function getAggregateFromServerDirect( aggregateQuery: AggregateQuery ): Promise { - - // const firestore = cast(aggregateQuery.query.firestore, Firestore); - const datastore = getDatastore(aggregateQuery.query.firestore); - const userDataWriter = new LiteUserDataWriter(aggregateQuery.query.firestore); - console.log("===========",datastore) + const firestore = cast(aggregateQuery.query.firestore, Firestore); + const datastore = getDatastore(firestore); + const userDataWriter = new LiteUserDataWriter(firestore); - return invokeRunQueryRpc(datastore, aggregateQuery.query._query).then(result => { + return invokeRunAggregateQueryRpc( + datastore, + aggregateQuery.query._query + ).then(result => { + console.log('==========result', result); const docs = result.map( doc => new QueryDocumentSnapshot( @@ -91,8 +89,11 @@ export function getAggregateFromServerDirect( aggregateQuery.query.converter ) ); + console.log('==========docs', docs); - return Promise.resolve(new AggregateQuerySnapshot(aggregateQuery, 42, docs)); + return Promise.resolve( + new AggregateQuerySnapshot(aggregateQuery, docs.length) + ); }); } diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 0d96661f6b1..a59903f91f4 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -232,6 +232,26 @@ export async function invokeRunQueryRpc( ); } +export async function invokeRunAggregateQueryRpc( + datastore: Datastore, + query: Query +): Promise { + const datastoreImpl = debugCast(datastore, DatastoreImpl); + const request = toQueryTarget(datastoreImpl.serializer, queryToTarget(query)); + const response = await datastoreImpl.invokeStreamingRPC< + ProtoRunQueryRequest, + ProtoRunQueryResponse + >('RunQuery', request.parent!, { structuredQuery: request.structuredQuery }); + return ( + response + // Omit RunQueryResponses that only contain readTimes. + .filter(proto => !!proto.document) + .map(proto => + fromDocument(datastoreImpl.serializer, proto.document!, undefined) + ) + ); +} + export function newPersistentWriteStream( datastore: Datastore, queue: AsyncQueue, diff --git a/packages/firestore/test/integration/api/count.test.ts b/packages/firestore/test/integration/api/count.test.ts deleted file mode 100644 index e677b213ee0..00000000000 --- a/packages/firestore/test/integration/api/count.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -/** - * @license - * 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. - */ - -import { expect } from 'chai'; - -import { - countQuery, - getAggregateFromServerDirect, - query, - collection, - doc, - documentId, - endAt, - endBefore, - getDoc, - getDocs, - limit, - orderBy, - setDoc, - startAfter, - startAt, - Timestamp, - where -} from '../util/firebase_export'; - -import { apiDescribe, withTestCollection,toDataArray } from '../util/helpers'; - -apiDescribe('Aggregation COUNT query:', (persistence: boolean) => { - it('empty collection count equals to 0', () => { - const testDocs = {}; - return withTestCollection(persistence, testDocs, coll => { - const countQuery_ = countQuery(query(coll)); - - expect(countQuery_.type).to.equal("AggregateQuery"); - return getAggregateFromServerDirect(countQuery_).then(snapshot => { - expect(snapshot.getCount()).to.equal(0); - }); - }); - }); - - it('test collection count equals to 6', () => { - const testDocs = { - a: { k: 'a' }, - b: { k: 'b' }, - c: { k: 'c' }, - d: { k: 'd' }, - e: { k: 'e' }, - f: { k: 'f' } - }; - return withTestCollection(persistence, testDocs, collection => { - const countQuery_ = countQuery(query(collection)); - console.log("?",countQuery_); - return getAggregateFromServerDirect(countQuery_).then(snapshot => { - expect(snapshot.getCount()).to.equal(6); - }); - }); - }); -}); From c1a53dc6c67e49a94395383701f13dabea2caac8 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Fri, 12 Aug 2022 12:46:04 -0700 Subject: [PATCH 03/24] wire up the CountQuery with invokeRunAggregationQueryRpc --- packages/firestore/src/lite-api/aggregate.ts | 22 +----- packages/firestore/src/lite-api/components.ts | 2 - .../src/protos/firestore_proto_api.ts | 36 +++++++++- packages/firestore/src/remote/datastore.ts | 31 +++++---- .../firestore/src/remote/rest_connection.ts | 2 +- packages/firestore/src/remote/serializer.ts | 68 ++++++++++++++++++- .../firestore/test/lite/integration.test.ts | 35 ++++++++++ 7 files changed, 157 insertions(+), 39 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 20200d1e4c2..6c8dd43c96d 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -19,9 +19,7 @@ import { cast } from '../util/input_validation'; import { Query, queryEqual } from './reference'; import { Firestore } from './database'; import { getDatastore } from './components'; -import { LiteUserDataWriter } from './reference_impl'; -import { invokeRunAggregateQueryRpc } from '../remote/datastore'; -import { QueryDocumentSnapshot } from './snapshot'; +import { invokeRunAggregationQueryRpc, invokeRunQueryRpc } from '../remote/datastore'; export class AggregateQuery { readonly type = 'AggregateQuery'; @@ -72,27 +70,13 @@ export function getAggregateFromServerDirect( ): Promise { const firestore = cast(aggregateQuery.query.firestore, Firestore); const datastore = getDatastore(firestore); - const userDataWriter = new LiteUserDataWriter(firestore); - return invokeRunAggregateQueryRpc( +return invokeRunAggregationQueryRpc( datastore, aggregateQuery.query._query ).then(result => { - console.log('==========result', result); - const docs = result.map( - doc => - new QueryDocumentSnapshot( - aggregateQuery.query.firestore, - userDataWriter, - doc.key, - doc, - aggregateQuery.query.converter - ) - ); - console.log('==========docs', docs); - return Promise.resolve( - new AggregateQuerySnapshot(aggregateQuery, docs.length) + new AggregateQuerySnapshot(aggregateQuery, result.length) ); }); } diff --git a/packages/firestore/src/lite-api/components.ts b/packages/firestore/src/lite-api/components.ts index ea61d9dcaba..7900d27b245 100644 --- a/packages/firestore/src/lite-api/components.ts +++ b/packages/firestore/src/lite-api/components.ts @@ -84,10 +84,8 @@ export function getDatastore(firestore: FirestoreService): Datastore { connection, serializer ); - console.log("datastoreInstances--------",datastoreInstances) datastoreInstances.set(firestore, datastore); } - console.log("datastoreInstances 2--------",datastoreInstances) return datastoreInstances.get(firestore)!; } diff --git a/packages/firestore/src/protos/firestore_proto_api.ts b/packages/firestore/src/protos/firestore_proto_api.ts index 8634f2a7d4a..4164f793e63 100644 --- a/packages/firestore/src/protos/firestore_proto_api.ts +++ b/packages/firestore/src/protos/firestore_proto_api.ts @@ -334,6 +334,32 @@ export declare namespace firestoreV1ApiClientInterfaces { readTime?: string; skippedResults?: number; } + interface RunAggregationQueryRequest { + parent?: string; + structuredAggregationQuery?: StructuredAggregationQuery; + transaction?: string; + newTransaction?: TransactionOptions; + readTime?: string; + } + interface RunAggregationQueryResponse { + result?: AggregationResult; + transaction?: string; + readTime?: string; + } + interface AggregationResult { + aggregateFields?: ApiClientObjectMap; + } + interface StructuredAggregationQuery { + structuredQuery?: StructuredQuery; + aggregations?: Aggregation[]; + } + interface Aggregation { + count?: Count; + alias?: string; + } + interface Count { + upTo?: number + } interface Status { code?: number; message?: string; @@ -474,11 +500,15 @@ export declare type QueryTarget = firestoreV1ApiClientInterfaces.QueryTarget; export declare type ReadOnly = firestoreV1ApiClientInterfaces.ReadOnly; export declare type ReadWrite = firestoreV1ApiClientInterfaces.ReadWrite; export declare type RollbackRequest = - firestoreV1ApiClientInterfaces.RollbackRequest; +firestoreV1ApiClientInterfaces.RollbackRequest; export declare type RunQueryRequest = - firestoreV1ApiClientInterfaces.RunQueryRequest; +firestoreV1ApiClientInterfaces.RunQueryRequest; export declare type RunQueryResponse = - firestoreV1ApiClientInterfaces.RunQueryResponse; +firestoreV1ApiClientInterfaces.RunQueryResponse; +export declare type RunAggregationQueryRequest = + firestoreV1ApiClientInterfaces.RunAggregationQueryRequest; +export declare type RunAggregationQueryResponse = + firestoreV1ApiClientInterfaces.RunAggregationQueryResponse; export declare type Status = firestoreV1ApiClientInterfaces.Status; export declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index a59903f91f4..341486d9c65 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -25,7 +25,10 @@ import { BatchGetDocumentsRequest as ProtoBatchGetDocumentsRequest, BatchGetDocumentsResponse as ProtoBatchGetDocumentsResponse, RunQueryRequest as ProtoRunQueryRequest, - RunQueryResponse as ProtoRunQueryResponse + RunQueryResponse as ProtoRunQueryResponse, + RunAggregationQueryRequest as ProtoRunAggregationQueryRequest, + RunAggregationQueryResponse as ProtoRunAggregationQueryResponse, + Value, } from '../protos/firestore_proto_api'; import { debugAssert, debugCast, hardAssert } from '../util/assert'; import { AsyncQueue } from '../util/async_queue'; @@ -45,7 +48,8 @@ import { JsonProtoSerializer, toMutation, toName, - toQueryTarget + toQueryTarget, + toRunAggregationQueryRequest } from './serializer'; /** @@ -232,22 +236,23 @@ export async function invokeRunQueryRpc( ); } -export async function invokeRunAggregateQueryRpc( +export async function invokeRunAggregationQueryRpc( datastore: Datastore, query: Query -): Promise { +): Promise< (Value|undefined)[]> { + const datastoreImpl = debugCast(datastore, DatastoreImpl); - const request = toQueryTarget(datastoreImpl.serializer, queryToTarget(query)); - const response = await datastoreImpl.invokeStreamingRPC< - ProtoRunQueryRequest, - ProtoRunQueryResponse - >('RunQuery', request.parent!, { structuredQuery: request.structuredQuery }); + const request = toRunAggregationQueryRequest(datastoreImpl.serializer, queryToTarget(query)); + console.log("request--------", request) + const response = await datastoreImpl.invokeStreamingRPC('RunAggregationQuery', request.parent!, {structuredAggregationQuery: request.structuredAggregationQuery}); + console.log("response--------", response) return ( response - // Omit RunQueryResponses that only contain readTimes. - .filter(proto => !!proto.document) - .map(proto => - fromDocument(datastoreImpl.serializer, proto.document!, undefined) + .filter(proto => !!proto.result) + .map(proto => proto.result?.aggregateFields!["count"] + // fromDocument(datastoreImpl.serializer, proto.result!, undefined) ) ); } diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts index 9c318486620..67fffbdde92 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -37,6 +37,7 @@ const RPC_NAME_URL_MAPPING: StringMap = {}; RPC_NAME_URL_MAPPING['BatchGetDocuments'] = 'batchGet'; RPC_NAME_URL_MAPPING['Commit'] = 'commit'; RPC_NAME_URL_MAPPING['RunQuery'] = 'runQuery'; +RPC_NAME_URL_MAPPING['RunAggregationQuery'] = 'runAggregationQuery'; const RPC_URL_VERSION = 'v1'; @@ -78,7 +79,6 @@ export abstract class RestConnection implements Connection { const headers = {}; this.modifyHeadersForRequest(headers, authToken, appCheckToken); - return this.performRPCRequest(rpcName, url, headers, req).then( response => { logDebug(LOG_TAG, 'Received: ', response); diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index e3c5de5c5ed..f27f33452cd 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -82,7 +82,8 @@ import { TargetChangeTargetChangeType as ProtoTargetChangeTargetChangeType, Timestamp as ProtoTimestamp, Write as ProtoWrite, - WriteResult as ProtoWriteResult + WriteResult as ProtoWriteResult, + RunAggregationQueryRequest as ProtoRunAggregationQueryRequest } from '../protos/firestore_proto_api'; import { debugAssert, fail, hardAssert } from '../util/assert'; import { ByteString } from '../util/byte_string'; @@ -806,6 +807,7 @@ export function toQueryTarget( // Dissect the path into parent, collectionId, and optional key filter. const result: ProtoQueryTarget = { structuredQuery: {} }; const path = target.path; + if (target.collectionGroup !== null) { debugAssert( path.length % 2 === 0, @@ -852,6 +854,70 @@ export function toQueryTarget( return result; } +export function toRunAggregationQueryRequest( + serializer: JsonProtoSerializer, + target: Target +): ProtoRunAggregationQueryRequest { + // Dissect the path into parent, collectionId, and optional key filter. + const result: ProtoRunAggregationQueryRequest = { + structuredAggregationQuery: { + aggregations: [ + { + count: {} + } + ], + structuredQuery: {} + } + }; + const path = target.path; + if (target.collectionGroup !== null) { + debugAssert( + path.length % 2 === 0, + 'Collection Group queries should be within a document path or root.' + ); + result.parent = toQueryPath(serializer, path); + result.structuredAggregationQuery!.structuredQuery!.from = [ + { + collectionId: target.collectionGroup, + allDescendants: true + } + ]; + } else { + debugAssert( + path.length % 2 !== 0, + 'Document queries with filters are not supported.' + ); + result.parent = toQueryPath(serializer, path.popLast()); + result.structuredAggregationQuery!.structuredQuery!.from = [ + { collectionId: path.lastSegment() } + ]; + } + + const where = toFilter(target.filters); + if (where) { + result.structuredAggregationQuery!.structuredQuery!.where = where; + } + + const orderBy = toOrder(target.orderBy); + if (orderBy) { + result.structuredAggregationQuery!.structuredQuery!.orderBy = orderBy; + } + + const limit = toInt32Proto(serializer, target.limit); + if (limit !== null) { + result.structuredAggregationQuery!.structuredQuery!.limit = limit; + } + + if (target.startAt) { + result.structuredAggregationQuery!.structuredQuery!.startAt = toStartAtCursor(target.startAt); + } + if (target.endAt) { + result.structuredAggregationQuery!.structuredQuery!.endAt = toEndAtCursor(target.endAt); + } + + return result; +} + export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query { let path = fromQueryPath(target.parent!); diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index deaa9a0236f..913d56c8536 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -77,6 +77,11 @@ import { import { Timestamp } from '../../src/lite-api/timestamp'; import { runTransaction } from '../../src/lite-api/transaction'; import { writeBatch } from '../../src/lite-api/write_batch'; +import { + countQuery, + getAggregateFromServerDirect +} from '../../src/lite-api/aggregate'; + import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS @@ -2038,3 +2043,33 @@ describe('withConverter() support', () => { }); }); }); + +describe('countQuery()', () => { + it.only('empty collection count equals to 0', () => { + return withTestCollection(async coll => { + const countQuery_ = countQuery(query(coll)); + expect(countQuery_.type).to.equal('AggregateQuery'); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(0); + expect(snapshot.count).to.equal(0); + }); + }); + + it.only('test collection count equals to 6', () => { + const testDocs = [ + { k: 'a' }, + { k: 'b' }, + { k: 'c' }, + { k: 'd' }, + { k: 'e' }, + { k: 'f' } + ]; + return withTestCollectionAndInitialData(testDocs, async collection => { + const countQuery_ = countQuery(query(collection)); + expect(countQuery_.type).to.equal('AggregateQuery'); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(6); + expect(snapshot.count).to.equal(6); + }); + }); +}); From 970818a11d00e1065b8c8bd487677e239f3d8ad0 Mon Sep 17 00:00:00 2001 From: milaGGL Date: Fri, 12 Aug 2022 20:04:44 +0000 Subject: [PATCH 04/24] Update API reports --- common/api-review/firestore.api.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index e77f2353747..e8950ae6247 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -18,9 +18,8 @@ export type AddPrefixToKeys { - // (undocumented) - readonly query: Query; +export class AggregateQuery { + readonly query: Query; // (undocumented) readonly type = "AggregateQuery"; } @@ -30,6 +29,10 @@ export function aggregateQueryEqual(left: AggregateQuery, right: AggregateQuery) // @public (undocumented) export class AggregateQuerySnapshot { + // (undocumented) + compare(): void; + // (undocumented) + get count(): number | null; // (undocumented) getCount(): number | null; // (undocumented) @@ -94,7 +97,7 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por }): void; // @public (undocumented) -export function countQuery(query: Query): AggregateQuery; +export function countQuery(query: Query): AggregateQuery; // @public export function deleteDoc(reference: DocumentReference): Promise; @@ -237,7 +240,7 @@ export class GeoPoint { } // @public (undocumented) -export function getAggregateFromServerDirect(query: AggregateQuery): Promise; +export function getAggregateFromServerDirect(aggregateQuery: AggregateQuery): Promise; // @public export function getDoc(reference: DocumentReference): Promise>; From c88463526ebf020c70d165436ea47bbf0d6644f2 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 16 Aug 2022 10:08:31 -0700 Subject: [PATCH 05/24] upload partial code --- packages/firestore/src/lite-api/aggregate.ts | 43 +++++++++++++------ .../src/protos/firestore_proto_api.ts | 2 + packages/firestore/src/remote/datastore.ts | 30 +++++++------ packages/firestore/src/remote/serializer.ts | 12 +++++- 4 files changed, 58 insertions(+), 29 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 6c8dd43c96d..ebb7c99fb96 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -19,7 +19,8 @@ import { cast } from '../util/input_validation'; import { Query, queryEqual } from './reference'; import { Firestore } from './database'; import { getDatastore } from './components'; -import { invokeRunAggregationQueryRpc, invokeRunQueryRpc } from '../remote/datastore'; +import { invokeRunAggregationQueryRpc } from '../remote/datastore'; +import {ObjectValue} from "../model/object_value"; export class AggregateQuery { readonly type = 'AggregateQuery'; @@ -35,29 +36,34 @@ export class AggregateQuery { constructor(query: Query) { this.query = query; } + + getQuery(): Query { + return this.query; + } } export class AggregateQuerySnapshot { readonly type = 'AggregateQuerySnapshot'; readonly query: AggregateQuery; + readonly count: number | null; /** @hideconstructor */ - constructor(query: AggregateQuery, readonly _count: number) { + constructor(query: AggregateQuery, count: number | null) { this.query = query; + this.count = count; } - //which one we prefer? method or accessor? - getCount(): number | null { - return this._count; - } - - get count(): number | null { - return this._count; + /** @return The original {@link AggregateQuery} this snapshot is a result of. */ + getQuery(): AggregateQuery { + return this.query; } - compare() { - console.log(this.getCount()); - console.log(this.count); + /** + * @return The result of a document count aggregation. Returns null if no count aggregation is + * available in the result. + */ + getCount(): number | null { + return this.count; } } @@ -71,14 +77,23 @@ export function getAggregateFromServerDirect( const firestore = cast(aggregateQuery.query.firestore, Firestore); const datastore = getDatastore(firestore); -return invokeRunAggregationQueryRpc( + return invokeRunAggregationQueryRpc( datastore, aggregateQuery.query._query ).then(result => { + console.log("aggregarte result:" , result) + let count= null; + const aggregationFields = result.map(proto=>{ + for (const [key, value] of Object.entries(proto)) { + if (key == "count") count = parseInt(value.integerValue) + } + return proto + }) return Promise.resolve( - new AggregateQuerySnapshot(aggregateQuery, result.length) + new AggregateQuerySnapshot(aggregateQuery, count) ); }); + } export function aggregateQueryEqual( diff --git a/packages/firestore/src/protos/firestore_proto_api.ts b/packages/firestore/src/protos/firestore_proto_api.ts index 4164f793e63..f8998fb65f6 100644 --- a/packages/firestore/src/protos/firestore_proto_api.ts +++ b/packages/firestore/src/protos/firestore_proto_api.ts @@ -509,6 +509,8 @@ export declare type RunAggregationQueryRequest = firestoreV1ApiClientInterfaces.RunAggregationQueryRequest; export declare type RunAggregationQueryResponse = firestoreV1ApiClientInterfaces.RunAggregationQueryResponse; +export declare type AggregationResult = + firestoreV1ApiClientInterfaces.AggregationResult; export declare type Status = firestoreV1ApiClientInterfaces.Status; export declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 341486d9c65..e280969adff 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -28,7 +28,8 @@ import { RunQueryResponse as ProtoRunQueryResponse, RunAggregationQueryRequest as ProtoRunAggregationQueryRequest, RunAggregationQueryResponse as ProtoRunAggregationQueryResponse, - Value, + AggregationResult, + Value } from '../protos/firestore_proto_api'; import { debugAssert, debugCast, hardAssert } from '../util/assert'; import { AsyncQueue } from '../util/async_queue'; @@ -239,21 +240,24 @@ export async function invokeRunQueryRpc( export async function invokeRunAggregationQueryRpc( datastore: Datastore, query: Query -): Promise< (Value|undefined)[]> { - +): Promise { const datastoreImpl = debugCast(datastore, DatastoreImpl); - const request = toRunAggregationQueryRequest(datastoreImpl.serializer, queryToTarget(query)); - console.log("request--------", request) - const response = await datastoreImpl.invokeStreamingRPC('RunAggregationQuery', request.parent!, {structuredAggregationQuery: request.structuredAggregationQuery}); - console.log("response--------", response) + const request = toRunAggregationQueryRequest( + datastoreImpl.serializer, + queryToTarget(query) + ); + const response = await datastoreImpl.invokeStreamingRPC< + ProtoRunAggregationQueryRequest, + ProtoRunAggregationQueryResponse + >('RunAggregationQuery', request.parent!, { + structuredAggregationQuery: request.structuredAggregationQuery + }); + console.log("rpc response : ", response) return ( response - .filter(proto => !!proto.result) - .map(proto => proto.result?.aggregateFields!["count"] - // fromDocument(datastoreImpl.serializer, proto.result!, undefined) - ) + // Omit RunQueryResponses that only contain readTimes. + .filter(proto => !!proto.result && !!proto.result.aggregateFields) + .map(proto => proto.result!.aggregateFields!) ); } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index f27f33452cd..ce6958d6cb4 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -404,6 +404,7 @@ export function fromDocument( document: ProtoDocument, hasCommittedMutations?: boolean ): MutableDocument { + console.log("documents", document) const key = fromName(serializer, document.name!); const version = fromVersion(document.updateTime!); const data = new ObjectValue({ mapValue: { fields: document.fields } }); @@ -863,8 +864,15 @@ export function toRunAggregationQueryRequest( structuredAggregationQuery: { aggregations: [ { - count: {} - } + count: { + }, + alias:"count" + }, + // { + // count: { + // }, + // alias:"count2" + // }, ], structuredQuery: {} } From 81b988a6a4e5540d32378cc9c22ef7682675a14b Mon Sep 17 00:00:00 2001 From: milaGGL Date: Tue, 16 Aug 2022 17:28:57 +0000 Subject: [PATCH 06/24] Update API reports --- common/api-review/firestore.api.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index e8950ae6247..e41279c1cc7 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -19,6 +19,8 @@ export type AddPrefixToKeys; readonly query: Query; // (undocumented) readonly type = "AggregateQuery"; @@ -30,11 +32,9 @@ export function aggregateQueryEqual(left: AggregateQuery, right: AggregateQuery) // @public (undocumented) export class AggregateQuerySnapshot { // (undocumented) - compare(): void; - // (undocumented) - get count(): number | null; - // (undocumented) + readonly count: number | null; getCount(): number | null; + getQuery(): AggregateQuery; // (undocumented) readonly query: AggregateQuery; // (undocumented) From c0b088c07c705eb3a54ee2ae1412dd3a2b6b0881 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 16 Aug 2022 17:48:19 -0700 Subject: [PATCH 07/24] add count query internal logic --- packages/firestore/src/lite-api/aggregate.ts | 39 +++++++++++-------- .../src/protos/firestore_proto_api.ts | 10 ++--- packages/firestore/src/remote/datastore.ts | 11 +++--- .../firestore/test/lite/integration.test.ts | 4 +- 4 files changed, 33 insertions(+), 31 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index ebb7c99fb96..c3b7afdb76e 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -20,7 +20,7 @@ import { Query, queryEqual } from './reference'; import { Firestore } from './database'; import { getDatastore } from './components'; import { invokeRunAggregationQueryRpc } from '../remote/datastore'; -import {ObjectValue} from "../model/object_value"; +import { LiteUserDataWriter } from './reference_impl'; export class AggregateQuery { readonly type = 'AggregateQuery'; @@ -76,24 +76,29 @@ export function getAggregateFromServerDirect( ): Promise { const firestore = cast(aggregateQuery.query.firestore, Firestore); const datastore = getDatastore(firestore); + const userDataWriter = new LiteUserDataWriter(firestore); - return invokeRunAggregationQueryRpc( - datastore, - aggregateQuery.query._query - ).then(result => { - console.log("aggregarte result:" , result) - let count= null; - const aggregationFields = result.map(proto=>{ - for (const [key, value] of Object.entries(proto)) { - if (key == "count") count = parseInt(value.integerValue) + return invokeRunAggregationQueryRpc(datastore, aggregateQuery).then( + result => { + const aggregationFields = new Map(); + /** + * while getting aggregation fields from server direct, it should get only + * one ProtoRunAggregationQueryResponse returned. + * But we used streaming rpc here, so we will have an array of + * (onr , or possibly more)RunAggregationQueryResponse. For this specific + * function, we get the first RunAggregationQueryResponse only. + */ + for (const [key, value] of Object.entries(result[0])) { + aggregationFields.set(key, userDataWriter.convertValue(value)); } - return proto - }) - return Promise.resolve( - new AggregateQuerySnapshot(aggregateQuery, count) - ); - }); - + return Promise.resolve( + new AggregateQuerySnapshot( + aggregateQuery, + aggregationFields.get('count') + ) + ); + } + ); } export function aggregateQueryEqual( diff --git a/packages/firestore/src/protos/firestore_proto_api.ts b/packages/firestore/src/protos/firestore_proto_api.ts index f8998fb65f6..e7dfe0a88b2 100644 --- a/packages/firestore/src/protos/firestore_proto_api.ts +++ b/packages/firestore/src/protos/firestore_proto_api.ts @@ -358,7 +358,7 @@ export declare namespace firestoreV1ApiClientInterfaces { alias?: string; } interface Count { - upTo?: number + upTo?: number; } interface Status { code?: number; @@ -500,17 +500,15 @@ export declare type QueryTarget = firestoreV1ApiClientInterfaces.QueryTarget; export declare type ReadOnly = firestoreV1ApiClientInterfaces.ReadOnly; export declare type ReadWrite = firestoreV1ApiClientInterfaces.ReadWrite; export declare type RollbackRequest = -firestoreV1ApiClientInterfaces.RollbackRequest; + firestoreV1ApiClientInterfaces.RollbackRequest; export declare type RunQueryRequest = -firestoreV1ApiClientInterfaces.RunQueryRequest; + firestoreV1ApiClientInterfaces.RunQueryRequest; export declare type RunQueryResponse = -firestoreV1ApiClientInterfaces.RunQueryResponse; + firestoreV1ApiClientInterfaces.RunQueryResponse; export declare type RunAggregationQueryRequest = firestoreV1ApiClientInterfaces.RunAggregationQueryRequest; export declare type RunAggregationQueryResponse = firestoreV1ApiClientInterfaces.RunAggregationQueryResponse; -export declare type AggregationResult = - firestoreV1ApiClientInterfaces.AggregationResult; export declare type Status = firestoreV1ApiClientInterfaces.Status; export declare type StructuredQuery = firestoreV1ApiClientInterfaces.StructuredQuery; diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index e280969adff..23f2d50760e 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -28,8 +28,7 @@ import { RunQueryResponse as ProtoRunQueryResponse, RunAggregationQueryRequest as ProtoRunAggregationQueryRequest, RunAggregationQueryResponse as ProtoRunAggregationQueryResponse, - AggregationResult, - Value + Value as ProtoValue } from '../protos/firestore_proto_api'; import { debugAssert, debugCast, hardAssert } from '../util/assert'; import { AsyncQueue } from '../util/async_queue'; @@ -52,6 +51,7 @@ import { toQueryTarget, toRunAggregationQueryRequest } from './serializer'; +import { AggregateQuery } from '../lite-api/aggregate'; /** * Datastore and its related methods are a wrapper around the external Google @@ -239,12 +239,12 @@ export async function invokeRunQueryRpc( export async function invokeRunAggregationQueryRpc( datastore: Datastore, - query: Query -): Promise { + aggregateQuery: AggregateQuery +): Promise { const datastoreImpl = debugCast(datastore, DatastoreImpl); const request = toRunAggregationQueryRequest( datastoreImpl.serializer, - queryToTarget(query) + queryToTarget(aggregateQuery.query._query) ); const response = await datastoreImpl.invokeStreamingRPC< ProtoRunAggregationQueryRequest, @@ -252,7 +252,6 @@ export async function invokeRunAggregationQueryRpc( >('RunAggregationQuery', request.parent!, { structuredAggregationQuery: request.structuredAggregationQuery }); - console.log("rpc response : ", response) return ( response // Omit RunQueryResponses that only contain readTimes. diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 913d56c8536..6a1ab132c16 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2049,9 +2049,9 @@ describe('countQuery()', () => { return withTestCollection(async coll => { const countQuery_ = countQuery(query(coll)); expect(countQuery_.type).to.equal('AggregateQuery'); + const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(0); - expect(snapshot.count).to.equal(0); }); }); @@ -2067,9 +2067,9 @@ describe('countQuery()', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const countQuery_ = countQuery(query(collection)); expect(countQuery_.type).to.equal('AggregateQuery'); + const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(6); - expect(snapshot.count).to.equal(6); }); }); }); From 97f7316a958e89fd09c0e71e995d7e3059d81bed Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 16 Aug 2022 18:34:06 -0700 Subject: [PATCH 08/24] add comments to functions --- packages/firestore/src/lite-api/aggregate.ts | 35 +++++++++++++++++--- packages/firestore/src/remote/serializer.ts | 7 +--- 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index c3b7afdb76e..7b97b1f20e6 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -22,6 +22,14 @@ import { getDatastore } from './components'; import { invokeRunAggregationQueryRpc } from '../remote/datastore'; import { LiteUserDataWriter } from './reference_impl'; +/** + * 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. + */ export class AggregateQuery { readonly type = 'AggregateQuery'; /** @@ -37,11 +45,19 @@ export class AggregateQuery { this.query = query; } + /** Returns the base {@link Query} for this aggregate query. */ getQuery(): Query { return this.query; } } +/** + * 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. + */ export class AggregateQuerySnapshot { readonly type = 'AggregateQuerySnapshot'; readonly query: AggregateQuery; @@ -60,14 +76,23 @@ export class AggregateQuerySnapshot { /** * @return The result of a document count aggregation. Returns null if no count aggregation is - * available in the result. + * available in the result. */ getCount(): number | null { return this.count; } } +/** + * 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. + */ export function countQuery(query: Query): AggregateQuery { + /** + * TODO(mila): add the "count" aggregateField to the params after the AggregateQuery is updated. + */ return new AggregateQuery(query); } @@ -82,10 +107,10 @@ export function getAggregateFromServerDirect( result => { const aggregationFields = new Map(); /** - * while getting aggregation fields from server direct, it should get only - * one ProtoRunAggregationQueryResponse returned. + * while getting aggregation fields from server direct, it should have only + * one RunAggregationQueryResponse returned. * But we used streaming rpc here, so we will have an array of - * (onr , or possibly more)RunAggregationQueryResponse. For this specific + * (one, or possibly more) RunAggregationQueryResponse. For this specific * function, we get the first RunAggregationQueryResponse only. */ for (const [key, value] of Object.entries(result[0])) { @@ -94,7 +119,7 @@ export function getAggregateFromServerDirect( return Promise.resolve( new AggregateQuerySnapshot( aggregateQuery, - aggregationFields.get('count') + aggregationFields.get('count_alias') ) ); } diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index ce6958d6cb4..b9038788c9a 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -866,13 +866,8 @@ export function toRunAggregationQueryRequest( { count: { }, - alias:"count" + alias:"count_alias" }, - // { - // count: { - // }, - // alias:"count2" - // }, ], structuredQuery: {} } From 0330caa69551226bda765d69a5c32c45a13499d6 Mon Sep 17 00:00:00 2001 From: milaGGL Date: Wed, 17 Aug 2022 01:53:11 +0000 Subject: [PATCH 09/24] Update API reports --- common/api-review/firestore.api.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index e41279c1cc7..ac12051d673 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -17,9 +17,8 @@ export type AddPrefixToKeys; readonly query: Query; // (undocumented) @@ -29,7 +28,7 @@ export class AggregateQuery { // @public (undocumented) export function aggregateQueryEqual(left: AggregateQuery, right: AggregateQuery): boolean; -// @public (undocumented) +// @public export class AggregateQuerySnapshot { // (undocumented) readonly count: number | null; @@ -96,7 +95,7 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por mockUserToken?: EmulatorMockTokenOptions | string; }): void; -// @public (undocumented) +// @public export function countQuery(query: Query): AggregateQuery; // @public From 80394574cd49ca22b1986ee42725d978607227a6 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 16 Aug 2022 18:54:07 -0700 Subject: [PATCH 10/24] reformat, reduce noise --- packages/firestore/src/lite-api/aggregate.ts | 6 +++--- packages/firestore/src/lite-api/components.ts | 2 +- packages/firestore/src/remote/datastore.ts | 6 +++--- packages/firestore/src/remote/rest_connection.ts | 1 + packages/firestore/src/remote/serializer.ts | 2 -- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 7b97b1f20e6..44d3c0718da 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -15,11 +15,11 @@ * limitations under the License. */ +import { invokeRunAggregationQueryRpc } from '../remote/datastore'; import { cast } from '../util/input_validation'; -import { Query, queryEqual } from './reference'; -import { Firestore } from './database'; import { getDatastore } from './components'; -import { invokeRunAggregationQueryRpc } from '../remote/datastore'; +import { Firestore } from './database'; +import { Query, queryEqual } from './reference'; import { LiteUserDataWriter } from './reference_impl'; /** diff --git a/packages/firestore/src/lite-api/components.ts b/packages/firestore/src/lite-api/components.ts index 7900d27b245..8280d396b0b 100644 --- a/packages/firestore/src/lite-api/components.ts +++ b/packages/firestore/src/lite-api/components.ts @@ -84,9 +84,9 @@ export function getDatastore(firestore: FirestoreService): Datastore { connection, serializer ); + datastoreInstances.set(firestore, datastore); } - return datastoreInstances.get(firestore)!; } diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 23f2d50760e..214b063c929 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -18,16 +18,17 @@ import { CredentialsProvider } from '../api/credentials'; import { User } from '../auth/user'; import { Query, queryToTarget } from '../core/query'; +import { AggregateQuery } from '../lite-api/aggregate'; import { Document } from '../model/document'; import { DocumentKey } from '../model/document_key'; import { Mutation } from '../model/mutation'; import { BatchGetDocumentsRequest as ProtoBatchGetDocumentsRequest, BatchGetDocumentsResponse as ProtoBatchGetDocumentsResponse, - RunQueryRequest as ProtoRunQueryRequest, - RunQueryResponse as ProtoRunQueryResponse, RunAggregationQueryRequest as ProtoRunAggregationQueryRequest, RunAggregationQueryResponse as ProtoRunAggregationQueryResponse, + RunQueryRequest as ProtoRunQueryRequest, + RunQueryResponse as ProtoRunQueryResponse, Value as ProtoValue } from '../protos/firestore_proto_api'; import { debugAssert, debugCast, hardAssert } from '../util/assert'; @@ -51,7 +52,6 @@ import { toQueryTarget, toRunAggregationQueryRequest } from './serializer'; -import { AggregateQuery } from '../lite-api/aggregate'; /** * Datastore and its related methods are a wrapper around the external Google diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts index 67fffbdde92..7550c492f71 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -79,6 +79,7 @@ export abstract class RestConnection implements Connection { const headers = {}; this.modifyHeadersForRequest(headers, authToken, appCheckToken); + return this.performRPCRequest(rpcName, url, headers, req).then( response => { logDebug(LOG_TAG, 'Received: ', response); diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index b9038788c9a..9e44b3dd8b6 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -404,7 +404,6 @@ export function fromDocument( document: ProtoDocument, hasCommittedMutations?: boolean ): MutableDocument { - console.log("documents", document) const key = fromName(serializer, document.name!); const version = fromVersion(document.updateTime!); const data = new ObjectValue({ mapValue: { fields: document.fields } }); @@ -808,7 +807,6 @@ export function toQueryTarget( // Dissect the path into parent, collectionId, and optional key filter. const result: ProtoQueryTarget = { structuredQuery: {} }; const path = target.path; - if (target.collectionGroup !== null) { debugAssert( path.length % 2 === 0, From 8078444471a6360153f386010c792b929945efdc Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Wed, 17 Aug 2022 11:05:00 -0700 Subject: [PATCH 11/24] remove it.only --- packages/firestore/src/remote/rest_connection.ts | 1 - packages/firestore/test/lite/integration.test.ts | 12 ++++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/packages/firestore/src/remote/rest_connection.ts b/packages/firestore/src/remote/rest_connection.ts index 7550c492f71..67fffbdde92 100644 --- a/packages/firestore/src/remote/rest_connection.ts +++ b/packages/firestore/src/remote/rest_connection.ts @@ -79,7 +79,6 @@ export abstract class RestConnection implements Connection { const headers = {}; this.modifyHeadersForRequest(headers, authToken, appCheckToken); - return this.performRPCRequest(rpcName, url, headers, req).then( response => { logDebug(LOG_TAG, 'Received: ', response); diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 6a1ab132c16..416ca8dfd66 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -20,6 +20,10 @@ import { initializeApp } from '@firebase/app'; import { expect, use } from 'chai'; import chaiAsPromised from 'chai-as-promised'; +import { + countQuery, + getAggregateFromServerDirect +} from '../../src/lite-api/aggregate'; import { Bytes } from '../../src/lite-api/bytes'; import { Firestore, @@ -77,10 +81,6 @@ import { import { Timestamp } from '../../src/lite-api/timestamp'; import { runTransaction } from '../../src/lite-api/transaction'; import { writeBatch } from '../../src/lite-api/write_batch'; -import { - countQuery, - getAggregateFromServerDirect -} from '../../src/lite-api/aggregate'; import { DEFAULT_PROJECT_ID, @@ -2045,7 +2045,7 @@ describe('withConverter() support', () => { }); describe('countQuery()', () => { - it.only('empty collection count equals to 0', () => { + it('empty collection count equals to 0', () => { return withTestCollection(async coll => { const countQuery_ = countQuery(query(coll)); expect(countQuery_.type).to.equal('AggregateQuery'); @@ -2055,7 +2055,7 @@ describe('countQuery()', () => { }); }); - it.only('test collection count equals to 6', () => { + it('test collection count equals to 6', () => { const testDocs = [ { k: 'a' }, { k: 'b' }, From f245e0121952f2ed2f2d6dec9027d538f6ac7d57 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Thu, 18 Aug 2022 13:48:31 -0700 Subject: [PATCH 12/24] make sure getCount return null or number --- packages/firestore/src/lite-api/aggregate.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 44d3c0718da..a0cab089b72 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -105,7 +105,7 @@ export function getAggregateFromServerDirect( return invokeRunAggregationQueryRpc(datastore, aggregateQuery).then( result => { - const aggregationFields = new Map(); + const aggregationFields = new Map(); /** * while getting aggregation fields from server direct, it should have only * one RunAggregationQueryResponse returned. @@ -119,7 +119,10 @@ export function getAggregateFromServerDirect( return Promise.resolve( new AggregateQuerySnapshot( aggregateQuery, - aggregationFields.get('count_alias') + //return the count value , or null if count is undfined + aggregationFields.has('count_alias') + ? aggregationFields.get('count_alias') + : null ) ); } From 6f63799338f098e67ae58d854b6f37a475ec54f9 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Thu, 18 Aug 2022 14:34:40 -0700 Subject: [PATCH 13/24] add limit, filter and their integration test --- packages/firestore/src/lite-api/aggregate.ts | 16 +-- packages/firestore/src/remote/serializer.ts | 12 +- .../firestore/test/lite/integration.test.ts | 121 ++++++++++++++++-- 3 files changed, 124 insertions(+), 25 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index a0cab089b72..6e311552815 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -19,7 +19,7 @@ import { invokeRunAggregationQueryRpc } from '../remote/datastore'; import { cast } from '../util/input_validation'; import { getDatastore } from './components'; import { Firestore } from './database'; -import { Query, queryEqual } from './reference'; +import { DocumentData, Query, queryEqual } from './reference'; import { LiteUserDataWriter } from './reference_impl'; /** @@ -30,7 +30,7 @@ import { LiteUserDataWriter } from './reference_impl'; * in test mocks. Subclassing is not supported in production code and new SDK releases may break * code that does so. */ -export class AggregateQuery { +export class AggregateQuery { readonly type = 'AggregateQuery'; /** * The query on which you called {@link countQuery} in order to get this @@ -38,15 +38,15 @@ export class AggregateQuery { * Query type is set to unknown to avoid error caused by query type converter. * might change it back to T after testing if the error do exist or not */ - readonly query: Query; + readonly query: Query; /** @hideconstructor */ - constructor(query: Query) { + constructor(query: Query) { this.query = query; } /** Returns the base {@link Query} for this aggregate query. */ - getQuery(): Query { + getQuery(): Query { return this.query; } } @@ -89,15 +89,15 @@ export class AggregateQuerySnapshot { * @return An {@link AggregateQuery} object that can be used to count the number of documents in * the result set of this query. */ -export function countQuery(query: Query): AggregateQuery { +export function countQuery(query: Query): AggregateQuery { /** * TODO(mila): add the "count" aggregateField to the params after the AggregateQuery is updated. */ return new AggregateQuery(query); } -export function getAggregateFromServerDirect( - aggregateQuery: AggregateQuery +export function getAggregateFromServerDirect( + aggregateQuery: AggregateQuery ): Promise { const firestore = cast(aggregateQuery.query.firestore, Firestore); const datastore = getDatastore(firestore); diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 9e44b3dd8b6..3774a025c1c 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -861,6 +861,7 @@ export function toRunAggregationQueryRequest( const result: ProtoRunAggregationQueryRequest = { structuredAggregationQuery: { aggregations: [ + //TODO: dynamically add the aggregate fields in future development { count: { }, @@ -898,7 +899,9 @@ export function toRunAggregationQueryRequest( if (where) { result.structuredAggregationQuery!.structuredQuery!.where = where; } - + /** QUESTION: in count query, do we need to add OrderBy? Number of + * documents shouldn't be impacted by how the documents are ordered. + */ const orderBy = toOrder(target.orderBy); if (orderBy) { result.structuredAggregationQuery!.structuredQuery!.orderBy = orderBy; @@ -908,13 +911,6 @@ export function toRunAggregationQueryRequest( if (limit !== null) { result.structuredAggregationQuery!.structuredQuery!.limit = limit; } - - if (target.startAt) { - result.structuredAggregationQuery!.structuredQuery!.startAt = toStartAtCursor(target.startAt); - } - if (target.endAt) { - result.structuredAggregationQuery!.structuredQuery!.endAt = toEndAtCursor(target.endAt); - } return result; } diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 416ca8dfd66..325fd0536d3 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2045,7 +2045,17 @@ describe('withConverter() support', () => { }); describe('countQuery()', () => { - it('empty collection count equals to 0', () => { + const converter = { + toFirestore(value: string): DocumentData { + return { key: value }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot): string { + const data = snapshot.data(); + return data.value; + } + }; + + it.only('empty collection count equals to 0', () => { return withTestCollection(async coll => { const countQuery_ = countQuery(query(coll)); expect(countQuery_.type).to.equal('AggregateQuery'); @@ -2055,19 +2065,112 @@ describe('countQuery()', () => { }); }); - it('test collection count equals to 6', () => { + it.only('test collection count equals to 6', () => { const testDocs = [ - { k: 'a' }, - { k: 'b' }, - { k: 'c' }, - { k: 'd' }, - { k: 'e' }, - { k: 'f' } + { key: 'a' }, + { key: 'b' }, + { key: 'c' }, + { key: 'd' }, + { key: 'e' }, + { key: 'f' } ]; return withTestCollectionAndInitialData(testDocs, async collection => { const countQuery_ = countQuery(query(collection)); - expect(countQuery_.type).to.equal('AggregateQuery'); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(6); + }); + }); + + it.only('test collection count with filter', () => { + const testDocs = [ + { key: 'a' }, + { key: 'b' }, + { key: 'c' }, + { key: 'a' }, + { key: 'b' }, + { key: 'c' } + ]; + return withTestCollectionAndInitialData(testDocs, async collection => { + let query_ = query(collection, where('key', '==', 'a')); + + const countQuery_ = countQuery(query_); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(2); + }); + }); + + it.only('test collection count with filter effected by small limit', () => { + const testDocs = [ + { key: 'a' }, + { key: 'b' }, + { key: 'c' }, + { key: 'a' }, + { key: 'b' }, + { key: 'c' } + ]; + // limit that is less that the actual count would work like count up_to. + return withTestCollectionAndInitialData(testDocs, async collection => { + let query_ = query(collection, where('key', '==', 'a'), limit(1)); + + const countQuery_ = countQuery(query_); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(1); + }); + }); + + it.only('test collection count with filter not effected by large limit', () => { + const testDocs = [ + { key: 'a' }, + { key: 'b' }, + { key: 'c' }, + { key: 'a' }, + { key: 'b' }, + { key: 'c' } + ]; + //limit that is larger than actual count wouldn't impact the return value + return withTestCollectionAndInitialData(testDocs, async collection => { + let query_ = query(collection, where('key', '==', 'a'), limit(3)); + + const countQuery_ = countQuery(query_); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(2); + }); + }); + + it.only('test collection count with converter on query', () => { + const testDocs = [ + { key: 'a' }, + { key: 'b' }, + { key: 'c' }, + { key: 'a' }, + { key: 'b' }, + { key: 'c' } + ]; + //testing out the converter impact on the AggregateQuery type + return withTestCollectionAndInitialData(testDocs, async collection => { + let query_ = query(collection).withConverter(converter); + + const countQuery_ = countQuery(query_); + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.getCount()).to.equal(6); + }); + }); + + it.only('test collection count with converter on collection', () => { + const testDocs = [ + { key: 'a' }, + { key: 'b' }, + { key: 'c' }, + { key: 'a' }, + { key: 'b' }, + { key: 'c' } + ]; + //testing out the converter impact on the AggregateQuery type + return withTestCollectionAndInitialData(testDocs, async collection => { + const ref = collection.withConverter(converter); + let query_ = query(ref); + const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(6); }); From 60bce4a9ce433dca8781f45a26c52c8f1b7207aa Mon Sep 17 00:00:00 2001 From: milaGGL Date: Thu, 18 Aug 2022 21:53:19 +0000 Subject: [PATCH 14/24] Update API reports --- common/api-review/firestore.api.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index ac12051d673..835b0126080 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -18,9 +18,9 @@ export type AddPrefixToKeys; - readonly query: Query; +export class AggregateQuery { + getQuery(): Query; + readonly query: Query; // (undocumented) readonly type = "AggregateQuery"; } @@ -96,7 +96,7 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por }): void; // @public -export function countQuery(query: Query): AggregateQuery; +export function countQuery(query: Query): AggregateQuery; // @public export function deleteDoc(reference: DocumentReference): Promise; @@ -239,7 +239,7 @@ export class GeoPoint { } // @public (undocumented) -export function getAggregateFromServerDirect(aggregateQuery: AggregateQuery): Promise; +export function getAggregateFromServerDirect(aggregateQuery: AggregateQuery): Promise; // @public export function getDoc(reference: DocumentReference): Promise>; From f914afd8f15d1a6b7e38b133d8b4fc604b0514bc Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Mon, 22 Aug 2022 13:34:59 -0700 Subject: [PATCH 15/24] change AggregateQuery type to unknwon --- packages/firestore/src/lite-api/aggregate.ts | 27 ++++--------- .../firestore/test/lite/integration.test.ts | 40 +++++++++---------- 2 files changed, 28 insertions(+), 39 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 6e311552815..8d2cb854cbf 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -30,7 +30,7 @@ import { LiteUserDataWriter } from './reference_impl'; * in test mocks. Subclassing is not supported in production code and new SDK releases may break * code that does so. */ -export class AggregateQuery { +export class AggregateQuery { readonly type = 'AggregateQuery'; /** * The query on which you called {@link countQuery} in order to get this @@ -38,17 +38,13 @@ export class AggregateQuery { * Query type is set to unknown to avoid error caused by query type converter. * might change it back to T after testing if the error do exist or not */ - readonly query: Query; + readonly query: Query; /** @hideconstructor */ - constructor(query: Query) { + constructor(query: Query) { this.query = query; } - /** Returns the base {@link Query} for this aggregate query. */ - getQuery(): Query { - return this.query; - } } /** @@ -61,17 +57,10 @@ export class AggregateQuery { export class AggregateQuerySnapshot { readonly type = 'AggregateQuerySnapshot'; readonly query: AggregateQuery; - readonly count: number | null; /** @hideconstructor */ - constructor(query: AggregateQuery, count: number | null) { + constructor(query: AggregateQuery, private readonly _count: number) { this.query = query; - this.count = count; - } - - /** @return The original {@link AggregateQuery} this snapshot is a result of. */ - getQuery(): AggregateQuery { - return this.query; } /** @@ -79,7 +68,7 @@ export class AggregateQuerySnapshot { * available in the result. */ getCount(): number | null { - return this.count; + return this._count; } } @@ -89,15 +78,15 @@ export class AggregateQuerySnapshot { * @return An {@link AggregateQuery} object that can be used to count the number of documents in * the result set of this query. */ -export function countQuery(query: Query): AggregateQuery { +export function countQuery(query: Query): AggregateQuery { /** * TODO(mila): add the "count" aggregateField to the params after the AggregateQuery is updated. */ return new AggregateQuery(query); } -export function getAggregateFromServerDirect( - aggregateQuery: AggregateQuery +export function getAggregateFromServerDirect( + aggregateQuery: AggregateQuery ): Promise { const firestore = cast(aggregateQuery.query.firestore, Firestore); const datastore = getDatastore(firestore); diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 325fd0536d3..7cbdf3cc6b3 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2046,12 +2046,12 @@ describe('withConverter() support', () => { describe('countQuery()', () => { const converter = { - toFirestore(value: string): DocumentData { - return { key: value }; + toFirestore(input: { id: string, content: string}): DocumentData { + return { key: input.id, value:input.content }; }, - fromFirestore(snapshot: QueryDocumentSnapshot): string { + fromFirestore(snapshot: QueryDocumentSnapshot): {id: string, content: string} { const data = snapshot.data(); - return data.value; + return {content:data.value, id:data.documentId} ; } }; @@ -2139,40 +2139,40 @@ describe('countQuery()', () => { it.only('test collection count with converter on query', () => { const testDocs = [ - { key: 'a' }, - { key: 'b' }, - { key: 'c' }, - { key: 'a' }, - { key: 'b' }, - { key: 'c' } + { key: 'a', value:"Adam" }, + { key: 'b' , value: "Bob"}, + { key: 'c', value: "Chris" }, + { key: 'a', value:"Anna" }, + { key: 'b' , value: "Bill"}, + { key: 'c', value: "Cooper" } ]; //testing out the converter impact on the AggregateQuery type return withTestCollectionAndInitialData(testDocs, async collection => { - let query_ = query(collection).withConverter(converter); + let query_ = query(collection, where('key', '==', 'a')).withConverter(converter); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); - expect(snapshot.getCount()).to.equal(6); + expect(snapshot.getCount()).to.equal(2); }); }); it.only('test collection count with converter on collection', () => { const testDocs = [ - { key: 'a' }, - { key: 'b' }, - { key: 'c' }, - { key: 'a' }, - { key: 'b' }, - { key: 'c' } + { key: 'a', value:"Adam" }, + { key: 'b' , value: "Bob"}, + { key: 'c', value: "Chris" }, + { key: 'a', value:"Anna" }, + { key: 'b' , value: "Bill"}, + { key: 'c', value: "Cooper" } ]; //testing out the converter impact on the AggregateQuery type return withTestCollectionAndInitialData(testDocs, async collection => { const ref = collection.withConverter(converter); - let query_ = query(ref); + let query_ = query(ref,where('key', '==', 'a')); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); - expect(snapshot.getCount()).to.equal(6); + expect(snapshot.getCount()).to.equal(2); }); }); }); From d3465961140d6c9e5f13103539d5b521a6882b2d Mon Sep 17 00:00:00 2001 From: milaGGL Date: Mon, 22 Aug 2022 20:53:55 +0000 Subject: [PATCH 16/24] Update API reports --- common/api-review/firestore.api.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index 835b0126080..cae95cd696f 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -18,9 +18,8 @@ export type AddPrefixToKeys { - getQuery(): Query; - readonly query: Query; +export class AggregateQuery { + readonly query: Query; // (undocumented) readonly type = "AggregateQuery"; } @@ -30,10 +29,7 @@ export function aggregateQueryEqual(left: AggregateQuery, right: AggregateQuery) // @public export class AggregateQuerySnapshot { - // (undocumented) - readonly count: number | null; getCount(): number | null; - getQuery(): AggregateQuery; // (undocumented) readonly query: AggregateQuery; // (undocumented) @@ -96,7 +92,7 @@ export function connectFirestoreEmulator(firestore: Firestore, host: string, por }): void; // @public -export function countQuery(query: Query): AggregateQuery; +export function countQuery(query: Query): AggregateQuery; // @public export function deleteDoc(reference: DocumentReference): Promise; @@ -239,7 +235,7 @@ export class GeoPoint { } // @public (undocumented) -export function getAggregateFromServerDirect(aggregateQuery: AggregateQuery): Promise; +export function getAggregateFromServerDirect(aggregateQuery: AggregateQuery): Promise; // @public export function getDoc(reference: DocumentReference): Promise>; From 189085e0869db80fa13057d887eea7714914d97a Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Mon, 22 Aug 2022 14:39:33 -0700 Subject: [PATCH 17/24] update integration tests --- .../firestore/test/lite/integration.test.ts | 138 +++++++++--------- 1 file changed, 73 insertions(+), 65 deletions(-) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 7cbdf3cc6b3..d2058af7303 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -22,7 +22,9 @@ import chaiAsPromised from 'chai-as-promised'; import { countQuery, - getAggregateFromServerDirect + getAggregateFromServerDirect, + aggregateQueryEqual, + aggregateQuerySnapshotEqual } from '../../src/lite-api/aggregate'; import { Bytes } from '../../src/lite-api/bytes'; import { @@ -2046,16 +2048,28 @@ describe('withConverter() support', () => { describe('countQuery()', () => { const converter = { - toFirestore(input: { id: string, content: string}): DocumentData { - return { key: input.id, value:input.content }; + toFirestore(input: { id: string; content: string }): DocumentData { + return { key: input.id, value: input.content }; }, - fromFirestore(snapshot: QueryDocumentSnapshot): {id: string, content: string} { + fromFirestore(snapshot: QueryDocumentSnapshot): { + id: string; + content: string; + } { const data = snapshot.data(); - return {content:data.value, id:data.documentId} ; + return { content: data.value, id: data.documentId }; } }; - it.only('empty collection count equals to 0', () => { + const testDocs = [ + { key: 'a', value: 'Adam' }, + { key: 'b', value: 'Bob' }, + { key: 'c', value: 'Chris' }, + { key: 'a', value: 'Anna' }, + { key: 'b', value: 'Bill' }, + { key: 'c', value: 'Cooper' } + ]; + + it('empty collection count equals to 0', () => { return withTestCollection(async coll => { const countQuery_ = countQuery(query(coll)); expect(countQuery_.type).to.equal('AggregateQuery'); @@ -2065,15 +2079,7 @@ describe('countQuery()', () => { }); }); - it.only('test collection count equals to 6', () => { - const testDocs = [ - { key: 'a' }, - { key: 'b' }, - { key: 'c' }, - { key: 'd' }, - { key: 'e' }, - { key: 'f' } - ]; + it('test collection count equals to 6', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const countQuery_ = countQuery(query(collection)); const snapshot = await getAggregateFromServerDirect(countQuery_); @@ -2081,17 +2087,9 @@ describe('countQuery()', () => { }); }); - it.only('test collection count with filter', () => { - const testDocs = [ - { key: 'a' }, - { key: 'b' }, - { key: 'c' }, - { key: 'a' }, - { key: 'b' }, - { key: 'c' } - ]; + it('test collection count with filter', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - let query_ = query(collection, where('key', '==', 'a')); + const query_ = query(collection, where('key', '==', 'a')); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); @@ -2099,18 +2097,10 @@ describe('countQuery()', () => { }); }); - it.only('test collection count with filter effected by small limit', () => { - const testDocs = [ - { key: 'a' }, - { key: 'b' }, - { key: 'c' }, - { key: 'a' }, - { key: 'b' }, - { key: 'c' } - ]; + it('test collection count with filter effected by small limit', () => { // limit that is less that the actual count would work like count up_to. return withTestCollectionAndInitialData(testDocs, async collection => { - let query_ = query(collection, where('key', '==', 'a'), limit(1)); + const query_ = query(collection, where('key', '==', 'a'), limit(1)); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); @@ -2118,18 +2108,10 @@ describe('countQuery()', () => { }); }); - it.only('test collection count with filter not effected by large limit', () => { - const testDocs = [ - { key: 'a' }, - { key: 'b' }, - { key: 'c' }, - { key: 'a' }, - { key: 'b' }, - { key: 'c' } - ]; + it('test collection count with filter not effected by large limit', () => { //limit that is larger than actual count wouldn't impact the return value return withTestCollectionAndInitialData(testDocs, async collection => { - let query_ = query(collection, where('key', '==', 'a'), limit(3)); + const query_ = query(collection, where('key', '==', 'a'), limit(3)); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); @@ -2137,18 +2119,12 @@ describe('countQuery()', () => { }); }); - it.only('test collection count with converter on query', () => { - const testDocs = [ - { key: 'a', value:"Adam" }, - { key: 'b' , value: "Bob"}, - { key: 'c', value: "Chris" }, - { key: 'a', value:"Anna" }, - { key: 'b' , value: "Bill"}, - { key: 'c', value: "Cooper" } - ]; + it('test collection count with converter on query', () => { //testing out the converter impact on the AggregateQuery type return withTestCollectionAndInitialData(testDocs, async collection => { - let query_ = query(collection, where('key', '==', 'a')).withConverter(converter); + const query_ = query(collection, where('key', '==', 'a')).withConverter( + converter + ); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); @@ -2156,23 +2132,55 @@ describe('countQuery()', () => { }); }); - it.only('test collection count with converter on collection', () => { - const testDocs = [ - { key: 'a', value:"Adam" }, - { key: 'b' , value: "Bob"}, - { key: 'c', value: "Chris" }, - { key: 'a', value:"Anna" }, - { key: 'b' , value: "Bill"}, - { key: 'c', value: "Cooper" } - ]; + it('test collection count with converter on collection', () => { //testing out the converter impact on the AggregateQuery type return withTestCollectionAndInitialData(testDocs, async collection => { const ref = collection.withConverter(converter); - let query_ = query(ref,where('key', '==', 'a')); + const query_ = query(ref, where('key', '==', 'a')); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); + + it('aggregateQueryEqual returns true on same queries', () => { + return withTestCollectionAndInitialData(testDocs, async collection => { + const query_1 = query(collection, where('key', '==', 'a')); + const query_2 = query(collection, where('key', '==', 'a')); + + const countQuery_1 = countQuery(query_1); + const countQuery_2 = countQuery(query_2); + expect(aggregateQueryEqual(countQuery_1, countQuery_2)).to.be.true; + }); + }); + + it('aggregateQueryEqual returns false on different queries', () => { + return withTestCollectionAndInitialData(testDocs, async collection => { + const query_1 = query(collection, where('key', '==', 'a')); + const query_2 = query(collection, where('key', '!=', 'a')); + + const countQuery_1 = countQuery(query_1); + const countQuery_2 = countQuery(query_2); + expect(aggregateQueryEqual(countQuery_1, countQuery_2)).to.be.false; + }); + }); + + it('aggregateQuerySnapshotEqual returns true on same queries', () => { + return withTestCollectionAndInitialData(testDocs, async collection => { + const query_original = query(collection, where('key', '==', 'a')); + const query_copy = query(collection, where('key', '==', 'a')); + + const countQuery_original_1 = countQuery(query_original); + const countQuery_original_2 = countQuery(query_original); + const countQuery_copy = countQuery(query_copy); + + const snapshot_original_1 = await getAggregateFromServerDirect(countQuery_original_1); + const snapshot_original_2 = await getAggregateFromServerDirect(countQuery_original_2); + const snapshot_copy = await getAggregateFromServerDirect(countQuery_copy); + + expect(aggregateQuerySnapshotEqual(snapshot_original_1, snapshot_original_2)).to.be.true; + expect(aggregateQuerySnapshotEqual(snapshot_original_1, snapshot_copy)).to.be.true; + }); + }); }); From dc39f065d0f9e587876049c9550dd742211817ce Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 23 Aug 2022 10:22:16 -0700 Subject: [PATCH 18/24] resolve comments --- packages/firestore/src/lite-api/aggregate.ts | 50 +++------ packages/firestore/src/remote/datastore.ts | 2 +- packages/firestore/src/remote/serializer.ts | 64 ++--------- .../firestore/test/lite/integration.test.ts | 102 +++++++++--------- 4 files changed, 79 insertions(+), 139 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 8d2cb854cbf..dff3f463737 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -16,25 +16,21 @@ */ import { invokeRunAggregationQueryRpc } from '../remote/datastore'; +import { hardAssert } from '../util/assert'; import { cast } from '../util/input_validation'; import { getDatastore } from './components'; import { Firestore } from './database'; -import { DocumentData, Query, queryEqual } from './reference'; +import { Query, queryEqual } from './reference'; import { LiteUserDataWriter } from './reference_impl'; /** - * 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. + * A `AggregateQuery` computes some aggregation statistics from the result set of + * a base `Query`. */ export class AggregateQuery { readonly type = 'AggregateQuery'; /** - * The query on which you called {@link countQuery} in order to get this - * `AggregateQuery`. + * The query on which you called `countQuery` in order to get this `AggregateQuery`. * Query type is set to unknown to avoid error caused by query type converter. * might change it back to T after testing if the error do exist or not */ @@ -44,15 +40,10 @@ export class AggregateQuery { constructor(query: Query) { this.query = query; } - } /** - * 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. + * A `AggregateQuerySnapshot` contains results of a `AggregateQuery`. */ export class AggregateQuerySnapshot { readonly type = 'AggregateQuerySnapshot'; @@ -73,15 +64,12 @@ export class AggregateQuerySnapshot { } /** - * Creates an {@link AggregateQuery} counting the number of documents matching this query. + * Creates an `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 + * @return An `AggregateQuery` object that can be used to count the number of documents in * the result set of this query. */ export function countQuery(query: Query): AggregateQuery { - /** - * TODO(mila): add the "count" aggregateField to the params after the AggregateQuery is updated. - */ return new AggregateQuery(query); } @@ -95,24 +83,18 @@ export function getAggregateFromServerDirect( return invokeRunAggregationQueryRpc(datastore, aggregateQuery).then( result => { const aggregationFields = new Map(); - /** - * while getting aggregation fields from server direct, it should have only - * one RunAggregationQueryResponse returned. - * But we used streaming rpc here, so we will have an array of - * (one, or possibly more) RunAggregationQueryResponse. For this specific - * function, we get the first RunAggregationQueryResponse only. - */ + for (const [key, value] of Object.entries(result[0])) { aggregationFields.set(key, userDataWriter.convertValue(value)); } + const countField = aggregationFields.get('count_alias'); + + hardAssert( + countField !== undefined && typeof countField === 'number', + 'Count aggeragte field is invalid. countField:' + countField + ); return Promise.resolve( - new AggregateQuerySnapshot( - aggregateQuery, - //return the count value , or null if count is undfined - aggregationFields.has('count_alias') - ? aggregationFields.get('count_alias') - : null - ) + new AggregateQuerySnapshot(aggregateQuery, countField!) ); } ); diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index 214b063c929..fc0681f6e9b 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -254,7 +254,7 @@ export async function invokeRunAggregationQueryRpc( }); return ( response - // Omit RunQueryResponses that only contain readTimes. + // Omit RunAggregationQueryResponse that only contain readTimes. .filter(proto => !!proto.result && !!proto.result.aggregateFields) .map(proto => proto.result!.aggregateFields!) ); diff --git a/packages/firestore/src/remote/serializer.ts b/packages/firestore/src/remote/serializer.ts index 3774a025c1c..21091d55a36 100644 --- a/packages/firestore/src/remote/serializer.ts +++ b/packages/firestore/src/remote/serializer.ts @@ -77,13 +77,13 @@ import { OrderDirection as ProtoOrderDirection, Precondition as ProtoPrecondition, QueryTarget as ProtoQueryTarget, + RunAggregationQueryRequest as ProtoRunAggregationQueryRequest, Status as ProtoStatus, Target as ProtoTarget, TargetChangeTargetChangeType as ProtoTargetChangeTargetChangeType, Timestamp as ProtoTimestamp, Write as ProtoWrite, - WriteResult as ProtoWriteResult, - RunAggregationQueryRequest as ProtoRunAggregationQueryRequest + WriteResult as ProtoWriteResult } from '../protos/firestore_proto_api'; import { debugAssert, fail, hardAssert } from '../util/assert'; import { ByteString } from '../util/byte_string'; @@ -857,62 +857,20 @@ export function toRunAggregationQueryRequest( serializer: JsonProtoSerializer, target: Target ): ProtoRunAggregationQueryRequest { - // Dissect the path into parent, collectionId, and optional key filter. - const result: ProtoRunAggregationQueryRequest = { + const queryTarget = toQueryTarget(serializer, target); + + return { structuredAggregationQuery: { aggregations: [ - //TODO: dynamically add the aggregate fields in future development { - count: { - }, - alias:"count_alias" - }, + count: {}, + alias: 'count_alias' + } ], - structuredQuery: {} - } + structuredQuery: queryTarget.structuredQuery + }, + parent: queryTarget.parent }; - const path = target.path; - if (target.collectionGroup !== null) { - debugAssert( - path.length % 2 === 0, - 'Collection Group queries should be within a document path or root.' - ); - result.parent = toQueryPath(serializer, path); - result.structuredAggregationQuery!.structuredQuery!.from = [ - { - collectionId: target.collectionGroup, - allDescendants: true - } - ]; - } else { - debugAssert( - path.length % 2 !== 0, - 'Document queries with filters are not supported.' - ); - result.parent = toQueryPath(serializer, path.popLast()); - result.structuredAggregationQuery!.structuredQuery!.from = [ - { collectionId: path.lastSegment() } - ]; - } - - const where = toFilter(target.filters); - if (where) { - result.structuredAggregationQuery!.structuredQuery!.where = where; - } - /** QUESTION: in count query, do we need to add OrderBy? Number of - * documents shouldn't be impacted by how the documents are ordered. - */ - const orderBy = toOrder(target.orderBy); - if (orderBy) { - result.structuredAggregationQuery!.structuredQuery!.orderBy = orderBy; - } - - const limit = toInt32Proto(serializer, target.limit); - if (limit !== null) { - result.structuredAggregationQuery!.structuredQuery!.limit = limit; - } - - return result; } export function convertQueryTargetToQuery(target: ProtoQueryTarget): Query { diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index d2058af7303..bf801c3c176 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2069,17 +2069,29 @@ describe('countQuery()', () => { { key: 'c', value: 'Cooper' } ]; - it('empty collection count equals to 0', () => { + it.only('AggregateQuery and AggregateQuerySnapshot inherits the original query', () => { return withTestCollection(async coll => { - const countQuery_ = countQuery(query(coll)); + const query_ = query(coll); + + const countQuery_ = countQuery(query_); expect(countQuery_.type).to.equal('AggregateQuery'); + expect(countQuery_.query).to.equal(query_); + + const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.query.query).to.equal(query_); + }); + }); + + it.only('empty collection count equals to 0', () => { + return withTestCollection(async coll => { + const countQuery_ = countQuery(query(coll)); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(0); }); }); - it('test collection count equals to 6', () => { + it.only('test collection count equals to 6', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const countQuery_ = countQuery(query(collection)); const snapshot = await getAggregateFromServerDirect(countQuery_); @@ -2087,100 +2099,88 @@ describe('countQuery()', () => { }); }); - it('test collection count with filter', () => { + it.only('test collection count with filter', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query_ = query(collection, where('key', '==', 'a')); - const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); - it('test collection count with filter effected by small limit', () => { - // limit that is less that the actual count would work like count up_to. + it.only('test collection count with filter and a small limit size', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query_ = query(collection, where('key', '==', 'a'), limit(1)); - const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(1); }); }); - it('test collection count with filter not effected by large limit', () => { - //limit that is larger than actual count wouldn't impact the return value + it.only('test collection count with filter and a large limit size', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query_ = query(collection, where('key', '==', 'a'), limit(3)); - const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); - it('test collection count with converter on query', () => { - //testing out the converter impact on the AggregateQuery type + it.only('test collection count with converter on query', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query_ = query(collection, where('key', '==', 'a')).withConverter( converter ); - const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); - it('test collection count with converter on collection', () => { - //testing out the converter impact on the AggregateQuery type + it.only('aggregateQueryEqual returns true on same queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const ref = collection.withConverter(converter); - const query_ = query(ref, where('key', '==', 'a')); - - const countQuery_ = countQuery(query_); - const snapshot = await getAggregateFromServerDirect(countQuery_); - expect(snapshot.getCount()).to.equal(2); + const query1 = query(collection, where('key', '==', 'a')); + const query2 = query(collection, where('key', '==', 'a')); + const countQuery1 = countQuery(query1); + const countQuery2 = countQuery(query2); + expect(aggregateQueryEqual(countQuery1, countQuery2)).to.be.true; }); }); - it('aggregateQueryEqual returns true on same queries', () => { + it.only('aggregateQueryEqual returns false on different queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query_1 = query(collection, where('key', '==', 'a')); - const query_2 = query(collection, where('key', '==', 'a')); - - const countQuery_1 = countQuery(query_1); - const countQuery_2 = countQuery(query_2); - expect(aggregateQueryEqual(countQuery_1, countQuery_2)).to.be.true; + const query1 = query(collection, where('key', '==', 'a')); + const query2 = query(collection, where('key', '!=', 'a')); + const countQuery1 = countQuery(query1); + const countQuery2 = countQuery(query2); + expect(aggregateQueryEqual(countQuery1, countQuery2)).to.be.false; }); }); - - it('aggregateQueryEqual returns false on different queries', () => { - return withTestCollectionAndInitialData(testDocs, async collection => { - const query_1 = query(collection, where('key', '==', 'a')); - const query_2 = query(collection, where('key', '!=', 'a')); - const countQuery_1 = countQuery(query_1); - const countQuery_2 = countQuery(query_2); - expect(aggregateQueryEqual(countQuery_1, countQuery_2)).to.be.false; + it.only('aggregateQuerySnapshotEqual returns true on same queries', () => { + return withTestCollectionAndInitialData(testDocs, async collection => { + const query1 = query(collection, where('key', '==', 'a')); + const query2 = query(collection, where('key', '==', 'a')); + const countQuery1A = countQuery(query1); + const countQuery1B = countQuery(query1); + const countQuery2 = countQuery(query2); + const snapshot1A = await getAggregateFromServerDirect(countQuery1A); + const snapshot1B = await getAggregateFromServerDirect(countQuery1B); + const snapshot2 = await getAggregateFromServerDirect(countQuery2); + expect(aggregateQuerySnapshotEqual(snapshot1A, snapshot1B)).to.be.true; + expect(aggregateQuerySnapshotEqual(snapshot1A, snapshot2)).to.be.true; }); }); - it('aggregateQuerySnapshotEqual returns true on same queries', () => { + it.only('aggregateQuerySnapshotEqual returns false on different queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query_original = query(collection, where('key', '==', 'a')); - const query_copy = query(collection, where('key', '==', 'a')); - - const countQuery_original_1 = countQuery(query_original); - const countQuery_original_2 = countQuery(query_original); - const countQuery_copy = countQuery(query_copy); - - const snapshot_original_1 = await getAggregateFromServerDirect(countQuery_original_1); - const snapshot_original_2 = await getAggregateFromServerDirect(countQuery_original_2); - const snapshot_copy = await getAggregateFromServerDirect(countQuery_copy); - - expect(aggregateQuerySnapshotEqual(snapshot_original_1, snapshot_original_2)).to.be.true; - expect(aggregateQuerySnapshotEqual(snapshot_original_1, snapshot_copy)).to.be.true; + const query1 = query(collection, where('key', '==', 'a')); + const query2 = query(collection, where('key', '==', 'b')); + const countQuery1 = countQuery(query1); + const countQuery2 = countQuery(query2); + const snapshot1 = await getAggregateFromServerDirect(countQuery1); + const snapshot2 = await getAggregateFromServerDirect(countQuery2); + expect(aggregateQuerySnapshotEqual(snapshot1, snapshot2)).to.be.false; }); }); }); From 99c2c35fffaa7b36fcd7995d36413ec1e1bd05e1 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 23 Aug 2022 12:17:17 -0700 Subject: [PATCH 19/24] add validation to getAggregateFromServerDirect --- packages/firestore/src/lite-api/aggregate.ts | 22 +++++++++++++------- packages/firestore/src/remote/datastore.ts | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index dff3f463737..4d0fd272f3c 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -15,6 +15,7 @@ * limitations under the License. */ +import { CompositeFilterOpEnum } from '../protos/firestore_proto_api'; import { invokeRunAggregationQueryRpc } from '../remote/datastore'; import { hardAssert } from '../util/assert'; import { cast } from '../util/input_validation'; @@ -82,19 +83,24 @@ export function getAggregateFromServerDirect( return invokeRunAggregationQueryRpc(datastore, aggregateQuery).then( result => { - const aggregationFields = new Map(); + hardAssert( + result[0] !== undefined, + 'Aggregation fields are missing from result.' + ); - for (const [key, value] of Object.entries(result[0])) { - aggregationFields.set(key, userDataWriter.convertValue(value)); - } - const countField = aggregationFields.get('count_alias'); + const countField = (result[0] as any).count_alias; + hardAssert( + countField !== undefined, + 'Count field is missing from result.' + ); + const countAggregateResult = userDataWriter.convertValue(countField); hardAssert( - countField !== undefined && typeof countField === 'number', - 'Count aggeragte field is invalid. countField:' + countField + typeof countAggregateResult === 'number', + 'Count aggeragte field is not a number: ' + countAggregateResult ); return Promise.resolve( - new AggregateQuerySnapshot(aggregateQuery, countField!) + new AggregateQuerySnapshot(aggregateQuery, countAggregateResult) ); } ); diff --git a/packages/firestore/src/remote/datastore.ts b/packages/firestore/src/remote/datastore.ts index fc0681f6e9b..febae1187dc 100644 --- a/packages/firestore/src/remote/datastore.ts +++ b/packages/firestore/src/remote/datastore.ts @@ -255,7 +255,7 @@ export async function invokeRunAggregationQueryRpc( return ( response // Omit RunAggregationQueryResponse that only contain readTimes. - .filter(proto => !!proto.result && !!proto.result.aggregateFields) + .filter(proto => !!proto.result) .map(proto => proto.result!.aggregateFields!) ); } From 0b5c3c520c7a59aab003366e580881e7e236461e Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:13:22 -0700 Subject: [PATCH 20/24] update the test docs --- .../firestore/test/lite/integration.test.ts | 85 +++++++++---------- 1 file changed, 40 insertions(+), 45 deletions(-) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index bf801c3c176..97723637d08 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2047,29 +2047,15 @@ describe('withConverter() support', () => { }); describe('countQuery()', () => { - const converter = { - toFirestore(input: { id: string; content: string }): DocumentData { - return { key: input.id, value: input.content }; - }, - fromFirestore(snapshot: QueryDocumentSnapshot): { - id: string; - content: string; - } { - const data = snapshot.data(); - return { content: data.value, id: data.documentId }; - } - }; - const testDocs = [ - { key: 'a', value: 'Adam' }, - { key: 'b', value: 'Bob' }, - { key: 'c', value: 'Chris' }, - { key: 'a', value: 'Anna' }, - { key: 'b', value: 'Bill' }, - { key: 'c', value: 'Cooper' } + { author: 'authorA', title: 'titleA' }, + { author: 'authorA', title: 'titleB' }, + { author: 'authorB', title: 'titleC' }, + { author: 'authorB', title: 'titleD' }, + { author: 'authorB', title: 'titleE' } ]; - it.only('AggregateQuery and AggregateQuerySnapshot inherits the original query', () => { + it('AggregateQuery and AggregateQuerySnapshot inherits the original query', () => { return withTestCollection(async coll => { const query_ = query(coll); @@ -2082,7 +2068,7 @@ describe('countQuery()', () => { }); }); - it.only('empty collection count equals to 0', () => { + it('empty test collection count', () => { return withTestCollection(async coll => { const countQuery_ = countQuery(query(coll)); @@ -2091,76 +2077,85 @@ describe('countQuery()', () => { }); }); - it.only('test collection count equals to 6', () => { + it('test collection count with sample docs ', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const countQuery_ = countQuery(query(collection)); const snapshot = await getAggregateFromServerDirect(countQuery_); - expect(snapshot.getCount()).to.equal(6); + expect(snapshot.getCount()).to.equal(5); }); }); - it.only('test collection count with filter', () => { + it('test collection count with filter', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, where('key', '==', 'a')); + const query_ = query(collection, where('author', '==', 'authorA')); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); - it.only('test collection count with filter and a small limit size', () => { + it('test collection count with filter and a small limit size', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, where('key', '==', 'a'), limit(1)); + const query_ = query( + collection, + where('author', '==', 'authorA'), + limit(1) + ); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(1); }); }); - it.only('test collection count with filter and a large limit size', () => { + it('test collection count with filter and a large limit size', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, where('key', '==', 'a'), limit(3)); + const query_ = query( + collection, + where('author', '==', 'authorA'), + limit(3) + ); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); - it.only('test collection count with converter on query', () => { + it('test collection count with converter on query', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query_ = query(collection, where('key', '==', 'a')).withConverter( - converter - ); + const query_ = query( + collection, + where('author', '==', 'authorA') + ).withConverter(postConverter); const countQuery_ = countQuery(query_); const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(2); }); }); - it.only('aggregateQueryEqual returns true on same queries', () => { + it('aggregateQueryEqual returns true on same queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('key', '==', 'a')); - const query2 = query(collection, where('key', '==', 'a')); + const query1 = query(collection, where('author', '==', 'authorA')); + const query2 = query(collection, where('author', '==', 'authorA')); const countQuery1 = countQuery(query1); const countQuery2 = countQuery(query2); expect(aggregateQueryEqual(countQuery1, countQuery2)).to.be.true; }); }); - it.only('aggregateQueryEqual returns false on different queries', () => { + it('aggregateQueryEqual returns false on different queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('key', '==', 'a')); - const query2 = query(collection, where('key', '!=', 'a')); + const query1 = query(collection, where('author', '==', 'authorA')); + const query2 = query(collection, where('author', '==', 'authorB')); const countQuery1 = countQuery(query1); const countQuery2 = countQuery(query2); expect(aggregateQueryEqual(countQuery1, countQuery2)).to.be.false; }); }); - it.only('aggregateQuerySnapshotEqual returns true on same queries', () => { + it('aggregateQuerySnapshotEqual returns true on same queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('key', '==', 'a')); - const query2 = query(collection, where('key', '==', 'a')); + const query1 = query(collection, where('author', '==', 'authorA')); + const query2 = query(collection, where('author', '==', 'authorA')); const countQuery1A = countQuery(query1); const countQuery1B = countQuery(query1); const countQuery2 = countQuery(query2); @@ -2172,10 +2167,10 @@ describe('countQuery()', () => { }); }); - it.only('aggregateQuerySnapshotEqual returns false on different queries', () => { + it('aggregateQuerySnapshotEqual returns false on different queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { - const query1 = query(collection, where('key', '==', 'a')); - const query2 = query(collection, where('key', '==', 'b')); + const query1 = query(collection, where('author', '==', 'authorA')); + const query2 = query(collection, where('author', '==', 'authorB')); const countQuery1 = countQuery(query1); const countQuery2 = countQuery(query2); const snapshot1 = await getAggregateFromServerDirect(countQuery1); From 654075e09625b86dfde4dcad965f53a0d424583a Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Tue, 23 Aug 2022 13:17:26 -0700 Subject: [PATCH 21/24] modify test description --- packages/firestore/test/lite/integration.test.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 97723637d08..470db41c30c 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -2132,7 +2132,7 @@ describe('countQuery()', () => { }); }); - it('aggregateQueryEqual returns true on same queries', () => { + it('aggregateQueryEqual on same queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query1 = query(collection, where('author', '==', 'authorA')); const query2 = query(collection, where('author', '==', 'authorA')); @@ -2142,7 +2142,7 @@ describe('countQuery()', () => { }); }); - it('aggregateQueryEqual returns false on different queries', () => { + it('aggregateQueryEqual on different queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query1 = query(collection, where('author', '==', 'authorA')); const query2 = query(collection, where('author', '==', 'authorB')); @@ -2152,7 +2152,7 @@ describe('countQuery()', () => { }); }); - it('aggregateQuerySnapshotEqual returns true on same queries', () => { + it('aggregateQuerySnapshotEqual on same queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query1 = query(collection, where('author', '==', 'authorA')); const query2 = query(collection, where('author', '==', 'authorA')); @@ -2167,7 +2167,7 @@ describe('countQuery()', () => { }); }); - it('aggregateQuerySnapshotEqual returns false on different queries', () => { + it('aggregateQuerySnapshotEqual on different queries', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const query1 = query(collection, where('author', '==', 'authorA')); const query2 = query(collection, where('author', '==', 'authorB')); From d7b5ec363b4140a612756099a4f407df4ea18a04 Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Wed, 24 Aug 2022 11:21:48 -0700 Subject: [PATCH 22/24] resolve comments --- packages/firestore/src/lite-api/aggregate.ts | 56 +++++++++---------- .../firestore/test/lite/integration.test.ts | 8 +-- 2 files changed, 30 insertions(+), 34 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index 4d0fd272f3c..f4f7920cb86 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -15,17 +15,18 @@ * limitations under the License. */ -import { CompositeFilterOpEnum } from '../protos/firestore_proto_api'; +import { Value } from '../protos/firestore_proto_api'; import { invokeRunAggregationQueryRpc } from '../remote/datastore'; import { hardAssert } from '../util/assert'; import { cast } from '../util/input_validation'; + import { getDatastore } from './components'; import { Firestore } from './database'; import { Query, queryEqual } from './reference'; import { LiteUserDataWriter } from './reference_impl'; /** - * A `AggregateQuery` computes some aggregation statistics from the result set of + * An `AggregateQuery` computes some aggregation statistics from the result set of * a base `Query`. */ export class AggregateQuery { @@ -44,7 +45,7 @@ export class AggregateQuery { } /** - * A `AggregateQuerySnapshot` contains results of a `AggregateQuery`. + * An `AggregateQuerySnapshot` contains results of a `AggregateQuery`. */ export class AggregateQuerySnapshot { readonly type = 'AggregateQuerySnapshot'; @@ -56,7 +57,7 @@ export class AggregateQuerySnapshot { } /** - * @return The result of a document count aggregation. Returns null if no count aggregation is + * @returns The result of a document count aggregation. Returns null if no count aggregation is * available in the result. */ getCount(): number | null { @@ -67,7 +68,7 @@ export class AggregateQuerySnapshot { /** * Creates an `AggregateQuery` counting the number of documents matching this query. * - * @return An `AggregateQuery` object that can be used to count the number of documents in + * @returns An `AggregateQuery` object that can be used to count the number of documents in * the result set of this query. */ export function countQuery(query: Query): AggregateQuery { @@ -75,35 +76,34 @@ export function countQuery(query: Query): AggregateQuery { } export function getAggregateFromServerDirect( - aggregateQuery: AggregateQuery + query: AggregateQuery ): Promise { - const firestore = cast(aggregateQuery.query.firestore, Firestore); + const firestore = cast(query.query.firestore, Firestore); const datastore = getDatastore(firestore); const userDataWriter = new LiteUserDataWriter(firestore); - return invokeRunAggregationQueryRpc(datastore, aggregateQuery).then( - result => { - hardAssert( - result[0] !== undefined, - 'Aggregation fields are missing from result.' - ); + return invokeRunAggregationQueryRpc(datastore, query).then(result => { + hardAssert( + result[0] !== undefined, + 'Aggregation fields are missing from result.' + ); - const countField = (result[0] as any).count_alias; - hardAssert( - countField !== undefined, - 'Count field is missing from result.' - ); + const counts = Object.entries(result[0]) + .filter(([key, value]) => key === 'count_alias') + .map(([key, value]) => userDataWriter.convertValue(value as Value)); - const countAggregateResult = userDataWriter.convertValue(countField); - hardAssert( - typeof countAggregateResult === 'number', - 'Count aggeragte field is not a number: ' + countAggregateResult - ); - return Promise.resolve( - new AggregateQuerySnapshot(aggregateQuery, countAggregateResult) - ); - } - ); + const count = counts[0]; + hardAssert( + count !== undefined, + 'count_alias property is missing from result.' + ); + hardAssert( + typeof count === 'number', + 'Count aggeragte field is not a number: ' + count + ); + + return Promise.resolve(new AggregateQuerySnapshot(query, count)); + }); } export function aggregateQueryEqual( diff --git a/packages/firestore/test/lite/integration.test.ts b/packages/firestore/test/lite/integration.test.ts index 470db41c30c..398175c195f 100644 --- a/packages/firestore/test/lite/integration.test.ts +++ b/packages/firestore/test/lite/integration.test.ts @@ -83,7 +83,6 @@ import { import { Timestamp } from '../../src/lite-api/timestamp'; import { runTransaction } from '../../src/lite-api/transaction'; import { writeBatch } from '../../src/lite-api/write_batch'; - import { DEFAULT_PROJECT_ID, DEFAULT_SETTINGS @@ -2058,12 +2057,10 @@ describe('countQuery()', () => { it('AggregateQuery and AggregateQuerySnapshot inherits the original query', () => { return withTestCollection(async coll => { const query_ = query(coll); - const countQuery_ = countQuery(query_); - expect(countQuery_.type).to.equal('AggregateQuery'); expect(countQuery_.query).to.equal(query_); - const snapshot = await getAggregateFromServerDirect(countQuery_); + expect(snapshot.query).to.equal(countQuery_); expect(snapshot.query.query).to.equal(query_); }); }); @@ -2071,13 +2068,12 @@ describe('countQuery()', () => { it('empty test collection count', () => { return withTestCollection(async coll => { const countQuery_ = countQuery(query(coll)); - const snapshot = await getAggregateFromServerDirect(countQuery_); expect(snapshot.getCount()).to.equal(0); }); }); - it('test collection count with sample docs ', () => { + it('test collection count with 5 docs', () => { return withTestCollectionAndInitialData(testDocs, async collection => { const countQuery_ = countQuery(query(collection)); const snapshot = await getAggregateFromServerDirect(countQuery_); From c6df2c0f8b680a5748e4a494e11ce5802b510140 Mon Sep 17 00:00:00 2001 From: milaGGL Date: Wed, 24 Aug 2022 18:48:17 +0000 Subject: [PATCH 23/24] Update API reports --- common/api-review/firestore.api.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/common/api-review/firestore.api.md b/common/api-review/firestore.api.md index cae95cd696f..35e21d31786 100644 --- a/common/api-review/firestore.api.md +++ b/common/api-review/firestore.api.md @@ -29,6 +29,7 @@ export function aggregateQueryEqual(left: AggregateQuery, right: AggregateQuery) // @public export class AggregateQuerySnapshot { + // (undocumented) getCount(): number | null; // (undocumented) readonly query: AggregateQuery; @@ -235,7 +236,7 @@ export class GeoPoint { } // @public (undocumented) -export function getAggregateFromServerDirect(aggregateQuery: AggregateQuery): Promise; +export function getAggregateFromServerDirect(query: AggregateQuery): Promise; // @public export function getDoc(reference: DocumentReference): Promise>; From 1a0cce302c37f523761f7c207368421dffc6c51c Mon Sep 17 00:00:00 2001 From: milaGGL <107142260+milaGGL@users.noreply.github.com> Date: Wed, 24 Aug 2022 13:04:48 -0700 Subject: [PATCH 24/24] Update aggregate.ts --- packages/firestore/src/lite-api/aggregate.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/firestore/src/lite-api/aggregate.ts b/packages/firestore/src/lite-api/aggregate.ts index f4f7920cb86..49770bb8293 100644 --- a/packages/firestore/src/lite-api/aggregate.ts +++ b/packages/firestore/src/lite-api/aggregate.ts @@ -33,8 +33,6 @@ export class AggregateQuery { readonly type = 'AggregateQuery'; /** * The query on which you called `countQuery` in order to get this `AggregateQuery`. - * Query type is set to unknown to avoid error caused by query type converter. - * might change it back to T after testing if the error do exist or not */ readonly query: Query; @@ -93,13 +91,9 @@ export function getAggregateFromServerDirect( .map(([key, value]) => userDataWriter.convertValue(value as Value)); const count = counts[0]; - hardAssert( - count !== undefined, - 'count_alias property is missing from result.' - ); hardAssert( typeof count === 'number', - 'Count aggeragte field is not a number: ' + count + 'Count aggeragte field value is not a number: ' + count ); return Promise.resolve(new AggregateQuerySnapshot(query, count));