lance-go

module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 17, 2026 License: Apache-2.0

README

lance-go

CI Go Reference

Production Go bindings for the Lance columnar format: datasets, scans, mutations, schema evolution, versioning, and the full index/search surface (vector + full-text), exposed as an idiomatic Go API.

Status: v0.1.0 (first release), full dataset + index + distributed + caching/callbacks parity, built against lance = 8.0.0.

Module:  github.com/gstamatakis95/lance-go
Import:  github.com/gstamatakis95/lance-go/lance

lance-go is implemented as cgo bindings over a small Rust FFI shim (rust/) that links the official lance crate into a static library. All data moves between Go and Rust zero-copy via the Arrow C Data Interface using arrow-go v18. See the guides in docs/ for the architecture and usage details.

Everything returns wrapped sentinel errors (lance.ErrNotFound, lance.ErrInvalidArgument, ...) that work with errors.Is. Every method that takes a context.Context cancels its in-flight native call when the context is done, surfacing context.Canceled / context.DeadlineExceeded (cancellation is cooperative — see Known limitations).

Quickstart

Two paths to a running program. Prebuilt artifacts (below) is the fast path: no Rust toolchain, no protoc, no 6-25 minute cold compile. Build from source is the fully supported fallback for platforms outside the prebuilt matrix or for developing lance-go itself.

Quickstart: prebuilt artifacts

scripts/download-artifacts.sh fetches a checksum-verified prebuilt liblance_go.a for your platform; linux/amd64, linux/arm64, and darwin/arm64 are covered (see Platform support).

