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.
@@ -1,4 +1,4 @@
1
- import { syntaxError } from '../errors.js'
1
+ import { syntaxError } from '../parseErrors.js'
2
2
 
3
3
  /**
4
4
  * @import { ParserState, Token, TokenType } from '../types.js'
@@ -31,12 +31,22 @@ export function peekToken(state, offset) {
31
31
  */
32
32
  export function consume(state) {
33
33
  const tok = current(state)
34
+ state.lastPos = tok.positionEnd
34
35
  if (state.pos < state.tokens.length - 1) {
35
36
  state.pos += 1
36
37
  }
37
38
  return tok
38
39
  }
39
40
 
41
+ /**
42
+ * Gets the position after the last consumed token.
43
+ * @param {ParserState} state
44
+ * @returns {number}
45
+ */
46
+ export function lastPosition(state) {
47
+ return state.lastPos ?? 0
48
+ }
49
+
40
50
  /**
41
51
  * @param {ParserState} state
42
52
  * @param {TokenType} type
@@ -83,12 +93,12 @@ export function expectIdentifier(state) {
83
93
  * Helper function to create consistent parser error messages.
84
94
  * @param {ParserState} state
85
95
  * @param {string} expected - Description of what was expected
86
- * @returns {Error}
96
+ * @returns {import('../parseErrors.js').ParseError}
87
97
  */
88
98
  export function parseError(state, expected) {
89
99
  const tok = current(state)
90
100
  const prevToken = state.tokens[state.pos - 1]
91
101
  const after = prevToken ? prevToken.originalValue ?? prevToken.value : undefined
92
102
  const received = tok.type === 'eof' ? 'end of query' : `"${tok.originalValue ?? tok.value}"`
93
- return syntaxError({ expected, received, position: tok.position, after })
103
+ return syntaxError({ expected, received, positionStart: tok.positionStart, positionEnd: tok.positionEnd, after })
94
104
  }
@@ -2,7 +2,7 @@ import {
2
2
  invalidLiteralError,
3
3
  unexpectedCharError,
4
4
  unterminatedError,
5
- } from '../errors.js'
5
+ } from '../parseErrors.js'
6
6
 
