squirreling 0.7.5 → 0.7.6
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/execute/execute.js +14 -3
- package/src/execute/expression.js +16 -10
- package/src/execute/having.js +1 -1
- package/src/execute/utils.js +1 -1
- package/src/validation.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "Squirreling SQL Engine",
|
|
5
5
|
"author": "Hyperparam",
|
|
6
6
|
"homepage": "https://hyperparam.app",
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"test": "vitest run"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "25.0.
|
|
40
|
+
"@types/node": "25.0.6",
|
|
41
41
|
"@vitest/coverage-v8": "4.0.16",
|
|
42
42
|
"eslint": "9.39.2",
|
|
43
43
|
"eslint-plugin-jsdoc": "62.0.0",
|
package/src/execute/execute.js
CHANGED
|
@@ -10,7 +10,7 @@ import { resolveTableSource } from './tableSource.js'
|
|
|
10
10
|
import { compareForTerm, defaultDerivedAlias, stringify } from './utils.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @import { AsyncCells, AsyncDataSource, AsyncRow,
|
|
13
|
+
* @import { AsyncCells, AsyncDataSource, AsyncRow, ExecuteSqlOptions, ExprNode, OrderByItem, QueryHints, SelectColumn, SelectStatement, SqlPrimitive, UserDefinedFunction, WithClause } from '../types.js'
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -143,9 +143,10 @@ async function applyDistinct(rows, distinct) {
|
|
|
143
143
|
* @param {OrderByItem[]} options.orderBy - the sort specifications
|
|
144
144
|
* @param {Record<string, AsyncDataSource>} options.tables
|
|
145
145
|
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
146
|
+
* @param {Map<string, ExprNode>} [options.aliases] - SELECT column aliases for ORDER BY resolution
|
|
146
147
|
* @returns {Promise<AsyncRow[]>} the sorted rows
|
|
147
148
|
*/
|
|
148
|
-
async function sortRows({ rows, orderBy, tables, functions }) {
|
|
149
|
+
async function sortRows({ rows, orderBy, tables, functions, aliases }) {
|
|
149
150
|
if (!orderBy.length) return rows
|
|
150
151
|
|
|
151
152
|
// Cache for evaluated values: evaluatedValues[rowIdx][colIdx]
|
|
@@ -177,6 +178,7 @@ async function sortRows({ rows, orderBy, tables, functions }) {
|
|
|
177
178
|
row: rows[idx],
|
|
178
179
|
tables,
|
|
179
180
|
functions,
|
|
181
|
+
aliases,
|
|
180
182
|
})
|
|
181
183
|
}
|
|
182
184
|
}
|
|
@@ -459,7 +461,16 @@ async function* evaluateBuffered({ select, dataSource, tables, functions, hasAgg
|
|
|
459
461
|
} else {
|
|
460
462
|
// No grouping, simple projection
|
|
461
463
|
// Sort before projection so ORDER BY can access columns not in SELECT
|
|
462
|
-
|
|
464
|
+
|
|
465
|
+
// Pass aliases so ORDER BY can reference SELECT column aliases
|
|
466
|
+
/** @type {Map<string, ExprNode>} */
|
|
467
|
+
const aliases = new Map()
|
|
468
|
+
for (const col of select.columns) {
|
|
469
|
+
if (col.kind === 'derived' && col.alias) {
|
|
470
|
+
aliases.set(col.alias, col.expr)
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const sorted = await sortRows({ rows: filtered, orderBy: select.orderBy, tables, functions, aliases })
|
|
463
474
|
|
|
464
475
|
// OPTIMIZATION: For non-DISTINCT queries, apply OFFSET/LIMIT before projection
|
|
465
476
|
// to avoid reading expensive cells for rows that won't be in the final result
|
|
@@ -23,9 +23,10 @@ import { applyBinaryOp, stringify } from './utils.js'
|
|
|
23
23
|
* @param {Record<string, UserDefinedFunction>} [params.functions] - User-defined functions
|
|
24
24
|
* @param {number} [params.rowIndex] - 1-based row index for error reporting
|
|
25
25
|
* @param {AsyncRow[]} [params.rows] - Group of rows for aggregate functions
|
|
26
|
+
* @param {Map<string, ExprNode>} [params.aliases] - SELECT column aliases for ORDER BY resolution
|
|
26
27
|
* @returns {Promise<SqlPrimitive>} The result of the evaluation
|
|
27
28
|
*/
|
|
28
|
-
export async function evaluateExpr({ node, row, tables, functions, rowIndex, rows }) {
|
|
29
|
+
export async function evaluateExpr({ node, row, tables, functions, rowIndex, rows, aliases }) {
|
|
29
30
|
if (node.type === 'literal') {
|
|
30
31
|
return node.value
|
|
31
32
|
}
|
|
@@ -42,6 +43,11 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
42
43
|
return row.cells[colName]()
|
|
43
44
|
}
|
|
44
45
|
}
|
|
46
|
+
// Check if it's a SELECT alias (for ORDER BY)
|
|
47
|
+
if (aliases?.has(node.name)) {
|
|
48
|
+
return evaluateExpr({ node: aliases.get(node.name), row, tables, functions, rowIndex, rows, aliases })
|
|
49
|
+
}
|
|
50
|
+
// Unknown identifier
|
|
45
51
|
throw columnNotFoundError({
|
|
46
52
|
columnName: node.name,
|
|
47
53
|
availableColumns: Object.keys(row.cells),
|
|
@@ -63,16 +69,16 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
63
69
|
// Unary operators
|
|
64
70
|
if (node.type === 'unary') {
|
|
65
71
|
if (node.op === 'NOT') {
|
|
66
|
-
return !await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows })
|
|
72
|
+
return !await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows, aliases })
|
|
67
73
|
}
|
|
68
74
|
if (node.op === 'IS NULL') {
|
|
69
|
-
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows }) == null
|
|
75
|
+
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows, aliases }) == null
|
|
70
76
|
}
|
|
71
77
|
if (node.op === 'IS NOT NULL') {
|
|
72
|
-
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows }) != null
|
|
78
|
+
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows, aliases }) != null
|
|
73
79
|
}
|
|
74
80
|
if (node.op === '-') {
|
|
75
|
-
const val = await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows })
|
|
81
|
+
const val = await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows, aliases })
|
|
76
82
|
if (val == null) return null
|
|
77
83
|
return -val
|
|
78
84
|
}
|
|
@@ -82,15 +88,15 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
82
88
|
if (node.type === 'binary') {
|
|
83
89
|
// Handle date +/- interval at AST level
|
|
84
90
|
if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
|
|
85
|
-
const dateVal = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows })
|
|
91
|
+
const dateVal = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows, aliases })
|
|
86
92
|
return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
|
|
87
93
|
}
|
|
88
94
|
if (node.op === '+' && node.left.type === 'interval') {
|
|
89
|
-
const dateVal = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows })
|
|
95
|
+
const dateVal = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows, aliases })
|
|
90
96
|
return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
|
|
91
97
|
}
|
|
92
98
|
|
|
93
|
-
const left = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows })
|
|
99
|
+
const left = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows, aliases })
|
|
94
100
|
|
|
95
101
|
// Short-circuit evaluation for AND and OR
|
|
96
102
|
if (node.op === 'AND') {
|
|
@@ -100,7 +106,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
100
106
|
if (left) return true
|
|
101
107
|
}
|
|
102
108
|
|
|
103
|
-
const right = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows })
|
|
109
|
+
const right = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows, aliases })
|
|
104
110
|
return applyBinaryOp(node.op, left, right)
|
|
105
111
|
}
|
|
106
112
|
|
|
@@ -202,7 +208,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
202
208
|
}
|
|
203
209
|
|
|
204
210
|
/** @type {SqlPrimitive[]} */
|
|
205
|
-
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, functions, rowIndex, rows })))
|
|
211
|
+
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, functions, rowIndex, rows, aliases })))
|
|
206
212
|
|
|
207
213
|
if (isStringFunc(funcName)) {
|
|
208
214
|
return evaluateStringFunc({
|
package/src/execute/having.js
CHANGED
|
@@ -20,7 +20,7 @@ import { applyBinaryOp } from './utils.js'
|
|
|
20
20
|
*/
|
|
21
21
|
export async function evaluateHavingExpr({ expr, row, group, tables, functions }) {
|
|
22
22
|
// Having context
|
|
23
|
-
const context = { ...group[0]
|
|
23
|
+
const context = { ...group[0], ...row }
|
|
24
24
|
|
|
25
25
|
// For HAVING, we need special handling of aggregate functions
|
|
26
26
|
// They need to be re-evaluated against the group
|
package/src/execute/utils.js
CHANGED
package/src/validation.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
|
|
2
2
|
/**
|
|
3
|
-
* @import {AggregateFunc, BinaryOp,
|
|
3
|
+
* @import { AggregateFunc, BinaryOp, IntervalUnit, MathFunc, StringFunc, UserDefinedFunction } from './types.js'
|
|
4
4
|
* @param {string} name
|
|
5
5
|
* @returns {name is AggregateFunc}
|
|
6
6
|
*/
|