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
package/src/plan/columns.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { dataSourceColumns } from '../backend/dataSource.js'
|
|
2
2
|
import { derivedAlias } from '../expression/alias.js'
|
|
3
|
+
import { tableFunctionDefaultColumns } from '../parse/parse.js'
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* @import { AsyncDataSource, ExprNode, FromFunction, FromSubquery, FromTable, IdentifierNode, SelectStatement, Statement } from '../types.js'
|
|
6
7
|
*/
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* cteColumns?: Map<string, string[]>,
|
|
12
|
+
* tables?: Record<string, AsyncDataSource>,
|
|
13
|
+
* outerAliases?: Set<string>,
|
|
14
|
+
* }} ColumnCollectionContext
|
|
15
|
+
*/
|
|
16
|
+
|
|
8
17
|
/**
|
|
9
18
|
* @param {FromTable | FromSubquery | FromFunction | undefined} from
|
|
10
19
|
* @returns {string | undefined}
|
|
@@ -60,9 +69,13 @@ export function tableFunctionColumnNames(from) {
|
|
|
60
69
|
* @param {object} options
|
|
61
70
|
* @param {SelectStatement} options.select
|
|
62
71
|
* @param {IdentifierNode[]} [options.parentColumns] - columns needed by the parent query
|
|
72
|
+
* @param {Set<string>} [options.scopeColumns] - bare column names available in the current scope
|
|
73
|
+
* @param {Map<string, string[]>} [options.cteColumns]
|
|
74
|
+
* @param {Record<string, AsyncDataSource>} [options.tables]
|
|
75
|
+
* @param {string[]} [options.outerScope]
|
|
63
76
|
* @returns {Map<string, string[] | undefined>}
|
|
64
77
|
*/
|
|
65
|
-
export function extractColumns({ select, parentColumns }) {
|
|
78
|
+
export function extractColumns({ select, parentColumns, scopeColumns, cteColumns, tables, outerScope }) {
|
|
66
79
|
/** @type {Map<string, string[] | undefined>} */
|
|
67
80
|
const result = new Map()
|
|
68
81
|
|
|
@@ -74,6 +87,11 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
74
87
|
for (const join of select.joins) {
|
|
75
88
|
aliases.push(join.alias ?? join.table)
|
|
76
89
|
}
|
|
90
|
+
const collectionContext = {
|
|
91
|
+
cteColumns,
|
|
92
|
+
tables,
|
|
93
|
+
outerAliases: new Set([...aliases, ...outerScope ?? []]),
|
|
94
|
+
}
|
|
77
95
|
|
|
78
96
|
// If any unqualified SELECT * exists, all tables need all columns
|
|
79
97
|
if (select.columns.some(col => col.type === 'star' && !col.table)) {
|
|
@@ -133,26 +151,26 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
133
151
|
if (!parentColumns.some(id => id.name === outputName)) continue
|
|
134
152
|
}
|
|
135
153
|
// Exclude earlier SELECT aliases so they aren't treated as source columns
|
|
136
|
-
collectColumnsFromExpr(col.expr, identifiers, selectAliases)
|
|
154
|
+
collectColumnsFromExpr(col.expr, identifiers, selectAliases, collectionContext)
|
|
137
155
|
if (col.alias) {
|
|
138
156
|
selectAliases.add(col.alias)
|
|
139
157
|
}
|
|
140
158
|
}
|
|
141
159
|
}
|
|
142
|
-
collectColumnsFromExpr(select.where, identifiers)
|
|
160
|
+
collectColumnsFromExpr(select.where, identifiers, undefined, collectionContext)
|
|
143
161
|
|
|
144
162
|
for (const item of select.orderBy) {
|
|
145
|
-
collectColumnsFromExpr(item.expr, identifiers, selectAliases)
|
|
163
|
+
collectColumnsFromExpr(item.expr, identifiers, selectAliases, collectionContext)
|
|
146
164
|
}
|
|
147
165
|
for (const expr of select.groupBy) {
|
|
148
|
-
collectColumnsFromExpr(expr, identifiers, selectAliases)
|
|
166
|
+
collectColumnsFromExpr(expr, identifiers, selectAliases, collectionContext)
|
|
149
167
|
}
|
|
150
|
-
collectColumnsFromExpr(select.having, identifiers, selectAliases)
|
|
168
|
+
collectColumnsFromExpr(select.having, identifiers, selectAliases, collectionContext)
|
|
151
169
|
/** @type {string[]} */
|
|
152
170
|
const visibleLateralAliases = []
|
|
153
171
|
if (sourceAlias !== undefined) visibleLateralAliases.push(sourceAlias)
|
|
154
172
|
for (const join of select.joins) {
|
|
155
|
-
collectColumnsFromExpr(join.on, identifiers)
|
|
173
|
+
collectColumnsFromExpr(join.on, identifiers, undefined, collectionContext)
|
|
156
174
|
// USING columns are equi-join keys on both sides; keep them in every
|
|
157
175
|
// table's needed set so projection pushdown can't prune the join key.
|
|
158
176
|
if (join.using) {
|
|
@@ -165,7 +183,7 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
165
183
|
/** @type {IdentifierNode[]} */
|
|
166
184
|
const lateralArgIdentifiers = []
|
|
167
185
|
for (const arg of join.fromFunction.args) {
|
|
168
|
-
collectColumnsFromExpr(arg, lateralArgIdentifiers)
|
|
186
|
+
collectColumnsFromExpr(arg, lateralArgIdentifiers, undefined, collectionContext)
|
|
169
187
|
}
|
|
170
188
|
lateralArgGroups.push({ identifiers: lateralArgIdentifiers, visibleAliases: [...visibleLateralAliases] })
|
|
171
189
|
}
|
|
@@ -175,9 +193,18 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
175
193
|
// Partition identifiers by table prefix
|
|
176
194
|
for (const { prefix, name } of identifiers) {
|
|
177
195
|
if (prefix) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
196
|
+
if (perTable.has(prefix)) {
|
|
197
|
+
// Table-qualified: add to the matching table only
|
|
198
|
+
perTable.get(prefix)?.add(name)
|
|
199
|
+
} else if (scopeColumns?.has(prefix)) {
|
|
200
|
+
// Struct-field access: the prefix is the source column being read.
|
|
201
|
+
if (aliases.length === 1) {
|
|
202
|
+
perTable.get(aliases[0])?.add(prefix)
|
|
203
|
+
} else {
|
|
204
|
+
// As with an unqualified column in a join, its owner is ambiguous.
|
|
205
|
+
for (const alias of aliases) perTable.set(alias, undefined)
|
|
206
|
+
}
|
|
207
|
+
}
|
|
181
208
|
} else if (aliases.length > 1) {
|
|
182
209
|
// Unqualified in a JOIN: can't disambiguate, request all columns from all tables
|
|
183
210
|
for (const alias of aliases) {
|
|
@@ -225,49 +252,50 @@ export function extractColumns({ select, parentColumns }) {
|
|
|
225
252
|
* @param {ExprNode} expr
|
|
226
253
|
* @param {IdentifierNode[]} columns
|
|
227
254
|
* @param {Set<string>} [aliases] - aliases to exclude from columns
|
|
255
|
+
* @param {ColumnCollectionContext} [context]
|
|
228
256
|
*/
|
|
229
|
-
export function collectColumnsFromExpr(expr, columns, aliases) {
|
|
257
|
+
export function collectColumnsFromExpr(expr, columns, aliases, context) {
|
|
230
258
|
if (!expr) return
|
|
231
259
|
if (expr.type === 'identifier') {
|
|
232
260
|
if (expr.prefix || !aliases?.has(expr.name)) {
|
|
233
261
|
columns.push(expr)
|
|
234
262
|
}
|
|
235
263
|
} else if (expr.type === 'binary') {
|
|
236
|
-
collectColumnsFromExpr(expr.left, columns, aliases)
|
|
237
|
-
collectColumnsFromExpr(expr.right, columns, aliases)
|
|
264
|
+
collectColumnsFromExpr(expr.left, columns, aliases, context)
|
|
265
|
+
collectColumnsFromExpr(expr.right, columns, aliases, context)
|
|
238
266
|
} else if (expr.type === 'unary') {
|
|
239
|
-
collectColumnsFromExpr(expr.argument, columns, aliases)
|
|
267
|
+
collectColumnsFromExpr(expr.argument, columns, aliases, context)
|
|
240
268
|
} else if (expr.type === 'function') {
|
|
241
269
|
for (const arg of expr.args) {
|
|
242
|
-
collectColumnsFromExpr(arg, columns, aliases)
|
|
270
|
+
collectColumnsFromExpr(arg, columns, aliases, context)
|
|
243
271
|
}
|
|
244
|
-
collectColumnsFromExpr(expr.filter, columns, aliases)
|
|
272
|
+
collectColumnsFromExpr(expr.filter, columns, aliases, context)
|
|
245
273
|
} else if (expr.type === 'window') {
|
|
246
|
-
for (const arg of expr.args) collectColumnsFromExpr(arg, columns, aliases)
|
|
247
|
-
for (const p of expr.partitionBy) collectColumnsFromExpr(p, columns, aliases)
|
|
248
|
-
for (const o of expr.orderBy) collectColumnsFromExpr(o.expr, columns, aliases)
|
|
274
|
+
for (const arg of expr.args) collectColumnsFromExpr(arg, columns, aliases, context)
|
|
275
|
+
for (const p of expr.partitionBy) collectColumnsFromExpr(p, columns, aliases, context)
|
|
276
|
+
for (const o of expr.orderBy) collectColumnsFromExpr(o.expr, columns, aliases, context)
|
|
249
277
|
} else if (expr.type === 'cast') {
|
|
250
|
-
collectColumnsFromExpr(expr.expr, columns, aliases)
|
|
278
|
+
collectColumnsFromExpr(expr.expr, columns, aliases, context)
|
|
251
279
|
} else if (expr.type === 'in valuelist') {
|
|
252
|
-
collectColumnsFromExpr(expr.expr, columns, aliases)
|
|
280
|
+
collectColumnsFromExpr(expr.expr, columns, aliases, context)
|
|
253
281
|
for (const val of expr.values) {
|
|
254
|
-
collectColumnsFromExpr(val, columns, aliases)
|
|
282
|
+
collectColumnsFromExpr(val, columns, aliases, context)
|
|
255
283
|
}
|
|
256
284
|
} else if (expr.type === 'in') {
|
|
257
|
-
collectColumnsFromExpr(expr.expr, columns, aliases)
|
|
285
|
+
collectColumnsFromExpr(expr.expr, columns, aliases, context)
|
|
258
286
|
} else if (expr.type === 'subscript') {
|
|
259
|
-
collectColumnsFromExpr(expr.expr, columns, aliases)
|
|
260
|
-
collectColumnsFromExpr(expr.index, columns, aliases)
|
|
287
|
+
collectColumnsFromExpr(expr.expr, columns, aliases, context)
|
|
288
|
+
collectColumnsFromExpr(expr.index, columns, aliases, context)
|
|
261
289
|
} else if (expr.type === 'case') {
|
|
262
290
|
if (expr.caseExpr) {
|
|
263
|
-
collectColumnsFromExpr(expr.caseExpr, columns, aliases)
|
|
291
|
+
collectColumnsFromExpr(expr.caseExpr, columns, aliases, context)
|
|
264
292
|
}
|
|
265
293
|
for (const when of expr.whenClauses) {
|
|
266
|
-
collectColumnsFromExpr(when.condition, columns, aliases)
|
|
267
|
-
collectColumnsFromExpr(when.result, columns, aliases)
|
|
294
|
+
collectColumnsFromExpr(when.condition, columns, aliases, context)
|
|
295
|
+
collectColumnsFromExpr(when.result, columns, aliases, context)
|
|
268
296
|
}
|
|
269
297
|
if (expr.elseResult) {
|
|
270
|
-
collectColumnsFromExpr(expr.elseResult, columns, aliases)
|
|
298
|
+
collectColumnsFromExpr(expr.elseResult, columns, aliases, context)
|
|
271
299
|
}
|
|
272
300
|
}
|
|
273
301
|
// Subqueries: collect prefixed identifiers for correlated column detection.
|
|
@@ -276,60 +304,76 @@ export function collectColumnsFromExpr(expr, columns, aliases) {
|
|
|
276
304
|
// from the inner query would incorrectly be attributed to the outer table.
|
|
277
305
|
if (expr.type === 'subquery' || expr.type === 'in' || expr.type === 'exists' || expr.type === 'not exists') {
|
|
278
306
|
if (expr.type === 'in') {
|
|
279
|
-
collectColumnsFromExpr(expr.expr, columns, aliases)
|
|
307
|
+
collectColumnsFromExpr(expr.expr, columns, aliases, context)
|
|
280
308
|
}
|
|
281
309
|
const sub = expr.subquery
|
|
282
310
|
if (sub) {
|
|
283
|
-
|
|
284
|
-
const inner = []
|
|
285
|
-
collectColumnsFromStatement(sub, inner)
|
|
286
|
-
for (const id of inner) {
|
|
287
|
-
if (id.prefix) columns.push(id)
|
|
288
|
-
}
|
|
311
|
+
collectCorrelatedColumnsFromStatement(sub, columns, context)
|
|
289
312
|
// FROM-function args (e.g. UNNEST in the subquery's FROM) are evaluated
|
|
290
313
|
// against the outer scope — the table function is itself the FROM, so
|
|
291
314
|
// any identifier inside its args must be correlated. Push them even if
|
|
292
315
|
// unprefixed so the outer scan reads the columns they reference.
|
|
293
|
-
collectFromFunctionArgs(sub, columns)
|
|
316
|
+
collectFromFunctionArgs(sub, columns, context)
|
|
294
317
|
}
|
|
295
318
|
}
|
|
296
319
|
// No columns: count(*), literal, interval
|
|
297
320
|
}
|
|
298
321
|
|
|
299
322
|
/**
|
|
300
|
-
* Collects identifiers
|
|
323
|
+
* Collects qualified identifiers that escape a subquery statement's scope.
|
|
324
|
+
* Each SELECT filters its own aliases before identifiers propagate outward,
|
|
325
|
+
* preserving ownership through nested and compound subqueries.
|
|
301
326
|
*
|
|
302
327
|
* @param {Statement} stmt
|
|
303
328
|
* @param {IdentifierNode[]} columns
|
|
329
|
+
* @param {ColumnCollectionContext} [context]
|
|
304
330
|
*/
|
|
305
|
-
function
|
|
331
|
+
function collectCorrelatedColumnsFromStatement(stmt, columns, context) {
|
|
306
332
|
if (stmt.type === 'compound') {
|
|
307
|
-
|
|
308
|
-
|
|
333
|
+
collectCorrelatedColumnsFromStatement(stmt.left, columns, context)
|
|
334
|
+
collectCorrelatedColumnsFromStatement(stmt.right, columns, context)
|
|
309
335
|
return
|
|
310
336
|
}
|
|
311
337
|
if (stmt.type === 'with') {
|
|
312
|
-
|
|
338
|
+
collectCorrelatedColumnsFromStatement(stmt.query, columns, context)
|
|
313
339
|
return
|
|
314
340
|
}
|
|
341
|
+
const scope = statementScope(stmt) ?? []
|
|
342
|
+
const nestedContext = context && {
|
|
343
|
+
...context,
|
|
344
|
+
outerAliases: new Set([...context.outerAliases ?? [], ...scope]),
|
|
345
|
+
}
|
|
346
|
+
/** @type {IdentifierNode[]} */
|
|
347
|
+
const identifiers = []
|
|
315
348
|
for (const col of stmt.columns) {
|
|
316
|
-
if (col.type === 'derived') collectColumnsFromExpr(col.expr,
|
|
349
|
+
if (col.type === 'derived') collectColumnsFromExpr(col.expr, identifiers, undefined, nestedContext)
|
|
317
350
|
}
|
|
318
|
-
collectColumnsFromExpr(stmt.where,
|
|
351
|
+
collectColumnsFromExpr(stmt.where, identifiers, undefined, nestedContext)
|
|
319
352
|
if (stmt.from && stmt.from.type === 'subquery') {
|
|
320
|
-
|
|
353
|
+
collectCorrelatedColumnsFromStatement(stmt.from.query, identifiers, nestedContext)
|
|
321
354
|
}
|
|
322
355
|
for (const join of stmt.joins) {
|
|
323
|
-
collectColumnsFromExpr(join.on,
|
|
356
|
+
collectColumnsFromExpr(join.on, identifiers, undefined, nestedContext)
|
|
324
357
|
if (join.fromFunction) {
|
|
325
358
|
for (const arg of join.fromFunction.args) {
|
|
326
|
-
collectColumnsFromExpr(arg,
|
|
359
|
+
collectColumnsFromExpr(arg, identifiers, undefined, nestedContext)
|
|
327
360
|
}
|
|
328
361
|
}
|
|
329
362
|
}
|
|
330
|
-
for (const expr of stmt.groupBy) collectColumnsFromExpr(expr,
|
|
331
|
-
collectColumnsFromExpr(stmt.having,
|
|
332
|
-
for (const item of stmt.orderBy) collectColumnsFromExpr(item.expr,
|
|
363
|
+
for (const expr of stmt.groupBy) collectColumnsFromExpr(expr, identifiers, undefined, nestedContext)
|
|
364
|
+
collectColumnsFromExpr(stmt.having, identifiers, undefined, nestedContext)
|
|
365
|
+
for (const item of stmt.orderBy) collectColumnsFromExpr(item.expr, identifiers, undefined, nestedContext)
|
|
366
|
+
|
|
367
|
+
const localColumns = context
|
|
368
|
+
? collectScopeColumns({ select: stmt, cteColumns: context.cteColumns, tables: context.tables })
|
|
369
|
+
: new Set()
|
|
370
|
+
for (const identifier of identifiers) {
|
|
371
|
+
if (identifier.prefix &&
|
|
372
|
+
!scope.includes(identifier.prefix) &&
|
|
373
|
+
(context?.outerAliases?.has(identifier.prefix) || !localColumns.has(identifier.prefix))) {
|
|
374
|
+
columns.push(identifier)
|
|
375
|
+
}
|
|
376
|
+
}
|
|
333
377
|
}
|
|
334
378
|
|
|
335
379
|
/**
|
|
@@ -339,23 +383,24 @@ function collectColumnsFromStatement(stmt, columns) {
|
|
|
339
383
|
*
|
|
340
384
|
* @param {Statement} stmt
|
|
341
385
|
* @param {IdentifierNode[]} columns
|
|
386
|
+
* @param {ColumnCollectionContext} [context]
|
|
342
387
|
*/
|
|
343
|
-
function collectFromFunctionArgs(stmt, columns) {
|
|
388
|
+
function collectFromFunctionArgs(stmt, columns, context) {
|
|
344
389
|
if (stmt.type === 'compound') {
|
|
345
|
-
collectFromFunctionArgs(stmt.left, columns)
|
|
346
|
-
collectFromFunctionArgs(stmt.right, columns)
|
|
390
|
+
collectFromFunctionArgs(stmt.left, columns, context)
|
|
391
|
+
collectFromFunctionArgs(stmt.right, columns, context)
|
|
347
392
|
return
|
|
348
393
|
}
|
|
349
394
|
if (stmt.type === 'with') {
|
|
350
|
-
collectFromFunctionArgs(stmt.query, columns)
|
|
395
|
+
collectFromFunctionArgs(stmt.query, columns, context)
|
|
351
396
|
return
|
|
352
397
|
}
|
|
353
398
|
if (stmt.from?.type === 'function') {
|
|
354
399
|
for (const arg of stmt.from.args) {
|
|
355
|
-
collectColumnsFromExpr(arg, columns)
|
|
400
|
+
collectColumnsFromExpr(arg, columns, undefined, context)
|
|
356
401
|
}
|
|
357
402
|
} else if (stmt.from?.type === 'subquery') {
|
|
358
|
-
collectFromFunctionArgs(stmt.from.query, columns)
|
|
403
|
+
collectFromFunctionArgs(stmt.from.query, columns, context)
|
|
359
404
|
}
|
|
360
405
|
}
|
|
361
406
|
|
|
@@ -473,7 +518,8 @@ export function inferSelectSourceColumns({ select, cteColumns, tables }) {
|
|
|
473
518
|
* @returns {string[]}
|
|
474
519
|
*/
|
|
475
520
|
function lookupTableColumns(table, cteColumns, tables) {
|
|
476
|
-
|
|
521
|
+
const source = tables?.[table]
|
|
522
|
+
return cteColumns?.get(table.toLowerCase()) ?? (source ? dataSourceColumns(source) : [])
|
|
477
523
|
}
|
|
478
524
|
|
|
479
525
|
/**
|
package/src/plan/plan.js
CHANGED
|
@@ -82,6 +82,8 @@ function planSetOperation({ compound, ctePlans, cteColumns, tables, parentColumn
|
|
|
82
82
|
all: compound.all,
|
|
83
83
|
left,
|
|
84
84
|
right,
|
|
85
|
+
leftScope: statementScope(compound.left),
|
|
86
|
+
rightScope: statementScope(compound.right),
|
|
85
87
|
}
|
|
86
88
|
|
|
87
89
|
if (compound.orderBy.length) {
|
|
@@ -206,7 +208,14 @@ function planSelect({ select, ctePlans, cteColumns, tables, parentColumns, outer
|
|
|
206
208
|
// included so they are only applied to fresh scans, not CTE/subquery plans)
|
|
207
209
|
/** @type {ScanOptions} */
|
|
208
210
|
const hints = {}
|
|
209
|
-
const perTableColumns = extractColumns({
|
|
211
|
+
const perTableColumns = extractColumns({
|
|
212
|
+
select: originalSelect,
|
|
213
|
+
parentColumns,
|
|
214
|
+
scopeColumns,
|
|
215
|
+
cteColumns,
|
|
216
|
+
tables,
|
|
217
|
+
outerScope,
|
|
218
|
+
})
|
|
210
219
|
if (sourceAlias !== undefined) hints.columns = perTableColumns.get(sourceAlias)
|
|
211
220
|
// Capture what the parent reads from a FROM subquery before the reset
|
|
212
221
|
// below, so aggregate outputs it never reads can still be pruned when the
|
|
@@ -406,7 +415,7 @@ function planFrom({ select, ctePlans, cteColumns, hints, tables, outerScope }) {
|
|
|
406
415
|
return ctePlan
|
|
407
416
|
}
|
|
408
417
|
validateScan({ ...select.from, hints, tables })
|
|
409
|
-
return { type: 'Scan', table: select.from.table, hints }
|
|
418
|
+
return { type: 'Scan', table: select.from.table, alias: select.from.alias, hints }
|
|
410
419
|
} else if (select.from.type === 'function') {
|
|
411
420
|
for (const arg of select.from.args) {
|
|
412
421
|
validateNoIdentifiers(arg, select.from.funcName, outerScope)
|
|
@@ -545,7 +554,7 @@ function planJoin({ left, joins, leftTable, ctePlans, cteColumns, perTableColumn
|
|
|
545
554
|
// For CTE joins, use CTE column metadata for hints
|
|
546
555
|
rightHints.columns = perTableColumns.get(rightTable) ?? cteColumns?.get(join.table.toLowerCase())
|
|
547
556
|
}
|
|
548
|
-
rightScan = ctePlan ?? { type: 'Scan', table: join.table, hints: rightHints }
|
|
557
|
+
rightScan = ctePlan ?? { type: 'Scan', table: join.table, alias: join.alias, hints: rightHints }
|
|
549
558
|
}
|
|
550
559
|
|
|
551
560
|
if (join.joinType === 'POSITIONAL') {
|
package/src/plan/types.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type QueryPlan =
|
|
|
23
23
|
export interface ScanNode {
|
|
24
24
|
type: 'Scan'
|
|
25
25
|
table: string
|
|
26
|
+
alias?: string
|
|
26
27
|
hints: ScanOptions
|
|
27
28
|
}
|
|
28
29
|
|
|
@@ -130,6 +131,8 @@ export interface SetOperationNode {
|
|
|
130
131
|
all: boolean
|
|
131
132
|
left: QueryPlan
|
|
132
133
|
right: QueryPlan
|
|
134
|
+
leftScope?: string[]
|
|
135
|
+
rightScope?: string[]
|
|
133
136
|
}
|
|
134
137
|
|
|
135
138
|
// Wraps a derived-table or CTE subplan with the lexical alias scope of its
|
package/src/types.d.ts
CHANGED
|
@@ -11,10 +11,148 @@ export { QueryPlan } from './plan/types.js'
|
|
|
11
11
|
export interface QueryResults {
|
|
12
12
|
columns: string[]
|
|
13
13
|
rows(): AsyncGenerator<AsyncRow>
|
|
14
|
+
batches?(): AsyncIterable<AsyncBatch>
|
|
14
15
|
numRows?: number
|
|
15
16
|
maxRows?: number
|
|
16
17
|
}
|
|
17
18
|
|
|
19
|
+
export type SqlType =
|
|
20
|
+
| { type: 'unknown' }
|
|
21
|
+
| { type: 'string' }
|
|
22
|
+
| { type: 'number' }
|
|
23
|
+
| { type: 'bigint' }
|
|
24
|
+
| { type: 'boolean' }
|
|
25
|
+
| { type: 'date' }
|
|
26
|
+
| { type: 'array', items: SqlType }
|
|
27
|
+
| { type: 'struct', fields: readonly Field[] }
|
|
28
|
+
|
|
29
|
+
export interface Field {
|
|
30
|
+
id: number
|
|
31
|
+
name: string
|
|
32
|
+
dataType: SqlType
|
|
33
|
+
nullable: boolean
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface RelationSchema {
|
|
37
|
+
fields: readonly Field[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* A selection over a base domain of `length` rows.
|
|
42
|
+
*/
|
|
43
|
+
export type RowSelection =
|
|
44
|
+
| { type: 'all', length: number }
|
|
45
|
+
| { type: 'range', start: number, end: number, length: number }
|
|
46
|
+
| { type: 'indices', indices: Uint32Array, length: number }
|
|
47
|
+
|
|
48
|
+
export type NumericArray =
|
|
49
|
+
| Int8Array
|
|
50
|
+
| Uint8Array
|
|
51
|
+
| Uint8ClampedArray
|
|
52
|
+
| Int16Array
|
|
53
|
+
| Uint16Array
|
|
54
|
+
| Int32Array
|
|
55
|
+
| Uint32Array
|
|
56
|
+
| Float32Array
|
|
57
|
+
| Float64Array
|
|
58
|
+
| BigInt64Array
|
|
59
|
+
| BigUint64Array
|
|
60
|
+
|
|
61
|
+
export type ColumnVector =
|
|
62
|
+
| {
|
|
63
|
+
type: 'values'
|
|
64
|
+
values: readonly SqlPrimitive[]
|
|
65
|
+
length: number
|
|
66
|
+
}
|
|
67
|
+
| {
|
|
68
|
+
type: 'typed'
|
|
69
|
+
values: NumericArray
|
|
70
|
+
validity?: Uint8Array
|
|
71
|
+
length: number
|
|
72
|
+
}
|
|
73
|
+
| {
|
|
74
|
+
type: 'constant'
|
|
75
|
+
value: SqlPrimitive
|
|
76
|
+
length: number
|
|
77
|
+
}
|
|
78
|
+
| {
|
|
79
|
+
type: 'selected'
|
|
80
|
+
source: ColumnVector
|
|
81
|
+
selection: RowSelection
|
|
82
|
+
length: number
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ColumnReadRequest {
|
|
86
|
+
batch: AsyncBatch
|
|
87
|
+
selection: RowSelection
|
|
88
|
+
signal?: AbortSignal
|
|
89
|
+
rowOffset?: number
|
|
90
|
+
rowOrdinals?: ColumnVector
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export type ColumnResult = ColumnVector | Promise<ColumnVector>
|
|
94
|
+
export type ReadColumn = (request: ColumnReadRequest) => ColumnResult
|
|
95
|
+
|
|
96
|
+
export type BatchColumn =
|
|
97
|
+
| ColumnVector
|
|
98
|
+
| {
|
|
99
|
+
read: ReadColumn
|
|
100
|
+
input?: AsyncBatch
|
|
101
|
+
rowOffset?: number
|
|
102
|
+
rowOrdinals?: ColumnVector
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface AsyncBatch {
|
|
106
|
+
selection: RowSelection
|
|
107
|
+
columns: readonly BatchColumn[]
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export interface ReadBatchColumnOptions {
|
|
111
|
+
batch: AsyncBatch
|
|
112
|
+
columnIndex: number
|
|
113
|
+
selection?: RowSelection
|
|
114
|
+
signal?: AbortSignal
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface RowsToBatchesOptions {
|
|
118
|
+
batchRows?: number
|
|
119
|
+
signal?: AbortSignal
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export interface ColumnDemand {
|
|
123
|
+
field: number
|
|
124
|
+
phase: number
|
|
125
|
+
purpose: 'filter' | 'output'
|
|
126
|
+
mode: 'required' | 'deferred'
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
export interface ScanRequest {
|
|
130
|
+
columns: readonly ColumnDemand[]
|
|
131
|
+
filter?: ExprNode
|
|
132
|
+
limit?: number
|
|
133
|
+
offset?: number
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export interface ScanProperties {
|
|
137
|
+
exactRows?: number
|
|
138
|
+
maxRows?: number
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export interface ScanResidual {
|
|
142
|
+
filter?: ExprNode
|
|
143
|
+
limit?: number
|
|
144
|
+
offset?: number
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
export interface PreparedScan {
|
|
148
|
+
schema: RelationSchema
|
|
149
|
+
residual: ScanResidual
|
|
150
|
+
properties: ScanProperties
|
|
151
|
+
batches(options?: { signal?: AbortSignal }): AsyncIterable<AsyncBatch>
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export type PrepareScan = (request: ScanRequest) => PreparedScan
|
|
155
|
+
|
|
18
156
|
// parseSql(options)
|
|
19
157
|
export interface ParseSqlOptions {
|
|
20
158
|
query: string
|
|
@@ -72,16 +210,30 @@ export type AsyncCell = () => Promise<SqlPrimitive>
|
|
|
72
210
|
export type Row = Record<string, SqlPrimitive>[]
|
|
73
211
|
|
|
74
212
|
/**
|
|
75
|
-
* Async data source for streaming SQL execution.
|
|
213
|
+
* Async data source for streaming SQL execution. A source must implement
|
|
214
|
+
* either scan() or prepareScan().
|
|
76
215
|
*/
|
|
77
|
-
|
|
216
|
+
interface AsyncDataSourceBase {
|
|
78
217
|
numRows?: number
|
|
79
|
-
columns: string[]
|
|
80
|
-
scan(options: ScanOptions): ScanResults
|
|
81
218
|
// Optional method for fast column scans
|
|
82
219
|
scanColumn?(options: ScanColumnOptions): AsyncIterable<ArrayLike<SqlPrimitive>> | ScanColumnResults
|
|
83
220
|
}
|
|
84
221
|
|
|
222
|
+
export type AsyncDataSource = AsyncDataSourceBase & (
|
|
223
|
+
| {
|
|
224
|
+
columns: string[]
|
|
225
|
+
scan(options: ScanOptions): ScanResults
|
|
226
|
+
schema?: RelationSchema
|
|
227
|
+
prepareScan?: PrepareScan
|
|
228
|
+
}
|
|
229
|
+
| {
|
|
230
|
+
columns?: string[]
|
|
231
|
+
scan?(options: ScanOptions): ScanResults
|
|
232
|
+
schema: RelationSchema
|
|
233
|
+
prepareScan: PrepareScan
|
|
234
|
+
}
|
|
235
|
+
)
|
|
236
|
+
|
|
85
237
|
/**
|
|
86
238
|
* Result of a scan: streaming rows and flags indicating which hints were
|
|
87
239
|
* applied by the data source.
|
package/src/validation/tables.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { dataSourceColumns } from '../backend/dataSource.js'
|
|
1
2
|
import { ExecutionError } from './executionErrors.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -34,11 +35,12 @@ export function validateTable({ table, qualified, tables, positionStart, positio
|
|
|
34
35
|
export function validateScan({ table, hints, tables, positionStart, positionEnd }) {
|
|
35
36
|
if (!tables) return
|
|
36
37
|
const resolved = validateTable({ table, tables, positionStart, positionEnd })
|
|
37
|
-
const
|
|
38
|
+
const columns = dataSourceColumns(resolved)
|
|
39
|
+
const missingColumn = hints.columns?.find(col => !columns.includes(col))
|
|
38
40
|
if (missingColumn) {
|
|
39
41
|
throw new ColumnNotFoundError({
|
|
40
42
|
missingColumn,
|
|
41
|
-
availableColumns:
|
|
43
|
+
availableColumns: columns,
|
|
42
44
|
positionStart,
|
|
43
45
|
positionEnd,
|
|
44
46
|
})
|