sqlx adds struct scanning and named parameters on top of Go's database/sql. It is a
maintained fork of jmoiron/sqlx (MIT, the original
copyright is kept in LICENSE); import it as github.com/gookit/sqlx.
The sqlx versions of sql.DB, sql.Tx, sql.Stmt and sql.Conn leave the underlying
interfaces untouched, so they are a superset of the standard ones and existing
database/sql code keeps working.
- Scan a row into a struct (embedded structs included), a map or a slice
- Named parameters for a struct or a map, and batch insert for a slice of them
Get,Select,StructScan,SliceScan,MapScanhelpers- Works on
*sqlx.DB,*sqlx.Txand*sqlx.Conn, with...Contextvariants - Custom field mapping (
NameMapper,dbtags) and an unsafe mode that tolerates destination structs with fewer fields - No cgo in the test suite: it runs on the pure Go
modernc.org/sqlitedriver
go get github.com/gookit/sqlxpackage main
import (
"log"
_ "github.com/go-sql-driver/mysql"
"github.com/gookit/sqlx"
)
type Person struct {
FirstName string `db:"first_name"`
LastName string `db:"last_name"`
Email string
}
func main() {
// Connect pings the database; use sqlx.Open for sql.Open semantics
db, err := sqlx.Connect("mysql", "user:pass@tcp(127.0.0.1:3306)/db?parseTime=true")
if err != nil {
log.Fatal(err) // a password in the message is masked
}
defer db.Close()
db.MustExec("CREATE TABLE IF NOT EXISTS person (first_name text, last_name text, email text)")
// a transaction with named parameters
tx := db.MustBegin()
tx.NamedExec("INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)",
&Person{"Jason", "Moiron", "jmoiron@jmoiron.net"})
tx.Commit()
// many rows into a slice
people := []Person{}
if err := db.Select(&people, "SELECT * FROM person ORDER BY first_name"); err != nil {
log.Fatal(err)
}
// one row into a struct; sql.ErrNoRows when the query returns nothing
jason := Person{}
if err := db.Get(&jason, "SELECT * FROM person WHERE first_name = ?", "Jason"); err != nil {
log.Fatal(err)
}
// batch insert: a slice of structs or of maps
_, err = db.NamedExec(
"INSERT INTO person (first_name, last_name, email) VALUES (:first_name, :last_name, :email)",
[]Person{
{"Ardie", "Savea", "asavea@ab.co.nz"},
{"Ngani", "Laumape", "nlaumape@ab.co.nz"},
},
)
if err != nil {
log.Fatal(err)
}
// iterate rows into a reusable struct
rows, err := db.Queryx("SELECT * FROM person")
if err != nil {
log.Fatal(err)
}
defer rows.Close()
for rows.Next() {
p := Person{}
if err := rows.StructScan(&p); err != nil {
log.Fatal(err)
}
}
}| bind type | driver names |
|---|---|
QUESTION (?) |
mysql, sqlite, sqlite3, nrmysql, nrsqlite3 |
DOLLAR ($1) |
postgres, pgx, pgx/v4, pgx/v5, pq-timeouts, cloudsqlpostgres, ql, nrpostgres, cockroach |
NAMED (:arg1) |
oci8, ora, goracle, godror |
AT (@p1) |
sqlserver, azuresql |
sqlx.BindType(driverName)returns the bind type of a driver.sqlx.BindDriver(driverName, bindType)registers a driver that is not listed above.db.Rebind(query)converts a?query to the bindvar of the driver.sqlx.In(query, args...)expands a slice to the right number of?placeholders. It always works on?, whatever the driver: writeIN (?)and rebind the result for PostgreSQL.
- The module path is
github.com/gookit/sqlx; the API stays compatible withjmoiron/sqlx. - The pure Go
modernc.org/sqlitedriver (sqlite) is mapped toQUESTIONand used by the test suite, so the tests need no cgo.mattn/go-sqlite3is no longer a dependency. - Integrated upstream fixes: a
?inside a string or comment is left alone byRebind(#768),Connhas the named-query methods ofDB/Tx(#780), a PostgreSQL::cast right after a named parameter is supported (#1004),Getno longer touches the destination when it returnssql.ErrNoRows(#1005), a password in a connection error is masked (#1006), plus #757, #798, #805, #811, #846, #858, #938, #953, #962, #979, #993 and #1003. anyinstead ofinterface{}; Go 1.24 or later is required.- CI runs on GitHub Actions only, the old CircleCI config is gone.
Row.Scanrefuses*sql.RawBytes, likedatabase/sqldoes forQueryRow.- Multi-statement
Execbehaves differently per driver:pqruns them all, sqlite does not. - A
SELECT *with nullable columns needssql.Null*(or a pointer) in the destination struct. sqlx.Open/Connectmask the password of a malformed DSN in the returned error.
make test-race # go test -race -count=1 ./...
make lint # go vet ./... && staticcheck -checks=all ./...
make vuln-check # govulncheck ./...CI runs go test ./... and go vet ./... on Go 1.24.x and 1.26.x against a PostgreSQL
service. Pushing a v* tag runs the Tag-release workflow, which writes the release
notes from the commit log with chlog.
MIT, see LICENSE.