squirreling 0.15.2 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/backend/batch.js +236 -0
- package/src/backend/batchAdapters.js +178 -0
- package/src/backend/dataSource.js +17 -1
- package/src/execute/aggregates.js +2 -1
- package/src/execute/batchResults.js +28 -0
- package/src/execute/batches.js +274 -0
- package/src/execute/execute.js +440 -24
- package/src/execute/streamingAggregate.js +151 -1
- package/src/execute/utils.js +37 -0
- package/src/expression/batch.js +659 -0
- package/src/expression/binary.js +17 -4
- package/src/expression/evaluate.js +39 -109
- package/src/expression/scalar.js +105 -0
- package/src/index.d.ts +44 -1
- package/src/index.js +9 -0
- package/src/internalTypes.d.ts +28 -0
- package/src/plan/columns.js +105 -59
- package/src/plan/plan.js +12 -3
- package/src/plan/types.d.ts +3 -0
- package/src/types.d.ts +156 -4
- package/src/validation/tables.js +4 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { selectedRowCount, valueAt } from '../backend/batch.js'
|
|
1
2
|
import { derivedAlias } from '../expression/alias.js'
|
|
3
|
+
import { compileBatchExpression } from '../expression/batch.js'
|
|
2
4
|
import { evaluateAll, evaluateExpr } from '../expression/evaluate.js'
|
|
3
5
|
import { collectColumnsFromExpr } from '../plan/columns.js'
|
|
4
6
|
import { isAggregateFunc } from '../validation/functions.js'
|
|
@@ -8,7 +10,8 @@ import { keyify } from './utils.js'
|
|
|
8
10
|
import { yieldToEventLoop } from './yield.js'
|
|
9
11
|
|
|
10
12
|
/**
|
|
11
|
-
* @import {
|
|
13
|
+
* @import { BatchAggregateInputs, CompiledBatchExpression } from '../internalTypes.js'
|
|
14
|
+
* @import { AsyncBatch, AsyncCells, AsyncRow, ColumnVector, ExecuteContext, ExprNode, FunctionNode, IdentifierNode, QueryResults, SelectColumn, SqlPrimitive } from '../types.js'
|
|
12
15
|
* @import { HashAggregateNode, ScalarAggregateNode } from '../plan/types.js'
|
|
13
16
|
* @import { Accumulator } from './accumulator.js'
|
|
14
17
|
*/
|
|
@@ -419,6 +422,141 @@ async function accumulateChunk({ chunk, groupBy, specs, groups, needsRow, contex
|
|
|
419
422
|
}
|
|
420
423
|
}
|
|
421
424
|
|
|
425
|
+
/**
|
|
426
|
+
* Compiles the expressions an aggregate reads from each input batch. A
|
|
427
|
+
* filtered non-star aggregate retains the row path because its argument must
|
|
428
|
+
* only be evaluated for passing rows.
|
|
429
|
+
*
|
|
430
|
+
* @param {ExprNode[]} groupBy
|
|
431
|
+
* @param {StreamingAggSpec[]} specs
|
|
432
|
+
* @param {readonly string[]} columns
|
|
433
|
+
* @param {ExecuteContext} context
|
|
434
|
+
* @returns {BatchAggregateInputs | undefined}
|
|
435
|
+
*/
|
|
436
|
+
function compileBatchAggregateInputs(groupBy, specs, columns, context) {
|
|
437
|
+
/** @type {CompiledBatchExpression[]} */
|
|
438
|
+
const keys = []
|
|
439
|
+
for (const expression of groupBy) {
|
|
440
|
+
if (referencesRowScope(expression, context)) return undefined
|
|
441
|
+
const key = compileBatchExpression(expression, columns)
|
|
442
|
+
if (!key) return undefined
|
|
443
|
+
keys.push(key)
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/** @type {(CompiledBatchExpression | undefined)[]} */
|
|
447
|
+
const filters = []
|
|
448
|
+
/** @type {(CompiledBatchExpression | undefined)[]} */
|
|
449
|
+
const args = []
|
|
450
|
+
for (const spec of specs) {
|
|
451
|
+
if (spec.node.filter && !spec.star) return undefined
|
|
452
|
+
if (spec.node.filter && referencesRowScope(spec.node.filter, context)) return undefined
|
|
453
|
+
if (!spec.star && referencesRowScope(spec.node.args[0], context)) return undefined
|
|
454
|
+
const filter = spec.node.filter
|
|
455
|
+
? compileBatchExpression(spec.node.filter, columns)
|
|
456
|
+
: undefined
|
|
457
|
+
const argument = spec.star
|
|
458
|
+
? undefined
|
|
459
|
+
: compileBatchExpression(spec.node.args[0], columns)
|
|
460
|
+
if (spec.node.filter && !filter || !spec.star && !argument) return undefined
|
|
461
|
+
filters.push(filter)
|
|
462
|
+
args.push(argument)
|
|
463
|
+
}
|
|
464
|
+
return {
|
|
465
|
+
keys,
|
|
466
|
+
filters,
|
|
467
|
+
args,
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/**
|
|
472
|
+
* Returns whether an expression reads a qualified identifier whose table
|
|
473
|
+
* scope the batch compiler cannot distinguish from struct-field access.
|
|
474
|
+
*
|
|
475
|
+
* @param {ExprNode} expression
|
|
476
|
+
* @param {ExecuteContext} context
|
|
477
|
+
* @returns {boolean}
|
|
478
|
+
*/
|
|
479
|
+
function referencesRowScope(expression, context) {
|
|
480
|
+
/** @type {IdentifierNode[]} */
|
|
481
|
+
const identifiers = []
|
|
482
|
+
collectColumnsFromExpr(expression, identifiers)
|
|
483
|
+
return identifiers.some(function isScopedReference(identifier) {
|
|
484
|
+
return Boolean(identifier.prefix && (
|
|
485
|
+
context.scope?.includes(identifier.prefix) || context.outerAliases?.has(identifier.prefix)
|
|
486
|
+
))
|
|
487
|
+
})
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
/**
|
|
491
|
+
* Resolves one set of compiled expressions against a batch.
|
|
492
|
+
*
|
|
493
|
+
* @param {(CompiledBatchExpression | undefined)[]} expressions
|
|
494
|
+
* @param {AsyncBatch} batch
|
|
495
|
+
* @param {ExecuteContext} context
|
|
496
|
+
* @param {number} rowOffset
|
|
497
|
+
* @returns {Promise<(ColumnVector | undefined)[]>}
|
|
498
|
+
*/
|
|
499
|
+
function evaluateBatchInputs(expressions, batch, context, rowOffset) {
|
|
500
|
+
return Promise.all(expressions.map(function evaluateInput(expression) {
|
|
501
|
+
return expression?.evaluate({ batch, selection: batch.selection, signal: context.signal, rowOffset })
|
|
502
|
+
}))
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Folds a native batch directly into aggregate state without constructing
|
|
507
|
+
* rows, cells, or per-value promises.
|
|
508
|
+
*
|
|
509
|
+
* @param {object} options
|
|
510
|
+
* @param {AsyncBatch} options.batch
|
|
511
|
+
* @param {BatchAggregateInputs} options.inputs
|
|
512
|
+
* @param {StreamingAggSpec[]} options.specs
|
|
513
|
+
* @param {Map<unknown, StreamingGroup>} options.groups
|
|
514
|
+
* @param {ExecuteContext} options.context
|
|
515
|
+
* @param {number} options.rowOffset
|
|
516
|
+
* @returns {Promise<void>}
|
|
517
|
+
*/
|
|
518
|
+
async function accumulateBatch({ batch, inputs, specs, groups, context, rowOffset }) {
|
|
519
|
+
const [keys, filters, args] = await Promise.all([
|
|
520
|
+
evaluateBatchInputs(inputs.keys, batch, context, rowOffset),
|
|
521
|
+
evaluateBatchInputs(inputs.filters, batch, context, rowOffset),
|
|
522
|
+
evaluateBatchInputs(inputs.args, batch, context, rowOffset),
|
|
523
|
+
])
|
|
524
|
+
const rowCount = selectedRowCount(batch.selection)
|
|
525
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
526
|
+
if (rowIndex > 0 && rowIndex % CHUNK_SIZE === 0) {
|
|
527
|
+
await yieldToEventLoop()
|
|
528
|
+
context.signal?.throwIfAborted()
|
|
529
|
+
}
|
|
530
|
+
const keyValues = keys.map(function keyValue(vector) {
|
|
531
|
+
if (!vector) throw new Error('Missing compiled group key')
|
|
532
|
+
return valueAt(vector, rowIndex)
|
|
533
|
+
})
|
|
534
|
+
const key = keyValues.length === 0
|
|
535
|
+
? true
|
|
536
|
+
: keyValues.length === 1 ? keyify(keyValues[0]) : keyify(...keyValues)
|
|
537
|
+
let group = groups.get(key)
|
|
538
|
+
if (!group) {
|
|
539
|
+
group = {
|
|
540
|
+
firstRow: undefined,
|
|
541
|
+
keyValues,
|
|
542
|
+
accumulators: specs.map(spec => newAccumulator(spec.funcName, spec.node.distinct)),
|
|
543
|
+
}
|
|
544
|
+
groups.set(key, group)
|
|
545
|
+
}
|
|
546
|
+
for (let specIndex = 0; specIndex < specs.length; specIndex++) {
|
|
547
|
+
const filter = filters[specIndex]
|
|
548
|
+
if (filter && !valueAt(filter, rowIndex)) continue
|
|
549
|
+
const spec = specs[specIndex]
|
|
550
|
+
if (spec.star && spec.funcName === 'COUNT') {
|
|
551
|
+
group.accumulators[specIndex].count++
|
|
552
|
+
} else {
|
|
553
|
+
const argument = args[specIndex]
|
|
554
|
+
updateAccumulator(spec.funcName, group.accumulators[specIndex], argument ? valueAt(argument, rowIndex) : null)
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
422
560
|
/**
|
|
423
561
|
* Consumes the child rows into per-group accumulators, holding at most one
|
|
424
562
|
* chunk of rows at a time. Throws when aborted so partial accumulators are
|
|
@@ -435,6 +573,18 @@ async function accumulateChunk({ chunk, groupBy, specs, groups, needsRow, contex
|
|
|
435
573
|
async function accumulateGroups({ child, groupBy, specs, needsRow, context }) {
|
|
436
574
|
/** @type {Map<unknown, StreamingGroup>} */
|
|
437
575
|
const groups = new Map()
|
|
576
|
+
const batchInputs = child.batches && !needsRow
|
|
577
|
+
? compileBatchAggregateInputs(groupBy, specs, child.columns, context)
|
|
578
|
+
: undefined
|
|
579
|
+
if (batchInputs && child.batches) {
|
|
580
|
+
let rowOffset = 0
|
|
581
|
+
for await (const batch of child.batches()) {
|
|
582
|
+
await accumulateBatch({ batch, inputs: batchInputs, specs, groups, context, rowOffset })
|
|
583
|
+
rowOffset += selectedRowCount(batch.selection)
|
|
584
|
+
context.signal?.throwIfAborted()
|
|
585
|
+
}
|
|
586
|
+
return groups
|
|
587
|
+
}
|
|
438
588
|
/** @type {AsyncRow[]} */
|
|
439
589
|
let chunk = []
|
|
440
590
|
for await (const row of child.rows()) {
|
package/src/execute/utils.js
CHANGED
|
@@ -1,9 +1,42 @@
|
|
|
1
|
+
import { collectBatches } from '../backend/batchAdapters.js'
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* @import { AsyncRow, OrderByItem, QueryResults, SqlPrimitive } from '../types.js'
|
|
3
5
|
*/
|
|
4
6
|
|
|
5
7
|
const primitiveTypes = new Set(['number', 'bigint', 'boolean', 'string'])
|
|
6
8
|
|
|
9
|
+
/** @type {WeakMap<QueryResults, AbortSignal>} */
|
|
10
|
+
const querySignals = new WeakMap()
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Associates an execution signal with results without expanding the public
|
|
14
|
+
* result shape solely for the collection adapter.
|
|
15
|
+
*
|
|
16
|
+
* @param {QueryResults} results
|
|
17
|
+
* @param {AbortSignal} [signal]
|
|
18
|
+
* @returns {QueryResults}
|
|
19
|
+
*/
|
|
20
|
+
export function bindQuerySignal(results, signal) {
|
|
21
|
+
if (!signal || querySignals.get(results) === signal) return results
|
|
22
|
+
|
|
23
|
+
const { batches, rows } = results
|
|
24
|
+
results.rows = async function* boundRows() {
|
|
25
|
+
signal.throwIfAborted()
|
|
26
|
+
yield* rows.call(results)
|
|
27
|
+
signal.throwIfAborted()
|
|
28
|
+
}
|
|
29
|
+
if (batches) {
|
|
30
|
+
results.batches = async function* boundBatches() {
|
|
31
|
+
signal.throwIfAborted()
|
|
32
|
+
yield* batches.call(results)
|
|
33
|
+
signal.throwIfAborted()
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
querySignals.set(results, signal)
|
|
37
|
+
return results
|
|
38
|
+
}
|
|
39
|
+
|
|
7
40
|
/**
|
|
8
41
|
* Compares two values for a single ORDER BY term, handling nulls and direction
|
|
9
42
|
*
|
|
@@ -49,6 +82,10 @@ export function compareForTerm(a, b, term) {
|
|
|
49
82
|
* @returns {Promise<Record<string, SqlPrimitive>[]>} array of all yielded values
|
|
50
83
|
*/
|
|
51
84
|
export async function collect(results) {
|
|
85
|
+
if (results.batches) {
|
|
86
|
+
return await collectBatches(results.batches(), results.columns, querySignals.get(results))
|
|
87
|
+
}
|
|
88
|
+
|
|
52
89
|
// Collect all rows first, then materialize cells concurrently
|
|
53
90
|
// This enables dataloader-style batching of cell accessors
|
|
54
91
|
/** @type {AsyncRow[]} */
|