squirreling 0.1.0 → 0.1.2
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 +7 -0
- package/package.json +2 -2
- package/src/execute/aggregates.js +35 -8
- package/src/execute/execute.js +12 -0
- package/src/execute/expression.js +15 -0
- package/src/execute/having.js +193 -0
- package/src/parse/expression.js +42 -2
- package/src/parse/parse.js +22 -2
- package/src/types.d.ts +20 -5
package/README.md
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# Squirreling SQL Engine
|
|
2
2
|
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/squirreling)
|
|
6
|
+
[](https://www.npmjs.com/package/squirreling)
|
|
7
|
+
[](https://www.npmjs.com/package/squirreling)
|
|
8
|
+
[](https://github.com/hyparam/squirreling/actions)
|
|
3
9
|
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
[](https://www.npmjs.com/package/squirreling?activeTab=dependencies)
|
|
4
11
|
|
|
5
12
|
Squirreling is a lightweight SQL engine for JavaScript applications, designed to provide efficient and easy-to-use database functionalities in the browser.
|
|
6
13
|
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"author": "Hyperparam",
|
|
5
5
|
"homepage": "https://hyperparam.app",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
9
|
-
"url": "git+https://github.com/hyparam/
|
|
9
|
+
"url": "git+https://github.com/hyparam/squirreling.git"
|
|
10
10
|
},
|
|
11
11
|
"type": "module",
|
|
12
12
|
"sideEffects": false,
|
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { evaluateExpr } from './expression.js'
|
|
1
2
|
|
|
2
3
|
/**
|
|
3
4
|
* Evaluates an aggregate function over a set of rows
|
|
4
5
|
*
|
|
5
|
-
* @import { AggregateColumn, Row } from '../types.js'
|
|
6
|
+
* @import { AggregateColumn, ExprNode, Row } from '../types.js'
|
|
6
7
|
* @param {AggregateColumn} col - aggregate column definition
|
|
7
8
|
* @param {Row[]} rows - rows to aggregate
|
|
8
9
|
* @returns {number | null} aggregated result
|
|
@@ -12,10 +13,9 @@ export function evaluateAggregate(col, rows) {
|
|
|
12
13
|
|
|
13
14
|
if (func === 'COUNT') {
|
|
14
15
|
if (arg.kind === 'star') return rows.length
|
|
15
|
-
const field = arg.column
|
|
16
16
|
let count = 0
|
|
17
17
|
for (let i = 0; i < rows.length; i += 1) {
|
|
18
|
-
const v = rows[i]
|
|
18
|
+
const v = evaluateExpr(arg.expr, rows[i])
|
|
19
19
|
if (v !== null && v !== undefined) {
|
|
20
20
|
count += 1
|
|
21
21
|
}
|
|
@@ -27,7 +27,6 @@ export function evaluateAggregate(col, rows) {
|
|
|
27
27
|
if (arg.kind === 'star') {
|
|
28
28
|
throw new Error(func + '(*) is not supported, use a column name')
|
|
29
29
|
}
|
|
30
|
-
const field = arg.column
|
|
31
30
|
let sum = 0
|
|
32
31
|
let count = 0
|
|
33
32
|
/** @type {number | null} */
|
|
@@ -36,7 +35,7 @@ export function evaluateAggregate(col, rows) {
|
|
|
36
35
|
let max = null
|
|
37
36
|
|
|
38
37
|
for (let i = 0; i < rows.length; i += 1) {
|
|
39
|
-
const raw = rows[i]
|
|
38
|
+
const raw = evaluateExpr(arg.expr, rows[i])
|
|
40
39
|
if (raw == null) continue
|
|
41
40
|
const num = Number(raw)
|
|
42
41
|
if (!Number.isFinite(num)) continue
|
|
@@ -63,11 +62,39 @@ export function evaluateAggregate(col, rows) {
|
|
|
63
62
|
|
|
64
63
|
/**
|
|
65
64
|
* Generates a default alias name for an aggregate function
|
|
66
|
-
*
|
|
67
|
-
*
|
|
65
|
+
* (e.g., "count_all", "sum_amount")
|
|
66
|
+
*
|
|
67
|
+
* @param {AggregateColumn} col
|
|
68
|
+
* @returns {string}
|
|
68
69
|
*/
|
|
69
70
|
export function defaultAggregateAlias(col) {
|
|
70
71
|
const base = col.func.toLowerCase()
|
|
71
72
|
if (col.arg.kind === 'star') return base + '_all'
|
|
72
|
-
return base + '_' + col.arg.
|
|
73
|
+
return base + '_' + defaultAggregateAliasExpr(col.arg.expr)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @param {ExprNode} expr
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export
|
|
81
|
+
function defaultAggregateAliasExpr(expr) {
|
|
82
|
+
if (expr.type === 'identifier') {
|
|
83
|
+
return expr.name
|
|
84
|
+
}
|
|
85
|
+
if (expr.type === 'literal') {
|
|
86
|
+
return String(expr.value)
|
|
87
|
+
}
|
|
88
|
+
if (expr.type === 'cast') {
|
|
89
|
+
return defaultAggregateAliasExpr(expr.expr) + '_as_' + expr.toType
|
|
90
|
+
}
|
|
91
|
+
if (expr.type === 'unary') {
|
|
92
|
+
return expr.op + '_' + defaultAggregateAliasExpr(expr.argument)
|
|
93
|
+
}
|
|
94
|
+
if (expr.type === 'binary') {
|
|
95
|
+
return defaultAggregateAliasExpr(expr.left) + '_' + expr.op + '_' + defaultAggregateAliasExpr(expr.right)
|
|
96
|
+
}
|
|
97
|
+
if (expr.type === 'function') {
|
|
98
|
+
return expr.name.toLowerCase() + '_' + expr.args.map(defaultAggregateAliasExpr).join('_')
|
|
99
|
+
}
|
|
73
100
|
}
|
package/src/execute/execute.js
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { defaultAggregateAlias, evaluateAggregate } from './aggregates.js'
|
|
6
6
|
import { evaluateExpr } from './expression.js'
|
|
7
|
+
import { createHavingContext, evaluateHavingExpr } from './having.js'
|
|
7
8
|
import { parseSql } from '../parse/parse.js'
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -230,6 +231,17 @@ function evaluateSelectAst(select, rows) {
|
|
|
230
231
|
continue
|
|
231
232
|
}
|
|
232
233
|
}
|
|
234
|
+
|
|
235
|
+
// Apply HAVING filter before adding to projected results
|
|
236
|
+
if (select.having) {
|
|
237
|
+
// For HAVING, we need to evaluate aggregates in the context of the group
|
|
238
|
+
// Create a special row context that includes both the group data and aggregate values
|
|
239
|
+
const havingContext = createHavingContext(resultRow, group)
|
|
240
|
+
if (!evaluateHavingExpr(select.having, havingContext, group)) {
|
|
241
|
+
continue
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
233
245
|
projected.push(resultRow)
|
|
234
246
|
}
|
|
235
247
|
} else {
|
|
@@ -79,6 +79,21 @@ export function evaluateExpr(node, row) {
|
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
+
// BETWEEN and NOT BETWEEN
|
|
83
|
+
if (node.type === 'between' || node.type === 'not between') {
|
|
84
|
+
const expr = evaluateExpr(node.expr, row)
|
|
85
|
+
const lower = evaluateExpr(node.lower, row)
|
|
86
|
+
const upper = evaluateExpr(node.upper, row)
|
|
87
|
+
|
|
88
|
+
// If any value is NULL, return false (SQL behavior)
|
|
89
|
+
if (expr == null || lower == null || upper == null) {
|
|
90
|
+
return false
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const isBetween = expr >= lower && expr <= upper
|
|
94
|
+
return node.type === 'between' ? isBetween : !isBetween
|
|
95
|
+
}
|
|
96
|
+
|
|
82
97
|
// Function calls
|
|
83
98
|
if (node.type === 'function') {
|
|
84
99
|
const funcName = node.name.toUpperCase()
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ExprNode, Row, SqlPrimitive } from '../types.js'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { evaluateExpr } from './expression.js'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Creates a context for evaluating HAVING expressions
|
|
9
|
+
* @param {Row} resultRow - The aggregated result row
|
|
10
|
+
* @param {Row[]} group - The group of rows
|
|
11
|
+
* @returns {Row} A context row for HAVING evaluation
|
|
12
|
+
*/
|
|
13
|
+
export function createHavingContext(resultRow, group) {
|
|
14
|
+
// Include the first row of the group (for GROUP BY columns)
|
|
15
|
+
const firstRow = group[0] || {}
|
|
16
|
+
// Merge with result row (which has aggregates computed)
|
|
17
|
+
return { ...firstRow, ...resultRow }
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Evaluates a HAVING expression with support for aggregate functions
|
|
22
|
+
* @param {ExprNode} expr - The HAVING expression
|
|
23
|
+
* @param {Row} context - The context row with aggregated values
|
|
24
|
+
* @param {Row[]} group - The group of rows for re-evaluating aggregates
|
|
25
|
+
* @returns {boolean} Whether the HAVING condition is satisfied
|
|
26
|
+
*/
|
|
27
|
+
export function evaluateHavingExpr(expr, context, group) {
|
|
28
|
+
// For HAVING, we need special handling of aggregate functions
|
|
29
|
+
// They need to be re-evaluated against the group
|
|
30
|
+
if (expr.type === 'function') {
|
|
31
|
+
const funcName = expr.name.toUpperCase()
|
|
32
|
+
if (['COUNT', 'SUM', 'AVG', 'MIN', 'MAX'].includes(funcName)) {
|
|
33
|
+
// Evaluate aggregate function on the group
|
|
34
|
+
return Boolean(evaluateAggregateFunction(funcName, expr.args, group))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (expr.type === 'binary') {
|
|
39
|
+
const left = evaluateHavingValue(expr.left, context, group)
|
|
40
|
+
const right = evaluateHavingValue(expr.right, context, group)
|
|
41
|
+
|
|
42
|
+
if (expr.op === 'AND') {
|
|
43
|
+
return Boolean(left && right)
|
|
44
|
+
}
|
|
45
|
+
if (expr.op === 'OR') {
|
|
46
|
+
return Boolean(left || right)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Handle NULL comparisons
|
|
50
|
+
if (left == null || right == null) {
|
|
51
|
+
if (expr.op === '=' || expr.op === '!=' || expr.op === '<>') {
|
|
52
|
+
return false
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (expr.op === '=') return left === right
|
|
57
|
+
if (expr.op === '!=' || expr.op === '<>') return left !== right
|
|
58
|
+
if (expr.op === '<') return left < right
|
|
59
|
+
if (expr.op === '>') return left > right
|
|
60
|
+
if (expr.op === '<=') return left <= right
|
|
61
|
+
if (expr.op === '>=') return left >= right
|
|
62
|
+
if (expr.op === 'LIKE') {
|
|
63
|
+
const str = String(left)
|
|
64
|
+
const pattern = String(right)
|
|
65
|
+
const regexPattern = pattern
|
|
66
|
+
.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')
|
|
67
|
+
.replace(/%/g, '.*')
|
|
68
|
+
.replace(/_/g, '.')
|
|
69
|
+
const regex = new RegExp('^' + regexPattern + '$', 'i')
|
|
70
|
+
return regex.test(str)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (expr.type === 'unary') {
|
|
75
|
+
if (expr.op === 'NOT') {
|
|
76
|
+
return !evaluateHavingExpr(expr.argument, context, group)
|
|
77
|
+
}
|
|
78
|
+
if (expr.op === 'IS NULL') {
|
|
79
|
+
return evaluateHavingValue(expr.argument, context, group) == null
|
|
80
|
+
}
|
|
81
|
+
if (expr.op === 'IS NOT NULL') {
|
|
82
|
+
return evaluateHavingValue(expr.argument, context, group) != null
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (expr.type === 'between' || expr.type === 'not between') {
|
|
87
|
+
const exprVal = evaluateHavingValue(expr.expr, context, group)
|
|
88
|
+
const lower = evaluateHavingValue(expr.lower, context, group)
|
|
89
|
+
const upper = evaluateHavingValue(expr.upper, context, group)
|
|
90
|
+
|
|
91
|
+
// If any value is NULL, return false (SQL behavior)
|
|
92
|
+
if (exprVal == null || lower == null || upper == null) {
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const isBetween = exprVal >= lower && exprVal <= upper
|
|
97
|
+
return expr.type === 'between' ? isBetween : !isBetween
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// For other expression types, use the context row
|
|
101
|
+
return Boolean(evaluateExpr(expr, context))
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Evaluates a value in a HAVING expression
|
|
106
|
+
* @param {ExprNode} expr - The expression
|
|
107
|
+
* @param {Row} context - The context row
|
|
108
|
+
* @param {Row[]} group - The group of rows
|
|
109
|
+
* @returns {SqlPrimitive} The evaluated value
|
|
110
|
+
*/
|
|
111
|
+
function evaluateHavingValue(expr, context, group) {
|
|
112
|
+
if (expr.type === 'function') {
|
|
113
|
+
const funcName = expr.name.toUpperCase()
|
|
114
|
+
if (['COUNT', 'SUM', 'AVG', 'MIN', 'MAX'].includes(funcName)) {
|
|
115
|
+
return evaluateAggregateFunction(funcName, expr.args, group)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// For binary expressions, we need to use evaluateHavingExpr to properly handle aggregates
|
|
120
|
+
if (expr.type === 'binary' || expr.type === 'unary' || expr.type === 'between' || expr.type === 'not between') {
|
|
121
|
+
return evaluateHavingExpr(expr, context, group)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
return evaluateExpr(expr, context)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Evaluates an aggregate function on a group
|
|
129
|
+
* @param {string} funcName - The aggregate function name
|
|
130
|
+
* @param {ExprNode[]} args - The function arguments
|
|
131
|
+
* @param {Row[]} group - The group of rows
|
|
132
|
+
* @returns {SqlPrimitive} The aggregate result
|
|
133
|
+
*/
|
|
134
|
+
function evaluateAggregateFunction(funcName, args, group) {
|
|
135
|
+
if (funcName === 'COUNT') {
|
|
136
|
+
if (args.length === 1 && args[0].type === 'identifier' && args[0].name === '*') {
|
|
137
|
+
return group.length
|
|
138
|
+
}
|
|
139
|
+
// COUNT(column) - count non-null values
|
|
140
|
+
let count = 0
|
|
141
|
+
for (const row of group) {
|
|
142
|
+
const val = evaluateExpr(args[0], row)
|
|
143
|
+
if (val != null) count++
|
|
144
|
+
}
|
|
145
|
+
return count
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (funcName === 'SUM') {
|
|
149
|
+
let sum = 0
|
|
150
|
+
for (const row of group) {
|
|
151
|
+
const val = evaluateExpr(args[0], row)
|
|
152
|
+
if (val != null) sum += Number(val)
|
|
153
|
+
}
|
|
154
|
+
return sum
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (funcName === 'AVG') {
|
|
158
|
+
let sum = 0
|
|
159
|
+
let count = 0
|
|
160
|
+
for (const row of group) {
|
|
161
|
+
const val = evaluateExpr(args[0], row)
|
|
162
|
+
if (val != null) {
|
|
163
|
+
sum += Number(val)
|
|
164
|
+
count++
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return count > 0 ? sum / count : null
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (funcName === 'MIN') {
|
|
171
|
+
let min = null
|
|
172
|
+
for (const row of group) {
|
|
173
|
+
const val = evaluateExpr(args[0], row)
|
|
174
|
+
if (val != null && (min == null || val < min)) {
|
|
175
|
+
min = val
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return min
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (funcName === 'MAX') {
|
|
182
|
+
let max = null
|
|
183
|
+
for (const row of group) {
|
|
184
|
+
const val = evaluateExpr(args[0], row)
|
|
185
|
+
if (val != null && (max == null || val > max)) {
|
|
186
|
+
max = val
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return max
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
throw new Error('Unsupported aggregate function: ' + funcName)
|
|
193
|
+
}
|
package/src/parse/expression.js
CHANGED
|
@@ -42,8 +42,17 @@ export function parsePrimary(c) {
|
|
|
42
42
|
|
|
43
43
|
if (c.current().type !== 'paren' || c.current().value !== ')') {
|
|
44
44
|
while (true) {
|
|
45
|
-
|
|
46
|
-
|
|
45
|
+
// Handle COUNT(*) - treat * as a special identifier
|
|
46
|
+
if (c.current().type === 'operator' && c.current().value === '*') {
|
|
47
|
+
c.consume()
|
|
48
|
+
args.push({
|
|
49
|
+
type: 'identifier',
|
|
50
|
+
name: '*',
|
|
51
|
+
})
|
|
52
|
+
} else {
|
|
53
|
+
const arg = parseExpression(c)
|
|
54
|
+
args.push(arg)
|
|
55
|
+
}
|
|
47
56
|
if (!c.match('comma')) break
|
|
48
57
|
}
|
|
49
58
|
}
|
|
@@ -217,6 +226,37 @@ function parseComparison(c) {
|
|
|
217
226
|
}
|
|
218
227
|
}
|
|
219
228
|
|
|
229
|
+
// [NOT] BETWEEN
|
|
230
|
+
if (tok.type === 'keyword' && tok.value === 'NOT') {
|
|
231
|
+
const nextTok = c.peek(1)
|
|
232
|
+
if (nextTok.type === 'keyword' && nextTok.value === 'BETWEEN') {
|
|
233
|
+
c.consume() // NOT
|
|
234
|
+
c.consume() // BETWEEN
|
|
235
|
+
const lower = parsePrimary(c)
|
|
236
|
+
c.expect('keyword', 'AND')
|
|
237
|
+
const upper = parsePrimary(c)
|
|
238
|
+
return {
|
|
239
|
+
type: 'not between',
|
|
240
|
+
expr: left,
|
|
241
|
+
lower,
|
|
242
|
+
upper,
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (tok.type === 'keyword' && tok.value === 'BETWEEN') {
|
|
248
|
+
c.consume()
|
|
249
|
+
const lower = parsePrimary(c)
|
|
250
|
+
c.expect('keyword', 'AND')
|
|
251
|
+
const upper = parsePrimary(c)
|
|
252
|
+
return {
|
|
253
|
+
type: 'between',
|
|
254
|
+
expr: left,
|
|
255
|
+
lower,
|
|
256
|
+
upper,
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
220
260
|
if (tok.type === 'operator' && isComparisonOperator(tok.value)) {
|
|
221
261
|
c.consume()
|
|
222
262
|
const right = parsePrimary(c)
|
package/src/parse/parse.js
CHANGED
|
@@ -226,11 +226,24 @@ function parseAggregateItem(state, func) {
|
|
|
226
226
|
if (cur.type === 'operator' && cur.value === '*') {
|
|
227
227
|
consume(state)
|
|
228
228
|
arg = { kind: 'star' }
|
|
229
|
+
} else if (cur.type === 'identifier' && cur.value === 'CAST') {
|
|
230
|
+
// Handle CAST inside aggregate: SUM(CAST(x AS type))
|
|
231
|
+
expectIdentifier(state) // consume CAST
|
|
232
|
+
expect(state, 'paren', '(')
|
|
233
|
+
const cursor = createExprCursor(state)
|
|
234
|
+
const expr = parseExpression(cursor)
|
|
235
|
+
expect(state, 'keyword', 'AS')
|
|
236
|
+
const typeTok = expectIdentifier(state)
|
|
237
|
+
expect(state, 'paren', ')')
|
|
238
|
+
arg = {
|
|
239
|
+
kind: 'expression',
|
|
240
|
+
expr: { type: 'cast', expr, toType: typeTok.value },
|
|
241
|
+
}
|
|
229
242
|
} else {
|
|
230
243
|
const colTok = expectIdentifier(state)
|
|
231
244
|
arg = {
|
|
232
|
-
kind: '
|
|
233
|
-
|
|
245
|
+
kind: 'expression',
|
|
246
|
+
expr: { type: 'identifier', name: colTok.value },
|
|
234
247
|
}
|
|
235
248
|
}
|
|
236
249
|
|
|
@@ -394,6 +407,8 @@ function parseSelectInternal(state) {
|
|
|
394
407
|
let where
|
|
395
408
|
/** @type {ExprNode[]} */
|
|
396
409
|
const groupBy = []
|
|
410
|
+
/** @type {ExprNode | undefined} */
|
|
411
|
+
let having
|
|
397
412
|
/** @type {OrderByItem[]} */
|
|
398
413
|
const orderBy = []
|
|
399
414
|
/** @type {number | undefined} */
|
|
@@ -416,6 +431,10 @@ function parseSelectInternal(state) {
|
|
|
416
431
|
}
|
|
417
432
|
}
|
|
418
433
|
|
|
434
|
+
if (match(state, 'keyword', 'HAVING')) {
|
|
435
|
+
having = parseExpression(cursor)
|
|
436
|
+
}
|
|
437
|
+
|
|
419
438
|
if (match(state, 'keyword', 'ORDER')) {
|
|
420
439
|
expect(state, 'keyword', 'BY')
|
|
421
440
|
while (true) {
|
|
@@ -484,6 +503,7 @@ function parseSelectInternal(state) {
|
|
|
484
503
|
joins,
|
|
485
504
|
where,
|
|
486
505
|
groupBy,
|
|
506
|
+
having,
|
|
487
507
|
orderBy,
|
|
488
508
|
limit,
|
|
489
509
|
offset,
|
package/src/types.d.ts
CHANGED
|
@@ -9,6 +9,7 @@ export interface SelectStatement {
|
|
|
9
9
|
joins: JoinClause[]
|
|
10
10
|
where?: ExprNode
|
|
11
11
|
groupBy: ExprNode[]
|
|
12
|
+
having?: ExprNode
|
|
12
13
|
orderBy: OrderByItem[]
|
|
13
14
|
limit?: number
|
|
14
15
|
offset?: number
|
|
@@ -81,7 +82,21 @@ export interface CastNode {
|
|
|
81
82
|
toType: string
|
|
82
83
|
}
|
|
83
84
|
|
|
84
|
-
export
|
|
85
|
+
export interface BetweenNode {
|
|
86
|
+
type: 'between' | 'not between'
|
|
87
|
+
expr: ExprNode
|
|
88
|
+
lower: ExprNode
|
|
89
|
+
upper: ExprNode
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export type ExprNode =
|
|
93
|
+
| LiteralNode
|
|
94
|
+
| IdentifierNode
|
|
95
|
+
| UnaryNode
|
|
96
|
+
| BinaryNode
|
|
97
|
+
| FunctionNode
|
|
98
|
+
| CastNode
|
|
99
|
+
| BetweenNode
|
|
85
100
|
|
|
86
101
|
export interface StarColumn {
|
|
87
102
|
kind: 'star'
|
|
@@ -102,12 +117,12 @@ export interface AggregateArgStar {
|
|
|
102
117
|
kind: 'star'
|
|
103
118
|
}
|
|
104
119
|
|
|
105
|
-
export interface
|
|
106
|
-
kind: '
|
|
107
|
-
|
|
120
|
+
export interface AggregateArgExpression {
|
|
121
|
+
kind: 'expression'
|
|
122
|
+
expr: ExprNode
|
|
108
123
|
}
|
|
109
124
|
|
|
110
|
-
export type AggregateArg = AggregateArgStar |
|
|
125
|
+
export type AggregateArg = AggregateArgStar | AggregateArgExpression
|
|
111
126
|
|
|
112
127
|
export interface AggregateColumn {
|
|
113
128
|
kind: 'aggregate'
|