squirreling 0.16.7 → 0.16.8

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.16.7",
3
+ "version": "0.16.8",
4
4
  "description": "Squirreling Async SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -40,10 +40,10 @@
40
40
  },
41
41
  "devDependencies": {
42
42
  "@eslint/js": "10.0.1",
43
- "@types/node": "26.6.2",
43
+ "@types/node": "26.6.3",
44
44
  "@vitest/coverage-v8": "5.0.2",
45
45
  "eslint": "10.11.0",
46
- "eslint-plugin-jsdoc": "64.5.4",
46
+ "eslint-plugin-jsdoc": "65.0.0",
47
47
  "globals": "17.12.0",
48
48
  "typescript": "7.0.2",
49
49
  "vitest": "5.0.2"
@@ -125,6 +125,7 @@ export function selectVector(vector, selection) {
125
125
  throw new Error(`Cannot select ${selection.length} rows from vector length ${vector.length}`)
126
126
  }
127
127
  if (selection.type === 'all') return vector
128
+ if (vector.type === 'constant') return { ...vector, length: selectedRowCount(selection) }
128
129
  if (vector.type === 'selected') {
129
130
  const composed = composeSelections(vector.selection, selection)
130
131
  return {
@@ -309,6 +309,9 @@ function tryColumnScanAggregate(plan, { tables, signal }) {
309
309
  specs.push(spec)
310
310
  }
311
311
 
312
+ // Prepared batches can count selected rows without decoding a physical column.
313
+ if (table.prepareScan && specs.every(spec => spec.funcName === 'COUNT' && spec.star)) return
314
+
312
315
  const physicalColumns = new Set(specs.map(spec => spec.column))
313
316
  const hasPushdown = where || limit !== undefined || offset !== undefined
314
317
  if (hasPushdown) {
@@ -238,12 +238,16 @@ function projectedBatchColumn(batch, columnIndex) {
238
238
  /**
239
239
  * @param {ColumnVector} predicate
240
240
  * @param {number} rowCount
241
- * @returns {{ selection: Extract<RowSelection, { type: 'indices' }>, selectedCount: number }}
241
+ * @returns {{ selection: RowSelection, selectedCount: number }}
242
242
  */
243
243
  function predicateSelection(predicate, rowCount) {
244
244
  if (predicate.length !== rowCount) {
245
245
  throw new Error(`Predicate returned ${predicate.length} rows, expected ${rowCount}`)
246
246
  }
247
+ if (predicate.type === 'constant') {
248
+ const selectedCount = predicate.value ? rowCount : 0
249
+ return { selection: { type: 'range', start: 0, end: selectedCount, length: rowCount }, selectedCount }
250
+ }
247
251
  const indices = new Uint32Array(rowCount)
248
252
  let selectedCount = 0
249
253
  for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
@@ -523,6 +523,25 @@ async function accumulateBatch({ batch, inputs, specs, groups, context, rowOffse
523
523
  evaluateBatchInputs(inputs.args, batch, context, rowOffset),
524
524
  ])
525
525
  const rowCount = selectedRowCount(batch.selection)
526
+ context.signal?.throwIfAborted()
527
+ if (rowCount > 0 && keys.every(vector => vector?.type === 'constant') &&
528
+ specs.every(spec => spec.star && spec.funcName === 'COUNT' && !spec.node.distinct && !spec.node.filter)) {
529
+ const keyValues = keys.map(vector => valueAt(/** @type {ColumnVector} */ vector, 0))
530
+ const key = keyValues.length === 0
531
+ ? true
532
+ : keyValues.length === 1 ? keyify(keyValues[0]) : keyify(...keyValues)
533
+ let group = groups.get(key)
534
+ if (!group) {
535
+ group = {
536
+ firstRow: undefined,
537
+ keyValues,
538
+ accumulators: specs.map(spec => newAccumulator(spec.funcName, spec.node.distinct)),
539
+ }
540
+ groups.set(key, group)
541
+ }
542
+ for (const accumulator of group.accumulators) accumulator.count += rowCount
543
+ return
544
+ }
526
545
  for (let rowIndex = 0; rowIndex < rowCount; rowIndex++) {
527
546
  if (rowIndex > 0 && rowIndex % CHUNK_SIZE === 0) {
528
547
  await yieldToEventLoop()
@@ -626,6 +626,13 @@ function resolveIdentifier(identifier, columns) {
626
626
  */
627
627
  function evaluateKernel(kernel, vectors, selection, signal, rowOffset = 0, rowOrdinals) {
628
628
  const length = selectedRowCount(selection)
629
+ // Supported kernels are deterministic; streamRowIndex only labels errors.
630
+ // Empty selections must not evaluate expressions (including invalid casts).
631
+ if (length > 0 && vectors.every(vector => vector.type === 'constant')) {
632
+ signal?.throwIfAborted()
633
+ const ordinal = rowOrdinals ? Number(valueAt(rowOrdinals, 0)) : 0
634
+ return { type: 'constant', value: kernel(vectors, 0, rowOffset + ordinal), length }
635
+ }
629
636
  if (signal && length > YIELD_INTERVAL) {
630
637
  return evaluateKernelAsync(kernel, vectors, length, signal, rowOffset, rowOrdinals)
631
638
  }