squirreling 0.4.8 → 0.5.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirreling",
3
- "version": "0.4.8",
3
+ "version": "0.5.0",
4
4
  "description": "Squirreling SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -37,10 +37,10 @@
37
37
  "test": "vitest run"
38
38
  },
39
39
  "devDependencies": {
40
- "@types/node": "24.10.1",
40
+ "@types/node": "24.10.2",
41
41
  "@vitest/coverage-v8": "4.0.15",
42
42
  "eslint": "9.39.1",
43
- "eslint-plugin-jsdoc": "61.4.2",
43
+ "eslint-plugin-jsdoc": "61.5.0",
44
44
  "typescript": "5.9.3",
45
45
  "vitest": "4.0.15"
46
46
  }
@@ -1,4 +1,5 @@
1
- import { aggregateError, unknownFunctionError } from '../errors.js'
1
+ import { unknownFunctionError } from '../parseErrors.js'
2
+ import { aggregateError } from '../validationErrors.js'
2
3
  import { evaluateExpr } from './expression.js'
3
4
  import { defaultDerivedAlias, stringify } from './utils.js'
4
5
 
@@ -39,7 +40,7 @@ export async function evaluateAggregate({ col, rows, tables }) {
39
40
 
40
41
  if (func === 'SUM' || func === 'AVG' || func === 'MIN' || func === 'MAX') {
41
42
  if (arg.kind === 'star') {
42
- throw aggregateError(func, '(*) is not supported, use a column name')
43
+ throw aggregateError({ funcName: func, issue: '(*) is not supported, use a column name' })
43
44
  }
44
45
  let sum = 0
45
46
  let count = 0
@@ -73,7 +74,7 @@ export async function evaluateAggregate({ col, rows, tables }) {
73
74
 
74
75
  if (func === 'JSON_ARRAYAGG') {
75
76
  if (arg.kind === 'star') {
76
- throw aggregateError('JSON_ARRAYAGG', '(*) is not supported, use a column name or expression')
77
+ throw aggregateError({ funcName: 'JSON_ARRAYAGG', issue: '(*) is not supported, use a column name or expression' })
77
78
  }
78
79
  /** @type {SqlPrimitive[]} */
79
80
  const values = []
@@ -96,7 +97,12 @@ export async function evaluateAggregate({ col, rows, tables }) {
96
97
  return values
97
98
  }
98
99
 
99
- throw unknownFunctionError(func, undefined, 'COUNT, SUM, AVG, MIN, MAX, JSON_ARRAYAGG')
100
+ throw unknownFunctionError({
101
+ funcName: func,
102
+ positionStart: 0,
103
+ positionEnd: 0,
104
+ validFunctions: 'COUNT, SUM, AVG, MIN, MAX, JSON_ARRAYAGG',
105
+ })
100
106
  }
101
107
 
102
108
  /**
@@ -1,4 +1,5 @@
1
- import { missingClauseError, tableNotFoundError, unsupportedOperationError } from '../errors.js'
1
+ import { missingClauseError } from '../parseErrors.js'
2
+ import { tableNotFoundError, unsupportedOperationError } from '../executionErrors.js'
2
3
  import { generatorSource, memorySource } from '../backend/dataSource.js'
3
4
  import { parseSql } from '../parse/parse.js'
4
5
  import { defaultAggregateAlias, evaluateAggregate } from './aggregates.js'
@@ -61,7 +62,7 @@ export async function* executeSelect(select, tables) {
61
62
  fromTableName = select.from.alias ?? select.from.table
62
63
  dataSource = tables[select.from.table]
63
64
  if (dataSource === undefined) {
64
- throw tableNotFoundError(select.from.table)
65
+ throw tableNotFoundError({ tableName: select.from.table })
65
66
  }
66
67
  } else {
67
68
  // Nested subquery - recursively resolve
@@ -236,6 +237,7 @@ async function* evaluateSelectAst(select, dataSource, tables) {
236
237
  async function* evaluateStreaming(select, dataSource, tables) {
237
238
  let rowsYielded = 0
238
239
  let rowsSkipped = 0
240
+ let rowIndex = 0
239
241
  const offset = select.offset ?? 0
240
242
  const limit = select.limit ?? Infinity
241
243
  if (limit <= 0) return
@@ -254,9 +256,10 @@ async function* evaluateStreaming(select, dataSource, tables) {
254
256
  }
255
257
 
256
258
  for await (const row of dataSource.getRows(hints)) {
259
+ rowIndex++
257
260
  // WHERE filter
258
261
  if (select.where) {
259
- const pass = await evaluateExpr({ node: select.where, row, tables })
262
+ const pass = await evaluateExpr({ node: select.where, row, tables, rowIndex })
260
263
  if (!pass) continue
261
264
  }
262
265
 
@@ -269,6 +272,7 @@ async function* evaluateStreaming(select, dataSource, tables) {
269
272
  // SELECT projection
270
273
  /** @type {AsyncRow} */
271
274
  const outRow = {}
275
+ const currentRowIndex = rowIndex
272
276
  for (const col of select.columns) {
273
277
  if (col.kind === 'star') {
274
278
  for (const [key, cell] of Object.entries(row)) {
@@ -276,7 +280,7 @@ async function* evaluateStreaming(select, dataSource, tables) {
276
280
  }
277
281
  } else if (col.kind === 'derived') {
278
282
  const alias = col.alias ?? defaultDerivedAlias(col.expr)
279
- outRow[alias] = () => evaluateExpr({ node: col.expr, row, tables })
283
+ outRow[alias] = () => evaluateExpr({ node: col.expr, row, tables, rowIndex: currentRowIndex })
280
284
  } else if (col.kind === 'aggregate') {
281
285
  throw new Error(
282
286
  'Aggregate functions require GROUP BY or will act on the whole dataset; add GROUP BY or remove aggregates'
@@ -334,9 +338,11 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
334
338
  /** @type {AsyncRow[]} */
335
339
  const filtered = []
336
340
 
337
- for (const row of working) {
341
+ for (let i = 0; i < working.length; i++) {
342
+ const row = working[i]
343
+ const rowIndex = i + 1 // 1-based
338
344
  if (select.where) {
339
- const passes = await evaluateExpr({ node: select.where, row, tables })
345
+ const passes = await evaluateExpr({ node: select.where, row, tables, rowIndex })
340
346
 
341
347
  if (!passes) {
342
348
  continue
@@ -379,10 +385,10 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
379
385
 
380
386
  const hasStar = select.columns.some(col => col.kind === 'star')
381
387
  if (hasStar && hasAggregate) {
382
- throw unsupportedOperationError(
383
- 'SELECT * with aggregate functions is not supported',
384
- 'Replace * with specific column names when using aggregate functions.'
385
- )
388
+ throw unsupportedOperationError({
389
+ operation: 'SELECT * with aggregate functions is not supported',
390
+ hint: 'Replace * with specific column names when using aggregate functions.',
391
+ })
386
392
  }
387
393
 
388
394
  for (const group of groups) {
@@ -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
- invalidContextError,
6
- unknownFunctionError,
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,9 +22,10 @@ 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
  }
@@ -57,16 +60,16 @@ export async function evaluateExpr({ node, row, tables }) {
57
60
  // Unary operators
58
61
  if (node.type === 'unary') {
59
62
  if (node.op === 'NOT') {
60
- return !await evaluateExpr({ node: node.argument, row, tables })
63
+ return !await evaluateExpr({ node: node.argument, row, tables, rowIndex })
61
64
  }
62
65
  if (node.op === 'IS NULL') {
63
- return await evaluateExpr({ node: node.argument, row, tables }) == null
66
+ return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) == null
64
67
  }
65
68
  if (node.op === 'IS NOT NULL') {
66
- return await evaluateExpr({ node: node.argument, row, tables }) != null
69
+ return await evaluateExpr({ node: node.argument, row, tables, rowIndex }) != null
67
70
  }
68
71
  if (node.op === '-') {
69
- const val = await evaluateExpr({ node: node.argument, row, tables })
72
+ const val = await evaluateExpr({ node: node.argument, row, tables, rowIndex })
70
73
  if (val == null) return null
71
74
  return -val
72
75
  }
@@ -76,15 +79,15 @@ export async function evaluateExpr({ node, row, tables }) {
76
79
  if (node.type === 'binary') {
77
80
  // Handle date +/- interval at AST level
78
81
  if ((node.op === '+' || node.op === '-') && node.right.type === 'interval') {
79
- const dateVal = await evaluateExpr({ node: node.left, row, tables })
82
+ const dateVal = await evaluateExpr({ node: node.left, row, tables, rowIndex })
80
83
  return applyIntervalToDate(dateVal, node.right.value, node.right.unit, node.op)
81
84
  }
82
85
  if (node.op === '+' && node.left.type === 'interval') {
83
- const dateVal = await evaluateExpr({ node: node.right, row, tables })
86
+ const dateVal = await evaluateExpr({ node: node.right, row, tables, rowIndex })
84
87
  return applyIntervalToDate(dateVal, node.left.value, node.left.unit, '+')
85
88
  }
86
89
 
87
- const left = await evaluateExpr({ node: node.left, row, tables })
90
+ const left = await evaluateExpr({ node: node.left, row, tables, rowIndex })
88
91
 
89
92
  // Short-circuit evaluation for AND and OR
90
93
  if (node.op === 'AND') {
@@ -94,7 +97,7 @@ export async function evaluateExpr({ node, row, tables }) {
94
97
  if (left) return true
95
98
  }
96
99
 
97
- const right = await evaluateExpr({ node: node.right, row, tables })
100
+ const right = await evaluateExpr({ node: node.right, row, tables, rowIndex })
98
101
  return applyBinaryOp(node.op, left, right)
99
102
  }
100
103
 
@@ -102,38 +105,77 @@ export async function evaluateExpr({ node, row, tables }) {
102
105
  if (node.type === 'function') {
103
106
  const funcName = node.name.toUpperCase()
104
107
  /** @type {SqlPrimitive[]} */
105
- const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables })))
108
+ const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables, rowIndex })))
106
109
 
107
110
  if (funcName === 'UPPER') {
108
- if (args.length !== 1) throw argCountError('UPPER', 1, args.length)
111
+ if (args.length !== 1) {
112
+ throw argCountError({
113
+ funcName: 'UPPER',
114
+ expected: 1,
115
+ received: args.length,
116
+ positionStart: node.positionStart,
117
+ positionEnd: node.positionEnd,
118
+ rowNumber: rowIndex,
119
+ })
120
+ }
109
121
  const val = args[0]
110
122
  if (val == null) return null
111
123
  return String(val).toUpperCase()
112
124
  }
113
125
 
114
126
  if (funcName === 'LOWER') {
115
- if (args.length !== 1) throw argCountError('LOWER', 1, args.length)
127
+ if (args.length !== 1) {
128
+ throw argCountError({
129
+ funcName: 'LOWER',
130
+ expected: 1,
131
+ received: args.length,
132
+ positionStart: node.positionStart,
133
+ positionEnd: node.positionEnd,
134
+ rowNumber: rowIndex,
135
+ })
136
+ }
116
137
  const val = args[0]
117
138
  if (val == null) return null
118
139
  return String(val).toLowerCase()
119
140
  }
120
141
 
121
142
  if (funcName === 'CONCAT') {
122
- if (args.length < 1) throw argCountError('CONCAT', 'at least 1', args.length)
143
+ if (args.length < 1) {
144
+ throw argCountError({
145
+ funcName: 'CONCAT',
146
+ expected: 'at least 1',
147
+ received: args.length,
148
+ positionStart: node.positionStart,
149
+ positionEnd: node.positionEnd,
150
+ rowNumber: rowIndex,
151
+ })
152
+ }
123
153
  // SQL CONCAT returns NULL if any argument is NULL
124
154
  if (args.some(a => a == null)) return null
125
155
  if (args.some(a => typeof a === 'object')) {
126
156
  throw argValueError({
127
157
  funcName: 'CONCAT',
128
158
  message: 'does not support object arguments',
159
+ positionStart: node.positionStart,
160
+ positionEnd: node.positionEnd,
129
161
  hint: 'Use CAST to convert objects to strings first.',
162
+ rowNumber: rowIndex,
130
163
  })
131
164
  }
132
165
  return args.map(a => String(a)).join('')
133
166
  }
134
167
 
135
168
  if (funcName === 'LENGTH') {
136
- if (args.length !== 1) throw argCountError('LENGTH', 1, args.length)
169
+ if (args.length !== 1) {
170
+ throw argCountError({
171
+ funcName: 'LENGTH',
172
+ expected: 1,
173
+ received: args.length,
174
+ positionStart: node.positionStart,
175
+ positionEnd: node.positionEnd,
176
+ rowNumber: rowIndex,
177
+ })
178
+ }
137
179
  const val = args[0]
138
180
  if (val == null) return null
139
181
  return String(val).length
@@ -141,7 +183,14 @@ export async function evaluateExpr({ node, row, tables }) {
141
183
 
142
184
  if (funcName === 'SUBSTRING' || funcName === 'SUBSTR') {
143
185
  if (args.length < 2 || args.length > 3) {
144
- throw argCountError(funcName, '2 or 3', args.length)
186
+ throw argCountError({
187
+ funcName,
188
+ expected: '2 or 3',
189
+ received: args.length,
190
+ positionStart: node.positionStart,
191
+ positionEnd: node.positionEnd,
192
+ rowNumber: rowIndex,
193
+ })
145
194
  }
146
195
  const str = args[0]
147
196
  if (str == null) return null
@@ -151,7 +200,10 @@ export async function evaluateExpr({ node, row, tables }) {
151
200
  throw argValueError({
152
201
  funcName,
153
202
  message: `start position must be a positive integer, got ${args[1]}`,
203
+ positionStart: node.positionStart,
204
+ positionEnd: node.positionEnd,
154
205
  hint: 'SQL uses 1-based indexing.',
206
+ rowNumber: rowIndex,
155
207
  })
156
208
  }
157
209
  // SQL uses 1-based indexing
@@ -162,6 +214,9 @@ export async function evaluateExpr({ node, row, tables }) {
162
214
  throw argValueError({
163
215
  funcName,
164
216
  message: `length must be a non-negative integer, got ${args[2]}`,
217
+ positionStart: node.positionStart,
218
+ positionEnd: node.positionEnd,
219
+ rowNumber: rowIndex,
165
220
  })
166
221
  }
167
222
  return strVal.substring(startIdx, startIdx + len)
@@ -170,14 +225,32 @@ export async function evaluateExpr({ node, row, tables }) {
170
225
  }
171
226
 
172
227
  if (funcName === 'TRIM') {
173
- if (args.length !== 1) throw argCountError('TRIM', 1, args.length)
228
+ if (args.length !== 1) {
229
+ throw argCountError({
230
+ funcName: 'TRIM',
231
+ expected: 1,
232
+ received: args.length,
233
+ positionStart: node.positionStart,
234
+ positionEnd: node.positionEnd,
235
+ rowNumber: rowIndex,
236
+ })
237
+ }
174
238
  const val = args[0]
175
239
  if (val == null) return null
176
240
  return String(val).trim()
177
241
  }
178
242
 
179
243
  if (funcName === 'REPLACE') {
180
- if (args.length !== 3) throw argCountError('REPLACE', 3, args.length)
244
+ if (args.length !== 3) {
245
+ throw argCountError({
246
+ funcName: 'REPLACE',
247
+ expected: 3,
248
+ received: args.length,
249
+ positionStart: node.positionStart,
250
+ positionEnd: node.positionEnd,
251
+ rowNumber: rowIndex,
252
+ })
253
+ }
181
254
  const str = args[0]
182
255
  const searchStr = args[1]
183
256
  const replaceStr = args[2]
@@ -187,28 +260,71 @@ export async function evaluateExpr({ node, row, tables }) {
187
260
  }
188
261
 
189
262
  if (funcName === 'RANDOM' || funcName === 'RAND') {
190
- if (args.length !== 0) throw argCountError(funcName, 0, args.length)
263
+ if (args.length !== 0) {
264
+ throw argCountError({
265
+ funcName,
266
+ expected: 0,
267
+ received: args.length,
268
+ positionStart: node.positionStart,
269
+ positionEnd: node.positionEnd,
270
+ rowNumber: rowIndex,
271
+ })
272
+ }
191
273
  return Math.random()
192
274
  }
193
275
 
194
276
  if (funcName === 'CURRENT_DATE') {
195
- if (args.length !== 0) throw argCountError('CURRENT_DATE', 0, args.length)
277
+ if (args.length !== 0) {
278
+ throw argCountError({
279
+ funcName: 'CURRENT_DATE',
280
+ expected: 0,
281
+ received: args.length,
282
+ positionStart: node.positionStart,
283
+ positionEnd: node.positionEnd,
284
+ rowNumber: rowIndex,
285
+ })
286
+ }
196
287
  return new Date().toISOString().split('T')[0]
197
288
  }
198
289
 
199
290
  if (funcName === 'CURRENT_TIME') {
200
- if (args.length !== 0) throw argCountError('CURRENT_TIME', 0, args.length)
291
+ if (args.length !== 0) {
292
+ throw argCountError({
293
+ funcName: 'CURRENT_TIME',
294
+ expected: 0,
295
+ received: args.length,
296
+ positionStart: node.positionStart,
297
+ positionEnd: node.positionEnd,
298
+ rowNumber: rowIndex,
299
+ })
300
+ }
201
301
  return new Date().toISOString().split('T')[1].replace('Z', '')
202
302
  }
203
303
 
204
304
  if (funcName === 'CURRENT_TIMESTAMP') {
205
- if (args.length !== 0) throw argCountError('CURRENT_TIMESTAMP', 0, args.length)
305
+ if (args.length !== 0) {
306
+ throw argCountError({
307
+ funcName: 'CURRENT_TIMESTAMP',
308
+ expected: 0,
309
+ received: args.length,
310
+ positionStart: node.positionStart,
311
+ positionEnd: node.positionEnd,
312
+ rowNumber: rowIndex,
313
+ })
314
+ }
206
315
  return new Date().toISOString()
207
316
  }
208
317
 
209
318
  if (funcName === 'JSON_OBJECT') {
210
319
  if (args.length % 2 !== 0) {
211
- throw argCountError('JSON_OBJECT', 'even number', args.length)
320
+ throw argCountError({
321
+ funcName: 'JSON_OBJECT',
322
+ expected: 'even number',
323
+ received: args.length,
324
+ positionStart: node.positionStart,
325
+ positionEnd: node.positionEnd,
326
+ rowNumber: rowIndex,
327
+ })
212
328
  }
213
329
  /** @type {Record<string, SqlPrimitive>} */
214
330
  const result = {}
@@ -219,7 +335,10 @@ export async function evaluateExpr({ node, row, tables }) {
219
335
  throw argValueError({
220
336
  funcName: 'JSON_OBJECT',
221
337
  message: 'key cannot be null',
338
+ positionStart: node.positionStart,
339
+ positionEnd: node.positionEnd,
222
340
  hint: 'All keys must be non-null values.',
341
+ rowNumber: rowIndex,
223
342
  })
224
343
  }
225
344
  result[String(key)] = value
@@ -228,7 +347,16 @@ export async function evaluateExpr({ node, row, tables }) {
228
347
  }
229
348
 
230
349
  if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY') {
231
- if (args.length !== 2) throw argCountError(funcName, 2, args.length)
350
+ if (args.length !== 2) {
351
+ throw argCountError({
352
+ funcName,
353
+ expected: 2,
354
+ received: args.length,
355
+ positionStart: node.positionStart,
356
+ positionEnd: node.positionEnd,
357
+ rowNumber: rowIndex,
358
+ })
359
+ }
232
360
  let jsonArg = args[0]
233
361
  const pathArg = args[1]
234
362
  if (jsonArg == null || pathArg == null) return null
@@ -241,7 +369,10 @@ export async function evaluateExpr({ node, row, tables }) {
241
369
  throw argValueError({
242
370
  funcName,
243
371
  message: 'invalid JSON string',
372
+ positionStart: node.positionStart,
373
+ positionEnd: node.positionEnd,
244
374
  hint: 'First argument must be valid JSON.',
375
+ rowNumber: rowIndex,
245
376
  })
246
377
  }
247
378
  }
@@ -249,6 +380,9 @@ export async function evaluateExpr({ node, row, tables }) {
249
380
  throw argValueError({
250
381
  funcName,
251
382
  message: `first argument must be JSON string or object, got ${typeof jsonArg}`,
383
+ positionStart: node.positionStart,
384
+ positionEnd: node.positionEnd,
385
+ rowNumber: rowIndex,
252
386
  })
253
387
  }
254
388
 
@@ -278,11 +412,25 @@ export async function evaluateExpr({ node, row, tables }) {
278
412
  return current
279
413
  }
280
414
 
281
- throw unknownFunctionError(funcName)
415
+ if (isMathFunc(funcName)) {
416
+ return evaluateMathFunc({
417
+ funcName,
418
+ args,
419
+ positionStart: node.positionStart,
420
+ positionEnd: node.positionEnd,
421
+ rowNumber: rowIndex,
422
+ })
423
+ }
424
+
425
+ throw unknownFunctionError({
426
+ funcName,
427
+ positionStart: node.positionStart,
428
+ positionEnd: node.positionEnd,
429
+ })
282
430
  }
283
431
 
284
432
  if (node.type === 'cast') {
285
- const val = await evaluateExpr({ node: node.expr, row, tables })
433
+ const val = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
286
434
  if (val == null) return null
287
435
  const toType = node.toType.toUpperCase()
288
436
  if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
@@ -290,7 +438,15 @@ export async function evaluateExpr({ node, row, tables }) {
290
438
  return String(val)
291
439
  }
292
440
  // Can only cast primitives to other primitive types
293
- if (typeof val === 'object') throw castError(node.toType, 'object')
441
+ if (typeof val === 'object') {
442
+ throw castError({
443
+ toType: node.toType,
444
+ positionStart: node.positionStart,
445
+ positionEnd: node.positionEnd,
446
+ fromType: 'object',
447
+ rowNumber: rowIndex,
448
+ })
449
+ }
294
450
  if (toType === 'INTEGER' || toType === 'INT') {
295
451
  const num = Number(val)
296
452
  if (isNaN(num)) return null
@@ -307,21 +463,26 @@ export async function evaluateExpr({ node, row, tables }) {
307
463
  if (toType === 'BOOLEAN' || toType === 'BOOL') {
308
464
  return Boolean(val)
309
465
  }
310
- throw castError(node.toType)
466
+ throw castError({
467
+ toType: node.toType,
468
+ positionStart: node.positionStart,
469
+ positionEnd: node.positionEnd,
470
+ rowNumber: rowIndex,
471
+ })
311
472
  }
312
473
 
313
474
  // IN and NOT IN with value lists
314
475
  if (node.type === 'in valuelist') {
315
- const exprVal = await evaluateExpr({ node: node.expr, row, tables })
476
+ const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
316
477
  for (const valueNode of node.values) {
317
- const val = await evaluateExpr({ node: valueNode, row, tables })
478
+ const val = await evaluateExpr({ node: valueNode, row, tables, rowIndex })
318
479
  if (exprVal === val) return true
319
480
  }
320
481
  return false
321
482
  }
322
483
  // IN with subqueries
323
484
  if (node.type === 'in') {
324
- const exprVal = await evaluateExpr({ node: node.expr, row, tables })
485
+ const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
325
486
  const results = executeSelect(node.subquery, tables)
326
487
  /** @type {SqlPrimitive[]} */
327
488
  const values = []
@@ -346,28 +507,28 @@ export async function evaluateExpr({ node, row, tables }) {
346
507
  // CASE expressions
347
508
  if (node.type === 'case') {
348
509
  // For simple CASE: evaluate the case expression once
349
- const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables })
510
+ const caseValue = node.caseExpr && await evaluateExpr({ node: node.caseExpr, row, tables, rowIndex })
350
511
 
351
512
  // Iterate through WHEN clauses
352
513
  for (const whenClause of node.whenClauses) {
353
514
  let conditionResult
354
515
  if (caseValue !== undefined) {
355
516
  // Simple CASE: compare caseValue with condition
356
- const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables })
517
+ const whenValue = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
357
518
  conditionResult = caseValue === whenValue
358
519
  } else {
359
520
  // Searched CASE: evaluate condition as boolean
360
- conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables })
521
+ conditionResult = await evaluateExpr({ node: whenClause.condition, row, tables, rowIndex })
361
522
  }
362
523
 
363
524
  if (conditionResult) {
364
- return evaluateExpr({ node: whenClause.result, row, tables })
525
+ return evaluateExpr({ node: whenClause.result, row, tables, rowIndex })
365
526
  }
366
527
  }
367
528
 
368
529
  // No WHEN clause matched, return ELSE result or NULL
369
530
  if (node.elseResult) {
370
- return evaluateExpr({ node: node.elseResult, row, tables })
531
+ return evaluateExpr({ node: node.elseResult, row, tables, rowIndex })
371
532
  }
372
533
  return null
373
534
  }
@@ -378,6 +539,9 @@ export async function evaluateExpr({ node, row, tables }) {
378
539
  throw invalidContextError({
379
540
  item: 'INTERVAL',
380
541
  validContext: 'date arithmetic (+ or -)',
542
+ positionStart: node.positionStart,
543
+ positionEnd: node.positionEnd,
544
+ rowNumber: rowIndex,
381
545
  })
382
546
  }
383
547
 
@@ -1,4 +1,4 @@
1
- import { unknownFunctionError } from '../errors.js'
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(funcName, undefined, 'COUNT, SUM, AVG, MIN, MAX')
156
+ throw unknownFunctionError({
157
+ funcName,
158
+ positionStart: 0,
159
+ positionEnd: 0,
160
+ validFunctions: 'COUNT, SUM, AVG, MIN, MAX',
161
+ })
157
162
  }
@@ -1,4 +1,5 @@
1
- import { missingClauseError, tableNotFoundError } from '../errors.js'
1
+ import { missingClauseError } from '../parseErrors.js'
2
+ import { tableNotFoundError } from '../executionErrors.js'
2
3
  import { evaluateExpr } from './expression.js'
3
4
  import { stringify } from './utils.js'
4
5
 
@@ -23,7 +24,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
23
24
  const join = joins[0]
24
25
  const rightSource = tables[join.table]
25
26
  if (rightSource === undefined) {
26
- throw tableNotFoundError(join.table)
27
+ throw tableNotFoundError({ tableName: join.table })
27
28
  }
28
29
 
29
30
  // Buffer right rows for hash index (required for hash join)
@@ -63,7 +64,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
63
64
  const join = joins[i]
64
65
  const rightSource = tables[join.table]
65
66
  if (rightSource === undefined) {
66
- throw tableNotFoundError(join.table)
67
+ throw tableNotFoundError({ tableName: join.table })
67
68
  }
68
69
 
69
70
  /** @type {AsyncRow[]} */
@@ -99,7 +100,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
99
100
  const lastJoin = joins[joins.length - 1]
100
101
  const rightSource = tables[lastJoin.table]
101
102
  if (rightSource === undefined) {
102
- throw tableNotFoundError(lastJoin.table)
103
+ throw tableNotFoundError({ tableName: lastJoin.table })
103
104
  }
104
105
 
105
106
  /** @type {AsyncRow[]} */