squirreling 0.10.0 → 0.10.2

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,9 +1,3 @@
1
- // ============================================================================
2
- // PARSE ERRORS - Issues during SQL tokenization and parsing
3
- // ============================================================================
4
-
5
- import { FUNCTION_SIGNATURES } from './validationErrors.js'
6
-
7
1
  /**
8
2
  * Structured parse error with position range.
9
3
  */
@@ -11,8 +5,8 @@ export class ParseError extends Error {
11
5
  /**
12
6
  * @param {Object} options
13
7
  * @param {string} options.message - Human-readable error message
14
- * @param {number} options.positionStart - Start position (0-based character offset)
15
- * @param {number} options.positionEnd - End position (exclusive, 0-based character offset)
8
+ * @param {number} options.positionStart
9
+ * @param {number} options.positionEnd
16
10
  */
17
11
  constructor({ message, positionStart, positionEnd }) {
18
12
  super(message)
@@ -28,8 +22,8 @@ export class ParseError extends Error {
28
22
  * @param {Object} options
29
23
  * @param {string} options.expected - Description of what was expected
30
24
  * @param {string} options.received - What was actually found
31
- * @param {number} options.positionStart - Start character position in query
32
- * @param {number} options.positionEnd - End character position in query
25
+ * @param {number} options.positionStart
26
+ * @param {number} options.positionEnd
33
27
  * @param {string} [options.after] - What token came before (for context)
34
28
  * @returns {ParseError}
35
29
  */
@@ -43,8 +37,8 @@ export function syntaxError({ expected, received, positionStart, positionEnd, af
43
37
  *
44
38
  * @param {Object} options
45
39
  * @param {'string' | 'identifier'} options.type - Type of unterminated literal
46
- * @param {number} options.positionStart - Starting position
47
- * @param {number} options.positionEnd - End position
40
+ * @param {number} options.positionStart
41
+ * @param {number} options.positionEnd
48
42
  * @returns {ParseError}
49
43
  */
50
44
  export function unterminatedError({ type, positionStart, positionEnd }) {
@@ -58,8 +52,8 @@ export function unterminatedError({ type, positionStart, positionEnd }) {
58
52
  * @param {Object} options
59
53
  * @param {string} options.type - Type of invalid literal (e.g., 'number', 'interval value', 'interval unit')
60
54
  * @param {string} options.value - The invalid value
61
- * @param {number} options.positionStart - Start position in query
62
- * @param {number} options.positionEnd - End position in query
55
+ * @param {number} options.positionStart
56
+ * @param {number} options.positionEnd
63
57
  * @param {string} [options.validValues] - List of valid values (for enums like interval units)
64
58
  * @returns {ParseError}
65
59
  */
@@ -73,11 +67,11 @@ export function invalidLiteralError({ type, value, positionStart, positionEnd, v
73
67
  *
74
68
  * @param {Object} options
75
69
  * @param {string} options.char - The unexpected character
76
- * @param {number} options.positionStart - Position in query
77
- * @param {boolean} [options.expectsSelect=false] - Whether SELECT was expected (first token)
70
+ * @param {number} options.positionStart
71
+ * @param {boolean} options.expectsSelect - Whether SELECT was expected (first token)
78
72
  * @returns {ParseError}
79
73
  */
80
- export function unexpectedCharError({ char, positionStart, expectsSelect = false }) {
74
+ export function unexpectedCharError({ char, positionStart, expectsSelect }) {
81
75
  const positionEnd = positionStart + 1
82
76
  if (expectsSelect) {
83
77
  return new ParseError({ message: `Expected SELECT but found "${char}" at position ${positionStart}. Queries must start with SELECT or WITH.`, positionStart, positionEnd })
@@ -90,44 +84,14 @@ export function unexpectedCharError({ char, positionStart, expectsSelect = false
90
84
  *
91
85
  * @param {Object} options
92
86
  * @param {string} options.funcName - The unknown function name
93
- * @param {number} options.positionStart - Start position in query
94
- * @param {number} options.positionEnd - End position in query
95
- * @param {string} [options.validFunctions] - List of valid functions
96
- * @returns {ParseError}
97
- */
98
- export function unknownFunctionError({ funcName, positionStart, positionEnd, validFunctions }) {
99
- const supported = validFunctions ||
100
- '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'
101
-
102
- return new ParseError({
103
- message: `Unknown function "${funcName}" at position ${positionStart}. Supported: ${supported}`,
104
- positionStart,
105
- positionEnd,
106
- })
107
- }
108
-
109
- /**
110
- * Error for wrong number of function arguments at parse time.
111
- *
112
- * @param {Object} options
113
- * @param {string} options.funcName - The function name
114
- * @param {number | string} options.expected - Expected count (number or range like "2 to 3")
115
- * @param {number} options.received - Actual argument count
116
- * @param {number} options.positionStart - Start position in query
117
- * @param {number} options.positionEnd - End position in query
87
+ * @param {number} options.positionStart
88
+ * @param {number} options.positionEnd
118
89
  * @returns {ParseError}
119
90
  */
120
- export function argCountParseError({ funcName, expected, received, positionStart, positionEnd }) {
121
- const signature = FUNCTION_SIGNATURES[funcName] ?? ''
122
- let expectedStr = `${expected} arguments`
123
- if (expected === 0) expectedStr = 'no arguments'
124
- if (expected === 1) expectedStr = '1 argument'
125
- if (typeof expected === 'string' && expected.endsWith(' 1')) {
126
- expectedStr = `${expected} argument`
127
- }
128
-
91
+ export function unknownFunctionError({ funcName, positionStart, positionEnd }) {
92
+ // TODO: suggest similar function names based on edit distance
129
93
  return new ParseError({
130
- message: `${funcName}(${signature}) function requires ${expectedStr}, got ${received}`,
94
+ message: `Unknown function "${funcName}" at position ${positionStart}.`,
131
95
  positionStart,
132
96
  positionEnd,
133
97
  })
@@ -139,12 +103,12 @@ export function argCountParseError({ funcName, expected, received, positionStart
139
103
  * @param {Object} options
140
104
  * @param {string} options.missing - What is missing (e.g., 'WHEN clause', 'FROM clause', 'ON condition')
141
105
  * @param {string} options.context - Where it's missing from (e.g., 'CASE expression', 'SELECT statement', 'JOIN')
142
- * @param {number} options.positionStart - Start position in query
143
- * @param {number} options.positionEnd - End position in query
106
+ * @param {number} options.positionStart
107
+ * @param {number} options.positionEnd
144
108
  * @returns {ParseError}
145
109
  */
146
110
  export function missingClauseError({ missing, context, positionStart, positionEnd }) {
147
- return new ParseError({ message: `${context} requires ${missing}`, positionStart: positionStart ?? 0, positionEnd: positionEnd ?? 0 })
111
+ return new ParseError({ message: `${context} requires ${missing}`, positionStart, positionEnd })
148
112
  }
149
113
 
150
114
  /**
@@ -152,8 +116,8 @@ export function missingClauseError({ missing, context, positionStart, positionEn
152
116
  *
153
117
  * @param {Object} options
154
118
  * @param {string} options.cteName - The duplicate CTE name
155
- * @param {number} options.positionStart - Start position in query
156
- * @param {number} options.positionEnd - End position in query
119
+ * @param {number} options.positionStart
120
+ * @param {number} options.positionEnd
157
121
  * @returns {ParseError}
158
122
  */
159
123
  export function duplicateCTEError({ cteName, positionStart, positionEnd }) {
@@ -0,0 +1,42 @@
1
+ import { ExecutionError } from './executionErrors.js'
2
+
3
+ /**
4
+ * @param {Object} options
5
+ * @param {string} options.table - The missing table name
6
+ * @param {Record<string, any>} options.tables - Available tables object
7
+ * @param {number} [options.positionStart]
8
+ * @param {number} [options.positionEnd]
9
+ * @returns {ExecutionError}
10
+ */
11
+ export function tableNotFoundError({ table, tables, positionStart, positionEnd }) {
12
+ const names = tables ? Object.keys(tables) : []
13
+ const available = names.length
14
+ ? `. Available tables: ${names.join(', ')}`
15
+ : ''
16
+ return new ExecutionError({
17
+ message: `Table "${table}" not found${available}`,
18
+ positionStart,
19
+ positionEnd,
20
+ })
21
+ }
22
+
23
+ /**
24
+ * @param {Object} options
25
+ * @param {string} options.columnName - The missing column name
26
+ * @param {string[]} options.availableColumns - List of available column names
27
+ * @param {number} options.positionStart
28
+ * @param {number} options.positionEnd
29
+ * @param {number} [options.rowIndex] - 1-based row number where error occurred
30
+ * @returns {ExecutionError}
31
+ */
32
+ export function columnNotFoundError({ columnName, availableColumns, positionStart, positionEnd, rowIndex }) {
33
+ const available = availableColumns.length > 0
34
+ ? `. Available columns: ${availableColumns.join(', ')}`
35
+ : ''
36
+ return new ExecutionError({
37
+ message: `Column "${columnName}" not found${available}`,
38
+ positionStart,
39
+ positionEnd,
40
+ rowIndex,
41
+ })
42
+ }
@@ -1,80 +0,0 @@
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.rowIndex] - 1-based row number where error occurred
15
- */
16
- constructor({ message, positionStart, positionEnd, rowIndex }) {
17
- const rowSuffix = rowIndex != null ? ` (row ${rowIndex})` : ''
18
- super(message + rowSuffix)
19
- this.name = 'ExecutionError'
20
- this.positionStart = positionStart
21
- this.positionEnd = positionEnd
22
- this.rowIndex = rowIndex
23
- }
24
- }
25
-
26
- /**
27
- * @param {Object} options
28
- * @param {string} options.tableName - The missing table name
29
- * @returns {Error}
30
- */
31
- export function tableNotFoundError({ tableName }) {
32
- return new Error(`Table "${tableName}" not found. Check spelling or add it to the tables parameter.`)
33
- }
34
-
35
- /**
36
- * Error for invalid context (e.g., INTERVAL without date arithmetic).
37
- *
38
- * @param {Object} options
39
- * @param {string} options.item - What was used incorrectly
40
- * @param {string} options.validContext - Where it can be used
41
- * @param {number} options.positionStart - Start position in query
42
- * @param {number} options.positionEnd - End position in query
43
- * @param {number} [options.rowIndex] - 1-based row number where error occurred
44
- * @returns {ExecutionError}
45
- */
46
- export function invalidContextError({ item, validContext, positionStart, positionEnd, rowIndex }) {
47
- return new ExecutionError({ message: `${item} can only be used with ${validContext}`, positionStart, positionEnd, rowIndex })
48
- }
49
-
50
- /**
51
- * @param {Object} options
52
- * @param {string} options.operation - The unsupported operation
53
- * @param {string} [options.hint] - How to fix it
54
- * @returns {Error}
55
- */
56
- export function unsupportedOperationError({ operation, hint }) {
57
- const suffix = hint ? `. ${hint}` : ''
58
- return new Error(`${operation}${suffix}`)
59
- }
60
-
61
- /**
62
- * @param {Object} options
63
- * @param {string} options.columnName - The missing column name
64
- * @param {string[]} options.availableColumns - List of available column names
65
- * @param {number} options.positionStart - Start position in query
66
- * @param {number} options.positionEnd - End position in query
67
- * @param {number} [options.rowIndex] - 1-based row number where error occurred
68
- * @returns {ExecutionError}
69
- */
70
- export function columnNotFoundError({ columnName, availableColumns, positionStart, positionEnd, rowIndex }) {
71
- const available = availableColumns.length > 0
72
- ? `. Available columns: ${availableColumns.join(', ')}`
73
- : ''
74
- return new ExecutionError({
75
- message: `Column "${columnName}" not found${available}`,
76
- positionStart,
77
- positionEnd,
78
- rowIndex,
79
- })
80
- }
package/src/validation.js DELETED
@@ -1,343 +0,0 @@
1
- import { ParseError } from './parseErrors.js'
2
-
3
- /**
4
- * @import { AggregateFunc, BinaryOp, ExprNode, FunctionNode, IntervalUnit, MathFunc, SpatialFunc, StringFunc, UserDefinedFunction } from './types.js'
5
- * @param {string} name
6
- * @returns {name is AggregateFunc}
7
- */
8
- export function isAggregateFunc(name) {
9
- return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'JSON_ARRAYAGG', 'STDDEV_SAMP', 'STDDEV_POP'].includes(name)
10
- }
11
-
12
- /**
13
- * Finds the first aggregate function call in an expression tree.
14
- * Does not recurse into subqueries (they have their own aggregate scope).
15
- *
16
- * @param {ExprNode | undefined} expr
17
- * @returns {FunctionNode | undefined}
18
- */
19
- export function findAggregate(expr) {
20
- if (!expr) return undefined
21
- if (expr.type === 'function' && isAggregateFunc(expr.name.toUpperCase())) {
22
- return expr
23
- }
24
- if (expr.type === 'binary') {
25
- return findAggregate(expr.left) || findAggregate(expr.right)
26
- }
27
- if (expr.type === 'unary') {
28
- return findAggregate(expr.argument)
29
- }
30
- if (expr.type === 'cast') {
31
- return findAggregate(expr.expr)
32
- }
33
- if (expr.type === 'case') {
34
- if (expr.caseExpr) {
35
- const found = findAggregate(expr.caseExpr)
36
- if (found) return found
37
- }
38
- for (const when of expr.whenClauses) {
39
- const found = findAggregate(when.condition) || findAggregate(when.result)
40
- if (found) return found
41
- }
42
- return findAggregate(expr.elseResult)
43
- }
44
- if (expr.type === 'in valuelist') {
45
- const found = findAggregate(expr.expr)
46
- if (found) return found
47
- for (const val of expr.values) {
48
- const found = findAggregate(val)
49
- if (found) return found
50
- }
51
- }
52
- // Subqueries have their own aggregate scope
53
- return undefined
54
- }
55
-
56
- /**
57
- * Throws a ParseError if the expression contains an aggregate function.
58
- *
59
- * @param {ExprNode | undefined} expr - The expression to check
60
- * @param {string} clause - The clause name (e.g., 'WHERE', 'JOIN ON', 'GROUP BY')
61
- */
62
- export function expectNoAggregate(expr, clause) {
63
- const agg = findAggregate(expr)
64
- if (agg) {
65
- const hint = clause === 'WHERE' ? '. Use HAVING instead.' : ''
66
- throw new ParseError({
67
- message: `Aggregate function ${agg.name.toUpperCase()} is not allowed in ${clause} clause${hint}`,
68
- positionStart: agg.positionStart,
69
- positionEnd: agg.positionEnd,
70
- })
71
- }
72
- }
73
-
74
- /**
75
- * @param {string} name
76
- * @returns {boolean}
77
- */
78
- export function isRegexpFunc(name) {
79
- return ['REGEXP_SUBSTR', 'REGEXP_REPLACE'].includes(name)
80
- }
81
-
82
- /**
83
- * @param {string} name
84
- * @returns {name is SpatialFunc}
85
- */
86
- export function isSpatialFunc(name) {
87
- return [
88
- 'ST_INTERSECTS', 'ST_CONTAINS', 'ST_CONTAINSPROPERLY', 'ST_WITHIN',
89
- 'ST_OVERLAPS', 'ST_TOUCHES', 'ST_EQUALS', 'ST_CROSSES',
90
- 'ST_COVERS', 'ST_COVEREDBY', 'ST_DWITHIN',
91
- 'ST_GEOMFROMTEXT', 'ST_MAKEENVELOPE', 'ST_ASTEXT',
92
- ].includes(name)
93
- }
94
-
95
- /**
96
- * @param {string} name
97
- * @returns {name is MathFunc}
98
- */
99
- export function isMathFunc(name) {
100
- return [
101
- 'FLOOR', 'CEIL', 'CEILING', 'ROUND', 'ABS', 'SIGN', 'MOD', 'EXP', 'LN', 'LOG10', 'POWER', 'SQRT',
102
- 'SIN', 'COS', 'TAN', 'COT', 'ASIN', 'ACOS', 'ATAN', 'ATAN2', 'DEGREES', 'RADIANS', 'PI',
103
- 'RAND', 'RANDOM',
104
- ].includes(name)
105
- }
106
-
107
- /**
108
- * @param {string} name
109
- * @returns {name is IntervalUnit}
110
- */
111
- export function isIntervalUnit(name) {
112
- return ['DAY', 'MONTH', 'YEAR', 'HOUR', 'MINUTE', 'SECOND'].includes(name)
113
- }
114
-
115
- /**
116
- * @param {string} name
117
- * @returns {boolean}
118
- */
119
- export function isExtractField(name) {
120
- return ['YEAR', 'MONTH', 'DAY', 'HOUR', 'MINUTE', 'SECOND', 'DOW', 'EPOCH'].includes(name)
121
- }
122
-
123
- /**
124
- * @param {string} name
125
- * @returns {name is StringFunc}
126
- */
127
- export function isStringFunc(name) {
128
- return [
129
- 'UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'SUBSTRING', 'SUBSTR', 'TRIM',
130
- 'REPLACE', 'LEFT', 'RIGHT', 'INSTR',
131
- ].includes(name)
132
- }
133
-
134
- /**
135
- * @param {string} op
136
- * @returns {op is BinaryOp}
137
- */
138
- export function isBinaryOp(op) {
139
- return ['AND', 'OR', 'LIKE', '=', '!=', '<>', '<', '>', '<=', '>='].includes(op)
140
- }
141
-
142
- /**
143
- * Function argument count specifications.
144
- * min: minimum number of arguments
145
- * max: maximum number of arguments
146
- * @type {Record<string, {min: number, max?: number}>}
147
- */
148
- export const FUNCTION_ARG_COUNTS = {
149
- // String functions
150
- UPPER: { min: 1, max: 1 },
151
- LOWER: { min: 1, max: 1 },
152
- LENGTH: { min: 1, max: 1 },
153
- TRIM: { min: 1, max: 1 },
154
- REPLACE: { min: 3, max: 3 },
155
- SUBSTRING: { min: 2, max: 3 },
156
- SUBSTR: { min: 2, max: 3 },
157
- CONCAT: { min: 1 },
158
- LEFT: { min: 2, max: 2 },
159
- RIGHT: { min: 2, max: 2 },
160
- INSTR: { min: 2, max: 2 },
161
- REGEXP_SUBSTR: { min: 2, max: 4 },
162
- REGEXP_REPLACE: { min: 3, max: 5 },
163
-
164
- // Date/time functions
165
- RANDOM: { min: 0, max: 0 },
166
- RAND: { min: 0, max: 0 },
167
- CURRENT_DATE: { min: 0, max: 0 },
168
- CURRENT_TIME: { min: 0, max: 0 },
169
- CURRENT_TIMESTAMP: { min: 0, max: 0 },
170
- DATE_TRUNC: { min: 2, max: 2 },
171
- DATE_PART: { min: 2, max: 2 },
172
- EXTRACT: { min: 2, max: 2 },
173
-
174
- // Math functions
175
- FLOOR: { min: 1, max: 1 },
176
- CEIL: { min: 1, max: 1 },
177
- CEILING: { min: 1, max: 1 },
178
- ROUND: { min: 1, max: 2 },
179
- ABS: { min: 1, max: 1 },
180
- SIGN: { min: 1, max: 1 },
181
- MOD: { min: 2, max: 2 },
182
- EXP: { min: 1, max: 1 },
183
- LN: { min: 1, max: 1 },
184
- LOG10: { min: 1, max: 1 },
185
- POWER: { min: 2, max: 2 },
186
- SQRT: { min: 1, max: 1 },
187
- SIN: { min: 1, max: 1 },
188
- COS: { min: 1, max: 1 },
189
- TAN: { min: 1, max: 1 },
190
- COT: { min: 1, max: 1 },
191
- ASIN: { min: 1, max: 1 },
192
- ACOS: { min: 1, max: 1 },
193
- ATAN: { min: 1, max: 2 },
194
- ATAN2: { min: 2, max: 2 },
195
- DEGREES: { min: 1, max: 1 },
196
- RADIANS: { min: 1, max: 1 },
197
- PI: { min: 0, max: 0 },
198
-
199
- // JSON functions
200
- JSON_VALUE: { min: 2, max: 2 },
201
- JSON_QUERY: { min: 2, max: 2 },
202
- JSON_OBJECT: { min: 0 },
203
- JSON_ARRAYAGG: { min: 1, max: 1 },
204
-
205
- // Array functions
206
- ARRAY_LENGTH: { min: 1, max: 1 },
207
- ARRAY_POSITION: { min: 2, max: 2 },
208
- ARRAY_SORT: { min: 1, max: 1 },
209
- CARDINALITY: { min: 1, max: 1 },
210
-
211
- // Conditional functions
212
- COALESCE: { min: 1 },
213
- NULLIF: { min: 2, max: 2 },
214
-
215
- // Aggregate functions
216
- COUNT: { min: 1, max: 1 },
217
- SUM: { min: 1, max: 1 },
218
- AVG: { min: 1, max: 1 },
219
- MIN: { min: 1, max: 1 },
220
- MAX: { min: 1, max: 1 },
221
- STDDEV_SAMP: { min: 1, max: 1 },
222
- STDDEV_POP: { min: 1, max: 1 },
223
-
224
- // Spatial predicate functions
225
- ST_INTERSECTS: { min: 2, max: 2 },
226
- ST_CONTAINS: { min: 2, max: 2 },
227
- ST_CONTAINSPROPERLY: { min: 2, max: 2 },
228
- ST_WITHIN: { min: 2, max: 2 },
229
- ST_OVERLAPS: { min: 2, max: 2 },
230
- ST_TOUCHES: { min: 2, max: 2 },
231
- ST_EQUALS: { min: 2, max: 2 },
232
- ST_CROSSES: { min: 2, max: 2 },
233
- ST_COVERS: { min: 2, max: 2 },
234
- ST_COVEREDBY: { min: 2, max: 2 },
235
- ST_DWITHIN: { min: 3, max: 3 },
236
- ST_GEOMFROMTEXT: { min: 1, max: 1 },
237
- ST_MAKEENVELOPE: { min: 4, max: 4 },
238
- ST_ASTEXT: { min: 1, max: 1 },
239
- }
240
-
241
- /**
242
- * Format expected argument count for error messages.
243
- * @param {number} min
244
- * @param {number | undefined} max
245
- * @returns {string | number}
246
- */
247
- function formatExpected(min, max) {
248
- if (max == null) return `at least ${min}`
249
- if (min === max) return min
250
- return `${min} or ${max}`
251
- }
252
-
253
- /**
254
- * Validates function argument count.
255
- * @param {string} funcName - The function name (uppercase)
256
- * @param {number} argCount - Number of arguments provided
257
- * @param {Record<string, UserDefinedFunction>} [functions] - User-defined functions
258
- * @returns {{ valid: boolean, expected: string | number }}
259
- */
260
- export function validateFunctionArgCount(funcName, argCount, functions) {
261
- // Check built-in functions
262
- let spec = FUNCTION_ARG_COUNTS[funcName]
263
-
264
- // Check user-defined functions (case-insensitive)
265
- if (!spec && functions) {
266
- const udfName = Object.keys(functions).find(k => k.toUpperCase() === funcName)
267
- if (udfName) {
268
- spec = functions[udfName].arguments
269
- }
270
- }
271
-
272
- if (!spec) return { valid: true, expected: 0 }
273
-
274
- const { min, max } = spec
275
-
276
- if (argCount < min) {
277
- return { valid: false, expected: formatExpected(min, max) }
278
- }
279
- if (max != null && argCount > max) {
280
- return { valid: false, expected: formatExpected(min, max) }
281
- }
282
-
283
- return { valid: true, expected: formatExpected(min, max) }
284
- }
285
-
286
- /**
287
- * Checks if a function is known (either built-in or user-defined).
288
- * @param {string} funcName - The function name (uppercase)
289
- * @param {Record<string, UserDefinedFunction>} [functions] - User-defined functions
290
- * @returns {boolean}
291
- */
292
- export function isKnownFunction(funcName, functions) {
293
- // Check built-in functions
294
- if (
295
- isAggregateFunc(funcName) ||
296
- isMathFunc(funcName) ||
297
- isStringFunc(funcName) ||
298
- isRegexpFunc(funcName) ||
299
- isSpatialFunc(funcName)
300
- ) {
301
- return true
302
- }
303
-
304
- // Date/time, JSON, conditional, and CAST functions
305
- if ([
306
- 'CURRENT_DATE', 'CURRENT_TIME', 'CURRENT_TIMESTAMP', 'DATE_TRUNC', 'DATE_PART', 'EXTRACT',
307
- 'JSON_VALUE', 'JSON_QUERY', 'JSON_OBJECT',
308
- 'ARRAY_LENGTH', 'ARRAY_POSITION', 'ARRAY_SORT', 'CARDINALITY',
309
- 'COALESCE', 'NULLIF', 'CAST',
310
- ].includes(funcName)) {
311
- return true
312
- }
313
-
314
- // Check user-defined functions (case-insensitive)
315
- if (functions) {
316
- return Object.keys(functions).some(k => k.toUpperCase() === funcName)
317
- }
318
-
319
- return false
320
- }
321
-
322
- // Reserved keywords that cannot be used as identifiers in expressions.
323
- // Non-reserved keywords (e.g. DAY, MONTH, FILTER, ASC) can be used as column alias references.
324
- export const RESERVED_KEYWORDS = new Set([
325
- 'SELECT', 'FROM', 'WHERE', 'WITH',
326
- 'AND', 'OR', 'NOT', 'IS', 'LIKE', 'IN', 'BETWEEN',
327
- 'TRUE', 'FALSE', 'NULL',
328
- 'EXISTS', 'CASE', 'WHEN', 'THEN', 'ELSE', 'END', 'INTERVAL',
329
- 'GROUP', 'BY', 'HAVING', 'ORDER', 'LIMIT', 'OFFSET',
330
- 'AS', 'ALL', 'DISTINCT',
331
- 'JOIN', 'INNER', 'LEFT', 'RIGHT', 'FULL', 'OUTER', 'ON',
332
- ])
333
-
334
- // Keywords that cannot be used as implicit aliases after a column
335
- export const RESERVED_AFTER_COLUMN = new Set([
336
- 'FROM', 'WHERE', 'GROUP', 'HAVING', 'ORDER', 'LIMIT', 'OFFSET',
337
- ])
338
-
339
- // Keywords that cannot be used as table aliases
340
- export const RESERVED_AFTER_TABLE = new Set([
341
- 'WHERE', 'GROUP', 'HAVING', 'ORDER', 'LIMIT', 'OFFSET', 'JOIN', 'INNER',
342
- 'LEFT', 'RIGHT', 'FULL', 'CROSS', 'ON', 'POSITIONAL',
343
- ])