Skip to content

fix: span_union / + on span × span returns spanset, not garbage#45

Merged
nhungoc1508 merged 1 commit intomainfrom
fix/span-union-returns-spanset
Apr 30, 2026
Merged

fix: span_union / + on span × span returns spanset, not garbage#45
nhungoc1508 merged 1 commit intomainfrom
fix/span-union-returns-spanset

Conversation

@estebanzimanyi
Copy link
Copy Markdown
Member

Summary

Fixes two bugs in the `Union_span_span` wrapper that made `+` and `span_union` between two spans return the canonical empty span `(t, t)` regardless of input.

Bug 1 — wrong byte count

```cpp
SpanSet *ret = union_span_span(span1, span2);
size_t span_size = sizeof(*ret); // <-- returns sizeof header only
uint8_t span_buffer = (uint8_t) malloc(span_size);
memcpy(span_buffer, ret, span_size);
```

`SpanSet` is a varlena type with a flexible `Span elems[1]` member — `sizeof` returns only the fixed-header size, so the variable-length payload (the actual spans) was never copied. The decoded blob then read as the canonical empty span.

Fixed by switching to `spanset_mem_size(ret)` (the canonical size helper used elsewhere in `spanset_functions.cpp`).

Bug 2 — wrong return type

The 10 `ScalarFunction` registrations for `+` and `span_union` over span × span declared return type `SpanType`, but MEOS's `union_span_span` returns `SpanSet` — the union of two disjoint spans cannot be expressed as a single span. Updated all 10 registrations across `{int, bigint, float, date, tstz}span` to return the matching spanset type.

Verification

```sql
-- before fix:
SELECT floatspan '[1, 3]' + floatspan '[1, 3]'; -- (t, t)
SELECT floatspan '[1, 3]' + floatspan '(3, 5]'; -- (t, t)
SELECT floatspan '[1, 1]' + floatspan '[3, 4]'; -- (t, t)

-- after fix:
SELECT floatspan '[1, 3]' + floatspan '[1, 3]'; -- {[1, 3]}
SELECT floatspan '[1, 3]' + floatspan '(3, 5]'; -- {[1, 5]}
SELECT floatspan '[1, 1]' + floatspan '[3, 4]'; -- {[1, 1], [3, 4]}
```

Surfaced by parity work in PR #17 (005_span_ops.test). The skip block there can be removed in a follow-up once this lands.

Test plan

  • Smoke tests for overlap, adjacent, disjoint cases pass locally
  • Existing parity test 005_span_ops.test still passes (the union skip block remains in place)

Two bugs in the wrapper for span × span union:

1. Union_span_span called sizeof(*ret) where ret is SpanSet*. SpanSet
   is a varlena type with a Span elems[1] flexible array — sizeof
   only returns the header size, so memcpy stored an incomplete blob
   that decoded as the canonical empty span (t, t).

   Fixed by switching to spanset_mem_size(ret) which is the canonical
   helper used elsewhere in spanset_functions.cpp.

2. The 10 ScalarFunction registrations for `+` and `span_union` over
   span × span declared the return type as SpanType, but MEOS's
   union_span_span returns SpanSet (since the union of two disjoint
   spans cannot be expressed as a single span). Updated all 10
   registrations across {int, bigint, float, date, tstz}span to
   return the matching spanset type.

After the fix:
  floatspan '[1, 3]' + floatspan '[1, 3]'   = {[1, 3]}
  floatspan '[1, 3]' + floatspan '(3, 5]'   = {[1, 5]}
  floatspan '[1, 1]' + floatspan '[3, 4]'   = {[1, 1], [3, 4]}
@nhungoc1508 nhungoc1508 merged commit da4c8dd into main Apr 30, 2026
17 checks passed
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.

2 participants