Skip to content

Commit b35ae72

Browse files
author
Brian Chen
authored
Add clearPersistence(), separate functionality out from shutdown() (#1712)
1 parent dc70589 commit b35ae72

16 files changed

+164
-67
lines changed

packages/firestore/src/api/database.ts

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ import {
3838
} from '../core/query';
3939
import { Transaction as InternalTransaction } from '../core/transaction';
4040
import { ChangeType, ViewSnapshot } from '../core/view_snapshot';
41+
import { IndexedDbPersistence } from '../local/indexeddb_persistence';
4142
import { LruParams } from '../local/lru_garbage_collector';
4243
import { Document, MaybeDocument, NoDocument } from '../model/document';
4344
import { DocumentKey } from '../model/document_key';
@@ -307,13 +308,16 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
307308
// are already set to synchronize on the async queue.
308309
private _firestoreClient: FirestoreClient | undefined;
309310

311+
private clientRunning: boolean;
312+
310313
// Public for use in tests.
311314
// TODO(mikelehen): Use modularized initialization instead.
312315
readonly _queue = new AsyncQueue();
313316

314317
_dataConverter: UserDataConverter;
315318

316319
constructor(databaseIdOrApp: FirestoreDatabase | FirebaseApp) {
320+
this.clientRunning = false;
317321
const config = new FirestoreConfig();
318322
if (typeof (databaseIdOrApp as FirebaseApp).options === 'object') {
319323
// This is very likely a Firebase app object
@@ -406,6 +410,19 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
406410
);
407411
}
408412

413+
_clearPersistence(): Promise<void> {
414+
if (this.clientRunning) {
415+
throw new FirestoreError(
416+
Code.FAILED_PRECONDITION,
417+
'Persistence cannot be cleared while the client is running'
418+
);
419+
}
420+
const persistenceKey = IndexedDbPersistence.buildStoragePrefix(
421+
this.makeDatabaseInfo()
422+
);
423+
return IndexedDbPersistence.clearPersistence(persistenceKey);
424+
}
425+
409426
ensureClientConfigured(): FirestoreClient {
410427
if (!this._firestoreClient) {
411428
// Kick off starting the client but don't actually wait for it.
@@ -415,6 +432,16 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
415432
return this._firestoreClient as FirestoreClient;
416433
}
417434

435+
private makeDatabaseInfo(): DatabaseInfo {
436+
return new DatabaseInfo(
437+
this._config.databaseId,
438+
this._config.persistenceKey,
439+
this._config.settings.host,
440+
this._config.settings.ssl,
441+
this._config.settings.forceLongPolling
442+
);
443+
}
444+
418445
private configureClient(
419446
persistenceSettings: InternalPersistenceSettings
420447
): Promise<void> {
@@ -425,13 +452,8 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
425452

426453
assert(!this._firestoreClient, 'configureClient() called multiple times');
427454

428-
const databaseInfo = new DatabaseInfo(
429-
this._config.databaseId,
430-
this._config.persistenceKey,
431-
this._config.settings.host,
432-
this._config.settings.ssl,
433-
this._config.settings.forceLongPolling
434-
);
455+
this.clientRunning = true;
456+
const databaseInfo = this.makeDatabaseInfo();
435457

436458
const preConverter = (value: unknown) => {
437459
if (value instanceof DocumentReference) {
@@ -458,6 +480,7 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
458480
this._config.credentials,
459481
this._queue
460482
);
483+
461484
return this._firestoreClient.start(persistenceSettings);
462485
}
463486

@@ -492,13 +515,12 @@ export class Firestore implements firestore.FirebaseFirestore, FirebaseService {
492515
}
493516

494517
INTERNAL = {
495-
delete: async (options?: {
496-
purgePersistenceWithDataLoss?: boolean;
497-
}): Promise<void> => {
518+
delete: async (): Promise<void> => {
498519
// The client must be initalized to ensure that all subsequent API usage
499520
// throws an exception.
500521
this.ensureClientConfigured();
501-
return this._firestoreClient!.shutdown(options);
522+
await this._firestoreClient!.shutdown();
523+
this.clientRunning = false;
502524
}
503525
};
504526

packages/firestore/src/core/firestore_client.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -488,9 +488,7 @@ export class FirestoreClient {
488488
});
489489
}
490490

491-
shutdown(options?: {
492-
purgePersistenceWithDataLoss?: boolean;
493-
}): Promise<void> {
491+
shutdown(): Promise<void> {
494492
if (this.isShutdown === true) {
495493
return Promise.resolve();
496494
}
@@ -501,9 +499,7 @@ export class FirestoreClient {
501499
}
502500
await this.remoteStore.shutdown();
503501
await this.sharedClientState.shutdown();
504-
await this.persistence.shutdown(
505-
options && options.purgePersistenceWithDataLoss
506-
);
502+
await this.persistence.shutdown();
507503

508504
// `removeChangeListener` must be called after shutting down the
509505
// RemoteStore as it will prevent the RemoteStore from retrieving

packages/firestore/src/local/indexeddb_persistence.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -671,7 +671,7 @@ export class IndexedDbPersistence implements Persistence {
671671
});
672672
}
673673

674-
async shutdown(deleteData?: boolean): Promise<void> {
674+
async shutdown(): Promise<void> {
675675
// The shutdown() operations are idempotent and can be called even when
676676
// start() aborted (e.g. because it couldn't acquire the persistence lease).
677677
this._started = false;
@@ -696,9 +696,6 @@ export class IndexedDbPersistence implements Persistence {
696696
// Remove the entry marking the client as zombied from LocalStorage since
697697
// we successfully deleted its metadata from IndexedDb.
698698
this.removeClientZombiedEntry();
699-
if (deleteData) {
700-
await SimpleDb.delete(this.dbName);
701-
}
702699
}
703700

704701
/**
@@ -732,6 +729,14 @@ export class IndexedDbPersistence implements Persistence {
732729
);
733730
}
734731

732+
static async clearPersistence(persistenceKey: string): Promise<void> {
733+
if (!IndexedDbPersistence.isAvailable()) {
734+
return Promise.resolve();
735+
}
736+
const dbName = persistenceKey + IndexedDbPersistence.MAIN_DATABASE;
737+
await SimpleDb.delete(dbName);
738+
}
739+
735740
get started(): boolean {
736741
return this._started;
737742
}

packages/firestore/src/local/memory_persistence.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export class MemoryPersistence implements Persistence {
114114
);
115115
}
116116

117-
shutdown(deleteData?: boolean): Promise<void> {
117+
shutdown(): Promise<void> {
118118
// No durable state to ensure is closed on shutdown.
119119
this._started = false;
120120
return Promise.resolve();

packages/firestore/src/local/persistence.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,8 @@ export interface Persistence {
151151

152152
/**
153153
* Releases any resources held during eager shutdown.
154-
*
155-
* @param deleteData Whether to delete the persisted data. This causes
156-
* irrecoverable data loss and should only be used to delete test data.
157154
*/
158-
shutdown(deleteData?: boolean): Promise<void>;
155+
shutdown(): Promise<void>;
159156

160157
/**
161158
* Registers a listener that gets called when the primary state of the

packages/firestore/test/integration/api/database.test.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { EventsAccumulator } from '../util/events_accumulator';
2929
import firebase from '../util/firebase_export';
3030
import {
3131
apiDescribe,
32+
clearPersistence,
3233
withTestCollection,
3334
withTestDb,
3435
withTestDbs,
@@ -928,6 +929,57 @@ apiDescribe('Database', persistence => {
928929
});
929930
});
930931

932+
(persistence ? it : it.skip)(
933+
'maintains persistence after restarting app',
934+
async () => {
935+
await withTestDoc(persistence, async docRef => {
936+
await docRef.set({ foo: 'bar' });
937+
const app = docRef.firestore.app;
938+
const name = app.name;
939+
const options = app.options;
940+
941+
await app.delete();
942+
const app2 = firebase.initializeApp(options, name);
943+
const firestore2 = firebase.firestore!(app2);
944+
await firestore2.enablePersistence();
945+
const docRef2 = firestore2.doc(docRef.path);
946+
const docSnap2 = await docRef2.get({ source: 'cache' });
947+
expect(docSnap2.exists).to.be.true;
948+
});
949+
}
950+
);
951+
952+
(persistence ? it : it.skip)(
953+
'can clear persistence if the client has not been initialized',
954+
async () => {
955+
await withTestDoc(persistence, async docRef => {
956+
await docRef.set({ foo: 'bar' });
957+
const app = docRef.firestore.app;
958+
const name = app.name;
959+
const options = app.options;
960+
961+
await app.delete();
962+
await clearPersistence(docRef.firestore);
963+
const app2 = firebase.initializeApp(options, name);
964+
const firestore2 = firebase.firestore!(app2);
965+
await firestore2.enablePersistence();
966+
const docRef2 = firestore2.doc(docRef.path);
967+
await expect(
968+
docRef2.get({ source: 'cache' })
969+
).to.eventually.be.rejectedWith('Failed to get document from cache.');
970+
});
971+
}
972+
);
973+
974+
it('can not clear persistence if the client has been initialized', async () => {
975+
await withTestDoc(persistence, async docRef => {
976+
const firestore = docRef.firestore;
977+
await expect(clearPersistence(firestore)).to.eventually.be.rejectedWith(
978+
'Persistence cannot be cleared while the client is running'
979+
);
980+
});
981+
});
982+
931983
it('can get documents while offline', async () => {
932984
await withTestDoc(persistence, async docRef => {
933985
const firestore = docRef.firestore;

packages/firestore/test/integration/util/helpers.ts

Lines changed: 23 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import * as firestore from '@firebase/firestore-types';
19+
import { clearTestPersistence } from './../../unit/local/persistence_test_helpers';
1920
import firebase from './firebase_export';
2021

2122
/**
@@ -105,6 +106,14 @@ function apiDescribeInternal(
105106
}
106107
}
107108

109+
// TODO(b/131094514): Remove after clearPersistence() is updated in index.d.ts.
110+
export async function clearPersistence(
111+
firestore: firestore.FirebaseFirestore
112+
): Promise<void> {
113+
// tslint:disable-next-line:no-any
114+
await (firestore as any)._clearPersistence();
115+
}
116+
108117
/** Converts the documents in a QuerySnapshot to an array with the data of each document. */
109118
export function toDataArray(
110119
docSet: firestore.QuerySnapshot
@@ -176,7 +185,7 @@ export function withTestDbs(
176185

177186
let appCount = 0;
178187

179-
export function withTestDbsSettings(
188+
export async function withTestDbsSettings(
180189
persistence: boolean,
181190
projectId: string,
182191
settings: firestore.Settings,
@@ -208,32 +217,19 @@ export function withTestDbsSettings(
208217
promises.push(ready);
209218
}
210219

211-
return Promise.all(promises).then((dbs: firestore.FirebaseFirestore[]) => {
212-
const cleanup = () => {
213-
return wipeDb(dbs[0]).then(() =>
214-
dbs.reduce(
215-
(chain, db) =>
216-
chain.then(
217-
db.INTERNAL.delete.bind(this, {
218-
purgePersistenceWithDataLoss: true
219-
})
220-
),
221-
Promise.resolve()
222-
)
223-
);
224-
};
225-
226-
return fn(dbs).then(
227-
() => cleanup(),
228-
err => {
229-
// Do cleanup but propagate original error.
230-
return cleanup().then(
231-
() => Promise.reject(err),
232-
() => Promise.reject(err)
233-
);
234-
}
235-
);
236-
});
220+
const dbs = await Promise.all(promises);
221+
222+
try {
223+
await fn(dbs);
224+
} finally {
225+
await wipeDb(dbs[0]);
226+
for (const db of dbs) {
227+
await db.INTERNAL.delete();
228+
}
229+
if (persistence) {
230+
await clearTestPersistence();
231+
}
232+
}
237233
}
238234

239235
export function withTestDoc(

packages/firestore/test/unit/local/index_manager.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ function genericIndexManagerTests(
6262

6363
afterEach(async () => {
6464
if (persistence.started) {
65-
await persistence.shutdown(/* deleteData= */ true);
65+
await persistence.shutdown();
66+
await persistenceHelpers.clearTestPersistence();
6667
}
6768
});
6869

packages/firestore/test/unit/local/local_store.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,7 +301,10 @@ function genericLocalStoreTests(
301301
localStore = new LocalStore(persistence, User.UNAUTHENTICATED);
302302
});
303303

304-
afterEach(() => persistence.shutdown(/* deleteData= */ true));
304+
afterEach(async () => {
305+
await persistence.shutdown();
306+
await persistenceHelpers.clearTestPersistence();
307+
});
305308

306309
function expectLocalStore(): LocalStoreTester {
307310
return new LocalStoreTester(localStore, gcIsEager);

packages/firestore/test/unit/local/lru_garbage_collector.test.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,10 @@ function genericLruGarbageCollectorTests(
8787
});
8888

8989
afterEach(async () => {
90-
await queue.enqueue(() => persistence.shutdown(/* deleteData= */ true));
90+
await queue.enqueue(async () => {
91+
await persistence.shutdown();
92+
await PersistenceTestHelpers.clearTestPersistence();
93+
});
9194
});
9295

9396
let persistence: Persistence;
@@ -102,7 +105,10 @@ function genericLruGarbageCollectorTests(
102105
params: LruParams = LruParams.DEFAULT
103106
): Promise<void> {
104107
if (persistence && persistence.started) {
105-
await queue.enqueue(() => persistence.shutdown(/* deleteData= */ true));
108+
await queue.enqueue(async () => {
109+
await persistence.shutdown();
110+
await PersistenceTestHelpers.clearTestPersistence();
111+
});
106112
}
107113
lruParams = params;
108114
persistence = await newPersistence(params, queue);

packages/firestore/test/unit/local/mutation_queue.test.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,10 @@ function genericMutationQueueTests(): void {
7878
);
7979
});
8080

81-
afterEach(() => persistence.shutdown(/* deleteData= */ true));
81+
afterEach(async () => {
82+
await persistence.shutdown();
83+
await persistenceHelpers.clearTestPersistence();
84+
});
8285

8386
/**
8487
* Creates a new MutationBatch with the next batch ID and a set of dummy

packages/firestore/test/unit/local/persistence_test_helpers.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,11 @@ export async function testMemoryLruPersistence(
142142
);
143143
}
144144

145+
/** Clears the persistence in tests */
146+
export async function clearTestPersistence(): Promise<void> {
147+
await IndexedDbPersistence.clearPersistence(TEST_PERSISTENCE_PREFIX);
148+
}
149+
145150
class NoOpSharedClientStateSyncer implements SharedClientStateSyncer {
146151
constructor(private readonly activeClients: ClientId[]) {}
147152
async applyBatchState(

0 commit comments

Comments
 (0)