Go SQL driver for DuckDB
The DuckDB driver conforms to the built-in database/sql
interface.
go get github.com/marcboeker/go-duckdb
You must have the correct version of gcc and the necessary runtime libraries installed on Windows. One method to do this is using msys64. To begin, install msys64 using their installer. Once you installed msys64, open a msys64 shell and run:
pacman -S mingw-w64-ucrt-x86_64-gcc
Select "yes" when necessary; it is okay if the shell closes.
Then, add gcc to the path using whatever method you prefer.
In powershell this is $env:PATH = "C:\msys64\ucrt64\bin:$env:PATH"
.
After, you can compile this package in Windows.
Note: For readability, we omit error handling in most examples.
go-duckdb
hooks into the database/sql
interface provided by the Go stdlib
.
To open a connection, specify the driver type as duckdb
.
db, err := sql.Open("duckdb", "")
defer db.Close()
The above lines create an in-memory instance of DuckDB. To open a persistent database, specify a file path to the database file. If the file does not exist, then DuckDB creates it.
db, err := sql.Open("duckdb", "/path/to/foo.db")
defer db.Close()
If you want to set specific config options for DuckDB,
you can add them as query style parameters in the form of name=value
pairs to the DSN.
db, err := sql.Open("duckdb", "/path/to/foo.db?access_mode=read_only&threads=4")
defer db.Close()
Alternatively, you can use sql.OpenDB.
That way, you can perform initialization steps in a callback function before opening the database.
Here's an example that configures some parameters when opening a database with sql.OpenDB(connector)
.
connector, err := duckdb.NewConnector("/path/to/foo.db?access_mode=read_only&threads=4", func(execer driver.ExecerContext) error {
bootQueries := []string{
"SET schema=main",
"SET search_path=main",
}
for _, query := range bootQueries {
_, err = execer.ExecContext(context.Background(), query, nil)
if err != nil {
return err
}
}
return nil
})
db := sql.OpenDB(connector)
defer db.Close()
Please refer to the database/sql documentation for further instructions on usage.
By default, go-duckdb
statically links pre-build DuckDB libraries into your binary.
Statically linking DuckDB increases your binary size.
go-duckdb
bundles the following pre-compiled static libraries.
However, due to GitHub file size restrictions (100MB) and Go repository size limitations (500MB), these might change in the future.
- MacOS: amd64, arm64.
- Linux: amd64, arm64.
- FreeBSD: amd64.
- Windows: amd64.
If none of the pre-build libraries satisfy your needs, you can build a custom static library; see deps.yaml
.
Note: The DuckDB team is currently working on deploying pre-built static libraries as part of their releases and nightly builds. Once available, you can also download these libraries. They bundle the default extensions for duckdb releases.
Once a static library (libduckdb_bundle.a
) is available, you can build your project like this.
CGO_LDFLAGS="-lc++ -lduckdb_bundle -L/path/to/folder/with/lib" go build -tags=duckdb_use_static_lib
Alternatively, you can dynamically link DuckDB by passing -tags=duckdb_use_lib
to go build
.
You must have a copy of libduckdb
available on your system (.so
on Linux or .dylib
on macOS),
which you can download from the DuckDB releases page.
For example:
# On Linux.
CGO_ENABLED=1 CGO_LDFLAGS="-L/path/to/libs" go build -tags=duckdb_use_lib main.go
LD_LIBRARY_PATH=/path/to/libs ./main
# On macOS.
CGO_ENABLED=1 CGO_LDFLAGS="-L/path/to/libs" go build -tags=duckdb_use_lib main.go
DYLD_LIBRARY_PATH=/path/to/libs ./main
undefined: conn
Some people encounter an undefined: conn
error when building this package.
This error is due to the Go compiler determining that CGO is unavailable.
This error can happen due to a few issues.
The first cause, as noted in the comment here,
might be that the buildtools
are not installed.
To fix this for ubuntu, you can install them using:
sudo apt-get update && sudo apt-get install build-essential
Another cause can be cross-compilation since the Go compiler automatically disables CGO when cross-compiling.
To enable CGO when cross-compiling, use CC={C cross compiler} CGO_ENABLED=1 {command}
to force-enable CGO and set the right cross-compiler.
TIMESTAMP vs. TIMESTAMP_TZ
In the C API, DuckDB stores both TIMESTAMP
and TIMESTAMP_TZ
as duckdb_timestamp
, which holds the number of
microseconds elapsed since January 1, 1970, UTC (i.e., an instant without offset information).
When passing a time.Time
to go-duckdb, go-duckdb transforms it to an instant with UnixMicro()
,
even when using TIMESTAMP_TZ
. Later, scanning either type of value returns an instant, as SQL types do not model
time zone information for individual values.
DuckDB lives in process.
Therefore, all its memory lives in the driver.
All allocations live in the host process, which is the Go application.
Especially for long-running applications, it is crucial to call the corresponding Close
-functions as specified in database/sql.
The following is a list of examples.
db, err := sql.Open("duckdb", "")
defer db.Close()
conn, err := db.Conn(context.Background())
defer conn.Close()
rows, err := conn.QueryContext(context.Background(), "SELECT 42")
// Alternatively, rows.Next() has to return false.
rows.Close()
appender, err := NewAppenderFromConn(conn, "", "test")
defer appender.Close()
// If not passed to sql.OpenDB.
connector, err := NewConnector("", nil)
defer connector.Close()
If you want to use the DuckDB Appender API, you can obtain a new Appender
by passing a DuckDB connection to NewAppenderFromConn()
.
See examples/appender.go
for a complete example.
connector, err := duckdb.NewConnector("test.db", nil)
defer connector.Close()
conn, err := connector.Connect(context.Background())
defer conn.Close()
// Obtain an appender from the connection.
// NOTE: The table 'test_tbl' must exist in test.db.
appender, err := NewAppenderFromConn(conn, "", "test_tbl")
defer appender.Close()
err = appender.AppendRow(...)
This section describes using the DuckDB Profiling API.
DuckDB's profiling information is connection-local.
The following example walks you through the necessary steps to obtain the ProfilingInfo
type, which contains all available metrics.
Please refer to the DuckDB documentation on configuring and collecting specific metrics.
- First, you need to obtain a connection.
- Then, you enable profiling for the connection.
- Now, for each subsequent query on this connection, DuckDB will collect profiling information.
- Optionally, you can turn off profiling at any point.
- Next, you execute the query for which you want to obtain profiling information.
- Finally, directly after executing the query, retrieve any available profiling information.
db, err := sql.Open("duckdb", "")
con, err := db.Conn(context.Background())
_, err = con.ExecContext(context.Background(), `PRAGMA enable_profiling = 'no_output'`)
_, err = con.ExecContext(context.Background(), `PRAGMA profiling_mode = 'detailed'`)
res, err := con.QueryContext(context.Background(), `SELECT 42`)
info, err := GetProfilingInfo(con)
err = res.Close()
_, err = con.ExecContext(context.Background(), `PRAGMA disable_profiling`)
err = con.Close()
err = db.Close()
If you want to use the DuckDB Arrow Interface, you can obtain a new Arrow
by passing a DuckDB connection to NewArrowFromConn()
.
connector, err := duckdb.NewConnector("", nil)
defer connector.Close()
conn, err := connector.Connect(context.Background())
defer conn.Close()
// Obtain the Arrow from the connection.
arrow, err := duckdb.NewArrowFromConn(conn)
rdr, err := arrow.QueryContext(context.Background(), "SELECT * FROM generate_series(1, 10)")
defer rdr.Release()
for rdr.Next() {
// Process each record.
}
The Arrow interface is a heavy dependency.
If you do not need it, you can disable it by passing -tags=no_duckdb_arrow
to go build
.
Note: This will be made opt-in in V2.
go build -tags="no_duckdb_arrow"
If you want to vendor a module containing go-duckdb
, please use modvendor
to include the missing header files and libraries.
See issue #174 for more details.
go install github.com/goware/modvendor@latest
go mod vendor
modvendor -copy="**/*.a **/*.h" -v
Now, you can build your module as usual.
go-duckdb
statically builds the JSON
extension for its pre-compiled libraries.
Additionally, automatic extension loading is enabled.
The extensions available differ between the pre-compiled libraries.
Thus, if you fail to install and load an extension, you might have to link a custom DuckDB.
Specifically, for MingW (Windows), there are no distributed extensions (yet).
You can statically include them by extending the BUILD_EXTENSIONS="json"
variable in the Makefile
.