Skip to content

Commit f04e24f

Browse files
authored
Merge 5a159f3 into 5f6304d
2 parents 5f6304d + 5a159f3 commit f04e24f

12 files changed

+274
-0
lines changed

.changeset/healthy-peas-heal.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@firebase/firestore': patch
3+
'firebase': patch
4+
---
5+
6+
Implemented internal logic to delete all client-side indexes

packages/firestore/src/api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,7 @@ export {
205205
export {
206206
PersistentCacheIndexManager,
207207
getPersistentCacheIndexManager,
208+
deleteAllPersistentCacheIndexes,
208209
enablePersistentCacheIndexAutoCreation,
209210
disablePersistentCacheIndexAutoCreation
210211
} from './api/persistent_cache_index_manager';

packages/firestore/src/api/persistent_cache_index_manager.ts

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

1818
import {
19+
firestoreClientDeleteAllFieldIndexes,
1920
firestoreClientSetPersistentCacheIndexAutoCreationEnabled,
2021
FirestoreClient
2122
} from '../core/firestore_client';
@@ -99,6 +100,29 @@ export function disablePersistentCacheIndexAutoCreation(
99100
setPersistentCacheIndexAutoCreationEnabled(indexManager, false);
100101
}
101102

103+
/**
104+
* Removes all persistent cache indexes.
105+
*
106+
* Please note this function will also deletes indexes generated by
107+
* `setIndexConfiguration()`, which is deprecated.
108+
*
109+
* TODO(CSI) Remove @internal to make the API publicly available.
110+
* @internal
111+
*/
112+
export function deleteAllPersistentCacheIndexes(
113+
indexManager: PersistentCacheIndexManager
114+
): void {
115+
indexManager._client.verifyNotTerminated();
116+
117+
const promise = firestoreClientDeleteAllFieldIndexes(indexManager._client);
118+
119+
promise
120+
.then(_ => logDebug('deleting all persistent cache indexes succeeded'))
121+
.catch(error =>
122+
logWarn('deleting all persistent cache indexes failed', error)
123+
);
124+
}
125+
102126
function setPersistentCacheIndexAutoCreationEnabled(
103127
indexManager: PersistentCacheIndexManager,
104128
isEnabled: boolean

packages/firestore/src/core/firestore_client.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { User } from '../auth/user';
2626
import { LocalStore } from '../local/local_store';
2727
import {
2828
localStoreConfigureFieldIndexes,
29+
localStoreDeleteAllFieldIndexes,
2930
localStoreExecuteQuery,
3031
localStoreGetNamedQuery,
3132
localStoreHandleUserChange,
@@ -841,3 +842,11 @@ export function firestoreClientSetPersistentCacheIndexAutoCreationEnabled(
841842
);
842843
});
843844
}
845+
846+
export function firestoreClientDeleteAllFieldIndexes(
847+
client: FirestoreClient
848+
): Promise<void> {
849+
return client.asyncQueue.enqueue(async () => {
850+
return localStoreDeleteAllFieldIndexes(await getLocalStore(client));
851+
});
852+
}

packages/firestore/src/local/index_manager.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ export interface IndexManager {
105105
index: FieldIndex
106106
): PersistencePromise<void>;
107107

108+
/** Removes all field indexes and deletes all index values. */
109+
deleteAllFieldIndexes(
110+
transaction: PersistenceTransaction
111+
): PersistencePromise<void>;
112+
108113
/** Creates a full matched field index which serves the given target. */
109114
createTargetIndexes(
110115
transaction: PersistenceTransaction,

packages/firestore/src/local/indexeddb_index_manager.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,19 @@ export class IndexedDbIndexManager implements IndexManager {
252252
);
253253
}
254254

