squirreling 0.10.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirreling",
3
- "version": "0.10.0",
3
+ "version": "0.10.1",
4
4
  "description": "Squirreling Async SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -10,7 +10,9 @@
10
10
  "dataset",
11
11
  "hyperparam",
12
12
  "hyparquet",
13
- "parquet"
13
+ "parquet",
14
+ "query",
15
+ "relational"
14
16
  ],
15
17
  "license": "MIT",
16
18
  "repository": {
@@ -37,7 +39,7 @@
37
39
  "test": "vitest run"
38
40
  },
39
41
  "devDependencies": {
40
- "@types/node": "25.3.5",
42
+ "@types/node": "25.4.0",
41
43
  "@vitest/coverage-v8": "4.0.18",
42
44
  "eslint": "9.39.2",
43
45
  "eslint-plugin-jsdoc": "62.7.1",
package/src/ast.d.ts ADDED
@@ -0,0 +1,184 @@
1
+ export type SqlPrimitive =
2
+ | string
3
+ | number
4
+ | bigint
5
+ | boolean
6
+ | Date
7
+ | null
8
+ | SqlPrimitive[]
9
+ | Record<string, any>
10
+
11
+ export interface SelectStatement {
12
+ with?: WithClause
13
+ distinct: boolean
14
+ columns: SelectColumn[]
15
+ from: FromTable | FromSubquery
16
+ joins: JoinClause[]
17
+ where?: ExprNode
18
+ groupBy: ExprNode[]
19
+ having?: ExprNode
20
+ orderBy: OrderByItem[]
21
+ limit?: number
22
+ offset?: number
23
+ }
24
+
25
+ export interface WithClause {
26
+ ctes: CTEDefinition[]
27
+ }
28
+
29
+ export interface CTEDefinition {
30
+ name: string
31
+ query: SelectStatement
32
+ }
33
+
34
+ export interface FromTable extends AstBase {
35
+ kind: 'table'
36
+ table: string
37
+ alias?: string
38
+ }
39
+
40
+ export interface FromSubquery {
41
+ kind: 'subquery'
42
+ query: SelectStatement
43
+ alias: string
44
+ }
45
+
46
+ export type ArithmeticOp = '+' | '-' | '*' | '/' | '%'
47
+
48
+ export type BinaryOp = 'AND' | 'OR' | 'LIKE' | ComparisonOp | ArithmeticOp
49
+
50
+ export type ComparisonOp = '=' | '!=' | '<>' | '<' | '>' | '<=' | '>='
51
+
52
+ export interface LiteralNode extends AstBase {
53
+ type: 'literal'
54
+ value: SqlPrimitive
55
+ }
56
+
57
+ export interface IdentifierNode extends AstBase {
58
+ type: 'identifier'
59
+ name: string
60
+ }
61
+
62
+ export interface UnaryNode extends AstBase {
63
+ type: 'unary'
64
+ op: 'NOT' | 'IS NULL' | 'IS NOT NULL' | '-'
65
+ argument: ExprNode
66
+ }
67
+
68
+ export interface BinaryNode extends AstBase {
69
+ type: 'binary'
70
+ op: BinaryOp
71
+ left: ExprNode
72
+ right: ExprNode
73
+ }
74
+
75
+ export interface FunctionNode extends AstBase {
76
+ type: 'function'
77
+ funcName: string
78
+ args: ExprNode[]
79
+ distinct?: boolean
80
+ filter?: ExprNode
81
+ }
82
+
83
+ export type CastType = 'TEXT' | 'STRING' | 'VARCHAR' | 'INTEGER' | 'INT' | 'BIGINT' | 'FLOAT' | 'REAL' | 'DOUBLE' | 'BOOLEAN' | 'BOOL'
84
+
85
+ export interface CastNode extends AstBase {
86
+ type: 'cast'
87
+ expr: ExprNode
88
+ toType: CastType
89
+ }
90
+
91
+ export interface InSubqueryNode extends AstBase {
92
+ type: 'in'
93
+ expr: ExprNode
94
+ subquery: SelectStatement
95
+ }
96
+
97
+ export interface InValuesNode extends AstBase {
98
+ type: 'in valuelist'
99
+ expr: ExprNode
100
+ values: ExprNode[]
101
+ }
102
+
103
+ export interface ExistsNode extends AstBase {
104
+ type: 'exists' | 'not exists'
105
+ subquery: SelectStatement
106
+ }
107
+
108
+ export interface WhenClause extends AstBase {
109
+ condition: ExprNode
110
+ result: ExprNode
111
+ }
112
+
113
+ export interface CaseNode extends AstBase {
114
+ type: 'case'
115
+ caseExpr?: ExprNode
116
+ whenClauses: WhenClause[]
117
+ elseResult?: ExprNode
118
+ }
119
+
120
+ export interface SubqueryNode extends AstBase {
121
+ type: 'subquery'
122
+ subquery: SelectStatement
123
+ }
124
+
125
+ export type IntervalUnit = 'DAY' | 'MONTH' | 'YEAR' | 'HOUR' | 'MINUTE' | 'SECOND'
126
+
127
+ export interface IntervalNode extends AstBase {
128
+ type: 'interval'
129
+ value: number
130
+ unit: IntervalUnit
131
+ }
132
+
133
+ export interface StarNode extends AstBase {
134
+ type: 'star'
135
+ }
136
+
137
+ export type ExprNode =
138
+ | LiteralNode
139
+ | IdentifierNode
140
+ | UnaryNode
141
+ | BinaryNode
142
+ | FunctionNode
143
+ | CastNode
144
+ | InSubqueryNode
145
+ | InValuesNode
146
+ | ExistsNode
147
+ | CaseNode
148
+ | SubqueryNode
149
+ | IntervalNode
150
+ | StarNode
151
+
152
+ export interface StarColumn {
153
+ kind: 'star'
154
+ table?: string
155
+ }
156
+
157
+ export interface DerivedColumn {
158
+ kind: 'derived'
159
+ expr: ExprNode
160
+ alias?: string
161
+ }
162
+
163
+ export type SelectColumn = StarColumn | DerivedColumn
164
+
165
+ export interface OrderByItem {
166
+ expr: ExprNode
167
+ direction: 'ASC' | 'DESC'
168
+ nulls?: 'FIRST' | 'LAST'
169
+ }
170
+
171
+ export type JoinType = 'INNER' | 'LEFT' | 'RIGHT' | 'FULL' | 'CROSS' | 'POSITIONAL'
172
+
173
+ export interface JoinClause extends AstBase {
174
+ joinType: JoinType
175
+ table: string
176
+ alias?: string
177
+ on?: ExprNode
178
+ }
179
+
180
+ // All AST node derive from this base, which includes position info for error reporting and other purposes
181
+ interface AstBase {
182
+ positionStart: number // start position in query (0-based, inclusive)
183
+ positionEnd: number // end position in query (0-based, exclusive)
184
+ }
@@ -1,9 +1,9 @@
1
1
  import { memorySource } from '../backend/dataSource.js'
2
- import { tableNotFoundError } from '../executionErrors.js'
3
2
  import { derivedAlias } from '../expression/alias.js'
4
3
  import { evaluateExpr } from '../expression/evaluate.js'
5
4
  import { parseSql } from '../parse/parse.js'
6
5
  import { planSql } from '../plan/plan.js'
6
+ import { tableNotFoundError } from '../validation/planErrors.js'
7
7
  import { executeHashAggregate, executeScalarAggregate } from './aggregates.js'
8
8
  import { executeHashJoin, executeNestedLoopJoin, executePositionalJoin } from './join.js'
9
9
  import { executeSort } from './sort.js'
@@ -46,7 +46,7 @@ export async function* executeSql({ tables, query, functions, signal }) {
46
46
  * @yields {AsyncRow}
47
47
  */
48
48
  export async function* executeSelect({ select, context }) {
49
- const plan = planSql({ query: select, functions: context.functions })
49
+ const plan = planSql({ query: select, functions: context.functions, tables: context.tables })
50
50
  yield* executePlan({ plan, context })
51
51
  }
52
52
 
@@ -98,7 +98,7 @@ async function* executeScan(plan, context) {
98
98
  // check table
99
99
  const table = tables[plan.table]
100
100
  if (!table) {
101
- throw tableNotFoundError({ tableName: plan.table })
101
+ throw tableNotFoundError({ table: plan.table, tables })
102
102
  }
103
103
  // check columns
104
104
  const missingColumn = plan.hints.columns?.find(col => !table.columns.includes(col))
@@ -140,7 +140,7 @@ async function* executeScan(plan, context) {
140
140
  async function* executeCount(plan, { tables, signal }) {
141
141
  const table = tables[plan.table]
142
142
  if (!table) {
143
- throw tableNotFoundError({ tableName: plan.table })
143
+ throw tableNotFoundError({ table: plan.table, tables })
144
144
  }
145
145
 
146
146
  // Use source numRows if available
@@ -31,9 +31,9 @@ export function derivedAlias(expr) {
31
31
  if (expr.type === 'function') {
32
32
  // Handle aggregate functions with star (COUNT(*) -> count_all)
33
33
  if (expr.args.length === 1 && expr.args[0].type === 'star') {
34
- return expr.name.toLowerCase() + '_all'
34
+ return expr.funcName.toLowerCase() + '_all'
35
35
  }
36
- return expr.name.toLowerCase() + '_' + expr.args.map(derivedAlias).join('_')
36
+ return expr.funcName.toLowerCase() + '_' + expr.args.map(derivedAlias).join('_')
37
37
  }
38
38
  if (expr.type === 'interval') {
39
39
  return `interval_${expr.value}_${expr.unit.toLowerCase()}`
@@ -1,9 +1,10 @@
1
1
  import { executeSelect } from '../execute/execute.js'
2
2
  import { stringify } from '../execute/utils.js'
3
- import { columnNotFoundError, invalidContextError } from '../executionErrors.js'
4
- import { unknownFunctionError } from '../parseErrors.js'
5
- import { isAggregateFunc, isMathFunc, isRegexpFunc, isSpatialFunc, isStringFunc } from '../validation.js'
6
- import { aggregateError, argValueError, castError } from '../validationErrors.js'
3
+ import { invalidContextError } from '../validation/executionErrors.js'
4
+ import { aggregateError, argValueError, castError } from '../validation/expressionErrors.js'
5
+ import { isAggregateFunc, isMathFunc, isRegexpFunc, isSpatialFunc, isStringFunc } from '../validation/functions.js'
6
+ import { unknownFunctionError } from '../validation/parseErrors.js'
7
+ import { columnNotFoundError } from '../validation/planErrors.js'
7
8
  import { derivedAlias } from './alias.js'
8
9
  import { applyBinaryOp } from './binary.js'
9
10
  import { applyIntervalToDate, dateTrunc, extractField } from './date.js'
@@ -99,7 +100,7 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
99
100
 
100
101
  // Function calls
101
102
  if (node.type === 'function') {
102
- const funcName = node.name.toUpperCase()
103
+ const funcName = node.funcName.toUpperCase()
103
104
 
104
105
  // Handle aggregate functions
105
106
  if (isAggregateFunc(funcName)) {
@@ -110,11 +111,7 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
110
111
  if (row.columns.includes(alias)) {
111
112
  return row.cells[alias]()
112
113
  } else {
113
- throw aggregateError({
114
- funcName,
115
- positionStart: node.positionStart,
116
- positionEnd: node.positionEnd,
117
- })
114
+ throw aggregateError(node)
118
115
  }
119
116
  }
120
117
 
@@ -230,23 +227,11 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
230
227
  : await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, rowIndex, rows, context })))
231
228
 
232
229
  if (isStringFunc(funcName)) {
233
- return evaluateStringFunc({
234
- funcName,
235
- args,
236
- positionStart: node.positionStart,
237
- positionEnd: node.positionEnd,
238
- rowIndex,
239
- })
230
+ return evaluateStringFunc({ funcName, node, args, rowIndex })
240
231
  }
241
232
 
242
233
  if (isRegexpFunc(funcName)) {
243
- return evaluateRegexpFunc({
244
- funcName,
245
- args,
246
- positionStart: node.positionStart,
247
- positionEnd: node.positionEnd,
248
- rowIndex,
249
- })
234
+ return evaluateRegexpFunc({ funcName, node, args, rowIndex })
250
235
  }
251
236
 
252
237
  if (isMathFunc(funcName)) {
@@ -296,10 +281,8 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
296
281
  if (funcName === 'JSON_OBJECT') {
297
282
  if (args.length % 2 !== 0) {
298
283
  throw argValueError({
299
- funcName: 'JSON_OBJECT',
284
+ ...node,
300
285
  message: 'requires an even number of arguments (key-value pairs)',
301
- positionStart: node.positionStart,
302
- positionEnd: node.positionEnd,
303
286
  rowIndex,
304
287
  })
305
288
  }
@@ -310,10 +293,8 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
310
293
  const value = args[i + 1]
311
294
  if (key == null) {
312
295
  throw argValueError({
313
- funcName: 'JSON_OBJECT',
296
+ ...node,
314
297
  message: 'key cannot be null',
315
- positionStart: node.positionStart,
316
- positionEnd: node.positionEnd,
317
298
  hint: 'All keys must be non-null values.',
318
299
  rowIndex,
319
300
  })
@@ -360,10 +341,8 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
360
341
  jsonArg = JSON.parse(jsonArg)
361
342
  } catch {
362
343
  throw argValueError({
363
- funcName,
344
+ ...node,
364
345
  message: 'invalid JSON string',
365
- positionStart: node.positionStart,
366
- positionEnd: node.positionEnd,
367
346
  hint: 'First argument must be valid JSON.',
368
347
  rowIndex,
369
348
  })
@@ -371,10 +350,8 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
371
350
  }
372
351
  if (typeof jsonArg !== 'object' || jsonArg instanceof Date) {
373
352
  throw argValueError({
374
- funcName,
353
+ ...node,
375
354
  message: `first argument must be JSON string or object, got ${typeof jsonArg}`,
376
- positionStart: node.positionStart,
377
- positionEnd: node.positionEnd,
378
355
  rowIndex,
379
356
  })
380
357
  }
@@ -414,30 +391,20 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
414
391
  }
415
392
  }
416
393
 
417
- throw unknownFunctionError({
418
- funcName,
419
- positionStart: node.positionStart,
420
- positionEnd: node.positionEnd,
421
- })
394
+ throw unknownFunctionError(node)
422
395
  }
423
396
 
424
397
  if (node.type === 'cast') {
425
398
  const val = await evaluateExpr({ node: node.expr, row, rowIndex, rows, context })
426
399
  if (val == null) return null
427
- const toType = node.toType.toUpperCase()
400
+ const { toType } = node
428
401
  if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
429
402
  if (typeof val === 'object') return stringify(val)
430
403
  return String(val)
431
404
  }
432
405
  // Can only cast primitives to other primitive types
433
406
  if (typeof val === 'object') {
434
- throw castError({
435
- toType: node.toType,
436
- positionStart: node.positionStart,
437
- positionEnd: node.positionEnd,
438
- fromType: 'object',
439
- rowIndex,
440
- })
407
+ throw castError({ ...node, fromType: 'object', rowIndex })
441
408
  }
442
409
  if (toType === 'INTEGER' || toType === 'INT') {
443
410
  const num = Number(val)
@@ -455,12 +422,7 @@ export async function evaluateExpr({ node, row, rowIndex, rows, context }) {
455
422
  if (toType === 'BOOLEAN' || toType === 'BOOL') {
456
423
  return Boolean(val)
457
424
  }
458
- throw castError({
459
- toType: node.toType,
460
- positionStart: node.positionStart,
461
- positionEnd: node.positionEnd,
462
- rowIndex,
463
- })
425
+ throw castError({ ...node, rowIndex })
464
426
  }
465
427
 
466
428
  // IN and NOT IN with value lists
@@ -1,21 +1,20 @@
1
- import { argValueError } from '../validationErrors.js'
1
+ import { argValueError } from '../validation/expressionErrors.js'
2
2
 
3
3
  /**
4
- * @import { SqlPrimitive } from '../types.js'
4
+ * @import { FunctionNode, RegExpFunction, SqlPrimitive } from '../types.js'
5
5
  */
6
6
 
7
7
  /**
8
8
  * Evaluate a regexp function
9
9
  *
10
10
  * @param {Object} options
11
- * @param {string} options.funcName - Uppercase function name
11
+ * @param {RegExpFunction} options.funcName
12
+ * @param {FunctionNode} options.node
12
13
  * @param {SqlPrimitive[]} options.args - Function arguments
13
- * @param {number} options.positionStart - Start position in SQL string for error reporting
14
- * @param {number} options.positionEnd - End position in SQL string for error reporting
15
- * @param {number} [options.rowIndex] - Row number for error reporting
14
+ * @param {number} options.rowIndex - Row index for error reporting
16
15
  * @returns {SqlPrimitive}
17
16
  */
18
- export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd, rowIndex }) {
17
+ export function evaluateRegexpFunc({ funcName, node, args, rowIndex }) {
19
18
  if (funcName === 'REGEXP_SUBSTR') {
20
19
  const str = args[0]
21
20
  const pattern = args[1]
@@ -29,10 +28,8 @@ export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd,
29
28
  position = Number(args[2])
30
29
  if (!Number.isInteger(position) || position < 1) {
31
30
  throw argValueError({
32
- funcName,
31
+ ...node,
33
32
  message: `position must be a positive integer, got ${args[2]}`,
34
- positionStart,
35
- positionEnd,
36
33
  hint: 'SQL uses 1-based indexing.',
37
34
  rowIndex,
38
35
  })
@@ -45,10 +42,9 @@ export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd,
45
42
  occurrence = Number(args[3])
46
43
  if (!Number.isInteger(occurrence) || occurrence < 1) {
47
44
  throw argValueError({
48
- funcName,
45
+ ...node,
49
46
  message: `occurrence must be a positive integer, got ${args[3]}`,
50
- positionStart,
51
- positionEnd,
47
+ hint: 'SQL uses 1-based indexing.',
52
48
  rowIndex,
53
49
  })
54
50
  }
@@ -60,10 +56,8 @@ export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd,
60
56
  regex = new RegExp(patternStr, 'g')
61
57
  } catch (/** @type {any} */ error) {
62
58
  throw argValueError({
63
- funcName,
59
+ ...node,
64
60
  message: `invalid regex pattern: ${error.message}`,
65
- positionStart,
66
- positionEnd,
67
61
  rowIndex,
68
62
  })
69
63
  }
@@ -99,10 +93,8 @@ export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd,
99
93
  position = Number(args[3])
100
94
  if (!Number.isInteger(position) || position < 1) {
101
95
  throw argValueError({
102
- funcName,
96
+ ...node,
103
97
  message: `position must be a positive integer, got ${args[3]}`,
104
- positionStart,
105
- positionEnd,
106
98
  hint: 'SQL uses 1-based indexing.',
107
99
  rowIndex,
108
100
  })
@@ -115,10 +107,8 @@ export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd,
115
107
  occurrence = Number(args[4])
116
108
  if (!Number.isInteger(occurrence) || occurrence < 0) {
117
109
  throw argValueError({
118
- funcName,
110
+ ...node,
119
111
  message: `occurrence must be a non-negative integer, got ${args[4]}`,
120
- positionStart,
121
- positionEnd,
122
112
  hint: 'Use 0 to replace all occurrences.',
123
113
  rowIndex,
124
114
  })
@@ -131,10 +121,8 @@ export function evaluateRegexpFunc({ funcName, args, positionStart, positionEnd,
131
121
  regex = new RegExp(patternStr, 'g')
132
122
  } catch (/** @type {any} */ error) {
133
123
  throw argValueError({
134
- funcName,
124
+ ...node,
135
125
  message: `invalid regex pattern: ${error.message}`,
136
- positionStart,
137
- positionEnd,
138
126
  rowIndex,
139
127
  })
140
128
  }
@@ -1,30 +1,27 @@
1
1
  /**
2
- * @import { SqlPrimitive, StringFunc } from '../types.js'
2
+ * @import { FunctionNode, SqlPrimitive, StringFunc } from '../types.js'
3
3
  */
4
4
 
5
- import { argValueError } from '../validationErrors.js'
5
+ import { argValueError } from '../validation/expressionErrors.js'
6
6
 
7
7
  /**
8
8
  * Evaluate a string function
9
9
  *
10
10
  * @param {Object} options
11
- * @param {StringFunc} options.funcName - Uppercase function name
11
+ * @param {StringFunc} options.funcName
12
+ * @param {FunctionNode} options.node
12
13
  * @param {SqlPrimitive[]} options.args - Function arguments
13
- * @param {number} options.positionStart - Start position for error reporting
14
- * @param {number} options.positionEnd - End position for error reporting
15
- * @param {number} [options.rowIndex] - Row index for error reporting
14
+ * @param {number} options.rowIndex - Row index for error reporting
16
15
  * @returns {SqlPrimitive}
17
16
  */
18
- export function evaluateStringFunc({ funcName, args, positionStart, positionEnd, rowIndex }) {
17
+ export function evaluateStringFunc({ funcName, node, args, rowIndex }) {
19
18
  if (funcName === 'CONCAT') {
20
19
  // Returns NULL if any argument is NULL
21
20
  if (args.some(a => a == null)) return null
22
21
  if (args.some(a => typeof a === 'object')) {
23
22
  throw argValueError({
24
- funcName: 'CONCAT',
23
+ ...node,
25
24
  message: 'does not support object arguments',
26
- positionStart,
27
- positionEnd,
28
25
  hint: 'Use CAST to convert objects to strings first.',
29
26
  rowIndex,
30
27
  })
@@ -53,10 +50,8 @@ export function evaluateStringFunc({ funcName, args, positionStart, positionEnd,
53
50
  const start = Number(args[1])
54
51
  if (!Number.isInteger(start) || start < 1) {
55
52
  throw argValueError({
56
- funcName,
53
+ ...node,
57
54
  message: `start position must be a positive integer, got ${args[1]}`,
58
- positionStart,
59
- positionEnd,
60
55
  hint: 'SQL uses 1-based indexing.',
61
56
  rowIndex,
62
57
  })
@@ -67,10 +62,9 @@ export function evaluateStringFunc({ funcName, args, positionStart, positionEnd,
67
62
  const len = Number(args[2])
68
63
  if (!Number.isInteger(len) || len < 0) {
69
64
  throw argValueError({
70
- funcName,
65
+ ...node,
71
66
  message: `length must be a non-negative integer, got ${args[2]}`,
72
- positionStart,
73
- positionEnd,
67
+ hint: 'SQL uses 1-based indexing.',
74
68
  rowIndex,
75
69
  })
76
70
  }
@@ -97,10 +91,9 @@ export function evaluateStringFunc({ funcName, args, positionStart, positionEnd,
97
91
  const len = Number(n)
98
92
  if (!Number.isInteger(len) || len < 0) {
99
93
  throw argValueError({
100
- funcName,
94
+ ...node,
101
95
  message: `length must be a non-negative integer, got ${n}`,
102
- positionStart,
103
- positionEnd,
96
+ hint: 'SQL uses 1-based indexing.',
104
97
  rowIndex,
105
98
  })
106
99
  }
@@ -113,10 +106,9 @@ export function evaluateStringFunc({ funcName, args, positionStart, positionEnd,
113
106
  const len = Number(n)
114
107
  if (!Number.isInteger(len) || len < 0) {
115
108
  throw argValueError({
116
- funcName,
109
+ ...node,
117
110
  message: `length must be a non-negative integer, got ${n}`,
118
- positionStart,
119
- positionEnd,
111
+ hint: 'SQL uses 1-based indexing.',
120
112
  rowIndex,
121
113
  })
122
114
  }
package/src/index.d.ts CHANGED
@@ -55,6 +55,7 @@ export function parseSql(options: ParseSqlOptions): SelectStatement
55
55
  * @param options
56
56
  * @param options.query - SQL query string or parsed SelectStatement
57
57
  * @param options.functions - user-defined functions available in the SQL context
58
+ * @param options.tables - optional table metadata for planning
58
59
  * @returns the root of the query plan tree
59
60
  */
60
61
  export function planSql(options: PlanSqlOptions): QueryPlan
@@ -1,5 +1,5 @@
1
- import { syntaxError } from '../parseErrors.js'
2
- import { isBinaryOp } from '../validation.js'
1
+ import { isBinaryOp } from '../validation/functions.js'
2
+ import { syntaxError } from '../validation/parseErrors.js'
3
3
  import { parseAdditive, parseExpression, parseSubquery } from './expression.js'
4
4
  import { consume, current, expect, match, peekToken } from './state.js'
5
5