Skip to content

Firestore: deleteAllPersistentCacheIndexes() added, but hidden for now #7587

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/healthy-peas-heal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@firebase/firestore': patch
'firebase': patch
---

Implemented internal logic to delete all client-side indexes
1 change: 1 addition & 0 deletions packages/firestore/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ export {
export {
PersistentCacheIndexManager,
getPersistentCacheIndexManager,
deleteAllPersistentCacheIndexes,
enablePersistentCacheIndexAutoCreation,
disablePersistentCacheIndexAutoCreation
} from './api/persistent_cache_index_manager';
Expand Down
24 changes: 24 additions & 0 deletions packages/firestore/src/api/persistent_cache_index_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
*/

import {
firestoreClientDeleteAllFieldIndexes,
firestoreClientSetPersistentCacheIndexAutoCreationEnabled,
FirestoreClient
} from '../core/firestore_client';
Expand Down Expand Up @@ -99,6 +100,29 @@ export function disablePersistentCacheIndexAutoCreation(
setPersistentCacheIndexAutoCreationEnabled(indexManager, false);
}

/**
* Removes all persistent cache indexes.
*
* Please note this function will also deletes indexes generated by
* `setIndexConfiguration()`, which is deprecated.
*
* TODO(CSI) Remove @internal to make the API publicly available.
* @internal
*/
export function deleteAllPersistentCacheIndexes(
indexManager: PersistentCacheIndexManager
): void {
indexManager._client.verifyNotTerminated();

const promise = firestoreClientDeleteAllFieldIndexes(indexManager._client);

promise
.then(_ => logDebug('deleting all persistent cache indexes succeeded'))
.catch(error =>
logWarn('deleting all persistent cache indexes failed', error)
);
}

function setPersistentCacheIndexAutoCreationEnabled(
indexManager: PersistentCacheIndexManager,
isEnabled: boolean
Expand Down
9 changes: 9 additions & 0 deletions packages/firestore/src/core/firestore_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { User } from '../auth/user';
import { LocalStore } from '../local/local_store';
import {
localStoreConfigureFieldIndexes,
localStoreDeleteAllFieldIndexes,
localStoreExecuteQuery,
localStoreGetNamedQuery,
localStoreHandleUserChange,
Expand Down Expand Up @@ -841,3 +842,11 @@ export function firestoreClientSetPersistentCacheIndexAutoCreationEnabled(
);
});
}

export function firestoreClientDeleteAllFieldIndexes(
client: FirestoreClient
): Promise<void> {
return client.asyncQueue.enqueue(async () => {
return localStoreDeleteAllFieldIndexes(await getLocalStore(client));
});
}
5 changes: 5 additions & 0 deletions packages/firestore/src/local/index_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ export interface IndexManager {
index: FieldIndex
): PersistencePromise<void>;

/** Removes all field indexes and deletes all index values. */
deleteAllFieldIndexes(
transaction: PersistenceTransaction
): PersistencePromise<void>;

/** Creates a full matched field index which serves the given target. */
createTargetIndexes(
transaction: PersistenceTransaction,
Expand Down
13 changes: 13 additions & 0 deletions packages/firestore/src/local/indexeddb_index_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,19 @@ export class IndexedDbIndexManager implements IndexManager {
);
}

deleteAllFieldIndexes(
transaction: PersistenceTransaction
): PersistencePromise<void> {
const indexes = indexConfigurationStore(transaction);
const entries = indexEntriesStore(transaction);
const states = indexStateStore(transaction);

return indexes
.deleteAll()
.next(() => entries.deleteAll())
.next(() => states.deleteAll());
}

createTargetIndexes(
transaction: PersistenceTransaction,
target: Target
Expand Down
12 changes: 12 additions & 0 deletions packages/firestore/src/local/local_store_impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1535,6 +1535,18 @@ export function localStoreSetIndexAutoCreationEnabled(
localStoreImpl.queryEngine.indexAutoCreationEnabled = isEnabled;
}

export function localStoreDeleteAllFieldIndexes(
localStore: LocalStore
): Promise<void> {
const localStoreImpl = debugCast(localStore, LocalStoreImpl);
const indexManager = localStoreImpl.indexManager;
return localStoreImpl.persistence.runTransaction(
'Delete All Indexes',
'readwrite',
transaction => indexManager.deleteAllFieldIndexes(transaction)
);
}

/**
* Test-only hooks into the SDK for use exclusively by tests.
*/
Expand Down
7 changes: 7 additions & 0 deletions packages/firestore/src/local/memory_index_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,13 @@ export class MemoryIndexManager implements IndexManager {
return PersistencePromise.resolve();
}

deleteAllFieldIndexes(
transaction: PersistenceTransaction
): PersistencePromise<void> {
// Field indices are not supported with memory persistence.
return PersistencePromise.resolve();
}

createTargetIndexes(
transaction: PersistenceTransaction,
target: Target
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import { expect } from 'chai';

import {
deleteAllPersistentCacheIndexes,
disablePersistentCacheIndexAutoCreation,
doc,
enablePersistentCacheIndexAutoCreation,
Expand Down Expand Up @@ -149,4 +150,66 @@ apiDescribe('PersistentCacheIndexManager', persistence => {
});
});
});

