squirreling 0.6.0 → 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 +5 -5
- package/src/backend/dataSource.js +13 -7
- package/src/execute/columns.js +39 -5
- package/src/execute/execute.js +64 -62
- package/src/execute/expression.js +133 -156
- package/src/execute/having.js +37 -31
- package/src/execute/join.js +42 -29
- package/src/execute/math.js +10 -209
- package/src/execute/utils.js +4 -0
- package/src/index.d.ts +3 -2
- package/src/parse/expression.js +33 -5
- package/src/parse/parse.js +6 -55
- package/src/parseErrors.js +29 -0
- package/src/types.d.ts +34 -41
- package/src/validation.js +121 -1
- package/src/validationErrors.js +1 -25
- package/src/execute/aggregates.js +0 -119
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import { unknownFunctionError } from '../parseErrors.js'
|
|
2
2
|
import { invalidContextError } from '../executionErrors.js'
|
|
3
3
|
import {
|
|
4
|
-
|
|
4
|
+
aggregateError,
|
|
5
5
|
argValueError,
|
|
6
6
|
castError,
|
|
7
7
|
} from '../validationErrors.js'
|
|
8
|
-
import { isMathFunc } from '../validation.js'
|
|
8
|
+
import { isAggregateFunc, isMathFunc } from '../validation.js'
|
|
9
9
|
import { applyIntervalToDate } from './date.js'
|
|
10
10
|
import { executeSelect } from './execute.js'
|
|
11
11
|
import { evaluateMathFunc } from './math.js'
|
|
12
12
|
import { applyBinaryOp, stringify } from './utils.js'
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* @import { ExprNode, AsyncRow, SqlPrimitive, AsyncDataSource, IntervalUnit } from '../types.js'
|
|
15
|
+
* @import { ExprNode, AsyncRow, SqlPrimitive, AsyncDataSource, IntervalUnit, UserDefinedFunction } from '../types.js'
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
/**
|
|
@@ -22,10 +22,12 @@ import { applyBinaryOp, stringify } from './utils.js'
|
|
|
22
22
|
* @param {ExprNode} params.node - The expression node to evaluate
|
|
23
23
|
* @param {AsyncRow} params.row - The data row to evaluate against
|
|
24
24
|
* @param {Record<string, AsyncDataSource>} params.tables
|
|
25
|
+
* @param {Record<string, UserDefinedFunction>} [params.functions] - User-defined functions
|
|
25
26
|
* @param {number} [params.rowIndex] - 1-based row index for error reporting
|
|
27
|
+
* @param {AsyncRow[]} [params.rows] - Group of rows for aggregate functions
|
|
26
28
|
* @returns {Promise<SqlPrimitive>} The result of the evaluation
|
|
27
29
|
*/
|
|
28
|
-
export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
30
|
+
export async function evaluateExpr({ node, row, tables, functions, rowIndex, rows }) {
|
|
29
31
|
if (node.type === 'literal') {
|
|
30
32
|
return node.value
|
|
31
33
|
}
|
|
@@ -47,7 +49,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
47
49
|
|
|
48
50
|
// Scalar subquery - returns a single value
|
|
49
51
|
if (node.type === 'subquery') {
|
|
50
|
-
const gen = executeSelect(node.subquery, tables)
|
|
52
|
+
const gen = executeSelect({ select: node.subquery, tables })
|
|
51
53
|
const { value } = await gen.next() // Start the generator
|
|
52
54
|
gen.return(undefined) // Stop further execution
|
|
53
55
|
if (!value) return null
|
|
@@ -57,16 +59,16 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
57
59
|
// Unary operators
|
|
58
60
|
if (node.type === 'unary') {
|
|
59
61
|
if (node.op === 'NOT') {
|
|
60
|
-
return !await evaluateExpr({ node: node.argument, row, tables, rowIndex })
|
|
62
|
+
return !await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows })
|
|
61
63
|
}
|
|
62
64
|
if (node.op === 'IS NULL') {
|
|
63
|
-
return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) == null
|
|
65
|
+
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows }) == null
|
|
64
66
|
}
|
|
65
67
|
if (node.op === 'IS NOT NULL') {
|
|
66
|
-
return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) != null
|
|
68
|
+
return await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows }) != null
|
|
67
69
|
}
|
|
68
70
|
if (node.op === '-') {
|
|
69
|
-
const val = await evaluateExpr({ node: node.argument, row, tables, rowIndex })
|
|
71
|
+
const val = await evaluateExpr({ node: node.argument, row, tables, functions, rowIndex, rows })
|
|
70
72
|
if (val == null) return null
|
|
71
73
|
return -val
|
|
72
74
|
}
|
|
@@ -76,15 +78,15 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
76
78
|
if (node.type === 'binary') {
|
|
77
79
|
// Handle date +/- interval at AST level
|
|
78
80
|
if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
|
|
79
|
-
const dateVal = await evaluateExpr({ node: node.left, row, tables, rowIndex })
|
|
81
|
+
const dateVal = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows })
|
|
80
82
|
return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
|
|
81
83
|
}
|
|
82
84
|
if (node.op === '+' && node.left.type === 'interval') {
|
|
83
|
-
const dateVal = await evaluateExpr({ node: node.right, row, tables, rowIndex })
|
|
85
|
+
const dateVal = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows })
|
|
84
86
|
return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
|
|
85
87
|
}
|
|
86
88
|
|
|
87
|
-
const left = await evaluateExpr({ node: node.left, row, tables, rowIndex })
|
|
89
|
+
const left = await evaluateExpr({ node: node.left, row, tables, functions, rowIndex, rows })
|
|
88
90
|
|
|
89
91
|
// Short-circuit evaluation for AND and OR
|
|
90
92
|
if (node.op === 'AND') {
|
|
@@ -94,59 +96,123 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
94
96
|
if (left) return true
|
|
95
97
|
}
|
|
96
98
|
|
|
97
|
-
const right = await evaluateExpr({ node: node.right, row, tables, rowIndex })
|
|
99
|
+
const right = await evaluateExpr({ node: node.right, row, tables, functions, rowIndex, rows })
|
|
98
100
|
return applyBinaryOp(node.op, left, right)
|
|
99
101
|
}
|
|
100
102
|
|
|
101
103
|
// Function calls
|
|
102
104
|
if (node.type === 'function') {
|
|
103
105
|
const funcName = node.name.toUpperCase()
|
|
104
|
-
/** @type {SqlPrimitive[]} */
|
|
105
|
-
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, rowIndex })))
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
positionStart: node.positionStart,
|
|
114
|
-
positionEnd: node.positionEnd,
|
|
115
|
-
rowNumber: rowIndex,
|
|
107
|
+
// Handle aggregate functions
|
|
108
|
+
if (isAggregateFunc(funcName)) {
|
|
109
|
+
if (!rows) {
|
|
110
|
+
throw aggregateError({
|
|
111
|
+
funcName,
|
|
112
|
+
issue: 'requires GROUP BY or will act on the whole dataset',
|
|
116
113
|
})
|
|
117
114
|
}
|
|
115
|
+
|
|
116
|
+
// Check for star argument (COUNT(*))
|
|
117
|
+
if (node.args.length === 1 && node.args[0].type === 'identifier' && node.args[0].name === '*') {
|
|
118
|
+
if (funcName === 'COUNT') {
|
|
119
|
+
return rows.length
|
|
120
|
+
}
|
|
121
|
+
throw aggregateError({
|
|
122
|
+
funcName,
|
|
123
|
+
issue: '(*) is not supported, use a column name',
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const argNode = node.args[0]
|
|
128
|
+
|
|
129
|
+
if (funcName === 'COUNT') {
|
|
130
|
+
if (node.distinct) {
|
|
131
|
+
const seen = new Set()
|
|
132
|
+
for (const r of rows) {
|
|
133
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
134
|
+
if (v != null) seen.add(v)
|
|
135
|
+
}
|
|
136
|
+
return seen.size
|
|
137
|
+
}
|
|
138
|
+
let count = 0
|
|
139
|
+
for (const r of rows) {
|
|
140
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
141
|
+
if (v != null) count++
|
|
142
|
+
}
|
|
143
|
+
return count
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (funcName === 'SUM' || funcName === 'AVG' || funcName === 'MIN' || funcName === 'MAX') {
|
|
147
|
+
let sum = 0
|
|
148
|
+
let count = 0
|
|
149
|
+
/** @type {number | null} */
|
|
150
|
+
let min = null
|
|
151
|
+
/** @type {number | null} */
|
|
152
|
+
let max = null
|
|
153
|
+
|
|
154
|
+
for (const r of rows) {
|
|
155
|
+
const raw = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
156
|
+
if (raw == null) continue
|
|
157
|
+
const num = Number(raw)
|
|
158
|
+
if (!Number.isFinite(num)) continue
|
|
159
|
+
|
|
160
|
+
if (count === 0) {
|
|
161
|
+
min = num
|
|
162
|
+
max = num
|
|
163
|
+
} else {
|
|
164
|
+
if (min == null || num < min) min = num
|
|
165
|
+
if (max == null || num > max) max = num
|
|
166
|
+
}
|
|
167
|
+
sum += num
|
|
168
|
+
count++
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
if (funcName === 'SUM') return sum
|
|
172
|
+
if (funcName === 'AVG') return count === 0 ? null : sum / count
|
|
173
|
+
if (funcName === 'MIN') return min
|
|
174
|
+
if (funcName === 'MAX') return max
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (funcName === 'JSON_ARRAYAGG') {
|
|
178
|
+
/** @type {SqlPrimitive[]} */
|
|
179
|
+
const values = []
|
|
180
|
+
if (node.distinct) {
|
|
181
|
+
const seen = new Set()
|
|
182
|
+
for (const r of rows) {
|
|
183
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
184
|
+
const key = stringify(v)
|
|
185
|
+
if (!seen.has(key)) {
|
|
186
|
+
seen.add(key)
|
|
187
|
+
values.push(v)
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
} else {
|
|
191
|
+
for (const r of rows) {
|
|
192
|
+
const v = await evaluateExpr({ node: argNode, row: r, tables, functions })
|
|
193
|
+
values.push(v)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return values
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** @type {SqlPrimitive[]} */
|
|
201
|
+
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, functions, rowIndex, rows })))
|
|
202
|
+
|
|
203
|
+
if (funcName === 'UPPER') {
|
|
118
204
|
const val = args[0]
|
|
119
205
|
if (val == null) return null
|
|
120
206
|
return String(val).toUpperCase()
|
|
121
207
|
}
|
|
122
208
|
|
|
123
209
|
if (funcName === 'LOWER') {
|
|
124
|
-
if (args.length !== 1) {
|
|
125
|
-
throw argCountError({
|
|
126
|
-
funcName: 'LOWER',
|
|
127
|
-
expected: 1,
|
|
128
|
-
received: args.length,
|
|
129
|
-
positionStart: node.positionStart,
|
|
130
|
-
positionEnd: node.positionEnd,
|
|
131
|
-
rowNumber: rowIndex,
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
210
|
const val = args[0]
|
|
135
211
|
if (val == null) return null
|
|
136
212
|
return String(val).toLowerCase()
|
|
137
213
|
}
|
|
138
214
|
|
|
139
215
|
if (funcName === 'CONCAT') {
|
|
140
|
-
if (args.length < 1) {
|
|
141
|
-
throw argCountError({
|
|
142
|
-
funcName: 'CONCAT',
|
|
143
|
-
expected: 'at least 1',
|
|
144
|
-
received: args.length,
|
|
145
|
-
positionStart: node.positionStart,
|
|
146
|
-
positionEnd: node.positionEnd,
|
|
147
|
-
rowNumber: rowIndex,
|
|
148
|
-
})
|
|
149
|
-
}
|
|
150
216
|
// SQL CONCAT returns NULL if any argument is NULL
|
|
151
217
|
if (args.some(a => a == null)) return null
|
|
152
218
|
if (args.some(a => typeof a === 'object')) {
|
|
@@ -163,32 +229,12 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
163
229
|
}
|
|
164
230
|
|
|
165
231
|
if (funcName === 'LENGTH') {
|
|
166
|
-
if (args.length !== 1) {
|
|
167
|
-
throw argCountError({
|
|
168
|
-
funcName: 'LENGTH',
|
|
169
|
-
expected: 1,
|
|
170
|
-
received: args.length,
|
|
171
|
-
positionStart: node.positionStart,
|
|
172
|
-
positionEnd: node.positionEnd,
|
|
173
|
-
rowNumber: rowIndex,
|
|
174
|
-
})
|
|
175
|
-
}
|
|
176
232
|
const val = args[0]
|
|
177
233
|
if (val == null) return null
|
|
178
234
|
return String(val).length
|
|
179
235
|
}
|
|
180
236
|
|
|
181
237
|
if (funcName === 'SUBSTRING' || funcName === 'SUBSTR') {
|
|
182
|
-
if (args.length < 2 || args.length > 3) {
|
|
183
|
-
throw argCountError({
|
|
184
|
-
funcName,
|
|
185
|
-
expected: '2 or 3',
|
|
186
|
-
received: args.length,
|
|
187
|
-
positionStart: node.positionStart,
|
|
188
|
-
positionEnd: node.positionEnd,
|
|
189
|
-
rowNumber: rowIndex,
|
|
190
|
-
})
|
|
191
|
-
}
|
|
192
238
|
const str = args[0]
|
|
193
239
|
if (str == null) return null
|
|
194
240
|
const strVal = String(str)
|
|
@@ -222,32 +268,12 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
222
268
|
}
|
|
223
269
|
|
|
224
270
|
if (funcName === 'TRIM') {
|
|
225
|
-
if (args.length !== 1) {
|
|
226
|
-
throw argCountError({
|
|
227
|
-
funcName: 'TRIM',
|
|
228
|
-
expected: 1,
|
|
229
|
-
received: args.length,
|
|
230
|
-
positionStart: node.positionStart,
|
|
231
|
-
positionEnd: node.positionEnd,
|
|
232
|
-
rowNumber: rowIndex,
|
|
233
|
-
})
|
|
234
|
-
}
|
|
235
271
|
const val = args[0]
|
|
236
272
|
if (val == null) return null
|
|
237
273
|
return String(val).trim()
|
|
238
274
|
}
|
|
239
275
|
|
|
240
276
|
if (funcName === 'REPLACE') {
|
|
241
|
-
if (args.length !== 3) {
|
|
242
|
-
throw argCountError({
|
|
243
|
-
funcName: 'REPLACE',
|
|
244
|
-
expected: 3,
|
|
245
|
-
received: args.length,
|
|
246
|
-
positionStart: node.positionStart,
|
|
247
|
-
positionEnd: node.positionEnd,
|
|
248
|
-
rowNumber: rowIndex,
|
|
249
|
-
})
|
|
250
|
-
}
|
|
251
277
|
const str = args[0]
|
|
252
278
|
const searchStr = args[1]
|
|
253
279
|
const replaceStr = args[2]
|
|
@@ -257,67 +283,26 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
257
283
|
}
|
|
258
284
|
|
|
259
285
|
if (funcName === 'RANDOM' || funcName === 'RAND') {
|
|
260
|
-
if (args.length !== 0) {
|
|
261
|
-
throw argCountError({
|
|
262
|
-
funcName,
|
|
263
|
-
expected: 0,
|
|
264
|
-
received: args.length,
|
|
265
|
-
positionStart: node.positionStart,
|
|
266
|
-
positionEnd: node.positionEnd,
|
|
267
|
-
rowNumber: rowIndex,
|
|
268
|
-
})
|
|
269
|
-
}
|
|
270
286
|
return Math.random()
|
|
271
287
|
}
|
|
272
288
|
|
|
273
289
|
if (funcName === 'CURRENT_DATE') {
|
|
274
|
-
if (args.length !== 0) {
|
|
275
|
-
throw argCountError({
|
|
276
|
-
funcName: 'CURRENT_DATE',
|
|
277
|
-
expected: 0,
|
|
278
|
-
received: args.length,
|
|
279
|
-
positionStart: node.positionStart,
|
|
280
|
-
positionEnd: node.positionEnd,
|
|
281
|
-
rowNumber: rowIndex,
|
|
282
|
-
})
|
|
283
|
-
}
|
|
284
290
|
return new Date().toISOString().split('T')[0]
|
|
285
291
|
}
|
|
286
292
|
|
|
287
293
|
if (funcName === 'CURRENT_TIME') {
|
|
288
|
-
if (args.length !== 0) {
|
|
289
|
-
throw argCountError({
|
|
290
|
-
funcName: 'CURRENT_TIME',
|
|
291
|
-
expected: 0,
|
|
292
|
-
received: args.length,
|
|
293
|
-
positionStart: node.positionStart,
|
|
294
|
-
positionEnd: node.positionEnd,
|
|
295
|
-
rowNumber: rowIndex,
|
|
296
|
-
})
|
|
297
|
-
}
|
|
298
294
|
return new Date().toISOString().split('T')[1].replace('Z', '')
|
|
299
295
|
}
|
|
300
296
|
|
|
301
297
|
if (funcName === 'CURRENT_TIMESTAMP') {
|
|
302
|
-
if (args.length !== 0) {
|
|
303
|
-
throw argCountError({
|
|
304
|
-
funcName: 'CURRENT_TIMESTAMP',
|
|
305
|
-
expected: 0,
|
|
306
|
-
received: args.length,
|
|
307
|
-
positionStart: node.positionStart,
|
|
308
|
-
positionEnd: node.positionEnd,
|
|
309
|
-
rowNumber: rowIndex,
|
|
310
|
-
})
|
|
311
|
-
}
|
|
312
298
|
return new Date().toISOString()
|
|
313
299
|
}
|
|
314
300
|
|
|
315
301
|
if (funcName === 'JSON_OBJECT') {
|
|
316
302
|
if (args.length % 2 !== 0) {
|
|
317
|
-
throw
|
|
303
|
+
throw argValueError({
|
|
318
304
|
funcName: 'JSON_OBJECT',
|
|
319
|
-
|
|
320
|
-
received: args.length,
|
|
305
|
+
message: 'requires an even number of arguments (key-value pairs)',
|
|
321
306
|
positionStart: node.positionStart,
|
|
322
307
|
positionEnd: node.positionEnd,
|
|
323
308
|
rowNumber: rowIndex,
|
|
@@ -344,16 +329,6 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
344
329
|
}
|
|
345
330
|
|
|
346
331
|
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY') {
|
|
347
|
-
if (args.length !== 2) {
|
|
348
|
-
throw argCountError({
|
|
349
|
-
funcName,
|
|
350
|
-
expected: 2,
|
|
351
|
-
received: args.length,
|
|
352
|
-
positionStart: node.positionStart,
|
|
353
|
-
positionEnd: node.positionEnd,
|
|
354
|
-
rowNumber: rowIndex,
|
|
355
|
-
})
|
|
356
|
-
}
|
|
357
332
|
let jsonArg = args[0]
|
|
358
333
|
const pathArg = args[1]
|
|
359
334
|
if (jsonArg == null || pathArg == null) return null
|
|
@@ -410,13 +385,15 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
410
385
|
}
|
|
411
386
|
|
|
412
387
|
if (isMathFunc(funcName)) {
|
|
413
|
-
return evaluateMathFunc({
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
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
|
+
}
|
|
420
397
|
}
|
|
421
398
|
|
|
422
399
|
throw unknownFunctionError({
|
|
@@ -427,7 +404,7 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
427
404
|
}
|
|
428
405
|
|
|
429
406
|
if (node.type === 'cast') {
|
|
430
|
-
const val = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
407
|
+
const val = await evaluateExpr({ node: node.expr, row, tables, functions, rowIndex, rows })
|
|
431
408
|
if (val == null) return null
|
|
432
409
|
const toType = node.toType.toUpperCase()
|
|
433
410
|
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
@@ -470,17 +447,17 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
470
447
|
|
|
471
448
|
// IN and NOT IN with value lists
|
|
472
449
|
if (node.type === 'in valuelist') {
|
|
473
|
-
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
450
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, tables, functions, rowIndex, rows })
|
|
474
451
|
for (const valueNode of node.values) {
|
|
475
|
-
const val = await evaluateExpr({ node: valueNode, row, tables, rowIndex })
|
|
452
|
+
const val = await evaluateExpr({ node: valueNode, row, tables, functions, rowIndex, rows })
|
|
476
453
|
if (exprVal === val) return true
|
|
477
454
|
}
|
|
478
455
|
return false
|
|
479
456
|
}
|
|
480
457
|
// IN with subqueries
|
|
481
458
|
if (node.type === 'in') {
|
|
482
|
-
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
483
|
-
const results = executeSelect(node.subquery, tables)
|
|
459
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, tables, functions, rowIndex, rows })
|
|
460
|
+
const results = executeSelect({ select: node.subquery, tables })
|
|
484
461
|
for await (const resRow of results) {
|
|
485
462
|
const value = await resRow.cells[resRow.columns[0]]()
|
|
486
463
|
if (exprVal === value) return true
|
|
@@ -490,39 +467,39 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
490
467
|
|
|
491
468
|
// EXISTS and NOT EXISTS with subqueries
|
|
492
469
|
if (node.type === 'exists') {
|
|
493
|
-
const results = await executeSelect(node.subquery, tables).next()
|
|
470
|
+
const results = await executeSelect({ select: node.subquery, tables }).next()
|
|
494
471
|
return results.done === false
|
|
495
472
|
}
|
|
496
473
|
if (node.type === 'not exists') {
|
|
497
|
-
const results = await executeSelect(node.subquery, tables).next()
|
|
474
|
+
const results = await executeSelect({ select: node.subquery, tables }).next()
|
|
498
475
|
return results.done === true
|
|
499
476
|
}
|
|
500
477
|
|
|
501
478
|
// CASE expressions
|
|
502
479
|
if (node.type === 'case') {
|
|
503
480
|
// For simple CASE: evaluate the case expression once
|
|
504
|
-
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, rowIndex })
|
|
481
|
+
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, functions, rowIndex, rows })
|
|
505
482
|
|
|
506
483
|
// Iterate through WHEN clauses
|
|
507
484
|
for (const whenClause of node.whenClauses) {
|
|
508
485
|
let conditionResult
|
|
509
486
|
if (caseValue !== undefined) {
|
|
510
487
|
// Simple CASE: compare caseValue with condition
|
|
511
|
-
const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
|
|
488
|
+
const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, functions, rowIndex, rows })
|
|
512
489
|
conditionResult = caseValue === whenValue
|
|
513
490
|
} else {
|
|
514
491
|
// Searched CASE: evaluate condition as boolean
|
|
515
|
-
conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
|
|
492
|
+
conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, functions, rowIndex, rows })
|
|
516
493
|
}
|
|
517
494
|
|
|
518
495
|
if (conditionResult) {
|
|
519
|
-
return evaluateExpr({ node: whenClause.result, row, tables, rowIndex })
|
|
496
|
+
return evaluateExpr({ node: whenClause.result, row, tables, functions, rowIndex, rows })
|
|
520
497
|
}
|
|
521
498
|
}
|
|
522
499
|
|
|
523
500
|
// No WHEN clause matched, return ELSE result or NULL
|
|
524
501
|
if (node.elseResult) {
|
|
525
|
-
return evaluateExpr({ node: node.elseResult, row, tables, rowIndex })
|
|
502
|
+
return evaluateExpr({ node: node.elseResult, row, tables, functions, rowIndex, rows })
|
|
526
503
|
}
|
|
527
504
|
return null
|
|
528
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
|
}
|