7
7
  /**
8
8
  * @import { Token } from '../types.d.ts'
@@ -117,24 +117,26 @@ export function tokenize(sql) {
117
117
  return {
118
118
  type: 'number',
119
119
  value: text,
120
- position: startPos,
120
+ positionStart: startPos,
121
+ positionEnd: i,
121
122
  numericValue: BigInt(text.slice(0, -1)),
122
123
  }
123
124
  } catch {
124
- throw invalidLiteralError({ type: 'bigint', value: text.slice(0, -1), position: startPos })
125
+ throw invalidLiteralError({ type: 'bigint', value: text.slice(0, -1), positionStart: startPos, positionEnd: i })
125
126
  }
126
127
  }
127
128
  if (isAlpha(peek())) {
128
- throw invalidLiteralError({ type: 'number', value: text + peek(), position: startPos })
129
+ throw invalidLiteralError({ type: 'number', value: text + peek(), positionStart: startPos, positionEnd: i + 1 })
129
130
  }
130
131
  const num = parseFloat(text)
131
132
  if (isNaN(num)) {
132
- throw invalidLiteralError({ type: 'number', value: text, position: startPos })
133
+ throw invalidLiteralError({ type: 'number', value: text, positionStart: startPos, positionEnd: i })
133
134
  }
134
135
  return {
135
136
  type: 'number',
136
137
  value: text,
137
- position: startPos,
138
+ positionStart: startPos,
139
+ positionEnd: i,
138
140
  numericValue: num,
139
141
  }
140
142
  }
@@ -204,13 +206,15 @@ export function tokenize(sql) {
204
206
  type: 'keyword',
205
207
  value: upper,
206
208
  originalValue: text,
207
- position: pos,
209
+ positionStart: pos,
210
+ positionEnd: i,
208
211
  })
209
212
  } else {
210
213
  tokens.push({
211
214
  type: 'identifier',
212
215
  value: text,
213
- position: pos,
216
+ positionStart: pos,
217
+ positionEnd: i,
214
218
  })
215
219
  }
216
220
  continue
@@ -222,7 +226,7 @@ export function tokenize(sql) {
222
226
  let text = ''
223
227
  while (i <= length) {
224
228
  if (i === length) {
225
- throw unterminatedError('string', pos)
229
+ throw unterminatedError('string', pos, length)
226
230
  }
227
231
  const c = nextChar()
228
232
  if (c === quote) {
@@ -239,7 +243,8 @@ export function tokenize(sql) {
239
243
  tokens.push({
240
244
  type: 'string',
241
245
  value: text,
242
- position: pos,
246
+ positionStart: pos,
247
+ positionEnd: i,
243
248
  })
244
249
  continue
245
250
  }
@@ -250,7 +255,7 @@ export function tokenize(sql) {
250
255
  let text = ''
251
256
  while (i <= length) {
252
257
  if (i === length) {
253
- throw unterminatedError('identifier', pos)
258
+ throw unterminatedError('identifier', pos, length)
254
259
  }
255
260
  const c = nextChar()
256
261
  if (c === quote) {
@@ -267,7 +272,8 @@ export function tokenize(sql) {
267
272
  tokens.push({
268
273
  type: 'identifier',
269
274
  value: text,
270
- position: pos,
275
+ positionStart: pos,
276
+ positionEnd: i,
271
277
  })
272
278
  continue
273
279
  }
@@ -283,7 +289,8 @@ export function tokenize(sql) {
283
289
  tokens.push({
284
290
  type: 'operator',
285
291
  value: op,
286
- position: pos,
292
+ positionStart: pos,
293
+ positionEnd: i,
287
294
  })
288
295
  continue
289
296
  }
@@ -294,7 +301,8 @@ export function tokenize(sql) {
294
301
  tokens.push({
295
302
  type: 'operator',
296
303
  value: ch,
297
- position: pos,
304
+ positionStart: pos,
305
+ positionEnd: i,
298
306
  })
299
307
  continue
300
308
  }
@@ -304,7 +312,8 @@ export function tokenize(sql) {
304
312
  tokens.push({
305
313
  type: 'comma',
306
314
  value: ',',
307
- position: pos,
315
+ positionStart: pos,
316
+ positionEnd: i,
308
317
  })
309
318
  continue
310
319
  }
@@ -314,7 +323,8 @@ export function tokenize(sql) {
314
323
  tokens.push({
315
324
  type: 'dot',
316
325
  value: '.',
317
- position: pos,
326
+ positionStart: pos,
327
+ positionEnd: i,
318
328
  })
319
329
  continue
320
330
  }
@@ -324,7 +334,8 @@ export function tokenize(sql) {
324
334
  tokens.push({
325
335
  type: 'paren',
326
336
  value: ch,
327
- position: pos,
337
+ positionStart: pos,
338
+ positionEnd: i,
328
339
  })
329
340
  continue
330
341
  }
@@ -334,21 +345,23 @@ export function tokenize(sql) {
334
345
  tokens.push({
335
346
  type: 'semicolon',
336
347
  value: ';',
337
- position: pos,
348
+ positionStart: pos,
349
+ positionEnd: i,
338
350
  })
339
351
  continue
340
352
  }
341
353
 
342
354
  if (tokens.length === 0) {
343
- throw unexpectedCharError(ch, pos, true)
355
+ throw unexpectedCharError({ char: ch, positionStart: pos, expectsSelect: true })
344
356
  }
345
- throw unexpectedCharError(ch, pos)
357
+ throw unexpectedCharError({ char: ch, positionStart: pos })
346
358
  }
347
359
 
348
360
  tokens.push({
349
361
  type: 'eof',
350
362
  value: '',
351
- position: length,
363
+ positionStart: length,
364
+ positionEnd: length,
352
365
  })
353
366
 
354
367
  return tokens
@@ -0,0 +1,117 @@
1
+ // ============================================================================
2
+ // PARSE ERRORS - Issues during SQL tokenization and parsing
3
+ // ============================================================================
4
+
5
+ /**
6
+ * Structured parse error with position range.
7
+ */
8
+ export class ParseError extends Error {
9
+ /**
10
+ * @param {string} message - Human-readable error message
11
+ * @param {number} positionStart - Start position (0-based character offset)
12
+ * @param {number} positionEnd - End position (exclusive, 0-based character offset)
13
+ */
14
+ constructor(message, positionStart, positionEnd) {
15
+ super(message)
16
+ this.name = 'ParseError'
17
+ this.positionStart = positionStart
18
+ this.positionEnd = positionEnd
19
+ }
20
+ }
21
+
22
+ /**
23
+ * General syntax error for unexpected tokens.
24
+ *
25
+ * @param {Object} options
26
+ * @param {string} options.expected - Description of what was expected
27
+ * @param {string} options.received - What was actually found
28
+ * @param {number} options.positionStart - Start character position in query
29
+ * @param {number} options.positionEnd - End character position in query
30
+ * @param {string} [options.after] - What token came before (for context)
31
+ * @returns {ParseError}
32
+ */
33
+ export function syntaxError({ expected, received, positionStart, positionEnd, after }) {
34
+ const afterClause = after ? ` after "${after}"` : ''
35
+ return new ParseError(`Expected ${expected}${afterClause} but found ${received} at position ${positionStart}`, positionStart, positionEnd)
36
+ }
37
+
38
+ /**
39
+ * Error for unterminated literals (strings, identifiers).
40
+ *
41
+ * @param {'string' | 'identifier'} type - Type of unterminated literal
42
+ * @param {number} positionStart - Starting position
43
+ * @param {number} positionEnd - End position
44
+ * @returns {ParseError}
45
+ */
46
+ export function unterminatedError(type, positionStart, positionEnd) {
47
+ const name = type === 'string' ? 'string literal' : 'identifier'
48
+ return new ParseError(`Unterminated ${name} starting at position ${positionStart}`, positionStart, positionEnd)
49
+ }
50
+
51
+ /**
52
+ * Error for invalid literals (numbers, intervals, etc).
53
+ *
54
+ * @param {Object} options
55
+ * @param {string} options.type - Type of invalid literal (e.g., 'number', 'interval value', 'interval unit')
56
+ * @param {string} options.value - The invalid value
57
+ * @param {number} options.positionStart - Start position in query
58
+ * @param {number} options.positionEnd - End position in query
59
+ * @param {string} [options.validValues] - List of valid values (for enums like interval units)
60
+ * @returns {ParseError}
61
+ */
62
+ export function invalidLiteralError({ type, value, positionStart, positionEnd, validValues }) {
63
+ const suffix = validValues ? `. Valid values: ${validValues}` : ''
64
+ return new ParseError(`Invalid ${type} ${value} at position ${positionStart}${suffix}`, positionStart, positionEnd)
65
+ }
66
+
67
+ /**
68
+ * Error for unexpected characters during tokenization.
69
+ *
70
+ * @param {Object} options
71
+ * @param {string} options.char - The unexpected character
72
+ * @param {number} options.positionStart - Position in query
73
+ * @param {boolean} [options.expectsSelect=false] - Whether SELECT was expected (first token)
74
+ * @returns {ParseError}
75
+ */
76
+ export function unexpectedCharError({ char, positionStart, expectsSelect = false }) {
77
+ const positionEnd = positionStart + 1
78
+ if (expectsSelect) {
79
+ return new ParseError(`Expected SELECT but found "${char}" at position ${positionStart}. Queries must start with SELECT.`, positionStart, positionEnd)
80
+ }
81
+ return new ParseError(`Unexpected character "${char}" at position ${positionStart}`, positionStart, positionEnd)
82
+ }
83
+
84
+ /**
85
+ * Error for unknown/unsupported functions.
86
+ *
87
+ * @param {Object} options
88
+ * @param {string} options.funcName - The unknown function name
89
+ * @param {number} options.positionStart - Start position in query
90
+ * @param {number} options.positionEnd - End position in query
91
+ * @param {string} [options.validFunctions] - List of valid functions
92
+ * @returns {ParseError}
93
+ */
94
+ export function unknownFunctionError({ funcName, positionStart, positionEnd, validFunctions }) {
95
+ const supported = validFunctions ||
96
+ '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
+ return new ParseError(
99
+ `Unknown function "${funcName}" at position ${positionStart}. Supported: ${supported}`,
100
+ positionStart,
101
+ positionEnd
102
+ )
103
+ }
104
+
105
+ /**
106
+ * Error for missing required clause or structure.
107
+ *
108
+ * @param {Object} options
109
+ * @param {string} options.missing - What is missing (e.g., 'WHEN clause', 'FROM clause', 'ON condition')
110
+ * @param {string} options.context - Where it's missing from (e.g., 'CASE expression', 'SELECT statement', 'JOIN')
111
+ * @param {number} [options.positionStart] - Start position in query
112
+ * @param {number} [options.positionEnd] - End position in query
113
+ * @returns {ParseError}
114
+ */
115
+ export function missingClauseError({ missing, context, positionStart, positionEnd }) {
116
+ return new ParseError(`${context} requires ${missing}`, positionStart ?? 0, positionEnd ?? 0)
117
+ }
package/src/types.d.ts CHANGED
@@ -6,6 +6,11 @@
6
6
  export interface QueryHints {
7
7
  columns?: string[] // columns needed
8
8
  where?: ExprNode // where clause
9
+ // important: only apply limit/offset if where is fully applied by the data source
10
+ // otherwise, the data source must return at least enough rows to ensure the engine
11
+ // can apply limit/offset correctly after filtering
12
+ // even with offset, the datasource must return rows starting from offset 0
13
+ // but doesn't need to resolve async rows before the offset
9
14
  limit?: number
10
15
  offset?: number
11
16
  }
