squirreling 0.8.0 → 0.9.1
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 +3 -3
- package/src/execute/aggregates.js +21 -32
- package/src/execute/execute.js +25 -37
- package/src/execute/join.js +15 -31
- package/src/execute/sort.js +5 -10
- package/src/execute/utils.js +1 -40
- package/src/expression/alias.js +42 -0
- package/src/expression/evaluate.js +56 -59
- package/src/index.d.ts +33 -2
- package/src/index.js +3 -2
- package/src/parse/comparison.js +7 -7
- package/src/parse/expression.js +14 -14
- package/src/parse/functions.js +23 -6
- package/src/parse/parse.js +55 -68
- package/src/parse/state.js +0 -9
- package/src/parse/tokenize.js +2 -2
- package/src/parse/types.d.ts +1 -1
- package/src/parseErrors.js +5 -4
- package/src/plan/columns.js +149 -0
- package/src/plan/plan.js +116 -46
- package/src/plan/types.d.ts +44 -47
- package/src/types.d.ts +24 -6
- package/src/validationErrors.js +10 -5
- package/src/execute/columns.js +0 -102
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "squirreling",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.1",
|
|
4
4
|
"description": "Squirreling Async SQL Engine",
|
|
5
5
|
"author": "Hyperparam",
|
|
6
6
|
"homepage": "https://hyperparam.app",
|
|
@@ -37,10 +37,10 @@
|
|
|
37
37
|
"test": "vitest run"
|
|
38
38
|
},
|
|
39
39
|
"devDependencies": {
|
|
40
|
-
"@types/node": "25.2.
|
|
40
|
+
"@types/node": "25.2.3",
|
|
41
41
|
"@vitest/coverage-v8": "4.0.18",
|
|
42
42
|
"eslint": "9.39.2",
|
|
43
|
-
"eslint-plugin-jsdoc": "62.
|
|
43
|
+
"eslint-plugin-jsdoc": "62.6.0",
|
|
44
44
|
"typescript": "5.9.3",
|
|
45
45
|
"vitest": "4.0.18"
|
|
46
46
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import { derivedAlias } from '../expression/alias.js'
|
|
1
2
|
import { evaluateExpr } from '../expression/evaluate.js'
|
|
2
|
-
import { defaultDerivedAlias, stringify } from './utils.js'
|
|
3
3
|
import { executePlan } from './execute.js'
|
|
4
|
+
import { stringify } from './utils.js'
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* @import { AsyncCells,
|
|
7
|
-
* @import {
|
|
7
|
+
* @import { AsyncCells, AsyncRow, ExecuteContext, SelectColumn } from '../types.js'
|
|
8
|
+
* @import { HashAggregateNode, ScalarAggregateNode } from '../plan/types.js'
|
|
8
9
|
*/
|
|
9
10
|
|
|
10
11
|
/**
|
|
@@ -12,12 +13,10 @@ import { executePlan } from './execute.js'
|
|
|
12
13
|
*
|
|
13
14
|
* @param {SelectColumn[]} selectColumns
|
|
14
15
|
* @param {AsyncRow[]} group
|
|
15
|
-
* @param {
|
|
16
|
-
* @param {Record<string, UserDefinedFunction>} [functions]
|
|
17
|
-
* @param {AbortSignal} [signal]
|
|
16
|
+
* @param {ExecuteContext} context
|
|
18
17
|
* @returns {AsyncRow}
|
|
19
18
|
*/
|
|
20
|
-
function projectAggregateColumns(selectColumns, group,
|
|
19
|
+
function projectAggregateColumns(selectColumns, group, context) {
|
|
21
20
|
/** @type {string[]} */
|
|
22
21
|
const columns = []
|
|
23
22
|
/** @type {AsyncCells} */
|
|
@@ -33,15 +32,13 @@ function projectAggregateColumns(selectColumns, group, tables, functions, signal
|
|
|
33
32
|
}
|
|
34
33
|
}
|
|
35
34
|
} else if (col.kind === 'derived') {
|
|
36
|
-
const alias = col.alias ??
|
|
35
|
+
const alias = col.alias ?? derivedAlias(col.expr)
|
|
37
36
|
columns.push(alias)
|
|
38
37
|
cells[alias] = () => evaluateExpr({
|
|
39
38
|
node: col.expr,
|
|
40
39
|
row: group[0] ?? { columns: [], cells: {} },
|
|
41
|
-
tables,
|
|
42
|
-
functions,
|
|
43
40
|
rows: group,
|
|
44
|
-
|
|
41
|
+
context,
|
|
45
42
|
})
|
|
46
43
|
}
|
|
47
44
|
}
|
|
@@ -57,13 +54,11 @@ function projectAggregateColumns(selectColumns, group, tables, functions, signal
|
|
|
57
54
|
* @yields {AsyncRow}
|
|
58
55
|
*/
|
|
59
56
|
export async function* executeHashAggregate(plan, context) {
|
|
60
|
-
const { tables, functions, signal } = context
|
|
61
|
-
|
|
62
57
|
// Collect all rows
|
|
63
58
|
/** @type {AsyncRow[]} */
|
|
64
59
|
const allRows = []
|
|
65
|
-
for await (const row of executePlan(plan.child, context)) {
|
|
66
|
-
if (signal?.aborted) return
|
|
60
|
+
for await (const row of executePlan({ plan: plan.child, context })) {
|
|
61
|
+
if (context.signal?.aborted) return
|
|
67
62
|
allRows.push(row)
|
|
68
63
|
}
|
|
69
64
|
|
|
@@ -77,7 +72,7 @@ export async function* executeHashAggregate(plan, context) {
|
|
|
77
72
|
/** @type {string[]} */
|
|
78
73
|
const keyParts = []
|
|
79
74
|
for (const expr of plan.groupBy) {
|
|
80
|
-
const v = await evaluateExpr({ node: expr, row,
|
|
75
|
+
const v = await evaluateExpr({ node: expr, row, context })
|
|
81
76
|
keyParts.push(stringify(v))
|
|
82
77
|
}
|
|
83
78
|
const key = keyParts.join('|')
|
|
@@ -92,18 +87,16 @@ export async function* executeHashAggregate(plan, context) {
|
|
|
92
87
|
|
|
93
88
|
// Yield one row per group
|
|
94
89
|
for (const group of groups) {
|
|
95
|
-
const asyncRow = projectAggregateColumns(plan.columns, group,
|
|
90
|
+
const asyncRow = projectAggregateColumns(plan.columns, group, context)
|
|
96
91
|
|
|
97
92
|
// Apply HAVING filter
|
|
98
93
|
if (plan.having) {
|
|
99
|
-
const
|
|
94
|
+
const havingRow = { ...group[0], ...asyncRow }
|
|
100
95
|
const passes = await evaluateExpr({
|
|
101
96
|
node: plan.having,
|
|
102
|
-
row:
|
|
97
|
+
row: havingRow,
|
|
103
98
|
rows: group,
|
|
104
|
-
|
|
105
|
-
functions,
|
|
106
|
-
signal,
|
|
99
|
+
context,
|
|
107
100
|
})
|
|
108
101
|
if (!passes) continue
|
|
109
102
|
}
|
|
@@ -120,28 +113,24 @@ export async function* executeHashAggregate(plan, context) {
|
|
|
120
113
|
* @yields {AsyncRow}
|
|
121
114
|
*/
|
|
122
115
|
export async function* executeScalarAggregate(plan, context) {
|
|
123
|
-
const { tables, functions, signal } = context
|
|
124
|
-
|
|
125
116
|
// Collect all rows into single group
|
|
126
117
|
/** @type {AsyncRow[]} */
|
|
127
118
|
const group = []
|
|
128
|
-
for await (const row of executePlan(plan.child, context)) {
|
|
129
|
-
if (signal?.aborted) return
|
|
119
|
+
for await (const row of executePlan({ plan: plan.child, context })) {
|
|
120
|
+
if (context.signal?.aborted) return
|
|
130
121
|
group.push(row)
|
|
131
122
|
}
|
|
132
123
|
|
|
133
|
-
const asyncRow = projectAggregateColumns(plan.columns, group,
|
|
124
|
+
const asyncRow = projectAggregateColumns(plan.columns, group, context)
|
|
134
125
|
|
|
135
126
|
// Apply HAVING filter
|
|
136
127
|
if (plan.having) {
|
|
137
|
-
const
|
|
128
|
+
const havingRow = { ...group[0], ...asyncRow }
|
|
138
129
|
const passes = await evaluateExpr({
|
|
139
130
|
node: plan.having,
|
|
140
|
-
row:
|
|
131
|
+
row: havingRow,
|
|
141
132
|
rows: group,
|
|
142
|
-
|
|
143
|
-
functions,
|
|
144
|
-
signal,
|
|
133
|
+
context,
|
|
145
134
|
})
|
|
146
135
|
if (!passes) return
|
|
147
136
|
}
|
package/src/execute/execute.js
CHANGED
|
@@ -1,36 +1,28 @@
|
|
|
1
1
|
import { memorySource } from '../backend/dataSource.js'
|
|
2
2
|
import { tableNotFoundError } from '../executionErrors.js'
|
|
3
|
+
import { derivedAlias } from '../expression/alias.js'
|
|
3
4
|
import { evaluateExpr } from '../expression/evaluate.js'
|
|
4
5
|
import { parseSql } from '../parse/parse.js'
|
|
5
|
-
import {
|
|
6
|
-
import { queryPlan } from '../plan/plan.js'
|
|
6
|
+
import { planSql } from '../plan/plan.js'
|
|
7
7
|
import { executeHashAggregate, executeScalarAggregate } from './aggregates.js'
|
|
8
8
|
import { executeHashJoin, executeNestedLoopJoin, executePositionalJoin } from './join.js'
|
|
9
9
|
import { executeSort } from './sort.js'
|
|
10
|
-
import {
|
|
10
|
+
import { stableRowKey } from './utils.js'
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
* @import { AsyncCells, AsyncDataSource, AsyncRow, ExecuteSqlOptions, ExprNode, SelectStatement
|
|
14
|
-
* @import { DistinctNode,
|
|
13
|
+
* @import { AsyncCells, AsyncDataSource, AsyncRow, ExecuteContext, ExecuteSqlOptions, ExprNode, SelectStatement } from '../types.js'
|
|
14
|
+
* @import { DistinctNode, FilterNode, LimitNode, ProjectNode, QueryPlan, ScanNode } from '../plan/types.js'
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
/**
|
|
18
|
-
* Executes a SQL SELECT query against
|
|
18
|
+
* Executes a SQL SELECT query against tables
|
|
19
19
|
*
|
|
20
|
-
* @param {ExecuteSqlOptions} options
|
|
21
|
-
* @yields {AsyncRow}
|
|
20
|
+
* @param {ExecuteSqlOptions} options
|
|
21
|
+
* @yields {AsyncRow}
|
|
22
22
|
*/
|
|
23
23
|
export async function* executeSql({ tables, query, functions, signal }) {
|
|
24
24
|
const select = typeof query === 'string' ? parseSql({ query, functions }) : query
|
|
25
25
|
|
|
26
|
-
// Check for unsupported operations
|
|
27
|
-
if (!select.from) {
|
|
28
|
-
throw missingClauseError({
|
|
29
|
-
missing: 'FROM clause',
|
|
30
|
-
context: 'SELECT statement',
|
|
31
|
-
})
|
|
32
|
-
}
|
|
33
|
-
|
|
34
26
|
// Normalize tables: convert arrays to AsyncDataSource
|
|
35
27
|
/** @type {Record<string, AsyncDataSource>} */
|
|
36
28
|
const normalizedTables = {}
|
|
@@ -42,7 +34,7 @@ export async function* executeSql({ tables, query, functions, signal }) {
|
|
|
42
34
|
}
|
|
43
35
|
}
|
|
44
36
|
|
|
45
|
-
yield* executeSelect({ select, tables: normalizedTables, functions, signal })
|
|
37
|
+
yield* executeSelect({ select, context: { tables: normalizedTables, functions, signal } })
|
|
46
38
|
}
|
|
47
39
|
|
|
48
40
|
/**
|
|
@@ -50,24 +42,23 @@ export async function* executeSql({ tables, query, functions, signal }) {
|
|
|
50
42
|
*
|
|
51
43
|
* @param {Object} options
|
|
52
44
|
* @param {SelectStatement} options.select
|
|
53
|
-
* @param {
|
|
54
|
-
* @param {Record<string, UserDefinedFunction>} [options.functions]
|
|
55
|
-
* @param {AbortSignal} [options.signal]
|
|
45
|
+
* @param {ExecuteContext} options.context
|
|
56
46
|
* @yields {AsyncRow}
|
|
57
47
|
*/
|
|
58
|
-
export async function* executeSelect({ select,
|
|
59
|
-
const plan =
|
|
60
|
-
yield* executePlan(
|
|
48
|
+
export async function* executeSelect({ select, context }) {
|
|
49
|
+
const plan = planSql({ query: select, functions: context.functions })
|
|
50
|
+
yield* executePlan({ plan, context })
|
|
61
51
|
}
|
|
62
52
|
|
|
63
53
|
/**
|
|
64
54
|
* Executes a query plan and yields result rows
|
|
65
55
|
*
|
|
66
|
-
* @param {
|
|
67
|
-
* @param {
|
|
56
|
+
* @param {Object} options
|
|
57
|
+
* @param {QueryPlan} options.plan - the query plan to execute
|
|
58
|
+
* @param {ExecuteContext} options.context - execution context
|
|
68
59
|
* @returns {AsyncGenerator<AsyncRow>}
|
|
69
60
|
*/
|
|
70
|
-
export async function* executePlan(plan, context) {
|
|
61
|
+
export async function* executePlan({ plan, context }) {
|
|
71
62
|
if (plan.type === 'Scan') {
|
|
72
63
|
yield* executeScan(plan, context)
|
|
73
64
|
} else if (plan.type === 'Filter') {
|
|
@@ -147,7 +138,7 @@ async function* filterRows(rows, condition, context) {
|
|
|
147
138
|
for await (const row of rows) {
|
|
148
139
|
if (context.signal?.aborted) return
|
|
149
140
|
rowIndex++
|
|
150
|
-
const pass = await evaluateExpr({ node: condition, row, rowIndex,
|
|
141
|
+
const pass = await evaluateExpr({ node: condition, row, rowIndex, context })
|
|
151
142
|
if (pass) yield row
|
|
152
143
|
}
|
|
153
144
|
}
|
|
@@ -187,7 +178,7 @@ async function* limitRows(rows, limit, offset, signal) {
|
|
|
187
178
|
* @yields {AsyncRow}
|
|
188
179
|
*/
|
|
189
180
|
async function* executeFilter(plan, context) {
|
|
190
|
-
yield* filterRows(executePlan(plan.child, context), plan.condition, context)
|
|
181
|
+
yield* filterRows(executePlan({ plan: plan.child, context }), plan.condition, context)
|
|
191
182
|
}
|
|
192
183
|
|
|
193
184
|
/**
|
|
@@ -198,11 +189,10 @@ async function* executeFilter(plan, context) {
|
|
|
198
189
|
* @yields {AsyncRow}
|
|
199
190
|
*/
|
|
200
191
|
async function* executeProject(plan, context) {
|
|
201
|
-
const { tables, functions, signal } = context
|
|
202
192
|
let rowIndex = 0
|
|
203
193
|
|
|
204
|
-
for await (const row of executePlan(plan.child, context)) {
|
|
205
|
-
if (signal?.aborted) return
|
|
194
|
+
for await (const row of executePlan({ plan: plan.child, context })) {
|
|
195
|
+
if (context.signal?.aborted) return
|
|
206
196
|
rowIndex++
|
|
207
197
|
const currentRowIndex = rowIndex
|
|
208
198
|
|
|
@@ -218,15 +208,13 @@ async function* executeProject(plan, context) {
|
|
|
218
208
|
cells[key] = row.cells[key]
|
|
219
209
|
}
|
|
220
210
|
} else if (col.kind === 'derived') {
|
|
221
|
-
const alias = col.alias ??
|
|
211
|
+
const alias = col.alias ?? derivedAlias(col.expr)
|
|
222
212
|
columns.push(alias)
|
|
223
213
|
cells[alias] = () => evaluateExpr({
|
|
224
214
|
node: col.expr,
|
|
225
215
|
row,
|
|
226
|
-
tables,
|
|
227
|
-
functions,
|
|
228
216
|
rowIndex: currentRowIndex,
|
|
229
|
-
|
|
217
|
+
context,
|
|
230
218
|
})
|
|
231
219
|
}
|
|
232
220
|
}
|
|
@@ -248,7 +236,7 @@ async function* executeDistinct(plan, context) {
|
|
|
248
236
|
/** @type {Set<string>} */
|
|
249
237
|
const seen = new Set()
|
|
250
238
|
|
|
251
|
-
for await (const row of executePlan(plan.child, context)) {
|
|
239
|
+
for await (const row of executePlan({ plan: plan.child, context })) {
|
|
252
240
|
if (signal?.aborted) return
|
|
253
241
|
|
|
254
242
|
const key = await stableRowKey(row.cells)
|
|
@@ -267,5 +255,5 @@ async function* executeDistinct(plan, context) {
|
|
|
267
255
|
* @yields {AsyncRow}
|
|
268
256
|
*/
|
|
269
257
|
async function* executeLimit(plan, context) {
|
|
270
|
-
yield* limitRows(executePlan(plan.child, context), plan.limit, plan.offset, context.signal)
|
|
258
|
+
yield* limitRows(executePlan({ plan: plan.child, context }), plan.limit, plan.offset, context.signal)
|
|
271
259
|
}
|
package/src/execute/join.js
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { evaluateExpr } from '../expression/evaluate.js'
|
|
2
|
-
import { missingClauseError } from '../parseErrors.js'
|
|
3
2
|
import { stringify } from './utils.js'
|
|
4
3
|
import { executePlan } from './execute.js'
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* @import { AsyncCells, AsyncRow } from '../types.js'
|
|
8
|
-
* @import {
|
|
6
|
+
* @import { AsyncCells, AsyncRow, ExecuteContext } from '../types.js'
|
|
7
|
+
* @import { HashJoinNode, NestedLoopJoinNode, PositionalJoinNode } from '../plan/types.js'
|
|
9
8
|
*/
|
|
10
9
|
|
|
11
10
|
/**
|
|
@@ -16,22 +15,14 @@ import { executePlan } from './execute.js'
|
|
|
16
15
|
* @yields {AsyncRow}
|
|
17
16
|
*/
|
|
18
17
|
export async function* executeNestedLoopJoin(plan, context) {
|
|
19
|
-
const { tables, functions, signal } = context
|
|
20
18
|
const leftTable = plan.leftAlias
|
|
21
19
|
const rightTable = plan.rightAlias
|
|
22
20
|
|
|
23
|
-
if (!plan.condition) {
|
|
24
|
-
throw missingClauseError({
|
|
25
|
-
missing: 'ON condition',
|
|
26
|
-
context: 'JOIN',
|
|
27
|
-
})
|
|
28
|
-
}
|
|
29
|
-
|
|
30
21
|
// Buffer right rows
|
|
31
22
|
/** @type {AsyncRow[]} */
|
|
32
23
|
const rightRows = []
|
|
33
|
-
for await (const row of executePlan(plan.right, context)) {
|
|
34
|
-
if (signal?.aborted) return
|
|
24
|
+
for await (const row of executePlan({ plan: plan.right, context })) {
|
|
25
|
+
if (context.signal?.aborted) return
|
|
35
26
|
rightRows.push(row)
|
|
36
27
|
}
|
|
37
28
|
|
|
@@ -43,8 +34,8 @@ export async function* executeNestedLoopJoin(plan, context) {
|
|
|
43
34
|
/** @type {Set<AsyncRow> | null} */
|
|
44
35
|
const matchedRightRows = plan.joinType === 'RIGHT' || plan.joinType === 'FULL' ? new Set() : null
|
|
45
36
|
|
|
46
|
-
for await (const leftRow of executePlan(plan.left, context)) {
|
|
47
|
-
if (signal?.aborted) break
|
|
37
|
+
for await (const leftRow of executePlan({ plan: plan.left, context })) {
|
|
38
|
+
if (context.signal?.aborted) break
|
|
48
39
|
|
|
49
40
|
if (!leftPrefixedCols) {
|
|
50
41
|
leftPrefixedCols = prefixColumns(leftRow.columns, leftTable)
|
|
@@ -57,9 +48,7 @@ export async function* executeNestedLoopJoin(plan, context) {
|
|
|
57
48
|
const matches = await evaluateExpr({
|
|
58
49
|
node: plan.condition,
|
|
59
50
|
row: tempMerged,
|
|
60
|
-
|
|
61
|
-
functions,
|
|
62
|
-
signal,
|
|
51
|
+
context,
|
|
63
52
|
})
|
|
64
53
|
|
|
65
54
|
if (matches) {
|
|
@@ -101,14 +90,14 @@ export async function* executePositionalJoin(plan, context) {
|
|
|
101
90
|
// Buffer both sides (required for positional join)
|
|
102
91
|
/** @type {AsyncRow[]} */
|
|
103
92
|
const leftRows = []
|
|
104
|
-
for await (const row of executePlan(plan.left, context)) {
|
|
93
|
+
for await (const row of executePlan({ plan: plan.left, context })) {
|
|
105
94
|
if (signal?.aborted) return
|
|
106
95
|
leftRows.push(row)
|
|
107
96
|
}
|
|
108
97
|
|
|
109
98
|
/** @type {AsyncRow[]} */
|
|
110
99
|
const rightRows = []
|
|
111
|
-
for await (const row of executePlan(plan.right, context)) {
|
|
100
|
+
for await (const row of executePlan({ plan: plan.right, context })) {
|
|
112
101
|
if (signal?.aborted) return
|
|
113
102
|
rightRows.push(row)
|
|
114
103
|
}
|
|
@@ -135,15 +124,14 @@ export async function* executePositionalJoin(plan, context) {
|
|
|
135
124
|
* @yields {AsyncRow}
|
|
136
125
|
*/
|
|
137
126
|
export async function* executeHashJoin(plan, context) {
|
|
138
|
-
const { tables, functions, signal } = context
|
|
139
127
|
const leftTable = plan.leftAlias
|
|
140
128
|
const rightTable = plan.rightAlias
|
|
141
129
|
|
|
142
130
|
// Buffer right rows and build hash map
|
|
143
131
|
/** @type {AsyncRow[]} */
|
|
144
132
|
const rightRows = []
|
|
145
|
-
for await (const row of executePlan(plan.right, context)) {
|
|
146
|
-
if (signal?.aborted) return
|
|
133
|
+
for await (const row of executePlan({ plan: plan.right, context })) {
|
|
134
|
+
if (context.signal?.aborted) return
|
|
147
135
|
rightRows.push(row)
|
|
148
136
|
}
|
|
149
137
|
|
|
@@ -153,9 +141,7 @@ export async function* executeHashJoin(plan, context) {
|
|
|
153
141
|
const keyValue = await evaluateExpr({
|
|
154
142
|
node: plan.rightKey,
|
|
155
143
|
row: rightRow,
|
|
156
|
-
|
|
157
|
-
functions,
|
|
158
|
-
signal,
|
|
144
|
+
context,
|
|
159
145
|
})
|
|
160
146
|
if (keyValue == null) continue
|
|
161
147
|
const keyStr = stringify(keyValue)
|
|
@@ -177,8 +163,8 @@ export async function* executeHashJoin(plan, context) {
|
|
|
177
163
|
const matchedRightRows = plan.joinType === 'RIGHT' || plan.joinType === 'FULL' ? new Set() : null
|
|
178
164
|
|
|
179
165
|
// Probe phase: stream left rows
|
|
180
|
-
for await (const leftRow of executePlan(plan.left, context)) {
|
|
181
|
-
if (signal?.aborted) break
|
|
166
|
+
for await (const leftRow of executePlan({ plan: plan.left, context })) {
|
|
167
|
+
if (context.signal?.aborted) break
|
|
182
168
|
|
|
183
169
|
if (!leftPrefixedCols) {
|
|
184
170
|
leftPrefixedCols = prefixColumns(leftRow.columns, leftTable)
|
|
@@ -187,9 +173,7 @@ export async function* executeHashJoin(plan, context) {
|
|
|
187
173
|
const keyValue = await evaluateExpr({
|
|
188
174
|
node: plan.leftKey,
|
|
189
175
|
row: leftRow,
|
|
190
|
-
|
|
191
|
-
functions,
|
|
192
|
-
signal,
|
|
176
|
+
context,
|
|
193
177
|
})
|
|
194
178
|
const keyStr = stringify(keyValue)
|
|
195
179
|
const matchingRightRows = hashMap.get(keyStr)
|
package/src/execute/sort.js
CHANGED
|
@@ -3,8 +3,8 @@ import { executePlan } from './execute.js'
|
|
|
3
3
|
import { compareForTerm } from './utils.js'
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
-
* @import { AsyncRow, SqlPrimitive } from '../types.js'
|
|
7
|
-
* @import {
|
|
6
|
+
* @import { AsyncRow, ExecuteContext, SqlPrimitive } from '../types.js'
|
|
7
|
+
* @import { SortNode } from '../plan/types.js'
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
/**
|
|
@@ -15,13 +15,11 @@ import { compareForTerm } from './utils.js'
|
|
|
15
15
|
* @yields {AsyncRow}
|
|
16
16
|
*/
|
|
17
17
|
export async function* executeSort(plan, context) {
|
|
18
|
-
const { tables, functions, signal } = context
|
|
19
|
-
|
|
20
18
|
// Buffer all rows
|
|
21
19
|
/** @type {AsyncRow[]} */
|
|
22
20
|
const rows = []
|
|
23
|
-
for await (const row of executePlan(plan.child, context)) {
|
|
24
|
-
if (signal?.aborted) return
|
|
21
|
+
for await (const row of executePlan({ plan: plan.child, context })) {
|
|
22
|
+
if (context.signal?.aborted) return
|
|
25
23
|
rows.push(row)
|
|
26
24
|
}
|
|
27
25
|
|
|
@@ -51,10 +49,7 @@ export async function* executeSort(plan, context) {
|
|
|
51
49
|
evaluatedValues[idx][orderByIdx] = await evaluateExpr({
|
|
52
50
|
node: term.expr,
|
|
53
51
|
row: rows[idx],
|
|
54
|
-
|
|
55
|
-
functions,
|
|
56
|
-
aliases: plan.aliases,
|
|
57
|
-
signal,
|
|
52
|
+
context,
|
|
58
53
|
})
|
|
59
54
|
}
|
|
60
55
|
}
|
package/src/execute/utils.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { AsyncCells, AsyncRow,
|
|
2
|
+
* @import { AsyncCells, AsyncRow, OrderByItem, SqlPrimitive } from '../types.js'
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -57,45 +57,6 @@ export async function collect(asyncRows) {
|
|
|
57
57
|
return results
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
/**
|
|
61
|
-
* Generates a default alias for a derived column expression
|
|
62
|
-
*
|
|
63
|
-
* @param {ExprNode} expr - the expression node
|
|
64
|
-
* @returns {string} the generated alias
|
|
65
|
-
*/
|
|
66
|
-
export function defaultDerivedAlias(expr) {
|
|
67
|
-
if (expr.type === 'identifier') {
|
|
68
|
-
// For qualified names like 'users.name', use just the column part as alias
|
|
69
|
-
if (expr.name.includes('.')) {
|
|
70
|
-
return expr.name.split('.').pop()
|
|
71
|
-
}
|
|
72
|
-
return expr.name
|
|
73
|
-
}
|
|
74
|
-
if (expr.type === 'literal') {
|
|
75
|
-
return String(expr.value)
|
|
76
|
-
}
|
|
77
|
-
if (expr.type === 'cast') {
|
|
78
|
-
return defaultDerivedAlias(expr.expr) + '_as_' + expr.toType
|
|
79
|
-
}
|
|
80
|
-
if (expr.type === 'unary') {
|
|
81
|
-
return expr.op + '_' + defaultDerivedAlias(expr.argument)
|
|
82
|
-
}
|
|
83
|
-
if (expr.type === 'binary') {
|
|
84
|
-
return defaultDerivedAlias(expr.left) + '_' + expr.op + '_' + defaultDerivedAlias(expr.right)
|
|
85
|
-
}
|
|
86
|
-
if (expr.type === 'function') {
|
|
87
|
-
// Handle aggregate functions with star (COUNT(*) -> count_all)
|
|
88
|
-
if (expr.args.length === 1 && expr.args[0].type === 'identifier' && expr.args[0].name === '*') {
|
|
89
|
-
return expr.name.toLowerCase() + '_all'
|
|
90
|
-
}
|
|
91
|
-
return expr.name.toLowerCase() + '_' + expr.args.map(defaultDerivedAlias).join('_')
|
|
92
|
-
}
|
|
93
|
-
if (expr.type === 'interval') {
|
|
94
|
-
return `interval_${expr.value}_${expr.unit.toLowerCase()}`
|
|
95
|
-
}
|
|
96
|
-
return 'expr'
|
|
97
|
-
}
|
|
98
|
-
|
|
99
60
|
/**
|
|
100
61
|
* @param {SqlPrimitive} value
|
|
101
62
|
* @returns {string}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { ExprNode } from '../types.js'
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generates a default alias for a derived column expression
|
|
7
|
+
*
|
|
8
|
+
* @param {ExprNode} expr - the expression node
|
|
9
|
+
* @returns {string} the generated alias
|
|
10
|
+
*/
|
|
11
|
+
export function derivedAlias(expr) {
|
|
12
|
+
if (expr.type === 'identifier') {
|
|
13
|
+
// For qualified names like 'users.name', use just the column part as alias
|
|
14
|
+
if (expr.name.includes('.')) {
|
|
15
|
+
return expr.name.split('.').pop()
|
|
16
|
+
}
|
|
17
|
+
return expr.name
|
|
18
|
+
}
|
|
19
|
+
if (expr.type === 'literal') {
|
|
20
|
+
return String(expr.value)
|
|
21
|
+
}
|
|
22
|
+
if (expr.type === 'cast') {
|
|
23
|
+
return derivedAlias(expr.expr) + '_as_' + expr.toType
|
|
24
|
+
}
|
|
25
|
+
if (expr.type === 'unary') {
|
|
26
|
+
return expr.op + '_' + derivedAlias(expr.argument)
|
|
27
|
+
}
|
|
28
|
+
if (expr.type === 'binary') {
|
|
29
|
+
return derivedAlias(expr.left) + '_' + expr.op + '_' + derivedAlias(expr.right)
|
|
30
|
+
}
|
|
31
|
+
if (expr.type === 'function') {
|
|
32
|
+
// Handle aggregate functions with star (COUNT(*) -> count_all)
|
|
33
|
+
if (expr.args.length === 1 && expr.args[0].type === 'identifier' && expr.args[0].name === '*') {
|
|
34
|
+
return expr.name.toLowerCase() + '_all'
|
|
35
|
+
}
|
|
36
|
+
return expr.name.toLowerCase() + '_' + expr.args.map(derivedAlias).join('_')
|
|
37
|
+
}
|
|
38
|
+
if (expr.type === 'interval') {
|
|
39
|
+
return `interval_${expr.value}_${expr.unit.toLowerCase()}`
|
|
40
|
+
}
|
|
41
|
+
return 'expr'
|
|
42
|
+
}
|