squirreling 0.4.8 → 0.6.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 +5 -1
- package/package.json +3 -3
- package/src/backend/dataSource.js +15 -14
- package/src/execute/aggregates.js +10 -4
- package/src/execute/execute.js +58 -40
- package/src/execute/expression.js +212 -54
- package/src/execute/having.js +7 -2
- package/src/execute/join.js +35 -34
- package/src/execute/math.js +340 -0
- package/src/execute/utils.js +2 -2
- package/src/executionErrors.js +63 -0
- package/src/index.js +1 -0
- package/src/parse/comparison.js +41 -8
- package/src/parse/expression.js +53 -13
- package/src/parse/state.js +13 -3
- package/src/parse/tokenize.js +34 -21
- package/src/parseErrors.js +118 -0
- package/src/types.d.ts +55 -16
- package/src/validation.js +14 -1
- package/src/validationErrors.js +138 -0
- package/src/errors.js +0 -230
|
@@ -1,12 +1,14 @@
|
|
|
1
|
+
import { unknownFunctionError } from '../parseErrors.js'
|
|
2
|
+
import { invalidContextError } from '../executionErrors.js'
|
|
1
3
|
import {
|
|
2
4
|
argCountError,
|
|
3
5
|
argValueError,
|
|
4
6
|
castError,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from '../errors.js'
|
|
7
|
+
} from '../validationErrors.js'
|
|
8
|
+
import { isMathFunc } from '../validation.js'
|
|
8
9
|
import { applyIntervalToDate } from './date.js'
|
|
9
10
|
import { executeSelect } from './execute.js'
|
|
11
|
+
import { evaluateMathFunc } from './math.js'
|
|
10
12
|
import { applyBinaryOp, stringify } from './utils.js'
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -20,23 +22,24 @@ import { applyBinaryOp, stringify } from './utils.js'
|
|
|
20
22
|
* @param {ExprNode} params.node - The expression node to evaluate
|
|
21
23
|
* @param {AsyncRow} params.row - The data row to evaluate against
|
|
22
24
|
* @param {Record<string, AsyncDataSource>} params.tables
|
|
25
|
+
* @param {number} [params.rowIndex] - 1-based row index for error reporting
|
|
23
26
|
* @returns {Promise<SqlPrimitive>} The result of the evaluation
|
|
24
27
|
*/
|
|
25
|
-
export async function evaluateExpr({ node, row, tables }) {
|
|
28
|
+
export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
26
29
|
if (node.type === 'literal') {
|
|
27
30
|
return node.value
|
|
28
31
|
}
|
|
29
32
|
|
|
30
33
|
if (node.type === 'identifier') {
|
|
31
34
|
// Try exact match first (handles both qualified and unqualified names)
|
|
32
|
-
if (row[node.name]) {
|
|
33
|
-
return row[node.name]()
|
|
35
|
+
if (row.cells[node.name]) {
|
|
36
|
+
return row.cells[node.name]()
|
|
34
37
|
}
|
|
35
38
|
// For qualified names like 'users.id', also try just the column part
|
|
36
39
|
if (node.name.includes('.')) {
|
|
37
40
|
const colName = node.name.split('.').pop()
|
|
38
|
-
if (colName && row[colName]) {
|
|
39
|
-
return row[colName]()
|
|
41
|
+
if (colName && row.cells[colName]) {
|
|
42
|
+
return row.cells[colName]()
|
|
40
43
|
}
|
|
41
44
|
}
|
|
42
45
|
return null
|
|
@@ -45,28 +48,25 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
45
48
|
// Scalar subquery - returns a single value
|
|
46
49
|
if (node.type === 'subquery') {
|
|
47
50
|
const gen = executeSelect(node.subquery, tables)
|
|
48
|
-
const
|
|
51
|
+
const { value } = await gen.next() // Start the generator
|
|
49
52
|
gen.return(undefined) // Stop further execution
|
|
50
|
-
if (!
|
|
51
|
-
|
|
52
|
-
const firstRow = first.value
|
|
53
|
-
const firstKey = Object.keys(firstRow)[0]
|
|
54
|
-
return firstRow[firstKey]()
|
|
53
|
+
if (!value) return null
|
|
54
|
+
return value.cells[value.columns[0]]()
|
|
55
55
|
}
|
|
56
56
|
|
|
57
57
|
// Unary operators
|
|
58
58
|
if (node.type === 'unary') {
|
|
59
59
|
if (node.op === 'NOT') {
|
|
60
|
-
return !await evaluateExpr({ node: node.argument, row, tables })
|
|
60
|
+
return !await evaluateExpr({ node: node.argument, row, tables, rowIndex })
|
|
61
61
|
}
|
|
62
62
|
if (node.op === 'IS NULL') {
|
|
63
|
-
return await evaluateExpr({ node: node.argument, row, tables }) == null
|
|
63
|
+
return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) == null
|
|
64
64
|
}
|
|
65
65
|
if (node.op === 'IS NOT NULL') {
|
|
66
|
-
return await evaluateExpr({ node: node.argument, row, tables }) != null
|
|
66
|
+
return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) != null
|
|
67
67
|
}
|
|
68
68
|
if (node.op === '-') {
|
|
69
|
-
const val = await evaluateExpr({ node: node.argument, row, tables })
|
|
69
|
+
const val = await evaluateExpr({ node: node.argument, row, tables, rowIndex })
|
|
70
70
|
if (val == null) return null
|
|
71
71
|
return -val
|
|
72
72
|
}
|
|
@@ -76,15 +76,15 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
76
76
|
if (node.type === 'binary') {
|
|
77
77
|
// Handle date +/- interval at AST level
|
|
78
78
|
if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
|
|
79
|
-
const dateVal = await evaluateExpr({ node: node.left, row, tables })
|
|
79
|
+
const dateVal = await evaluateExpr({ node: node.left, row, tables, rowIndex })
|
|
80
80
|
return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
|
|
81
81
|
}
|
|
82
82
|
if (node.op === '+' && node.left.type === 'interval') {
|
|
83
|
-
const dateVal = await evaluateExpr({ node: node.right, row, tables })
|
|
83
|
+
const dateVal = await evaluateExpr({ node: node.right, row, tables, rowIndex })
|
|
84
84
|
return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const left = await evaluateExpr({ node: node.left, row, tables })
|
|
87
|
+
const left = await evaluateExpr({ node: node.left, row, tables, rowIndex })
|
|
88
88
|
|
|
89
89
|
// Short-circuit evaluation for AND and OR
|
|
90
90
|
if (node.op === 'AND') {
|
|
@@ -94,7 +94,7 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
94
94
|
if (left) return true
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
-
const right = await evaluateExpr({ node: node.right, row, tables })
|
|
97
|
+
const right = await evaluateExpr({ node: node.right, row, tables, rowIndex })
|
|
98
98
|
return applyBinaryOp(node.op, left, right)
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -102,38 +102,77 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
102
102
|
if (node.type === 'function') {
|
|
103
103
|
const funcName = node.name.toUpperCase()
|
|
104
104
|
/** @type {SqlPrimitive[]} */
|
|
105
|
-
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables })))
|
|
105
|
+
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, rowIndex })))
|
|
106
106
|
|
|
107
107
|
if (funcName === 'UPPER') {
|
|
108
|
-
if (args.length !== 1)
|
|
108
|
+
if (args.length !== 1) {
|
|
109
|
+
throw argCountError({
|
|
110
|
+
funcName: 'UPPER',
|
|
111
|
+
expected: 1,
|
|
112
|
+
received: args.length,
|
|
113
|
+
positionStart: node.positionStart,
|
|
114
|
+
positionEnd: node.positionEnd,
|
|
115
|
+
rowNumber: rowIndex,
|
|
116
|
+
})
|
|
117
|
+
}
|
|
109
118
|
const val = args[0]
|
|
110
119
|
if (val == null) return null
|
|
111
120
|
return String(val).toUpperCase()
|
|
112
121
|
}
|
|
113
122
|
|
|
114
123
|
if (funcName === 'LOWER') {
|
|
115
|
-
if (args.length !== 1)
|
|
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
|
+
}
|
|
116
134
|
const val = args[0]
|
|
117
135
|
if (val == null) return null
|
|
118
136
|
return String(val).toLowerCase()
|
|
119
137
|
}
|
|
120
138
|
|
|
121
139
|
if (funcName === 'CONCAT') {
|
|
122
|
-
if (args.length < 1)
|
|
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
|
+
}
|
|
123
150
|
// SQL CONCAT returns NULL if any argument is NULL
|
|
124
151
|
if (args.some(a => a == null)) return null
|
|
125
152
|
if (args.some(a => typeof a === 'object')) {
|
|
126
153
|
throw argValueError({
|
|
127
154
|
funcName: 'CONCAT',
|
|
128
155
|
message: 'does not support object arguments',
|
|
156
|
+
positionStart: node.positionStart,
|
|
157
|
+
positionEnd: node.positionEnd,
|
|
129
158
|
hint: 'Use CAST to convert objects to strings first.',
|
|
159
|
+
rowNumber: rowIndex,
|
|
130
160
|
})
|
|
131
161
|
}
|
|
132
162
|
return args.map(a => String(a)).join('')
|
|
133
163
|
}
|
|
134
164
|
|
|
135
165
|
if (funcName === 'LENGTH') {
|
|
136
|
-
if (args.length !== 1)
|
|
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
|
+
}
|
|
137
176
|
const val = args[0]
|
|
138
177
|
if (val == null) return null
|
|
139
178
|
return String(val).length
|
|
@@ -141,7 +180,14 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
141
180
|
|
|
142
181
|
if (funcName === 'SUBSTRING' || funcName === 'SUBSTR') {
|
|
143
182
|
if (args.length < 2 || args.length > 3) {
|
|
144
|
-
throw argCountError(
|
|
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
|
+
})
|
|
145
191
|
}
|
|
146
192
|
const str = args[0]
|
|
147
193
|
if (str == null) return null
|
|
@@ -151,7 +197,10 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
151
197
|
throw argValueError({
|
|
152
198
|
funcName,
|
|
153
199
|
message: `start position must be a positive integer, got ${args[1]}`,
|
|
200
|
+
positionStart: node.positionStart,
|
|
201
|
+
positionEnd: node.positionEnd,
|
|
154
202
|
hint: 'SQL uses 1-based indexing.',
|
|
203
|
+
rowNumber: rowIndex,
|
|
155
204
|
})
|
|
156
205
|
}
|
|
157
206
|
// SQL uses 1-based indexing
|
|
@@ -162,6 +211,9 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
162
211
|
throw argValueError({
|
|
163
212
|
funcName,
|
|
164
213
|
message: `length must be a non-negative integer, got ${args[2]}`,
|
|
214
|
+
positionStart: node.positionStart,
|
|
215
|
+
positionEnd: node.positionEnd,
|
|
216
|
+
rowNumber: rowIndex,
|
|
165
217
|
})
|
|
166
218
|
}
|
|
167
219
|
return strVal.substring(startIdx, startIdx + len)
|
|
@@ -170,14 +222,32 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
170
222
|
}
|
|
171
223
|
|
|
172
224
|
if (funcName === 'TRIM') {
|
|
173
|
-
if (args.length !== 1)
|
|
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
|
+
}
|
|
174
235
|
const val = args[0]
|
|
175
236
|
if (val == null) return null
|
|
176
237
|
return String(val).trim()
|
|
177
238
|
}
|
|
178
239
|
|
|
179
240
|
if (funcName === 'REPLACE') {
|
|
180
|
-
if (args.length !== 3)
|
|
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
|
+
}
|
|
181
251
|
const str = args[0]
|
|
182
252
|
const searchStr = args[1]
|
|
183
253
|
const replaceStr = args[2]
|
|
@@ -187,28 +257,71 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
187
257
|
}
|
|
188
258
|
|
|
189
259
|
if (funcName === 'RANDOM' || funcName === 'RAND') {
|
|
190
|
-
if (args.length !== 0)
|
|
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
|
+
}
|
|
191
270
|
return Math.random()
|
|
192
271
|
}
|
|
193
272
|
|
|
194
273
|
if (funcName === 'CURRENT_DATE') {
|
|
195
|
-
if (args.length !== 0)
|
|
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
|
+
}
|
|
196
284
|
return new Date().toISOString().split('T')[0]
|
|
197
285
|
}
|
|
198
286
|
|
|
199
287
|
if (funcName === 'CURRENT_TIME') {
|
|
200
|
-
if (args.length !== 0)
|
|
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
|
+
}
|
|
201
298
|
return new Date().toISOString().split('T')[1].replace('Z', '')
|
|
202
299
|
}
|
|
203
300
|
|
|
204
301
|
if (funcName === 'CURRENT_TIMESTAMP') {
|
|
205
|
-
if (args.length !== 0)
|
|
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
|
+
}
|
|
206
312
|
return new Date().toISOString()
|
|
207
313
|
}
|
|
208
314
|
|
|
209
315
|
if (funcName === 'JSON_OBJECT') {
|
|
210
316
|
if (args.length % 2 !== 0) {
|
|
211
|
-
throw argCountError(
|
|
317
|
+
throw argCountError({
|
|
318
|
+
funcName: 'JSON_OBJECT',
|
|
319
|
+
expected: 'even number',
|
|
320
|
+
received: args.length,
|
|
321
|
+
positionStart: node.positionStart,
|
|
322
|
+
positionEnd: node.positionEnd,
|
|
323
|
+
rowNumber: rowIndex,
|
|
324
|
+
})
|
|
212
325
|
}
|
|
213
326
|
/** @type {Record<string, SqlPrimitive>} */
|
|
214
327
|
const result = {}
|
|
@@ -219,7 +332,10 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
219
332
|
throw argValueError({
|
|
220
333
|
funcName: 'JSON_OBJECT',
|
|
221
334
|
message: 'key cannot be null',
|
|
335
|
+
positionStart: node.positionStart,
|
|
336
|
+
positionEnd: node.positionEnd,
|
|
222
337
|
hint: 'All keys must be non-null values.',
|
|
338
|
+
rowNumber: rowIndex,
|
|
223
339
|
})
|
|
224
340
|
}
|
|
225
341
|
result[String(key)] = value
|
|
@@ -228,7 +344,16 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
228
344
|
}
|
|
229
345
|
|
|
230
346
|
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY') {
|
|
231
|
-
if (args.length !== 2)
|
|
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
|
+
}
|
|
232
357
|
let jsonArg = args[0]
|
|
233
358
|
const pathArg = args[1]
|
|
234
359
|
if (jsonArg == null || pathArg == null) return null
|
|
@@ -241,7 +366,10 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
241
366
|
throw argValueError({
|
|
242
367
|
funcName,
|
|
243
368
|
message: 'invalid JSON string',
|
|
369
|
+
positionStart: node.positionStart,
|
|
370
|
+
positionEnd: node.positionEnd,
|
|
244
371
|
hint: 'First argument must be valid JSON.',
|
|
372
|
+
rowNumber: rowIndex,
|
|
245
373
|
})
|
|
246
374
|
}
|
|
247
375
|
}
|
|
@@ -249,6 +377,9 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
249
377
|
throw argValueError({
|
|
250
378
|
funcName,
|
|
251
379
|
message: `first argument must be JSON string or object, got ${typeof jsonArg}`,
|
|
380
|
+
positionStart: node.positionStart,
|
|
381
|
+
positionEnd: node.positionEnd,
|
|
382
|
+
rowNumber: rowIndex,
|
|
252
383
|
})
|
|
253
384
|
}
|
|
254
385
|
|
|
@@ -278,11 +409,25 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
278
409
|
return current
|
|
279
410
|
}
|
|
280
411
|
|
|
281
|
-
|
|
412
|
+
if (isMathFunc(funcName)) {
|
|
413
|
+
return evaluateMathFunc({
|
|
414
|
+
funcName,
|
|
415
|
+
args,
|
|
416
|
+
positionStart: node.positionStart,
|
|
417
|
+
positionEnd: node.positionEnd,
|
|
418
|
+
rowNumber: rowIndex,
|
|
419
|
+
})
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
throw unknownFunctionError({
|
|
423
|
+
funcName,
|
|
424
|
+
positionStart: node.positionStart,
|
|
425
|
+
positionEnd: node.positionEnd,
|
|
426
|
+
})
|
|
282
427
|
}
|
|
283
428
|
|
|
284
429
|
if (node.type === 'cast') {
|
|
285
|
-
const val = await evaluateExpr({ node: node.expr, row, tables })
|
|
430
|
+
const val = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
286
431
|
if (val == null) return null
|
|
287
432
|
const toType = node.toType.toUpperCase()
|
|
288
433
|
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
@@ -290,7 +435,15 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
290
435
|
return String(val)
|
|
291
436
|
}
|
|
292
437
|
// Can only cast primitives to other primitive types
|
|
293
|
-
if (typeof val === 'object')
|
|
438
|
+
if (typeof val === 'object') {
|
|
439
|
+
throw castError({
|
|
440
|
+
toType: node.toType,
|
|
441
|
+
positionStart: node.positionStart,
|
|
442
|
+
positionEnd: node.positionEnd,
|
|
443
|
+
fromType: 'object',
|
|
444
|
+
rowNumber: rowIndex,
|
|
445
|
+
})
|
|
446
|
+
}
|
|
294
447
|
if (toType === 'INTEGER' || toType === 'INT') {
|
|
295
448
|
const num = Number(val)
|
|
296
449
|
if (isNaN(num)) return null
|
|
@@ -307,30 +460,32 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
307
460
|
if (toType === 'BOOLEAN' || toType === 'BOOL') {
|
|
308
461
|
return Boolean(val)
|
|
309
462
|
}
|
|
310
|
-
throw castError(
|
|
463
|
+
throw castError({
|
|
464
|
+
toType: node.toType,
|
|
465
|
+
positionStart: node.positionStart,
|
|
466
|
+
positionEnd: node.positionEnd,
|
|
467
|
+
rowNumber: rowIndex,
|
|
468
|
+
})
|
|
311
469
|
}
|
|
312
470
|
|
|
313
471
|
// IN and NOT IN with value lists
|
|
314
472
|
if (node.type === 'in valuelist') {
|
|
315
|
-
const exprVal = await evaluateExpr({ node: node.expr, row, tables })
|
|
473
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
316
474
|
for (const valueNode of node.values) {
|
|
317
|
-
const val = await evaluateExpr({ node: valueNode, row, tables })
|
|
475
|
+
const val = await evaluateExpr({ node: valueNode, row, tables, rowIndex })
|
|
318
476
|
if (exprVal === val) return true
|
|
319
477
|
}
|
|
320
478
|
return false
|
|
321
479
|
}
|
|
322
480
|
// IN with subqueries
|
|
323
481
|
if (node.type === 'in') {
|
|
324
|
-
const exprVal = await evaluateExpr({ node: node.expr, row, tables })
|
|
482
|
+
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
325
483
|
const results = executeSelect(node.subquery, tables)
|
|
326
|
-
/** @type {SqlPrimitive[]} */
|
|
327
|
-
const values = []
|
|
328
484
|
for await (const resRow of results) {
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
values.push(val)
|
|
485
|
+
const value = await resRow.cells[resRow.columns[0]]()
|
|
486
|
+
if (exprVal === value) return true
|
|
332
487
|
}
|
|
333
|
-
return
|
|
488
|
+
return false
|
|
334
489
|
}
|
|
335
490
|
|
|
336
491
|
// EXISTS and NOT EXISTS with subqueries
|
|
@@ -346,28 +501,28 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
346
501
|
// CASE expressions
|
|
347
502
|
if (node.type === 'case') {
|
|
348
503
|
// For simple CASE: evaluate the case expression once
|
|
349
|
-
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables })
|
|
504
|
+
const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, rowIndex })
|
|
350
505
|
|
|
351
506
|
// Iterate through WHEN clauses
|
|
352
507
|
for (const whenClause of node.whenClauses) {
|
|
353
508
|
let conditionResult
|
|
354
509
|
if (caseValue !== undefined) {
|
|
355
510
|
// Simple CASE: compare caseValue with condition
|
|
356
|
-
const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables })
|
|
511
|
+
const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
|
|
357
512
|
conditionResult = caseValue === whenValue
|
|
358
513
|
} else {
|
|
359
514
|
// Searched CASE: evaluate condition as boolean
|
|
360
|
-
conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables })
|
|
515
|
+
conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
|
|
361
516
|
}
|
|
362
517
|
|
|
363
518
|
if (conditionResult) {
|
|
364
|
-
return evaluateExpr({ node: whenClause.result, row, tables })
|
|
519
|
+
return evaluateExpr({ node: whenClause.result, row, tables, rowIndex })
|
|
365
520
|
}
|
|
366
521
|
}
|
|
367
522
|
|
|
368
523
|
// No WHEN clause matched, return ELSE result or NULL
|
|
369
524
|
if (node.elseResult) {
|
|
370
|
-
return evaluateExpr({ node: node.elseResult, row, tables })
|
|
525
|
+
return evaluateExpr({ node: node.elseResult, row, tables, rowIndex })
|
|
371
526
|
}
|
|
372
527
|
return null
|
|
373
528
|
}
|
|
@@ -378,6 +533,9 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
378
533
|
throw invalidContextError({
|
|
379
534
|
item: 'INTERVAL',
|
|
380
535
|
validContext: 'date arithmetic (+ or -)',
|
|
536
|
+
positionStart: node.positionStart,
|
|
537
|
+
positionEnd: node.positionEnd,
|
|
538
|
+
rowNumber: rowIndex,
|
|
381
539
|
})
|
|
382
540
|
}
|
|
383
541
|
|
package/src/execute/having.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { unknownFunctionError } from '../
|
|
1
|
+
import { unknownFunctionError } from '../parseErrors.js'
|
|
2
2
|
import { isAggregateFunc } from '../validation.js'
|
|
3
3
|
import { evaluateExpr } from './expression.js'
|
|
4
4
|
import { applyBinaryOp } from './utils.js'
|
|
@@ -153,5 +153,10 @@ async function evaluateAggregateFunction(funcName, args, group, tables) {
|
|
|
153
153
|
return max
|
|
154
154
|
}
|
|
155
155
|
|
|
156
|
-
throw unknownFunctionError(
|
|
156
|
+
throw unknownFunctionError({
|
|
157
|
+
funcName,
|
|
158
|
+
positionStart: 0,
|
|
159
|
+
positionEnd: 0,
|
|
160
|
+
validFunctions: 'COUNT, SUM, AVG, MIN, MAX',
|
|
161
|
+
})
|
|
157
162
|
}
|