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 +2 -2
- package/src/execute/expression.js +10 -4
- package/src/executionErrors.js +21 -4
- package/src/parse/parse.js +22 -19
- package/src/types.d.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.7.
|
|
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": "
|
|
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
|
|
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
|
|
41
|
+
if (colName && colName in row.cells) {
|
|
42
42
|
return row.cells[colName]()
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
|
-
|
|
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
|
package/src/executionErrors.js
CHANGED
|
@@ -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
|
+
}
|
package/src/parse/parse.js
CHANGED
|
@@ -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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|