UNPKG

mingo

Version:

MongoDB query language for in-memory objects

165 lines (164 loc) 5.75 kB
import { concat, Lazy } from "../../lazy"; import { assert, HashMap, isArray, isDate, isNil, isNumber, isObject, isString, resolve } from "../../util"; import { TIME_UNITS } from "../expression/date/_internal"; import { $dateAdd } from "../expression/date/dateAdd"; import { $sort } from "./sort"; const $densify = (collection, expr, options) => { const { step, bounds, unit } = expr.range; if (unit) { assert(TIME_UNITS.includes(unit), ""); assert( Number.isInteger(step) && step > 0, "The step parameter in a range statement must be a whole number when densifying a date range." ); } else { assert( isNumber(step) && step > 0, "The step parameter in a range statement must be a strictly positive numeric value." ); } if (isArray(bounds)) { assert( !!bounds && bounds.length === 2, "A bounding array in a range statement must have exactly two elements." ); assert( (bounds.every(isNumber) || bounds.every(isDate)) && bounds[0] < bounds[1], "A bounding array must be an ascending array of either two dates or two numbers." ); assert( unit && !bounds.some(isNumber), "Numeric bounds may not have unit parameter." ); } if (expr.partitionByFields) { assert( isArray(expr.partitionByFields), "$densify: `partitionByFields` must be an array of strings" ); } collection = $sort(collection, { [expr.field]: 1 }, options); const computeNextValue = (value) => { return isNumber(value) ? value + step : $dateAdd(null, { startDate: value, unit, amount: step }, options); }; const isValidUnit = !!unit && TIME_UNITS.includes(unit); const getFieldValue = (o) => { const v = resolve(o, expr.field); assert( isNil(v) || isDate(v) && isValidUnit || isNumber(v) && !isValidUnit, "$densify: Densify field type must be numeric with 'unit' unspecified, or a date with 'unit' specified." ); return v; }; const peekItem = new Array(); const nilFieldsIterator = Lazy(() => { const item = collection.next(); const fieldValue = getFieldValue(item.value); if (isNil(fieldValue)) return item; peekItem.push(item); return { done: true }; }); const nextDensifyValueMap = HashMap.init(); const [lower, upper] = isArray(bounds) ? bounds : [bounds, bounds]; let maxFieldValue = void 0; const updateMaxFieldValue = (value) => { maxFieldValue = maxFieldValue === void 0 || maxFieldValue < value ? value : maxFieldValue; }; const rootKey = []; const densifyIterator = Lazy(() => { const item = peekItem.length > 0 ? peekItem.pop() : collection.next(); if (item.done) return item; let partitionKey = rootKey; if (isArray(expr.partitionByFields)) { partitionKey = expr.partitionByFields.map( (k) => resolve(item.value, k) ); assert( partitionKey.every(isString), "$densify: Partition fields must evaluate to string values." ); } assert(isObject(item.value), "$densify: collection must contain documents"); const itemValue = getFieldValue(item.value); if (!nextDensifyValueMap.has(partitionKey)) { if (lower == "full") { if (!nextDensifyValueMap.has(rootKey)) { nextDensifyValueMap.set(rootKey, itemValue); } nextDensifyValueMap.set(partitionKey, nextDensifyValueMap.get(rootKey)); } else if (lower == "partition") { nextDensifyValueMap.set(partitionKey, itemValue); } else { nextDensifyValueMap.set(partitionKey, lower); } } const densifyValue = nextDensifyValueMap.get(partitionKey); if ( // current item field value is lower than current densify value. itemValue <= densifyValue || // range value equals or exceeds upper bound upper != "full" && upper != "partition" && densifyValue >= upper ) { if (densifyValue <= itemValue) { nextDensifyValueMap.set(partitionKey, computeNextValue(densifyValue)); } updateMaxFieldValue(itemValue); return item; } nextDensifyValueMap.set(partitionKey, computeNextValue(densifyValue)); updateMaxFieldValue(densifyValue); const denseObj = { [expr.field]: densifyValue }; if (partitionKey) { partitionKey.forEach((v, i) => { denseObj[expr.partitionByFields[i]] = v; }); } peekItem.push(item); return { done: false, value: denseObj }; }); if (lower !== "full") return concat(nilFieldsIterator, densifyIterator); let paritionIndex = -1; let partitionKeysSet = void 0; const fullBoundsIterator = Lazy(() => { if (paritionIndex === -1) { const fullDensifyValue = nextDensifyValueMap.get(rootKey); nextDensifyValueMap.delete(rootKey); partitionKeysSet = Array.from(nextDensifyValueMap.keys()); if (partitionKeysSet.length === 0) { partitionKeysSet.push(rootKey); nextDensifyValueMap.set(rootKey, fullDensifyValue); } paritionIndex++; } do { const partitionKey = partitionKeysSet[paritionIndex]; const partitionMaxValue = nextDensifyValueMap.get(partitionKey); if (partitionMaxValue < maxFieldValue) { nextDensifyValueMap.set( partitionKey, computeNextValue(partitionMaxValue) ); const denseObj = { [expr.field]: partitionMaxValue }; partitionKey.forEach((v, i) => { denseObj[expr.partitionByFields[i]] = v; }); return { done: false, value: denseObj }; } paritionIndex++; } while (paritionIndex < partitionKeysSet.length); return { done: true }; }); return concat(nilFieldsIterator, densifyIterator, fullBoundsIterator); }; export { $densify };