255+
deleteAllFieldIndexes(
256+
transaction: PersistenceTransaction
257+
): PersistencePromise<void> {
258+
const indexes = indexConfigurationStore(transaction);
259+
const entries = indexEntriesStore(transaction);
260+
const states = indexStateStore(transaction);
261+
262+
return indexes
263+
.deleteAll()
264+
.next(() => entries.deleteAll())
265+
.next(() => states.deleteAll());
266+
}
267+
255268
createTargetIndexes(
256269
transaction: PersistenceTransaction,
257270
target: Target

packages/firestore/src/local/local_store_impl.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1535,6 +1535,18 @@ export function localStoreSetIndexAutoCreationEnabled(
15351535
localStoreImpl.queryEngine.indexAutoCreationEnabled = isEnabled;
15361536
}
15371537

1538+
export function localStoreDeleteAllFieldIndexes(
1539+
localStore: LocalStore
1540+
): Promise<void> {
1541+
const localStoreImpl = debugCast(localStore, LocalStoreImpl);
1542+
const indexManager = localStoreImpl.indexManager;
1543+
return localStoreImpl.persistence.runTransaction(
1544+
'Delete All Indexes',
1545+
'readwrite',
1546+
transaction => indexManager.deleteAllFieldIndexes(transaction)
1547+
);
1548+
}
1549+
15381550
/**
15391551
* Test-only hooks into the SDK for use exclusively by tests.
15401552
*/

packages/firestore/src/local/memory_index_manager.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,13 @@ export class MemoryIndexManager implements IndexManager {
6666
return PersistencePromise.resolve();
6767
}
6868

69+
deleteAllFieldIndexes(
70+
transaction: PersistenceTransaction
71+
): PersistencePromise<void> {
72+
// Field indices are not supported with memory persistence.
73+
return PersistencePromise.resolve();
74+
}
75+
6976
createTargetIndexes(
7077
transaction: PersistenceTransaction,
7178
target: Target

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

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import { expect } from 'chai';
1919

2020
import {
21+
deleteAllPersistentCacheIndexes,
2122
disablePersistentCacheIndexAutoCreation,
2223
doc,
2324
enablePersistentCacheIndexAutoCreation,
@@ -149,4 +150,66 @@ apiDescribe('PersistentCacheIndexManager', persistence => {
149150
});
150151
});
151152
});
153+
154+
describe('delete all persistent cache indexes', () => {
155+
it('deleteAllPersistentCacheIndexes() on new instance should succeed', () =>
156+
withTestDb(persistence, async db => {
157+
const indexManager = getPersistentCacheIndexManager(db)!;
158+
deleteAllPersistentCacheIndexes(indexManager);
159+
}));
160+
161+
it('deleteAllPersistentCacheIndexes() should be successful when auto-indexing is enabled', () =>
162+
withTestDb(persistence, async db => {
163+
const indexManager = getPersistentCacheIndexManager(db)!;
164+
enablePersistentCacheIndexAutoCreation(indexManager);
165+
deleteAllPersistentCacheIndexes(indexManager);
166+
}));
167+
168+
it('deleteAllPersistentCacheIndexes() should be successful when auto-indexing is disabled', () =>
169+
withTestDb(persistence, async db => {
170+
const indexManager = getPersistentCacheIndexManager(db)!;
171+
enablePersistentCacheIndexAutoCreation(indexManager);
172+
disablePersistentCacheIndexAutoCreation(indexManager);
173+
deleteAllPersistentCacheIndexes(indexManager);
174+
}));
175+
176+
it('deleteAllPersistentCacheIndexes() after terminate() should throw', () =>
177+
withTestDb(persistence, async db => {
178+
const indexManager = getPersistentCacheIndexManager(db)!;
179+
terminate(db).catch(e => expect.fail(`terminate() failed: ${e}`));
180+
expect(() => deleteAllPersistentCacheIndexes(indexManager)).to.throw(
181+
'The client has already been terminated.'
182+
);
183+
}));
184+
185+
it('query returns correct results when auto-created index has been deleted', () => {
186+
const testDocs = partitionedTestDocs({
187+
matching: { documentData: { match: true }, documentCount: 1 },
188+
nonmatching: { documentData: { match: false }, documentCount: 100 }
189+
});
190+
return withTestCollection(persistence, testDocs, async (coll, db) => {
191+
const indexManager = getPersistentCacheIndexManager(db)!;
192+
enablePersistentCacheIndexAutoCreation(indexManager);
193+
194+
// Populate the local cache with the entire collection's contents.
195+
await getDocs(coll);
196+
197+
// Run a query that matches only one of the documents in the collection;
198+
// this should cause an index to be auto-created.
199+
const query_ = query(coll, where('match', '==', true));
200+
const snapshot1 = await getDocsFromCache(query_);
201+
expect(snapshot1.size).to.equal(1);
202+
203+
// Delete the index
204+
deleteAllPersistentCacheIndexes(indexManager);
205+
206+
// Run the query that matches only one of the documents again, which
207+
// should _still_ return the one and only document that matches. Since
208+
// the public API surface does not reveal whether an index was used,
209+
// there isn't anything else that can be verified.
210+
const snapshot2 = await getDocsFromCache(query_);
211+
expect(snapshot2.size).to.equal(1);
212+
});
213+
});
214+
});
152215
});

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1734,6 +1734,21 @@ describe('IndexedDbIndexManager', async () => {
17341734
await validateIsFullIndex(query_);
17351735
});
17361736

