squirreling 0.7.4 → 0.7.5

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.7.4",
3
+ "version": "0.7.5",
4
4
  "description": "Squirreling SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -40,7 +40,7 @@
40
40
  "@types/node": "25.0.3",
41
41
  "@vitest/coverage-v8": "4.0.16",
42
42
  "eslint": "9.39.2",
43
- "eslint-plugin-jsdoc": "61.5.0",
43
+ "eslint-plugin-jsdoc": "62.0.0",
44
44
  "typescript": "5.9.3",
45
45
  "vitest": "4.0.16"
46
46
  }
@@ -1,5 +1,5 @@
1
1
  import { unknownFunctionError } from '../parseErrors.js'
2
- import { invalidContextError } from '../executionErrors.js'
2
+ import { columnNotFoundError, invalidContextError } from '../executionErrors.js'
3
3
  import { aggregateError, argValueError, castError } from '../validationErrors.js'
4
4
  import { isAggregateFunc, isMathFunc, isRegexpFunc, isStringFunc } from '../validation.js'
5
5
  import { applyIntervalToDate } from './date.js'
@@ -32,17 +32,23 @@ export async function evaluateExpr({ node, row, tables, functions, rowIndex, row
32
32
 
33
33
  if (node.type === 'identifier') {
34
34
  // Try exact match first (handles both qualified and unqualified names)
35
- if (row.cells[node.name]) {
35
+ if (node.name in row.cells) {
36
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.cells[colName]) {
41
+ if (colName && colName in row.cells) {
42
42
  return row.cells[colName]()
43
43
  }
44
44
  }
45
- return null
45
+ throw columnNotFoundError({
46
+ columnName: node.name,
47
+ availableColumns: Object.keys(row.cells),
48
+ positionStart: node.positionStart,
49
+ positionEnd: node.positionEnd,
50
+ rowNumber: rowIndex,
51
+ })
46
52
  }
47
53
 
48
54
  // Scalar subquery - returns a single value
@@ -24,8 +24,6 @@ export class ExecutionError extends Error {
24
24
  }
25
25
 
26
26
  /**
27
- * Error for missing table.
28
- *
29
27
  * @param {Object} options
30
28
  * @param {string} options.tableName - The missing table name
31
29
  * @returns {Error}
@@ -50,8 +48,6 @@ export function invalidContextError({ item, validContext, positionStart, positio
50
48
  }
51
49
 
52
50
  /**
53
- * Error for unsupported operation combinations.
54
- *
55
51
  * @param {Object} options
56
52
  * @param {string} options.operation - The unsupported operation
57
53
  * @param {string} [options.hint] - How to fix it
@@ -61,3 +57,24 @@ export function unsupportedOperationError({ operation, hint }) {
61
57
  const suffix = hint ? `. ${hint}` : ''
62
58
  return new Error(`${operation}${suffix}`)
63
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.rowNumber] - 1-based row number where error occurred
68
+ * @returns {ExecutionError}
69
+ */
70
+ export function columnNotFoundError({ columnName, availableColumns, positionStart, positionEnd, rowNumber }) {
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
+ rowNumber,
79
+ })
80
+ }
@@ -97,29 +97,32 @@ export function parseSql({ query, functions }) {
97
97
  function parseSelectList(state) {
98
98
  /** @type {SelectColumn[]} */
99
99
  const cols = []
100
- const tok = current(state)
101
100
 
102
- // Check for qualified asterisk (table.*)
103
- if (tok.type === 'identifier') {
104
- const next = peekToken(state, 1)
105
- const nextNext = peekToken(state, 2)
106
- if (next.type === 'dot' && nextNext.type === 'operator' && nextNext.value === '*') {
107
- const tableTok = consume(state) // consume table name
108
- consume(state) // consume dot
109
- consume(state) // consume asterisk
110
- cols.push({ kind: 'star', table: tableTok.value })
111
- return cols
101
+ while (true) {
102
+ const tok = current(state)
103
+
104
+ // Check for qualified asterisk (table.*)
105
+ if (tok.type === 'identifier') {
106
+ const next = peekToken(state, 1)
107
+ const nextNext = peekToken(state, 2)
108
+ if (next.type === 'dot' && nextNext.type === 'operator' && nextNext.value === '*') {
109
+ const tableTok = consume(state) // consume table name
110
+ consume(state) // consume dot
111
+ consume(state) // consume asterisk
112
+ cols.push({ kind: 'star', table: tableTok.value })
113
+ if (!match(state, 'comma')) break
114
+ continue
115
+ }
112
116
  }
113
- }
114
117
 
115
- // Check for unqualified asterisk (*)
116
- if (tok.type === 'operator' && tok.value === '*') {
117
- consume(state)
118
- cols.push({ kind: 'star' })
119
- return cols
120
- }
118
+ // Check for unqualified asterisk (*)
119
+ if (tok.type === 'operator' && tok.value === '*') {
120
+ consume(state)
121
+ cols.push({ kind: 'star' })
122
+ if (!match(state, 'comma')) break
123
+ continue
124
+ }
121
125
 
122
- while (true) {
123
126
  cols.push(parseSelectItem(state))
124
127
  if (!match(state, 'comma')) break
125
128
  }
package/src/types.d.ts CHANGED
@@ -38,7 +38,7 @@ export interface ScanOptions {
38
38
  * All hints are optional and "best effort" - sources may ignore them.
39
39
  */
40
40
  export interface QueryHints {
41
- columns?: string[] // columns needed
41
+ columns?: string[] // columns needed (undefined means all columns)
42
42
  where?: ExprNode // where clause
43
43
  // important: only apply limit/offset if where is fully applied by the data source
44
44
  // otherwise, the data source must return at least enough rows to ensure the engine