DuckDB virtual file system based on io/fs
The purpose of this package is to allow Go programs to mount io/fs read-only
file systems as backend for DuckDB databases. It guarantees that all I/O will be
executed by the Go runtime instead of being performed directly by DuckDB.
DuckDB has extensions such as httpfs or aws to integrate the query engine
with data sets available over the network, but those are implemented in C++,
they don't share the same I/O stack as the rest of a Go application, and this
duality introduces challenges when it comes to instrumentation, access control,
or performance.
By sandboxing DuckDB via a Virtual File System, the go-duckfs package
bridges all I/O operations back into the Go application to leverage pure Go
packages like net/http, cloud vendor native SDKs, telemetry wrappers, etc...
The package requires C++ symbols that are not present in the go-duckdb static build. The recommended way to install DuckDB is via pixi:
# Install DuckDB library
pixi install --locked
# Build with dynamic linking
CGO_ENABLED=1 \
CGO_LDFLAGS="-L.pixi/envs/default/lib" \
go build -tags=duckdb_use_libThe Go program must be compiled using the duckdb_use_lib tag to select the
version of the DuckDB Go bindings suited for dynamic linking.
See also: https://github.com/duckdb/duckdb-go?tab=readme-ov-file#linking-a-dynamic-library
Since go test builds the program from sources, it is necessary to set the
CGO environment variables and build tags:
CGO_ENABLED=1 \
CGO_LDFLAGS="-L.pixi/envs/default/lib" \
LD_LIBRARY_PATH=".pixi/envs/default/lib" \
go test -v ./... -tags=duckdb_use_libThe package exposes functions to create connectors for DuckDB instances with
a fs.FS as virtual file system, which can then be used to create a sql.DB.
c, err := duckfs.Open("", nil, os.DirFS("testdata"))
if err != nil {
log.Fatal(err)
}
db := sql.OpenDB(c)
defer db.Close()
// Query files using DuckDB's read functions
rows, err := db.Query(`SELECT * FROM read_parquet('data.parquet')`)Virtual paths use a protocol prefix (e.g., test://file.parquet). To handle
these paths, wrap your fs.FS to strip the protocol prefix:
type myFS struct {
fsys fs.FS
}
func (f *myFS) Open(name string) (fs.File, error) {
// Strip protocol prefix if present
name = strings.TrimPrefix(name, "myproto://")
return f.fsys.Open(name)
}Then use it with DuckDB queries:
c, err := duckfs.Open("", nil, &myFS{fsys: os.DirFS("data")})
if err != nil {
log.Fatal(err)
}
db := sql.OpenDB(c)
defer db.Close()
// Query using protocol prefix
row := db.QueryRow(`SELECT * FROM read_csv('myproto://records.csv')`)Contributions are welcome! To get started:
- Ensure you have Go 1.24.0+ and pixi installed
- Run pixi install --lockedto install the DuckDB library
- Run tests using the commands in the Testing section
Please report bugs and feature requests via GitHub Issues.
This project is licensed under the MIT License - see the LICENSE file for details.