squirreling 0.8.0 → 0.9.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/package.json +3 -3
- package/src/execute/aggregates.js +21 -32
- package/src/execute/execute.js +25 -37
- package/src/execute/join.js +15 -31
- package/src/execute/sort.js +5 -10
- package/src/execute/utils.js +1 -40
- package/src/expression/alias.js +42 -0
- package/src/expression/evaluate.js +56 -59
- package/src/index.d.ts +33 -2
- package/src/index.js +3 -2
- package/src/parse/comparison.js +7 -7
- package/src/parse/expression.js +14 -14
- package/src/parse/functions.js +23 -6
- package/src/parse/parse.js +55 -68
- package/src/parse/state.js +0 -9
- package/src/parse/tokenize.js +2 -2
- package/src/parse/types.d.ts +1 -1
- package/src/parseErrors.js +5 -4
- package/src/plan/columns.js +149 -0
- package/src/plan/plan.js +116 -46
- package/src/plan/types.d.ts +44 -47
- package/src/types.d.ts +24 -6
- package/src/validationErrors.js +10 -5
- package/src/execute/columns.js +0 -102
|
@@ -2,8 +2,9 @@ import { executeSelect } from '../execute/execute.js'
|
|
|
2
2
|
import { stringify } from '../execute/utils.js'
|
|
3
3
|
import { columnNotFoundError, invalidContextError } from '../executionErrors.js'
|
|
4
4
|
import { unknownFunctionError } from '../parseErrors.js'
|
|
5
|
-
import { aggregateError, argValueError, castError } from '../validationErrors.js'
|
|
6
5
|
import { isAggregateFunc, isMathFunc, isRegexpFunc, isStringFunc } from '../validation.js'
|
|
6
|
+
import { aggregateError, argValueError, castError } from '../validationErrors.js'
|
|
7
|
+
import { derivedAlias } from './alias.js'
|
|
7
8
|
import { applyBinaryOp } from './binary.js'
|
|
8
9
|
import { applyIntervalToDate } from './date.js'
|
|
9
10
|
import { evaluateMathFunc } from './math.js'
|
|
@@ -11,24 +12,21 @@ import { evaluateRegexpFunc } from './regexp.js'
|
|
|
11
12
|
import { evaluateStringFunc } from './strings.js'
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
|
-
* @import { ExprNode, AsyncRow,
|
|
15
|
+
* @import { ExprNode, AsyncRow, ExecuteContext, SqlPrimitive } from '../types.js'
|
|
15
16
|
*/
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
|
-
* Evaluates an expression node against a row of data
|
|
19
|
+
* Evaluates an expression node against a row of data
|
|
19
20
|
*
|
|
20
|
-
* @param {Object}
|
|
21
|
-
* @param {ExprNode}
|
|
22
|
-
* @param {AsyncRow}
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {AsyncRow[]} [params.rows] - Group of rows for aggregate functions
|
|
27
|
-
* @param {Map<string, ExprNode>} [params.aliases] - SELECT column aliases for ORDER BY resolution
|
|
28
|
-
* @param {AbortSignal} [params.signal] - abort signal for cancellation
|
|
21
|
+
* @param {Object} options
|
|
22
|
+
* @param {ExprNode} options.node - The expression node to evaluate
|
|
23
|
+
* @param {AsyncRow} options.row - The data row to evaluate against
|
|
24
|
+
* @param {number} [options.rowIndex] - 1-based row index for error reporting
|
|
25
|
+
* @param {AsyncRow[]} [options.rows] - Group of rows for aggregate functions (undefined if not in aggregate context)
|
|
26
|
+
* @param {ExecuteContext} options.context - execution context (tables, functions, signal)
|
|
29
27
|
* @returns {Promise<SqlPrimitive>} The result of the evaluation
|
|
30
28
|
*/
|
|
31
|
-
export async function evaluateExpr({ node, row,
|
|
29
|
+
export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
32
30
|
if (node.type === 'literal') {
|
|
33
31
|
return node.value
|
|
34
32
|
}
|
|
@@ -45,10 +43,6 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
45
43
|
return row.cells[colName]()
|
|
46
44
|
}
|
|
47
45
|
}
|
|
48
|
-
// Check if it's a SELECT alias (for ORDER BY)
|
|
49
|
-
if (aliases?.has(node.name)) {
|
|
50
|
-
return evaluateExpr({ node: aliases.get(node.name), row, tables, functions, rowIndex, rows, aliases, signal })
|
|
51
|
-
}
|
|
52
46
|
// Unknown identifier
|
|
53
47
|
throw columnNotFoundError({
|
|
54
48
|
columnName: node.name,
|
|
@@ -61,7 +55,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
61
55
|
|
|
62
56
|
// Scalar subquery - returns a single value
|
|
63
57
|
if (node.type === 'subquery') {
|
|
64
|
-
const gen = executeSelect({ select: node.subquery,
|
|
58
|
+
const gen = executeSelect({ select: node.subquery, context })
|
|
65
59
|
const { value } = await gen.next() // Start the generator
|
|
66
60
|
gen.return(undefined) // Stop further execution
|
|
67
61
|
if (!value) return null
|
|
@@ -70,7 +64,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
70
64
|
|
|
71
65
|
// Unary operators
|
|
72
66
|
if (node.type === 'unary') {
|
|
73
|
-
const val = await evaluateExpr({ node: node.argument, row,
|
|
67
|
+
const val = await evaluateExpr({ node: node.argument, row, rowIndex, rows, context })
|
|
74
68
|
if (node.op === '-') {
|
|
75
69
|
if (val == null) return null
|
|
76
70
|
return -val
|
|
@@ -84,21 +78,21 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
84
78
|
if (node.type === 'binary') {
|
|
85
79
|
// Handle date +/- interval
|
|
86
80
|
if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
|
|
87
|
-
const dateVal = await evaluateExpr({ node: node.left, row,
|
|
81
|
+
const dateVal = await evaluateExpr({ node: node.left, row, rowIndex, rows, context })
|
|
88
82
|
return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
|
|
89
83
|
}
|
|
90
84
|
if (node.op === '+' && node.left.type === 'interval') {
|
|
91
|
-
const dateVal = await evaluateExpr({ node: node.right, row,
|
|
85
|
+
const dateVal = await evaluateExpr({ node: node.right, row, rowIndex, rows, context })
|
|
92
86
|
return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
|
|
93
87
|
}
|
|
94
88
|
|
|
95
|
-
const left = await evaluateExpr({ node: node.left, row,
|
|
89
|
+
const left = await evaluateExpr({ node: node.left, row, rowIndex, rows, context })
|
|
96
90
|
|
|
97
91
|
// Short-circuit evaluation for AND and OR
|
|
98
92
|
if (node.op === 'AND' && !left) return false
|
|
99
93
|
if (node.op === 'OR' && left) return true
|
|
100
94
|
|
|
101
|
-
const right = await evaluateExpr({ node: node.right, row,
|
|
95
|
+
const right = await evaluateExpr({ node: node.right, row, rowIndex, rows, context })
|
|
102
96
|
return applyBinaryOp(node.op, left, right)
|
|
103
97
|
}
|
|
104
98
|
|
|
@@ -109,10 +103,18 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
109
103
|
// Handle aggregate functions
|
|
110
104
|
if (isAggregateFunc(funcName)) {
|
|
111
105
|
if (!rows) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
106
|
+
// Aggregate function used outside of aggregate context
|
|
107
|
+
// This is only allowed if same aggregate was in the SELECT list
|
|
108
|
+
const alias = derivedAlias(node)
|
|
109
|
+
if (row.columns.includes(alias)) {
|
|
110
|
+
return row.cells[alias]()
|
|
111
|
+
} else {
|
|
112
|
+
throw aggregateError({
|
|
113
|
+
funcName,
|
|
114
|
+
positionStart: node.positionStart,
|
|
115
|
+
positionEnd: node.positionEnd,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
116
118
|
}
|
|
117
119
|
|
|
118
120
|
// Apply FILTER clause if present
|
|
@@ -120,20 +122,14 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
120
122
|
if (node.filter) {
|
|
121
123
|
filteredRows = []
|
|
122
124
|
for (const row of rows) {
|
|
123
|
-
const passes = await evaluateExpr({ node: node.filter, row,
|
|
125
|
+
const passes = await evaluateExpr({ node: node.filter, row, context })
|
|
124
126
|
if (passes) filteredRows.push(row)
|
|
125
127
|
}
|
|
126
128
|
}
|
|
127
129
|
|
|
128
|
-
//
|
|
129
|
-
if (node.args.length === 1 && node.args[0].type === 'identifier' && node.args[0].name === '*') {
|
|
130
|
-
|
|
131
|
-
return filteredRows.length
|
|
132
|
-
}
|
|
133
|
-
throw aggregateError({
|
|
134
|
-
funcName,
|
|
135
|
-
issue: '(*) is not supported. Only COUNT supports *.',
|
|
136
|
-
})
|
|
130
|
+
// Handle COUNT(*) special case
|
|
131
|
+
if (node.args.length === 1 && node.args[0].type === 'identifier' && funcName === 'COUNT' && node.args[0].name === '*') {
|
|
132
|
+
return filteredRows.length
|
|
137
133
|
}
|
|
138
134
|
|
|
139
135
|
const argNode = node.args[0]
|
|
@@ -142,14 +138,14 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
142
138
|
if (node.distinct) {
|
|
143
139
|
const seen = new Set()
|
|
144
140
|
for (const row of filteredRows) {
|
|
145
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
141
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
146
142
|
if (v != null) seen.add(v)
|
|
147
143
|
}
|
|
148
144
|
return seen.size
|
|
149
145
|
}
|
|
150
146
|
let count = 0
|
|
151
147
|
for (const row of filteredRows) {
|
|
152
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
148
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
153
149
|
if (v != null) count++
|
|
154
150
|
}
|
|
155
151
|
return count
|
|
@@ -164,7 +160,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
164
160
|
let max = null
|
|
165
161
|
|
|
166
162
|
for (const row of filteredRows) {
|
|
167
|
-
const raw = await evaluateExpr({ node: argNode, row,
|
|
163
|
+
const raw = await evaluateExpr({ node: argNode, row, context })
|
|
168
164
|
if (raw == null) continue
|
|
169
165
|
const num = Number(raw)
|
|
170
166
|
if (!Number.isFinite(num)) continue
|
|
@@ -189,7 +185,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
189
185
|
if (funcName === 'STDDEV_SAMP' || funcName === 'STDDEV_POP') {
|
|
190
186
|
const values = []
|
|
191
187
|
for (const row of filteredRows) {
|
|
192
|
-
const raw = await evaluateExpr({ node: argNode, row,
|
|
188
|
+
const raw = await evaluateExpr({ node: argNode, row, context })
|
|
193
189
|
if (raw == null) continue
|
|
194
190
|
const num = Number(raw)
|
|
195
191
|
if (!Number.isFinite(num)) continue
|
|
@@ -211,7 +207,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
211
207
|
if (node.distinct) {
|
|
212
208
|
const seen = new Set()
|
|
213
209
|
for (const row of filteredRows) {
|
|
214
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
210
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
215
211
|
const key = stringify(v)
|
|
216
212
|
if (!seen.has(key)) {
|
|
217
213
|
seen.add(key)
|
|
@@ -220,7 +216,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
220
216
|
}
|
|
221
217
|
} else {
|
|
222
218
|
for (const row of filteredRows) {
|
|
223
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
219
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
224
220
|
values.push(v)
|
|
225
221
|
}
|
|
226
222
|
}
|
|
@@ -229,7 +225,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
229
225
|
}
|
|
230
226
|
|
|
231
227
|
/** @type {SqlPrimitive[]} */
|
|
232
|
-
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row,
|
|
228
|
+
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, rowIndex, rows, context })))
|
|
233
229
|
|
|
234
230
|
if (isStringFunc(funcName)) {
|
|
235
231
|
return evaluateStringFunc({
|
|
@@ -258,7 +254,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
258
254
|
if (funcName === 'COALESCE') {
|
|
259
255
|
// Short-circuit: evaluate args one at a time, return first non-null
|
|
260
256
|
for (const arg of node.args) {
|
|
261
|
-
const val = await evaluateExpr({ node: arg, row,
|
|
257
|
+
const val = await evaluateExpr({ node: arg, row, rowIndex, rows, context })
|
|
262
258
|
if (val != null) return val
|
|
263
259
|
}
|
|
264
260
|
return null
|
|
@@ -266,8 +262,8 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
266
262
|
|
|
267
263
|
if (funcName === 'NULLIF') {
|
|
268
264
|
// NULLIF(a, b) returns null if a = b, otherwise returns a
|
|
269
|
-
const val1 = await evaluateExpr({ node: node.args[0], row,
|
|
270
|
-
const val2 = await evaluateExpr({ node: node.args[1], row,
|
|
265
|
+
const val1 = await evaluateExpr({ node: node.args[0], row, rowIndex, rows, context })
|
|
266
|
+
const val2 = await evaluateExpr({ node: node.args[1], row, rowIndex, rows, context })
|
|
271
267
|
return val1 == val2 ? null : val1
|
|
272
268
|
}
|
|
273
269
|
|
|
@@ -370,6 +366,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
370
366
|
}
|
|
371
367
|
|
|
372
368
|
// Check user-defined functions (case-insensitive lookup)
|
|
369
|
+
const { functions } = context
|
|
373
370
|
if (functions) {
|
|
374
371
|
const udfName = Object.keys(functions).find(k => k.toUpperCase() === funcName)
|
|
375
372
|
if (udfName) {
|
|
@@ -385,7 +382,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
385
382
|
}
|
|
386
383
|
|
|
387
384
|
if (node.type === 'cast') {
|
|
388
|
-
const val = await evaluateExpr({ node: node.expr, row,
|
|
385
|
+
const val = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
389
386
|
if (val == null) return null
|
|
390
387
|
const toType = node.toType.toUpperCase()
|
|
391
388
|
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
@@ -428,17 +425,17 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
428
425
|
|
|
429
426
|
// IN and NOT IN with value lists
|
|
430
427
|
if (node.type === 'in valuelist') {
|
|
431
|
-
const exprVal = await evaluateExpr({ node: node.expr, row,
|
|
428
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
432
429
|
for (const valueNode of node.values) {
|
|
433
|
-
const val = await evaluateExpr({ node: valueNode, row,
|
|
430
|
+
const val = await evaluateExpr({ node: valueNode, row, rowIndex, rows, context })
|
|
434
431
|
if (exprVal == val) return true
|
|
435
432
|
}
|
|
436
433
|
return false
|
|
437
434
|
}
|
|
438
435
|
// IN with subqueries
|
|
439
436
|
if (node.type === 'in') {
|
|
440
|
-
const exprVal = await evaluateExpr({ node: node.expr, row,
|
|
441
|
-
const results = executeSelect({ select: node.subquery,
|
|
437
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
438
|
+
const results = executeSelect({ select: node.subquery, context })
|
|
442
439
|
for await (const resRow of results) {
|
|
443
440
|
const value = await resRow.cells[resRow.columns[0]]()
|
|
444
441
|
if (exprVal == value) return true
|
|
@@ -448,39 +445,39 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
448
445
|
|
|
449
446
|
// EXISTS and NOT EXISTS with subqueries
|
|
450
447
|
if (node.type === 'exists') {
|
|
451
|
-
const results = await executeSelect({ select: node.subquery,
|
|
448
|
+
const results = await executeSelect({ select: node.subquery, context }).next()
|
|
452
449
|
return results.done === false
|
|
453
450
|
}
|
|
454
451
|
if (node.type === 'not exists') {
|
|
455
|
-
const results = await executeSelect({ select: node.subquery,
|
|
452
|
+
const results = await executeSelect({ select: node.subquery, context }).next()
|
|
456
453
|
return results.done === true
|
|
457
454
|
}
|
|
458
455
|
|
|
459
456
|
// CASE expressions
|
|
460
457
|
if (node.type === 'case') {
|
|
461
458
|
// For simple CASE: evaluate the case expression once
|
|
462
|
-
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row,
|
|
459
|
+
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, rowIndex, rows, context })
|
|
463
460
|
|
|
464
461
|
// Iterate through WHEN clauses
|
|
465
462
|
for (const whenClause of node.whenClauses) {
|
|
466
463
|
let conditionResult
|
|
467
464
|
if (caseValue !== undefined) {
|
|
468
465
|
// Simple CASE: compare caseValue with condition
|
|
469
|
-
const whenValue = await evaluateExpr({ node: whenClause.condition, row,
|
|
466
|
+
const whenValue = await evaluateExpr({ node: whenClause.condition, row, rowIndex, rows, context })
|
|
470
467
|
conditionResult = caseValue == whenValue
|
|
471
468
|
} else {
|
|
472
469
|
// Searched CASE: evaluate condition as boolean
|
|
473
|
-
conditionResult = await evaluateExpr({ node: whenClause.condition, row,
|
|
470
|
+
conditionResult = await evaluateExpr({ node: whenClause.condition, row, rowIndex, rows, context })
|
|
474
471
|
}
|
|
475
472
|
|
|
476
473
|
if (conditionResult) {
|
|
477
|
-
return evaluateExpr({ node: whenClause.result, row,
|
|
474
|
+
return evaluateExpr({ node: whenClause.result, row, rowIndex, rows, context })
|
|
478
475
|
}
|
|
479
476
|
}
|
|
480
477
|
|
|
481
478
|
// No WHEN clause matched, return ELSE result or NULL
|
|
482
479
|
if (node.elseResult) {
|
|
483
|
-
return evaluateExpr({ node: node.elseResult, row,
|
|
480
|
+
return evaluateExpr({ node: node.elseResult, row, rowIndex, rows, context })
|
|
484
481
|
}
|
|
485
482
|
return null
|
|
486
483
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import type { AsyncDataSource, AsyncRow, ExecuteSqlOptions, ParseSqlOptions, SelectStatement, SqlPrimitive, Token } from './types.js'
|
|
1
|
+
import type { AsyncDataSource, AsyncRow, ExecuteContext, ExecuteSqlOptions, ExprNode, ParseSqlOptions, PlanSqlOptions, QueryPlan, SelectStatement, SqlPrimitive, Token } from './types.js'
|
|
2
2
|
export type {
|
|
3
3
|
AsyncCells,
|
|
4
4
|
AsyncDataSource,
|
|
5
5
|
AsyncRow,
|
|
6
|
+
ExecuteContext,
|
|
6
7
|
ExecuteSqlOptions,
|
|
7
8
|
ExprNode,
|
|
8
9
|
ParseSqlOptions,
|
|
10
|
+
PlanSqlOptions,
|
|
9
11
|
QueryPlan,
|
|
10
12
|
ScanOptions,
|
|
11
13
|
ScanResults,
|
|
@@ -16,7 +18,7 @@ export type {
|
|
|
16
18
|
} from './types.js'
|
|
17
19
|
|
|
18
20
|
/**
|
|
19
|
-
* Executes a SQL SELECT query against
|
|
21
|
+
* Executes a SQL SELECT query against tables
|
|
20
22
|
*
|
|
21
23
|
* @param options
|
|
22
24
|
* @param options.tables - source data as a list of objects or an AsyncDataSource
|
|
@@ -27,6 +29,16 @@ export type {
|
|
|
27
29
|
*/
|
|
28
30
|
export function executeSql(options: ExecuteSqlOptions): AsyncGenerator<AsyncRow>
|
|
29
31
|
|
|
32
|
+
/**
|
|
33
|
+
* Executes a query plan and yields result rows
|
|
34
|
+
*
|
|
35
|
+
* @param options
|
|
36
|
+
* @param options.plan - the query plan to execute
|
|
37
|
+
* @param options.context - execution context with tables, functions, and signal
|
|
38
|
+
* @returns async generator yielding result rows
|
|
39
|
+
*/
|
|
40
|
+
export function executePlan(options: { plan: QueryPlan, context: ExecuteContext }): AsyncGenerator<AsyncRow>
|
|
41
|
+
|
|
30
42
|
/**
|
|
31
43
|
* Parses a SQL query string into an abstract syntax tree
|
|
32
44
|
*
|
|
@@ -37,6 +49,16 @@ export function executeSql(options: ExecuteSqlOptions): AsyncGenerator<AsyncRow>
|
|
|
37
49
|
*/
|
|
38
50
|
export function parseSql(options: ParseSqlOptions): SelectStatement
|
|
39
51
|
|
|
52
|
+
/**
|
|
53
|
+
* Builds a query plan from a SQL query string or AST
|
|
54
|
+
*
|
|
55
|
+
* @param options
|
|
56
|
+
* @param options.query - SQL query string or parsed SelectStatement
|
|
57
|
+
* @param options.functions - user-defined functions available in the SQL context
|
|
58
|
+
* @returns the root of the query plan tree
|
|
59
|
+
*/
|
|
60
|
+
export function planSql(options: PlanSqlOptions): QueryPlan
|
|
61
|
+
|
|
40
62
|
/**
|
|
41
63
|
* Tokenizes a SQL query string into an array of tokens
|
|
42
64
|
*
|
|
@@ -54,3 +76,12 @@ export function tokenizeSql(sql: string): Token[]
|
|
|
54
76
|
export function collect<T>(asyncGen: AsyncGenerator<AsyncRow>): Promise<Record<string, SqlPrimitive>[]>
|
|
55
77
|
|
|
56
78
|
export function cachedDataSource(source: AsyncDataSource): AsyncDataSource
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Generates a default alias for a derived column expression.
|
|
82
|
+
* Useful for generating column names pre-execution.
|
|
83
|
+
*
|
|
84
|
+
* @param expr - the expression node
|
|
85
|
+
* @returns the generated alias
|
|
86
|
+
*/
|
|
87
|
+
export function derivedAlias(expr: ExprNode): string
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
export { executeSql } from './execute/execute.js'
|
|
1
|
+
export { executePlan, executeSql } from './execute/execute.js'
|
|
2
2
|
export { parseSql } from './parse/parse.js'
|
|
3
|
+
export { planSql } from './plan/plan.js'
|
|
3
4
|
export { tokenizeSql } from './parse/tokenize.js'
|
|
4
5
|
export { collect } from './execute/utils.js'
|
|
5
6
|
export { cachedDataSource } from './backend/dataSource.js'
|
|
6
|
-
export {
|
|
7
|
+
export { derivedAlias } from './expression/alias.js'
|
package/src/parse/comparison.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { syntaxError } from '../parseErrors.js'
|
|
2
2
|
import { isBinaryOp } from '../validation.js'
|
|
3
3
|
import { parseAdditive, parseExpression, parseSubquery } from './expression.js'
|
|
4
|
-
import { consume, current, expect,
|
|
4
|
+
import { consume, current, expect, match, peekToken } from './state.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @import { ExprNode, ParserState } from '../types.js'
|
|
@@ -27,7 +27,7 @@ export function parseComparison(state) {
|
|
|
27
27
|
op: 'IS NOT NULL',
|
|
28
28
|
argument: left,
|
|
29
29
|
positionStart: left.positionStart,
|
|
30
|
-
positionEnd:
|
|
30
|
+
positionEnd: state.lastPos,
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
33
|
expect(state, 'keyword', 'NULL')
|
|
@@ -36,7 +36,7 @@ export function parseComparison(state) {
|
|
|
36
36
|
op: 'IS NULL',
|
|
37
37
|
argument: left,
|
|
38
38
|
positionStart: left.positionStart,
|
|
39
|
-
positionEnd:
|
|
39
|
+
positionEnd: state.lastPos,
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
@@ -134,7 +134,7 @@ export function parseComparison(state) {
|
|
|
134
134
|
if (peekTok.type === 'keyword' && peekTok.value === 'SELECT') {
|
|
135
135
|
// Subquery - let parseSubquery handle the parens
|
|
136
136
|
const subquery = parseSubquery(state)
|
|
137
|
-
const positionEnd =
|
|
137
|
+
const positionEnd = state.lastPos
|
|
138
138
|
return {
|
|
139
139
|
type: 'unary',
|
|
140
140
|
op: 'NOT',
|
|
@@ -158,7 +158,7 @@ export function parseComparison(state) {
|
|
|
158
158
|
if (!match(state, 'comma')) break
|
|
159
159
|
}
|
|
160
160
|
expect(state, 'paren', ')')
|
|
161
|
-
const positionEnd =
|
|
161
|
+
const positionEnd = state.lastPos
|
|
162
162
|
return {
|
|
163
163
|
type: 'unary',
|
|
164
164
|
op: 'NOT',
|
|
@@ -194,7 +194,7 @@ export function parseComparison(state) {
|
|
|
194
194
|
expr: left,
|
|
195
195
|
subquery,
|
|
196
196
|
positionStart: left.positionStart,
|
|
197
|
-
positionEnd:
|
|
197
|
+
positionEnd: state.lastPos,
|
|
198
198
|
}
|
|
199
199
|
} else {
|
|
200
200
|
// Parse list of values - we handle the parens
|
|
@@ -211,7 +211,7 @@ export function parseComparison(state) {
|
|
|
211
211
|
expr: left,
|
|
212
212
|
values,
|
|
213
213
|
positionStart: left.positionStart,
|
|
214
|
-
positionEnd:
|
|
214
|
+
positionEnd: state.lastPos,
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
}
|
package/src/parse/expression.js
CHANGED
|
@@ -8,7 +8,7 @@ import { isIntervalUnit, isKnownFunction } from '../validation.js'
|
|
|
8
8
|
import { parseComparison } from './comparison.js'
|
|
9
9
|
import { parseFunctionCall } from './functions.js'
|
|
10
10
|
import { parseSelectInternal } from './parse.js'
|
|
11
|
-
import { consume, current, expect, expectIdentifier,
|
|
11
|
+
import { consume, current, expect, expectIdentifier, match, peekToken } from './state.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* @import { ExprNode, IntervalNode, ParserState, SelectStatement, WhenClause } from '../types.js'
|
|
@@ -40,7 +40,7 @@ export function parsePrimary(state) {
|
|
|
40
40
|
type: 'subquery',
|
|
41
41
|
subquery,
|
|
42
42
|
positionStart,
|
|
43
|
-
positionEnd:
|
|
43
|
+
positionEnd: state.lastPos,
|
|
44
44
|
}
|
|
45
45
|
}
|
|
46
46
|
// Regular grouped expression
|
|
@@ -66,7 +66,7 @@ export function parsePrimary(state) {
|
|
|
66
66
|
expr,
|
|
67
67
|
toType: typeTok.value,
|
|
68
68
|
positionStart,
|
|
69
|
-
positionEnd:
|
|
69
|
+
positionEnd: state.lastPos,
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
@@ -96,7 +96,7 @@ export function parsePrimary(state) {
|
|
|
96
96
|
name: tok.value,
|
|
97
97
|
args: [],
|
|
98
98
|
positionStart,
|
|
99
|
-
positionEnd:
|
|
99
|
+
positionEnd: state.lastPos,
|
|
100
100
|
}
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -114,7 +114,7 @@ export function parsePrimary(state) {
|
|
|
114
114
|
type: 'identifier',
|
|
115
115
|
name,
|
|
116
116
|
positionStart,
|
|
117
|
-
positionEnd:
|
|
117
|
+
positionEnd: state.lastPos,
|
|
118
118
|
}
|
|
119
119
|
}
|
|
120
120
|
|
|
@@ -124,7 +124,7 @@ export function parsePrimary(state) {
|
|
|
124
124
|
type: 'literal',
|
|
125
125
|
value: tok.numericValue ?? null,
|
|
126
126
|
positionStart,
|
|
127
|
-
positionEnd:
|
|
127
|
+
positionEnd: state.lastPos,
|
|
128
128
|
}
|
|
129
129
|
}
|
|
130
130
|
|
|
@@ -134,7 +134,7 @@ export function parsePrimary(state) {
|
|
|
134
134
|
type: 'literal',
|
|
135
135
|
value: tok.value,
|
|
136
136
|
positionStart,
|
|
137
|
-
positionEnd:
|
|
137
|
+
positionEnd: state.lastPos,
|
|
138
138
|
}
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -148,15 +148,15 @@ export function parsePrimary(state) {
|
|
|
148
148
|
|
|
149
149
|
if (tok.value === 'TRUE') {
|
|
150
150
|
consume(state)
|
|
151
|
-
return { type: 'literal', value: true, positionStart, positionEnd:
|
|
151
|
+
return { type: 'literal', value: true, positionStart, positionEnd: state.lastPos }
|
|
152
152
|
}
|
|
153
153
|
if (tok.value === 'FALSE') {
|
|
154
154
|
consume(state)
|
|
155
|
-
return { type: 'literal', value: false, positionStart, positionEnd:
|
|
155
|
+
return { type: 'literal', value: false, positionStart, positionEnd: state.lastPos }
|
|
156
156
|
}
|
|
157
157
|
if (tok.value === 'NULL') {
|
|
158
158
|
consume(state)
|
|
159
|
-
return { type: 'literal', value: null, positionStart, positionEnd:
|
|
159
|
+
return { type: 'literal', value: null, positionStart, positionEnd: state.lastPos }
|
|
160
160
|
}
|
|
161
161
|
if (tok.value === 'EXISTS') {
|
|
162
162
|
consume(state) // EXISTS
|
|
@@ -165,7 +165,7 @@ export function parsePrimary(state) {
|
|
|
165
165
|
type: 'exists',
|
|
166
166
|
subquery,
|
|
167
167
|
positionStart,
|
|
168
|
-
positionEnd:
|
|
168
|
+
positionEnd: state.lastPos,
|
|
169
169
|
}
|
|
170
170
|
}
|
|
171
171
|
if (tok.value === 'CASE') {
|
|
@@ -212,7 +212,7 @@ export function parsePrimary(state) {
|
|
|
212
212
|
whenClauses,
|
|
213
213
|
elseResult,
|
|
214
214
|
positionStart,
|
|
215
|
-
positionEnd:
|
|
215
|
+
positionEnd: state.lastPos,
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
if (tok.value === 'INTERVAL') {
|
|
@@ -293,7 +293,7 @@ function parseNot(state) {
|
|
|
293
293
|
type: 'not exists',
|
|
294
294
|
subquery,
|
|
295
295
|
positionStart,
|
|
296
|
-
positionEnd:
|
|
296
|
+
positionEnd: state.lastPos,
|
|
297
297
|
}
|
|
298
298
|
}
|
|
299
299
|
const argument = parseNot(state)
|
|
@@ -412,5 +412,5 @@ function parseInterval(state) {
|
|
|
412
412
|
}
|
|
413
413
|
consume(state)
|
|
414
414
|
|
|
415
|
-
return { type: 'interval', value, unit: unitTok.value, positionStart, positionEnd:
|
|
415
|
+
return { type: 'interval', value, unit: unitTok.value, positionStart, positionEnd: state.lastPos }
|
|
416
416
|
}
|
package/src/parse/functions.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { argCountParseError, syntaxError } from '../parseErrors.js'
|
|
1
|
+
import { ParseError, argCountParseError, syntaxError } from '../parseErrors.js'
|
|
2
2
|
import { isAggregateFunc, validateFunctionArgCount } from '../validation.js'
|
|
3
3
|
import { parseExpression } from './expression.js'
|
|
4
|
-
import { consume, current, expect,
|
|
4
|
+
import { consume, current, expect, match } from './state.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* @import { ExprNode, ParserState } from '../types.js'
|
|
@@ -41,7 +41,7 @@ export function parseFunctionCall(state, funcName, positionStart) {
|
|
|
41
41
|
type: 'identifier',
|
|
42
42
|
name: '*',
|
|
43
43
|
positionStart: starTok.positionStart,
|
|
44
|
-
positionEnd:
|
|
44
|
+
positionEnd: state.lastPos,
|
|
45
45
|
})
|
|
46
46
|
} else {
|
|
47
47
|
args.push(parseExpression(state))
|
|
@@ -72,8 +72,25 @@ export function parseFunctionCall(state, funcName, positionStart) {
|
|
|
72
72
|
expect(state, 'paren', ')')
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// Validate argument
|
|
75
|
+
// Validate star argument at parse time (only COUNT supports *)
|
|
76
76
|
const funcNameUpper = funcName.toUpperCase()
|
|
77
|
+
const hasStar = args.length === 1 && args[0].type === 'identifier' && args[0].name === '*'
|
|
78
|
+
if (hasStar && isAggregateFunc(funcNameUpper) && funcNameUpper !== 'COUNT') {
|
|
79
|
+
throw new ParseError({
|
|
80
|
+
message: `${funcName} cannot be applied to "*"`,
|
|
81
|
+
positionStart,
|
|
82
|
+
positionEnd: state.lastPos,
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
if (hasStar && distinct) {
|
|
86
|
+
throw new ParseError({
|
|
87
|
+
message: 'COUNT(DISTINCT *) is not allowed',
|
|
88
|
+
positionStart,
|
|
89
|
+
positionEnd: state.lastPos,
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Validate argument count at parse time
|
|
77
94
|
const validation = validateFunctionArgCount(funcNameUpper, args.length, state.functions)
|
|
78
95
|
if (!validation.valid) {
|
|
79
96
|
throw argCountParseError({
|
|
@@ -81,7 +98,7 @@ export function parseFunctionCall(state, funcName, positionStart) {
|
|
|
81
98
|
expected: validation.expected,
|
|
82
99
|
received: args.length,
|
|
83
100
|
positionStart,
|
|
84
|
-
positionEnd:
|
|
101
|
+
positionEnd: state.lastPos,
|
|
85
102
|
})
|
|
86
103
|
}
|
|
87
104
|
|
|
@@ -92,6 +109,6 @@ export function parseFunctionCall(state, funcName, positionStart) {
|
|
|
92
109
|
distinct: distinct || undefined,
|
|
93
110
|
filter,
|
|
94
111
|
positionStart,
|
|
95
|
-
positionEnd:
|
|
112
|
+
positionEnd: state.lastPos,
|
|
96
113
|
}
|
|
97
114
|
}
|