squirreling 0.1.2 → 0.2.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.
- package/README.md +2 -2
- package/package.json +10 -1
- package/src/backend/memory.js +37 -0
- package/src/execute/aggregates.js +3 -4
- package/src/execute/execute.js +60 -51
- package/src/execute/expression.js +5 -3
- package/src/execute/having.js +43 -20
- package/src/index.d.ts +2 -2
- package/src/parse/parse.js +1 -16
- package/src/types.d.ts +14 -1
- package/src/validation.js +17 -0
package/README.md
CHANGED
|
@@ -24,12 +24,12 @@ Squirreling is a lightweight SQL engine for JavaScript applications, designed to
|
|
|
24
24
|
```javascript
|
|
25
25
|
import { executeSql } from 'squirreling'
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const source = [
|
|
28
28
|
{ id: 1, name: 'Alice' },
|
|
29
29
|
{ id: 2, name: 'Bob' },
|
|
30
30
|
]
|
|
31
31
|
|
|
32
|
-
const result = executeSql(
|
|
32
|
+
const result = executeSql({ source, sql: 'SELECT UPPER(name) AS name_upper FROM users' })
|
|
33
33
|
console.log(result)
|
|
34
34
|
// Output: [ { name_upper: 'ALICE' }, { name_upper: 'BOB' } ]
|
|
35
35
|
```
|
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "",
|
|
4
5
|
"author": "Hyperparam",
|
|
5
6
|
"homepage": "https://hyperparam.app",
|
|
7
|
+
"keywords": [
|
|
8
|
+
"sql",
|
|
9
|
+
"data",
|
|
10
|
+
"dataset",
|
|
11
|
+
"hyperparam",
|
|
12
|
+
"hyparquet",
|
|
13
|
+
"parquet"
|
|
14
|
+
],
|
|
6
15
|
"license": "MIT",
|
|
7
16
|
"repository": {
|
|
8
17
|
"type": "git",
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { DataSource, RowSource } from '../types.js'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Creates a row accessor that wraps a plain JavaScript object
|
|
7
|
+
*
|
|
8
|
+
* @param {Record<string, any>} obj - the plain object
|
|
9
|
+
* @returns {RowSource} a row accessor interface
|
|
10
|
+
*/
|
|
11
|
+
export function createRowAccessor(obj) {
|
|
12
|
+
return {
|
|
13
|
+
getCell(name) {
|
|
14
|
+
return obj[name]
|
|
15
|
+
},
|
|
16
|
+
getKeys() {
|
|
17
|
+
return Object.keys(obj)
|
|
18
|
+
},
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Creates a memory-backed data source from an array of plain objects
|
|
24
|
+
*
|
|
25
|
+
* @param {Record<string, any>[]} data - array of plain objects
|
|
26
|
+
* @returns {DataSource} a data source interface
|
|
27
|
+
*/
|
|
28
|
+
export function createMemorySource(data) {
|
|
29
|
+
return {
|
|
30
|
+
getNumRows() {
|
|
31
|
+
return data.length
|
|
32
|
+
},
|
|
33
|
+
getRow(index) {
|
|
34
|
+
return createRowAccessor(data[index])
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -3,9 +3,9 @@ import { evaluateExpr } from './expression.js'
|
|
|
3
3
|
/**
|
|
4
4
|
* Evaluates an aggregate function over a set of rows
|
|
5
5
|
*
|
|
6
|
-
* @import { AggregateColumn, ExprNode,
|
|
6
|
+
* @import { AggregateColumn, ExprNode, RowSource } from '../types.js'
|
|
7
7
|
* @param {AggregateColumn} col - aggregate column definition
|
|
8
|
-
* @param {
|
|
8
|
+
* @param {RowSource[]} rows - rows to aggregate
|
|
9
9
|
* @returns {number | null} aggregated result
|
|
10
10
|
*/
|
|
11
11
|
export function evaluateAggregate(col, rows) {
|
|
@@ -77,8 +77,7 @@ export function defaultAggregateAlias(col) {
|
|
|
77
77
|
* @param {ExprNode} expr
|
|
78
78
|
* @returns {string}
|
|
79
79
|
*/
|
|
80
|
-
export
|
|
81
|
-
function defaultAggregateAliasExpr(expr) {
|
|
80
|
+
export function defaultAggregateAliasExpr(expr) {
|
|
82
81
|
if (expr.type === 'identifier') {
|
|
83
82
|
return expr.name
|
|
84
83
|
}
|
package/src/execute/execute.js
CHANGED
|
@@ -1,27 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { FunctionColumn, FunctionNode, OrderByItem,
|
|
2
|
+
* @import { DataSource, ExecuteSqlOptions, FunctionColumn, FunctionNode, OrderByItem, RowSource, SelectStatement, SqlPrimitive } from '../types.js'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { defaultAggregateAlias, evaluateAggregate } from './aggregates.js'
|
|
6
6
|
import { evaluateExpr } from './expression.js'
|
|
7
7
|
import { createHavingContext, evaluateHavingExpr } from './having.js'
|
|
8
8
|
import { parseSql } from '../parse/parse.js'
|
|
9
|
+
import { createMemorySource, createRowAccessor } from '../backend/memory.js'
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
* Executes a SQL SELECT query against
|
|
12
|
-
*
|
|
13
|
-
* @param {
|
|
14
|
-
* @returns {
|
|
12
|
+
* Executes a SQL SELECT query against a data source
|
|
13
|
+
*
|
|
14
|
+
* @param {ExecuteSqlOptions} options - the execution options
|
|
15
|
+
* @returns {Record<string, any>[]} the result rows matching the query
|
|
15
16
|
*/
|
|
16
|
-
export function executeSql(
|
|
17
|
+
export function executeSql({ source, sql }) {
|
|
17
18
|
const select = parseSql(sql)
|
|
18
|
-
|
|
19
|
+
const dataSource = Array.isArray(source) ? createMemorySource(source) : source
|
|
20
|
+
return evaluateSelectAst(select, dataSource)
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
/**
|
|
22
24
|
* Generates a default alias name for a string function
|
|
23
|
-
*
|
|
24
|
-
* @
|
|
25
|
+
*
|
|
26
|
+
* @param {FunctionColumn} col - the function column definition
|
|
27
|
+
* @returns {string} the generated alias (e.g., "upper_name", "concat_a_b")
|
|
25
28
|
*/
|
|
26
29
|
function defaultFunctionAlias(col) {
|
|
27
30
|
const base = col.func.toLowerCase()
|
|
@@ -37,8 +40,9 @@ function defaultFunctionAlias(col) {
|
|
|
37
40
|
|
|
38
41
|
/**
|
|
39
42
|
* Creates a stable string key for a row to enable deduplication
|
|
40
|
-
*
|
|
41
|
-
* @
|
|
43
|
+
*
|
|
44
|
+
* @param {Record<string, any>} row
|
|
45
|
+
* @returns {string} a stable string representation of the row
|
|
42
46
|
*/
|
|
43
47
|
function stableRowKey(row) {
|
|
44
48
|
const keys = Object.keys(row).sort()
|
|
@@ -53,9 +57,10 @@ function stableRowKey(row) {
|
|
|
53
57
|
|
|
54
58
|
/**
|
|
55
59
|
* Compares two SQL values for sorting
|
|
56
|
-
*
|
|
57
|
-
* @param {SqlPrimitive}
|
|
58
|
-
* @
|
|
60
|
+
*
|
|
61
|
+
* @param {SqlPrimitive} a
|
|
62
|
+
* @param {SqlPrimitive} b
|
|
63
|
+
* @returns {number} negative if a < b, positive if a > b, 0 if equal
|
|
59
64
|
*/
|
|
60
65
|
function compareValues(a, b) {
|
|
61
66
|
if (a === b) return 0
|
|
@@ -77,15 +82,16 @@ function compareValues(a, b) {
|
|
|
77
82
|
|
|
78
83
|
/**
|
|
79
84
|
* Applies DISTINCT filtering to remove duplicate rows
|
|
80
|
-
*
|
|
85
|
+
*
|
|
86
|
+
* @param {Record<string, any>[]} rows - The input rows
|
|
81
87
|
* @param {boolean} distinct - Whether to apply deduplication
|
|
82
|
-
* @returns {
|
|
88
|
+
* @returns {Record<string, any>[]} The deduplicated rows
|
|
83
89
|
*/
|
|
84
90
|
function applyDistinct(rows, distinct) {
|
|
85
91
|
if (!distinct) return rows
|
|
86
92
|
/** @type {Set<string>} */
|
|
87
93
|
const seen = new Set()
|
|
88
|
-
/** @type {
|
|
94
|
+
/** @type {Record<string, any>[]} */
|
|
89
95
|
const result = []
|
|
90
96
|
for (const row of rows) {
|
|
91
97
|
const key = stableRowKey(row)
|
|
@@ -98,20 +104,20 @@ function applyDistinct(rows, distinct) {
|
|
|
98
104
|
|
|
99
105
|
/**
|
|
100
106
|
* Applies ORDER BY sorting to rows
|
|
101
|
-
*
|
|
102
|
-
* @param {
|
|
103
|
-
* @
|
|
107
|
+
*
|
|
108
|
+
* @param {Record<string, any>[]} rows - the input rows
|
|
109
|
+
* @param {OrderByItem[]} orderBy - the sort specifications
|
|
110
|
+
* @returns {Record<string, any>[]} the sorted rows
|
|
104
111
|
*/
|
|
105
112
|
function applyOrderBy(rows, orderBy) {
|
|
106
|
-
if (!orderBy
|
|
113
|
+
if (!orderBy?.length) return rows
|
|
107
114
|
|
|
108
115
|
const sorted = rows.slice()
|
|
109
|
-
|
|
110
116
|
sorted.sort((a, b) => {
|
|
111
117
|
for (const term of orderBy) {
|
|
112
118
|
const dir = term.direction
|
|
113
|
-
const av = evaluateExpr(term.expr, a)
|
|
114
|
-
const bv = evaluateExpr(term.expr, b)
|
|
119
|
+
const av = evaluateExpr(term.expr, createRowAccessor(a))
|
|
120
|
+
const bv = evaluateExpr(term.expr, createRowAccessor(b))
|
|
115
121
|
const cmp = compareValues(av, bv)
|
|
116
122
|
if (cmp !== 0) {
|
|
117
123
|
return dir === 'DESC' ? -cmp : cmp
|
|
@@ -125,41 +131,41 @@ function applyOrderBy(rows, orderBy) {
|
|
|
125
131
|
|
|
126
132
|
/**
|
|
127
133
|
* Evaluates a parsed SELECT AST against data rows
|
|
128
|
-
*
|
|
129
|
-
* @param {
|
|
130
|
-
* @
|
|
134
|
+
*
|
|
135
|
+
* @param {SelectStatement} select - the parsed SQL AST
|
|
136
|
+
* @param {DataSource} dataSource - the data source
|
|
137
|
+
* @returns {Record<string, any>[]} the filtered, projected, and sorted result rows
|
|
131
138
|
*/
|
|
132
|
-
function evaluateSelectAst(select,
|
|
139
|
+
function evaluateSelectAst(select, dataSource) {
|
|
133
140
|
// Check for unsupported JOIN operations
|
|
134
141
|
if (select.joins.length) {
|
|
135
142
|
throw new Error('JOIN is not supported')
|
|
136
143
|
}
|
|
137
144
|
|
|
138
|
-
// WHERE
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
}
|
|
145
|
+
// WHERE clause filtering
|
|
146
|
+
/** @type {RowSource[]} */
|
|
147
|
+
const working = []
|
|
148
|
+
const length = dataSource.getNumRows()
|
|
149
|
+
for (let i = 0; i < length; i++) {
|
|
150
|
+
const row = dataSource.getRow(i)
|
|
151
|
+
if (!select.where || evaluateExpr(select.where, row)) {
|
|
152
|
+
working.push(row)
|
|
147
153
|
}
|
|
148
|
-
working = filtered
|
|
149
154
|
}
|
|
150
155
|
|
|
151
156
|
const hasAggregate = select.columns.some(col => col.kind === 'aggregate')
|
|
152
157
|
const useGrouping = hasAggregate || select.groupBy?.length > 0
|
|
153
158
|
|
|
154
|
-
/** @type {
|
|
159
|
+
/** @type {Record<string, any>[]} */
|
|
155
160
|
const projected = []
|
|
156
161
|
|
|
157
162
|
if (useGrouping) {
|
|
158
|
-
|
|
163
|
+
// Grouping due to GROUP BY or aggregate functions
|
|
164
|
+
/** @type {RowSource[][]} */
|
|
159
165
|
const groups = []
|
|
160
166
|
|
|
161
167
|
if (select.groupBy?.length) {
|
|
162
|
-
/** @type {Map<string,
|
|
168
|
+
/** @type {Map<string, RowSource[]>} */
|
|
163
169
|
const map = new Map()
|
|
164
170
|
for (const row of working) {
|
|
165
171
|
/** @type {string[]} */
|
|
@@ -187,14 +193,16 @@ function evaluateSelectAst(select, rows) {
|
|
|
187
193
|
}
|
|
188
194
|
|
|
189
195
|
for (const group of groups) {
|
|
190
|
-
/** @type {
|
|
196
|
+
/** @type {Record<string, any>} */
|
|
191
197
|
const resultRow = {}
|
|
192
198
|
for (const col of select.columns) {
|
|
193
199
|
if (col.kind === 'star') {
|
|
194
|
-
const firstRow = group[0]
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
200
|
+
const firstRow = group[0]
|
|
201
|
+
if (firstRow) {
|
|
202
|
+
const keys = firstRow.getKeys()
|
|
203
|
+
for (const key of keys) {
|
|
204
|
+
resultRow[key] = firstRow.getCell(key)
|
|
205
|
+
}
|
|
198
206
|
}
|
|
199
207
|
continue
|
|
200
208
|
}
|
|
@@ -203,7 +211,7 @@ function evaluateSelectAst(select, rows) {
|
|
|
203
211
|
const name = col.column
|
|
204
212
|
const alias = col.alias ?? name
|
|
205
213
|
// Evaluate on first row of group (all rows have same value for GROUP BY columns)
|
|
206
|
-
resultRow[alias] = group
|
|
214
|
+
resultRow[alias] = group[0]?.getCell(name)
|
|
207
215
|
continue
|
|
208
216
|
}
|
|
209
217
|
|
|
@@ -245,19 +253,20 @@ function evaluateSelectAst(select, rows) {
|
|
|
245
253
|
projected.push(resultRow)
|
|
246
254
|
}
|
|
247
255
|
} else {
|
|
256
|
+
// No grouping, simple projection
|
|
248
257
|
for (const row of working) {
|
|
249
|
-
/** @type {
|
|
258
|
+
/** @type {Record<string, any>} */
|
|
250
259
|
const outRow = {}
|
|
251
260
|
for (const col of select.columns) {
|
|
252
261
|
if (col.kind === 'star') {
|
|
253
|
-
const keys =
|
|
262
|
+
const keys = row.getKeys()
|
|
254
263
|
for (const key of keys) {
|
|
255
|
-
outRow[key] = row
|
|
264
|
+
outRow[key] = row.getCell(key)
|
|
256
265
|
}
|
|
257
266
|
} else if (col.kind === 'column') {
|
|
258
267
|
const name = col.column
|
|
259
268
|
const alias = col.alias ?? name
|
|
260
|
-
outRow[alias] = row
|
|
269
|
+
outRow[alias] = row.getCell(name)
|
|
261
270
|
} else if (col.kind === 'function') {
|
|
262
271
|
/** @type {FunctionNode} */
|
|
263
272
|
const funcNode = { type: 'function', name: col.func, args: col.args }
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
/**
|
|
3
3
|
* Evaluates an expression node against a row of data
|
|
4
4
|
*
|
|
5
|
-
* @import { ExprNode,
|
|
5
|
+
* @import { ExprNode, RowSource, SqlPrimitive } from '../types.js'
|
|
6
6
|
* @param {ExprNode} node - The expression node to evaluate
|
|
7
|
-
* @param {
|
|
7
|
+
* @param {RowSource} row - The data row to evaluate against
|
|
8
8
|
* @returns {SqlPrimitive} The result of the evaluation
|
|
9
9
|
*/
|
|
10
10
|
export function evaluateExpr(node, row) {
|
|
@@ -13,9 +13,10 @@ export function evaluateExpr(node, row) {
|
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
if (node.type === 'identifier') {
|
|
16
|
-
return row
|
|
16
|
+
return row.getCell(node.name)
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
// Unary operators
|
|
19
20
|
if (node.type === 'unary') {
|
|
20
21
|
if (node.op === 'NOT') {
|
|
21
22
|
return !evaluateExpr(node.argument, row)
|
|
@@ -33,6 +34,7 @@ export function evaluateExpr(node, row) {
|
|
|
33
34
|
}
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
// Binary operators
|
|
36
38
|
if (node.type === 'binary') {
|
|
37
39
|
if (node.op === 'AND') {
|
|
38
40
|
const leftVal = evaluateExpr(node.left, row)
|
package/src/execute/having.js
CHANGED
|
@@ -1,35 +1,56 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { ExprNode,
|
|
2
|
+
* @import { AggregateFunc, ExprNode, RowSource, SqlPrimitive } from '../types.js'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { isAggregateFunc } from '../validation.js'
|
|
5
6
|
import { evaluateExpr } from './expression.js'
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Creates a context for evaluating HAVING expressions
|
|
9
|
-
*
|
|
10
|
-
* @param {
|
|
11
|
-
* @
|
|
10
|
+
*
|
|
11
|
+
* @param {Record<string, any>} resultRow - the aggregated result row
|
|
12
|
+
* @param {RowSource[]} group - the group of rows
|
|
13
|
+
* @returns {RowSource} a context row for HAVING evaluation
|
|
12
14
|
*/
|
|
13
15
|
export function createHavingContext(resultRow, group) {
|
|
14
16
|
// Include the first row of the group (for GROUP BY columns)
|
|
15
|
-
const firstRow = group[0]
|
|
17
|
+
const firstRow = group[0]
|
|
18
|
+
/** @type {Record<string, any>} */
|
|
19
|
+
const context = {}
|
|
20
|
+
if (firstRow) {
|
|
21
|
+
const keys = firstRow.getKeys()
|
|
22
|
+
for (const key of keys) {
|
|
23
|
+
context[key] = firstRow.getCell(key)
|
|
24
|
+
}
|
|
25
|
+
}
|
|
16
26
|
// Merge with result row (which has aggregates computed)
|
|
17
|
-
|
|
27
|
+
Object.assign(context, resultRow)
|
|
28
|
+
|
|
29
|
+
// Return a Row accessor wrapping the context
|
|
30
|
+
return {
|
|
31
|
+
getCell(name) {
|
|
32
|
+
return context[name]
|
|
33
|
+
},
|
|
34
|
+
getKeys() {
|
|
35
|
+
return Object.keys(context)
|
|
36
|
+
},
|
|
37
|
+
}
|
|
18
38
|
}
|
|
19
39
|
|
|
20
40
|
/**
|
|
21
41
|
* Evaluates a HAVING expression with support for aggregate functions
|
|
22
|
-
*
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {
|
|
25
|
-
* @
|
|
42
|
+
*
|
|
43
|
+
* @param {ExprNode} expr - the HAVING expression
|
|
44
|
+
* @param {RowSource} context - the context row with aggregated values
|
|
45
|
+
* @param {RowSource[]} group - the group of rows for re-evaluating aggregates
|
|
46
|
+
* @returns {boolean} whether the HAVING condition is satisfied
|
|
26
47
|
*/
|
|
27
48
|
export function evaluateHavingExpr(expr, context, group) {
|
|
28
49
|
// For HAVING, we need special handling of aggregate functions
|
|
29
50
|
// They need to be re-evaluated against the group
|
|
30
51
|
if (expr.type === 'function') {
|
|
31
52
|
const funcName = expr.name.toUpperCase()
|
|
32
|
-
if (
|
|
53
|
+
if (isAggregateFunc(funcName)) {
|
|
33
54
|
// Evaluate aggregate function on the group
|
|
34
55
|
return Boolean(evaluateAggregateFunction(funcName, expr.args, group))
|
|
35
56
|
}
|
|
@@ -103,15 +124,16 @@ export function evaluateHavingExpr(expr, context, group) {
|
|
|
103
124
|
|
|
104
125
|
/**
|
|
105
126
|
* Evaluates a value in a HAVING expression
|
|
106
|
-
*
|
|
107
|
-
* @param {
|
|
108
|
-
* @param {
|
|
109
|
-
* @
|
|
127
|
+
*
|
|
128
|
+
* @param {ExprNode} expr
|
|
129
|
+
* @param {RowSource} context - the context row
|
|
130
|
+
* @param {RowSource[]} group - the group of rows
|
|
131
|
+
* @returns {SqlPrimitive} the evaluated value
|
|
110
132
|
*/
|
|
111
133
|
function evaluateHavingValue(expr, context, group) {
|
|
112
134
|
if (expr.type === 'function') {
|
|
113
135
|
const funcName = expr.name.toUpperCase()
|
|
114
|
-
if (
|
|
136
|
+
if (isAggregateFunc(funcName)) {
|
|
115
137
|
return evaluateAggregateFunction(funcName, expr.args, group)
|
|
116
138
|
}
|
|
117
139
|
}
|
|
@@ -126,10 +148,11 @@ function evaluateHavingValue(expr, context, group) {
|
|
|
126
148
|
|
|
127
149
|
/**
|
|
128
150
|
* Evaluates an aggregate function on a group
|
|
129
|
-
*
|
|
130
|
-
* @param {
|
|
131
|
-
* @param {
|
|
132
|
-
* @
|
|
151
|
+
*
|
|
152
|
+
* @param {AggregateFunc} funcName - aggregate function name
|
|
153
|
+
* @param {ExprNode[]} args - function arguments
|
|
154
|
+
* @param {RowSource[]} group - the group of rows
|
|
155
|
+
* @returns {SqlPrimitive} the aggregate result
|
|
133
156
|
*/
|
|
134
157
|
function evaluateAggregateFunction(funcName, args, group) {
|
|
135
158
|
if (funcName === 'COUNT') {
|
package/src/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { RowSource, SelectStatement } from './types.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Executes a SQL SELECT query against an array of data rows
|
|
@@ -7,7 +7,7 @@ import type { Row, SelectStatement } from './types.js'
|
|
|
7
7
|
* @param sql - SQL query string
|
|
8
8
|
* @returns rows matching the query
|
|
9
9
|
*/
|
|
10
|
-
export function executeSql(rows:
|
|
10
|
+
export function executeSql(rows: RowSource[], sql: string): RowSource[]
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Parses a SQL query string into an abstract syntax tree
|
package/src/parse/parse.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { tokenize } from './tokenize.js'
|
|
6
6
|
import { parseExpression, parsePrimary } from './expression.js'
|
|
7
|
+
import { isAggregateFunc, isStringFunc } from '../validation.js'
|
|
7
8
|
|
|
8
9
|
// Keywords that cannot be used as implicit aliases after a column
|
|
9
10
|
const RESERVED_AFTER_COLUMN = new Set([
|
|
@@ -522,19 +523,3 @@ function parseError(state, expected) {
|
|
|
522
523
|
const after = prevToken ? ` after "${prevToken.originalValue ?? prevToken.value}"` : ''
|
|
523
524
|
return new Error(`Expected ${expected}${after} at position ${tok.position}`)
|
|
524
525
|
}
|
|
525
|
-
|
|
526
|
-
/**
|
|
527
|
-
* @param {string} name
|
|
528
|
-
* @returns {name is AggregateFunc}
|
|
529
|
-
*/
|
|
530
|
-
function isAggregateFunc(name) {
|
|
531
|
-
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX'].includes(name)
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
/**
|
|
535
|
-
* @param {string} name
|
|
536
|
-
* @returns {name is StringFunc}
|
|
537
|
-
*/
|
|
538
|
-
function isStringFunc(name) {
|
|
539
|
-
return ['UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'SUBSTRING', 'TRIM'].includes(name)
|
|
540
|
-
}
|
package/src/types.d.ts
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
export
|
|
1
|
+
export interface RowSource {
|
|
2
|
+
getCell(name: string): any
|
|
3
|
+
getKeys(): string[]
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface DataSource {
|
|
7
|
+
getNumRows(): number
|
|
8
|
+
getRow(index: number): RowSource
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface ExecuteSqlOptions {
|
|
12
|
+
source: Record<string, any>[] | DataSource
|
|
13
|
+
sql: string
|
|
14
|
+
}
|
|
2
15
|
|
|
3
16
|
export type SqlPrimitive = string | number | bigint | boolean | null
|
|
4
17
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
|
|
2
|
+
/**
|
|
3
|
+
* @import {AggregateFunc, StringFunc} from './types.js'
|
|
4
|
+
* @param {string} name
|
|
5
|
+
* @returns {name is AggregateFunc}
|
|
6
|
+
*/
|
|
7
|
+
export function isAggregateFunc(name) {
|
|
8
|
+
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX'].includes(name)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {string} name
|
|
13
|
+
* @returns {name is StringFunc}
|
|
14
|
+
*/
|
|
15
|
+
export function isStringFunc(name) {
|
|
16
|
+
return ['UPPER', 'LOWER', 'CONCAT', 'LENGTH', 'SUBSTRING', 'TRIM'].includes(name)
|
|
17
|
+
}
|