sqinn-go: SQLite from Go without cgo
Golang SQLite without cgo
At a glance
- What is it?
- sqinn-go is a Go library for accessing SQLite databases without cgo, and it does so by launching a small C program called Sqinn as a child process and exchanging structured requests over stdin and stdout, with prebuilt binaries for linux_amd64 and windows_amd64 embedded in the module. It suits a Go developer who wants SQLite in a cross-compiled binary, and the trade is that it is not a database/sql driver and that each instance is single-threaded.
- Who is it for?
- Adopt sqinn-go if you want SQLite in a Go program that has to cross compile or that cannot depend on a C toolchain, since that is exactly the problem it solves and the prebuilt binaries cover the common platforms.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 8 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem it solves and the mechanism it uses
sqinn-go is a Go library for accessing SQLite databases without cgo.
The reason that phrase needs explaining is that the standard way to use SQLite from Go is through a cgo binding to the SQLite C library. That works, and it is what most Go projects do. But cgo has consequences: you need a C toolchain on every machine that builds the code, cross compilation becomes awkward or impossible, the build is slower, and the resulting binary carries the compiled C code.
This library takes a different route. It uses Sqinn under the hood, and Sqinn is a separate small C program. The Go library starts Sqinn as a child process through `os/exec` and communicates with it over stdin, stdout and stderr. The Sqinn child process is the one that does the SQLite work.
So the C dependency has not disappeared, it has been moved. Instead of the SQLite library being linked into your Go binary, a helper program is launched and talked to. The consequences of that are the subject of the rest of the README, and the library is honest about them.
The project is MIT licensed, the primary language is Go, the default branch is master, and the last push was on 2026-10-15 with releases v2.1.9, v2.1.8 and v2.1.7 in quick succession. The module path carries a major version suffix, `github.com/cvilsmeier/sqinn-go/v2`, and the go.mod declares go 1.25 with the comment that it supports the last three Go versions.
The repository is small. There is a `sqinn.go` at the root, a `sqinn_test.go` beside it, an `examples/` directory with `nullvalues/` and `simple/`, a `prebuilt/` directory holding the embedded binaries, a `go.mod`, and a `logo.png`.
The API, and what the Must functions are for
The usage example in the README is a complete program, and it shows the shape of the API better than a list of methods would.
The installation is:
go get -u github.com/cvilsmeier/sqinn-go/v2Then the program itself:
import (
"fmt"
"github.com/cvilsmeier/sqinn-go/v2"
)
func main() {
// Launch sqinn, close when done.
sq := sqinn.MustLaunch(sqinn.Options{
Db: ":memory:", // use a transient in-memory database
})
defer sq.Close()
// Create a table, cleanup when done
sq.MustExecSql("CREATE TABLE users (id INTEGER PRIMARY KEY NOT NULL, name TEXT)")
defer sq.MustExecSql("DROP TABLE users")The `Must` prefix is the Go convention for a function that panics on error rather than returning one, and the library uses it consistently. `MustLaunch` launches the child process, `MustExecSql` runs a statement with no parameters, and the deferred calls clean up.
The insert shows the parameter binding model, which is worth understanding because it differs from the usual database/sql shape:
// Insert users
sq.MustExecParams("INSERT INTO users (id, name) VALUES (?, ?)", 3, 2, []sqinn.Value{
sqinn.Int32Value(1), sqinn.StringValue("Alice"),
sqinn.Int32Value(2), sqinn.StringValue("Bob"),
sqinn.Int32Value(3), sqinn.StringValue("Carol"),
})There is a count, then a count of parameter groups, then the groups. The `sqinn.Value` type is an interface, with constructors such as `sqinn.Int32Value` and `sqinn.StringValue` producing the typed values, and the values are grouped because a single statement can take several parameter sets.
The query is the part with the most unusual detail:
// Query users
rows := sq.MustQueryRows(
"SELECT id, name FROM users WHERE id >= ? ORDER BY id",
[]sqinn.Value{sqinn.Int32Value(0)}, // query parameters
[]byte{sqinn.ValInt32, sqinn.ValString}, // fetch id as int, name as string
)
for _, values := range rows {
fmt.Printf("user id=%d, name=%s\n", values[0].Int32, values[1].String)
}The second argument is the query parameters and the third is the column type specification. That third argument is a `[]byte` of type tags, where `sqinn.ValInt32` and `sqinn.ValString` say how to decode each column, and the decoded values then come back as typed fields, `values[0].Int32` and `values[1].String`.
So the caller states up front how it wants the columns decoded, and the library does the conversion. That is the price of the design, and it is a price paid at the point where the interface differs most from database/sql: you describe the shape of the result rather than scanning into destinations.
More usage examples are in the `examples` directory, with a `simple/` and a `nullvalues/` case.
The prebuilt binary question
Sqinn is a separate program, and the library has to find it at runtime, which is the logistical problem the README addresses.
The library includes a prebuilt embedded build of sqinn for linux_amd64 and windows_amd64. That is the convenient path, and it covers the two platforms where cross-compiled Go binaries are most common. If you do not want to use a prebuilt sqinn binary, you can compile sqinn yourself, following the instructions in the Sqinn repository, and then specify the path to it:
sq := sqinn.MustLaunch(sqinn.Options{
Sqinn: "/path/to/sqinn",
})So there are two modes. Embedded and automatic, where the module carries a binary for your platform and the library works out where to put it, or explicit, where you point it at a Sqinn you built yourself.
The second mode is the one that matters for platforms without a prebuilt binary, and it is also the mode that gives you control over how Sqinn was compiled. If you need Sqinn built against a particular SQLite configuration, building it yourself and pointing at it is the way to get that.
The changelog shows this path being exercised regularly. v2.1.8 updated the prebuilt sqinn to v2.0.8, v2.1.7 updated it to v2.0.7 for an empty blob bugfix, and v2.1.6, v2.1.5, v2.1.4, v2.1.3, v2.1.2 and v2.1.1 each updated the prebuilt sqinn to the matching v2.0.x. The prebuilt binary is tracked against the upstream Sqinn releases, and every upstream release is picked up here.
Other changelog entries show the library's own work. v2.1.9 fixed ownership of decoded blob results, and v2.1.0 added bind and scan functions for converting Go types to sqinn values and back, which is the feature that lets the `sqinn.Value` constructors be written by hand rather than only by the library's own binding helpers.
One entry in the changelog is worth noting because it changes who can use the library: v2.1.4 changed the license from Unlicense to MIT. A library that previously placed no restrictions now carries an explicit permissive licence, which is a change that affects anyone who had assumed the public domain terms carried over.
What you gain and what you give up
The Pros and Cons section is unusually direct, and it is the most useful part of the README for deciding whether to use the library.
The advantages are four, and they are all consequences of not using cgo. No need to have gcc installed on development machines. Go cross compilation works. Faster build speed than cgo, with the specific figures given as 1s against 3s for the sample program. And smaller binary size than cgo, given as 2MB against 10MB for the sample program.
Those two numbers are the most concrete thing in the documentation, because they are the difference people actually feel. A build that goes from three seconds to one, and a binary that goes from ten megabytes to two, are both worth having in a container image or a lambda.
The disadvantages are three. There is no built-in connection pooling. Sqinn-Go is not a Go `database/sql` driver. And Sqinn covers only a subset of SQLite's C APIs.
Each of those is worth understanding in turn.
The lack of connection pooling is really a consequence of the architecture rather than an omission, and the concurrency section explains it. A single Sqinn instance is inherently single-threaded, with requests served one after another. If you want true concurrency at the database level, you can spin up multiple Sqinn instances, and you may even implement a connection pool yourself, but the warning is that when accessing a SQLite database concurrently, the dreaded SQLITE_BUSY error might occur, and the PRAGMA busy_timeout might help to avoid those errors.
So pooling is available as a pattern, just not as a feature. Multiple child processes against the same database file, with a busy timeout configured to handle contention.
The subset of SQLite's C APIs is a smaller caveat than it sounds, since most application code touches only the common surface, but it is a real ceiling on what you can do and worth checking against your needs.
The performance section points at a separate benchmark repository for the details, and states that for many use cases Sqinn-Go performance is better than cgo solutions. Given that each operation is an inter-process round trip, that is a claim worth reading the benchmark for rather than taking on faith.
Why there is no database/sql driver
The Discussion section explains the design decisions behind the API, and the database/sql one is the most interesting because it is a decision to not do something.
Database/sql is Go's default abstraction layer for SQL databases. It is widely used and there are many third-party packages built on top of it. Sqinn-Go does not implement the database/sql interfaces.
The reason given is a good one. The sql package provides low-level function calls to prepare statements, bind parameters, fetch column values, and so on. Sqinn could do that too. But since for every function call Sqinn-Go has to make an inter-process communication request and response roundtrip to a sqinn child process, this would be very slow.
So the library offers higher-level Exec and Query interfaces that should be used in favour of low-level fine-grained functions.
That is a coherent position. The database/sql model is built for a library that is linked into the process and can answer a Prepare or a Bind in nanoseconds. Over a pipe to a child process, the same granularity of interface would turn every one of those calls into a round trip, and the overhead would dominate. Rather than implement the interface badly, the author chose a different shape and said so.
The consequence for a user is the important part. If your application already uses database/sql, or a framework that does, sqinn-go is not a drop-in. You will be using a different API, with different error handling conventions and a different parameter and result model.
The Go without cgo discussion covers the runtime dependency honestly. Sqinn-Go is Go without cgo, as it does not use cgo, nor does it depend on third-party cgo packages. However, it has a runtime dependency on Sqinn, which is a program written in C. Sqinn has to be installed separately on each machine where a Sqinn-Go application is executing, and for this to work, Sqinn has to be compiled for every target platform. As an alternative, pre-built Sqinn binaries for common platforms are included in sqinn-go or can be downloaded from the Sqinn releases page.
So the no-cgo property is about the build of your Go program, not about the absence of C from the system. That distinction matters when you are deciding whether this library fits, and the README is clear about it.
Editorial conclusion
Adopt sqinn-go if you want SQLite in a Go program that has to cross compile or that cannot depend on a C toolchain, since that is exactly the problem it solves and the prebuilt binaries cover the common platforms. Do not adopt it if your application depends on the database/sql interfaces or on connection pooling, because neither is provided and the authors explain why, and check first whether your workload is concurrent, because a single Sqinn instance serves requests one after another.
Frequently asked questions
What is sqinn-go?
It is a Go library for accessing SQLite databases without cgo. It uses Sqinn under the hood, starting Sqinn as a child process through os/exec and communicating with it over stdin, stdout and stderr, and the Sqinn child process does the SQLite work. The licence is MIT and the module path is github.com/cvilsmeier/sqinn-go/v2.
How do I use sqinn-go to query a database?
Launch with `sqinn.MustLaunch(sqinn.Options{Db: ":memory:"})` and defer `sq.Close()`, then use MustQueryRows with the SQL, the query parameters and a `[]byte` of column type tags such as sqinn.ValInt32 and sqinn.ValString. The decoded values come back as typed fields on each row, for example values[0].Int32 and values[1].String.
What are the advantages of sqinn-go over a cgo SQLite driver?
No gcc is needed on development machines, Go cross compilation works, the build is faster than cgo at 1s against 3s for the sample program, and the binary is smaller at 2MB against 10MB. The downsides are no built-in connection pooling, no database/sql driver implementation, and coverage of only a subset of SQLite's C APIs.
Why is sqinn-go not a database/sql driver?
Because the sql package exposes low-level calls to prepare statements, bind parameters and fetch column values, and every one of those would have to become an inter-process request and response roundtrip to the Sqinn child process, which would be very slow. Sqinn-Go therefore provides higher-level Exec and Query interfaces instead.
Where does sqinn-go get the Sqinn binary?
The library includes a prebuilt embedded build of sqinn for linux_amd64 and windows_amd64. If you do not want the prebuilt binary you can compile sqinn yourself from its own repository and pass the path in the options with `Sqinn: "/path/to/sqinn"`. Prebuilt binaries for other platforms can also be downloaded from the Sqinn releases page.
Is sqinn-go still being maintained?
The repository is not archived and the last push was on 2026-10-15, with releases v2.1.9, v2.1.8 and v2.1.7 in quick succession. The changelog shows the embedded sqinn binary being updated with each upstream release, v2.1.7 fixed an empty blob bug and raised the minimum Go version to 1.25, and v2.1.4 changed the licence from Unlicense to MIT.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/cvilsmeier-sqinn-go)Was this useful?