squirreling 0.4.6 → 0.4.7
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 +11 -0
- package/package.json +1 -1
- package/src/execute/aggregates.js +28 -3
- package/src/execute/execute.js +5 -5
- package/src/execute/expression.js +72 -6
- package/src/execute/having.js +1 -1
- package/src/execute/join.js +3 -3
- package/src/execute/utils.js +31 -2
- package/src/parse/comparison.js +9 -9
- package/src/parse/expression.js +48 -0
- package/src/types.d.ts +18 -5
- package/src/validation.js +16 -2
package/README.md
CHANGED
|
@@ -67,3 +67,14 @@ const allUsers: Record<string, SqlPrimitive>[] = await collect(executeSql({
|
|
|
67
67
|
}))
|
|
68
68
|
console.log(allUsers)
|
|
69
69
|
```
|
|
70
|
+
|
|
71
|
+
## Supported SQL Features
|
|
72
|
+
|
|
73
|
+
- `SELECT` statements with `WHERE`, `ORDER BY`, `LIMIT`, `OFFSET`
|
|
74
|
+
- Subqueries in `SELECT`, `FROM`, and `WHERE` clauses
|
|
75
|
+
- `JOIN` operations: `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, `FULL JOIN`
|
|
76
|
+
- `GROUP BY` and `HAVING` clauses
|
|
77
|
+
- Aggregate functions: `COUNT`, `SUM`, `AVG`, `MIN`, `MAX`, `JSON_ARRAYAGG`
|
|
78
|
+
- String functions: `CONCAT`, `SUBSTRING`, `LENGTH`, `UPPER`, `LOWER`
|
|
79
|
+
- Json functions: `JSON_VALUE`, `JSON_QUERY`, `JSON_OBJECT`
|
|
80
|
+
- Basic expressions and arithmetic operations
|
package/package.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { evaluateExpr } from './expression.js'
|
|
2
|
-
import { defaultDerivedAlias } from './utils.js'
|
|
2
|
+
import { defaultDerivedAlias, stringify } from './utils.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Evaluates an aggregate function over a set of rows
|
|
6
6
|
*
|
|
7
|
-
* @import { AggregateColumn, AsyncDataSource,
|
|
7
|
+
* @import { AggregateColumn, AsyncDataSource, AsyncRow, SqlPrimitive } from '../types.js'
|
|
8
8
|
* @param {Object} options
|
|
9
9
|
* @param {AggregateColumn} options.col - aggregate column definition
|
|
10
10
|
* @param {AsyncRow[]} options.rows - rows to aggregate
|
|
11
11
|
* @param {Record<string, AsyncDataSource>} options.tables
|
|
12
|
-
* @returns {Promise<
|
|
12
|
+
* @returns {Promise<SqlPrimitive>} aggregated result
|
|
13
13
|
*/
|
|
14
14
|
export async function evaluateAggregate({ col, rows, tables }) {
|
|
15
15
|
const { arg, func } = col
|
|
@@ -70,6 +70,31 @@ export async function evaluateAggregate({ col, rows, tables }) {
|
|
|
70
70
|
if (func === 'MAX') return max
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
if (func === 'JSON_ARRAYAGG') {
|
|
74
|
+
if (arg.kind === 'star') {
|
|
75
|
+
throw new Error('JSON_ARRAYAGG(*) is not supported, use a column name or expression')
|
|
76
|
+
}
|
|
77
|
+
/** @type {SqlPrimitive[]} */
|
|
78
|
+
const values = []
|
|
79
|
+
if (arg.quantifier === 'distinct') {
|
|
80
|
+
const seen = new Set()
|
|
81
|
+
for (const row of rows) {
|
|
82
|
+
const v = await evaluateExpr({ node: arg.expr, row, tables })
|
|
83
|
+
const key = stringify(v)
|
|
84
|
+
if (!seen.has(key)) {
|
|
85
|
+
seen.add(key)
|
|
86
|
+
values.push(v)
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
for (const row of rows) {
|
|
91
|
+
const v = await evaluateExpr({ node: arg.expr, row, tables })
|
|
92
|
+
values.push(v)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return values
|
|
96
|
+
}
|
|
97
|
+
|
|
73
98
|
throw new Error('Unsupported aggregate function ' + func)
|
|
74
99
|
}
|
|
75
100
|
|
package/src/execute/execute.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { generatorSource, memorySource } from '../backend/dataSource.js'
|
|
2
2
|
import { parseSql } from '../parse/parse.js'
|
|
3
3
|
import { defaultAggregateAlias, evaluateAggregate } from './aggregates.js'
|
|
4
|
+
import { extractColumns } from './columns.js'
|
|
4
5
|
import { evaluateExpr } from './expression.js'
|
|
5
6
|
import { evaluateHavingExpr } from './having.js'
|
|
6
7
|
import { executeJoins } from './join.js'
|
|
7
|
-
import { compareForTerm, defaultDerivedAlias } from './utils.js'
|
|
8
|
-
import { extractColumns } from './columns.js'
|
|
8
|
+
import { compareForTerm, defaultDerivedAlias, stringify } from './utils.js'
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
|
-
* @import { AsyncDataSource, AsyncRow, ExecuteSqlOptions,
|
|
11
|
+
* @import { AsyncDataSource, AsyncRow, ExecuteSqlOptions, OrderByItem, QueryHints, SelectStatement, SqlPrimitive } from '../types.js'
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -85,7 +85,7 @@ async function stableRowKey(row) {
|
|
|
85
85
|
const parts = []
|
|
86
86
|
for (const k of keys) {
|
|
87
87
|
const v = await row[k]()
|
|
88
|
-
parts.push(k + ':' +
|
|
88
|
+
parts.push(k + ':' + stringify(v))
|
|
89
89
|
}
|
|
90
90
|
return parts.join('|')
|
|
91
91
|
}
|
|
@@ -358,7 +358,7 @@ async function* evaluateBuffered(select, dataSource, tables, hasAggregate, useGr
|
|
|
358
358
|
const keyParts = []
|
|
359
359
|
for (const expr of select.groupBy) {
|
|
360
360
|
const v = await evaluateExpr({ node: expr, row, tables })
|
|
361
|
-
keyParts.push(
|
|
361
|
+
keyParts.push(stringify(v))
|
|
362
362
|
}
|
|
363
363
|
const key = keyParts.join('|')
|
|
364
364
|
let group = map.get(key)
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { executeSelect } from './execute.js'
|
|
2
|
-
import { applyBinaryOp } from './utils.js'
|
|
2
|
+
import { applyBinaryOp, stringify } from './utils.js'
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @import { ExprNode, AsyncRow, SqlPrimitive, AsyncDataSource } from '../types.js'
|
|
@@ -83,6 +83,7 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
83
83
|
// Function calls
|
|
84
84
|
if (node.type === 'function') {
|
|
85
85
|
const funcName = node.name.toUpperCase()
|
|
86
|
+
/** @type {SqlPrimitive[]} */
|
|
86
87
|
const args = await Promise.all(node.args.map(arg => evaluateExpr({ node: arg, row, tables })))
|
|
87
88
|
|
|
88
89
|
if (funcName === 'UPPER') {
|
|
@@ -102,8 +103,9 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
102
103
|
if (funcName === 'CONCAT') {
|
|
103
104
|
if (args.length < 1) throw new Error('CONCAT requires at least 1 argument')
|
|
104
105
|
// SQL CONCAT returns NULL if any argument is NULL
|
|
105
|
-
|
|
106
|
-
|
|
106
|
+
if (args.some(a => a == null)) return null
|
|
107
|
+
if (args.some(a => typeof a === 'object')) {
|
|
108
|
+
throw new Error('CONCAT does not support object arguments')
|
|
107
109
|
}
|
|
108
110
|
return args.map(a => String(a)).join('')
|
|
109
111
|
}
|
|
@@ -160,6 +162,67 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
160
162
|
return Math.random()
|
|
161
163
|
}
|
|
162
164
|
|
|
165
|
+
if (funcName === 'JSON_OBJECT') {
|
|
166
|
+
if (args.length % 2 !== 0) {
|
|
167
|
+
throw new Error('JSON_OBJECT requires an even number of arguments (key-value pairs)')
|
|
168
|
+
}
|
|
169
|
+
/** @type {Record<string, SqlPrimitive>} */
|
|
170
|
+
const result = {}
|
|
171
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
172
|
+
const key = args[i]
|
|
173
|
+
const value = args[i + 1]
|
|
174
|
+
if (key == null) {
|
|
175
|
+
throw new Error('JSON_OBJECT: key cannot be null')
|
|
176
|
+
}
|
|
177
|
+
result[String(key)] = value
|
|
178
|
+
}
|
|
179
|
+
return result
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (funcName === 'JSON_VALUE' || funcName === 'JSON_QUERY') {
|
|
183
|
+
if (args.length !== 2) throw new Error(`${funcName} requires exactly 2 arguments`)
|
|
184
|
+
let jsonArg = args[0]
|
|
185
|
+
const pathArg = args[1]
|
|
186
|
+
if (jsonArg == null || pathArg == null) return null
|
|
187
|
+
|
|
188
|
+
// Parse JSON if string, otherwise use directly
|
|
189
|
+
if (typeof jsonArg === 'string') {
|
|
190
|
+
try {
|
|
191
|
+
jsonArg = JSON.parse(jsonArg)
|
|
192
|
+
} catch {
|
|
193
|
+
throw new Error(`${funcName}: invalid JSON string`)
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (typeof jsonArg !== 'object') {
|
|
197
|
+
throw new Error(`${funcName}: first argument must be JSON string or object`)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Parse path ("$.foo.bar[0].baz" or "foo.bar[0]")
|
|
201
|
+
const path = String(pathArg)
|
|
202
|
+
const normalizedPath = path.startsWith('$') ? path.slice(1) : path
|
|
203
|
+
|
|
204
|
+
// Navigate the path
|
|
205
|
+
let current = jsonArg
|
|
206
|
+
const segments = normalizedPath.match(/\.?([^.[]+)|\[(\d+)\]/g) || []
|
|
207
|
+
for (const segment of segments) {
|
|
208
|
+
if (current == null) return null
|
|
209
|
+
if (segment.startsWith('[')) {
|
|
210
|
+
// Array index access
|
|
211
|
+
const index = parseInt(segment.slice(1, -1), 10)
|
|
212
|
+
if (!Array.isArray(current)) return null
|
|
213
|
+
current = current[index]
|
|
214
|
+
} else {
|
|
215
|
+
// Property access
|
|
216
|
+
const key = segment.startsWith('.') ? segment.slice(1) : segment
|
|
217
|
+
if (typeof current !== 'object' || Array.isArray(current)) return null
|
|
218
|
+
current = current[key]
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (current == null) return null
|
|
223
|
+
return current
|
|
224
|
+
}
|
|
225
|
+
|
|
163
226
|
throw new Error('Unsupported function ' + funcName)
|
|
164
227
|
}
|
|
165
228
|
|
|
@@ -167,6 +230,12 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
167
230
|
const val = await evaluateExpr({ node: node.expr, row, tables })
|
|
168
231
|
if (val == null) return null
|
|
169
232
|
const toType = node.toType.toUpperCase()
|
|
233
|
+
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
234
|
+
if (typeof val === 'object') return stringify(val)
|
|
235
|
+
return String(val)
|
|
236
|
+
}
|
|
237
|
+
// Can only cast primitives to other primitive types
|
|
238
|
+
if (typeof val === 'object') throw new Error(`Cannot CAST object to type ${node.toType}`)
|
|
170
239
|
if (toType === 'INTEGER' || toType === 'INT') {
|
|
171
240
|
const num = Number(val)
|
|
172
241
|
if (isNaN(num)) return null
|
|
@@ -180,9 +249,6 @@ export async function evaluateExpr({ node, row, tables }) {
|
|
|
180
249
|
if (isNaN(num)) return null
|
|
181
250
|
return num
|
|
182
251
|
}
|
|
183
|
-
if (toType === 'TEXT' || toType === 'STRING' || toType === 'VARCHAR') {
|
|
184
|
-
return String(val)
|
|
185
|
-
}
|
|
186
252
|
if (toType === 'BOOLEAN' || toType === 'BOOL') {
|
|
187
253
|
return Boolean(val)
|
|
188
254
|
}
|
package/src/execute/having.js
CHANGED
|
@@ -41,7 +41,7 @@ export async function evaluateHavingExpr(expr, row, group, tables) {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
const right = await evaluateHavingValue(expr.right, context, group, tables)
|
|
44
|
-
return applyBinaryOp(expr.op, left, right)
|
|
44
|
+
return Boolean(applyBinaryOp(expr.op, left, right))
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
if (expr.type === 'unary') {
|
package/src/execute/join.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { evaluateExpr } from './expression.js'
|
|
2
|
+
import { stringify } from './utils.js'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* @import { AsyncRow, AsyncDataSource, JoinClause, ExprNode } from '../types.js'
|
|
@@ -258,8 +259,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
258
259
|
for (const rightRow of rightRows) {
|
|
259
260
|
const keyValue = await evaluateExpr({ node: keys.rightKey, row: rightRow, tables })
|
|
260
261
|
if (keyValue == null) continue // NULL keys never match
|
|
261
|
-
const keyStr =
|
|
262
|
-
|
|
262
|
+
const keyStr = stringify(keyValue)
|
|
263
263
|
let bucket = hashMap.get(keyStr)
|
|
264
264
|
if (!bucket) {
|
|
265
265
|
bucket = []
|
|
@@ -283,7 +283,7 @@ async function* hashJoin({ leftRows, rightRows, join, leftTable, rightTable, tab
|
|
|
283
283
|
}
|
|
284
284
|
|
|
285
285
|
const keyValue = await evaluateExpr({ node: keys.leftKey, row: leftRow, tables })
|
|
286
|
-
const keyStr =
|
|
286
|
+
const keyStr = stringify(keyValue)
|
|
287
287
|
|
|
288
288
|
const matchingRightRows = hashMap.get(keyStr)
|
|
289
289
|
|
package/src/execute/utils.js
CHANGED
|
@@ -3,14 +3,27 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
*
|
|
6
|
+
* Applies a binary operator to two values, handling nulls according to SQL semantics
|
|
7
7
|
*
|
|
8
8
|
* @param {BinaryOp} op
|
|
9
9
|
* @param {SqlPrimitive} a
|
|
10
10
|
* @param {SqlPrimitive} b
|
|
11
|
-
* @returns {
|
|
11
|
+
* @returns {SqlPrimitive}
|
|
12
12
|
*/
|
|
13
13
|
export function applyBinaryOp(op, a, b) {
|
|
14
|
+
// Arithmetic operators return null if either operand is null
|
|
15
|
+
if (op === '+' || op === '-' || op === '*' || op === '/' || op === '%') {
|
|
16
|
+
if (a == null || b == null) return null
|
|
17
|
+
const numA = Number(a)
|
|
18
|
+
const numB = Number(b)
|
|
19
|
+
if (op === '+') return numA + numB
|
|
20
|
+
if (op === '-') return numA - numB
|
|
21
|
+
if (op === '*') return numA * numB
|
|
22
|
+
if (op === '/') return numB === 0 ? null : numA / numB
|
|
23
|
+
if (op === '%') return numB === 0 ? null : numA % numB
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Comparison and logical operators
|
|
14
27
|
if (a == null || b == null) {
|
|
15
28
|
return false
|
|
16
29
|
}
|
|
@@ -33,6 +46,8 @@ export function applyBinaryOp(op, a, b) {
|
|
|
33
46
|
const regex = new RegExp(`^${regexPattern}$`, 'i')
|
|
34
47
|
return regex.test(str)
|
|
35
48
|
}
|
|
49
|
+
|
|
50
|
+
return null
|
|
36
51
|
}
|
|
37
52
|
|
|
38
53
|
/**
|
|
@@ -121,3 +136,17 @@ export function defaultDerivedAlias(expr) {
|
|
|
121
136
|
}
|
|
122
137
|
return 'expr'
|
|
123
138
|
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* @param {SqlPrimitive} value
|
|
142
|
+
* @returns {string}
|
|
143
|
+
*/
|
|
144
|
+
export function stringify(value) {
|
|
145
|
+
if (value == null) return 'NULL'
|
|
146
|
+
return JSON.stringify(value, (_, val) => {
|
|
147
|
+
if (typeof val === 'bigint') {
|
|
148
|
+
return val <= Number.MAX_SAFE_INTEGER ? Number(val) : val.toString()
|
|
149
|
+
}
|
|
150
|
+
return val
|
|
151
|
+
})
|
|
152
|
+
}
|
package/src/parse/comparison.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { isBinaryOp } from '../validation.js'
|
|
2
|
-
import {
|
|
2
|
+
import { parseAdditive, parseExpression, parseSubquery } from './expression.js'
|
|
3
3
|
import { consume, current, expect, match, peekToken } from './state.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -11,7 +11,7 @@ import { consume, current, expect, match, peekToken } from './state.js'
|
|
|
11
11
|
* @returns {ExprNode}
|
|
12
12
|
*/
|
|
13
13
|
export function parseComparison(state) {
|
|
14
|
-
const left =
|
|
14
|
+
const left = parseAdditive(state)
|
|
15
15
|
const tok = current(state)
|
|
16
16
|
|
|
17
17
|
// IS [NOT] NULL
|
|
@@ -41,7 +41,7 @@ export function parseComparison(state) {
|
|
|
41
41
|
if (nextTok.type === 'keyword' && nextTok.value === 'LIKE') {
|
|
42
42
|
consume(state) // NOT
|
|
43
43
|
consume(state) // LIKE
|
|
44
|
-
const right =
|
|
44
|
+
const right = parseAdditive(state)
|
|
45
45
|
return {
|
|
46
46
|
type: 'unary',
|
|
47
47
|
op: 'NOT',
|
|
@@ -57,7 +57,7 @@ export function parseComparison(state) {
|
|
|
57
57
|
|
|
58
58
|
if (tok.type === 'keyword' && tok.value === 'LIKE') {
|
|
59
59
|
consume(state)
|
|
60
|
-
const right =
|
|
60
|
+
const right = parseAdditive(state)
|
|
61
61
|
return {
|
|
62
62
|
type: 'binary',
|
|
63
63
|
op: 'LIKE',
|
|
@@ -72,9 +72,9 @@ export function parseComparison(state) {
|
|
|
72
72
|
if (nextTok.type === 'keyword' && nextTok.value === 'BETWEEN') {
|
|
73
73
|
consume(state) // NOT
|
|
74
74
|
consume(state) // BETWEEN
|
|
75
|
-
const lower =
|
|
75
|
+
const lower = parseAdditive(state)
|
|
76
76
|
expect(state, 'keyword', 'AND')
|
|
77
|
-
const upper =
|
|
77
|
+
const upper = parseAdditive(state)
|
|
78
78
|
// NOT BETWEEN -> expr < lower OR expr > upper
|
|
79
79
|
return {
|
|
80
80
|
type: 'binary',
|
|
@@ -87,9 +87,9 @@ export function parseComparison(state) {
|
|
|
87
87
|
|
|
88
88
|
if (tok.type === 'keyword' && tok.value === 'BETWEEN') {
|
|
89
89
|
consume(state)
|
|
90
|
-
const lower =
|
|
90
|
+
const lower = parseAdditive(state)
|
|
91
91
|
expect(state, 'keyword', 'AND')
|
|
92
|
-
const upper =
|
|
92
|
+
const upper = parseAdditive(state)
|
|
93
93
|
// BETWEEN -> expr >= lower AND expr <= upper
|
|
94
94
|
return {
|
|
95
95
|
type: 'binary',
|
|
@@ -186,7 +186,7 @@ export function parseComparison(state) {
|
|
|
186
186
|
|
|
187
187
|
if (tok.type === 'operator' && isBinaryOp(tok.value)) {
|
|
188
188
|
consume(state)
|
|
189
|
-
const right =
|
|
189
|
+
const right = parseAdditive(state)
|
|
190
190
|
return {
|
|
191
191
|
type: 'binary',
|
|
192
192
|
op: tok.value,
|
package/src/parse/expression.js
CHANGED
|
@@ -272,6 +272,54 @@ function parseNot(state) {
|
|
|
272
272
|
return parseComparison(state)
|
|
273
273
|
}
|
|
274
274
|
|
|
275
|
+
/**
|
|
276
|
+
* @param {ParserState} state
|
|
277
|
+
* @returns {ExprNode}
|
|
278
|
+
*/
|
|
279
|
+
export function parseAdditive(state) {
|
|
280
|
+
let node = parseMultiplicative(state)
|
|
281
|
+
while (true) {
|
|
282
|
+
const tok = current(state)
|
|
283
|
+
if (tok.type === 'operator' && (tok.value === '+' || tok.value === '-')) {
|
|
284
|
+
consume(state)
|
|
285
|
+
const right = parseMultiplicative(state)
|
|
286
|
+
node = {
|
|
287
|
+
type: 'binary',
|
|
288
|
+
op: tok.value,
|
|
289
|
+
left: node,
|
|
290
|
+
right,
|
|
291
|
+
}
|
|
292
|
+
} else {
|
|
293
|
+
break
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return node
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @param {ParserState} state
|
|
301
|
+
* @returns {ExprNode}
|
|
302
|
+
*/
|
|
303
|
+
function parseMultiplicative(state) {
|
|
304
|
+
let node = parsePrimary(state)
|
|
305
|
+
while (true) {
|
|
306
|
+
const tok = current(state)
|
|
307
|
+
if (tok.type === 'operator' && (tok.value === '*' || tok.value === '/' || tok.value === '%')) {
|
|
308
|
+
consume(state)
|
|
309
|
+
const right = parsePrimary(state)
|
|
310
|
+
node = {
|
|
311
|
+
type: 'binary',
|
|
312
|
+
op: tok.value,
|
|
313
|
+
left: node,
|
|
314
|
+
right,
|
|
315
|
+
}
|
|
316
|
+
} else {
|
|
317
|
+
break
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return node
|
|
321
|
+
}
|
|
322
|
+
|
|
275
323
|
/**
|
|
276
324
|
* Creates an ExprCursor adapter for the ParserState.
|
|
277
325
|
*
|
package/src/types.d.ts
CHANGED
|
@@ -27,7 +27,7 @@ export interface ExecuteSqlOptions {
|
|
|
27
27
|
query: string
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
export type SqlPrimitive = string | number | bigint | boolean | null
|
|
30
|
+
export type SqlPrimitive = string | number | bigint | boolean | SqlPrimitive[] | Record<string, any> | null
|
|
31
31
|
|
|
32
32
|
export interface SelectStatement {
|
|
33
33
|
distinct: boolean
|
|
@@ -54,7 +54,9 @@ export interface FromSubquery {
|
|
|
54
54
|
alias: string
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
export type
|
|
57
|
+
export type ArithmeticOp = '+' | '-' | '*' | '/' | '%'
|
|
58
|
+
|
|
59
|
+
export type BinaryOp = 'AND' | 'OR' | 'LIKE' | ComparisonOp | ArithmeticOp
|
|
58
60
|
|
|
59
61
|
export type ComparisonOp = '=' | '!=' | '<>' | '<' | '>' | '<=' | '>='
|
|
60
62
|
|
|
@@ -146,9 +148,20 @@ export interface StarColumn {
|
|
|
146
148
|
alias?: string
|
|
147
149
|
}
|
|
148
150
|
|
|
149
|
-
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX'
|
|
150
|
-
|
|
151
|
-
export type StringFunc =
|
|
151
|
+
export type AggregateFunc = 'COUNT' | 'SUM' | 'AVG' | 'MIN' | 'MAX' | 'JSON_ARRAYAGG'
|
|
152
|
+
|
|
153
|
+
export type StringFunc =
|
|
154
|
+
| 'UPPER'
|
|
155
|
+
| 'LOWER'
|
|
156
|
+
| 'CONCAT'
|
|
157
|
+
| 'LENGTH'
|
|
158
|
+
| 'SUBSTRING'
|
|
159
|
+
| 'SUBSTR'
|
|
160
|
+
| 'TRIM'
|
|
161
|
+
| 'REPLACE'
|
|
162
|
+
| 'JSON_VALUE'
|
|
163
|
+
| 'JSON_QUERY'
|
|
164
|
+
| 'JSON_OBJECT'
|
|
152
165
|
|
|
153
166
|
export interface AggregateArgStar {
|
|
154
167
|
kind: 'star'
|
package/src/validation.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* @returns {name is AggregateFunc}
|
|
6
6
|
*/
|
|
7
7
|
export function isAggregateFunc(name) {
|
|
8
|
-
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX'].includes(name)
|
|
8
|
+
return ['COUNT', 'SUM', 'AVG', 'MIN', 'MAX', 'JSON_ARRAYAGG'].includes(name)
|
|
9
9
|
}
|
|
10
10
|
|
|
11
11
|
/**
|
|
@@ -13,7 +13,21 @@ export function isAggregateFunc(name) {
|
|
|
13
13
|
* @returns {name is StringFunc}
|
|
14
14
|
*/
|
|
15
15
|
export function isStringFunc(name) {
|
|
16
|
-
return [
|
|
16
|
+
return [
|
|
17
|
+
'UPPER',
|
|
18
|
+
'LOWER',
|
|
19
|
+
'CONCAT',
|
|
20
|
+
'LENGTH',
|
|
21
|
+
'SUBSTRING',
|
|
22
|
+
'SUBSTR',
|
|
23
|
+
'TRIM',
|
|
24
|
+
'REPLACE',
|
|
25
|
+
'RANDOM',
|
|
26
|
+
'RAND',
|
|
27
|
+
'JSON_VALUE',
|
|
28
|
+
'JSON_QUERY',
|
|
29
|
+
'JSON_OBJECT',
|
|
30
|
+
].includes(name)
|
|
17
31
|
}
|
|
18
32
|
|
|
19
33
|
/**
|