123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251 |
- "use strict";
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.RecursiveDelete = exports.RECURSIVE_DELETE_MIN_PENDING_OPS = exports.RECURSIVE_DELETE_MAX_PENDING_OPS = exports.REFERENCE_NAME_MIN_ID = void 0;
- const assert = require("assert");
- const _1 = require(".");
- const util_1 = require("./util");
- const query_options_1 = require("./reference/query-options");
- /*!
- * Datastore allowed numeric IDs where Firestore only allows strings. Numeric
- * IDs are exposed to Firestore as __idNUM__, so this is the lowest possible
- * negative numeric value expressed in that format.
- *
- * This constant is used to specify startAt/endAt values when querying for all
- * descendants in a single collection.
- */
- exports.REFERENCE_NAME_MIN_ID = '__id-9223372036854775808__';
- /*!
- * The query limit used for recursive deletes when fetching all descendants of
- * the specified reference to delete. This is done to prevent the query stream
- * from streaming documents faster than Firestore can delete.
- */
- // Visible for testing.
- exports.RECURSIVE_DELETE_MAX_PENDING_OPS = 5000;
- /*!
- * The number of pending BulkWriter operations at which RecursiveDelete
- * starts the next limit query to fetch descendants. By starting the query
- * while there are pending operations, Firestore can improve BulkWriter
- * throughput. This helps prevent BulkWriter from idling while Firestore
- * fetches the next query.
- */
- exports.RECURSIVE_DELETE_MIN_PENDING_OPS = 1000;
- /**
- * Class used to store state required for running a recursive delete operation.
- * Each recursive delete call should use a new instance of the class.
- * @private
- * @internal
- */
- class RecursiveDelete {
- /**
- *
- * @param firestore The Firestore instance to use.
- * @param writer The BulkWriter instance to use for delete operations.
- * @param ref The document or collection reference to recursively delete.
- * @param maxLimit The query limit to use when fetching descendants
- * @param minLimit The number of pending BulkWriter operations at which
- * RecursiveDelete starts the next limit query to fetch descendants.
- */
- constructor(firestore, writer, ref, maxLimit, minLimit) {
- this.firestore = firestore;
- this.writer = writer;
- this.ref = ref;
- this.maxLimit = maxLimit;
- this.minLimit = minLimit;
- /**
- * The number of deletes that failed with a permanent error.
- * @private
- * @internal
- */
- this.errorCount = 0;
- /**
- * Whether there are still documents to delete that still need to be fetched.
- * @private
- * @internal
- */
- this.documentsPending = true;
- /**
- * Whether run() has been called.
- * @private
- * @internal
- */
- this.started = false;
- /**
- * A deferred promise that resolves when the recursive delete operation
- * is completed.
- * @private
- * @internal
- */
- this.completionDeferred = new util_1.Deferred();
- /**
- * Whether a query stream is currently in progress. Only one stream can be
- * run at a time.
- * @private
- * @internal
- */
- this.streamInProgress = false;
- /**
- * The number of pending BulkWriter operations. Used to determine when the
- * next query can be run.
- * @private
- * @internal
- */
- this.pendingOpsCount = 0;
- this.errorStack = '';
- this.maxPendingOps = maxLimit;
- this.minPendingOps = minLimit;
- }
- /**
- * Recursively deletes the reference provided in the class constructor.
- * Returns a promise that resolves when all descendants have been deleted, or
- * if an error occurs.
- */
- run() {
- assert(!this.started, 'RecursiveDelete.run() should only be called once.');
- // Capture the error stack to preserve stack tracing across async calls.
- this.errorStack = Error().stack;
- this.writer._verifyNotClosed();
- this.setupStream();
- return this.completionDeferred.promise;
- }
- /**
- * Creates a query stream and attaches event handlers to it.
- * @private
- * @internal
- */
- setupStream() {
- const stream = this.getAllDescendants(this.ref instanceof _1.CollectionReference
- ? this.ref
- : this.ref);
- this.streamInProgress = true;
- let streamedDocsCount = 0;
- stream
- .on('error', err => {
- err.code = 14 /* StatusCode.UNAVAILABLE */;
- err.stack = 'Failed to fetch children documents: ' + err.stack;
- this.lastError = err;
- this.onQueryEnd();
- })
- .on('data', (snap) => {
- streamedDocsCount++;
- this.lastDocumentSnap = snap;
- this.deleteRef(snap.ref);
- })
- .on('end', () => {
- this.streamInProgress = false;
- // If there are fewer than the number of documents specified in the
- // limit() field, we know that the query is complete.
- if (streamedDocsCount < this.minPendingOps) {
- this.onQueryEnd();
- }
- else if (this.pendingOpsCount === 0) {
- this.setupStream();
- }
- });
- }
- /**
- * Retrieves all descendant documents nested under the provided reference.
- * @param ref The reference to fetch all descendants for.
- * @private
- * @internal
- * @return {Stream<QueryDocumentSnapshot>} Stream of descendant documents.
- */
- getAllDescendants(ref) {
- // The parent is the closest ancestor document to the location we're
- // deleting. If we are deleting a document, the parent is the path of that
- // document. If we are deleting a collection, the parent is the path of the
- // document containing that collection (or the database root, if it is a
- // root collection).
- let parentPath = ref._resourcePath;
- if (ref instanceof _1.CollectionReference) {
- parentPath = parentPath.popLast();
- }
- const collectionId = ref instanceof _1.CollectionReference
- ? ref.id
- : ref.parent.id;
- let query = new _1.Query(this.firestore, query_options_1.QueryOptions.forKindlessAllDescendants(parentPath, collectionId,
- /* requireConsistency= */ false));
- // Query for names only to fetch empty snapshots.
- query = query.select(_1.FieldPath.documentId()).limit(this.maxPendingOps);
- if (ref instanceof _1.CollectionReference) {
- // To find all descendants of a collection reference, we need to use a
- // composite filter that captures all documents that start with the
- // collection prefix. The MIN_KEY constant represents the minimum key in
- // this collection, and a null byte + the MIN_KEY represents the minimum
- // key is the next possible collection.
- const nullChar = String.fromCharCode(0);
- const startAt = collectionId + '/' + exports.REFERENCE_NAME_MIN_ID;
- const endAt = collectionId + nullChar + '/' + exports.REFERENCE_NAME_MIN_ID;
- query = query
- .where(_1.FieldPath.documentId(), '>=', startAt)
- .where(_1.FieldPath.documentId(), '<', endAt);
- }
- if (this.lastDocumentSnap) {
- query = query.startAfter(this.lastDocumentSnap);
- }
- return query.stream();
- }
- /**
- * Called when all descendants of the provided reference have been streamed
- * or if a permanent error occurs during the stream. Deletes the developer
- * provided reference and wraps any errors that occurred.
- * @private
- * @internal
- */
- onQueryEnd() {
- this.documentsPending = false;
- if (this.ref instanceof _1.DocumentReference) {
- this.writer.delete(this.ref).catch(err => this.incrementErrorCount(err));
- }
- this.writer.flush().then(async () => {
- var _a;
- if (this.lastError === undefined) {
- this.completionDeferred.resolve();
- }
- else {
- let error = new (require('google-gax/build/src/fallback').GoogleError)(`${this.errorCount} ` +
- `${this.errorCount !== 1 ? 'deletes' : 'delete'} ` +
- 'failed. The last delete failed with: ');
- if (this.lastError.code !== undefined) {
- error.code = this.lastError.code;
- }
- error = (0, util_1.wrapError)(error, this.errorStack);
- // Wrap the BulkWriter error last to provide the full stack trace.
- this.completionDeferred.reject(this.lastError.stack
- ? (0, util_1.wrapError)(error, (_a = this.lastError.stack) !== null && _a !== void 0 ? _a : '')
- : error);
- }
- });
- }
- /**
- * Deletes the provided reference and starts the next stream if conditions
- * are met.
- * @private
- * @internal
- */
- deleteRef(docRef) {
- this.pendingOpsCount++;
- this.writer
- .delete(docRef)
- .catch(err => {
- this.incrementErrorCount(err);
- })
- .then(() => {
- this.pendingOpsCount--;
- // We wait until the previous stream has ended in order to sure the
- // startAfter document is correct. Starting the next stream while
- // there are pending operations allows Firestore to maximize
- // BulkWriter throughput.
- if (this.documentsPending &&
- !this.streamInProgress &&
- this.pendingOpsCount < this.minPendingOps) {
- this.setupStream();
- }
- });
- }
- incrementErrorCount(err) {
- this.errorCount++;
- this.lastError = err;
- }
- }
- exports.RecursiveDelete = RecursiveDelete;
- //# sourceMappingURL=recursive-delete.js.map
|