UNPKG

ravendb

Version:
419 lines 22 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.SessionTimeSeriesBase = void 0; /** * Abstract implementation for in memory session operations */ const InMemoryDocumentSessionOperations_js_1 = require("./InMemoryDocumentSessionOperations.js"); const index_js_1 = require("../../Exceptions/index.js"); const TypeUtil_js_1 = require("../../Utility/TypeUtil.js"); const StringUtil_js_1 = require("../../Utility/StringUtil.js"); const TimeSeriesOperation_js_1 = require("../Operations/TimeSeries/TimeSeriesOperation.js"); const IdTypeAndName_js_1 = require("../IdTypeAndName.js"); const TimeSeriesBatchCommandData_js_1 = require("../Commands/Batches/TimeSeriesBatchCommandData.js"); const DatesComparator_js_1 = require("../../Primitives/DatesComparator.js"); const GetTimeSeriesOperation_js_1 = require("../Operations/TimeSeries/GetTimeSeriesOperation.js"); const CaseInsensitiveKeysMap_js_1 = require("../../Primitives/CaseInsensitiveKeysMap.js"); const GetMultipleTimeSeriesOperation_js_1 = require("../Operations/TimeSeries/GetMultipleTimeSeriesOperation.js"); const Constants_js_1 = require("../../Constants.js"); const IncrementalTimeSeriesBatchCommandData_js_1 = require("../Commands/Batches/IncrementalTimeSeriesBatchCommandData.js"); class SessionTimeSeriesBase { docId; name; session; constructor(session, documentIdOrEntity, name) { if (TypeUtil_js_1.TypeUtil.isString(documentIdOrEntity)) { const documentId = documentIdOrEntity; if (!documentId) { (0, index_js_1.throwError)("InvalidArgumentException", "DocumentId cannot be null"); } if (!name) { (0, index_js_1.throwError)("InvalidArgumentException", "Name cannot be null"); } this.docId = documentId; this.name = name; this.session = session; } else { const entity = documentIdOrEntity; if (!entity) { (0, index_js_1.throwError)("InvalidArgumentException", "Entity cannot be null"); } const documentInfo = session.documentsByEntity.get(entity); if (!documentInfo) { this._throwEntityNotInSession(); return; } if (StringUtil_js_1.StringUtil.isNullOrWhitespace(name)) { (0, index_js_1.throwError)("InvalidArgumentException", "Name cannot be null or whitespace"); } this.docId = documentInfo.id; this.name = name; this.session = session; } } _appendInternal(timestamp, valueOrValues, tag) { const values = TypeUtil_js_1.TypeUtil.isArray(valueOrValues) ? valueOrValues : [valueOrValues]; const documentInfo = this.session.documentsById.getValue(this.docId); if (documentInfo && this.session.deletedEntities.contains(documentInfo.entity)) { SessionTimeSeriesBase._throwDocumentAlreadyDeletedInSession(this.docId, this.name); } const op = new TimeSeriesOperation_js_1.AppendOperation(timestamp, values, tag); const command = this.session.deferredCommandsMap.get(IdTypeAndName_js_1.IdTypeAndName.keyFor(this.docId, "TimeSeries", this.name)); if (command) { const tsCmd = command; tsCmd.timeSeries.append(op); } else { const appends = []; appends.push(op); this.session.defer(new TimeSeriesBatchCommandData_js_1.TimeSeriesBatchCommandData(this.docId, this.name, appends, null)); } } delete(from, to) { const documentInfo = this.session.documentsById.getValue(this.docId); if (documentInfo && this.session.deletedEntities.contains(documentInfo.entity)) { SessionTimeSeriesBase._throwDocumentAlreadyDeletedInSession(this.docId, this.name); } const op = new TimeSeriesOperation_js_1.DeleteOperation(from, to); const command = this.session.deferredCommandsMap.get(IdTypeAndName_js_1.IdTypeAndName.keyFor(this.docId, "TimeSeries", this.name)); if (command) { const tsCmd = command; tsCmd.timeSeries.delete(op); } else { const deletes = []; deletes.push(op); this.session.defer(new TimeSeriesBatchCommandData_js_1.TimeSeriesBatchCommandData(this.docId, this.name, null, deletes)); } this._removeFromCacheIfNeeded(from, to); } deleteAt(at) { this.delete(at, at); } _removeFromCacheIfNeeded(from, to) { const cache = this.session.timeSeriesByDocId.get(this.docId); if (!cache) { return; } if (!from && !to) { cache.delete(this.name); return; } const ranges = cache.get(this.name); if (ranges && ranges.length) { const newRanges = ranges.filter(range => DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(range.from), (0, DatesComparator_js_1.leftDate)(from)) > 0 || DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(range.to), (0, DatesComparator_js_1.rightDate)(to)) < 0); cache.set(this.name, newRanges); } } _incrementInternal(timestamp, values) { const documentInfo = this.session.documentsById.getValue(this.docId); if (documentInfo && this.session.deletedEntities.contains(documentInfo.entity)) { SessionTimeSeriesBase._throwDocumentAlreadyDeletedInSession(this.docId, this.name); } const op = new TimeSeriesOperation_js_1.IncrementOperation(); op.timestamp = timestamp; op.values = values; const command = this.session.deferredCommandsMap.get(IdTypeAndName_js_1.IdTypeAndName.keyFor(this.docId, "TimeSeriesWithIncrements", this.name)); if (command) { const tsCmd = command; tsCmd.timeSeries.increment(op); } else { const list = []; list.push(op); this.session.defer(new IncrementalTimeSeriesBatchCommandData_js_1.IncrementalTimeSeriesBatchCommandData(this.docId, this.name, list)); } } static _throwDocumentAlreadyDeletedInSession(documentId, timeSeries) { (0, index_js_1.throwError)("InvalidOperationException", "Can't modify timeseries " + timeSeries + " of document " + documentId + ", the document was already deleted in this session"); } _throwEntityNotInSession() { (0, index_js_1.throwError)("InvalidArgumentException", "Entity is not associated with the session, cannot perform timeseries operations to it. " + "Use documentId instead or track the entity in the session."); } async getTimeSeriesAndIncludes(from, to, includes, start, pageSize) { if (pageSize === 0) { return []; } const document = this.session.documentsById.getValue(this.docId); if (document) { const metadataTimeSeries = document.metadata[Constants_js_1.CONSTANTS.Documents.Metadata.TIME_SERIES]; if (metadataTimeSeries && TypeUtil_js_1.TypeUtil.isArray(metadataTimeSeries)) { if (!metadataTimeSeries.some(x => StringUtil_js_1.StringUtil.equalsIgnoreCase(x, this.name))) { // the document is loaded in the session, but the metadata says that there is no such timeseries return []; } } } this.session.incrementRequestCount(); const rangeResult = await this.session.operations.send(new GetTimeSeriesOperation_js_1.GetTimeSeriesOperation(this.docId, this.name, from, to, start, pageSize, includes), this.session.sessionInfo); if (!rangeResult) { return null; } if (!this.session.noTracking) { this._handleIncludes(rangeResult); let cache = this.session.timeSeriesByDocId.get(this.docId); if (!cache) { cache = CaseInsensitiveKeysMap_js_1.CaseInsensitiveKeysMap.create(); this.session.timeSeriesByDocId.set(this.docId, cache); } const ranges = cache.get(this.name); if (ranges && ranges.length > 0) { // update const index = DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[0].from), (0, DatesComparator_js_1.rightDate)(to)) > 0 ? 0 : ranges.length; ranges.splice(index, 0, rangeResult); } else { const item = []; item.push(rangeResult); cache.set(this.name, item); } } return rangeResult.entries; } _handleIncludes(rangeResult) { if (!rangeResult.includes) { return; } this.session.registerIncludes(rangeResult.includes); rangeResult.includes = null; } static _skipAndTrimRangeIfNeeded(from, to, fromRange, toRange, values, skip, trim) { if (fromRange && DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(fromRange.to), (0, DatesComparator_js_1.leftDate)(from)) >= 0) { // need to skip a part of the first range if (toRange && DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(toRange.from), (0, DatesComparator_js_1.rightDate)(to)) <= 0) { // also need to trim a part of the last range return values.slice(skip, values.length - trim); } return values.slice(skip); } if (toRange && DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(toRange.from), (0, DatesComparator_js_1.rightDate)(to)) <= 0) { // trim a part of the last range return values.slice(0, values.length - trim); } return values; } async _serveFromCache(from, to, start, pageSize, includes) { const cache = this.session.timeSeriesByDocId.get(this.docId); const ranges = cache.get(this.name); // try to find a range in cache that contains [from, to] // if found, chop just the relevant part from it and return to the user. // otherwise, try to find two ranges (fromRange, toRange), // such that 'fromRange' is the last occurence for which range.From <= from // and 'toRange' is the first occurence for which range.To >= to. // At the same time, figure out the missing partial ranges that we need to get from the server. let toRangeIndex; let fromRangeIndex = -1; let rangesToGetFromServer; for (toRangeIndex = 0; toRangeIndex < ranges.length; toRangeIndex++) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[toRangeIndex].from), (0, DatesComparator_js_1.leftDate)(from)) <= 0) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(ranges[toRangeIndex].to), (0, DatesComparator_js_1.rightDate)(to)) >= 0 || (ranges[toRangeIndex].entries.length - start >= pageSize)) { // we have the entire range in cache // we have all the range we need // or that we have all the results we need in smaller range return SessionTimeSeriesBase._chopRelevantRange(ranges[toRangeIndex], from, to, start, pageSize); } fromRangeIndex = toRangeIndex; continue; } // can't get the entire range from cache if (!rangesToGetFromServer) { rangesToGetFromServer = []; } // add the missing part [f, t] between current range start (or 'from') // and previous range end (or 'to') to the list of ranges we need to get from server const fromToUse = toRangeIndex === 0 || DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(ranges[toRangeIndex - 1].to), (0, DatesComparator_js_1.leftDate)(from)) < 0 ? from : ranges[toRangeIndex - 1].to; const toToUse = DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[toRangeIndex].from), (0, DatesComparator_js_1.rightDate)(to)) <= 0 ? ranges[toRangeIndex].from : to; rangesToGetFromServer.push({ name: this.name, from: fromToUse, to: toToUse }); if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(ranges[toRangeIndex].to), (0, DatesComparator_js_1.rightDate)(to)) >= 0) { break; } } if (toRangeIndex === ranges.length) { // requested range [from, to] ends after all ranges in cache // add the missing part between the last range end and 'to' // to the list of ranges we need to get from server if (!rangesToGetFromServer) { rangesToGetFromServer = []; } rangesToGetFromServer.push({ name: this.name, from: ranges.at(-1).to, to }); } // get all the missing parts from server this.session.incrementRequestCount(); const details = await this.session.operations.send(new GetMultipleTimeSeriesOperation_js_1.GetMultipleTimeSeriesOperation(this.docId, rangesToGetFromServer, start, pageSize, includes), this.session.sessionInfo); if (includes) { this._registerIncludes(details); } // merge all the missing parts we got from server // with all the ranges in cache that are between 'fromRange' and 'toRange' let resultToUser; const mergedValues = SessionTimeSeriesBase._mergeRangesWithResults(from, to, ranges, fromRangeIndex, toRangeIndex, details.values.get(this.name), r => resultToUser = r); if (!this.session.noTracking) { const fromDates = details.values.get(this.name) .map(x => (0, DatesComparator_js_1.leftDate)(x.from)); if (fromDates.length) { from = fromDates[0].date; for (const d of fromDates) { if (DatesComparator_js_1.DatesComparator.compare(d, (0, DatesComparator_js_1.leftDate)(from)) < 0) { from = d.date; } } } else { from = null; } const toDates = details.values.get(this.name) .map(x => (0, DatesComparator_js_1.rightDate)(x.to)); if (toDates.length) { to = toDates[0].date; for (const d of toDates) { if (DatesComparator_js_1.DatesComparator.compare(d, (0, DatesComparator_js_1.rightDate)(to)) > 0) { to = d.date; } } } else { to = null; } InMemoryDocumentSessionOperations_js_1.InMemoryDocumentSessionOperations.addToCache(this.name, from, to, fromRangeIndex, toRangeIndex, ranges, cache, mergedValues); } return resultToUser; } _registerIncludes(details) { for (const rangeResult of details.values.get(this.name)) { this._handleIncludes(rangeResult); } } static _mergeRangesWithResults(from, to, ranges, fromRangeIndex, toRangeIndex, resultFromServer, resultToUserSetter) { let skip = 0; let trim = 0; let currentResultIndex = 0; const mergedValues = []; const start = fromRangeIndex !== -1 ? fromRangeIndex : 0; const end = toRangeIndex === ranges.length ? ranges.length - 1 : toRangeIndex; for (let i = start; i <= end; i++) { if (i === fromRangeIndex) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[i].from), (0, DatesComparator_js_1.leftDate)(from)) <= 0 && DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(from), (0, DatesComparator_js_1.rightDate)(ranges[i].to)) <= 0) { // requested range [from, to] starts inside 'fromRange' // i.e fromRange.From <= from <= fromRange.To // so we might need to skip a part of it when we return the // result to the user (i.e. skip [fromRange.From, from]) if (ranges[i].entries) { for (const v of ranges[i].entries) { mergedValues.push(v); if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.definedDate)(v.timestamp), (0, DatesComparator_js_1.leftDate)(from)) < 0) { skip++; } } } } continue; } if (currentResultIndex < resultFromServer.length && DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(resultFromServer[currentResultIndex].from), (0, DatesComparator_js_1.leftDate)(ranges[i].from)) < 0) { // add current result from server to the merged list // in order to avoid duplication, skip first item in range // (unless this is the first time we're adding to the merged list) const toAdd = resultFromServer[currentResultIndex++] .entries .slice(mergedValues.length === 0 ? 0 : 1); mergedValues.push(...toAdd); } if (i === toRangeIndex) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[i].from), (0, DatesComparator_js_1.rightDate)(to)) <= 0) { // requested range [from, to] ends inside 'toRange' // so we might need to trim a part of it when we return the // result to the user (i.e. trim [to, toRange.to]) for (let index = mergedValues.length === 0 ? 0 : 1; index < ranges[i].entries.length; index++) { mergedValues.push(ranges[i].entries[index]); if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.definedDate)(ranges[i].entries[index].timestamp), (0, DatesComparator_js_1.rightDate)(to)) > 0) { trim++; } } } continue; } // add current range from cache to the merged list. // in order to avoid duplication, skip first item in range if needed let shouldSkip = false; if (mergedValues.length > 0) { shouldSkip = ranges[i].entries[0].timestamp.getTime() === mergedValues.at(-1).timestamp.getTime(); } const toAdd = ranges[i].entries.slice(!shouldSkip ? 0 : 1); mergedValues.push(...toAdd); } if (currentResultIndex < resultFromServer.length) { // the requested range ends after all the ranges in cache, // so the last missing part is from server // add last missing part to the merged list const toAdd = resultFromServer[currentResultIndex++] .entries .slice(mergedValues.length === 0 ? 0 : 1); mergedValues.push(...toAdd); } resultToUserSetter(SessionTimeSeriesBase._skipAndTrimRangeIfNeeded(from, to, fromRangeIndex === -1 ? null : ranges[fromRangeIndex], toRangeIndex === ranges.length ? null : ranges[toRangeIndex], mergedValues, skip, trim)); return mergedValues; } static _chopRelevantRange(range, from, to, start, pageSize) { if (!range.entries) { return []; } const result = []; for (const value of range.entries) { if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.definedDate)(value.timestamp), (0, DatesComparator_js_1.rightDate)(to)) > 0) { break; } if (DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.definedDate)(value.timestamp), (0, DatesComparator_js_1.leftDate)(from)) < 0) { continue; } if (start-- > 0) { continue; } if (pageSize-- <= 0) { break; } result.push(value); } return result; } async _getFromCache(from, to, includes, start, pageSize) { // RavenDB-16060 // Typed TimeSeries results need special handling when served from cache // since we cache the results untyped // in node we return untyped entries here const resultToUser = await this._serveFromCache(from, to, start, pageSize, includes); return resultToUser; } _notInCache(from, to) { const cache = this.session.timeSeriesByDocId.get(this.docId); if (!cache) { return true; } const ranges = cache.get(this.name); if (!ranges) { return true; } return ranges.length === 0 || DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.leftDate)(ranges[0].from), (0, DatesComparator_js_1.rightDate)(to)) > 0 || DatesComparator_js_1.DatesComparator.compare((0, DatesComparator_js_1.rightDate)(ranges.at(-1).to), (0, DatesComparator_js_1.leftDate)(from)) < 0; } } exports.SessionTimeSeriesBase = SessionTimeSeriesBase; //# sourceMappingURL=SessionTimeSeriesBase.js.map