squirreling 0.5.0 → 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 +1 -1
- package/src/backend/dataSource.js +15 -14
- package/src/execute/execute.js +43 -31
- package/src/execute/expression.js +10 -16
- package/src/execute/join.js +30 -30
- package/src/execute/math.js +178 -3
- package/src/execute/utils.js +2 -2
- package/src/executionErrors.js +7 -6
- package/src/parseErrors.js +15 -14
- package/src/types.d.ts +18 -3
- package/src/validation.js +2 -0
- package/src/validationErrors.js +14 -3
package/README.md
CHANGED
|
@@ -40,7 +40,10 @@ const users = [
|
|
|
40
40
|
// ...more rows
|
|
41
41
|
]
|
|
42
42
|
|
|
43
|
-
|
|
43
|
+
interface AsyncRow {
|
|
44
|
+
columns: string[]
|
|
45
|
+
cells: Record<string, AsyncCell>
|
|
46
|
+
}
|
|
44
47
|
type AsyncCell = () => Promise<SqlPrimitive>
|
|
45
48
|
|
|
46
49
|
// Returns an async iterable of rows with async cells
|
|
@@ -76,6 +79,7 @@ console.log(allUsers)
|
|
|
76
79
|
- `GROUP BY` and `HAVING` clauses
|
|
77
80
|
- Aggregate functions: `COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, `JSON_ARRAYAGG`
|
|
78
81
|
- String functions: `CONCAT`, `SUBSTRING`, `LENGTH`, `UPPER`, `LOWER`
|
|
82
|
+
- Math functions: `ABS`, `CEIL`, `FLOOR`, `ROUND`, `MOD`, `RAND`, `RANDOM`, `LN`, `LOG10`, `EXP`, `POWER`, `SQRT`, `SIN`, `COS`, `TAN`, `COT`, `ASIN`, `ACOS`, `ATAN`, `ATAN2`, `DEGREES`, `RADIANS`, `PI`
|
|
79
83
|
- Date functions: `CURRENT_DATE`, `CURRENT_TIME`, `CURRENT_TIMESTAMP`, `INTERVAL`
|
|
80
84
|
- Json functions: `JSON_VALUE`, `JSON_QUERY`, `JSON_OBJECT`
|
|
81
85
|
- Basic expressions and arithmetic operations
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { AsyncDataSource, AsyncRow, SqlPrimitive } from '../types.js'
|
|
2
|
+
* @import { AsyncCell, AsyncCells, AsyncDataSource, AsyncRow, SqlPrimitive } from '../types.js'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
export function generatorSource(gen) {
|
|
13
13
|
return {
|
|
14
|
-
async *
|
|
14
|
+
async *scan() {
|
|
15
15
|
yield* gen
|
|
16
16
|
},
|
|
17
17
|
}
|
|
@@ -24,12 +24,12 @@ export function generatorSource(gen) {
|
|
|
24
24
|
* @returns {AsyncRow} a row accessor interface
|
|
25
25
|
*/
|
|
26
26
|
function asyncRow(obj) {
|
|
27
|
-
/** @type {
|
|
28
|
-
const
|
|
27
|
+
/** @type {AsyncCells} */
|
|
28
|
+
const cells = {}
|
|
29
29
|
for (const [key, value] of Object.entries(obj)) {
|
|
30
|
-
|
|
30
|
+
cells[key] = () => Promise.resolve(value)
|
|
31
31
|
}
|
|
32
|
-
return
|
|
32
|
+
return { columns: Object.keys(obj), cells }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
@@ -40,7 +40,7 @@ function asyncRow(obj) {
|
|
|
40
40
|
*/
|
|
41
41
|
export function memorySource(data) {
|
|
42
42
|
return {
|
|
43
|
-
async *
|
|
43
|
+
async *scan() {
|
|
44
44
|
for (const item of data) {
|
|
45
45
|
yield asyncRow(item)
|
|
46
46
|
}
|
|
@@ -60,15 +60,16 @@ export function cachedDataSource(source) {
|
|
|
60
60
|
/**
|
|
61
61
|
* @yields {AsyncRow}
|
|
62
62
|
*/
|
|
63
|
-
async *
|
|
63
|
+
async *scan() {
|
|
64
64
|
let index = 0
|
|
65
|
-
for await (const row of source.
|
|
65
|
+
for await (const row of source.scan()) {
|
|
66
66
|
const rowIndex = index
|
|
67
|
-
/** @type {
|
|
68
|
-
const
|
|
69
|
-
for (const
|
|
67
|
+
/** @type {AsyncCells} */
|
|
68
|
+
const cells = {}
|
|
69
|
+
for (const key of row.columns) {
|
|
70
|
+
const cell = row.cells[key]
|
|
70
71
|
// Wrap the cell to cache accesses
|
|
71
|
-
|
|
72
|
+
cells[key] = () => {
|
|
72
73
|
const cacheKey = `${rowIndex}:${key}`
|
|
73
74
|
let value = cache.get(cacheKey)
|
|
74
75
|
if (!value) {
|
|
@@ -78,7 +79,7 @@ export function cachedDataSource(source) {
|
|
|
78
79
|
return value
|
|
79
80
|
}
|
|
80
81
|
}
|
|
81
|
-
yield
|
|
82
|
+
yield { columns: row.columns, cells }
|
|
82
83
|
index++
|
|
83
84
|
}
|
|
84
85
|
},
|
package/src/execute/execute.js
CHANGED
|
@@ -10,7 +10,7 @@ import { executeJoins } from './join.js'
|
|
|
10
10
|
import { compareForTerm, defaultDerivedAlias, stringify } from './utils.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @import { AsyncDataSource, AsyncRow, ExecuteSqlOptions, OrderByItem, QueryHints, SelectStatement, SqlPrimitive } from '../types.js'
|
|
13
|
+
* @import { AsyncCells, AsyncDataSource, AsyncRow, ExecuteSqlOptions, OrderByItem, QueryHints, SelectStatement, SqlPrimitive } from '../types.js'
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
/**
|
|
@@ -20,7 +20,7 @@ import { compareForTerm, defaultDerivedAlias, stringify } from './utils.js'
|
|
|
20
20
|
* @yields {AsyncRow} async generator yielding result rows
|
|
21
21
|
*/
|
|
22
22
|
export async function* executeSql({ tables, query }) {
|
|
23
|
-
const select = parseSql(query)
|
|
23
|
+
const select = typeof query === 'string' ? parseSql(query) : query
|
|
24
24
|
|
|
25
25
|
// Check for unsupported operations
|
|
26
26
|
if (!select.from) {
|
|
@@ -81,15 +81,15 @@ export async function* executeSelect(select, tables) {
|
|
|
81
81
|
/**
|
|
82
82
|
* Creates a stable string key for a row to enable deduplication
|
|
83
83
|
*
|
|
84
|
-
* @param {
|
|
84
|
+
* @param {AsyncCells} cells
|
|
85
85
|
* @returns {Promise<string>} a stable string representation of the row
|
|
86
86
|
*/
|
|
87
|
-
async function stableRowKey(
|
|
88
|
-
const keys = Object.keys(
|
|
87
|
+
async function stableRowKey(cells) {
|
|
88
|
+
const keys = Object.keys(cells).sort()
|
|
89
89
|
/** @type {string[]} */
|
|
90
90
|
const parts = []
|
|
91
91
|
for (const k of keys) {
|
|
92
|
-
const v = await
|
|
92
|
+
const v = await cells[k]()
|
|
93
93
|
parts.push(k + ':' + stringify(v))
|
|
94
94
|
}
|
|
95
95
|
return parts.join('|')
|
|
@@ -109,7 +109,7 @@ async function applyDistinct(rows, distinct) {
|
|
|
109
109
|
/** @type {AsyncRow[]} */
|
|
110
110
|
const result = []
|
|
111
111
|
for (const row of rows) {
|
|
112
|
-
const key = await stableRowKey(row)
|
|
112
|
+
const key = await stableRowKey(row.cells)
|
|
113
113
|
if (seen.has(key)) continue
|
|
114
114
|
seen.add(key)
|
|
115
115
|
result.push(row)
|
|
@@ -255,7 +255,7 @@ async function* evaluateStreaming(select, dataSource, tables) {
|
|
|
255
255
|
offset: select.offset,
|
|
256
256
|
}
|
|
257
257
|
|
|
258
|
-
for await (const row of dataSource.
|
|
258
|
+
for await (const row of dataSource.scan(hints)) {
|
|
259
259
|
rowIndex++
|
|
260
260
|
// WHERE filter
|
|
261
261
|
if (select.where) {
|
|
@@ -270,17 +270,21 @@ async function* evaluateStreaming(select, dataSource, tables) {
|
|
|
270
270
|
}
|
|
271
271
|
|
|
272
272
|
// SELECT projection
|
|
273
|
-
/** @type {
|
|
274
|
-
const
|
|
273
|
+
/** @type {string[]} */
|
|
274
|
+
const columns = []
|
|
275
|
+
/** @type {AsyncCells} */
|
|
276
|
+
const cells = {}
|
|
275
277
|
const currentRowIndex = rowIndex
|
|
276
278
|
for (const col of select.columns) {
|
|
277
279
|
if (col.kind === 'star') {
|
|
278
|
-
for (const
|
|
279
|
-
|
|
280
|
+
for (const key of row.columns) {
|
|
281
|
+
columns.push(key)
|
|
282
|
+
cells[key] = row.cells[key]
|
|
280
283
|
}
|
|
281
284
|
} else if (col.kind === 'derived') {
|
|
282
285
|
const alias = col.alias ?? defaultDerivedAlias(col.expr)
|
|
283
|
-
|
|
286
|
+
columns.push(alias)
|
|
287
|
+
cells[alias] = () => evaluateExpr({ node: col.expr, row, tables, rowIndex: currentRowIndex })
|
|
284
288
|
} else if (col.kind === 'aggregate') {
|
|
285
289
|
throw new Error(
|
|
286
290
|
'Aggregate functions require GROUP BY or will act on the whole dataset; add GROUP BY or remove aggregates'
|
|
@@ -290,7 +294,7 @@ async function* evaluateStreaming(select, dataSource, tables) {
|
|
|
290
294
|
|
|
291
295
|
// DISTINCT: skip duplicate rows
|
|
292
296
|
if (seen) {
|
|
293
|
-
const key = await stableRowKey(
|
|
297
|
+
const key = await stableRowKey(cells)
|
|
294
298
|
if (seen.has(key)) continue
|
|
295
299
|
seen.add(key)
|
|
296
300
|
// OFFSET applies to distinct rows
|
|
@@ -300,7 +304,7 @@ async function* evaluateStreaming(select, dataSource, tables) {
|
|
|
300
304
|
}
|
|
301
305
|
}
|
|
302
306
|
|
|
303
|
-
yield
|
|
307
|
+
yield { columns, cells }
|
|
304
308
|
rowsYielded++
|
|
305
309
|
if (rowsYielded >= limit) {
|
|
306
310
|
break
|
|
@@ -330,7 +334,7 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
|
|
|
330
334
|
// Step 1: Collect all rows from data source
|
|
331
335
|
/** @type {AsyncRow[]} */
|
|
332
336
|
const working = []
|
|
333
|
-
for await (const row of dataSource.
|
|
337
|
+
for await (const row of dataSource.scan(hints)) {
|
|
334
338
|
working.push(row)
|
|
335
339
|
}
|
|
336
340
|
|
|
@@ -392,14 +396,16 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
|
|
|
392
396
|
}
|
|
393
397
|
|
|
394
398
|
for (const group of groups) {
|
|
395
|
-
|
|
396
|
-
|
|
399
|
+
const columns = []
|
|
400
|
+
/** @type {AsyncCells} */
|
|
401
|
+
const cells = {}
|
|
397
402
|
for (const col of select.columns) {
|
|
398
403
|
if (col.kind === 'star') {
|
|
399
404
|
const firstRow = group[0]
|
|
400
405
|
if (firstRow) {
|
|
401
|
-
for (const
|
|
402
|
-
|
|
406
|
+
for (const key of firstRow.columns) {
|
|
407
|
+
columns.push(key)
|
|
408
|
+
cells[key] = firstRow.cells[key]
|
|
403
409
|
}
|
|
404
410
|
}
|
|
405
411
|
continue
|
|
@@ -407,29 +413,32 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
|
|
|
407
413
|
|
|
408
414
|
if (col.kind === 'derived') {
|
|
409
415
|
const alias = col.alias ?? defaultDerivedAlias(col.expr)
|
|
416
|
+
columns.push(alias)
|
|
410
417
|
if (group.length > 0) {
|
|
411
|
-
|
|
418
|
+
cells[alias] = () => evaluateExpr({ node: col.expr, row: group[0], tables })
|
|
412
419
|
} else {
|
|
413
|
-
delete
|
|
420
|
+
delete cells[alias]
|
|
414
421
|
}
|
|
415
422
|
continue
|
|
416
423
|
}
|
|
417
424
|
|
|
418
425
|
if (col.kind === 'aggregate') {
|
|
419
426
|
const alias = col.alias ?? defaultAggregateAlias(col)
|
|
420
|
-
|
|
427
|
+
columns.push(alias)
|
|
428
|
+
cells[alias] = () => evaluateAggregate({ col, rows: group, tables })
|
|
421
429
|
continue
|
|
422
430
|
}
|
|
423
431
|
}
|
|
432
|
+
const asyncRow = { columns, cells }
|
|
424
433
|
|
|
425
434
|
// Apply HAVING filter before adding to projected results
|
|
426
435
|
if (select.having) {
|
|
427
|
-
if (!await evaluateHavingExpr(select.having,
|
|
436
|
+
if (!await evaluateHavingExpr(select.having, asyncRow, group, tables)) {
|
|
428
437
|
continue
|
|
429
438
|
}
|
|
430
439
|
}
|
|
431
440
|
|
|
432
|
-
projected.push(
|
|
441
|
+
projected.push(asyncRow)
|
|
433
442
|
}
|
|
434
443
|
} else {
|
|
435
444
|
// No grouping, simple projection
|
|
@@ -446,19 +455,22 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
|
|
|
446
455
|
}
|
|
447
456
|
|
|
448
457
|
for (const row of rowsToProject) {
|
|
449
|
-
|
|
450
|
-
|
|
458
|
+
const columns = []
|
|
459
|
+
/** @type {AsyncCells} */
|
|
460
|
+
const cells = {}
|
|
451
461
|
for (const col of select.columns) {
|
|
452
462
|
if (col.kind === 'star') {
|
|
453
|
-
for (const
|
|
454
|
-
|
|
463
|
+
for (const key of row.columns) {
|
|
464
|
+
columns.push(key)
|
|
465
|
+
cells[key] = row.cells[key]
|
|
455
466
|
}
|
|
456
467
|
} else if (col.kind === 'derived') {
|
|
457
468
|
const alias = col.alias ?? defaultDerivedAlias(col.expr)
|
|
458
|
-
|
|
469
|
+
columns.push(alias)
|
|
470
|
+
cells[alias] = () => evaluateExpr({ node: col.expr, row, tables })
|
|
459
471
|
}
|
|
460
472
|
}
|
|
461
|
-
projected.push(
|
|
473
|
+
projected.push({ columns, cells })
|
|
462
474
|
}
|
|
463
475
|
}
|
|
464
476
|
|
|
@@ -32,14 +32,14 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
32
32
|
|
|
33
33
|
if (node.type === 'identifier') {
|
|
34
34
|
// Try exact match first (handles both qualified and unqualified names)
|
|
35
|
-
if (row[node.name]) {
|
|
36
|
-
return row[node.name]()
|
|
35
|
+
if (row.cells[node.name]) {
|
|
36
|
+
return row.cells[node.name]()
|
|
37
37
|
}
|
|
38
38
|
// For qualified names like 'users.id', also try just the column part
|
|
39
39
|
if (node.name.includes('.')) {
|
|
40
40
|
const colName = node.name.split('.').pop()
|
|
41
|
-
if (colName && row[colName]) {
|
|
42
|
-
return row[colName]()
|
|
41
|
+
if (colName && row.cells[colName]) {
|
|
42
|
+
return row.cells[colName]()
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
return null
|
|
@@ -48,13 +48,10 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
48
48
|
// Scalar subquery - returns a single value
|
|
49
49
|
if (node.type === 'subquery') {
|
|
50
50
|
const gen = executeSelect(node.subquery, tables)
|
|
51
|
-
const
|
|
51
|
+
const { value } = await gen.next() // Start the generator
|
|
52
52
|
gen.return(undefined) // Stop further execution
|
|
53
|
-
if (!
|
|
54
|
-
|
|
55
|
-
const firstRow = first.value
|
|
56
|
-
const firstKey = Object.keys(firstRow)[0]
|
|
57
|
-
return firstRow[firstKey]()
|
|
53
|
+
if (!value) return null
|
|
54
|
+
return value.cells[value.columns[0]]()
|
|
58
55
|
}
|
|
59
56
|
|
|
60
57
|
// Unary operators
|
|
@@ -484,14 +481,11 @@ export async function evaluateExpr({ node, row, tables, rowIndex }) {
|
|
|
484
481
|
if (node.type === 'in') {
|
|
485
482
|
const exprVal = await evaluateExpr({ node: node.expr, row, tables, rowIndex })
|
|
486
483
|
const results = executeSelect(node.subquery, tables)
|
|
487
|
-
/** @type {SqlPrimitive[]} */
|
|
488
|
-
const values = []
|
|
489
484
|
for await (const resRow of results) {
|
|
490
|
-
const
|
|
491
|
-
|
|
492
|
-
values.push(val)
|
|
485
|
+
const value = await resRow.cells[resRow.columns[0]]()
|
|
486
|
+
if (exprVal === value) return true
|
|
493
487
|
}
|
|
494
|
-
return
|
|
488
|
+
return false
|
|
495
489
|
}
|
|
496
490
|
|
|
497
491
|
// EXISTS and NOT EXISTS with subqueries
|
package/src/execute/join.js
CHANGED
|
@@ -4,7 +4,7 @@ import { evaluateExpr } from './expression.js'
|
|
|
4
4
|
import { stringify } from './utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
* @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode } from '../types.js'
|
|
7
|
+
* @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode, AsyncCells } from '../types.js'
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -30,7 +30,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
30
30
|
// Buffer right rows for hash index (required for hash join)
|
|
31
31
|
/** @type {AsyncRow[]} */
|
|
32
32
|
const rightRows = []
|
|
33
|
-
for await (const row of rightSource.
|
|
33
|
+
for await (const row of rightSource.scan()) {
|
|
34
34
|
rightRows.push(row)
|
|
35
35
|
}
|
|
36
36
|
|
|
@@ -39,9 +39,9 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
39
39
|
|
|
40
40
|
// Return streaming data source - left rows stream through without buffering
|
|
41
41
|
return {
|
|
42
|
-
async *
|
|
42
|
+
async *scan() {
|
|
43
43
|
yield* hashJoin({
|
|
44
|
-
leftRows: leftSource.
|
|
44
|
+
leftRows: leftSource.scan(), // Stream directly, not buffered
|
|
45
45
|
rightRows,
|
|
46
46
|
join,
|
|
47
47
|
leftTable: currentLeftTable,
|
|
@@ -55,7 +55,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
55
55
|
// Multiple joins: buffer intermediate results, stream final join
|
|
56
56
|
/** @type {AsyncRow[]} */
|
|
57
57
|
let leftRows = []
|
|
58
|
-
for await (const row of leftSource.
|
|
58
|
+
for await (const row of leftSource.scan()) {
|
|
59
59
|
leftRows.push(row)
|
|
60
60
|
}
|
|
61
61
|
|
|
@@ -69,7 +69,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
69
69
|
|
|
70
70
|
/** @type {AsyncRow[]} */
|
|
71
71
|
const rightRows = []
|
|
72
|
-
for await (const row of rightSource.
|
|
72
|
+
for await (const row of rightSource.scan()) {
|
|
73
73
|
rightRows.push(row)
|
|
74
74
|
}
|
|
75
75
|
|
|
@@ -105,7 +105,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
105
105
|
|
|
106
106
|
/** @type {AsyncRow[]} */
|
|
107
107
|
const rightRows = []
|
|
108
|
-
for await (const row of rightSource.
|
|
108
|
+
for await (const row of rightSource.scan()) {
|
|
109
109
|
rightRows.push(row)
|
|
110
110
|
}
|
|
111
111
|
|
|
@@ -113,7 +113,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
113
113
|
const lastRightTableName = lastJoin.alias ?? lastJoin.table
|
|
114
114
|
|
|
115
115
|
return {
|
|
116
|
-
async *
|
|
116
|
+
async *scan() {
|
|
117
117
|
yield* hashJoin({
|
|
118
118
|
leftRows,
|
|
119
119
|
rightRows,
|
|
@@ -172,12 +172,12 @@ function extractJoinKeys(onCondition, leftTable, rightTable) {
|
|
|
172
172
|
* @returns {AsyncRow}
|
|
173
173
|
*/
|
|
174
174
|
function createNullRow(columnNames) {
|
|
175
|
-
/** @type {
|
|
176
|
-
const
|
|
175
|
+
/** @type {AsyncCells} */
|
|
176
|
+
const cells = {}
|
|
177
177
|
for (const col of columnNames) {
|
|
178
|
-
|
|
178
|
+
cells[col] = () => Promise.resolve(null)
|
|
179
179
|
}
|
|
180
|
-
return
|
|
180
|
+
return { columns: columnNames, cells }
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/**
|
|
@@ -190,33 +190,35 @@ function createNullRow(columnNames) {
|
|
|
190
190
|
* @returns {AsyncRow}
|
|
191
191
|
*/
|
|
192
192
|
function mergeRows(leftRow, rightRow, leftTable, rightTable) {
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
const columns = []
|
|
194
|
+
/** @type {AsyncCells} */
|
|
195
|
+
const cells = {}
|
|
195
196
|
|
|
196
197
|
// Add left table columns with prefix
|
|
197
|
-
for (const [key, cell] of Object.entries(leftRow)) {
|
|
198
|
+
for (const [key, cell] of Object.entries(leftRow.cells)) {
|
|
198
199
|
// Skip already-prefixed keys (from previous joins)
|
|
199
200
|
if (!key.includes('.')) {
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
merged[key] = cell
|
|
201
|
+
const alias = `${leftTable}.${key}`
|
|
202
|
+
cells[alias] = cell
|
|
203
203
|
}
|
|
204
|
-
// Also keep unqualified name for convenience
|
|
205
|
-
|
|
204
|
+
// Also keep unqualified name for convenience
|
|
205
|
+
columns.push(key)
|
|
206
|
+
cells[key] = cell
|
|
206
207
|
}
|
|
207
208
|
|
|
208
209
|
// Add right table columns with prefix
|
|
209
|
-
for (const [key, cell] of Object.entries(rightRow)) {
|
|
210
|
+
for (const [key, cell] of Object.entries(rightRow.cells)) {
|
|
210
211
|
if (!key.includes('.')) {
|
|
211
|
-
|
|
212
|
+
cells[`${rightTable}.${key}`] = cell
|
|
212
213
|
} else {
|
|
213
|
-
|
|
214
|
+
cells[key] = cell
|
|
214
215
|
}
|
|
215
216
|
// Unqualified name (overwrites if same name exists in left table)
|
|
216
|
-
|
|
217
|
+
columns.push(key)
|
|
218
|
+
cells[key] = cell
|
|
217
219
|
}
|
|
218
220
|
|
|
219
|
-
return
|
|
221
|
+
return { columns, cells }
|
|
220
222
|
}
|
|
221
223
|
|
|
222
224
|
/**
|
|
@@ -245,7 +247,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
245
247
|
const keys = extractJoinKeys(onCondition, leftTable, rightTable)
|
|
246
248
|
|
|
247
249
|
// Get column names for NULL row generation (right side is always buffered)
|
|
248
|
-
const rightCols = rightRows.length ?
|
|
250
|
+
const rightCols = rightRows.length ? rightRows[0].columns : []
|
|
249
251
|
const rightPrefixedCols = rightCols.flatMap(col =>
|
|
250
252
|
col.includes('.') ? [col] : [`${rightTable}.${col}`, col]
|
|
251
253
|
)
|
|
@@ -281,8 +283,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
281
283
|
for await (const leftRow of leftRows) {
|
|
282
284
|
// Capture left column info from first row (for NULL row generation)
|
|
283
285
|
if (!leftPrefixedCols) {
|
|
284
|
-
|
|
285
|
-
leftPrefixedCols = leftCols.flatMap(col =>
|
|
286
|
+
leftPrefixedCols = leftRow.columns.flatMap(col =>
|
|
286
287
|
col.includes('.') ? [col] : [`${leftTable}.${col}`, col]
|
|
287
288
|
)
|
|
288
289
|
}
|
|
@@ -323,8 +324,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
323
324
|
for await (const leftRow of leftRows) {
|
|
324
325
|
// Capture left column info from first row (for NULL row generation)
|
|
325
326
|
if (!leftPrefixedCols) {
|
|
326
|
-
|
|
327
|
-
leftPrefixedCols = leftCols.flatMap(col =>
|
|
327
|
+
leftPrefixedCols = leftRow.columns.flatMap(col =>
|
|
328
328
|
col.includes('.') ? [col] : [`${leftTable}.${col}`, col]
|
|
329
329
|
)
|
|
330
330
|
}
|
package/src/execute/math.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { SqlPrimitive } from '../types.js'
|
|
2
|
+
* @import { MathFunc, SqlPrimitive } from '../types.js'
|
|
3
3
|
*/
|
|
4
4
|
import { argCountError } from '../validationErrors.js'
|
|
5
5
|
|
|
@@ -7,7 +7,7 @@ import { argCountError } from '../validationErrors.js'
|
|
|
7
7
|
* Evaluate a math function
|
|
8
8
|
*
|
|
9
9
|
* @param {Object} options
|
|
10
|
-
* @param {
|
|
10
|
+
* @param {MathFunc} options.funcName - Uppercase function name
|
|
11
11
|
* @param {SqlPrimitive[]} options.args - Function arguments
|
|
12
12
|
* @param {number} options.positionStart - Start position in query
|
|
13
13
|
* @param {number} options.positionEnd - End position in query
|
|
@@ -161,5 +161,180 @@ export function evaluateMathFunc({ funcName, args, positionStart, positionEnd, r
|
|
|
161
161
|
return Math.sqrt(Number(val))
|
|
162
162
|
}
|
|
163
163
|
|
|
164
|
-
|
|
164
|
+
if (funcName === 'SIN') {
|
|
165
|
+
if (args.length !== 1) {
|
|
166
|
+
throw argCountError({
|
|
167
|
+
funcName: 'SIN',
|
|
168
|
+
expected: 1,
|
|
169
|
+
received: args.length,
|
|
170
|
+
positionStart,
|
|
171
|
+
positionEnd,
|
|
172
|
+
rowNumber,
|
|
173
|
+
})
|
|
174
|
+
}
|
|
175
|
+
const val = args[0]
|
|
176
|
+
if (val == null) return null
|
|
177
|
+
return Math.sin(Number(val))
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (funcName === 'COS') {
|
|
181
|
+
if (args.length !== 1) {
|
|
182
|
+
throw argCountError({
|
|
183
|
+
funcName: 'COS',
|
|
184
|
+
expected: 1,
|
|
185
|
+
received: args.length,
|
|
186
|
+
positionStart,
|
|
187
|
+
positionEnd,
|
|
188
|
+
rowNumber,
|
|
189
|
+
})
|
|
190
|
+
}
|
|
191
|
+
const val = args[0]
|
|
192
|
+
if (val == null) return null
|
|
193
|
+
return Math.cos(Number(val))
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (funcName === 'TAN') {
|
|
197
|
+
if (args.length !== 1) {
|
|
198
|
+
throw argCountError({
|
|
199
|
+
funcName: 'TAN',
|
|
200
|
+
expected: 1,
|
|
201
|
+
received: args.length,
|
|
202
|
+
positionStart,
|
|
203
|
+
positionEnd,
|
|
204
|
+
rowNumber,
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
const val = args[0]
|
|
208
|
+
if (val == null) return null
|
|
209
|
+
return Math.tan(Number(val))
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (funcName === 'COT') {
|
|
213
|
+
if (args.length !== 1) {
|
|
214
|
+
throw argCountError({
|
|
215
|
+
funcName: 'COT',
|
|
216
|
+
expected: 1,
|
|
217
|
+
received: args.length,
|
|
218
|
+
positionStart,
|
|
219
|
+
positionEnd,
|
|
220
|
+
rowNumber,
|
|
221
|
+
})
|
|
222
|
+
}
|
|
223
|
+
const val = args[0]
|
|
224
|
+
if (val == null) return null
|
|
225
|
+
return 1 / Math.tan(Number(val))
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (funcName === 'ASIN') {
|
|
229
|
+
if (args.length !== 1) {
|
|
230
|
+
throw argCountError({
|
|
231
|
+
funcName: 'ASIN',
|
|
232
|
+
expected: 1,
|
|
233
|
+
received: args.length,
|
|
234
|
+
positionStart,
|
|
235
|
+
positionEnd,
|
|
236
|
+
rowNumber,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
const val = args[0]
|
|
240
|
+
if (val == null) return null
|
|
241
|
+
return Math.asin(Number(val))
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (funcName === 'ACOS') {
|
|
245
|
+
if (args.length !== 1) {
|
|
246
|
+
throw argCountError({
|
|
247
|
+
funcName: 'ACOS',
|
|
248
|
+
expected: 1,
|
|
249
|
+
received: args.length,
|
|
250
|
+
positionStart,
|
|
251
|
+
positionEnd,
|
|
252
|
+
rowNumber,
|
|
253
|
+
})
|
|
254
|
+
}
|
|
255
|
+
const val = args[0]
|
|
256
|
+
if (val == null) return null
|
|
257
|
+
return Math.acos(Number(val))
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
if (funcName === 'ATAN') {
|
|
261
|
+
if (args.length !== 1) {
|
|
262
|
+
throw argCountError({
|
|
263
|
+
funcName: 'ATAN',
|
|
264
|
+
expected: 1,
|
|
265
|
+
received: args.length,
|
|
266
|
+
positionStart,
|
|
267
|
+
positionEnd,
|
|
268
|
+
rowNumber,
|
|
269
|
+
})
|
|
270
|
+
}
|
|
271
|
+
const val = args[0]
|
|
272
|
+
if (val == null) return null
|
|
273
|
+
return Math.atan(Number(val))
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (funcName === 'ATAN2') {
|
|
277
|
+
if (args.length !== 2) {
|
|
278
|
+
throw argCountError({
|
|
279
|
+
funcName: 'ATAN2',
|
|
280
|
+
expected: 2,
|
|
281
|
+
received: args.length,
|
|
282
|
+
positionStart,
|
|
283
|
+
positionEnd,
|
|
284
|
+
rowNumber,
|
|
285
|
+
})
|
|
286
|
+
}
|
|
287
|
+
const y = args[0]
|
|
288
|
+
const x = args[1]
|
|
289
|
+
if (y == null || x == null) return null
|
|
290
|
+
return Math.atan2(Number(y), Number(x))
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (funcName === 'DEGREES') {
|
|
294
|
+
if (args.length !== 1) {
|
|
295
|
+
throw argCountError({
|
|
296
|
+
funcName: 'DEGREES',
|
|
297
|
+
expected: 1,
|
|
298
|
+
received: args.length,
|
|
299
|
+
positionStart,
|
|
300
|
+
positionEnd,
|
|
301
|
+
rowNumber,
|
|
302
|
+
})
|
|
303
|
+
}
|
|
304
|
+
const val = args[0]
|
|
305
|
+
if (val == null) return null
|
|
306
|
+
return Number(val) * 180 / Math.PI
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (funcName === 'RADIANS') {
|
|
310
|
+
if (args.length !== 1) {
|
|
311
|
+
throw argCountError({
|
|
312
|
+
funcName: 'RADIANS',
|
|
313
|
+
expected: 1,
|
|
314
|
+
received: args.length,
|
|
315
|
+
positionStart,
|
|
316
|
+
positionEnd,
|
|
317
|
+
rowNumber,
|
|
318
|
+
})
|
|
319
|
+
}
|
|
320
|
+
const val = args[0]
|
|
321
|
+
if (val == null) return null
|
|
322
|
+
return Number(val) * Math.PI / 180
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (funcName === 'PI') {
|
|
326
|
+
if (args.length !== 0) {
|
|
327
|
+
throw argCountError({
|
|
328
|
+
funcName: 'PI',
|
|
329
|
+
expected: 0,
|
|
330
|
+
received: args.length,
|
|
331
|
+
positionStart,
|
|
332
|
+
positionEnd,
|
|
333
|
+
rowNumber,
|
|
334
|
+
})
|
|
335
|
+
}
|
|
336
|
+
return Math.PI
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return null
|
|
165
340
|
}
|
package/src/execute/utils.js
CHANGED
|
@@ -97,8 +97,8 @@ export async function collect(asyncRows) {
|
|
|
97
97
|
for await (const asyncRow of asyncRows) {
|
|
98
98
|
/** @type {Record<string, SqlPrimitive>} */
|
|
99
99
|
const item = {}
|
|
100
|
-
for (const
|
|
101
|
-
item[key] = await
|
|
100
|
+
for (const key of asyncRow.columns) {
|
|
101
|
+
item[key] = await asyncRow.cells[key]()
|
|
102
102
|
}
|
|
103
103
|
results.push(item)
|
|
104
104
|
}
|
package/src/executionErrors.js
CHANGED
|
@@ -7,12 +7,13 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export class ExecutionError extends Error {
|
|
9
9
|
/**
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {number}
|
|
13
|
-
* @param {number}
|
|
10
|
+
* @param {Object} options
|
|
11
|
+
* @param {string} options.message - Human-readable error message
|
|
12
|
+
* @param {number} options.positionStart - Start position (0-based character offset)
|
|
13
|
+
* @param {number} options.positionEnd - End position (exclusive, 0-based character offset)
|
|
14
|
+
* @param {number} [options.rowNumber] - 1-based row number where error occurred
|
|
14
15
|
*/
|
|
15
|
-
constructor(message, positionStart, positionEnd, rowNumber) {
|
|
16
|
+
constructor({ message, positionStart, positionEnd, rowNumber }) {
|
|
16
17
|
const rowSuffix = rowNumber != null ? ` (row ${rowNumber})` : ''
|
|
17
18
|
super(message + rowSuffix)
|
|
18
19
|
this.name = 'ExecutionError'
|
|
@@ -45,7 +46,7 @@ export function tableNotFoundError({ tableName }) {
|
|
|
45
46
|
* @returns {ExecutionError}
|
|
46
47
|
*/
|
|
47
48
|
export function invalidContextError({ item, validContext, positionStart, positionEnd, rowNumber }) {
|
|
48
|
-
return new ExecutionError(`${item} can only be used with ${validContext}`, positionStart, positionEnd, rowNumber)
|
|
49
|
+
return new ExecutionError({ message: `${item} can only be used with ${validContext}`, positionStart, positionEnd, rowNumber })
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/**
|
package/src/parseErrors.js
CHANGED
|
@@ -7,11 +7,12 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export class ParseError extends Error {
|
|
9
9
|
/**
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {
|
|
12
|
-
* @param {number}
|
|
10
|
+
* @param {Object} options
|
|
11
|
+
* @param {string} options.message - Human-readable error message
|
|
12
|
+
* @param {number} options.positionStart - Start position (0-based character offset)
|
|
13
|
+
* @param {number} options.positionEnd - End position (exclusive, 0-based character offset)
|
|
13
14
|
*/
|
|
14
|
-
constructor(message, positionStart, positionEnd) {
|
|
15
|
+
constructor({ message, positionStart, positionEnd }) {
|
|
15
16
|
super(message)
|
|
16
17
|
this.name = 'ParseError'
|
|
17
18
|
this.positionStart = positionStart
|
|
@@ -32,7 +33,7 @@ export class ParseError extends Error {
|
|
|
32
33
|
*/
|
|
33
34
|
export function syntaxError({ expected, received, positionStart, positionEnd, after }) {
|
|
34
35
|
const afterClause = after ? ` after "${after}"` : ''
|
|
35
|
-
return new ParseError(`Expected ${expected}${afterClause} but found ${received} at position ${positionStart}`, positionStart, positionEnd)
|
|
36
|
+
return new ParseError({ message: `Expected ${expected}${afterClause} but found ${received} at position ${positionStart}`, positionStart, positionEnd })
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
/**
|
|
@@ -45,7 +46,7 @@ export function syntaxError({ expected, received, positionStart, positionEnd, af
|
|
|
45
46
|
*/
|
|
46
47
|
export function unterminatedError(type, positionStart, positionEnd) {
|
|
47
48
|
const name = type === 'string' ? 'string literal' : 'identifier'
|
|
48
|
-
return new ParseError(`Unterminated ${name} starting at position ${positionStart}`, positionStart, positionEnd)
|
|
49
|
+
return new ParseError({ message: `Unterminated ${name} starting at position ${positionStart}`, positionStart, positionEnd })
|
|
49
50
|
}
|
|
50
51
|
|
|
51
52
|
/**
|
|
@@ -61,7 +62,7 @@ export function unterminatedError(type, positionStart, positionEnd) {
|
|
|
61
62
|
*/
|
|
62
63
|
export function invalidLiteralError({ type, value, positionStart, positionEnd, validValues }) {
|
|
63
64
|
const suffix = validValues ? `. Valid values: ${validValues}` : ''
|
|
64
|
-
return new ParseError(`Invalid ${type} ${value} at position ${positionStart}${suffix}`, positionStart, positionEnd)
|
|
65
|
+
return new ParseError({ message: `Invalid ${type} ${value} at position ${positionStart}${suffix}`, positionStart, positionEnd })
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
/**
|
|
@@ -76,9 +77,9 @@ export function invalidLiteralError({ type, value, positionStart, positionEnd, v
|
|
|
76
77
|
export function unexpectedCharError({ char, positionStart, expectsSelect = false }) {
|
|
77
78
|
const positionEnd = positionStart + 1
|
|
78
79
|
if (expectsSelect) {
|
|
79
|
-
return new ParseError(`Expected SELECT but found "${char}" at position ${positionStart}. Queries must start with SELECT.`, positionStart, positionEnd)
|
|
80
|
+
return new ParseError({ message: `Expected SELECT but found "${char}" at position ${positionStart}. Queries must start with SELECT.`, positionStart, positionEnd })
|
|
80
81
|
}
|
|
81
|
-
return new ParseError(`Unexpected character "${char}" at position ${positionStart}`, positionStart, positionEnd)
|
|
82
|
+
return new ParseError({ message: `Unexpected character "${char}" at position ${positionStart}`, positionStart, positionEnd })
|
|
82
83
|
}
|
|
83
84
|
|
|
84
85
|
/**
|
|
@@ -95,11 +96,11 @@ export function unknownFunctionError({ funcName, positionStart, positionEnd, val
|
|
|
95
96
|
const supported = validFunctions ||
|
|
96
97
|
'COUNT, SUM, AVG, MIN, MAX, UPPER, LOWER, CONCAT, LENGTH, SUBSTRING, TRIM, REPLACE, FLOOR, CEIL, ABS, MOD, EXP, LN, LOG10, POWER, SQRT, JSON_OBJECT, JSON_VALUE, JSON_QUERY, JSON_ARRAYAGG'
|
|
97
98
|
|
|
98
|
-
return new ParseError(
|
|
99
|
-
`Unknown function "${funcName}" at position ${positionStart}. Supported: ${supported}`,
|
|
99
|
+
return new ParseError({
|
|
100
|
+
message: `Unknown function "${funcName}" at position ${positionStart}. Supported: ${supported}`,
|
|
100
101
|
positionStart,
|
|
101
|
-
positionEnd
|
|
102
|
-
)
|
|
102
|
+
positionEnd,
|
|
103
|
+
})
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
@@ -113,5 +114,5 @@ export function unknownFunctionError({ funcName, positionStart, positionEnd, val
|
|
|
113
114
|
* @returns {ParseError}
|
|
114
115
|
*/
|
|
115
116
|
export function missingClauseError({ missing, context, positionStart, positionEnd }) {
|
|
116
|
-
return new ParseError(`${context} requires ${missing}`, positionStart ?? 0, positionEnd ?? 0)
|
|
117
|
+
return new ParseError({ message: `${context} requires ${missing}`, positionStart: positionStart ?? 0, positionEnd: positionEnd ?? 0 })
|
|
117
118
|
}
|
package/src/types.d.ts
CHANGED
|
@@ -20,16 +20,20 @@ export interface QueryHints {
|
|
|
20
20
|
* Provides an async iterator over rows.
|
|
21
21
|
*/
|
|
22
22
|
export interface AsyncDataSource {
|
|
23
|
-
|
|
23
|
+
scan(hints?: QueryHints): AsyncIterable<AsyncRow>
|
|
24
24
|
}
|
|
25
|
-
export
|
|
25
|
+
export interface AsyncRow {
|
|
26
|
+
columns: string[]
|
|
27
|
+
cells: AsyncCells
|
|
28
|
+
}
|
|
29
|
+
export type AsyncCells = Record<string, AsyncCell>
|
|
26
30
|
export type AsyncCell = () => Promise<SqlPrimitive>
|
|
27
31
|
|
|
28
32
|
export type Row = Record<string, SqlPrimitive>[]
|
|
29
33
|
|
|
30
34
|
export interface ExecuteSqlOptions {
|
|
31
35
|
tables: Record<string, Row | AsyncDataSource>
|
|
32
|
-
query: string
|
|
36
|
+
query: string | SelectStatement
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
export type SqlPrimitive =
|
|
@@ -188,6 +192,17 @@ export type MathFunc =
|
|
|
188
192
|
| 'LOG10'
|
|
189
193
|
| 'POWER'
|
|
190
194
|
| 'SQRT'
|
|
195
|
+
| 'SIN'
|
|
196
|
+
| 'COS'
|
|
197
|
+
| 'TAN'
|
|
198
|
+
| 'COT'
|
|
199
|
+
| 'ASIN'
|
|
200
|
+
| 'ACOS'
|
|
201
|
+
| 'ATAN'
|
|
202
|
+
| 'ATAN2'
|
|
203
|
+
| 'DEGREES'
|
|
204
|
+
| 'RADIANS'
|
|
205
|
+
| 'PI'
|
|
191
206
|
|
|
192
207
|
export type StringFunc =
|
|
193
208
|
| 'UPPER'
|
package/src/validation.js
CHANGED
package/src/validationErrors.js
CHANGED
|
@@ -38,6 +38,17 @@ const FUNCTION_SIGNATURES = {
|
|
|
38
38
|
LOG10: 'number',
|
|
39
39
|
POWER: 'base, exponent',
|
|
40
40
|
SQRT: 'number',
|
|
41
|
+
SIN: 'radians',
|
|
42
|
+
COS: 'radians',
|
|
43
|
+
TAN: 'radians',
|
|
44
|
+
COT: 'radians',
|
|
45
|
+
ASIN: 'number',
|
|
46
|
+
ACOS: 'number',
|
|
47
|
+
ATAN: 'number',
|
|
48
|
+
ATAN2: 'y, x',
|
|
49
|
+
DEGREES: 'radians',
|
|
50
|
+
RADIANS: 'degrees',
|
|
51
|
+
PI: '',
|
|
41
52
|
|
|
42
53
|
// JSON functions
|
|
43
54
|
JSON_VALUE: 'expression, path',
|
|
@@ -74,7 +85,7 @@ export function argCountError({ funcName, expected, received, positionStart, pos
|
|
|
74
85
|
expectedStr = `${expected} argument`
|
|
75
86
|
}
|
|
76
87
|
|
|
77
|
-
return new ExecutionError(`${funcName}(${signature}) function requires ${expectedStr}, got ${received}`, positionStart, positionEnd, rowNumber)
|
|
88
|
+
return new ExecutionError({ message: `${funcName}(${signature}) function requires ${expectedStr}, got ${received}`, positionStart, positionEnd, rowNumber })
|
|
78
89
|
}
|
|
79
90
|
|
|
80
91
|
/**
|
|
@@ -92,7 +103,7 @@ export function argCountError({ funcName, expected, received, positionStart, pos
|
|
|
92
103
|
export function argValueError({ funcName, message, positionStart, positionEnd, hint, rowNumber }) {
|
|
93
104
|
const signature = FUNCTION_SIGNATURES[funcName] ?? ''
|
|
94
105
|
const suffix = hint ? `. ${hint}` : ''
|
|
95
|
-
return new ExecutionError(`${funcName}(${signature}): ${message}${suffix}`, positionStart, positionEnd, rowNumber)
|
|
106
|
+
return new ExecutionError({ message: `${funcName}(${signature}): ${message}${suffix}`, positionStart, positionEnd, rowNumber })
|
|
96
107
|
}
|
|
97
108
|
|
|
98
109
|
/**
|
|
@@ -123,5 +134,5 @@ export function castError({ toType, positionStart, positionEnd, fromType, rowNum
|
|
|
123
134
|
? `Cannot CAST ${fromType} to ${toType}`
|
|
124
135
|
: `Unsupported CAST to type ${toType}`
|
|
125
136
|
|
|
126
|
-
return new ExecutionError(`${message}. Supported types: TEXT, VARCHAR, INTEGER, INT, BIGINT, FLOAT, REAL, DOUBLE, BOOLEAN`, positionStart, positionEnd, rowNumber)
|
|
137
|
+
return new ExecutionError({ message: `${message}. Supported types: TEXT, VARCHAR, INTEGER, INT, BIGINT, FLOAT, REAL, DOUBLE, BOOLEAN`, positionStart, positionEnd, rowNumber })
|
|
127
138
|
}
|