Skip to content

feat: add stringAgg aggregate function#1382

Open
KyleAMathews wants to merge 3 commits intomainfrom
string-agg
Open

feat: add stringAgg aggregate function#1382
KyleAMathews wants to merge 3 commits intomainfrom
string-agg

Conversation

@KyleAMathews
Copy link
Collaborator

Summary

Adds a stringAgg aggregate function that concatenates string values within groups, with configurable separators and ordering. Available across the full stack: query builder, compiler, and IVM engine.

Approach

Two-tier IVM architecture:

  • Stateful incremental path (used by the query compiler): Maintains a sorted array of entries per group with O(log n) binary search for insertion/removal. Fast-path string slicing for head/tail changes avoids full rebuilds — only middle-position mutations trigger a rebuild from the ordered entries.
  • Stateless fallback path (no rowKeyExtractor): Simple sort-and-join on each update. Used when row identity isn't available.

Query builder overloads disambiguate the flexible API surface:

stringAgg(value)                        // default order, no separator
stringAgg(value, orderBy)               // ordered, no separator  
stringAgg(value, separator)             // separator, default order
stringAgg(value, separator, orderBy)    // both

The compiler distinguishes separator (literal string) from orderBy (column reference) by expression type, and always provides a rowKeyExtractor to enable the incremental path.

Key invariants

  • entriesByKey and orderedEntries must stay synchronized — removeStringAggEntry now throws on desynchronization rather than silently returning
  • Fast-path text splicing is only used for first/last position changes; middle mutations set textDirty for a full rebuild
  • Null/undefined values are excluded from concatenation (consistent with SQL string_agg semantics)

Non-goals

  • Custom comparators for ordering (uses built-in comparison with Date normalization)
  • Streaming/lazy concatenation for very large groups

Verification

pnpm vitest run packages/db-ivm/tests/operators/groupBy.test.ts packages/db/tests/query/builder/functions.test.ts packages/db/tests/query/group-by.test.ts packages/db/tests/query/group-by.test-d.ts

All 140 tests pass, including new coverage for:

  • Incremental inserts/removes/updates with ordering
  • Group deletion + re-creation (cleanup path)
  • Fallback path (no rowKeyExtractor)
  • Values containing the separator string
  • Builder overload disambiguation
  • Type-level tests for return types

Files changed

File Change
packages/db-ivm/src/operators/groupBy.ts Core stringAgg implementation with binary search, incremental text maintenance, and fallback path
packages/db-ivm/src/operators/reduce.ts Pass group key to reduction function
packages/db/src/query/builder/functions.ts stringAgg builder with 4 overloads and OrderByLike type
packages/db/src/query/compiler/group-by.ts Compiler case mapping builder IR to IVM stringAgg
packages/db/src/query/index.ts Re-export stringAgg
docs/guides/live-queries.md Usage examples and API reference
Test files (4) Comprehensive coverage across IVM, builder, compiler, and type levels

🤖 Generated with Claude Code

KyleAMathews and others added 3 commits March 17, 2026 17:05
Adds a new stringAgg aggregate function that concatenates string values
within groups with configurable separator and ordering. Uses a two-tier
architecture: a stateful incremental path with O(log n) binary search
for efficient delta updates, and a stateless fallback for simpler use
cases. Includes query builder overloads, compiler integration, docs,
and comprehensive test coverage.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@pkg-pr-new
Copy link

pkg-pr-new bot commented Mar 17, 2026

More templates

@tanstack/angular-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/angular-db@1382

@tanstack/db

npm i https://pkg.pr.new/TanStack/db/@tanstack/db@1382

@tanstack/db-browser-wa-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-browser-wa-sqlite-persisted-collection@1382

@tanstack/db-ivm

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-ivm@1382

@tanstack/db-react-native-sqlite-persisted-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-react-native-sqlite-persisted-collection@1382

@tanstack/db-sqlite-persisted-collection-core

npm i https://pkg.pr.new/TanStack/db/@tanstack/db-sqlite-persisted-collection-core@1382

@tanstack/electric-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/electric-db-collection@1382

@tanstack/offline-transactions

npm i https://pkg.pr.new/TanStack/db/@tanstack/offline-transactions@1382

@tanstack/powersync-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/powersync-db-collection@1382

@tanstack/query-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/query-db-collection@1382

@tanstack/react-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/react-db@1382

@tanstack/rxdb-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/rxdb-db-collection@1382

@tanstack/solid-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/solid-db@1382

@tanstack/svelte-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/svelte-db@1382

@tanstack/trailbase-db-collection

npm i https://pkg.pr.new/TanStack/db/@tanstack/trailbase-db-collection@1382

@tanstack/vue-db

npm i https://pkg.pr.new/TanStack/db/@tanstack/vue-db@1382

commit: 4888335

@github-actions
Copy link
Contributor

Size Change: +272 B (+0.25%)

Total Size: 110 kB