This path is for consumers of the published lance-go module: prebuilt artifacts match tagged releases only. From a git checkout of this repo (developing lance-go itself, or building an unreleased commit), use make rust instead — the artifacts won't include this branch's changes, and running the download script in-repo overwrites the checked-in include/lance_go.h, which then fails make header-check.

  1. Create a module and add the dependency:

    mkdir lance-quickstart && cd lance-quickstart
    go mod init lance-quickstart
    go get github.com/gstamatakis95/lance-go/lance
    
  2. Fetch the prebuilt artifact for your platform (extracts into lib/{os}_{arch}/ + include/ in the current directory):

    curl -fsSL https://raw.githubusercontent.com/gstamatakis95/lance-go/main/scripts/download-artifacts.sh | bash
    

    From a checkout of this repo, make artifacts does the same thing (see Prebuilt artifacts below).

  3. Export the CGO flags the script printed:

    export CGO_CFLAGS="-I$PWD/include"
    export CGO_LDFLAGS="-L$PWD/lib/<os>_<arch>"
    

    (Or re-print them any time with VERSION=<tag> ./scripts/download-artifacts.sh --print-export: with a pinned VERSION that's already installed, this skips the download and just prints the exports.)

  4. Write main.go:

    package main
    
    import (
    	"context"
    	"fmt"
    	"log"
    
    	"github.com/apache/arrow-go/v18/arrow"
    	"github.com/apache/arrow-go/v18/arrow/array"
    	"github.com/gstamatakis95/lance-go/lance"
    )
    
    func main() {
    	ctx := context.Background()
    
    	// IMPORTANT: buffers exported to the native library must live outside
    	// the Go heap. Always build records with a C-malloc-backed allocator.
    	mem := lance.Allocator()
    
    	schema := arrow.NewSchema([]arrow.Field{
    		{Name: "id", Type: arrow.PrimitiveTypes.Int64},
    	}, nil)
    
    	b := array.NewRecordBuilder(mem, schema)
    	defer b.Release()
    	b.Field(0).(*array.Int64Builder).AppendValues([]int64{1, 2, 3}, nil)
    	rec := b.NewRecordBatch()
    	defer rec.Release()
    
    	rdr, err := lance.Records(schema, rec)
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer rdr.Release()
    
    	// Overwrite makes the demo re-runnable; the default mode fails if the
    	// dataset already exists.
    	ds, err := lance.Write(ctx, "/tmp/quickstart.lance", rdr,
    		lance.WithMode(lance.WriteModeOverwrite))
    	if err != nil {
    		log.Fatal(err)
    	}
    	defer ds.Close()
    
    	for batch, err := range ds.Scan().All(ctx) {
    		if err != nil {
    			log.Fatal(err)
    		}
    		fmt.Println(batch)
    	}
    }
    
  5. Run it:

    go run .
    

To build your own module against lance-go you need the native library. Two verified paths:

  • Prebuilt artifacts (above and below): export the CGO_CFLAGS / CGO_LDFLAGS that scripts/download-artifacts.sh or make platform-info prints so the linker finds the library.
  • replace directive at a lance-go checkout with make rust already run: the lance package's #cgo directives point at include/ and rust/target/release/ relative to the package, so plain go build works with no extra environment.
Quickstart: build from source

Building liblance_go.a from source takes a Rust toolchain, protoc, and a 6-25 minute cold compile of the lance + DataFusion tree. Prefer the prebuilt-artifacts quickstart above unless you're developing lance-go itself, targeting a platform outside the prebuilt matrix (see Platform support), or need a version not yet released.

Prerequisites:

  • Go 1.25+
  • Rust 1.94+ (CI pins 1.94.0)
  • protoc on PATH (the lance crate generates protobuf code at build time)

Build and test:

git clone https://github.com/gstamatakis95/lance-go
cd lance-go
make rust           # builds rust/target/release/liblance_go.a (first build: ~6-25 min)
make test           # Rust tests + Go race detector + strict cgo pointer checks
make lint           # cargo fmt, release-profile clippy, and go vet checks
make docs-check     # verify docs/api.md is current (regenerate with `make docs`)
make platform-info  # print the CGO_CFLAGS / CGO_LDFLAGS consumers need
make artifacts      # fetch a prebuilt library instead of building from source

The first make rust compiles the full lance + DataFusion dependency tree. Subsequent shim-only rebuilds take seconds.

Notes:

  • rust/Cargo.lock pins time = 0.3.47 to work around a rustc-1.94 / aws-smithy E0119 conflict. Never run a bare cargo update.
  • macOS links -framework Security -framework CoreFoundation -framework SystemConfiguration -framework IOKit. Linux links -lm -ldl -lpthread. The lance Go package carries these in its #cgo directives. macOS builds consistently target macOS 13 or later; override MACOSX_DEPLOYMENT_TARGET before invoking Make only if you intentionally build for a newer minimum.
  • include/lance_go.h is generated by cbindgen from rust/src during make rust. Never edit it by hand.

The Rust release profile strips native debug information to reduce the static archive without imposing whole-program LTO's build cost. For smaller application binaries, strip Go's symbol and DWARF tables as well:

go build -trimpath -ldflags="-s -w" ./cmd/your-app

Once you have a working build (either path above), see examples/vector_search for a runnable program that builds an IVF_PQ index and runs filtered nearest-neighbor search; the full example set is indexed in examples/README.md.

Object stores

Point the URI at S3 / Azure / GCS and pass provider options:

ds, err := lance.Open(ctx, "s3://my-bucket/datasets/demo.lance",
	lance.WithStorageOptions(map[string]string{
		"aws_region":            "eu-west-1",
		"aws_access_key_id":     os.Getenv("AWS_ACCESS_KEY_ID"),
		"aws_secret_access_key": os.Getenv("AWS_SECRET_ACCESS_KEY"),
	}))

For safe concurrent writers on S3, commit through a DynamoDB table:

ds, err := lance.Write(ctx, "s3+ddb://my-bucket/demo.lance?ddbTableName=lance-commits", rdr,
	lance.WithWriteStorageOptions(map[string]string{"aws_region": "eu-west-1"}))

See docs/storage.md for the full per-provider option reference and the local emulator test harness.

Prebuilt artifacts

Tagged releases (v*) publish prebuilt static libraries as GitHub Release assets named liblance_go-{os}-{arch}.tar.gz (containing liblance_go.a, lance_go.h, and LICENSE), plus a SHA256SUMS file. Fetch and verify the right one for your platform with:

./scripts/download-artifacts.sh                  # latest release
VERSION=v0.1.0 ./scripts/download-artifacts.sh
VERSION=v0.1.0 ./scripts/download-artifacts.sh --print-export    # re-print the CGO_* exports

or, from a checkout of this repo:

make artifacts   # wraps download-artifacts.sh with sensible defaults

The script extracts into lib/{os}_{arch}/ + include/ and prints the CGO_CFLAGS / CGO_LDFLAGS to export so go build links the prebuilt library instead of a locally built one. With --print-export and a pinned VERSION that's already installed, it skips the download and verification and just prints the exports; otherwise (including the default VERSION=latest) it downloads and installs first. Release CI smoke-tests every archive by building and running a fresh Go module against its bundled library and header. See the release workflow (.github/workflows/release.yml).

Platform support

Platform Build & test Prebuilt artifact
linux/amd64 CI yes
linux/arm64 CI yes
darwin/arm64 (Apple Silicon) CI yes
darwin/amd64 expected to work, untested no
windows not supported no

Documentation

  • docs/usage.md: dataset lifecycle, point reads, SQL, mutations, CDC, config, branches, schema evolution, versioning
  • docs/indexes.md: every index type, its parameters and defaults, FTS tokenizers, describe/load/prewarm
  • docs/storage.md: URI schemes, per-provider storage options, multi-base writes, emulator testing
  • docs/distributed.md: distributed writes and distributed index builds
  • docs/caching.md: Session, CacheBackend, ObjectStoreCache, building a Redis-backed cache
  • docs/callbacks.md: the Go callback/plugin model, write progress, column UDFs, checkpointing
  • docs/memory.md: ownership rules, Release() obligations, blobs, callbacks, error contract, threading
  • docs/observability.md: OpenTelemetry traces, metrics, and logs; enabling a backend
  • docs/troubleshooting.md: build, link, environment, and callback problems with copy-paste fixes
  • docs/api.md: generated API reference (godoc for the lance package); regenerate with make docs
  • examples/: runnable examples (incl. sql_query, take_blobs, distributed_index, plugincache, schema_evolution, maintenance)

Known limitations

  • Cancellation is cooperative: context cancellation aborts in-flight native calls (the future is dropped), but background work already spawned may still run to completion, and a cancelled write can leave orphaned data files for CleanupOldVersions to reclaim. See the "Context cancellation" section of docs/usage.md.
  • Panics become internal errors: every exported Rust C function contains unwinding and records ErrInternal; no Rust panic unwinds into Go. Go callback shims independently recover callback panics before returning to Rust.
  • C allocator required for writes: Arrow buffers crossing into the native library must be allocated with a C-backed allocator such as lance.Allocator() (docs/memory.md).

Contributing

Bug reports, feature requests, and pull requests are welcome. See CONTRIBUTING.md for build/test prerequisites, the hard rules the codebase enforces, and the pre-PR checklist.

License

Apache License 2.0. See LICENSE. lance-go statically links the lance crate and its dependency tree, which are likewise Apache-2.0 licensed.

Directories

Path Synopsis
examples
distributed_index command
Command distributed_index demonstrates the v0.2 distributed write and distributed index-build workflow:
Command distributed_index demonstrates the v0.2 distributed write and distributed index-build workflow:
fts command
Command fts builds an inverted (full-text search) index over a small document corpus and runs match and phrase queries against it.
Command fts builds an inverted (full-text search) index over a small document corpus and runs match and phrase queries against it.
maintenance command
Command maintenance demonstrates dataset maintenance: creating a dataset with several small appends (which leaves many small fragments), compacting them into fewer, larger fragments with CompactFiles, then reclaiming the storage held by old versions with CleanupOldVersions.
Command maintenance demonstrates dataset maintenance: creating a dataset with several small appends (which leaves many small fragments), compacting them into fewer, larger fragments with CompactFiles, then reclaiming the storage held by old versions with CleanupOldVersions.
object_store command
Command object_store writes and scans a Lance dataset on any object store.
Command object_store writes and scans a Lance dataset on any object store.
observability command
Command observability demonstrates lance-go's OpenTelemetry instrumentation.
Command observability demonstrates lance-go's OpenTelemetry instrumentation.
plugincache command
Command plugincache demonstrates the v0.2 cache building blocks: a Go lance.CacheBackend (the external index-cache store attached to a Session) and a lance.ObjectStoreCache (a byte-range cache for immutable file reads).
Command plugincache demonstrates the v0.2 cache building blocks: a Go lance.CacheBackend (the external index-cache store attached to a Session) and a lance.ObjectStoreCache (a byte-range cache for immutable file reads).
schema_evolution command
Command schema_evolution walks through Lance's schema evolution operations: adding a computed column with AddColumnsSQL, renaming and casting columns with AlterColumns, dropping a column with DropColumns, and joining in new columns with Merge.
Command schema_evolution walks through Lance's schema evolution operations: adding a computed column with AddColumnsSQL, renaming and casting columns with AlterColumns, dropping a column with DropColumns, and joining in new columns with Merge.
sql_query command
Command sql_query writes a small dataset and runs SQL queries over it with Dataset.SQL (DataFusion under the hood).
Command sql_query writes a small dataset and runs SQL queries over it with Dataset.SQL (DataFusion under the hood).
take_blobs command
Command take_blobs writes a dataset with a Lance blob column (a LargeBinary column tagged lance-encoding:blob=true), then reads blob bytes back with Dataset.TakeBlobs (whole blobs, sub-ranges, and cursor seeks) without materializing them into a scan.
Command take_blobs writes a dataset with a Lance blob column (a LargeBinary column tagged lance-encoding:blob=true), then reads blob bytes back with Dataset.TakeBlobs (whole blobs, sub-ranges, and cursor seeks) without materializing them into a scan.
vector_search command
Command vector_search builds an IVF_PQ vector index over a small dataset and runs nearest-neighbor searches, including a prefiltered one.
Command vector_search builds an IVF_PQ vector index over a small dataset and runs nearest-neighbor searches, including a prefiltered one.
versioning command
Command versioning demonstrates Lance's versioning features: every write commits a new version, old versions stay readable (time travel), and tags give versions stable names.
Command versioning demonstrates Lance's versioning features: every write commits a new version, old versions stay readable (time travel), and tags give versions stable names.
write_scan command
Command write_scan writes a Lance dataset to the local filesystem, scans it back in full, and runs a filtered + projected scan.
Command write_scan writes a Lance dataset to the local filesystem, scans it back in full, and runs a filtered + projected scan.
internal
testutil
Package testutil holds test helpers shared across lance-go packages.
Package testutil holds test helpers shared across lance-go packages.
Package lance provides Go bindings for the Lance columnar format, implemented as cgo bindings over the lance-go-ffi Rust static library (see rust/ and include/lance_go.h in this repository).
Package lance provides Go bindings for the Lance columnar format, implemented as cgo bindings over the lance-go-ffi Rust static library (see rust/ and include/lance_go.h in this repository).

Jump to

Keyboard shortcuts

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