ravendb
Version:
RavenDB client for Node.js
419 lines • 22 kB
JavaScript
"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