Filename Size Change
./packages/db/dist/esm/index.js 2.88 kB +20 B (+0.7%)
./packages/db/dist/esm/query/builder/functions.js 872 B +80 B (+10.1%) ⚠️
./packages/db/dist/esm/query/compiler/group-by.js 2.86 kB +172 B (+6.39%) 🔍
ℹ️ View Unchanged
Filename Size
./packages/db/dist/esm/collection/change-events.js 1.39 kB
./packages/db/dist/esm/collection/changes.js 1.38 kB
./packages/db/dist/esm/collection/cleanup-queue.js 810 B
./packages/db/dist/esm/collection/events.js 434 B
./packages/db/dist/esm/collection/index.js 3.69 kB
./packages/db/dist/esm/collection/indexes.js 2.35 kB
./packages/db/dist/esm/collection/lifecycle.js 1.76 kB
./packages/db/dist/esm/collection/mutations.js 2.47 kB
./packages/db/dist/esm/collection/state.js 5.2 kB
./packages/db/dist/esm/collection/subscription.js 3.71 kB
./packages/db/dist/esm/collection/sync.js 2.43 kB
./packages/db/dist/esm/collection/transaction-metadata.js 144 B
./packages/db/dist/esm/deferred.js 207 B
./packages/db/dist/esm/errors.js 4.83 kB
./packages/db/dist/esm/event-emitter.js 748 B
./packages/db/dist/esm/indexes/auto-index.js 777 B
./packages/db/dist/esm/indexes/base-index.js 766 B
./packages/db/dist/esm/indexes/btree-index.js 2.17 kB
./packages/db/dist/esm/indexes/lazy-index.js 1.24 kB
./packages/db/dist/esm/indexes/reverse-index.js 538 B
./packages/db/dist/esm/local-only.js 890 B
./packages/db/dist/esm/local-storage.js 2.1 kB
./packages/db/dist/esm/optimistic-action.js 359 B
./packages/db/dist/esm/paced-mutations.js 496 B
./packages/db/dist/esm/proxy.js 3.75 kB
./packages/db/dist/esm/query/builder/index.js 5.15 kB
./packages/db/dist/esm/query/builder/ref-proxy.js 1.05 kB
./packages/db/dist/esm/query/compiler/evaluators.js 1.62 kB
./packages/db/dist/esm/query/compiler/expressions.js 430 B
./packages/db/dist/esm/query/compiler/index.js 3.62 kB
./packages/db/dist/esm/query/compiler/joins.js 2.11 kB
./packages/db/dist/esm/query/compiler/order-by.js 1.5 kB
./packages/db/dist/esm/query/compiler/select.js 1.11 kB
./packages/db/dist/esm/query/effect.js 4.78 kB
./packages/db/dist/esm/query/expression-helpers.js 1.43 kB
./packages/db/dist/esm/query/ir.js 784 B
./packages/db/dist/esm/query/live-query-collection.js 360 B
./packages/db/dist/esm/query/live/collection-config-builder.js 7.63 kB
./packages/db/dist/esm/query/live/collection-registry.js 264 B
./packages/db/dist/esm/query/live/collection-subscriber.js 1.94 kB
./packages/db/dist/esm/query/live/internal.js 145 B
./packages/db/dist/esm/query/live/utils.js 1.57 kB
./packages/db/dist/esm/query/optimizer.js 2.62 kB
./packages/db/dist/esm/query/predicate-utils.js 2.97 kB
./packages/db/dist/esm/query/query-once.js 359 B
./packages/db/dist/esm/query/subset-dedupe.js 960 B
./packages/db/dist/esm/scheduler.js 1.3 kB
./packages/db/dist/esm/SortedMap.js 1.3 kB
./packages/db/dist/esm/strategies/debounceStrategy.js 247 B
./packages/db/dist/esm/strategies/queueStrategy.js 428 B
./packages/db/dist/esm/strategies/throttleStrategy.js 246 B
./packages/db/dist/esm/transactions.js 2.9 kB
./packages/db/dist/esm/utils.js 927 B
./packages/db/dist/esm/utils/browser-polyfills.js 304 B
./packages/db/dist/esm/utils/btree.js 5.61 kB
./packages/db/dist/esm/utils/comparison.js 1.05 kB
./packages/db/dist/esm/utils/cursor.js 457 B
./packages/db/dist/esm/utils/index-optimization.js 1.54 kB
./packages/db/dist/esm/utils/type-guards.js 157 B
./packages/db/dist/esm/virtual-props.js 360 B

compressed-size-action::db-package-size

@github-actions
Copy link
Contributor

Size Change: 0 B

Total Size: 4.23 kB

ℹ️ View Unchanged
Filename Size
./packages/react-db/dist/esm/index.js 249 B
./packages/react-db/dist/esm/useLiveInfiniteQuery.js 1.32 kB
./packages/react-db/dist/esm/useLiveQuery.js 1.34 kB
./packages/react-db/dist/esm/useLiveQueryEffect.js 355 B
./packages/react-db/dist/esm/useLiveSuspenseQuery.js 559 B
./packages/react-db/dist/esm/usePacedMutations.js 401 B

compressed-size-action::react-db-package-size

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant