Skip to content

feat(bindings): tCount(temporal) aggregate — first skiplist-backed aggregate#50

Closed
estebanzimanyi wants to merge 1 commit intofeat/aggregate-fn-extent-tnumberfrom
feat/aggregate-fn-tcount
Closed

feat(bindings): tCount(temporal) aggregate — first skiplist-backed aggregate#50
estebanzimanyi wants to merge 1 commit intofeat/aggregate-fn-extent-tnumberfrom
feat/aggregate-fn-tcount

Conversation

@estebanzimanyi
Copy link
Copy Markdown
Member

Summary

Adds `tCount()` aggregate over all 4 temporal base types (`tint`, `tbool`, `tfloat`, `ttext`). Returns a `tint` where each instant carries the count of input temporal values valid at that instant.

```sql
-- distinct timestamps, count = 1 at each instant
SELECT tCount(t) FROM (VALUES (tint '1@2000-01-01'), (tint '2@2000-01-02')) tt(t);
-- {1@2000-01-01, 1@2000-01-02}

-- duplicate timestamp, count sums to 2
SELECT tCount(t) FROM (VALUES (tint '1@2000-01-01'), (tint '5@2000-01-01')) tt(t);
-- {2@2000-01-01}

-- overlapping sequences, count = 2 in overlap region
SELECT tCount(t) FROM (VALUES (tint '[1@2000-01-01, 1@2000-01-05]'),
(tint '[1@2000-01-03, 1@2000-01-07]')) tt(t);
-- {[1@2000-01-01, 2@2000-01-03, 2@2000-01-05], (1@2000-01-05, 1@2000-01-07]}
```

Implementation pattern (new for MobilityDuck)

This is the first skiplist-backed aggregate in MobilityDuck. The pattern established here generalises to `tand`, `tor`, `tmin`, `tmax`, `tsum`, `tavg`, `twAvg`, etc. — only the underlying `*_transfn` and merge function differ.

Component What it does
`TCountState` Holds a heap-allocated `SkipList *skiplist` — the skiplist itself lives outside the inline aggregate state buffer.
Registration `AggregateFunction::UnaryAggregateDestructor` instead of `UnaryAggregate` — installs a destructor calling `TCountFunction::Destroy` → `skiplist_free`.
`Operation` Copies the input Temporal blob (varlena) into a heap allocation and calls `temporal_tcount_transfn` — MEOS allocates a fresh skiplist on the first call and extends it in place on subsequent calls.
`Combine` Merges two skiplists via `temporal_skiplist_splice` with a sum merge function. MEOS's `datum_sum_int32` is not exported, so we provide a local equivalent (`mduck_datum_sum_int32`).
`Finalize` Calls `temporal_tagg_finalfn` which produces a `Temporal*` and internally calls `skiplist_free`. We null `state.skiplist` after the call so the destructor doesn't double-free.

Stacked on

Test plan

  • Single-row tCount on tint/tfloat/tbool/ttext
  • Multi-row distinct timestamps (count = 1 at each instant)
  • Multi-row duplicate timestamps (count sums correctly)
  • Overlapping sequences (count rises in overlap region)
  • NULL handling: NULL ignored mid-run, all-NULL returns NULL
  • No segfaults on aggregate state cleanup (destructor + Finalize ordering)

…gregate

Adds tCount() temporal-count aggregate over all 4 temporal base types:
tint, tbool, tfloat, ttext. Returns a tint where each instant carries
the count of input temporal values valid at that instant.

Implementation pattern (new in MobilityDuck — establishes the scaffolding
for tand/tor/tmin/tmax/tsum):

- TCountState holds a heap-allocated MEOS SkipList* (the skiplist
  itself lives outside the inline aggregate state buffer).
- Lifetime is managed by registering the aggregate with
  AggregateFunction::UnaryAggregateDestructor, which installs a
  destructor that calls our TCountFunction::Destroy → skiplist_free.
- Operation copies the input Temporal blob (varlena) into a heap
  allocation and calls temporal_tcount_transfn, which allocates a
  fresh SkipList on the first call and extends it in place after.
- Combine merges two skiplist states via temporal_skiplist_splice
  with a sum merge function. MEOS's datum_sum_int32 isn't exported
  so we provide a local equivalent (mduck_datum_sum_int32).
- Finalize calls temporal_tagg_finalfn which produces a Temporal*
  AND frees the skiplist; we null state.skiplist after the call so
  the destructor doesn't double-free.

Verified for:
  * single-row, multi-distinct, dup-instant (sums to 2), overlapping
    sequences (count rises in overlap region)
  * tint, tfloat, tbool, ttext
  * NULL handling: ignored mid-run, all-NULL → returns NULL
@estebanzimanyi
Copy link
Copy Markdown
Member Author

Consolidated into #60 (aggregate cluster squash). The full chain of aggregate work is now reviewable as a single PR; this branch's commits are preserved in #60's history. Closing to minimize the review queue.

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