squirreling 0.12.16 → 0.12.18

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirreling",
3
- "version": "0.12.16",
3
+ "version": "0.12.18",
4
4
  "description": "Squirreling Async SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -381,7 +381,11 @@ function computeScanRows(tableNumRows, limit, offset) {
381
381
  */
382
382
  async function* filterRows(rows, condition, context, limit) {
383
383
  const MAX_CHUNK = 256
384
- let chunkSize = limit ?? Infinity
384
+ // Without a LIMIT hint, evaluate row-by-row to preserve streaming.
385
+ // With a LIMIT hint, batch in growing chunks to parallelize async cell
386
+ // evaluation across rows that may be discarded anyway.
387
+ let chunkSize = limit ?? 1
388
+ const grow = limit !== undefined
385
389
  let rowIndex = 0
386
390
 
387
391
  /** @type {{ row: AsyncRow, rowIndex: number }[]} */
@@ -400,7 +404,7 @@ async function* filterRows(rows, condition, context, limit) {
400
404
  if (results[i]) yield buffer[i].row
401
405
  }
402
406
  buffer = []
403
- chunkSize = Math.min(chunkSize * 2, MAX_CHUNK)
407
+ if (grow) chunkSize = Math.min(chunkSize * 2, MAX_CHUNK)
404
408
  }
405
409
  }
406
410
 
