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
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
import { readBatchColumn, selectBatch, selectedRowCount, selectionOrdinals, valueAt } from '../backend/batch.js'
|
|
2
|
+
import { keyify } from './utils.js'
|
|
3
|
+
import { yieldToEventLoop } from './yield.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @import { BatchProjection, CompiledBatchExpression } from '../internalTypes.js'
|
|
7
|
+
* @import { AsyncBatch, BatchColumn, ColumnResult, ColumnVector, ReadColumn, RowSelection, SqlPrimitive } from '../types.js'
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const INITIAL_FILTER_WINDOW_ROWS = 256
|
|
11
|
+
const MAX_FILTER_WINDOW_ROWS = 16_384
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Applies LIMIT/OFFSET by composing batch selections and stops consuming the
|
|
15
|
+
* source as soon as the requested range is complete.
|
|
16
|
+
*
|
|
17
|
+
* @param {AsyncIterable<AsyncBatch>} batches
|
|
18
|
+
* @param {number} [limit]
|
|
19
|
+
* @param {number} [offset]
|
|
20
|
+
* @param {AbortSignal} [signal]
|
|
21
|
+
* @yields {AsyncBatch}
|
|
22
|
+
*/
|
|
23
|
+
export async function* limitBatches(batches, limit = Infinity, offset = 0, signal) {
|
|
24
|
+
if (limit <= 0) return
|
|
25
|
+
let skipped = 0
|
|
26
|
+
let yielded = 0
|
|
27
|
+
for await (const batch of batches) {
|
|
28
|
+
signal?.throwIfAborted()
|
|
29
|
+
const rowCount = selectedRowCount(batch.selection)
|
|
30
|
+
if (skipped + rowCount <= offset) {
|
|
31
|
+
skipped += rowCount
|
|
32
|
+
continue
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const start = Math.max(0, offset - skipped)
|
|
36
|
+
const remaining = limit - yielded
|
|
37
|
+
const end = Math.min(rowCount, start + remaining)
|
|
38
|
+
if (end > start) {
|
|
39
|
+
if (start === 0 && end === rowCount) {
|
|
40
|
+
yield batch
|
|
41
|
+
} else {
|
|
42
|
+
yield selectBatch(batch, {
|
|
43
|
+
type: 'range',
|
|
44
|
+
start,
|
|
45
|
+
end,
|
|
46
|
+
length: rowCount,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
yielded += end - start
|
|
50
|
+
}
|
|
51
|
+
skipped += rowCount
|
|
52
|
+
if (yielded >= limit) return
|
|
53
|
+
}
|
|
54
|
+
signal?.throwIfAborted()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Evaluates a predicate in adaptive windows when a downstream limit can stop
|
|
59
|
+
* early, then composes selections without reading or copying payload columns.
|
|
60
|
+
*
|
|
61
|
+
* @param {AsyncIterable<AsyncBatch>} batches
|
|
62
|
+
* @param {CompiledBatchExpression} expression
|
|
63
|
+
* @param {AbortSignal} [signal]
|
|
64
|
+
* @param {number} [targetRows] - Matches needed by a downstream limit.
|
|
65
|
+
* @yields {AsyncBatch}
|
|
66
|
+
*/
|
|
67
|
+
export async function* filterBatches(batches, expression, signal, targetRows) {
|
|
68
|
+
let rowOffset = 0
|
|
69
|
+
let matchedRows = 0
|
|
70
|
+
let windowRows = targetRows === undefined
|
|
71
|
+
? Infinity
|
|
72
|
+
: Math.min(MAX_FILTER_WINDOW_ROWS, Math.max(INITIAL_FILTER_WINDOW_ROWS, targetRows))
|
|
73
|
+
for await (const batch of batches) {
|
|
74
|
+
signal?.throwIfAborted()
|
|
75
|
+
const rowCount = selectedRowCount(batch.selection)
|
|
76
|
+
let start = 0
|
|
77
|
+
while (start < rowCount) {
|
|
78
|
+
const remainingRows = rowCount - start
|
|
79
|
+
const requestedRows = targetRows !== undefined && matchedRows < targetRows
|
|
80
|
+
? windowRows
|
|
81
|
+
: remainingRows
|
|
82
|
+
const end = Math.min(rowCount, start + requestedRows)
|
|
83
|
+
const windowBatch = start === 0 && end === rowCount
|
|
84
|
+
? batch
|
|
85
|
+
: selectBatch(batch, {
|
|
86
|
+
type: 'range',
|
|
87
|
+
start,
|
|
88
|
+
end,
|
|
89
|
+
length: rowCount,
|
|
90
|
+
})
|
|
91
|
+
const windowRowCount = end - start
|
|
92
|
+
const result = expression.evaluate({
|
|
93
|
+
batch: windowBatch,
|
|
94
|
+
selection: windowBatch.selection,
|
|
95
|
+
signal,
|
|
96
|
+
rowOffset: rowOffset + start,
|
|
97
|
+
})
|
|
98
|
+
const predicate = result instanceof Promise ? await result : result
|
|
99
|
+
const { selection, selectedCount } = predicateSelection(predicate, windowRowCount)
|
|
100
|
+
start = end
|
|
101
|
+
matchedRows += selectedCount
|
|
102
|
+
|
|
103
|
+
if (targetRows !== undefined && matchedRows < targetRows) {
|
|
104
|
+
if (selectedCount === 0) {
|
|
105
|
+
windowRows = Math.min(MAX_FILTER_WINDOW_ROWS, windowRows * 2)
|
|
106
|
+
} else {
|
|
107
|
+
const remainingMatches = targetRows - matchedRows
|
|
108
|
+
const estimatedRows = Math.ceil(remainingMatches * windowRowCount / selectedCount * 1.25)
|
|
109
|
+
windowRows = Math.min(MAX_FILTER_WINDOW_ROWS, Math.max(INITIAL_FILTER_WINDOW_ROWS, estimatedRows))
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (selectedCount === 0) continue
|
|
114
|
+
if (selectedCount === windowRowCount) {
|
|
115
|
+
yield windowBatch
|
|
116
|
+
} else {
|
|
117
|
+
yield selectBatch(windowBatch, selection)
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
rowOffset += rowCount
|
|
121
|
+
}
|
|
122
|
+
signal?.throwIfAborted()
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Removes duplicate rows while retaining zero-copy selections over the input
|
|
127
|
+
* batches. DISTINCT requires every output column as both a key and payload, so
|
|
128
|
+
* resolving all batch columns here does not introduce eager payload work.
|
|
129
|
+
*
|
|
130
|
+
* @param {AsyncIterable<AsyncBatch>} batches
|
|
131
|
+
* @param {AbortSignal} [signal]
|
|
132
|
+
* @yields {AsyncBatch}
|
|
133
|
+
*/
|
|
134
|
+
export async function* distinctBatches(batches, signal) {
|
|
135
|
+
const seen = new Set()
|
|
136
|
+
for await (const batch of batches) {
|
|
137
|
+
signal?.throwIfAborted()
|
|
138
|
+
const results = batch.columns.map(function readColumn(_column, columnIndex) {
|
|
139
|
+
return readBatchColumn({ batch, columnIndex, signal })
|
|
140
|
+
})
|
|
141
|
+
const resolved = resolveVectors(results)
|
|
142
|
+
const vectors = resolved instanceof Promise ? await resolved : resolved
|
|
143
|
+
const rowCount = selectedRowCount(batch.selection)
|
|
144
|
+
const indices = new Uint32Array(rowCount)
|
|
145
|
+
/** @type {SqlPrimitive[]} */
|
|
146
|
+
const row = new Array(vectors.length)
|
|
147
|
+
let selectedCount = 0
|
|
148
|
+
|
|
149
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
150
|
+
if (signal && rowIndex > 0 && rowIndex % 4000 === 0) {
|
|
151
|
+
await yieldToEventLoop()
|
|
152
|
+
signal.throwIfAborted()
|
|
153
|
+
}
|
|
154
|
+
for (let columnIndex = 0; columnIndex < vectors.length; columnIndex++) {
|
|
155
|
+
row[columnIndex] = valueAt(vectors[columnIndex], rowIndex)
|
|
156
|
+
}
|
|
157
|
+
const key = keyify(...row)
|
|
158
|
+
if (seen.has(key)) continue
|
|
159
|
+
seen.add(key)
|
|
160
|
+
indices[selectedCount++] = rowIndex
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (selectedCount === 0) continue
|
|
164
|
+
if (selectedCount === rowCount) {
|
|
165
|
+
yield batch
|
|
166
|
+
} else {
|
|
167
|
+
yield selectBatch(batch, {
|
|
168
|
+
type: 'indices',
|
|
169
|
+
indices: indices.subarray(0, selectedCount),
|
|
170
|
+
length: rowCount,
|
|
171
|
+
})
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
signal?.throwIfAborted()
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Projects batches into direct, constant, or lazily computed columns. A
|
|
179
|
+
* computed column retains the input batch that owns its dependency indices,
|
|
180
|
+
* so output columns remain aligned without hidden dependency columns.
|
|
181
|
+
*
|
|
182
|
+
* @param {AsyncIterable<AsyncBatch>} batches
|
|
183
|
+
* @param {readonly BatchProjection[]} projections
|
|
184
|
+
* @yields {AsyncBatch}
|
|
185
|
+
*/
|
|
186
|
+
export async function* projectExpressionBatches(batches, projections) {
|
|
187
|
+
let rowOffset = 0
|
|
188
|
+
for await (const batch of batches) {
|
|
189
|
+
const currentRowOffset = rowOffset
|
|
190
|
+
rowOffset += selectedRowCount(batch.selection)
|
|
191
|
+
/** @type {ColumnVector | undefined} */
|
|
192
|
+
let rowOrdinals
|
|
193
|
+
yield {
|
|
194
|
+
selection: batch.selection,
|
|
195
|
+
columns: projections.map(function projectColumn(projection) {
|
|
196
|
+
/** @type {BatchColumn} */
|
|
197
|
+
let column
|
|
198
|
+
if (projection.type === 'column') {
|
|
199
|
+
column = projectedBatchColumn(batch, projection.columnIndex)
|
|
200
|
+
} else if (projection.type === 'constant') {
|
|
201
|
+
column = {
|
|
202
|
+
type: 'constant',
|
|
203
|
+
value: projection.value,
|
|
204
|
+
length: batch.selection.length,
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
rowOrdinals ??= selectionOrdinals(batch.selection)
|
|
208
|
+
column = {
|
|
209
|
+
read: projection.expression.evaluate,
|
|
210
|
+
input: batch,
|
|
211
|
+
rowOffset: currentRowOffset,
|
|
212
|
+
rowOrdinals,
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return column
|
|
216
|
+
}),
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Preserves the source batch's deferred-read cache through projection.
|
|
223
|
+
*
|
|
224
|
+
* @param {AsyncBatch} batch
|
|
225
|
+
* @param {number} columnIndex
|
|
226
|
+
* @returns {BatchColumn}
|
|
227
|
+
*/
|
|
228
|
+
function projectedBatchColumn(batch, columnIndex) {
|
|
229
|
+
const column = batch.columns[columnIndex]
|
|
230
|
+
if (!('read' in column)) return column
|
|
231
|
+
/** @type {ReadColumn} */
|
|
232
|
+
function readProjectedColumn({ selection, signal }) {
|
|
233
|
+
return readBatchColumn({ batch, columnIndex, selection, signal })
|
|
234
|
+
}
|
|
235
|
+
return { read: readProjectedColumn }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @param {ColumnVector} predicate
|
|
240
|
+
* @param {number} rowCount
|
|
241
|
+
* @returns {{ selection: Extract<RowSelection, { type: 'indices' }>, selectedCount: number }}
|
|
242
|
+
*/
|
|
243
|
+
function predicateSelection(predicate, rowCount) {
|
|
244
|
+
if (predicate.length !== rowCount) {
|
|
245
|
+
throw new Error(`Predicate returned ${predicate.length} rows, expected ${rowCount}`)
|
|
246
|
+
}
|
|
247
|
+
const indices = new Uint32Array(rowCount)
|
|
248
|
+
let selectedCount = 0
|
|
249
|
+
for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
|
|
250
|
+
if (!valueAt(predicate, rowIndex)) continue
|
|
251
|
+
indices[selectedCount++] = rowIndex
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
selection: { type: 'indices', indices: indices.subarray(0, selectedCount), length: rowCount },
|
|
255
|
+
selectedCount,
|
|
256
|
+
}
|
|
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
|
+
}
|