184 lines
6.0 KiB
JavaScript
184 lines
6.0 KiB
JavaScript
import { ComputeOptions } from "../../core";
|
|
import { concat, Lazy } from "../../lazy";
|
|
import { TIME_UNITS } from "../../types";
|
|
import {
|
|
assert,
|
|
isArray,
|
|
isDate,
|
|
isNil,
|
|
isNumber,
|
|
isObject,
|
|
isString,
|
|
resolve,
|
|
ValueMap
|
|
} from "../../util";
|
|
import { $dateAdd } from "../expression/date/dateAdd";
|
|
import { $sort } from "./sort";
|
|
const EMPTY_OBJECT = Object.freeze({});
|
|
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 nilOptions = ComputeOptions.init(options, null);
|
|
const computeNextValue = (value) => {
|
|
return isNumber(value) ? value + step : $dateAdd(
|
|
EMPTY_OBJECT,
|
|
{ startDate: value, unit, amount: step },
|
|
nilOptions
|
|
);
|
|
};
|
|
const isValidUnit = !!unit && TIME_UNITS.includes(unit);
|
|
const getFieldValue = (o) => {
|
|
const v = resolve(o, expr.field);
|
|
if (isNil(v)) return v;
|
|
if (isNumber(v)) {
|
|
assert(
|
|
!isValidUnit,
|
|
"$densify: Encountered non-date value in collection when step has a date unit."
|
|
);
|
|
} else if (isDate(v)) {
|
|
assert(
|
|
isValidUnit,
|
|
"$densify: Encountered date value in collection when step does not have a date unit."
|
|
);
|
|
} else {
|
|
assert(false, "$densify: Densify field type must be numeric or a date");
|
|
}
|
|
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 = ValueMap.init(
|
|
options.hashFunction
|
|
);
|
|
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
|
|
};
|