diff --git a/packages/firestore/CHANGELOG.md b/packages/firestore/CHANGELOG.md index cfd5e61bb2e..acee329a12e 100644 --- a/packages/firestore/CHANGELOG.md +++ b/packages/firestore/CHANGELOG.md @@ -1,4 +1,9 @@ -# Unreleased (1.5.0) +# Unreleased +- [fixed] Fixed a regression that caused queries with nested field filters to + crash the client if the field was not present in the local copy of the + document. + +# 1.5.0 - [feature] Added a `Firestore.waitForPendingWrites()` method that allows users to wait until all pending writes are acknowledged by the Firestore backend. diff --git a/packages/firestore/src/model/document.ts b/packages/firestore/src/model/document.ts index 55c6a55d657..9d3ae2d5027 100644 --- a/packages/firestore/src/model/document.ts +++ b/packages/firestore/src/model/document.ts @@ -120,7 +120,7 @@ export class Document extends MaybeDocument { data(): ObjectValue { if (!this.objectValue) { let result = ObjectValue.EMPTY; - obj.forEach(this.proto!.fields, (key: string, value: api.Value) => { + obj.forEach(this.proto!.fields || {}, (key: string, value: api.Value) => { result = result.set(new FieldPath([key]), this.converter!(value)); }); this.objectValue = result; @@ -170,11 +170,11 @@ export class Document extends MaybeDocument { 'Can only call getProtoField() when proto is defined' ); - let protoValue: api.Value | undefined = this.proto!.fields[ - path.firstSegment() - ]; + let protoValue: api.Value | undefined = this.proto!.fields + ? this.proto!.fields[path.firstSegment()] + : undefined; for (let i = 1; i < path.length; ++i) { - if (!protoValue || !protoValue.mapValue) { + if (!protoValue || !protoValue.mapValue || !protoValue.mapValue.fields) { return undefined; } protoValue = protoValue.mapValue.fields[path.get(i)]; diff --git a/packages/firestore/src/protos/firestore_proto_api.d.ts b/packages/firestore/src/protos/firestore_proto_api.d.ts index d7255a6e4a4..bad793d4716 100644 --- a/packages/firestore/src/protos/firestore_proto_api.d.ts +++ b/packages/firestore/src/protos/firestore_proto_api.d.ts @@ -22,8 +22,10 @@ @typescript-eslint/interface-name-prefix, @typescript-eslint/class-name-casing */ export declare type ApiClientHookFactory = any; -export declare type ApiClientObjectMap = any; export declare type PromiseRequestService = any; +export interface ApiClientObjectMap { + [k: string]: T; +} export declare type CompositeFilterOp = 'OPERATOR_UNSPECIFIED' | 'AND'; export interface ICompositeFilterOpEnum { diff --git a/packages/firestore/test/integration/api/query.test.ts b/packages/firestore/test/integration/api/query.test.ts index e07a9eb7dee..0c7cbd3ae9e 100644 --- a/packages/firestore/test/integration/api/query.test.ts +++ b/packages/firestore/test/integration/api/query.test.ts @@ -819,4 +819,20 @@ apiDescribe('Queries', (persistence: boolean) => { } }); }); + + it('can use filter with nested field', () => { + // Reproduces https://github.com/firebase/firebase-js-sdk/issues/2204 + const testDocs = { + a: {}, + b: { map: {} }, + c: { map: { nested: {} } }, + d: { map: { nested: 'foo' } } + }; + + return withTestCollection(persistence, testDocs, async coll => { + await coll.get(); // Populate the cache + const snapshot = await coll.where('map.nested', '==', 'foo').get(); + expect(toDataArray(snapshot)).to.deep.equal([{ map: { nested: 'foo' } }]); + }); + }); });