UNPKG

rxdb

Version:

A local-first realtime NoSQL Database for JavaScript applications - https://rxdb.info/

264 lines (254 loc) 9.54 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.attachmentMapKey = attachmentMapKey; exports.bulkInsertToState = bulkInsertToState; exports.compareDocsWithIndex = compareDocsWithIndex; exports.ensureNotRemoved = ensureNotRemoved; exports.getMemoryCollectionKey = getMemoryCollectionKey; exports.putWriteRowToState = putWriteRowToState; exports.removeDocFromState = removeDocFromState; var _arrayPushAtSortPosition = require("array-push-at-sort-position"); var _rxError = require("../../rx-error.js"); var _binarySearchBounds = require("./binary-search-bounds.js"); function getMemoryCollectionKey(databaseName, collectionName, schemaVersion) { return [databaseName, collectionName, schemaVersion].join('--memory--'); } function ensureNotRemoved(instance) { if (instance.internals.removed) { throw new Error('removed already ' + instance.databaseName + ' - ' + instance.collectionName + ' - ' + instance.schema.version); } } function attachmentMapKey(documentId, attachmentId) { return documentId + '||' + attachmentId; } /** * @performance * Threshold for using in-place splice vs. full merge-sort when inserting * documents into indexes. Below this batch size, in-place binary search + splice * is faster because it avoids allocating a new full-size array and copying all elements. */ var IN_PLACE_INSERT_THRESHOLD = 64; function sortByIndexStringComparator(a, b) { if (a[0] < b[0]) { return -1; } else { return 1; } } /** * @hotPath */ function putWriteRowToState(docId, state, stateByIndex, document, docInState) { state.documents.set(docId, document); var stateByIndexLength = stateByIndex.length; for (var i = 0; i < stateByIndexLength; ++i) { var byIndex = stateByIndex[i]; var docsWithIndex = byIndex.docsWithIndex; var getIndexableString = byIndex.getIndexableString; var newIndexString = getIndexableString(document); /** * @performance * When updating a document, first compute whether the index changed. * If it did not change, we only need to update the document reference * in-place without any splice operations. */ if (docInState) { var previousIndexString = getIndexableString(docInState); if (previousIndexString === newIndexString) { /** * Performance shortcut. * Index did not change, so the old entry is at the same position. * We can find it by string-specialized binary search and update in-place. */ var eqPos = (0, _binarySearchBounds.boundEQByIndexString)(docsWithIndex, previousIndexString); if (eqPos !== -1) { /** * There might be multiple entries with the same index string * (e.g. different documents). Search around eqPos for ours. */ if (docsWithIndex[eqPos][2] === docId) { docsWithIndex[eqPos][1] = document; continue; } // Check neighbors var prev = docsWithIndex[eqPos - 1]; if (prev && prev[0] === previousIndexString && prev[2] === docId) { docsWithIndex[eqPos - 1][1] = document; continue; } var next = docsWithIndex[eqPos + 1]; if (next && next[0] === previousIndexString && next[2] === docId) { docsWithIndex[eqPos + 1][1] = document; continue; } } // Fallback: use the old insert+remove approach var insertPosition = (0, _arrayPushAtSortPosition.pushAtSortPosition)(docsWithIndex, [newIndexString, document, docId], sortByIndexStringComparator, 0); var prevEntry = docsWithIndex[insertPosition - 1]; if (prevEntry && prevEntry[2] === docId) { docsWithIndex.splice(insertPosition - 1, 1); } else { var nextEntry = docsWithIndex[insertPosition + 1]; if (nextEntry[2] === docId) { docsWithIndex.splice(insertPosition + 1, 1); } else { throw (0, _rxError.newRxError)('SNH', { document, args: { byIndex } }); } } continue; } else { /** * Index changed, we must remove the old entry and insert the new one. */ var indexBefore = (0, _binarySearchBounds.boundEQByIndexString)(docsWithIndex, previousIndexString); if (indexBefore !== -1) { docsWithIndex.splice(indexBefore, 1); } } } (0, _arrayPushAtSortPosition.pushAtSortPosition)(docsWithIndex, [newIndexString, document, docId], sortByIndexStringComparator, 0); } } /** * @hotPath * Efficiently inserts multiple documents into the state at once. * * Uses two strategies based on batch size: * - For small batches (relative to existing index size), uses in-place * binary search + splice per document. This avoids allocating a new * full-size array and copying all elements, reducing GC pressure. * - For large batches (or empty indexes), pre-computes all index entries, * sorts them, and merges into the existing sorted arrays in a single pass. */ function bulkInsertToState(primaryPath, state, stateByIndex, docs) { var docsLength = docs.length; var stateByIndexLength = stateByIndex.length; // Extract documents and docIds once, store in Map var documents = new Array(docsLength); var docIds = new Array(docsLength); for (var i = 0; i < docsLength; ++i) { var doc = docs[i].document; var docId = doc[primaryPath]; documents[i] = doc; docIds[i] = docId; state.documents.set(docId, doc); } /** * @performance * For small batch sizes, use in-place binary search + splice * instead of creating a full merged array copy. This is faster * for serial inserts and small bulk inserts because it avoids: * - Allocating a new array of size n+m * - Copying all n existing elements * - GC pressure from discarding the old array * * The threshold is based on when the merge approach becomes more * efficient than individual splices. */ var useInPlaceInsert = docsLength < IN_PLACE_INSERT_THRESHOLD; if (useInPlaceInsert) { for (var indexI = 0; indexI < stateByIndexLength; ++indexI) { var byIndex = stateByIndex[indexI]; var docsWithIndex = byIndex.docsWithIndex; var getIndexableString = byIndex.getIndexableString; if (docsWithIndex.length === 0) { for (var _i = 0; _i < docsLength; ++_i) { var _doc = documents[_i]; var indexString = getIndexableString(_doc); docsWithIndex.push([indexString, _doc, docIds[_i]]); } docsWithIndex.sort(sortByIndexStringComparator); } else { for (var _i2 = 0; _i2 < docsLength; ++_i2) { var _doc2 = documents[_i2]; var _indexString = getIndexableString(_doc2); var newEntry = [_indexString, _doc2, docIds[_i2]]; (0, _arrayPushAtSortPosition.pushAtSortPosition)(docsWithIndex, newEntry, sortByIndexStringComparator, 0); } } } } else { // For each index, batch-compute entries, sort, and merge for (var _indexI = 0; _indexI < stateByIndexLength; ++_indexI) { var _byIndex = stateByIndex[_indexI]; var _docsWithIndex = _byIndex.docsWithIndex; var _getIndexableString = _byIndex.getIndexableString; // Build new entries var newEntries = new Array(docsLength); for (var _i3 = 0; _i3 < docsLength; ++_i3) { var _doc3 = documents[_i3]; newEntries[_i3] = [_getIndexableString(_doc3), _doc3, docIds[_i3]]; } // Sort by index string newEntries.sort(sortByIndexStringComparator); if (_docsWithIndex.length === 0) { // Index is empty, just assign sorted entries _byIndex.docsWithIndex = newEntries; } else { // Merge sorted arrays _byIndex.docsWithIndex = mergeSortedArrays(_docsWithIndex, newEntries); } } } } /** * Merges two sorted DocWithIndexString arrays into a single sorted array. * Runs in O(n + m) where n and m are the lengths of the input arrays. * @performance Comparator is inlined to avoid function call overhead * per comparison, which is significant for large arrays. */ function mergeSortedArrays(a, b) { var aLen = a.length; var bLen = b.length; var result = new Array(aLen + bLen); var ai = 0; var bi = 0; var ri = 0; while (ai < aLen && bi < bLen) { if (a[ai][0] <= b[bi][0]) { result[ri++] = a[ai++]; } else { result[ri++] = b[bi++]; } } while (ai < aLen) { result[ri++] = a[ai++]; } while (bi < bLen) { result[ri++] = b[bi++]; } return result; } function removeDocFromState(primaryPath, schema, state, doc) { var docId = doc[primaryPath]; state.documents.delete(docId); var stateByIndex = state.byIndexArray; for (var i = 0; i < stateByIndex.length; ++i) { var byIndex = stateByIndex[i]; var docsWithIndex = byIndex.docsWithIndex; var indexString = byIndex.getIndexableString(doc); var positionInIndex = (0, _binarySearchBounds.boundEQByIndexString)(docsWithIndex, indexString); if (positionInIndex !== -1) { docsWithIndex.splice(positionInIndex, 1); } } } function compareDocsWithIndex(a, b) { var indexStringA = a[0]; var indexStringB = b[0]; if (indexStringA < indexStringB) { return -1; } else if (indexStringA === indexStringB) { return 0; } else { return 1; } } //# sourceMappingURL=memory-helper.js.map