squirreling 0.7.9 → 0.8.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 +46 -0
- package/package.json +6 -6
- package/src/backend/dataSource.js +52 -47
- package/src/execute/aggregates.js +150 -0
- package/src/execute/columns.js +0 -39
- package/src/execute/execute.js +158 -415
- package/src/execute/join.js +179 -333
- package/src/execute/sort.js +99 -0
- package/src/execute/utils.js +18 -49
- package/src/executionErrors.js +10 -10
- package/src/expression/binary.js +51 -0
- package/src/{execute → expression}/date.js +18 -18
- package/src/{execute/expression.js → expression/evaluate.js} +61 -62
- package/src/{execute → expression}/math.js +46 -81
- package/src/{execute → expression}/regexp.js +7 -7
- package/src/{execute → expression}/strings.js +33 -45
- package/src/index.d.ts +15 -1
- package/src/parse/expression.js +42 -50
- package/src/parse/joins.js +10 -11
- package/src/parse/parse.js +14 -3
- package/src/parse/state.js +2 -1
- package/src/parse/types.d.ts +30 -0
- package/src/plan/plan.js +234 -0
- package/src/plan/types.d.ts +101 -0
- package/src/types.d.ts +19 -39
- package/src/validation.js +66 -2
- package/src/validationErrors.js +9 -7
- package/src/execute/having.js +0 -202
- package/src/execute/tableSource.js +0 -63
package/src/execute/having.js
DELETED
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
import { unknownFunctionError } from '../parseErrors.js'
|
|
2
|
-
import { isAggregateFunc } from '../validation.js'
|
|
3
|
-
import { evaluateExpr } from './expression.js'
|
|
4
|
-
import { applyBinaryOp } from './utils.js'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* @import { AggregateFunc, AsyncDataSource, ExprNode, AsyncRow, SqlPrimitive, UserDefinedFunction } from '../types.js'
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Evaluates a HAVING expression with support for aggregate functions
|
|
12
|
-
*
|
|
13
|
-
* @param {Object} options
|
|
14
|
-
* @param {ExprNode} options.expr - the HAVING expression
|
|
15
|
-
* @param {AsyncRow} options.row - the aggregated result row
|
|
16
|
-
* @param {AsyncRow[]} options.group - the group of rows for re-evaluating aggregates
|
|
17
|
-
* @param {Record<string, AsyncDataSource>} options.tables
|
|
18
|
-
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
19
|
-
* @returns {Promise<boolean>} whether the HAVING condition is satisfied
|
|
20
|
-
*/
|
|
21
|
-
export async function evaluateHavingExpr({ expr, row, group, tables, functions }) {
|
|
22
|
-
// Having context
|
|
23
|
-
const context = { ...group[0], ...row }
|
|
24
|
-
|
|
25
|
-
// For HAVING, we need special handling of aggregate functions
|
|
26
|
-
// They need to be re-evaluated against the group
|
|
27
|
-
if (expr.type === 'function') {
|
|
28
|
-
const funcName = expr.name.toUpperCase()
|
|
29
|
-
if (isAggregateFunc(funcName)) {
|
|
30
|
-
// Evaluate aggregate function on the group
|
|
31
|
-
return Boolean(await evaluateAggregateFunction({ funcName, args: expr.args, filter: expr.filter, group, tables, functions }))
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (expr.type === 'binary') {
|
|
36
|
-
const left = await evaluateHavingValue({ expr: expr.left, context, group, tables, functions })
|
|
37
|
-
|
|
38
|
-
// Short-circuit evaluation for AND and OR
|
|
39
|
-
if (expr.op === 'AND') {
|
|
40
|
-
if (!left) return false
|
|
41
|
-
}
|
|
42
|
-
if (expr.op === 'OR') {
|
|
43
|
-
if (left) return true
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const right = await evaluateHavingValue({ expr: expr.right, context, group, tables, functions })
|
|
47
|
-
return Boolean(applyBinaryOp(expr.op, left, right))
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
if (expr.type === 'unary') {
|
|
51
|
-
if (expr.op === 'NOT') {
|
|
52
|
-
return !await evaluateHavingExpr({ expr: expr.argument, row: context, group, tables, functions })
|
|
53
|
-
}
|
|
54
|
-
if (expr.op === 'IS NULL') {
|
|
55
|
-
return await evaluateHavingValue({ expr: expr.argument, context, group, tables, functions }) == null
|
|
56
|
-
}
|
|
57
|
-
if (expr.op === 'IS NOT NULL') {
|
|
58
|
-
return await evaluateHavingValue({ expr: expr.argument, context, group, tables, functions }) != null
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// For other expression types, use the context row
|
|
63
|
-
return Boolean(await evaluateExpr({ node: expr, row: context, tables, functions }))
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/**
|
|
67
|
-
* Evaluates a value in a HAVING expression
|
|
68
|
-
*
|
|
69
|
-
* @param {Object} options
|
|
70
|
-
* @param {ExprNode} options.expr
|
|
71
|
-
* @param {AsyncRow} options.context - the context row
|
|
72
|
-
* @param {AsyncRow[]} options.group - the group of rows
|
|
73
|
-
* @param {Record<string, AsyncDataSource>} options.tables
|
|
74
|
-
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
75
|
-
* @returns {Promise<SqlPrimitive>} the evaluated value
|
|
76
|
-
*/
|
|
77
|
-
function evaluateHavingValue({ expr, context, group, tables, functions }) {
|
|
78
|
-
if (expr.type === 'function') {
|
|
79
|
-
const funcName = expr.name.toUpperCase()
|
|
80
|
-
if (isAggregateFunc(funcName)) {
|
|
81
|
-
return evaluateAggregateFunction({ funcName, args: expr.args, filter: expr.filter, group, tables, functions })
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// For binary expressions, we need to use evaluateHavingExpr to properly handle aggregates
|
|
86
|
-
if (expr.type === 'binary' || expr.type === 'unary') {
|
|
87
|
-
return evaluateHavingExpr({ expr, row: context, group, tables, functions })
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
return evaluateExpr({ node: expr, row: context, tables, functions })
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Evaluates an aggregate function on a group
|
|
95
|
-
*
|
|
96
|
-
* @param {Object} options
|
|
97
|
-
* @param {AggregateFunc} options.funcName - aggregate function name
|
|
98
|
-
* @param {ExprNode[]} options.args - function arguments
|
|
99
|
-
* @param {ExprNode} [options.filter] - optional FILTER clause expression
|
|
100
|
-
* @param {AsyncRow[]} options.group - the group of rows
|
|
101
|
-
* @param {Record<string, AsyncDataSource>} options.tables
|
|
102
|
-
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
103
|
-
* @returns {Promise<SqlPrimitive>} the aggregate result
|
|
104
|
-
*/
|
|
105
|
-
async function evaluateAggregateFunction({ funcName, args, filter, group, tables, functions }) {
|
|
106
|
-
// Apply FILTER clause if present
|
|
107
|
-
let filteredGroup = group
|
|
108
|
-
if (filter) {
|
|
109
|
-
filteredGroup = []
|
|
110
|
-
for (const row of group) {
|
|
111
|
-
const passes = await evaluateExpr({ node: filter, row, tables, functions })
|
|
112
|
-
if (passes) filteredGroup.push(row)
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (funcName === 'COUNT') {
|
|
117
|
-
if (args.length === 1 && args[0].type === 'identifier' && args[0].name === '*') {
|
|
118
|
-
return filteredGroup.length
|
|
119
|
-
}
|
|
120
|
-
// COUNT(column) - count non-null values
|
|
121
|
-
let count = 0
|
|
122
|
-
for (const row of filteredGroup) {
|
|
123
|
-
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
124
|
-
if (val != null) count++
|
|
125
|
-
}
|
|
126
|
-
return count
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
if (funcName === 'SUM') {
|
|
130
|
-
let sum = 0
|
|
131
|
-
let hasValue = false
|
|
132
|
-
for (const row of filteredGroup) {
|
|
133
|
-
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
134
|
-
if (val != null) {
|
|
135
|
-
sum += Number(val)
|
|
136
|
-
hasValue = true
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return hasValue ? sum : null
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (funcName === 'AVG') {
|
|
143
|
-
let sum = 0
|
|
144
|
-
let count = 0
|
|
145
|
-
for (const row of filteredGroup) {
|
|
146
|
-
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
147
|
-
if (val != null) {
|
|
148
|
-
sum += Number(val)
|
|
149
|
-
count++
|
|
150
|
-
}
|
|
151
|
-
}
|
|
152
|
-
return count > 0 ? sum / count : null
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
if (funcName === 'MIN') {
|
|
156
|
-
let min = null
|
|
157
|
-
for (const row of filteredGroup) {
|
|
158
|
-
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
159
|
-
if (val != null && (min == null || val < min)) {
|
|
160
|
-
min = val
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
return min
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (funcName === 'MAX') {
|
|
167
|
-
let max = null
|
|
168
|
-
for (const row of filteredGroup) {
|
|
169
|
-
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
170
|
-
if (val != null && (max == null || val > max)) {
|
|
171
|
-
max = val
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
return max
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
if (funcName === 'STDDEV_SAMP' || funcName === 'STDDEV_POP') {
|
|
178
|
-
const values = []
|
|
179
|
-
for (const row of filteredGroup) {
|
|
180
|
-
const val = await evaluateExpr({ node: args[0], row, tables, functions })
|
|
181
|
-
if (val == null) continue
|
|
182
|
-
const num = Number(val)
|
|
183
|
-
if (!Number.isFinite(num)) continue
|
|
184
|
-
values.push(num)
|
|
185
|
-
}
|
|
186
|
-
const n = values.length
|
|
187
|
-
if (n === 0) return null
|
|
188
|
-
if (funcName === 'STDDEV_SAMP' && n === 1) return null
|
|
189
|
-
|
|
190
|
-
const mean = values.reduce((a, b) => a + b, 0) / n
|
|
191
|
-
const squaredDiffs = values.reduce((acc, val) => acc + (val - mean) ** 2, 0)
|
|
192
|
-
const divisor = funcName === 'STDDEV_SAMP' ? n - 1 : n
|
|
193
|
-
return Math.sqrt(squaredDiffs / divisor)
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
throw unknownFunctionError({
|
|
197
|
-
funcName,
|
|
198
|
-
positionStart: 0,
|
|
199
|
-
positionEnd: 0,
|
|
200
|
-
validFunctions: 'COUNT, SUM, AVG, MIN, MAX, STDDEV_SAMP, STDDEV_POP',
|
|
201
|
-
})
|
|
202
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import { tableNotFoundError } from '../executionErrors.js'
|
|
2
|
-
import { generatorSource } from '../backend/dataSource.js'
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* @import { AsyncDataSource, CTEDefinition, UserDefinedFunction, WithClause } from '../types.js'
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Gets CTEs defined before the target CTE (excluding the target itself).
|
|
10
|
-
* Enforces SQL scoping rules: each CTE can only reference CTEs defined before it.
|
|
11
|
-
*
|
|
12
|
-
* @param {CTEDefinition[]} allCtes - all CTE definitions in order
|
|
13
|
-
* @param {string} targetCteName - the CTE name (case-insensitive)
|
|
14
|
-
* @returns {WithClause} CTEs available to the target
|
|
15
|
-
*/
|
|
16
|
-
export function getCtesDefinedBefore(allCtes, targetCteName) {
|
|
17
|
-
const available = []
|
|
18
|
-
for (const cte of allCtes) {
|
|
19
|
-
if (cte.name.toLowerCase() === targetCteName) break
|
|
20
|
-
available.push(cte)
|
|
21
|
-
}
|
|
22
|
-
return { ctes: available }
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Resolves a table name to an AsyncDataSource, checking CTEs first
|
|
27
|
-
*
|
|
28
|
-
* @param {string} tableName - the table name to resolve
|
|
29
|
-
* @param {Record<string, AsyncDataSource>} tables - regular tables
|
|
30
|
-
* @param {import('../types.js').WithClause} [withClause] - WITH clause containing CTE definitions
|
|
31
|
-
* @param {Function} [executeSelectFn] - function to execute SELECT for CTEs
|
|
32
|
-
* @param {Record<string, UserDefinedFunction>} [functions]
|
|
33
|
-
* @param {AbortSignal} [signal]
|
|
34
|
-
* @returns {AsyncDataSource}
|
|
35
|
-
*/
|
|
36
|
-
export function resolveTableSource(tableName, tables, withClause, executeSelectFn, functions, signal) {
|
|
37
|
-
// Check CTEs first (case-insensitive) - only build map when CTE is actually found
|
|
38
|
-
if (withClause && executeSelectFn) {
|
|
39
|
-
const lowerName = tableName.toLowerCase()
|
|
40
|
-
const cte = withClause.ctes.find(c => c.name.toLowerCase() === lowerName)
|
|
41
|
-
|
|
42
|
-
if (cte) {
|
|
43
|
-
// CTE reference: wrap in generatorSource, re-execute each time (streaming)
|
|
44
|
-
// Pass only CTEs defined before this one to prevent self-reference
|
|
45
|
-
const availableCtes = getCtesDefinedBefore(withClause.ctes, lowerName)
|
|
46
|
-
|
|
47
|
-
return generatorSource(executeSelectFn({
|
|
48
|
-
select: cte.query,
|
|
49
|
-
tables,
|
|
50
|
-
withClause: availableCtes,
|
|
51
|
-
functions,
|
|
52
|
-
signal,
|
|
53
|
-
}))
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Regular table lookup
|
|
58
|
-
const tableSource = tables[tableName]
|
|
59
|
-
if (tableSource === undefined) {
|
|
60
|
-
throw tableNotFoundError({ tableName })
|
|
61
|
-
}
|
|
62
|
-
return tableSource
|
|
63
|
-
}
|