arquero
Version:
Query processing and transformation of array-backed data tables.
279 lines (252 loc) • 5.87 kB
JavaScript
import { error } from '../util/error.js';
import { isValid } from '../util/is-valid.js';
import { noop } from '../util/no-op.js';
import { NULL } from '../util/null.js';
/**
* Initialize a window operator.
* @callback WindowInit
* @return {void}
*/
/**
* A storage object for the state of the window.
* @typedef {import('../verbs/window/window-state.js').windowState} WindowState
*/
/**
* Retrieve an output value from a window operator.
* @callback WindowValue
* @param {WindowState} state The window state object.
* @return {*} The output value.
*/
/**
* Initialize an aggregate operator.
* @typedef {import('./aggregate-functions.js').AggregateInit} AggregateInit
*/
/**
* Retrive an output value from an aggregate operator.
* @typedef {import('./aggregate-functions.js').AggregateValue} AggregateValue
*/
/**
* An operator instance for a window function.
* @typedef {object} WindowOperator
* @property {AggregateInit} init Initialize the operator.
* @property {AggregateValue} value Retrieve an output value.
*/
/**
* Create a new window operator instance.
* @callback WindowCreate
* @param {...any} params The aggregate operator parameters.
* @return {WindowOperator} The instantiated window operator.
*/
/**
* Create a new aggregate operator instance.
* @typedef {import('./aggregate-functions.js').AggregateCreate} AggregateCreate
*/
/**
* An operator definition for a window function.
* @typedef {object} WindowDef
* @property {AggregateCreate} create Create a new operator instance.
* @property {number[]} param Two-element array containing the
* counts of input fields and additional parameters.
*/
const rank = {
create() {
let rank;
return {
init: () => rank = 1,
value: w => {
const i = w.index;
return (i && !w.peer(i)) ? (rank = i + 1) : rank;
}
};
},
param: []
};
const cume_dist = {
create() {
let cume;
return {
init: () => cume = 0,
value: w => {
const { index, peer, size } = w;
let i = index;
if (cume < i) {
while (i + 1 < size && peer(i + 1)) ++i;
cume = i;
}
return (1 + cume) / size;
}
};
},
param: []
};
/**
* Window operator definitions.
*/
export const windowFunctions = {
/** @type {WindowDef} */
row_number: {
create() {
return {
init: noop,
value: w => w.index + 1
};
},
param: []
},
/** @type {WindowDef} */
rank,
/** @type {WindowDef} */
avg_rank: {
create() {
let j, rank;
return {
init: () => (j = -1, rank = 1),
value: w => {
const i = w.index;
if (i >= j) {
for (rank = j = i + 1; w.peer(j); rank += ++j);
rank /= (j - i);
}
return rank;
}
};
},
param: []
},
/** @type {WindowDef} */
dense_rank: {
create() {
let drank;
return {
init: () => drank = 1,
value: w => {
const i = w.index;
return (i && !w.peer(i)) ? ++drank : drank;
}
};
},
param: []
},
/** @type {WindowDef} */
percent_rank: {
create() {
const { init, value } = rank.create();
return {
init,
value: w => (value(w) - 1) / (w.size - 1)
};
},
param: []
},
/** @type {WindowDef} */
cume_dist,
/** @type {WindowDef} */
ntile: {
create(num) {
num = +num;
if (!(num > 0)) error('ntile num must be greater than zero.');
const { init, value } = cume_dist.create();
return {
init,
value: w => Math.ceil(num * value(w))
};
},
param: [0, 1]
},
/** @type {WindowDef} */
lag: {
create(offset, defaultValue = NULL) {
offset = +offset || 1;
return {
init: noop,
value: (w, f) => {
const i = w.index - offset;
return i >= 0 ? w.value(i, f) : defaultValue;
}
};
},
param: [1, 2]
},
/** @type {WindowDef} */
lead: {
create(offset, defaultValue = NULL) {
offset = +offset || 1;
return {
init: noop,
value: (w, f) => {
const i = w.index + offset;
return i < w.size ? w.value(i, f) : defaultValue;
}
};
},
param: [1, 2]
},
/** @type {WindowDef} */
first_value: {
create() {
return {
init: noop,
value: (w, f) => w.value(w.i0, f)
};
},
param: [1]
},
/** @type {WindowDef} */
last_value: {
create() {
return {
init: noop,
value: (w, f) => w.value(w.i1 - 1, f)
};
},
param: [1]
},
/** @type {WindowDef} */
nth_value: {
create(nth) {
nth = +nth;
if (!(nth > 0)) error('nth_value nth must be greater than zero.');
return {
init: noop,
value: (w, f) => {
const i = w.i0 + (nth - 1);
return i < w.i1 ? w.value(i, f) : NULL;
}
};
},
param: [1, 1]
},
/** @type {WindowDef} */
fill_down: {
create(defaultValue = NULL) {
let value;
return {
init: () => value = defaultValue,
value: (w, f) => {
const v = w.value(w.index, f);
return isValid(v) ? (value = v) : value;
}
};
},
param: [1, 1]
},
/** @type {WindowDef} */
fill_up: {
create(defaultValue = NULL) {
let value, idx;
return {
init: () => (value = defaultValue, idx = -1),
value: (w, f) => w.index <= idx ? value
: (idx = find(w, f, w.index)) >= 0 ? (value = w.value(idx, f))
: (idx = w.size, value = defaultValue)
};
},
param: [1, 1]
}
};
function find(w, f, i) {
for (const n = w.size; i < n; ++i) {
if (isValid(w.value(i, f))) return i;
}
return -1;
}