squirreling 0.16.3 → 0.16.4

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 CHANGED
@@ -95,10 +95,10 @@ Because Squirreling uses lazy cell evaluation, the `AI_SCORE` function only exec
95
95
 
96
96
  ### Custom Data Sources
97
97
 
98
- Squirreling can work with any data source that implements the `AsyncDataSource` interface.
98
+ Squirreling can work with any data source that implements the `AsyncDataSource` interface. The simplest form is a `ScannableDataSource`: column names plus a row scan.
99
99
 
100
100
  ```typescript
101
- interface AsyncDataSource {
101
+ interface ScannableDataSource {
102
102
  numRows?: number
103
103
  columns: string[]
104
104
  scan(options: ScanOptions): ScanResults
@@ -144,7 +144,7 @@ Squirreling mostly follows the SQL standard. The following features are supporte
144
144
  - `WITH` clause for Common Table Expressions (CTEs)
145
145
  - Subqueries in `SELECT`, `FROM`, `WHERE`, and correlated subqueries
146
146
  - `JOIN` operations: `INNER JOIN`, `LEFT JOIN`, `RIGHT JOIN`, `FULL JOIN`, `CROSS JOIN`, `POSITIONAL JOIN`, `LATERAL VIEW [OUTER] EXPLODE(...)`, with `ON` or `USING (col, ...)` conditions
147
- - `GROUP BY` and `HAVING` clauses
147
+ - `GROUP BY` and `HAVING` clauses, including `GROUP BY ALL`
148
148
  - Set operations: `UNION`, `UNION ALL`, `INTERSECT`, `INTERSECT ALL`, `EXCEPT`, `EXCEPT ALL`
149
149
  - Expressions: `CASE`, `CAST`, `TRY_CAST`, `BETWEEN`, `IN`, `LIKE`, `IS NULL`, `IS NOT NULL`, string concatenation `||`
150
150
  - Subscript access: zero-based array indexing `col[0]`, struct field access `col['field']`, and chains like `col[0].field`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirreling",
3
- "version": "0.16.3",
3
+ "version": "0.16.4",
4
4
  "description": "Squirreling Async SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -39,11 +39,13 @@
39
39
  "test": "vitest run"
40
40
  },
41
41
  "devDependencies": {
42
- "@types/node": "26.4.0",
43
- "@vitest/coverage-v8": "4.1.11",
44
- "eslint": "9.39.4",
45
- "eslint-plugin-jsdoc": "64.2.1",
42
+ "@eslint/js": "10.0.1",
43
+ "@types/node": "26.4.1",
44
+ "@vitest/coverage-v8": "5.0.0",
45
+ "eslint": "10.10.0",
46
+ "eslint-plugin-jsdoc": "64.3.6",
47
+ "globals": "17.12.0",
46
48
  "typescript": "7.0.2",
47
- "vitest": "4.1.11"
49
+ "vitest": "5.0.0"
48
50
  }
49
51
  }
@@ -588,7 +588,9 @@ function executeCount(plan, context) {
588
588
  throw new Error(`Data source "${plan.table}" does not implement scan()`)
589
589
  }
590
590
  let count = 0
591
- const { rows } = table.scan({ signal })
591
+ // Counting needs row presence, not payload columns. Sources may still
592
+ // read columns required for their own visibility or delete filters.
593
+ const { rows } = table.scan({ columns: [], signal })
592
594
  // eslint-disable-next-line no-unused-vars
593
595
  for await (const _ of rows()) {
594
596
  signal?.throwIfAborted()
package/src/index.d.ts CHANGED
@@ -1,8 +1,10 @@
1
- import type { AsyncBatch, AsyncDataSource, AsyncRow, ColumnResult, ColumnVector, ExecuteContext, ExecuteSqlOptions, ExprNode, ParseSqlOptions, PlanSqlOptions, QueryPlan, QueryResults, ReadBatchColumnOptions, RowsToBatchesOptions, RowSelection, SqlPrimitive, Statement, Token } from './types.js'
1
+ import type { AsyncBatch, AsyncDataSource, AsyncRow, PreparableDataSource, ScannableDataSource, ColumnResult, ColumnVector, ExecuteContext, ExecuteSqlOptions, ExprNode, ParseSqlOptions, PlanSqlOptions, QueryPlan, QueryResults, ReadBatchColumnOptions, RowsToBatchesOptions, RowSelection, SqlPrimitive, Statement, Token } from './types.js'
2
2
  export type {
3
3
  AsyncBatch,
4
4
  AsyncCells,
5
5
  AsyncDataSource,
6
+ PreparableDataSource,
7
+ ScannableDataSource,
6
8
  AsyncRow,
7
9
  BatchColumn,
8
10
  ColumnDemand,
@@ -274,11 +274,16 @@ function parseSelect(state) {
274
274
 
275
275
  if (match(state, 'keyword', 'GROUP')) {
276
276
  expect(state, 'keyword', 'BY')
277
- while (true) {
278
- const expr = resolvePositionalRef(parseExpression(state), columns, 'GROUP BY')
279
- expectNoAggregate(expr, 'GROUP BY')
280
- groupBy.push(expr)
281
- if (!match(state, 'comma')) break
277
+ if (current(state).type === 'keyword' && current(state).value === 'ALL') {
278
+ const tok = consume(state)
279
+ groupBy.push(...expandGroupByAll(columns, tok.positionStart, tok.positionEnd))
280
+ } else {
281
+ while (true) {
282
+ const expr = resolvePositionalRef(parseExpression(state), columns, 'GROUP BY')
283
+ expectNoAggregate(expr, 'GROUP BY')
284
+ groupBy.push(expr)
285
+ if (!match(state, 'comma')) break
286
+ }
282
287
  }
283
288
  }
284
289
 
@@ -371,6 +376,30 @@ function parseSelect(state) {
371
376
  }
372
377
  }
373
378
 
379
+ /**
380
+ * Expand GROUP BY ALL into every SELECT expression that contains no aggregate.
381
+ *
382
+ * @param {SelectColumn[]} columns
383
+ * @param {number} positionStart
384
+ * @param {number} positionEnd
385
+ * @returns {ExprNode[]}
386
+ */
387
+ function expandGroupByAll(columns, positionStart, positionEnd) {
388
+ /** @type {ExprNode[]} */
389
+ const groupBy = []
390
+ for (const col of columns) {
391
+ if (col.type === 'star') {
392
+ throw new ParseError({
393
+ message: `GROUP BY ALL does not support * at position ${positionStart}`,
394
+ positionStart,
395
+ positionEnd,
396
+ })
397
+ }
398
+ if (!findAggregate(col.expr)) groupBy.push(col.expr)
399
+ }
400
+ return groupBy
401
+ }
402
+
374
403
  /**
375
404
  * Resolves a positive integer literal in GROUP BY or ORDER BY as a positional
376
405
  * reference to the Nth SELECT column. Non-integer and non-literal expressions
package/src/types.d.ts CHANGED
@@ -209,30 +209,33 @@ export type AsyncCell = () => Promise<SqlPrimitive>
209
209
 
210
210
  export type Row = Record<string, SqlPrimitive>[]
211
211
 
212
- /**
213
- * Async data source for streaming SQL execution. A source must implement
214
- * either scan() or prepareScan().
215
- */
216
212
  interface AsyncDataSourceBase {
217
213
  numRows?: number
218
214
  // Optional method for fast column scans
219
215
  scanColumn?(options: ScanColumnOptions): AsyncIterable<ArrayLike<SqlPrimitive>> | ScanColumnResults
220
216
  }
221
217
 
222
- export type AsyncDataSource = AsyncDataSourceBase & (
223
- | {
224
- columns: string[]
225
- scan(options: ScanOptions): ScanResults
226
- schema?: RelationSchema
227
- prepareScan?: PrepareScan
228
- }
229
- | {
230
- columns?: string[]
231
- scan?(options: ScanOptions): ScanResults
232
- schema: RelationSchema
233
- prepareScan: PrepareScan
234
- }
235
- )
218
+ /** A data source that always provides column names and a row scan. */
219
+ export interface ScannableDataSource extends AsyncDataSourceBase {
220
+ columns: string[]
221
+ scan(options: ScanOptions): ScanResults
222
+ schema?: RelationSchema
223
+ prepareScan?: PrepareScan
224
+ }
225
+
226
+ /** A data source that always provides a schema and a prepared batch scan. */
227
+ export interface PreparableDataSource extends AsyncDataSourceBase {
228
+ columns?: string[]
229
+ scan?(options: ScanOptions): ScanResults
230
+ schema: RelationSchema
231
+ prepareScan: PrepareScan
232
+ }
233
+
234
+ /**
235
+ * Async data source for streaming SQL execution. A source must implement
236
+ * either scan() or prepareScan().
237
+ */
238
+ export type AsyncDataSource = ScannableDataSource | PreparableDataSource
236
239
 
237
240
  /**
238
241
  * Result of a scan: streaming rows and flags indicating which hints were