arquero
Version:
Query processing and transformation of array-backed data tables.
96 lines (83 loc) • 2.6 kB
JavaScript
import { reducers } from '../reduce/util.js';
import { getWindow, hasAggregate } from '../../op/index.js';
import { concat } from '../../util/concat.js';
import { unroll } from '../../util/unroll.js';
import { windowState } from './window-state.js';
const frameValue = op =>
(op.frame || [null, null]).map(v => Number.isFinite(v) ? Math.abs(v) : null);
const peersValue = op => !!op.peers;
function windowOp(spec) {
const { id, name, fields = [], params = [] } = spec;
return {
...getWindow(name).create(...params),
get: fields.length ? fields[0] : null,
id
};
}
export function window(table, cols, exprs, result = {}, ops) {
// instantiate window states
const data = table.data();
const states = windowStates(ops, data);
const nstate = states.length;
const write = unroll(
['r', 'd', 'op'],
'{' + concat(cols, (_, i) => `_${i}[r] = $${i}(r, d, op);`) + '}',
cols, exprs
);
// scan each ordered partition
table.partitions().forEach((rows, key) => {
const size = rows.length;
const peers = windowPeers(table, rows);
// initialize window states
for (let i = 0; i < nstate; ++i) {
states[i].init(rows, peers, result, key);
}
// calculate window values per-row
const op = id => result[id][key];
for (let index = 0; index < size; ++index) {
// advance window frame, updates result object
for (let i = 0; i < nstate; ++i) {
states[i].step(index);
}
write(rows[index], data, op);
}
});
}
function windowStates(ops, data) {
const map = {};
// group operations by window frame parameters
ops.forEach(op => {
const frame = frameValue(op);
const peers = peersValue(op);
const key = `${frame},${peers}`;
const { aggOps, winOps } = map[key] || (map[key] = {
frame,
peers,
aggOps: [],
winOps: []
});
hasAggregate(op.name)
? aggOps.push(op)
: winOps.push(windowOp(op));
});
return Object.values(map).map(_ => windowState(
data, _.frame, _.peers, _.winOps,
reducers(_.aggOps, _.frame[0] != null ? -1 : 1)
));
}
function windowPeers(table, rows) {
if (table.isOrdered()) {
// generate peer ids for sort equality checking
const compare = table.comparator();
const data = table.data();
const nrows = rows.length;
const peers = new Uint32Array(nrows);
for (let i = 1, index = 0; i < nrows; ++i) {
peers[i] = compare(rows[i - 1], rows[i], data) ? ++index : index;
}
return peers;
} else {
// no sort, no peers: reuse row indices as peer ids
return rows;
}
}