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
package/src/execute/join.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { missingClauseError
|
|
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
|
|
|
5
6
|
/**
|
|
6
|
-
* @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode } from '../types.js'
|
|
7
|
+
* @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode, AsyncCells } from '../types.js'
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -23,13 +24,13 @@ 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)
|
|
30
31
|
/** @type {AsyncRow[]} */
|
|
31
32
|
const rightRows = []
|
|
32
|
-
for await (const row of rightSource.
|
|
33
|
+
for await (const row of rightSource.scan()) {
|
|
33
34
|
rightRows.push(row)
|
|
34
35
|
}
|
|
35
36
|
|
|
@@ -38,9 +39,9 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
38
39
|
|
|
39
40
|
// Return streaming data source - left rows stream through without buffering
|
|
40
41
|
return {
|
|
41
|
-
async *
|
|
42
|
+
async *scan() {
|
|
42
43
|
yield* hashJoin({
|
|
43
|
-
leftRows: leftSource.
|
|
44
|
+
leftRows: leftSource.scan(), // Stream directly, not buffered
|
|
44
45
|
rightRows,
|
|
45
46
|
join,
|
|
46
47
|
leftTable: currentLeftTable,
|
|
@@ -54,7 +55,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
54
55
|
// Multiple joins: buffer intermediate results, stream final join
|
|
55
56
|
/** @type {AsyncRow[]} */
|
|
56
57
|
let leftRows = []
|
|
57
|
-
for await (const row of leftSource.
|
|
58
|
+
for await (const row of leftSource.scan()) {
|
|
58
59
|
leftRows.push(row)
|
|
59
60
|
}
|
|
60
61
|
|
|
@@ -63,12 +64,12 @@ 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[]} */
|
|
70
71
|
const rightRows = []
|
|
71
|
-
for await (const row of rightSource.
|
|
72
|
+
for await (const row of rightSource.scan()) {
|
|
72
73
|
rightRows.push(row)
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -99,12 +100,12 @@ 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[]} */
|
|
106
107
|
const rightRows = []
|
|
107
|
-
for await (const row of rightSource.
|
|
108
|
+
for await (const row of rightSource.scan()) {
|
|
108
109
|
rightRows.push(row)
|
|
109
110
|
}
|
|
110
111
|
|
|
@@ -112,7 +113,7 @@ export async function executeJoins(leftSource, joins, leftTableName, tables) {
|
|
|
112
113
|
const lastRightTableName = lastJoin.alias ?? lastJoin.table
|
|
113
114
|
|
|
114
115
|
return {
|
|
115
|
-
async *
|
|
116
|
+
async *scan() {
|
|
116
117
|
yield* hashJoin({
|
|
117
118
|
leftRows,
|
|
118
119
|
rightRows,
|
|
@@ -171,12 +172,12 @@ function extractJoinKeys(onCondition, leftTable, rightTable) {
|
|
|
171
172
|
* @returns {AsyncRow}
|
|
172
173
|
*/
|
|
173
174
|
function createNullRow(columnNames) {
|
|
174
|
-
/** @type {
|
|
175
|
-
const
|
|
175
|
+
/** @type {AsyncCells} */
|
|
176
|
+
const cells = {}
|
|
176
177
|
for (const col of columnNames) {
|
|
177
|
-
|
|
178
|
+
cells[col] = () => Promise.resolve(null)
|
|
178
179
|
}
|
|
179
|
-
return
|
|
180
|
+
return { columns: columnNames, cells }
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
/**
|
|
@@ -189,33 +190,35 @@ function createNullRow(columnNames) {
|
|
|
189
190
|
* @returns {AsyncRow}
|
|
190
191
|
*/
|
|
191
192
|
function mergeRows(leftRow, rightRow, leftTable, rightTable) {
|
|
192
|
-
|
|
193
|
-
|
|
193
|
+
const columns = []
|
|
194
|
+
/** @type {AsyncCells} */
|
|
195
|
+
const cells = {}
|
|
194
196
|
|
|
195
197
|
// Add left table columns with prefix
|
|
196
|
-
for (const [key, cell] of Object.entries(leftRow)) {
|
|
198
|
+
for (const [key, cell] of Object.entries(leftRow.cells)) {
|
|
197
199
|
// Skip already-prefixed keys (from previous joins)
|
|
198
200
|
if (!key.includes('.')) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
merged[key] = cell
|
|
201
|
+
const alias = `${leftTable}.${key}`
|
|
202
|
+
cells[alias] = cell
|
|
202
203
|
}
|
|
203
|
-
// Also keep unqualified name for convenience
|
|
204
|
-
|
|
204
|
+
// Also keep unqualified name for convenience
|
|
205
|
+
columns.push(key)
|
|
206
|
+
cells[key] = cell
|
|
205
207
|
}
|
|
206
208
|
|
|
207
209
|
// Add right table columns with prefix
|
|
208
|
-
for (const [key, cell] of Object.entries(rightRow)) {
|
|
210
|
+
for (const [key, cell] of Object.entries(rightRow.cells)) {
|
|
209
211
|
if (!key.includes('.')) {
|
|
210
|
-
|
|
212
|
+
cells[`${rightTable}.${key}`] = cell
|
|
211
213
|
} else {
|
|
212
|
-
|
|
214
|
+
cells[key] = cell
|
|
213
215
|
}
|
|
214
216
|
// Unqualified name (overwrites if same name exists in left table)
|
|
215
|
-
|
|
217
|
+
columns.push(key)
|
|
218
|
+
cells[key] = cell
|
|
216
219
|
}
|
|
217
220
|
|
|
218
|
-
return
|
|
221
|
+
return { columns, cells }
|
|
219
222
|
}
|
|
220
223
|
|
|
221
224
|
/**
|
|
@@ -244,7 +247,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
244
247
|
const keys = extractJoinKeys(onCondition, leftTable, rightTable)
|
|
245
248
|
|
|
246
249
|
// Get column names for NULL row generation (right side is always buffered)
|
|
247
|
-
const rightCols = rightRows.length ?
|
|
250
|
+
const rightCols = rightRows.length ? rightRows[0].columns : []
|
|
248
251
|
const rightPrefixedCols = rightCols.flatMap(col =>
|
|
249
252
|
col.includes('.') ? [col] : [`${rightTable}.${col}`, col]
|
|
250
253
|
)
|
|
@@ -280,8 +283,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
280
283
|
for await (const leftRow of leftRows) {
|
|
281
284
|
// Capture left column info from first row (for NULL row generation)
|
|
282
285
|
if (!leftPrefixedCols) {
|
|
283
|
-
|
|
284
|
-
leftPrefixedCols = leftCols.flatMap(col =>
|
|
286
|
+
leftPrefixedCols = leftRow.columns.flatMap(col =>
|
|
285
287
|
col.includes('.') ? [col] : [`${leftTable}.${col}`, col]
|
|
286
288
|
)
|
|
287
289
|
}
|
|
@@ -322,8 +324,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
322
324
|
for await (const leftRow of leftRows) {
|
|
323
325
|
// Capture left column info from first row (for NULL row generation)
|
|
324
326
|
if (!leftPrefixedCols) {
|
|
325
|
-
|
|
326
|
-
leftPrefixedCols = leftCols.flatMap(col =>
|
|
327
|
+
leftPrefixedCols = leftRow.columns.flatMap(col =>
|
|
327
328
|
col.includes('.') ? [col] : [`${leftTable}.${col}`, col]
|
|
328
329
|
)
|
|
329
330
|
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { MathFunc, SqlPrimitive } from '../types.js'
|
|
3
|
+
*/
|
|
4
|
+
import { argCountError } from '../validationErrors.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Evaluate a math function
|
|
8
|
+
*
|
|
9
|
+
* @param {Object} options
|
|
10
|
+
* @param {MathFunc} options.funcName - Uppercase function name
|
|
11
|
+
* @param {SqlPrimitive[]} options.args - Function arguments
|
|
12
|
+
* @param {number} options.positionStart - Start position in query
|
|
13
|
+
* @param {number} options.positionEnd - End position in query
|
|
14
|
+
* @param {number} [options.rowNumber] - 1-based row number for error reporting
|
|
15
|
+
* @returns {SqlPrimitive} Result
|
|
16
|
+
*/
|
|
17
|
+
export function evaluateMathFunc({ funcName, args, positionStart, positionEnd, rowNumber }) {
|
|
18
|
+
if (funcName === 'FLOOR') {
|
|
19
|
+
if (args.length !== 1) {
|
|
20
|
+
throw argCountError({
|
|
21
|
+
funcName: 'FLOOR',
|
|
22
|
+
expected: 1,
|
|
23
|
+
received: args.length,
|
|
24
|
+
positionStart,
|
|
25
|
+
positionEnd,
|
|
26
|
+
rowNumber,
|
|
27
|
+
})
|
|
28
|
+
}
|
|
29
|
+
const val = args[0]
|
|
30
|
+
if (val == null) return null
|
|
31
|
+
return Math.floor(Number(val))
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (funcName === 'CEIL' || funcName === 'CEILING') {
|
|
35
|
+
if (args.length !== 1) {
|
|
36
|
+
throw argCountError({
|
|
37
|
+
funcName,
|
|
38
|
+
expected: 1,
|
|
39
|
+
received: args.length,
|
|
40
|
+
positionStart,
|
|
41
|
+
positionEnd,
|
|
42
|
+
rowNumber,
|
|
43
|
+
})
|
|
44
|
+
}
|
|
45
|
+
const val = args[0]
|
|
46
|
+
if (val == null) return null
|
|
47
|
+
return Math.ceil(Number(val))
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (funcName === 'ABS') {
|
|
51
|
+
if (args.length !== 1) {
|
|
52
|
+
throw argCountError({
|
|
53
|
+
funcName: 'ABS',
|
|
54
|
+
expected: 1,
|
|
55
|
+
received: args.length,
|
|
56
|
+
positionStart,
|
|
57
|
+
positionEnd,
|
|
58
|
+
rowNumber,
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
const val = args[0]
|
|
62
|
+
if (val == null) return null
|
|
63
|
+
return Math.abs(Number(val))
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (funcName === 'MOD') {
|
|
67
|
+
if (args.length !== 2) {
|
|
68
|
+
throw argCountError({
|
|
69
|
+
funcName: 'MOD',
|
|
70
|
+
expected: 2,
|
|
71
|
+
received: args.length,
|
|
72
|
+
positionStart,
|
|
73
|
+
positionEnd,
|
|
74
|
+
rowNumber,
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
const dividend = args[0]
|
|
78
|
+
const divisor = args[1]
|
|
79
|
+
if (dividend == null || divisor == null) return null
|
|
80
|
+
return Number(dividend) % Number(divisor)
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (funcName === 'EXP') {
|
|
84
|
+
if (args.length !== 1) {
|
|
85
|
+
throw argCountError({
|
|
86
|
+
funcName: 'EXP',
|
|
87
|
+
expected: 1,
|
|
88
|
+
received: args.length,
|
|
89
|
+
positionStart,
|
|
90
|
+
positionEnd,
|
|
91
|
+
rowNumber,
|
|
92
|
+
})
|
|
93
|
+
}
|
|
94
|
+
const val = args[0]
|
|
95
|
+
if (val == null) return null
|
|
96
|
+
return Math.exp(Number(val))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (funcName === 'LN') {
|
|
100
|
+
if (args.length !== 1) {
|
|
101
|
+
throw argCountError({
|
|
102
|
+
funcName: 'LN',
|
|
103
|
+
expected: 1,
|
|
104
|
+
received: args.length,
|
|
105
|
+
positionStart,
|
|
106
|
+
positionEnd,
|
|
107
|
+
rowNumber,
|
|
108
|
+
})
|
|
109
|
+
}
|
|
110
|
+
const val = args[0]
|
|
111
|
+
if (val == null) return null
|
|
112
|
+
return Math.log(Number(val))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (funcName === 'LOG10') {
|
|
116
|
+
if (args.length !== 1) {
|
|
117
|
+
throw argCountError({
|
|
118
|
+
funcName: 'LOG10',
|
|
119
|
+
expected: 1,
|
|
120
|
+
received: args.length,
|
|
121
|
+
positionStart,
|
|
122
|
+
positionEnd,
|
|
123
|
+
rowNumber,
|
|
124
|
+
})
|
|
125
|
+
}
|
|
126
|
+
const val = args[0]
|
|
127
|
+
if (val == null) return null
|
|
128
|
+
return Math.log10(Number(val))
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (funcName === 'POWER') {
|
|
132
|
+
if (args.length !== 2) {
|
|
133
|
+
throw argCountError({
|
|
134
|
+
funcName: 'POWER',
|
|
135
|
+
expected: 2,
|
|
136
|
+
received: args.length,
|
|
137
|
+
positionStart,
|
|
138
|
+
positionEnd,
|
|
139
|
+
rowNumber,
|
|
140
|
+
})
|
|
141
|
+
}
|
|
142
|
+
const base = args[0]
|
|
143
|
+
const exponent = args[1]
|
|
144
|
+
if (base == null || exponent == null) return null
|
|
145
|
+
return Number(base) ** Number(exponent)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (funcName === 'SQRT') {
|
|
149
|
+
if (args.length !== 1) {
|
|
150
|
+
throw argCountError({
|
|
151
|
+
funcName: 'SQRT',
|
|
152
|
+
expected: 1,
|
|
153
|
+
received: args.length,
|
|
154
|
+
positionStart,
|
|
155
|
+
positionEnd,
|
|
156
|
+
rowNumber,
|
|
157
|
+
})
|
|
158
|
+
}
|
|
159
|
+
const val = args[0]
|
|
160
|
+
if (val == null) return null
|
|
161
|
+
return Math.sqrt(Number(val))
|
|
162
|
+
}
|
|
163
|
+
|
|
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
|
|
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
|
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ============================================================================
|
|
2
|
+
// EXECUTION ERRORS - Issues during query execution
|
|
3
|
+
// ============================================================================
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Structured execution error with position range and optional row number.
|
|
7
|
+
*/
|
|
8
|
+
export class ExecutionError extends Error {
|
|
9
|
+
/**
|
|
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
|
|
15
|
+
*/
|
|
16
|
+
constructor({ message, positionStart, positionEnd, rowNumber }) {
|
|
17
|
+
const rowSuffix = rowNumber != null ? ` (row ${rowNumber})` : ''
|
|
18
|
+
super(message + rowSuffix)
|
|
19
|
+
this.name = 'ExecutionError'
|
|
20
|
+
this.positionStart = positionStart
|
|
21
|
+
this.positionEnd = positionEnd
|
|
22
|
+
this.rowNumber = rowNumber
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Error for missing table.
|
|
28
|
+
*
|
|
29
|
+
* @param {Object} options
|
|
30
|
+
* @param {string} options.tableName - The missing table name
|
|
31
|
+
* @returns {Error}
|
|
32
|
+
*/
|
|
33
|
+
export function tableNotFoundError({ tableName }) {
|
|
34
|
+
return new Error(`Table "${tableName}" not found. Check spelling or add it to the tables parameter.`)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Error for invalid context (e.g., INTERVAL without date arithmetic).
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} options
|
|
41
|
+
* @param {string} options.item - What was used incorrectly
|
|
42
|
+
* @param {string} options.validContext - Where it can be used
|
|
43
|
+
* @param {number} options.positionStart - Start position in query
|
|
44
|
+
* @param {number} options.positionEnd - End position in query
|
|
45
|
+
* @param {number} [options.rowNumber] - 1-based row number where error occurred
|
|
46
|
+
* @returns {ExecutionError}
|
|
47
|
+
*/
|
|
48
|
+
export function invalidContextError({ item, validContext, positionStart, positionEnd, rowNumber }) {
|
|
49
|
+
return new ExecutionError({ message: `${item} can only be used with ${validContext}`, positionStart, positionEnd, rowNumber })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Error for unsupported operation combinations.
|
|
54
|
+
*
|
|
55
|
+
* @param {Object} options
|
|
56
|
+
* @param {string} options.operation - The unsupported operation
|
|
57
|
+
* @param {string} [options.hint] - How to fix it
|
|
58
|
+
* @returns {Error}
|
|
59
|
+
*/
|
|
60
|
+
export function unsupportedOperationError({ operation, hint }) {
|
|
61
|
+
const suffix = hint ? `. ${hint}` : ''
|
|
62
|
+
return new Error(`${operation}${suffix}`)
|
|
63
|
+
}
|
package/src/index.js
CHANGED