describe('delete all persistent cache indexes', () => {
it('deleteAllPersistentCacheIndexes() on new instance should succeed', () =>
withTestDb(persistence, async db => {
const indexManager = getPersistentCacheIndexManager(db)!;
deleteAllPersistentCacheIndexes(indexManager);
}));

it('deleteAllPersistentCacheIndexes() should be successful when auto-indexing is enabled', () =>
withTestDb(persistence, async db => {
const indexManager = getPersistentCacheIndexManager(db)!;
enablePersistentCacheIndexAutoCreation(indexManager);
deleteAllPersistentCacheIndexes(indexManager);
}));

it('deleteAllPersistentCacheIndexes() should be successful when auto-indexing is disabled', () =>
withTestDb(persistence, async db => {
const indexManager = getPersistentCacheIndexManager(db)!;
enablePersistentCacheIndexAutoCreation(indexManager);
disablePersistentCacheIndexAutoCreation(indexManager);
deleteAllPersistentCacheIndexes(indexManager);
}));

it('deleteAllPersistentCacheIndexes() after terminate() should throw', () =>
withTestDb(persistence, async db => {
const indexManager = getPersistentCacheIndexManager(db)!;
terminate(db).catch(e => expect.fail(`terminate() failed: ${e}`));
expect(() => deleteAllPersistentCacheIndexes(indexManager)).to.throw(
'The client has already been terminated.'
);
}));

it('query returns correct results when auto-created index has been deleted', () => {
const testDocs = partitionedTestDocs({
matching: { documentData: { match: true }, documentCount: 1 },
nonmatching: { documentData: { match: false }, documentCount: 100 }
});
return withTestCollection(persistence, testDocs, async (coll, db) => {
const indexManager = getPersistentCacheIndexManager(db)!;
enablePersistentCacheIndexAutoCreation(indexManager);

// Populate the local cache with the entire collection's contents.
await getDocs(coll);

// Run a query that matches only one of the documents in the collection;
// this should cause an index to be auto-created.
const query_ = query(coll, where('match', '==', true));
const snapshot1 = await getDocsFromCache(query_);
expect(snapshot1.size).to.equal(1);

// Delete the index
deleteAllPersistentCacheIndexes(indexManager);

// Run the query that matches only one of the documents again, which
// should _still_ return the one and only document that matches. Since
// the public API surface does not reveal whether an index was used,
// there isn't anything else that can be verified.
const snapshot2 = await getDocsFromCache(query_);
expect(snapshot2.size).to.equal(1);
});
});
});
});
15 changes: 15 additions & 0 deletions packages/firestore/test/unit/local/index_manager.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1734,6 +1734,21 @@ describe('IndexedDbIndexManager', async () => {
await validateIsFullIndex(query_);
});

it('deleteAllFieldIndexes() deletes all indexes', async () => {
// Create some indexes.
const query1 = queryWithAddedFilter(query('coll'), filter('a', '==', 42));
await indexManager.createTargetIndexes(queryToTarget(query1));
await validateIsFullIndex(query1);
const query2 = queryWithAddedFilter(query('coll'), filter('b', '==', 42));
await indexManager.createTargetIndexes(queryToTarget(query2));
await validateIsFullIndex(query2);

// Verify that deleteAllFieldIndexes() deletes the indexes.
await indexManager.deleteAllFieldIndexes();
await validateIsNoneIndex(query1);
await validateIsNoneIndex(query2);
});

