squirreling 0.8.0 → 0.9.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 +3 -3
- package/src/execute/aggregates.js +18 -30
- package/src/execute/execute.js +22 -35
- package/src/execute/join.js +15 -31
- package/src/execute/sort.js +5 -10
- package/src/expression/evaluate.js +56 -60
- package/src/index.d.ts +24 -2
- package/src/index.js +2 -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 +19 -0
- package/src/validationErrors.js +10 -5
- package/src/execute/columns.js +0 -102
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { executeSelect } from '../execute/execute.js'
|
|
2
|
-
import { stringify } from '../execute/utils.js'
|
|
2
|
+
import { defaultDerivedAlias, 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
7
|
import { applyBinaryOp } from './binary.js'
|
|
8
8
|
import { applyIntervalToDate } from './date.js'
|
|
9
9
|
import { evaluateMathFunc } from './math.js'
|
|
@@ -11,24 +11,21 @@ import { evaluateRegexpFunc } from './regexp.js'
|
|
|
11
11
|
import { evaluateStringFunc } from './strings.js'
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
|
-
* @import { ExprNode, AsyncRow,
|
|
14
|
+
* @import { ExprNode, AsyncRow, ExecuteContext, SqlPrimitive } from '../types.js'
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Evaluates an expression node against a row of data
|
|
18
|
+
* Evaluates an expression node against a row of data
|
|
19
19
|
*
|
|
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
|
|
20
|
+
* @param {Object} options
|
|
21
|
+
* @param {ExprNode} options.node - The expression node to evaluate
|
|
22
|
+
* @param {AsyncRow} options.row - The data row to evaluate against
|
|
23
|
+
* @param {number} [options.rowIndex] - 1-based row index for error reporting
|
|
24
|
+
* @param {AsyncRow[]} [options.rows] - Group of rows for aggregate functions (undefined if not in aggregate context)
|
|
25
|
+
* @param {ExecuteContext} options.context - execution context (tables, functions, signal)
|
|
29
26
|
* @returns {Promise<SqlPrimitive>} The result of the evaluation
|
|
30
27
|
*/
|
|
31
|
-
export async function evaluateExpr({ node, row,
|
|
28
|
+
export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
|
|
32
29
|
if (node.type === 'literal') {
|
|
33
30
|
return node.value
|
|
34
31
|
}
|
|
@@ -45,10 +42,6 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
45
42
|
return row.cells[colName]()
|
|
46
43
|
}
|
|
47
44
|
}
|
|
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
45
|
// Unknown identifier
|
|
53
46
|
throw columnNotFoundError({
|
|
54
47
|
columnName: node.name,
|
|
@@ -61,7 +54,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
61
54
|
|
|
62
55
|
// Scalar subquery - returns a single value
|
|
63
56
|
if (node.type === 'subquery') {
|
|
64
|
-
const gen = executeSelect({ select: node.subquery,
|
|
57
|
+
const gen = executeSelect({ select: node.subquery, context })
|
|
65
58
|
const { value } = await gen.next() // Start the generator
|
|
66
59
|
gen.return(undefined) // Stop further execution
|
|
67
60
|
if (!value) return null
|
|
@@ -70,7 +63,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
70
63
|
|
|
71
64
|
// Unary operators
|
|
72
65
|
if (node.type === 'unary') {
|
|
73
|
-
const val = await evaluateExpr({ node: node.argument, row,
|
|
66
|
+
const val = await evaluateExpr({ node: node.argument, row, rowIndex, rows, context })
|
|
74
67
|
if (node.op === '-') {
|
|
75
68
|
if (val == null) return null
|
|
76
69
|
return -val
|
|
@@ -84,21 +77,21 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
84
77
|
if (node.type === 'binary') {
|
|
85
78
|
// Handle date +/- interval
|
|
86
79
|
if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
|
|
87
|
-
const dateVal = await evaluateExpr({ node: node.left, row,
|
|
80
|
+
const dateVal = await evaluateExpr({ node: node.left, row, rowIndex, rows, context })
|
|
88
81
|
return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
|
|
89
82
|
}
|
|
90
83
|
if (node.op === '+' && node.left.type === 'interval') {
|
|
91
|
-
const dateVal = await evaluateExpr({ node: node.right, row,
|
|
84
|
+
const dateVal = await evaluateExpr({ node: node.right, row, rowIndex, rows, context })
|
|
92
85
|
return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
|
|
93
86
|
}
|
|
94
87
|
|
|
95
|
-
const left = await evaluateExpr({ node: node.left, row,
|
|
88
|
+
const left = await evaluateExpr({ node: node.left, row, rowIndex, rows, context })
|
|
96
89
|
|
|
97
90
|
// Short-circuit evaluation for AND and OR
|
|
98
91
|
if (node.op === 'AND' && !left) return false
|
|
99
92
|
if (node.op === 'OR' && left) return true
|
|
100
93
|
|
|
101
|
-
const right = await evaluateExpr({ node: node.right, row,
|
|
94
|
+
const right = await evaluateExpr({ node: node.right, row, rowIndex, rows, context })
|
|
102
95
|
return applyBinaryOp(node.op, left, right)
|
|
103
96
|
}
|
|
104
97
|
|
|
@@ -109,10 +102,18 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
109
102
|
// Handle aggregate functions
|
|
110
103
|
if (isAggregateFunc(funcName)) {
|
|
111
104
|
if (!rows) {
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
105
|
+
// Aggregate function used outside of aggregate context
|
|
106
|
+
// This is only allowed if same aggregate was in the SELECT list
|
|
107
|
+
const derivedAlias = defaultDerivedAlias(node)
|
|
108
|
+
if (row.columns.includes(derivedAlias)) {
|
|
109
|
+
return row.cells[derivedAlias]()
|
|
110
|
+
} else {
|
|
111
|
+
throw aggregateError({
|
|
112
|
+
funcName,
|
|
113
|
+
positionStart: node.positionStart,
|
|
114
|
+
positionEnd: node.positionEnd,
|
|
115
|
+
})
|
|
116
|
+
}
|
|
116
117
|
}
|
|
117
118
|
|
|
118
119
|
// Apply FILTER clause if present
|
|
@@ -120,20 +121,14 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
120
121
|
if (node.filter) {
|
|
121
122
|
filteredRows = []
|
|
122
123
|
for (const row of rows) {
|
|
123
|
-
const passes = await evaluateExpr({ node: node.filter, row,
|
|
124
|
+
const passes = await evaluateExpr({ node: node.filter, row, context })
|
|
124
125
|
if (passes) filteredRows.push(row)
|
|
125
126
|
}
|
|
126
127
|
}
|
|
127
128
|
|
|
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
|
-
})
|
|
129
|
+
// Handle COUNT(*) special case
|
|
130
|
+
if (node.args.length === 1 && node.args[0].type === 'identifier' && funcName === 'COUNT' && node.args[0].name === '*') {
|
|
131
|
+
return filteredRows.length
|
|
137
132
|
}
|
|
138
133
|
|
|
139
134
|
const argNode = node.args[0]
|
|
@@ -142,14 +137,14 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
142
137
|
if (node.distinct) {
|
|
143
138
|
const seen = new Set()
|
|
144
139
|
for (const row of filteredRows) {
|
|
145
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
140
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
146
141
|
if (v != null) seen.add(v)
|
|
147
142
|
}
|
|
148
143
|
return seen.size
|
|
149
144
|
}
|
|
150
145
|
let count = 0
|
|
151
146
|
for (const row of filteredRows) {
|
|
152
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
147
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
153
148
|
if (v != null) count++
|
|
154
149
|
}
|
|
155
150
|
return count
|
|
@@ -164,7 +159,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
164
159
|
let max = null
|
|
165
160
|
|
|
166
161
|
for (const row of filteredRows) {
|
|
167
|
-
const raw = await evaluateExpr({ node: argNode, row,
|
|
162
|
+
const raw = await evaluateExpr({ node: argNode, row, context })
|
|
168
163
|
if (raw == null) continue
|
|
169
164
|
const num = Number(raw)
|
|
170
165
|
if (!Number.isFinite(num)) continue
|
|
@@ -189,7 +184,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
189
184
|
if (funcName === 'STDDEV_SAMP' || funcName === 'STDDEV_POP') {
|
|
190
185
|
const values = []
|
|
191
186
|
for (const row of filteredRows) {
|
|
192
|
-
const raw = await evaluateExpr({ node: argNode, row,
|
|
187
|
+
const raw = await evaluateExpr({ node: argNode, row, context })
|
|
193
188
|
if (raw == null) continue
|
|
194
189
|
const num = Number(raw)
|
|
195
190
|
if (!Number.isFinite(num)) continue
|
|
@@ -211,7 +206,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
211
206
|
if (node.distinct) {
|
|
212
207
|
const seen = new Set()
|
|
213
208
|
for (const row of filteredRows) {
|
|
214
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
209
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
215
210
|
const key = stringify(v)
|
|
216
211
|
if (!seen.has(key)) {
|
|
217
212
|
seen.add(key)
|
|
@@ -220,7 +215,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
220
215
|
}
|
|
221
216
|
} else {
|
|
222
217
|
for (const row of filteredRows) {
|
|
223
|
-
const v = await evaluateExpr({ node: argNode, row,
|
|
218
|
+
const v = await evaluateExpr({ node: argNode, row, context })
|
|
224
219
|
values.push(v)
|
|
225
220
|
}
|
|
226
221
|
}
|
|
@@ -229,7 +224,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
229
224
|
}
|
|
230
225
|
|
|
231
226
|
/** @type {SqlPrimitive[]} */
|
|
232
|
-
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row,
|
|
227
|
+
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, rowIndex, rows, context })))
|
|
233
228
|
|
|
234
229
|
if (isStringFunc(funcName)) {
|
|
235
230
|
return evaluateStringFunc({
|
|
@@ -258,7 +253,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
258
253
|
if (funcName === 'COALESCE') {
|
|
259
254
|
// Short-circuit: evaluate args one at a time, return first non-null
|
|
260
255
|
for (const arg of node.args) {
|
|
261
|
-
const val = await evaluateExpr({ node: arg, row,
|
|
256
|
+
const val = await evaluateExpr({ node: arg, row, rowIndex, rows, context })
|
|
262
257
|
if (val != null) return val
|
|
263
258
|
}
|
|
264
259
|
return null
|
|
@@ -266,8 +261,8 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
266
261
|
|
|
267
262
|
if (funcName === 'NULLIF') {
|
|
268
263
|
// 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,
|
|
264
|
+
const val1 = await evaluateExpr({ node: node.args[0], row, rowIndex, rows, context })
|
|
265
|
+
const val2 = await evaluateExpr({ node: node.args[1], row, rowIndex, rows, context })
|
|
271
266
|
return val1 == val2 ? null : val1
|
|
272
267
|
}
|
|
273
268
|
|
|
@@ -370,6 +365,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
370
365
|
}
|
|
371
366
|
|
|
372
367
|
// Check user-defined functions (case-insensitive lookup)
|
|
368
|
+
const { functions } = context
|
|
373
369
|
if (functions) {
|
|
374
370
|
const udfName = Object.keys(functions).find(k => k.toUpperCase() === funcName)
|
|
375
371
|
if (udfName) {
|
|
@@ -385,7 +381,7 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
385
381
|
}
|
|
386
382
|
|
|
387
383
|
if (node.type === 'cast') {
|
|
388
|
-
const val = await evaluateExpr({ node: node.expr, row,
|
|
384
|
+
const val = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
389
385
|
if (val == null) return null
|
|
390
386
|
const toType = node.toType.toUpperCase()
|
|
391
387
|
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
@@ -428,17 +424,17 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
428
424
|
|
|
429
425
|
// IN and NOT IN with value lists
|
|
430
426
|
if (node.type === 'in valuelist') {
|
|
431
|
-
const exprVal = await evaluateExpr({ node: node.expr, row,
|
|
427
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
432
428
|
for (const valueNode of node.values) {
|
|
433
|
-
const val = await evaluateExpr({ node: valueNode, row,
|
|
429
|
+
const val = await evaluateExpr({ node: valueNode, row, rowIndex, rows, context })
|
|
434
430
|
if (exprVal == val) return true
|
|
435
431
|
}
|
|
436
432
|
return false
|
|
437
433
|
}
|
|
438
434
|
// IN with subqueries
|
|
439
435
|
if (node.type === 'in') {
|
|
440
|
-
const exprVal = await evaluateExpr({ node: node.expr, row,
|
|
441
|
-
const results = executeSelect({ select: node.subquery,
|
|
436
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
|
|
437
|
+
const results = executeSelect({ select: node.subquery, context })
|
|
442
438
|
for await (const resRow of results) {
|
|
443
439
|
const value = await resRow.cells[resRow.columns[0]]()
|
|
444
440
|
if (exprVal == value) return true
|
|
@@ -448,39 +444,39 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
|
|
|
448
444
|
|
|
449
445
|
// EXISTS and NOT EXISTS with subqueries
|
|
450
446
|
if (node.type === 'exists') {
|
|
451
|
-
const results = await executeSelect({ select: node.subquery,
|
|
447
|
+
const results = await executeSelect({ select: node.subquery, context }).next()
|
|
452
448
|
return results.done === false
|
|
453
449
|
}
|
|
454
450
|
if (node.type === 'not exists') {
|
|
455
|
-
const results = await executeSelect({ select: node.subquery,
|
|
451
|
+
const results = await executeSelect({ select: node.subquery, context }).next()
|
|
456
452
|
return results.done === true
|
|
457
453
|
}
|
|
458
454
|
|
|
459
455
|
// CASE expressions
|
|
460
456
|
if (node.type === 'case') {
|
|
461
457
|
// For simple CASE: evaluate the case expression once
|
|
462
|
-
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row,
|
|
458
|
+
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, rowIndex, rows, context })
|
|
463
459
|
|
|
464
460
|
// Iterate through WHEN clauses
|
|
465
461
|
for (const whenClause of node.whenClauses) {
|
|
466
462
|
let conditionResult
|
|
467
463
|
if (caseValue !== undefined) {
|
|
468
464
|
// Simple CASE: compare caseValue with condition
|
|
469
|
-
const whenValue = await evaluateExpr({ node: whenClause.condition, row,
|
|
465
|
+
const whenValue = await evaluateExpr({ node: whenClause.condition, row, rowIndex, rows, context })
|
|
470
466
|
conditionResult = caseValue == whenValue
|
|
471
467
|
} else {
|
|
472
468
|
// Searched CASE: evaluate condition as boolean
|
|
473
|
-
conditionResult = await evaluateExpr({ node: whenClause.condition, row,
|
|
469
|
+
conditionResult = await evaluateExpr({ node: whenClause.condition, row, rowIndex, rows, context })
|
|
474
470
|
}
|
|
475
471
|
|
|
476
472
|
if (conditionResult) {
|
|
477
|
-
return evaluateExpr({ node: whenClause.result, row,
|
|
473
|
+
return evaluateExpr({ node: whenClause.result, row, rowIndex, rows, context })
|
|
478
474
|
}
|
|
479
475
|
}
|
|
480
476
|
|
|
481
477
|
// No WHEN clause matched, return ELSE result or NULL
|
|
482
478
|
if (node.elseResult) {
|
|
483
|
-
return evaluateExpr({ node: node.elseResult, row,
|
|
479
|
+
return evaluateExpr({ node: node.elseResult, row, rowIndex, rows, context })
|
|
484
480
|
}
|
|
485
481
|
return null
|
|
486
482
|
}
|
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, 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
|
*
|
package/src/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
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 { ParseError } from './parseErrors.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
|
}
|