Skip to content

v0.8.0 - header first, streams, rows, UTF-16 text, async, and Android

Choose a tag to compare

@buvinghausen buvinghausen released this 09 Oct 07:03
· 84 commits to master since this release

HyperTabular 0.8.0 answers the first downstream feedback (#8–#12, filed against the C# binding) in every binding the language allows, and adds Android for C#, Go and Swift. It is built on HyperCast 0.8.0, the first HyperCast with Android. The native ABI is unchanged: the core's calls were always two-phase. The full list is in CHANGELOG.md.

Install

Language Package
Rust cargo add hypertabular
C# (.NET 11) dotnet add package HyperTabular
Java (JDK 25) implementation("io.github.skunkwerkx:hypertabular:0.8.0")
Go go get github.com/SkunkWerkx/HyperTabular/go@v0.8.0
Swift .package(url: "https://github.com/SkunkWerkx/HyperTabular", from: "0.8.0")
Python (3.11+) pip install hypertabular
Ruby (3.3+) gem install hypertabular
PHP (8.2+) composer require skunkwerkx/hypertabular

Highlights

  • Header first, plan after (#8). A delimited reader or a sheet opens without a plan and reads its header. You resolve column names to ordinals, then bind the plan once.
    • Name lookup is exact and case-sensitive, and returns the first match. A missing name is an error that names the column.
    • This works over a forward-only stream, with no second pass.
    • A plan known by position can still be given up front, as before.
  • Workbooks from a stream (#9). Embedded resources, HTTP responses, any stream: it is read to the end into memory the workbook owns. The format is detected from the bytes.
  • Rows (#10). A batch can also be read across, a row at a time, and every reader and sheet iterates rows across batches. The storage stays column-major. In C#, Row is a ref struct and allocates nothing.
  • Text without strings (#11).
    • C# GetChars and Java chars decode a text cell to UTF-16 in a batch-owned arena, ready for span-based parsers, without making a string.
    • Go TextString views the cell as a string without copying it.
    • Rust text_str borrows the cell as a str.
  • Async with cancellation (#12).
    • C#: ReadAsync, OpenAsync and IAsyncDisposable. These work on Blazor WebAssembly, whose browser streams cannot be read synchronously.
    • Swift: readAsync and async initializers.
    • Python: asyncio.
    • Rust: the async feature.
    • A cancelled read loses nothing, and the next read continues from where it was.
  • Android: arm64 and x86_64, 16 KB pages.
    • C#: CoreCLR and Native AOT, for .NET MAUI.
    • Go: GOOS=android.
    • Swift: the Swift SDK for Android.
    • CI builds the APKs and checks them with zipalign -P 16, then runs the x86_64 builds and the Go and Swift suites in an emulator.

By binding

Header lookup Plan-less open Rows Native text Async
Rust require / ordinal *_unbound for row in batch text_str async feature
C# Ordinal / TryOrdinal overloads + Bind foreach, Rows() GetChars ReadAsync, OpenAsync
Java ordinal / findOrdinal overloads + bind Iterable<Row>, forEachRow chars —
Go Ordinal / Find *Unbound + Bind All() iterators TextString —
Swift ordinal(of:) / firstIndex(of:) overloads + bind Collection of Row — readAsync
Python ordinal / get plan=None + bind iter(batch), rows() — asyncio
Ruby ordinal / find_ordinal plan = nil + bind Enumerable — —
PHP ordinal / find ?array $plan = null + bind foreach, rows() — —

A dash means the language has no use for the feature, not that it is missing. For example, Python, Ruby and PHP strings are already native, Go cancels through its io.Reader, and Java's virtual threads make a blocking read the norm. docs/design.md gives the reason for each.

Upgrading from 0.7

  • HyperCast 0.8.0 is the floor in every package.
  • C#, Java, Swift: the header property is now a Header, which is still a list of strings.
    • Code compiled against 0.7 needs recompiling.
    • In Swift, wrap it in Array(_:) where a [String] is expected.
  • Go: Header() returns Header, a named []string. Only reflection-based comparisons, such as reflect.DeepEqual or testify's Equal, need []string(header).
  • Rust: Error gains NoColumn, Unbound and AlreadyBound. A match that names every variant needs updating.
  • Ruby: a nil plan now means "bind later". Pass [] for an empty plan.

Verifying

Every artifact is built in CI, and all but one are attested, as in 0.7.0:

gh attestation verify <file> --repo SkunkWerkx/HyperTabular

The one exception, found after this release went out: the browser-wasm archive inside the NuGet package, runtimes/browser-wasm/nativeassets/net10.0/libhypertabular.a, the one a Blazor WebAssembly app links. It was built by the same CI workflow as the rest but never attested, so gh attestation verify finds no provenance for it. Everything else in the package, and every other artifact of this release, verifies. 0.9.0 attests the archive where it is built and checks every native before packing.