async function validateIsPartialIndex(query: Query): Promise<void> {
await validateIndexType(query, IndexType.PARTIAL);
}
Expand Down
111 changes: 111 additions & 0 deletions packages/firestore/test/unit/local/local_store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ import {
localStoreAllocateTarget,
localStoreApplyBundledDocuments,
localStoreApplyRemoteEventToLocalCache,
localStoreConfigureFieldIndexes,
localStoreDeleteAllFieldIndexes,
localStoreExecuteQuery,
localStoreGetHighestUnacknowledgedBatchId,
localStoreGetTargetData,
Expand All @@ -64,6 +66,12 @@ import {
DocumentMap
} from '../../../src/model/collections';
import { Document } from '../../../src/model/document';
import {
FieldIndex,
IndexKind,
IndexSegment,
IndexState
} from '../../../src/model/field_index';
import { FieldMask } from '../../../src/model/field_mask';
import {
FieldTransform,
Expand All @@ -78,6 +86,7 @@ import {
MutationBatchResult
} from '../../../src/model/mutation_batch';
import { ObjectValue } from '../../../src/model/object_value';
import { FieldPath } from '../../../src/model/path';
import { serverTimestamp } from '../../../src/model/server_timestamps';
import { ServerTimestampTransform } from '../../../src/model/transform_operation';
import { BundleMetadata as ProtoBundleMetadata } from '../../../src/protos/firestore_bundle_proto';
Expand Down Expand Up @@ -367,6 +376,22 @@ class LocalStoreTester {
return this;
}

afterDeleteAllFieldIndexes(): LocalStoreTester {
this.prepareNextStep();
this.promiseChain = this.promiseChain.then(() =>
localStoreDeleteAllFieldIndexes(this.localStore)
);
return this;
}

afterConfigureFieldIndexes(fieldIndexes: FieldIndex[]): LocalStoreTester {
this.prepareNextStep();
this.promiseChain = this.promiseChain.then(() =>
localStoreConfigureFieldIndexes(this.localStore, fieldIndexes)
);
return this;
}

afterBackfillIndexes(options?: {
maxDocumentsToProcess?: number;
}): LocalStoreTester {
Expand Down Expand Up @@ -648,6 +673,18 @@ function compareDocsWithCreateTime(
);
}

function fieldIndex(
collectionGroup: string,
indexId: number,
indexState: IndexState,
field: string,
kind: IndexKind
): FieldIndex {
const fieldPath = new FieldPath(field.split('.'));
const segments = [new IndexSegment(fieldPath, kind)];
return new FieldIndex(indexId, collectionGroup, segments, indexState);
}

describe('LocalStore w/ Memory Persistence', () => {
async function initialize(): Promise<LocalStoreComponents> {
const queryEngine = new CountingQueryEngine();
Expand Down Expand Up @@ -2987,4 +3024,78 @@ function indexedDbLocalStoreTests(
.toReturnChanged('coll/a', 'coll/f')
.finish();
});

it('delete all indexes works with index auto creation', () => {
const query_ = query('coll', filter('value', '==', 'match'));
return (
expectLocalStore()
.afterAllocatingQuery(query_)
.toReturnTargetId(2)
.afterIndexAutoCreationConfigure({
isEnabled: true,
indexAutoCreationMinCollectionSize: 0,
relativeIndexReadCostPerDocument: 2
})
.afterRemoteEvents([
docAddedRemoteEvent(doc('coll/a', 10, { value: 'match' }), [2], []),
docAddedRemoteEvent(
doc('coll/b', 10, { value: Number.NaN }),
[2],
[]
),
docAddedRemoteEvent(doc('coll/c', 10, { value: null }), [2], []),
docAddedRemoteEvent(
doc('coll/d', 10, { value: 'mismatch' }),
[2],
[]
),
docAddedRemoteEvent(doc('coll/e', 10, { value: 'match' }), [2], [])
])
// First time query is running without indexes.
// Based on current heuristic, collection document counts (5) >
// 2 * resultSize (2).
// Full matched index should be created.
.afterExecutingQuery(query_)
.toHaveRead({ documentsByKey: 0, documentsByCollection: 2 })
.toReturnChanged('coll/a', 'coll/e')
.afterIndexAutoCreationConfigure({ isEnabled: false })
.afterBackfillIndexes()
.afterExecutingQuery(query_)
.toHaveRead({ documentsByKey: 2, documentsByCollection: 0 })
.toReturnChanged('coll/a', 'coll/e')
.afterDeleteAllFieldIndexes()
.afterExecutingQuery(query_)
.toHaveRead({ documentsByKey: 0, documentsByCollection: 2 })
.toReturnChanged('coll/a', 'coll/e')
.finish()
);
});

it('delete all indexes works with manual added indexes', () => {
const query_ = query('coll', filter('matches', '==', true));
return expectLocalStore()
.afterConfigureFieldIndexes([
fieldIndex(
'coll',
0,
IndexState.empty(),
'matches',
IndexKind.ASCENDING
)
])
.afterAllocatingQuery(query_)
.toReturnTargetId(2)
.afterRemoteEvents([
docAddedRemoteEvent(doc('coll/a', 10, { matches: true }), [2], [])
])
.afterBackfillIndexes()
.afterExecutingQuery(query_)
.toHaveRead({ documentsByKey: 1, documentsByCollection: 0 })
.toReturnChanged('coll/a')
.afterDeleteAllFieldIndexes()
.afterExecutingQuery(query_)
.toHaveRead({ documentsByKey: 0, documentsByCollection: 1 })
.toReturnChanged('coll/a')
.finish();
});
}
Loading