Help us shape the future of Go! Take the 2026 Go Developer Survey

arrowout

package
v0.6.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 8 Imported by: 0

Documentation

Overview

Package arrowout exports an ursus batch as Arrow, which is how anything outside this module reads ursus's output without a copy.

Why this is mostly free

ursus already stores Arrow's memory layout. data.Column holds *memory.Buffer allocated through arrowx, validity is an LSB-numbered bitmap, strings are int32 offsets plus a character buffer, lists are offsets plus a child. A data.Column IS an Arrow array with a different Go type wrapped around the same bytes, so the conversion is a re-wrap and the bytes never move.

Except at two seams, and both would be silent

  • Decimal and Int128. i128.Int128 is {Hi int64, Lo uint64}; arrow's decimal128.Num is {lo uint64, hi int64}. The widths match, the types look compatible, and a zero-copy wrap would swap the high and low 64 bits of every value — structurally valid and wrong by about 2^64. Every integer Sum outputs Int128, so this is the common path, not a corner. The words are swapped here.

  • Time at second or millisecond resolution. ursus stores every Time as int64 ticks; Arrow puts those two units on Time32 and only microseconds and nanoseconds on Time64. So they narrow. Step 57's wrap is what makes the narrowing total: a time of day is below 86_400_000 milliseconds, which fits int32 with room to spare, so this cannot be the silently-wrapped int32 that step 49 removed from rescaleTemporal and step 57 removed from the Parquet writer.

The exported buffers are refcount-inert, deliberately

arrow.Buffer.Release() at a zero refcount does `b.buf, b.length = nil, 0` — it nils the slice header INSIDE the shared Buffer. So a Buffer handed to a caller who releases it twice stops having data, and anything else pointing at that same Buffer stops having data with it. Silently: no panic, just an empty column.

Every buffer here is therefore re-wrapped with memory.NewBufferBytes, which produces a Buffer with no allocator and no parent — and Release on one of those is unconditionally a no-op. The data is still shared, because NewBufferBytes does not copy; only the refcounting is severed. The exported record's lifetime is then completely independent of the frame's, in both directions, which is the model arrowx states for ursus's own memory: "ursus hides Arrow's Retain/Release refcounting completely."

What that guard is worth TODAY, stated exactly

For the payload it is belt-and-braces, and saying otherwise would be the prose claim this project counts as its largest defect class. data.Column keeps its *memory.Buffer fields unexported and offers only RawFixed/RawOffsets/RawChars, which return []byte — so the payload path cannot hand out a live Buffer whether or not this package wraps. The guard matters the day someone adds the obvious accessor returning c.fixed directly, which is a change this file would otherwise not notice.

The one path that yields a live Buffer at all is validity: bitmap.View.Buffer() allocates when the bitmap carries a non-zero bit offset, which is what a non-byte-aligned Column.Slice produces. Even there no ordinary sequence empties it — Record.Release is itself refcounted, so releasing a record twice does not release its buffers twice, and this package keeps a reference that stops the count reaching zero.

So the wrapping prevents nothing that is reachable today. It is kept because it makes the contract a SENTENCE rather than a case analysis — "Release on an exported buffer is a no-op", true unconditionally and safe to promise in the public doc — and TestReleaseIsInert asserts that property directly rather than through a scenario that cannot happen.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Column

func Column(c *data.Column) (arrow.Array, error)

Column exports one column as an Arrow array.

func Field

func Field(name string, d dtype.DataType, nullable bool) (arrow.Field, error)

Field maps one ursus field to an Arrow field.

An Int128 is marked with arrowx.FieldTypeKey, because its Arrow type alone is indistinguishable from a Decimal(38, 0). Every field this package builds goes through here — a schema's, a list's element, a struct's children — so the mark is present at whatever depth the Int128 sits, and TestInt128IsMarkedAtEveryDepth walks each of those arms.

func Record

func Record(b *data.Batch) (arrow.Record, error)

Record exports a batch as an Arrow record, sharing ursus's memory.

The caller may Release it or not; see the package doc. Nothing here owns anything the frame does not already own.

func Schema

func Schema(s *dtype.Schema) (*arrow.Schema, error)

Schema maps an ursus schema to an Arrow schema.

func Type

func Type(d dtype.DataType) (arrow.DataType, error)

Type maps an ursus type to its Arrow equivalent.

It is total over dtype.TypeID: every declared id either has an arm or reaches the refusal, and TestEveryTypeIDIsMappedOrNamed walks the enum to prove no id falls through silently. A type added without an arm is a refusal, never a wrong mapping.

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL