squirreling 0.12.26 → 0.13.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/package.json +2 -2
- package/src/execute/accumulator.js +89 -0
- package/src/execute/aggregates.js +27 -62
- package/src/execute/execute.js +35 -28
- package/src/execute/join.js +13 -13
- package/src/execute/sort.js +8 -3
- package/src/execute/streamingAggregate.js +598 -0
- package/src/execute/window.js +8 -8
- package/src/expression/evaluate.js +1 -1
- package/src/index.d.ts +1 -1
- package/src/plan/columns.js +1 -1
- package/src/plan/plan.js +39 -1
package/src/plan/plan.js
CHANGED
|
@@ -4,7 +4,7 @@ import { findAggregate } from '../validation/aggregates.js'
|
|
|
4
4
|
import { ParseError } from '../validation/parseErrors.js'
|
|
5
5
|
import { ColumnNotFoundError, TableNotFoundError } from '../validation/tables.js'
|
|
6
6
|
import { validateNoIdentifiers, validateScan, validateTableRefs } from '../validation/tables.js'
|
|
7
|
-
import { collectScopeColumns, extractColumns, fromAlias, inferSelectSourceColumns, inferStatementColumns, statementScope, tableFunctionColumnNames } from './columns.js'
|
|
7
|
+
import { collectColumnsFromExpr, collectScopeColumns, extractColumns, fromAlias, inferSelectSourceColumns, inferStatementColumns, statementScope, tableFunctionColumnNames } from './columns.js'
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* @import { AsyncDataSource, DerivedColumn, ExprNode, FromFunction, IdentifierNode, JoinClause, OrderByItem, PlanSqlOptions, ScanOptions, SelectColumn, SelectStatement, SetOperationStatement, Statement, WindowFunctionNode } from '../types.js'
|
|
@@ -208,6 +208,10 @@ function planSelect({ select, ctePlans, cteColumns, tables, parentColumns, outer
|
|
|
208
208
|
const hints = {}
|
|
209
209
|
const perTableColumns = extractColumns({ select: originalSelect, parentColumns })
|
|
210
210
|
if (sourceAlias !== undefined) hints.columns = perTableColumns.get(sourceAlias)
|
|
211
|
+
// Capture what the parent reads from a FROM subquery before the reset
|
|
212
|
+
// below, so aggregate outputs it never reads can still be pruned when the
|
|
213
|
+
// parent reads nothing at all (an empty array here means exactly that).
|
|
214
|
+
const subqueryNeeds = select.from?.type === 'subquery' ? hints.columns : undefined
|
|
211
215
|
// Empty columns array means no columns were referenced, but a FROM subquery
|
|
212
216
|
// still needs its own columns (e.g. for DISTINCT). Treat empty as unrestricted.
|
|
213
217
|
if (hints.columns?.length === 0 && select.from?.type === 'subquery') {
|
|
@@ -224,6 +228,7 @@ function planSelect({ select, ctePlans, cteColumns, tables, parentColumns, outer
|
|
|
224
228
|
// Start with the data source (FROM clause)
|
|
225
229
|
/** @type {QueryPlan} */
|
|
226
230
|
let plan = planFrom({ select, ctePlans, cteColumns, hints, tables, outerScope })
|
|
231
|
+
pruneAggregateColumns(plan, subqueryNeeds)
|
|
227
232
|
|
|
228
233
|
// Add JOINs
|
|
229
234
|
if (select.joins.length) {
|
|
@@ -349,6 +354,38 @@ function pushLimitIntoSort(plan, limit, offset) {
|
|
|
349
354
|
}
|
|
350
355
|
}
|
|
351
356
|
|
|
357
|
+
/**
|
|
358
|
+
* Drops derived columns from a subquery's aggregate node when the parent
|
|
359
|
+
* query never reads them. The buffered path defers aggregate cells so unread
|
|
360
|
+
* outputs are never evaluated, but the streaming path accumulates every
|
|
361
|
+
* planned aggregate eagerly, so unread aggregates must be removed at plan
|
|
362
|
+
* time to preserve that lazy behavior. Columns referenced by HAVING or the
|
|
363
|
+
* aggregate's ORDER BY are kept: both evaluate against the group context
|
|
364
|
+
* row, which exposes output aliases. Descends only through Subquery and
|
|
365
|
+
* Limit wrappers; Sort and Distinct depend on the full column set, so
|
|
366
|
+
* anything below them is left untouched.
|
|
367
|
+
*
|
|
368
|
+
* @param {QueryPlan} plan
|
|
369
|
+
* @param {string[] | undefined} needed - output column names the parent reads
|
|
370
|
+
*/
|
|
371
|
+
function pruneAggregateColumns(plan, needed) {
|
|
372
|
+
if (!needed) return
|
|
373
|
+
let node = plan
|
|
374
|
+
while (node.type === 'Subquery' || node.type === 'Limit') node = node.child
|
|
375
|
+
if (node.type !== 'HashAggregate' && node.type !== 'ScalarAggregate') return
|
|
376
|
+
/** @type {IdentifierNode[]} */
|
|
377
|
+
const identifiers = []
|
|
378
|
+
collectColumnsFromExpr(node.having, identifiers)
|
|
379
|
+
if (node.type === 'HashAggregate' && node.orderBy) {
|
|
380
|
+
for (const term of node.orderBy) collectColumnsFromExpr(term.expr, identifiers)
|
|
381
|
+
}
|
|
382
|
+
const keep = new Set(needed)
|
|
383
|
+
for (const { name } of identifiers) keep.add(name)
|
|
384
|
+
node.columns = node.columns.filter(col =>
|
|
385
|
+
col.type === 'star' || keep.has(col.alias ?? derivedAlias(col.expr))
|
|
386
|
+
)
|
|
387
|
+
}
|
|
388
|
+
|
|
352
389
|
/**
|
|
353
390
|
* @param {object} options
|
|
354
391
|
* @param {SelectStatement} options.select
|
|
@@ -487,6 +524,7 @@ function planJoin({ left, joins, leftTable, ctePlans, cteColumns, perTableColumn
|
|
|
487
524
|
outerScope,
|
|
488
525
|
parentColumns: subColumns?.map(name => ({ type: 'identifier', name, positionStart: 0, positionEnd: 0 })),
|
|
489
526
|
})
|
|
527
|
+
pruneAggregateColumns(subPlan, perTableColumns.get(rightTable))
|
|
490
528
|
const availableColumns = inferStatementColumns({ stmt: join.subquery.query, cteColumns, tables })
|
|
491
529
|
if (subColumns && availableColumns.length) {
|
|
492
530
|
const missingColumn = subColumns.find(col => !availableColumns.includes(col))
|