@@ -68,54 +73,59 @@ export type BinaryOp = 'AND' | 'OR' | 'LIKE' | ComparisonOp | ArithmeticOp
68
73
 
69
74
  export type ComparisonOp = '=' | '!=' | '<>' | '<' | '>' | '<=' | '>='
70
75
 
71
- export interface LiteralNode {
76
+ export interface ExprNodeBase {
77
+ positionStart: number
78
+ positionEnd: number
79
+ }
80
+
81
+ export interface LiteralNode extends ExprNodeBase {
72
82
  type: 'literal'
73
83
  value: SqlPrimitive
74
84
  }
75
85
 
76
- export interface IdentifierNode {
86
+ export interface IdentifierNode extends ExprNodeBase {
77
87
  type: 'identifier'
78
88
  name: string
79
89
  }
80
90
 
81
- export interface UnaryNode {
91
+ export interface UnaryNode extends ExprNodeBase {
82
92
  type: 'unary'
83
93
  op: 'NOT' | 'IS NULL' | 'IS NOT NULL' | '-'
84
94
  argument: ExprNode
85
95
  }
86
96
 
87
- export interface BinaryNode {
97
+ export interface BinaryNode extends ExprNodeBase {
88
98
  type: 'binary'
89
99
  op: BinaryOp
90
100
  left: ExprNode
91
101
  right: ExprNode
92
102
  }
93
103
 
94
- export interface FunctionNode {
104
+ export interface FunctionNode extends ExprNodeBase {
95
105
  type: 'function'
96
106
  name: string
97
107
  args: ExprNode[]
98
108
  }
99
109
 
100
- export interface CastNode {
110
+ export interface CastNode extends ExprNodeBase {
101
111
  type: 'cast'
102
112
  expr: ExprNode
103
113
  toType: string
104
114
  }
105
115
 
106
- export interface InSubqueryNode {
116
+ export interface InSubqueryNode extends ExprNodeBase {
107
117
  type: 'in'
108
118
  expr: ExprNode
109
119
  subquery: SelectStatement
110
120
  }
111
121
 
112
- export interface InValuesNode {
122
+ export interface InValuesNode extends ExprNodeBase {
113
123
  type: 'in valuelist'
114
124
  expr: ExprNode
115
125
  values: ExprNode[]
116
126
  }
117
127
 
118
- export interface ExistsNode {
128
+ export interface ExistsNode extends ExprNodeBase {
119
129
  type: 'exists' | 'not exists'
120
130
  subquery: SelectStatement
121
131
  }
@@ -125,21 +135,21 @@ export interface WhenClause {
125
135
  result: ExprNode
126
136
  }
127
137
 
128
- export interface CaseNode {
138
+ export interface CaseNode extends ExprNodeBase {
129
139
  type: 'case'
130
140
  caseExpr?: ExprNode
131
141
  whenClauses: WhenClause[]
132
142
  elseResult?: ExprNode
133
143
  }
134
144
 
135
- export interface SubqueryNode {
145
+ export interface SubqueryNode extends ExprNodeBase {
136
146
  type: 'subquery'
137
147
  subquery: SelectStatement
138
148
  }
139
149
 
140
150
  export type IntervalUnit = 'DAY' | 'MONTH' | 'YEAR' | 'HOUR' | 'MINUTE' | 'SECOND'
141
151
 
142
- export interface IntervalNode {
152
+ export interface IntervalNode extends ExprNodeBase {
143
153
  type: 'interval'
144
154
  value: number
145
155
  unit: IntervalUnit
@@ -167,6 +177,18 @@ export interface StarColumn {
167
177
 
168
178
  export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'JSON_ARRAYAGG'
169
179
 
180
+ export type MathFunc =
181
+ | 'FLOOR'
182
+ | 'CEIL'
183
+ | 'CEILING'
184
+ | 'ABS'
185
+ | 'MOD'
186
+ | 'EXP'
187
+ | 'LN'
188
+ | 'LOG10'
189
+ | 'POWER'
190
+ | 'SQRT'
191
+
170
192
  export type StringFunc =
171
193
  | 'UPPER'
172
194
  | 'LOWER'
@@ -228,6 +250,7 @@ export interface JoinClause {
228
250
  export interface ParserState {
229
251
  tokens: Token[]
230
252
  pos: number
253
+ lastPos?: number
231
254
  }
232
255
 
233
256
  // Tokenizer types
@@ -246,7 +269,8 @@ export type TokenType =
246
269
  export interface Token {
247
270
  type: TokenType
248
271
  value: string
249
- position: number
272
+ positionStart: number
273
+ positionEnd: number
250
274
  numericValue?: number | bigint
251
275
  originalValue?: string
252
276
  }
package/src/validation.js CHANGED
@@ -1,6 +1,6 @@
1
1
 
2
2
  /**
3
- * @import {AggregateFunc, BinaryOp, ComparisonOp, IntervalUnit, StringFunc} from './types.js'
3
+ * @import {AggregateFunc, BinaryOp, ComparisonOp, IntervalUnit, MathFunc, StringFunc} from './types.js'
4
4
  * @param {string} name
5
5
  * @returns {name is AggregateFunc}
6
6
  */
@@ -8,6 +8,17 @@ export function isAggregateFunc(name) {
8
8
  return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'JSON_ARRAYAGG'].includes(name)
9
9
  }
10
10
 
11
+ /**
12
+ * @param {string} name
13
+ * @returns {name is MathFunc}
14
+ */
15
+ export function isMathFunc(name) {
16
+ return [
17
+ 'FLOOR', 'CEIL', 'CEILING', 'ABS', 'MOD',
18
+ 'EXP', 'LN', 'LOG10', 'POWER', 'SQRT',
19
+ ].includes(name)
20
+ }
21
+
11
22
  /**
12
23
  * @param {string} name
13
24
  * @returns {name is IntervalUnit}
@@ -0,0 +1,127 @@
1
+ import { ExecutionError } from './executionErrors.js'
2
+
3
+ // ============================================================================
4
+ // VALIDATION ERRORS - Function argument and type validation
5
+ // ============================================================================
6
+
7
+ /**
8
+ * Function signatures for helpful error messages.
9
+ * Maps function name to its parameter signature.
10
+ * @type {Record<string, string>}
11
+ */
12
+ const FUNCTION_SIGNATURES = {
13
+ // String functions
14
+ UPPER: 'string',
15
+ LOWER: 'string',
16
+ LENGTH: 'string',
17
+ TRIM: 'string',
18
+ REPLACE: 'string, search, replacement',
19
+ SUBSTRING: 'string, start[, length]',
20
+ SUBSTR: 'string, start[, length]',
21
+ CONCAT: 'value1, value2[, ...]',
22
+
23
+ // Date/time functions
24
+ RANDOM: '',
25
+ RAND: '',
26
+ CURRENT_DATE: '',
27
+ CURRENT_TIME: '',
28
+ CURRENT_TIMESTAMP: '',
29
+
30
+ // Math functions
31
+ FLOOR: 'number',
32
+ CEIL: 'number',
33
+ CEILING: 'number',
34
+ ABS: 'number',
35
+ MOD: 'dividend, divisor',
36
+ EXP: 'number',
37
+ LN: 'number',
38
+ LOG10: 'number',
39
+ POWER: 'base, exponent',
40
+ SQRT: 'number',
41
+
42
+ // JSON functions
43
+ JSON_VALUE: 'expression, path',
44
+ JSON_QUERY: 'expression, path',
45
+ JSON_OBJECT: 'key1, value1[, ...]',
46
+ JSON_ARRAYAGG: 'expression',
47
+
48
+ // Aggregate functions
49
+ COUNT: 'expression',
50
+ SUM: 'expression',
51
+ AVG: 'expression',
52
+ MIN: 'expression',
53
+ MAX: 'expression',
54
+ }
55
+
56
+ /**
57
+ * Error for wrong number of function arguments.
58
+ *
59
+ * @param {Object} options
60
+ * @param {string} options.funcName - The function name
61
+ * @param {number | string} options.expected - Expected count (number or range like "2 or 3")
62
+ * @param {number} options.received - Actual argument count
63
+ * @param {number} options.positionStart - Start position in query
64
+ * @param {number} options.positionEnd - End position in query
65
+ * @param {number} [options.rowNumber] - 1-based row number where error occurred
66
+ * @returns {ExecutionError}
67
+ */
68
+ export function argCountError({ funcName, expected, received, positionStart, positionEnd, rowNumber }) {
69
+ const signature = FUNCTION_SIGNATURES[funcName] ?? ''
70
+ let expectedStr = `${expected} arguments`
71
+ if (expected === 0) expectedStr = 'no arguments'
72
+ if (expected === 1) expectedStr = '1 argument'
73
+ if (typeof expected === 'string' && expected.endsWith(' 1')) {
74
+ expectedStr = `${expected} argument`
75
+ }
76
+
77
+ return new ExecutionError(`${funcName}(${signature}) function requires ${expectedStr}, got ${received}`, positionStart, positionEnd, rowNumber)
78
+ }
79
+
80
+ /**
81
+ * Error for invalid argument type or value.
82
+ *
83
+ * @param {Object} options
84
+ * @param {string} options.funcName - The function name
85
+ * @param {string} options.message - Specific error message
86
+ * @param {number} options.positionStart - Start position in query
87
+ * @param {number} options.positionEnd - End position in query
88
+ * @param {string} [options.hint] - Recovery hint
89
+ * @param {number} [options.rowNumber] - 1-based row number where error occurred
90
+ * @returns {ExecutionError}
91
+ */
92
+ export function argValueError({ funcName, message, positionStart, positionEnd, hint, rowNumber }) {
93
+ const signature = FUNCTION_SIGNATURES[funcName] ?? ''
94
+ const suffix = hint ? `. ${hint}` : ''
95
+ return new ExecutionError(`${funcName}(${signature}): ${message}${suffix}`, positionStart, positionEnd, rowNumber)
96
+ }
97
+
98
+ /**
99
+ * Error for aggregate function misuse (e.g., SUM(*)).
100
+ *
101
+ * @param {Object} options
102
+ * @param {string} options.funcName - The aggregate function
103
+ * @param {string} options.issue - What's wrong (e.g., "(*) is not supported")
104
+ * @returns {Error}
105
+ */
106
+ export function aggregateError({ funcName, issue }) {
107
+ return new Error(`${funcName}${issue}. Only COUNT supports *. Use a column name for ${funcName}.`)
108
+ }
109
+
110
+ /**
111
+ * Error for unsupported CAST type.
112
+ *
113
+ * @param {Object} options
114
+ * @param {string} options.toType - The unsupported target type
115
+ * @param {number} options.positionStart - Start position in query
116
+ * @param {number} options.positionEnd - End position in query
117
+ * @param {string} [options.fromType] - The source type (optional)
118
+ * @param {number} [options.rowNumber] - 1-based row number where error occurred
119
+ * @returns {ExecutionError}
120
+ */
121
+ export function castError({ toType, positionStart, positionEnd, fromType, rowNumber }) {
122
+ const message = fromType
123
+ ? `Cannot CAST ${fromType} to ${toType}`
124
+ : `Unsupported CAST to type ${toType}`
125
+
126
+ return new ExecutionError(`${message}. Supported types: TEXT, VARCHAR, INTEGER, INT, BIGINT, FLOAT, REAL, DOUBLE, BOOLEAN`, positionStart, positionEnd, rowNumber)
127
+ }