package/src/index.d.ts CHANGED
@@ -53,6 +53,18 @@ export function executePlan(options: { plan: QueryPlan, context: ExecuteContext
53
53
  */
54
54
  export function parseSql(options: ParseSqlOptions): Statement
55
55
 
56
+ /**
57
+ * Collects every external table referenced from FROM and JOIN clauses in a
58
+ * parsed statement, descending into subqueries (IN, EXISTS, derived tables,
59
+ * scalar subqueries) and the branches of compound queries. CTE names defined
60
+ * by an enclosing WITH are skipped. Returned in first-seen order with
61
+ * duplicates removed.
62
+ *
63
+ * @param statement - parsed SQL statement (output of `parseSql`)
64
+ * @returns table names referenced in the query, excluding CTE aliases
65
+ */
66
+ export function extractTables(statement: Statement): string[]
67
+
56
68
  /**
57
69
  * Builds a query plan from a SQL query string or AST
58
70
  *
package/src/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { executePlan, executeSql } from './execute/execute.js'
2
+ export { extractTables } from './parse/extractTables.js'
2
3
  export { parseSql } from './parse/parse.js'
3
4
  export { planSql } from './plan/plan.js'
4
5
  export { tokenizeSql } from './parse/tokenize.js'
@@ -0,0 +1,125 @@
1
+ /**
2
+ * @import { ExprNode, Statement } from '../types.js'
3
+ */
4
+
5
+ /**
6
+ * Collect every external table referenced from FROM and JOIN clauses in a
7
+ * parsed statement, including those inside subqueries (IN, EXISTS, derived
8
+ * tables, scalar subqueries) and the branches of compound (UNION /
9
+ * INTERSECT / EXCEPT) queries. CTE names defined by an enclosing WITH are
10
+ * skipped, including across sibling CTEs and nested WITHs — the result is
11
+ * the set of names a caller would need to provide as `tables` to
12
+ * `executeSql`.
13
+ *
14
+ * Returned in first-seen order with duplicates removed. Names are returned
15
+ * in the original case they were written in the query.
16
+ *
17
+ * @param {Statement} statement
18
+ * @returns {string[]}
19
+ */
20
+ export function extractTables(statement) {
21
+ /** @type {Set<string>} */
22
+ const refs = new Set()
23
+ walkStatement(statement, new Set(), refs)
24
+ return [...refs]
25
+ }
26
+
27
+ /**
28
+ * @param {Statement} stmt
29
+ * @param {Set<string>} cteScope - lowercased CTE names visible at this point
30
+ * @param {Set<string>} refs
31
+ * @returns {void}
32
+ */
33
+ function walkStatement(stmt, cteScope, refs) {
34
+ if (stmt.type === 'with') {
35
+ const scope = new Set(cteScope)
36
+ for (const cte of stmt.ctes) {
37
+ walkStatement(cte.query, scope, refs)
38
+ scope.add(cte.name.toLowerCase())
39
+ }
40
+ walkStatement(stmt.query, scope, refs)
41
+ return
42
+ }
43
+ if (stmt.type === 'compound') {
44
+ walkStatement(stmt.left, cteScope, refs)
45
+ walkStatement(stmt.right, cteScope, refs)
46
+ for (const o of stmt.orderBy) walkExpr(o.expr, cteScope, refs)
47
+ return
48
+ }
49
+ // select
50
+ if (stmt.from.type === 'table') {
51
+ if (!cteScope.has(stmt.from.table.toLowerCase())) refs.add(stmt.from.table)
52
+ } else if (stmt.from.type === 'subquery') {
53
+ walkStatement(stmt.from.query, cteScope, refs)
54
+ } else {
55
+ for (const a of stmt.from.args) walkExpr(a, cteScope, refs)
56
+ }
57
+ for (const j of stmt.joins) {
58
+ if (j.fromFunction) {
59
+ for (const a of j.fromFunction.args) walkExpr(a, cteScope, refs)
60
+ } else if (!cteScope.has(j.table.toLowerCase())) {
61
+ refs.add(j.table)
62
+ }
63
+ if (j.on) walkExpr(j.on, cteScope, refs)
64
+ }
65
+ for (const c of stmt.columns) {
66
+ if (c.type === 'derived') walkExpr(c.expr, cteScope, refs)
67
+ }
68
+ if (stmt.where) walkExpr(stmt.where, cteScope, refs)
69
+ for (const g of stmt.groupBy) walkExpr(g, cteScope, refs)
70
+ if (stmt.having) walkExpr(stmt.having, cteScope, refs)
71
+ for (const o of stmt.orderBy) walkExpr(o.expr, cteScope, refs)
72
+ }
73
+
74
+ /**
75
+ * @param {ExprNode} expr
76
+ * @param {Set<string>} cteScope
77
+ * @param {Set<string>} refs
78
+ * @returns {void}
79
+ */
80
+ function walkExpr(expr, cteScope, refs) {
81
+ switch (expr.type) {
82
+ case 'unary':
83
+ walkExpr(expr.argument, cteScope, refs)
84
+ return
85
+ case 'binary':
86
+ walkExpr(expr.left, cteScope, refs)
87
+ walkExpr(expr.right, cteScope, refs)
88
+ return
89
+ case 'function':
90
+ for (const a of expr.args) walkExpr(a, cteScope, refs)
91
+ if (expr.filter) walkExpr(expr.filter, cteScope, refs)
92
+ return
93
+ case 'window':
94
+ for (const a of expr.args) walkExpr(a, cteScope, refs)
95
+ for (const p of expr.partitionBy) walkExpr(p, cteScope, refs)
96
+ for (const o of expr.orderBy) walkExpr(o.expr, cteScope, refs)
97
+ return
98
+ case 'cast':
99
+ walkExpr(expr.expr, cteScope, refs)
100
+ return
101
+ case 'in':
102
+ walkExpr(expr.expr, cteScope, refs)
103
+ walkStatement(expr.subquery, cteScope, refs)
104
+ return
105
+ case 'in valuelist':
106
+ walkExpr(expr.expr, cteScope, refs)
107
+ for (const v of expr.values) walkExpr(v, cteScope, refs)
108
+ return
109
+ case 'exists':
110
+ case 'not exists':
111
+ walkStatement(expr.subquery, cteScope, refs)
112
+ return
113
+ case 'case':
114
+ if (expr.caseExpr) walkExpr(expr.caseExpr, cteScope, refs)
115
+ for (const w of expr.whenClauses) {
116
+ walkExpr(w.condition, cteScope, refs)
117
+ walkExpr(w.result, cteScope, refs)
118
+ }
119
+ if (expr.elseResult) walkExpr(expr.elseResult, cteScope, refs)
120
+ return
121
+ case 'subquery':
122
+ walkStatement(expr.subquery, cteScope, refs)
123
+ }
124
+ // 'literal' / 'identifier' / 'interval' / 'star' are leaves with no children.
125
+ }