squirreling 0.12.0 → 0.12.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/README.md +4 -1
- package/package.json +4 -4
- package/src/execute/aggregates.js +5 -1
- package/src/execute/execute.js +39 -2
- package/src/execute/join.js +19 -0
- package/src/execute/sort.js +1 -0
- package/src/types.d.ts +1 -1
package/README.md
CHANGED
|
@@ -35,7 +35,10 @@ const users = [
|
|
|
35
35
|
|
|
36
36
|
// Squirreling return types
|
|
37
37
|
interface QueryResults {
|
|
38
|
-
|
|
38
|
+
columns: string[]
|
|
39
|
+
numRows?: number
|
|
40
|
+
maxRows?: number
|
|
41
|
+
rows(): AsyncGenerator<AsyncRow>
|
|
39
42
|
}
|
|
40
43
|
interface AsyncRow {
|
|
41
44
|
columns: string[]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.12.
|
|
3
|
+
"version": "0.12.1",
|
|
4
4
|
"description": "Squirreling Async SQL Engine",
|
|
5
5
|
"author": "Hyperparam",
|
|
6
6
|
"homepage": "https://hyperparam.app",
|
|
@@ -39,11 +39,11 @@
|
|
|
39
39
|
"test": "vitest run"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@types/node": "25.5.
|
|
43
|
-
"@vitest/coverage-v8": "4.1.
|
|
42
|
+
"@types/node": "25.5.2",
|
|
43
|
+
"@vitest/coverage-v8": "4.1.3",
|
|
44
44
|
"eslint": "9.39.2",
|
|
45
45
|
"eslint-plugin-jsdoc": "62.9.0",
|
|
46
46
|
"typescript": "6.0.2",
|
|
47
|
-
"vitest": "4.1.
|
|
47
|
+
"vitest": "4.1.3"
|
|
48
48
|
}
|
|
49
49
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { derivedAlias } from '../expression/alias.js'
|
|
2
2
|
import { evaluateExpr } from '../expression/evaluate.js'
|
|
3
|
-
import { executePlan } from './execute.js'
|
|
3
|
+
import { executePlan, selectColumnNames } from './execute.js'
|
|
4
4
|
import { keyify } from './utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -60,6 +60,7 @@ function projectAggregateColumns(selectColumns, group, context) {
|
|
|
60
60
|
export function executeHashAggregate(plan, context) {
|
|
61
61
|
const child = executePlan({ plan: plan.child, context })
|
|
62
62
|
return {
|
|
63
|
+
columns: selectColumnNames(plan.columns, child.columns),
|
|
63
64
|
maxRows: child.maxRows,
|
|
64
65
|
async *rows () {
|
|
65
66
|
// Collect all rows
|
|
@@ -119,9 +120,11 @@ export function executeHashAggregate(plan, context) {
|
|
|
119
120
|
*/
|
|
120
121
|
export function executeScalarAggregate(plan, context) {
|
|
121
122
|
// Fast path: use scanColumn when available
|
|
123
|
+
const scalarColumns = selectColumnNames(plan.columns, [])
|
|
122
124
|
const fast = tryColumnScanAggregate(plan, context)
|
|
123
125
|
if (fast) {
|
|
124
126
|
return {
|
|
127
|
+
columns: scalarColumns,
|
|
125
128
|
numRows: 1,
|
|
126
129
|
maxRows: 1,
|
|
127
130
|
rows: fast,
|
|
@@ -130,6 +133,7 @@ export function executeScalarAggregate(plan, context) {
|
|
|
130
133
|
|
|
131
134
|
const child = executePlan({ plan: plan.child, context })
|
|
132
135
|
return {
|
|
136
|
+
columns: selectColumnNames(plan.columns, child.columns),
|
|
133
137
|
numRows: plan.having ? undefined : 1,
|
|
134
138
|
maxRows: 1,
|
|
135
139
|
async *rows () {
|
package/src/execute/execute.js
CHANGED
|
@@ -10,7 +10,7 @@ import { executeSort } from './sort.js'
|
|
|
10
10
|
import { addBounds, minBounds, stableRowKey } from './utils.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @import { AsyncCells, AsyncDataSource, AsyncRow, ExecuteContext, ExecuteSqlOptions, ExprNode, QueryResults, Statement } from '../types.js'
|
|
13
|
+
* @import { AsyncCells, AsyncDataSource, AsyncRow, ExecuteContext, ExecuteSqlOptions, ExprNode, QueryResults, SelectColumn, Statement } from '../types.js'
|
|
14
14
|
* @import { CountNode, DistinctNode, FilterNode, LimitNode, ProjectNode, QueryPlan, ScanNode, SetOperationNode } from '../plan/types.js'
|
|
15
15
|
*/
|
|
16
16
|
|
|
@@ -88,7 +88,33 @@ export function executePlan({ plan, context }) {
|
|
|
88
88
|
} else if (plan.type === 'SetOperation') {
|
|
89
89
|
return executeSetOperation(plan, context)
|
|
90
90
|
}
|
|
91
|
-
return { async *rows () {} }
|
|
91
|
+
return { columns: [], async *rows () {} }
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Derives output column names from SELECT columns and available child columns.
|
|
96
|
+
*
|
|
97
|
+
* @param {SelectColumn[]} selectColumns
|
|
98
|
+
* @param {string[]} childColumns
|
|
99
|
+
* @returns {string[]}
|
|
100
|
+
*/
|
|
101
|
+
export function selectColumnNames(selectColumns, childColumns) {
|
|
102
|
+
/** @type {string[]} */
|
|
103
|
+
const result = []
|
|
104
|
+
for (const col of selectColumns) {
|
|
105
|
+
if (col.type === 'star') {
|
|
106
|
+
const prefix = col.table ? `${col.table}.` : undefined
|
|
107
|
+
for (const key of childColumns) {
|
|
108
|
+
if (prefix && !key.startsWith(prefix)) continue
|
|
109
|
+
const dotIndex = key.indexOf('.')
|
|
110
|
+
const outputKey = prefix ? key.substring(prefix.length) : dotIndex >= 0 ? key.substring(dotIndex + 1) : key
|
|
111
|
+
result.push(outputKey)
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
result.push(col.alias ?? derivedAlias(col.expr))
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
return result
|
|
92
118
|
}
|
|
93
119
|
|
|
94
120
|
/**
|
|
@@ -113,6 +139,7 @@ function executeScan(plan, context) {
|
|
|
113
139
|
})
|
|
114
140
|
const scanRows = computeScanRows(table.numRows, plan.hints.limit, plan.hints.offset)
|
|
115
141
|
return {
|
|
142
|
+
columns: [column],
|
|
116
143
|
numRows: scanRows,
|
|
117
144
|
maxRows: scanRows,
|
|
118
145
|
async *rows () {
|
|
@@ -142,6 +169,7 @@ function executeScan(plan, context) {
|
|
|
142
169
|
|
|
143
170
|
const scanRows = computeScanRows(table.numRows, plan.hints.limit, plan.hints.offset)
|
|
144
171
|
return {
|
|
172
|
+
columns: plan.hints.columns ?? table.columns,
|
|
145
173
|
numRows: !plan.hints.where ? scanRows : undefined,
|
|
146
174
|
maxRows: scanRows,
|
|
147
175
|
async *rows () {
|
|
@@ -174,6 +202,7 @@ function executeCount(plan, context) {
|
|
|
174
202
|
const table = validateTable({ ...plan, tables })
|
|
175
203
|
|
|
176
204
|
return {
|
|
205
|
+
columns: plan.columns.map(col => col.alias ?? derivedAlias(col.expr)),
|
|
177
206
|
numRows: 1,
|
|
178
207
|
maxRows: 1,
|
|
179
208
|
async *rows () {
|
|
@@ -300,6 +329,7 @@ async function* limitRows(rows, limit, offset, signal) {
|
|
|
300
329
|
function executeFilter(plan, context) {
|
|
301
330
|
const child = executePlan({ plan: plan.child, context })
|
|
302
331
|
return {
|
|
332
|
+
columns: child.columns,
|
|
303
333
|
maxRows: child.maxRows,
|
|
304
334
|
rows: () => filterRows(child.rows(), plan.condition, context),
|
|
305
335
|
}
|
|
@@ -315,6 +345,7 @@ function executeFilter(plan, context) {
|
|
|
315
345
|
function executeProject(plan, context) {
|
|
316
346
|
const child = executePlan({ plan: plan.child, context })
|
|
317
347
|
return {
|
|
348
|
+
columns: selectColumnNames(plan.columns, child.columns),
|
|
318
349
|
numRows: child.numRows,
|
|
319
350
|
maxRows: child.maxRows,
|
|
320
351
|
async *rows () {
|
|
@@ -369,6 +400,7 @@ function executeProject(plan, context) {
|
|
|
369
400
|
function executeDistinct(plan, context) {
|
|
370
401
|
const child = executePlan({ plan: plan.child, context })
|
|
371
402
|
return {
|
|
403
|
+
columns: child.columns,
|
|
372
404
|
maxRows: child.maxRows,
|
|
373
405
|
async *rows () {
|
|
374
406
|
const { signal } = context
|
|
@@ -421,6 +453,7 @@ function executeDistinct(plan, context) {
|
|
|
421
453
|
function executeLimit(plan, context) {
|
|
422
454
|
const child = executePlan({ plan: plan.child, context })
|
|
423
455
|
return {
|
|
456
|
+
columns: child.columns,
|
|
424
457
|
numRows: computeScanRows(child.numRows, plan.limit, plan.offset),
|
|
425
458
|
maxRows: computeScanRows(child.maxRows, plan.limit, plan.offset),
|
|
426
459
|
rows: () => limitRows(child.rows(), plan.limit, plan.offset, context.signal),
|
|
@@ -442,6 +475,7 @@ function executeSetOperation(plan, context) {
|
|
|
442
475
|
const left = executePlan({ plan: plan.left, context })
|
|
443
476
|
const right = executePlan({ plan: plan.right, context })
|
|
444
477
|
return {
|
|
478
|
+
columns: left.columns,
|
|
445
479
|
numRows: addBounds(left.numRows, right.numRows),
|
|
446
480
|
maxRows: addBounds(left.maxRows, right.maxRows),
|
|
447
481
|
async *rows () {
|
|
@@ -454,6 +488,7 @@ function executeSetOperation(plan, context) {
|
|
|
454
488
|
const left = executePlan({ plan: plan.left, context })
|
|
455
489
|
const right = executePlan({ plan: plan.right, context })
|
|
456
490
|
return {
|
|
491
|
+
columns: left.columns,
|
|
457
492
|
maxRows: addBounds(left.maxRows, right.maxRows),
|
|
458
493
|
async *rows () {
|
|
459
494
|
// UNION: yield deduplicated rows from both sides
|
|
@@ -481,6 +516,7 @@ function executeSetOperation(plan, context) {
|
|
|
481
516
|
const left = executePlan({ plan: plan.left, context })
|
|
482
517
|
const right = executePlan({ plan: plan.right, context })
|
|
483
518
|
return {
|
|
519
|
+
columns: left.columns,
|
|
484
520
|
maxRows: minBounds(left.maxRows, right.maxRows),
|
|
485
521
|
async *rows () {
|
|
486
522
|
// Materialize right side keys
|
|
@@ -522,6 +558,7 @@ function executeSetOperation(plan, context) {
|
|
|
522
558
|
const left = executePlan({ plan: plan.left, context })
|
|
523
559
|
const right = executePlan({ plan: plan.right, context })
|
|
524
560
|
return {
|
|
561
|
+
columns: left.columns,
|
|
525
562
|
maxRows: left.maxRows,
|
|
526
563
|
async *rows () {
|
|
527
564
|
// Materialize right side keys
|
package/src/execute/join.js
CHANGED
|
@@ -18,6 +18,7 @@ export function executeNestedLoopJoin(plan, context) {
|
|
|
18
18
|
const left = executePlan({ plan: plan.left, context })
|
|
19
19
|
const right = executePlan({ plan: plan.right, context })
|
|
20
20
|
return {
|
|
21
|
+
columns: mergeColumnNames(left.columns, right.columns, plan.leftAlias, plan.rightAlias),
|
|
21
22
|
async *rows () {
|
|
22
23
|
const leftTable = plan.leftAlias
|
|
23
24
|
const rightTable = plan.rightAlias
|
|
@@ -93,6 +94,7 @@ export function executePositionalJoin(plan, context) {
|
|
|
93
94
|
const numRows = left.numRows !== undefined && right.numRows !== undefined
|
|
94
95
|
? Math.max(left.numRows, right.numRows) : undefined
|
|
95
96
|
return {
|
|
97
|
+
columns: mergeColumnNames(left.columns, right.columns, plan.leftAlias, plan.rightAlias),
|
|
96
98
|
numRows,
|
|
97
99
|
maxRows: maxBounds(left.maxRows, right.maxRows),
|
|
98
100
|
async *rows () {
|
|
@@ -140,6 +142,7 @@ export function executeHashJoin(plan, context) {
|
|
|
140
142
|
const left = executePlan({ plan: plan.left, context })
|
|
141
143
|
const right = executePlan({ plan: plan.right, context })
|
|
142
144
|
return {
|
|
145
|
+
columns: mergeColumnNames(left.columns, right.columns, plan.leftAlias, plan.rightAlias),
|
|
143
146
|
async *rows () {
|
|
144
147
|
const leftTable = plan.leftAlias
|
|
145
148
|
const rightTable = plan.rightAlias
|
|
@@ -233,6 +236,22 @@ function createNullRow(columns) {
|
|
|
233
236
|
return { columns, cells }
|
|
234
237
|
}
|
|
235
238
|
|
|
239
|
+
/**
|
|
240
|
+
* Merges column name arrays with table prefixes, matching mergeRows logic.
|
|
241
|
+
*
|
|
242
|
+
* @param {string[]} leftColumns
|
|
243
|
+
* @param {string[]} rightColumns
|
|
244
|
+
* @param {string} leftTable
|
|
245
|
+
* @param {string} rightTable
|
|
246
|
+
* @returns {string[]}
|
|
247
|
+
*/
|
|
248
|
+
function mergeColumnNames(leftColumns, rightColumns, leftTable, rightTable) {
|
|
249
|
+
return [
|
|
250
|
+
...leftColumns.map(c => c.includes('.') ? c : `${leftTable}.${c}`),
|
|
251
|
+
...rightColumns.map(c => c.includes('.') ? c : `${rightTable}.${c}`),
|
|
252
|
+
]
|
|
253
|
+
}
|
|
254
|
+
|
|
236
255
|
/**
|
|
237
256
|
* Merges two rows into one, prefixing columns with table names
|
|
238
257
|
*
|
package/src/execute/sort.js
CHANGED
|
@@ -17,6 +17,7 @@ import { compareForTerm } from './utils.js'
|
|
|
17
17
|
export function executeSort(plan, context) {
|
|
18
18
|
const child = executePlan({ plan: plan.child, context })
|
|
19
19
|
return {
|
|
20
|
+
columns: child.columns,
|
|
20
21
|
numRows: child.numRows,
|
|
21
22
|
maxRows: child.maxRows,
|
|
22
23
|
async *rows () {
|
package/src/types.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export { QueryPlan } from './plan/types.js'
|
|
|
8
8
|
* Result of executing a SQL query.
|
|
9
9
|
*/
|
|
10
10
|
export interface QueryResults {
|
|
11
|
+
columns: string[]
|
|
11
12
|
rows(): AsyncGenerator<AsyncRow>
|
|
12
13
|
numRows?: number
|
|
13
14
|
maxRows?: number
|
|
@@ -68,7 +69,6 @@ export interface AsyncDataSource {
|
|
|
68
69
|
*/
|
|
69
70
|
export interface ScanResults {
|
|
70
71
|
rows(): AsyncIterable<AsyncRow>
|
|
71
|
-
numRows?: number // exact row count if known
|
|
72
72
|
appliedWhere: boolean // WHERE filter applied at scan time?
|
|
73
73
|
appliedLimitOffset: boolean // LIMIT and OFFSET applied at scan time?
|
|
74
74
|
}
|