squirreling 0.6.1 → 0.7.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/README.md +14 -10
- package/package.json +1 -1
- package/src/execute/execute.js +58 -66
- package/src/execute/expression.js +37 -167
- package/src/execute/having.js +37 -31
- package/src/execute/join.js +28 -22
- package/src/execute/math.js +10 -209
- package/src/index.d.ts +3 -2
- package/src/parse/expression.js +19 -11
- package/src/parse/parse.js +4 -4
- package/src/parseErrors.js +29 -0
- package/src/types.d.ts +5 -0
- package/src/validation.js +121 -1
- package/src/validationErrors.js +1 -25
|
@@ -2,7 +2,6 @@ import { unknownFunctionError } from '../parseErrors.js'
|
|
|
2
2
|
import { invalidContextError } from '../executionErrors.js'
|
|
3
3
|
import {
|
|
4
4
|
aggregateError,
|
|
5
|
-
argCountError,
|
|
6
5
|
argValueError,
|
|
7
6
|
castError,
|
|
8
7
|
} from '../validationErrors.js'
|
|
@@ -13,7 +12,7 @@ import { evaluateMathFunc } from './math.js'
|
|
|
13
12
|
import { applyBinaryOp, stringify } from './utils.js'
|
|
14
13
|
|
|
15
14
|
/**
|
|
16
|
-
* @import { ExprNode, AsyncRow, SqlPrimitive, AsyncDataSource, IntervalUnit } from '../types.js'
|
|
15
|
+
* @import { ExprNode, AsyncRow, SqlPrimitive, AsyncDataSource, IntervalUnit, UserDefinedFunction } from '../types.js'
|
|
17
16
|
*/
|
|
18
17
|
|
|
19
18
|
/**
|
|
@@ -23,11 +22,12 @@ import { applyBinaryOp, stringify } from './utils.js'
|
|
|
23
22
|
* @param {ExprNode} params.node - The expression node to evaluate
|
|
24
23
|
* @param {AsyncRow} params.row - The data row to evaluate against
|
|
25
24
|
* @param {Record<string, AsyncDataSource>} params.tables
|
|
25
|
+
* @param {Record<string, UserDefinedFunction>} [params.functions] - User-defined functions
|
|
26
26
|
* @param {number} [params.rowIndex] - 1-based row index for error reporting
|
|
27
27
|
* @param {AsyncRow[]} [params.rows] - Group of rows for aggregate functions
|
|
28
28
|
* @returns {Promise<SqlPrimitive>} The result of the evaluation
|
|
29
29
|
*/
|
|
30
|
-
export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
30
|
+
export async function evaluateExpr({ node, row, tables, functions, rowIndex, rows }) {
|
|
31
31
|
if (node.type === 'literal') {
|
|
32
32
|
return node.value
|
|
33
33
|
}
|
|
@@ -59,16 +59,16 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
59
59
|
// Unary operators
|
|
60
60
|
if (node.type === 'unary') {
|
|
61
61
|
if (node.op === 'NOT') {
|
|
62
|
-
return !await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows })
|
|
62
|
+
return !await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows })
|
|
63
63
|
}
|
|
64
64
|
if (node.op === 'IS NULL') {
|
|
65
|
-
return await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows }) == null
|
|
65
|
+
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows }) == null
|
|
66
66
|
}
|
|
67
67
|
if (node.op === 'IS NOT NULL') {
|
|
68
|
-
return await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows }) != null
|
|
68
|
+
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows }) != null
|
|
69
69
|
}
|
|
70
70
|
if (node.op === '-') {
|
|
71
|
-
const val = await evaluateExpr({ node: node.argument, row, tables, rowIndex, rows })
|
|
71
|
+
const val = await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows })
|
|
72
72
|
if (val == null) return null
|
|
73
73
|
return -val
|
|
74
74
|
}
|
|
@@ -78,15 +78,15 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
78
78
|
if (node.type === 'binary') {
|
|
79
79
|
// Handle date +/- interval at AST level
|
|
80
80
|
if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
|
|
81
|
-
const dateVal = await evaluateExpr({ node: node.left, row, tables, rowIndex, rows })
|
|
81
|
+
const dateVal = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows })
|
|
82
82
|
return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
|
|
83
83
|
}
|
|
84
84
|
if (node.op === '+' && node.left.type === 'interval') {
|
|
85
|
-
const dateVal = await evaluateExpr({ node: node.right, row, tables, rowIndex, rows })
|
|
85
|
+
const dateVal = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows })
|
|
86
86
|
return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
const left = await evaluateExpr({ node: node.left, row, tables, rowIndex, rows })
|
|
89
|
+
const left = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows })
|
|
90
90
|
|
|
91
91
|
// Short-circuit evaluation for AND and OR
|
|
92
92
|
if (node.op === 'AND') {
|
|
@@ -96,7 +96,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
96
96
|
if (left) return true
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
const right = await evaluateExpr({ node: node.right, row, tables, rowIndex, rows })
|
|
99
|
+
const right = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows })
|
|
100
100
|
return applyBinaryOp(node.op, left, right)
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -124,31 +124,20 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
124
124
|
})
|
|
125
125
|
}
|
|
126
126
|
|
|
127
|
-
if (node.args.length !== 1) {
|
|
128
|
-
throw argCountError({
|
|
129
|
-
funcName,
|
|
130
|
-
expected: 1,
|
|
131
|
-
received: node.args.length,
|
|
132
|
-
positionStart: node.positionStart,
|
|
133
|
-
positionEnd: node.positionEnd,
|
|
134
|
-
rowNumber: rowIndex,
|
|
135
|
-
})
|
|
136
|
-
}
|
|
137
|
-
|
|
138
127
|
const argNode = node.args[0]
|
|
139
128
|
|
|
140
129
|
if (funcName === 'COUNT') {
|
|
141
130
|
if (node.distinct) {
|
|
142
131
|
const seen = new Set()
|
|
143
132
|
for (const r of rows) {
|
|
144
|
-
const v = await evaluateExpr({ node: argNode, row: r, tables })
|
|
133
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
145
134
|
if (v != null) seen.add(v)
|
|
146
135
|
}
|
|
147
136
|
return seen.size
|
|
148
137
|
}
|
|
149
138
|
let count = 0
|
|
150
139
|
for (const r of rows) {
|
|
151
|
-
const v = await evaluateExpr({ node: argNode, row: r, tables })
|
|
140
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
152
141
|
if (v != null) count++
|
|
153
142
|
}
|
|
154
143
|
return count
|
|
@@ -163,7 +152,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
163
152
|
let max = null
|
|
164
153
|
|
|
165
154
|
for (const r of rows) {
|
|
166
|
-
const raw = await evaluateExpr({ node: argNode, row: r, tables })
|
|
155
|
+
const raw = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
167
156
|
if (raw == null) continue
|
|
168
157
|
const num = Number(raw)
|
|
169
158
|
if (!Number.isFinite(num)) continue
|
|
@@ -191,7 +180,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
191
180
|
if (node.distinct) {
|
|
192
181
|
const seen = new Set()
|
|
193
182
|
for (const r of rows) {
|
|
194
|
-
const v = await evaluateExpr({ node: argNode, row: r, tables })
|
|
183
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
195
184
|
const key = stringify(v)
|
|
196
185
|
if (!seen.has(key)) {
|
|
197
186
|
seen.add(key)
|
|
@@ -200,7 +189,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
200
189
|
}
|
|
201
190
|
} else {
|
|
202
191
|
for (const r of rows) {
|
|
203
|
-
const v = await evaluateExpr({ node: argNode, row: r, tables })
|
|
192
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
204
193
|
values.push(v)
|
|
205
194
|
}
|
|
206
195
|
}
|
|
@@ -209,51 +198,21 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
209
198
|
}
|
|
210
199
|
|
|
211
200
|
/** @type {SqlPrimitive[]} */
|
|
212
|
-
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, rowIndex, rows })))
|
|
201
|
+
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, functions, rowIndex, rows })))
|
|
213
202
|
|
|
214
203
|
if (funcName === 'UPPER') {
|
|
215
|
-
if (args.length !== 1) {
|
|
216
|
-
throw argCountError({
|
|
217
|
-
funcName: 'UPPER',
|
|
218
|
-
expected: 1,
|
|
219
|
-
received: args.length,
|
|
220
|
-
positionStart: node.positionStart,
|
|
221
|
-
positionEnd: node.positionEnd,
|
|
222
|
-
rowNumber: rowIndex,
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
204
|
const val = args[0]
|
|
226
205
|
if (val == null) return null
|
|
227
206
|
return String(val).toUpperCase()
|
|
228
207
|
}
|
|
229
208
|
|
|
230
209
|
if (funcName === 'LOWER') {
|
|
231
|
-
if (args.length !== 1) {
|
|
232
|
-
throw argCountError({
|
|
233
|
-
funcName: 'LOWER',
|
|
234
|
-
expected: 1,
|
|
235
|
-
received: args.length,
|
|
236
|
-
positionStart: node.positionStart,
|
|
237
|
-
positionEnd: node.positionEnd,
|
|
238
|
-
rowNumber: rowIndex,
|
|
239
|
-
})
|
|
240
|
-
}
|
|
241
210
|
const val = args[0]
|
|
242
211
|
if (val == null) return null
|
|
243
212
|
return String(val).toLowerCase()
|
|
244
213
|
}
|
|
245
214
|
|
|
246
215
|
if (funcName === 'CONCAT') {
|
|
247
|
-
if (args.length < 1) {
|
|
248
|
-
throw argCountError({
|
|
249
|
-
funcName: 'CONCAT',
|
|
250
|
-
expected: 'at least 1',
|
|
251
|
-
received: args.length,
|
|
252
|
-
positionStart: node.positionStart,
|
|
253
|
-
positionEnd: node.positionEnd,
|
|
254
|
-
rowNumber: rowIndex,
|
|
255
|
-
})
|
|
256
|
-
}
|
|
257
216
|
// SQL CONCAT returns NULL if any argument is NULL
|
|
258
217
|
if (args.some(a => a == null)) return null
|
|
259
218
|
if (args.some(a => typeof a === 'object')) {
|
|
@@ -270,32 +229,12 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
270
229
|
}
|
|
271
230
|
|
|
272
231
|
if (funcName === 'LENGTH') {
|
|
273
|
-
if (args.length !== 1) {
|
|
274
|
-
throw argCountError({
|
|
275
|
-
funcName: 'LENGTH',
|
|
276
|
-
expected: 1,
|
|
277
|
-
received: args.length,
|
|
278
|
-
positionStart: node.positionStart,
|
|
279
|
-
positionEnd: node.positionEnd,
|
|
280
|
-
rowNumber: rowIndex,
|
|
281
|
-
})
|
|
282
|
-
}
|
|
283
232
|
const val = args[0]
|
|
284
233
|
if (val == null) return null
|
|
285
234
|
return String(val).length
|
|
286
235
|
}
|
|
287
236
|
|
|
288
237
|
if (funcName === 'SUBSTRING' || funcName === 'SUBSTR') {
|
|
289
|
-
if (args.length < 2 || args.length > 3) {
|
|
290
|
-
throw argCountError({
|
|
291
|
-
funcName,
|
|
292
|
-
expected: '2 or 3',
|
|
293
|
-
received: args.length,
|
|
294
|
-
positionStart: node.positionStart,
|
|
295
|
-
positionEnd: node.positionEnd,
|
|
296
|
-
rowNumber: rowIndex,
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
238
|
const str = args[0]
|
|
300
239
|
if (str == null) return null
|
|
301
240
|
const strVal = String(str)
|
|
@@ -329,32 +268,12 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
329
268
|
}
|
|
330
269
|
|
|
331
270
|
if (funcName === 'TRIM') {
|
|
332
|
-
if (args.length !== 1) {
|
|
333
|
-
throw argCountError({
|
|
334
|
-
funcName: 'TRIM',
|
|
335
|
-
expected: 1,
|
|
336
|
-
received: args.length,
|
|
337
|
-
positionStart: node.positionStart,
|
|
338
|
-
positionEnd: node.positionEnd,
|
|
339
|
-
rowNumber: rowIndex,
|
|
340
|
-
})
|
|
341
|
-
}
|
|
342
271
|
const val = args[0]
|
|
343
272
|
if (val == null) return null
|
|
344
273
|
return String(val).trim()
|
|
345
274
|
}
|
|
346
275
|
|
|
347
276
|
if (funcName === 'REPLACE') {
|
|
348
|
-
if (args.length !== 3) {
|
|
349
|
-
throw argCountError({
|
|
350
|
-
funcName: 'REPLACE',
|
|
351
|
-
expected: 3,
|
|
352
|
-
received: args.length,
|
|
353
|
-
positionStart: node.positionStart,
|
|
354
|
-
positionEnd: node.positionEnd,
|
|
355
|
-
rowNumber: rowIndex,
|
|
356
|
-
})
|
|
357
|
-
}
|
|
358
277
|
const str = args[0]
|
|
359
278
|
const searchStr = args[1]
|
|
360
279
|
const replaceStr = args[2]
|
|
@@ -364,67 +283,26 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
364
283
|
}
|
|
365
284
|
|
|
366
285
|
if (funcName === 'RANDOM' || funcName === 'RAND') {
|
|
367
|
-
if (args.length !== 0) {
|
|
368
|
-
throw argCountError({
|
|
369
|
-
funcName,
|
|
370
|
-
expected: 0,
|
|
371
|
-
received: args.length,
|
|
372
|
-
positionStart: node.positionStart,
|
|
373
|
-
positionEnd: node.positionEnd,
|
|
374
|
-
rowNumber: rowIndex,
|
|
375
|
-
})
|
|
376
|
-
}
|
|
377
286
|
return Math.random()
|
|
378
287
|
}
|
|
379
288
|
|
|
380
289
|
if (funcName === 'CURRENT_DATE') {
|
|
381
|
-
if (args.length !== 0) {
|
|
382
|
-
throw argCountError({
|
|
383
|
-
funcName: 'CURRENT_DATE',
|
|
384
|
-
expected: 0,
|
|
385
|
-
received: args.length,
|
|
386
|
-
positionStart: node.positionStart,
|
|
387
|
-
positionEnd: node.positionEnd,
|
|
388
|
-
rowNumber: rowIndex,
|
|
389
|
-
})
|
|
390
|
-
}
|
|
391
290
|
return new Date().toISOString().split('T')[0]
|
|
392
291
|
}
|
|
393
292
|
|
|
394
293
|
if (funcName === 'CURRENT_TIME') {
|
|
395
|
-
if (args.length !== 0) {
|
|
396
|
-
throw argCountError({
|
|
397
|
-
funcName: 'CURRENT_TIME',
|
|
398
|
-
expected: 0,
|
|
399
|
-
received: args.length,
|
|
400
|
-
positionStart: node.positionStart,
|
|
401
|
-
positionEnd: node.positionEnd,
|
|
402
|
-
rowNumber: rowIndex,
|
|
403
|
-
})
|
|
404
|
-
}
|
|
405
294
|
return new Date().toISOString().split('T')[1].replace('Z', '')
|
|
406
295
|
}
|
|
407
296
|
|
|
408
297
|
if (funcName === 'CURRENT_TIMESTAMP') {
|
|
409
|
-
if (args.length !== 0) {
|
|
410
|
-
throw argCountError({
|
|
411
|
-
funcName: 'CURRENT_TIMESTAMP',
|
|
412
|
-
expected: 0,
|
|
413
|
-
received: args.length,
|
|
414
|
-
positionStart: node.positionStart,
|
|
415
|
-
positionEnd: node.positionEnd,
|
|
416
|
-
rowNumber: rowIndex,
|
|
417
|
-
})
|
|
418
|
-
}
|
|
419
298
|
return new Date().toISOString()
|
|
420
299
|
}
|
|
421
300
|
|
|
422
301
|
if (funcName === 'JSON_OBJECT') {
|
|
423
302
|
if (args.length % 2 !== 0) {
|
|
424
|
-
throw
|
|
303
|
+
throw argValueError({
|
|
425
304
|
funcName: 'JSON_OBJECT',
|
|
426
|
-
|
|
427
|
-
received: args.length,
|
|
305
|
+
message: 'requires an even number of arguments (key-value pairs)',
|
|
428
306
|
positionStart: node.positionStart,
|
|
429
307
|
positionEnd: node.positionEnd,
|
|
430
308
|
rowNumber: rowIndex,
|
|
@@ -451,16 +329,6 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
451
329
|
}
|
|
452
330
|
|
|
453
331
|
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY') {
|
|
454
|
-
if (args.length !== 2) {
|
|
455
|
-
throw argCountError({
|
|
456
|
-
funcName,
|
|
457
|
-
expected: 2,
|
|
458
|
-
received: args.length,
|
|
459
|
-
positionStart: node.positionStart,
|
|
460
|
-
positionEnd: node.positionEnd,
|
|
461
|
-
rowNumber: rowIndex,
|
|
462
|
-
})
|
|
463
|
-
}
|
|
464
332
|
let jsonArg = args[0]
|
|
465
333
|
const pathArg = args[1]
|
|
466
334
|
if (jsonArg == null || pathArg == null) return null
|
|
@@ -517,13 +385,15 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
517
385
|
}
|
|
518
386
|
|
|
519
387
|
if (isMathFunc(funcName)) {
|
|
520
|
-
return evaluateMathFunc({
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
388
|
+
return evaluateMathFunc({ funcName, args })
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Check user-defined functions (case-insensitive lookup)
|
|
392
|
+
if (functions) {
|
|
393
|
+
const udfName = Object.keys(functions).find(k => k.toUpperCase() === funcName)
|
|
394
|
+
if (udfName) {
|
|
395
|
+
return await functions[udfName](...args)
|
|
396
|
+
}
|
|
527
397
|
}
|
|
528
398
|
|
|
529
399
|
throw unknownFunctionError({
|
|
@@ -534,7 +404,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
534
404
|
}
|
|
535
405
|
|
|
536
406
|
if (node.type === 'cast') {
|
|
537
|
-
const val = await evaluateExpr({ node: node.expr, row, tables, rowIndex, rows })
|
|
407
|
+
const val = await evaluateExpr({ node: node.expr, row, tables, functions, rowIndex, rows })
|
|
538
408
|
if (val == null) return null
|
|
539
409
|
const toType = node.toType.toUpperCase()
|
|
540
410
|
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
@@ -577,16 +447,16 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
577
447
|
|
|
578
448
|
// IN and NOT IN with value lists
|
|
579
449
|
if (node.type === 'in valuelist') {
|
|
580
|
-
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex, rows })
|
|
450
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, tables, functions, rowIndex, rows })
|
|
581
451
|
for (const valueNode of node.values) {
|
|
582
|
-
const val = await evaluateExpr({ node: valueNode, row, tables, rowIndex, rows })
|
|
452
|
+
const val = await evaluateExpr({ node: valueNode, row, tables, functions, rowIndex, rows })
|
|
583
453
|
if (exprVal === val) return true
|
|
584
454
|
}
|
|
585
455
|
return false
|
|
586
456
|
}
|
|
587
457
|
// IN with subqueries
|
|
588
458
|
if (node.type === 'in') {
|
|
589
|
-
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex, rows })
|
|
459
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, tables, functions, rowIndex, rows })
|
|
590
460
|
const results = executeSelect({ select: node.subquery, tables })
|
|
591
461
|
for await (const resRow of results) {
|
|
592
462
|
const value = await resRow.cells[resRow.columns[0]]()
|
|
@@ -608,28 +478,28 @@ export async function evaluateExpr({ node, row, tables, rowIndex, rows }) {
|
|
|
608
478
|
// CASE expressions
|
|
609
479
|
if (node.type === 'case') {
|
|
610
480
|
// For simple CASE: evaluate the case expression once
|
|
611
|
-
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, rowIndex, rows })
|
|
481
|
+
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, functions, rowIndex, rows })
|
|
612
482
|
|
|
613
483
|
// Iterate through WHEN clauses
|
|
614
484
|
for (const whenClause of node.whenClauses) {
|
|
615
485
|
let conditionResult
|
|
616
486
|
if (caseValue !== undefined) {
|
|
617
487
|
// Simple CASE: compare caseValue with condition
|
|
618
|
-
const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex, rows })
|
|
488
|
+
const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, functions, rowIndex, rows })
|
|
619
489
|
conditionResult = caseValue === whenValue
|
|
620
490
|
} else {
|
|
621
491
|
// Searched CASE: evaluate condition as boolean
|
|
622
|
-
conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex, rows })
|
|
492
|
+
conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, functions, rowIndex, rows })
|
|
623
493
|
}
|
|
624
494
|
|
|
625
495
|
if (conditionResult) {
|
|
626
|
-
return evaluateExpr({ node: whenClause.result, row, tables, rowIndex, rows })
|
|
496
|
+
return evaluateExpr({ node: whenClause.result, row, tables, functions, rowIndex, rows })
|
|
627
497
|
}
|
|
628
498
|
}
|
|
629
499
|
|
|
630
500
|
// No WHEN clause matched, return ELSE result or NULL
|
|
631
501
|
if (node.elseResult) {
|
|
632
|
-
return evaluateExpr({ node: node.elseResult, row, tables, rowIndex, rows })
|
|
502
|
+
return evaluateExpr({ node: node.elseResult, row, tables, functions, rowIndex, rows })
|
|
633
503
|
}
|
|
634
504
|
return null
|
|
635
505
|
}
|
package/src/execute/having.js
CHANGED
|
@@ -4,19 +4,21 @@ import { evaluateExpr } from './expression.js'
|
|
|
4
4
|
import { applyBinaryOp } from './utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @import { AggregateFunc, AsyncDataSource, ExprNode, AsyncRow, SqlPrimitive } from '../types.js'
|
|
7
|
+
* @import { AggregateFunc, AsyncDataSource, ExprNode, AsyncRow, SqlPrimitive, UserDefinedFunction } from '../types.js'
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Evaluates a HAVING expression with support for aggregate functions
|
|
12
12
|
*
|
|
13
|
-
* @param {
|
|
14
|
-
* @param {
|
|
15
|
-
* @param {AsyncRow
|
|
16
|
-
* @param {
|
|
13
|
+
* @param {Object} options
|
|
14
|
+
* @param {ExprNode} options.expr - the HAVING expression
|
|
15
|
+
* @param {AsyncRow} options.row - the aggregated result row
|
|
16
|
+
* @param {AsyncRow[]} options.group - the group of rows for re-evaluating aggregates
|
|
17
|
+
* @param {Record<string, AsyncDataSource>} options.tables
|
|
18
|
+
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
17
19
|
* @returns {Promise<boolean>} whether the HAVING condition is satisfied
|
|
18
20
|
*/
|
|
19
|
-
export async function evaluateHavingExpr(expr, row, group, tables) {
|
|
21
|
+
export async function evaluateHavingExpr({ expr, row, group, tables, functions }) {
|
|
20
22
|
// Having context
|
|
21
23
|
const context = { ...group[0] ?? {}, ...row }
|
|
22
24
|
|
|
@@ -26,12 +28,12 @@ export async function evaluateHavingExpr(expr, row, group, tables) {
|
|
|
26
28
|
const funcName = expr.name.toUpperCase()
|
|
27
29
|
if (isAggregateFunc(funcName)) {
|
|
28
30
|
// Evaluate aggregate function on the group
|
|
29
|
-
return Boolean(await evaluateAggregateFunction(funcName, expr.args, group, tables))
|
|
31
|
+
return Boolean(await evaluateAggregateFunction({ funcName, args: expr.args, group, tables, functions }))
|
|
30
32
|
}
|
|
31
33
|
}
|
|
32
34
|
|
|
33
35
|
if (expr.type === 'binary') {
|
|
34
|
-
const left = await evaluateHavingValue(expr.left, context, group, tables)
|
|
36
|
+
const left = await evaluateHavingValue({ expr: expr.left, context, group, tables, functions })
|
|
35
37
|
|
|
36
38
|
// Short-circuit evaluation for AND and OR
|
|
37
39
|
if (expr.op === 'AND') {
|
|
@@ -41,61 +43,65 @@ export async function evaluateHavingExpr(expr, row, group, tables) {
|
|
|
41
43
|
if (left) return true
|
|
42
44
|
}
|
|
43
45
|
|
|
44
|
-
const right = await evaluateHavingValue(expr.right, context, group, tables)
|
|
46
|
+
const right = await evaluateHavingValue({ expr: expr.right, context, group, tables, functions })
|
|
45
47
|
return Boolean(applyBinaryOp(expr.op, left, right))
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
if (expr.type === 'unary') {
|
|
49
51
|
if (expr.op === 'NOT') {
|
|
50
|
-
return !await evaluateHavingExpr(expr.argument, context, group, tables)
|
|
52
|
+
return !await evaluateHavingExpr({ expr: expr.argument, row: context, group, tables, functions })
|
|
51
53
|
}
|
|
52
54
|
if (expr.op === 'IS NULL') {
|
|
53
|
-
return await evaluateHavingValue(expr.argument, context, group, tables) == null
|
|
55
|
+
return await evaluateHavingValue({ expr: expr.argument, context, group, tables, functions }) == null
|
|
54
56
|
}
|
|
55
57
|
if (expr.op === 'IS NOT NULL') {
|
|
56
|
-
return await evaluateHavingValue(expr.argument, context, group, tables) != null
|
|
58
|
+
return await evaluateHavingValue({ expr: expr.argument, context, group, tables, functions }) != null
|
|
57
59
|
}
|
|
58
60
|
}
|
|
59
61
|
|
|
60
62
|
// For other expression types, use the context row
|
|
61
|
-
return Boolean(await evaluateExpr({ node: expr, row: context, tables }))
|
|
63
|
+
return Boolean(await evaluateExpr({ node: expr, row: context, tables, functions }))
|
|
62
64
|
}
|
|
63
65
|
|
|
64
66
|
/**
|
|
65
67
|
* Evaluates a value in a HAVING expression
|
|
66
68
|
*
|
|
67
|
-
* @param {
|
|
68
|
-
* @param {
|
|
69
|
-
* @param {AsyncRow
|
|
70
|
-
* @param {
|
|
69
|
+
* @param {Object} options
|
|
70
|
+
* @param {ExprNode} options.expr
|
|
71
|
+
* @param {AsyncRow} options.context - the context row
|
|
72
|
+
* @param {AsyncRow[]} options.group - the group of rows
|
|
73
|
+
* @param {Record<string, AsyncDataSource>} options.tables
|
|
74
|
+
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
71
75
|
* @returns {Promise<SqlPrimitive>} the evaluated value
|
|
72
76
|
*/
|
|
73
|
-
function evaluateHavingValue(expr, context, group, tables) {
|
|
77
|
+
function evaluateHavingValue({ expr, context, group, tables, functions }) {
|
|
74
78
|
if (expr.type === 'function') {
|
|
75
79
|
const funcName = expr.name.toUpperCase()
|
|
76
80
|
if (isAggregateFunc(funcName)) {
|
|
77
|
-
return evaluateAggregateFunction(funcName, expr.args, group, tables)
|
|
81
|
+
return evaluateAggregateFunction({ funcName, args: expr.args, group, tables, functions })
|
|
78
82
|
}
|
|
79
83
|
}
|
|
80
84
|
|
|
81
85
|
// For binary expressions, we need to use evaluateHavingExpr to properly handle aggregates
|
|
82
86
|
if (expr.type === 'binary' || expr.type === 'unary') {
|
|
83
|
-
return evaluateHavingExpr(expr, context, group, tables)
|
|
87
|
+
return evaluateHavingExpr({ expr, row: context, group, tables, functions })
|
|
84
88
|
}
|
|
85
89
|
|
|
86
|
-
return evaluateExpr({ node: expr, row: context, tables })
|
|
90
|
+
return evaluateExpr({ node: expr, row: context, tables, functions })
|
|
87
91
|
}
|
|
88
92
|
|
|
89
93
|
/**
|
|
90
94
|
* Evaluates an aggregate function on a group
|
|
91
95
|
*
|
|
92
|
-
* @param {
|
|
93
|
-
* @param {
|
|
94
|
-
* @param {
|
|
95
|
-
* @param {
|
|
96
|
+
* @param {Object} options
|
|
97
|
+
* @param {AggregateFunc} options.funcName - aggregate function name
|
|
98
|
+
* @param {ExprNode[]} options.args - function arguments
|
|
99
|
+
* @param {AsyncRow[]} options.group - the group of rows
|
|
100
|
+
* @param {Record<string, AsyncDataSource>} options.tables
|
|
101
|
+
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
96
102
|
* @returns {Promise<SqlPrimitive>} the aggregate result
|
|
97
103
|
*/
|
|
98
|
-
async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
104
|
+
async function evaluateAggregateFunction({ funcName, args, group, tables, functions }) {
|
|
99
105
|
if (funcName === 'COUNT') {
|
|
100
106
|
if (args.length === 1 && args[0].type === 'identifier' && args[0].name === '*') {
|
|
101
107
|
return group.length
|
|
@@ -103,7 +109,7 @@ async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
|
103
109
|
// COUNT(column) - count non-null values
|
|
104
110
|
let count = 0
|
|
105
111
|
for (const row of group) {
|
|
106
|
-
const val = await evaluateExpr({ node: args[0], row, tables })
|
|
112
|
+
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
107
113
|
if (val != null) count++
|
|
108
114
|
}
|
|
109
115
|
return count
|
|
@@ -112,7 +118,7 @@ async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
|
112
118
|
if (funcName === 'SUM') {
|
|
113
119
|
let sum = 0
|
|
114
120
|
for (const row of group) {
|
|
115
|
-
const val = await evaluateExpr({ node: args[0], row, tables })
|
|
121
|
+
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
116
122
|
if (val != null) sum += Number(val)
|
|
117
123
|
}
|
|
118
124
|
return sum
|
|
@@ -122,7 +128,7 @@ async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
|
122
128
|
let sum = 0
|
|
123
129
|
let count = 0
|
|
124
130
|
for (const row of group) {
|
|
125
|
-
const val = await evaluateExpr({ node: args[0], row, tables })
|
|
131
|
+
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
126
132
|
if (val != null) {
|
|
127
133
|
sum += Number(val)
|
|
128
134
|
count++
|
|
@@ -134,7 +140,7 @@ async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
|
134
140
|
if (funcName === 'MIN') {
|
|
135
141
|
let min = null
|
|
136
142
|
for (const row of group) {
|
|
137
|
-
const val = await evaluateExpr({ node: args[0], row, tables })
|
|
143
|
+
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
138
144
|
if (val != null && (min == null || val < min)) {
|
|
139
145
|
min = val
|
|
140
146
|
}
|
|
@@ -145,7 +151,7 @@ async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
|
145
151
|
if (funcName === 'MAX') {
|
|
146
152
|
let max = null
|
|
147
153
|
for (const row of group) {
|
|
148
|
-
const val = await evaluateExpr({ node: args[0], row, tables })
|
|
154
|
+
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
149
155
|
if (val != null && (max == null || val > max)) {
|
|
150
156
|
max = val
|
|
151
157
|
}
|