1737+
it('deleteAllFieldIndexes() deletes all indexes', async () => {
1738+
// Create some indexes.
1739+
const query1 = queryWithAddedFilter(query('coll'), filter('a', '==', 42));
1740+
await indexManager.createTargetIndexes(queryToTarget(query1));
1741+
await validateIsFullIndex(query1);
1742+
const query2 = queryWithAddedFilter(query('coll'), filter('b', '==', 42));
1743+
await indexManager.createTargetIndexes(queryToTarget(query2));
1744+
await validateIsFullIndex(query2);
1745+
1746+
// Verify that deleteAllFieldIndexes() deletes the indexes.
1747+
await indexManager.deleteAllFieldIndexes();
1748+
await validateIsNoneIndex(query1);
1749+
await validateIsNoneIndex(query2);
1750+
});
1751+
17371752
async function validateIsPartialIndex(query: Query): Promise<void> {
17381753
await validateIndexType(query, IndexType.PARTIAL);
17391754
}

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

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ import {
3939
localStoreAllocateTarget,
4040
localStoreApplyBundledDocuments,
4141
localStoreApplyRemoteEventToLocalCache,
42+
localStoreConfigureFieldIndexes,
43+
localStoreDeleteAllFieldIndexes,
4244
localStoreExecuteQuery,
4345
localStoreGetHighestUnacknowledgedBatchId,
4446
localStoreGetTargetData,
@@ -64,6 +66,12 @@ import {
6466
DocumentMap
6567
} from '../../../src/model/collections';
6668
import { Document } from '../../../src/model/document';
69+
import {
70+
FieldIndex,
71+
IndexKind,
72+
IndexSegment,
73+
IndexState
74+
} from '../../../src/model/field_index';
6775
import { FieldMask } from '../../../src/model/field_mask';
6876
import {
6977
FieldTransform,
@@ -78,6 +86,7 @@ import {
7886
MutationBatchResult
7987
} from '../../../src/model/mutation_batch';
8088
import { ObjectValue } from '../../../src/model/object_value';
89+
import { FieldPath } from '../../../src/model/path';
8190
import { serverTimestamp } from '../../../src/model/server_timestamps';
8291
import { ServerTimestampTransform } from '../../../src/model/transform_operation';
8392
import { BundleMetadata as ProtoBundleMetadata } from '../../../src/protos/firestore_bundle_proto';
@@ -367,6 +376,22 @@ class LocalStoreTester {
367376
return this;
368377
}
369378

379+
afterDeleteAllFieldIndexes(): LocalStoreTester {
380+
this.prepareNextStep();
381+
this.promiseChain = this.promiseChain.then(() =>
382+
localStoreDeleteAllFieldIndexes(this.localStore)
383+
);
384+
return this;
385+
}
386+
387+
afterConfigureFieldIndexes(fieldIndexes: FieldIndex[]): LocalStoreTester {
388+
this.prepareNextStep();
389+
this.promiseChain = this.promiseChain.then(() =>
390+
localStoreConfigureFieldIndexes(this.localStore, fieldIndexes)
391+
);
392+
return this;
393+
}
394+
370395
afterBackfillIndexes(options?: {
371396
maxDocumentsToProcess?: number;
372397
}): LocalStoreTester {
@@ -648,6 +673,18 @@ function compareDocsWithCreateTime(
648673
);
649674
}
650675

676+
function fieldIndex(
677+
collectionGroup: string,
678+
indexId: number,
679+
indexState: IndexState,
680+
field: string,
681+
kind: IndexKind
682+
): FieldIndex {
683+
const fieldPath = new FieldPath(field.split('.'));
684+
const segments = [new IndexSegment(fieldPath, kind)];
685+
return new FieldIndex(indexId, collectionGroup, segments, indexState);
686+
}
687+
651688
describe('LocalStore w/ Memory Persistence', () => {
652689
async function initialize(): Promise<LocalStoreComponents> {
653690
const queryEngine = new CountingQueryEngine();
@@ -2987,4 +3024,78 @@ function indexedDbLocalStoreTests(
29873024
.toReturnChanged('coll/a', 'coll/f')
29883025
.finish();
29893026
});
3027+
3028+
it('delete all indexes works with index auto creation', () => {
3029+
const query_ = query('coll', filter('value', '==', 'match'));
3030+
return (
3031+
expectLocalStore()
3032+
.afterAllocatingQuery(query_)
3033+
.toReturnTargetId(2)
3034+
.afterIndexAutoCreationConfigure({
3035+
isEnabled: true,
3036+
indexAutoCreationMinCollectionSize: 0,
3037+
relativeIndexReadCostPerDocument: 2
3038+
})
3039+
.afterRemoteEvents([
3040+
docAddedRemoteEvent(doc('coll/a', 10, { value: 'match' }), [2], []),
3041+
docAddedRemoteEvent(
3042+
doc('coll/b', 10, { value: Number.NaN }),
3043+
[2],
3044+
[]
3045+
),
3046+
docAddedRemoteEvent(doc('coll/c', 10, { value: null }), [2], []),
3047+
docAddedRemoteEvent(
3048+
doc('coll/d', 10, { value: 'mismatch' }),
3049+
[2],
3050+
[]
3051+
),
3052+
docAddedRemoteEvent(doc('coll/e', 10, { value: 'match' }), [2], [])
3053+
])
3054+
// First time query is running without indexes.
3055+
// Based on current heuristic, collection document counts (5) >
3056+
// 2 * resultSize (2).
3057+
// Full matched index should be created.
3058+
.afterExecutingQuery(query_)
3059+
.toHaveRead({ documentsByKey: 0, documentsByCollection: 2 })
3060+
.toReturnChanged('coll/a', 'coll/e')
3061+
.afterIndexAutoCreationConfigure({ isEnabled: false })
3062+
.afterBackfillIndexes()
3063+
.afterExecutingQuery(query_)
3064+
.toHaveRead({ documentsByKey: 2, documentsByCollection: 0 })
3065+
.toReturnChanged('coll/a', 'coll/e')
3066+
.afterDeleteAllFieldIndexes()
3067+
.afterExecutingQuery(query_)
3068+
.toHaveRead({ documentsByKey: 0, documentsByCollection: 2 })
3069+
.toReturnChanged('coll/a', 'coll/e')
3070+
.finish()
3071+
);
3072+
});
3073+
3074+
it('delete all indexes works with manual added indexes', () => {
3075+
const query_ = query('coll', filter('matches', '==', true));
3076+
return expectLocalStore()
3077+
.afterConfigureFieldIndexes([
3078+
fieldIndex(
3079+
'coll',
3080+
0,
3081+
IndexState.empty(),
3082+
'matches',
3083+
IndexKind.ASCENDING
3084+
)
3085+
])
3086+
.afterAllocatingQuery(query_)
3087+
.toReturnTargetId(2)
3088+
.afterRemoteEvents([
3089+
docAddedRemoteEvent(doc('coll/a', 10, { matches: true }), [2], [])
3090+
])
3091+
.afterBackfillIndexes()
3092+
.afterExecutingQuery(query_)
3093+
.toHaveRead({ documentsByKey: 1, documentsByCollection: 0 })
3094+
.toReturnChanged('coll/a')
3095+
.afterDeleteAllFieldIndexes()
3096+
.afterExecutingQuery(query_)
3097+
.toHaveRead({ documentsByKey: 0, documentsByCollection: 1 })
3098+
.toReturnChanged('coll/a')
3099+
.finish();
3100+
});
29903101
}

0 commit comments

Comments
 (0)