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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirreling",
3
- "version": "0.7.5",
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.3",
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",
@@ -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, CTEDefinition, ExecuteSqlOptions, OrderByItem, QueryHints, SelectStatement, SqlPrimitive, UserDefinedFunction, WithClause } from '../types.js'
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
- const sorted = await sortRows({ rows: filtered, orderBy: select.orderBy, tables, functions })
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({
@@ -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] ?? {}, ...row }
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
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @import {AsyncRow, BinaryOp, ExprNode, OrderByItem, SqlPrimitive} from '../types.js'
2
+ * @import { AsyncRow, BinaryOp, ExprNode, OrderByItem, SqlPrimitive } from '../types.js'
3
3
  */
4
4
 
5
5
  /**
package/src/validation.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  /**
3
- * @import {AggregateFunc, BinaryOp, ComparisonOp, IntervalUnit, MathFunc, StringFunc, UserDefinedFunction} from './types.js'
3
+ * @import { AggregateFunc, BinaryOp, IntervalUnit, MathFunc, StringFunc, UserDefinedFunction } from './types.js'
4
4
  * @param {string} name
5
5
  * @returns {name is AggregateFunc}
6
6
  */