Skip to content

Commit 6061ff6

Browse files
fix(firestore): Fix PreferRest caching (#2040)
Fixed a caching issue when a non-empty FirestoreSettings object is passed to initializeFirestore(...)
1 parent c81f572 commit 6061ff6

File tree

3 files changed

+78
-44
lines changed

3 files changed

+78
-44
lines changed

src/firestore/firestore-internal.ts

Lines changed: 28 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -53,35 +53,43 @@ export class FirestoreService {
5353
this.appInternal = app;
5454
}
5555

56-
getDatabase(databaseId: string, settings?: FirestoreSettings): Firestore {
57-
settings ??= {};
56+
initializeDatabase(databaseId: string, settings: FirestoreSettings): Firestore {
57+
const existingInstance = this.databases.get(databaseId);
58+
if (existingInstance) {
59+
const initialSettings = this.firestoreSettings.get(databaseId) ?? {};
60+
if (this.checkIfSameSettings(settings, initialSettings)) {
61+
return existingInstance;
62+
}
63+
throw new FirebaseFirestoreError({
64+
code: 'failed-precondition',
65+
message: 'initializeFirestore() has already been called with ' +
66+
'different options. To avoid this error, call initializeFirestore() with the ' +
67+
'same options as when it was originally called, or call getFirestore() to return the' +
68+
' already initialized instance.'
69+
});
70+
}
71+
const newInstance = initFirestore(this.app, databaseId, settings);
72+
this.databases.set(databaseId, newInstance);
73+
this.firestoreSettings.set(databaseId, settings);
74+
return newInstance;
75+
}
76+
77+
getDatabase(databaseId: string): Firestore {
5878
let database = this.databases.get(databaseId);
5979
if (database === undefined) {
60-
database = initFirestore(this.app, databaseId, settings);
80+
database = initFirestore(this.app, databaseId, {});
6181
this.databases.set(databaseId, database);
62-
this.firestoreSettings.set(databaseId, settings);
63-
} else {
64-
if (!this.checkIfSameSettings(databaseId, settings)) {
65-
throw new FirebaseFirestoreError({
66-
code: 'failed-precondition',
67-
message: 'initializeFirestore() has already been called with ' +
68-
'different options. To avoid this error, call initializeFirestore() with the ' +
69-
'same options as when it was originally called, or call getFirestore() to return the' +
70-
' already initialized instance.'
71-
});
72-
}
82+
this.firestoreSettings.set(databaseId, {});
7383
}
7484
return database;
7585
}
7686

77-
private checkIfSameSettings(databaseId: string, firestoreSettings: FirestoreSettings): boolean {
87+
private checkIfSameSettings(settingsA: FirestoreSettings, settingsB: FirestoreSettings): boolean {
88+
const a = settingsA ?? {};
89+
const b = settingsB ?? {};
7890
// If we start passing more settings to Firestore constructor,
7991
// replace this with deep equality check.
80-
const existingSettings = this.firestoreSettings.get(databaseId);
81-
if (!existingSettings) {
82-
return true;
83-
}
84-
return (existingSettings.preferRest === firestoreSettings.preferRest);
92+
return (a.preferRest === b.preferRest);
8593
}
8694

8795
/**

src/firestore/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,5 +184,5 @@ export function initializeFirestore(
184184
const firestoreService = firebaseApp.getOrInitService(
185185
'firestore', (app) => new FirestoreService(app));
186186

187-
return firestoreService.getDatabase(databaseId, settings);
187+
return firestoreService.initializeDatabase(databaseId, settings);
188188
}

test/unit/firestore/index.spec.ts

Lines changed: 49 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,17 @@ chai.use(chaiAsPromised);
3333
const expect = chai.expect;
3434

3535
describe('Firestore', () => {
36-
let mockApp: App;
36+
let mockAppOne: App;
37+
let mockAppTwo: App;
3738
let mockCredentialApp: App;
3839

3940
const noProjectIdError = 'Failed to initialize Google Cloud Firestore client with the '
4041
+ 'available credentials. Must initialize the SDK with a certificate credential or '
4142
+ 'application default credentials to use Cloud Firestore API.';
4243

4344
beforeEach(() => {
44-
mockApp = mocks.app();
45+
mockAppOne = mocks.app();
46+
mockAppTwo = mocks.app();
4547
mockCredentialApp = mocks.mockCredentialApp();
4648
});
4749

@@ -61,26 +63,26 @@ describe('Firestore', () => {
6163

6264
it('should not throw given a valid app', () => {
6365
expect(() => {
64-
return getFirestore(mockApp);
66+
return getFirestore(mockAppOne);
6567
}).not.to.throw();
6668
});
6769

6870
it('should return the same instance for a given app instance', () => {
69-
const db1: Firestore = getFirestore(mockApp);
70-
const db2: Firestore = getFirestore(mockApp, DEFAULT_DATABASE_ID);
71+
const db1: Firestore = getFirestore(mockAppOne);
72+
const db2: Firestore = getFirestore(mockAppOne, DEFAULT_DATABASE_ID);
7173
expect(db1).to.equal(db2);
7274
});
7375

7476
it('should return the same instance for a given app instance and databaseId', () => {
75-
const db1: Firestore = getFirestore(mockApp, 'db');
76-
const db2: Firestore = getFirestore(mockApp, 'db');
77+
const db1: Firestore = getFirestore(mockAppOne, 'db');
78+
const db2: Firestore = getFirestore(mockAppOne, 'db');
7779
expect(db1).to.equal(db2);
7880
});
7981

8082
it('should return the different instance for given same app instance, but different databaseId', () => {
81-
const db0: Firestore = getFirestore(mockApp, DEFAULT_DATABASE_ID);
82-
const db1: Firestore = getFirestore(mockApp, 'db1');
83-
const db2: Firestore = getFirestore(mockApp, 'db2');
83+
const db0: Firestore = getFirestore(mockAppOne, DEFAULT_DATABASE_ID);
84+
const db1: Firestore = getFirestore(mockAppOne, 'db1');
85+
const db2: Firestore = getFirestore(mockAppOne, 'db2');
8486
expect(db0).to.not.equal(db1);
8587
expect(db0).to.not.equal(db2);
8688
expect(db1).to.not.equal(db2);
@@ -97,41 +99,65 @@ describe('Firestore', () => {
9799

98100
it('should not throw given a valid app', () => {
99101
expect(() => {
100-
return initializeFirestore(mockApp);
102+
return initializeFirestore(mockAppOne);
101103
}).not.to.throw();
102104
});
103105

104106
it('should return the same instance for a given app instance', () => {
105-
const db1: Firestore = initializeFirestore(mockApp);
106-
const db2: Firestore = initializeFirestore(mockApp, {}, DEFAULT_DATABASE_ID);
107+
const db1: Firestore = initializeFirestore(mockAppOne);
108+
const db2: Firestore = initializeFirestore(mockAppOne, {}, DEFAULT_DATABASE_ID);
109+
110+
const db3: Firestore = initializeFirestore(mockAppTwo, { preferRest: true });
111+
const db4: Firestore = initializeFirestore(mockAppTwo, { preferRest: true }, DEFAULT_DATABASE_ID);
112+
107113
expect(db1).to.equal(db2);
114+
expect(db3).to.equal(db4);
108115
});
109116

110117
it('should return the same instance for a given app instance and databaseId', () => {
111-
const db1: Firestore = initializeFirestore(mockApp, {}, 'db');
112-
const db2: Firestore = initializeFirestore(mockApp, {}, 'db');
118+
const db1: Firestore = initializeFirestore(mockAppOne, {}, 'db');
119+
const db2: Firestore = initializeFirestore(mockAppOne, {}, 'db');
120+
121+
const db3: Firestore = initializeFirestore(mockAppTwo, { preferRest: true }, 'db');
122+
const db4: Firestore = initializeFirestore(mockAppTwo, { preferRest: true }, 'db');
123+
113124
expect(db1).to.equal(db2);
125+
expect(db3).to.equal(db4);
114126
});
115127

116-
it('should return the different instance for given same app instance, but different databaseId', () => {
117-
const db0: Firestore = initializeFirestore(mockApp, {}, DEFAULT_DATABASE_ID);
118-
const db1: Firestore = initializeFirestore(mockApp, {}, 'db1');
119-
const db2: Firestore = initializeFirestore(mockApp, {}, 'db2');
128+
it('should return a different instance for given same app instance, but different databaseId', () => {
129+
const db0: Firestore = initializeFirestore(mockAppOne, {}, DEFAULT_DATABASE_ID);
130+
const db1: Firestore = initializeFirestore(mockAppOne, {}, 'db1');
131+
const db2: Firestore = initializeFirestore(mockAppOne, {}, 'db2');
132+
133+
const db3: Firestore = initializeFirestore(mockAppTwo, { preferRest: true }, DEFAULT_DATABASE_ID);
134+
const db4: Firestore = initializeFirestore(mockAppTwo, { preferRest: true }, 'db1');
135+
const db5: Firestore = initializeFirestore(mockAppTwo, { preferRest: true }, 'db2');
136+
120137
expect(db0).to.not.equal(db1);
121138
expect(db0).to.not.equal(db2);
122139
expect(db1).to.not.equal(db2);
140+
141+
expect(db3).to.not.equal(db4);
142+
expect(db3).to.not.equal(db5);
143+
expect(db4).to.not.equal(db5);
123144
});
124145

125146
it('getFirestore should return the same instance as initializeFirestore returned earlier', () => {
126-
const db1: Firestore = initializeFirestore(mockApp, {}, 'db');
127-
const db2: Firestore = getFirestore(mockApp, 'db');
147+
const db1: Firestore = initializeFirestore(mockAppOne, {}, 'db');
148+
const db2: Firestore = getFirestore(mockAppOne, 'db');
149+
150+
const db3: Firestore = initializeFirestore(mockAppTwo, { preferRest: true });
151+
const db4: Firestore = getFirestore(mockAppTwo);
152+
128153
expect(db1).to.equal(db2);
154+
expect(db3).to.equal(db4);
129155
});
130156

131157
it('initializeFirestore should not allow create an instance with different settings', () => {
132-
initializeFirestore(mockApp, {}, 'db');
158+
initializeFirestore(mockAppTwo, {}, 'db');
133159
expect(() => {
134-
return initializeFirestore(mockApp, { preferRest: true }, 'db');
160+
return initializeFirestore(mockAppTwo, { preferRest: true }, 'db');
135161
}).to.throw(/has already been called with different options/);
136162
});
137163
});

0 commit comments

Comments
 (0)