squirreling 0.16.0 → 0.16.1
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 +3 -3
- package/src/backend/batch.js +33 -4
- package/src/backend/batchAdapters.js +31 -34
- package/src/execute/batches.js +5 -22
- package/src/execute/execute.js +1 -20
- package/src/execute/rowScope.js +27 -0
- package/src/execute/streamingAggregate.js +4 -22
- package/src/expression/batch.js +3 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"description": "Squirreling Async SQL Engine",
|
|
5
5
|
"author": "Hyperparam",
|
|
6
6
|
"homepage": "https://hyperparam.app",
|
|
@@ -40,10 +40,10 @@
|
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@types/node": "26.2.0",
|
|
43
|
-
"@vitest/coverage-v8": "4.1.
|
|
43
|
+
"@vitest/coverage-v8": "4.1.11",
|
|
44
44
|
"eslint": "9.39.4",
|
|
45
45
|
"eslint-plugin-jsdoc": "64.2.1",
|
|
46
46
|
"typescript": "7.0.2",
|
|
47
|
-
"vitest": "4.1.
|
|
47
|
+
"vitest": "4.1.11"
|
|
48
48
|
}
|
|
49
49
|
}
|
package/src/backend/batch.js
CHANGED
|
@@ -17,6 +17,35 @@ export function selectedRowCount(selection) {
|
|
|
17
17
|
return selection.indices.length
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Recognizes promises and thenables without relying on realm identity.
|
|
22
|
+
*
|
|
23
|
+
* @template T
|
|
24
|
+
* @param {T | PromiseLike<T>} value
|
|
25
|
+
* @returns {value is PromiseLike<T>}
|
|
26
|
+
*/
|
|
27
|
+
export function isPromiseLike(value) {
|
|
28
|
+
if (value === null || typeof value !== 'object' && typeof value !== 'function') return false
|
|
29
|
+
return typeof Reflect.get(value, 'then') === 'function'
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Avoids a promise boundary when every column is already loaded synchronously.
|
|
34
|
+
*
|
|
35
|
+
* @param {ColumnResult[]} results
|
|
36
|
+
* @returns {ColumnVector[] | Promise<ColumnVector[]>}
|
|
37
|
+
*/
|
|
38
|
+
export function resolveColumnResults(results) {
|
|
39
|
+
if (results.some(isPromiseLike)) return Promise.all(results)
|
|
40
|
+
/** @type {ColumnVector[]} */
|
|
41
|
+
const vectors = []
|
|
42
|
+
for (const result of results) {
|
|
43
|
+
if (isPromiseLike(result)) throw new Error('Unexpected asynchronous column result')
|
|
44
|
+
vectors.push(result)
|
|
45
|
+
}
|
|
46
|
+
return vectors
|
|
47
|
+
}
|
|
48
|
+
|
|
20
49
|
/**
|
|
21
50
|
* Creates base-aligned ordinal values for the rows in a selection.
|
|
22
51
|
*
|
|
@@ -158,8 +187,8 @@ export function readBatchColumn({ batch, columnIndex, selection = batch.selectio
|
|
|
158
187
|
: undefined,
|
|
159
188
|
})
|
|
160
189
|
const validated = validateColumnResult(result, selectedRowCount(selection))
|
|
161
|
-
if (validated
|
|
162
|
-
const settled = validated.then(function cacheResolved(vector) {
|
|
190
|
+
if (isPromiseLike(validated)) {
|
|
191
|
+
const settled = Promise.resolve(validated).then(function cacheResolved(vector) {
|
|
163
192
|
cache.pending.delete(signal)
|
|
164
193
|
cache.resolved ??= vector
|
|
165
194
|
return vector
|
|
@@ -215,8 +244,8 @@ function selectionIndexAt(selection, index) {
|
|
|
215
244
|
* @returns {ColumnResult}
|
|
216
245
|
*/
|
|
217
246
|
function validateColumnResult(result, expectedLength) {
|
|
218
|
-
if (result
|
|
219
|
-
return result.then(function validateResolvedVector(vector) {
|
|
247
|
+
if (isPromiseLike(result)) {
|
|
248
|
+
return Promise.resolve(result).then(function validateResolvedVector(vector) {
|
|
220
249
|
return validateVectorLength(vector, expectedLength)
|
|
221
250
|
})
|
|
222
251
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { readBatchColumn, selectVector, selectedRowCount, valueAt } from './batch.js'
|
|
1
|
+
import { readBatchColumn, resolveColumnResults, selectVector, selectedRowCount, valueAt } from './batch.js'
|
|
2
2
|
import { yieldToEventLoop } from '../execute/yield.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @import { AsyncBatch, AsyncCells, AsyncRow,
|
|
5
|
+
* @import { AsyncBatch, AsyncCells, AsyncRow, RowsToBatchesOptions, SqlPrimitive } from '../types.js'
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
const DEFAULT_BATCH_ROWS = 1024
|
|
@@ -21,24 +21,19 @@ export async function* rowsToBatches(rows, columnNames, options) {
|
|
|
21
21
|
if (!Number.isInteger(batchRows) || batchRows <= 0) {
|
|
22
22
|
throw new RangeError(`batchRows must be a positive integer, got ${batchRows}`)
|
|
23
23
|
}
|
|
24
|
-
|
|
25
|
-
let
|
|
24
|
+
/** @type {AsyncRow[]} */
|
|
25
|
+
let bufferedRows = []
|
|
26
26
|
|
|
27
27
|
for await (const row of rows) {
|
|
28
28
|
options?.signal?.throwIfAborted()
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
rowCount++
|
|
34
|
-
if (rowCount === batchRows) {
|
|
35
|
-
yield loadedBatch(values, rowCount)
|
|
36
|
-
values = makeValueBuffers(columnNames.length)
|
|
37
|
-
rowCount = 0
|
|
29
|
+
bufferedRows.push(row)
|
|
30
|
+
if (bufferedRows.length === batchRows) {
|
|
31
|
+
yield await materializeRows(bufferedRows, columnNames)
|
|
32
|
+
bufferedRows = []
|
|
38
33
|
}
|
|
39
34
|
}
|
|
40
35
|
|
|
41
|
-
if (
|
|
36
|
+
if (bufferedRows.length > 0) yield await materializeRows(bufferedRows, columnNames)
|
|
42
37
|
options?.signal?.throwIfAborted()
|
|
43
38
|
}
|
|
44
39
|
|
|
@@ -100,7 +95,7 @@ export async function collectBatches(batches, names, signal) {
|
|
|
100
95
|
const results = batch.columns.map(function readColumn(_column, columnIndex) {
|
|
101
96
|
return readBatchColumn({ batch, columnIndex, signal })
|
|
102
97
|
})
|
|
103
|
-
const vectors = await
|
|
98
|
+
const vectors = await resolveColumnResults(results)
|
|
104
99
|
const rowCount = selectedRowCount(batch.selection)
|
|
105
100
|
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
106
101
|
if (signal && rowIndex % 4000 === 0) {
|
|
@@ -133,6 +128,27 @@ async function readRowCell(row, name) {
|
|
|
133
128
|
return await cell()
|
|
134
129
|
}
|
|
135
130
|
|
|
131
|
+
/**
|
|
132
|
+
* @param {AsyncRow[]} rows
|
|
133
|
+
* @param {string[]} columnNames
|
|
134
|
+
* @returns {Promise<AsyncBatch>}
|
|
135
|
+
*/
|
|
136
|
+
async function materializeRows(rows, columnNames) {
|
|
137
|
+
const values = makeValueBuffers(columnNames.length)
|
|
138
|
+
/** @type {Promise<void>[]} */
|
|
139
|
+
const pendingReads = []
|
|
140
|
+
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
|
|
141
|
+
for (let columnIndex = 0; columnIndex < columnNames.length; columnIndex++) {
|
|
142
|
+
const name = columnNames[columnIndex]
|
|
143
|
+
pendingReads.push(readRowCell(rows[rowIndex], name).then(function storeValue(value) {
|
|
144
|
+
values[columnIndex][rowIndex] = value
|
|
145
|
+
}))
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
await Promise.all(pendingReads)
|
|
149
|
+
return loadedBatch(values, rows.length)
|
|
150
|
+
}
|
|
151
|
+
|
|
136
152
|
/**
|
|
137
153
|
* @param {number} count
|
|
138
154
|
* @returns {SqlPrimitive[][]}
|
|
@@ -157,22 +173,3 @@ function loadedBatch(values, rowCount) {
|
|
|
157
173
|
}),
|
|
158
174
|
}
|
|
159
175
|
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Avoids a promise boundary when every column is already loaded synchronously.
|
|
163
|
-
*
|
|
164
|
-
* @param {ColumnResult[]} results
|
|
165
|
-
* @returns {ColumnVector[] | Promise<ColumnVector[]>}
|
|
166
|
-
*/
|
|
167
|
-
function resolveColumns(results) {
|
|
168
|
-
if (results.some(function isPromise(result) { return result instanceof Promise })) {
|
|
169
|
-
return Promise.all(results)
|
|
170
|
-
}
|
|
171
|
-
/** @type {ColumnVector[]} */
|
|
172
|
-
const vectors = []
|
|
173
|
-
for (const result of results) {
|
|
174
|
-
if (result instanceof Promise) throw new Error('Unexpected asynchronous column result')
|
|
175
|
-
vectors.push(result)
|
|
176
|
-
}
|
|
177
|
-
return vectors
|
|
178
|
-
}
|
package/src/execute/batches.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { readBatchColumn, selectBatch, selectedRowCount, selectionOrdinals, valueAt } from '../backend/batch.js'
|
|
1
|
+
import { isPromiseLike, readBatchColumn, resolveColumnResults, selectBatch, selectedRowCount, selectionOrdinals, valueAt } from '../backend/batch.js'
|
|
2
2
|
import { keyify } from './utils.js'
|
|
3
3
|
import { yieldToEventLoop } from './yield.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @import { BatchProjection, CompiledBatchExpression } from '../internalTypes.js'
|
|
7
|
-
* @import { AsyncBatch, BatchColumn,
|
|
7
|
+
* @import { AsyncBatch, BatchColumn, ColumnVector, ReadColumn, RowSelection, SqlPrimitive } from '../types.js'
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
const INITIAL_FILTER_WINDOW_ROWS = 256
|
|
@@ -95,7 +95,7 @@ export async function* filterBatches(batches, expression, signal, targetRows) {
|
|
|
95
95
|
signal,
|
|
96
96
|
rowOffset: rowOffset + start,
|
|
97
97
|
})
|
|
98
|
-
const predicate = result
|
|
98
|
+
const predicate = isPromiseLike(result) ? await result : result
|
|
99
99
|
const { selection, selectedCount } = predicateSelection(predicate, windowRowCount)
|
|
100
100
|
start = end
|
|
101
101
|
matchedRows += selectedCount
|
|
@@ -138,8 +138,8 @@ export async function* distinctBatches(batches, signal) {
|
|
|
138
138
|
const results = batch.columns.map(function readColumn(_column, columnIndex) {
|
|
139
139
|
return readBatchColumn({ batch, columnIndex, signal })
|
|
140
140
|
})
|
|
141
|
-
const resolved =
|
|
142
|
-
const vectors = resolved
|
|
141
|
+
const resolved = resolveColumnResults(results)
|
|
142
|
+
const vectors = isPromiseLike(resolved) ? await resolved : resolved
|
|
143
143
|
const rowCount = selectedRowCount(batch.selection)
|
|
144
144
|
const indices = new Uint32Array(rowCount)
|
|
145
145
|
/** @type {SqlPrimitive[]} */
|
|
@@ -255,20 +255,3 @@ function predicateSelection(predicate, rowCount) {
|
|
|
255
255
|
selectedCount,
|
|
256
256
|
}
|
|
257
257
|
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* @param {ColumnResult[]} results
|
|
261
|
-
* @returns {ColumnVector[] | Promise<ColumnVector[]>}
|
|
262
|
-
*/
|
|
263
|
-
function resolveVectors(results) {
|
|
264
|
-
if (results.some(function isPromise(result) { return result instanceof Promise })) {
|
|
265
|
-
return Promise.all(results)
|
|
266
|
-
}
|
|
267
|
-
/** @type {ColumnVector[]} */
|
|
268
|
-
const vectors = []
|
|
269
|
-
for (const result of results) {
|
|
270
|
-
if (result instanceof Promise) throw new Error('Unexpected asynchronous column result')
|
|
271
|
-
vectors.push(result)
|
|
272
|
-
}
|
|
273
|
-
return vectors
|
|
274
|
-
}
|
package/src/execute/execute.js
CHANGED
|
@@ -12,6 +12,7 @@ import { executeHashAggregate, executeScalarAggregate } from './aggregates.js'
|
|
|
12
12
|
import { batchResult } from './batchResults.js'
|
|
13
13
|
import { distinctBatches, filterBatches, limitBatches, projectExpressionBatches } from './batches.js'
|
|
14
14
|
import { executeHashJoin, executeNestedLoopJoin, executePositionalJoin } from './join.js'
|
|
15
|
+
import { referencesRowScope } from './rowScope.js'
|
|
15
16
|
import { normalizeScanColumnResult } from './scanColumn.js'
|
|
16
17
|
import { executeSort } from './sort.js'
|
|
17
18
|
import { addBounds, minBounds, stableRowKey } from './utils.js'
|
|
@@ -720,26 +721,6 @@ function compileUnscopedBatchExpression(expression, columns, context) {
|
|
|
720
721
|
: compileBatchExpression(expression, columns)
|
|
721
722
|
}
|
|
722
723
|
|
|
723
|
-
/**
|
|
724
|
-
* Returns whether an expression reads a qualified identifier from row scope.
|
|
725
|
-
*
|
|
726
|
-
* @param {ExprNode} expression
|
|
727
|
-
* @param {string[]} columns
|
|
728
|
-
* @param {ExecuteContext} context
|
|
729
|
-
* @returns {boolean}
|
|
730
|
-
*/
|
|
731
|
-
function referencesRowScope(expression, columns, context) {
|
|
732
|
-
/** @type {IdentifierNode[]} */
|
|
733
|
-
const identifiers = []
|
|
734
|
-
collectColumnsFromExpr(expression, identifiers)
|
|
735
|
-
return identifiers.some(function scopedIdentifier(identifier) {
|
|
736
|
-
return Boolean(identifier.prefix && (
|
|
737
|
-
context.outerAliases?.has(identifier.prefix) ||
|
|
738
|
-
context.scope?.includes(identifier.prefix) && columns.includes(identifier.prefix)
|
|
739
|
-
))
|
|
740
|
-
})
|
|
741
|
-
}
|
|
742
|
-
|
|
743
724
|
/**
|
|
744
725
|
* Executes a filter operation (WHERE clause)
|
|
745
726
|
*
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { collectColumnsFromExpr } from '../plan/columns.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @import { ExecuteContext, ExprNode, IdentifierNode } from '../types.js'
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Returns whether an expression reads a qualified identifier from row scope.
|
|
9
|
+
* A current-scope prefix is ambiguous only when it is also a child column and
|
|
10
|
+
* could therefore mean struct-field access.
|
|
11
|
+
*
|
|
12
|
+
* @param {ExprNode} expression
|
|
13
|
+
* @param {readonly string[]} columns
|
|
14
|
+
* @param {ExecuteContext} context
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
export function referencesRowScope(expression, columns, context) {
|
|
18
|
+
/** @type {IdentifierNode[]} */
|
|
19
|
+
const identifiers = []
|
|
20
|
+
collectColumnsFromExpr(expression, identifiers)
|
|
21
|
+
return identifiers.some(function scopedIdentifier(identifier) {
|
|
22
|
+
return Boolean(identifier.prefix && (
|
|
23
|
+
context.outerAliases?.has(identifier.prefix) ||
|
|
24
|
+
context.scope?.includes(identifier.prefix) && columns.includes(identifier.prefix)
|
|
25
|
+
))
|
|
26
|
+
})
|
|
27
|
+
}
|
|
@@ -5,6 +5,7 @@ import { evaluateAll, evaluateExpr } from '../expression/evaluate.js'
|
|
|
5
5
|
import { collectColumnsFromExpr } from '../plan/columns.js'
|
|
6
6
|
import { isAggregateFunc } from '../validation/functions.js'
|
|
7
7
|
import { finalizeAccumulator, newAccumulator, updateAccumulator } from './accumulator.js'
|
|
8
|
+
import { referencesRowScope } from './rowScope.js'
|
|
8
9
|
import { sortEntriesByTerms } from './sort.js'
|
|
9
10
|
import { keyify } from './utils.js'
|
|
10
11
|
import { yieldToEventLoop } from './yield.js'
|
|
@@ -437,7 +438,7 @@ function compileBatchAggregateInputs(groupBy, specs, columns, context) {
|
|
|
437
438
|
/** @type {CompiledBatchExpression[]} */
|
|
438
439
|
const keys = []
|
|
439
440
|
for (const expression of groupBy) {
|
|
440
|
-
if (referencesRowScope(expression, context)) return undefined
|
|
441
|
+
if (referencesRowScope(expression, columns, context)) return undefined
|
|
441
442
|
const key = compileBatchExpression(expression, columns)
|
|
442
443
|
if (!key) return undefined
|
|
443
444
|
keys.push(key)
|
|
@@ -449,8 +450,8 @@ function compileBatchAggregateInputs(groupBy, specs, columns, context) {
|
|
|
449
450
|
const args = []
|
|
450
451
|
for (const spec of specs) {
|
|
451
452
|
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
|
|
453
|
+
if (spec.node.filter && referencesRowScope(spec.node.filter, columns, context)) return undefined
|
|
454
|
+
if (!spec.star && referencesRowScope(spec.node.args[0], columns, context)) return undefined
|
|
454
455
|
const filter = spec.node.filter
|
|
455
456
|
? compileBatchExpression(spec.node.filter, columns)
|
|
456
457
|
: undefined
|
|
@@ -468,25 +469,6 @@ function compileBatchAggregateInputs(groupBy, specs, columns, context) {
|
|
|
468
469
|
}
|
|
469
470
|
}
|
|
470
471
|
|
|
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
472
|
/**
|
|
491
473
|
* Resolves one set of compiled expressions against a batch.
|
|
492
474
|
*
|
package/src/expression/batch.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { composeSelections, readBatchColumn, selectVector, selectedRowCount, valueAt } from '../backend/batch.js'
|
|
1
|
+
import { composeSelections, isPromiseLike, readBatchColumn, resolveColumnResults, selectVector, selectedRowCount, valueAt } from '../backend/batch.js'
|
|
2
2
|
import { isPlainObject, sqlEquals } from '../execute/utils.js'
|
|
3
3
|
import { yieldToEventLoop } from '../execute/yield.js'
|
|
4
4
|
import { isStringFunc } from '../validation/functions.js'
|
|
@@ -126,8 +126,8 @@ function compileKernelEvaluator(node, columns) {
|
|
|
126
126
|
const results = dependencies.map(function readDependency(columnIndex) {
|
|
127
127
|
return readBatchColumn({ batch, columnIndex, selection, signal })
|
|
128
128
|
})
|
|
129
|
-
const vectors =
|
|
130
|
-
if (vectors
|
|
129
|
+
const vectors = resolveColumnResults(results)
|
|
130
|
+
if (isPromiseLike(vectors)) {
|
|
131
131
|
return vectors.then(function evaluateResolved(resolved) {
|
|
132
132
|
signal?.throwIfAborted()
|
|
133
133
|
return evaluateKernel(kernel, resolved, selection, signal, rowOffset, rowOrdinals)
|
|
@@ -640,20 +640,3 @@ function readsIdentifier(node) {
|
|
|
640
640
|
if (node.type === 'function') return node.args.some(readsIdentifier)
|
|
641
641
|
return false
|
|
642
642
|
}
|
|
643
|
-
|
|
644
|
-
/**
|
|
645
|
-
* @param {ColumnResult[]} results
|
|
646
|
-
* @returns {ColumnVector[] | Promise<ColumnVector[]>}
|
|
647
|
-
*/
|
|
648
|
-
function resolveVectors(results) {
|
|
649
|
-
if (results.some(function isPromise(result) { return result instanceof Promise })) {
|
|
650
|
-
return Promise.all(results)
|
|
651
|
-
}
|
|
652
|
-
/** @type {ColumnVector[]} */
|
|
653
|
-
const vectors = []
|
|
654
|
-
for (const result of results) {
|
|
655
|
-
if (result instanceof Promise) throw new Error('Unexpected asynchronous column result')
|
|
656
|
-
vectors.push(result)
|
|
657
|
-
}
|
|
658
|
-
return vectors
|
|
659
|
-
}
|