portablesql
Portable SQL toolkit for Go
Write your database code once. Run it on MySQL, MariaDB, PostgreSQL, CockroachDB and SQLite.
Why portablesql?
A small ORM and query builder that keeps your SQL portable across engines.
Multi-Database
MySQL and MariaDB, PostgreSQL and CockroachDB, SQLite. Placeholders, upserts, LIMIT/OFFSET, LIKE and date arithmetic are rendered per engine, so the same code runs on all of them.
Zero Bloat
Each driver is its own Go module that registers itself on import. Import psql-sqlite and you will not download pgx or the MySQL driver.
Struct Binding
Map structs to tables with sql tags. Tables are created and missing columns or indexes added on first use; opt out with WithSchemaCheck(false) and call CheckStructure yourself. Generic Fetch[T], Get[T] and Iter[T] return your own types.
Query Builder
Fluent SELECT, INSERT, UPDATE and DELETE with joins, subqueries, ON CONFLICT and Limit(offset, count). Render gives you the SQL for inspection, RenderArgs the parameterized query and its arguments.
Transactions & Relations
Tx with nested savepoints and EscapeTx for out-of-transaction writes. belongs_to, has_one, has_many and many_to_many with batch preloading, plus Lazy futures that collapse many lookups into one IN query.
And More
Lifecycle hooks, soft delete with Restore and ForceDelete, enums, portable IsDuplicate / IsNotExist error helpers, Go 1.23 iterators, and vector similarity search on PostgreSQL (pgvector) and CockroachDB.
Quick Start
Import a driver, connect with a DSN, and work with your own structs. Requires Go 1.24 or later (1.25 for psql-pgsql and psql-sqlite).
package main import ( "context" "fmt" "log" "github.com/portablesql/psql" _ "github.com/portablesql/psql-sqlite" // or psql-mysql / psql-pgsql ) type User struct { psql.Name `sql:"users"` ID uint64 `sql:",key=PRIMARY"` Username string `sql:",type=VARCHAR,size=128"` Email string `sql:",type=VARCHAR,size=255,key=UNIQUE:email"` } func main() { be, err := psql.New(":memory:") // engine detected from the DSN if err != nil { log.Fatal(err) } ctx := be.Plug(context.Background()) // Insert: the table is created (or migrated) on first use err = psql.Insert(ctx, &User{ID: 1, Username: "alice", Email: "alice@example.com"}) if psql.IsDuplicate(err) { log.Println("already registered") } // Load one record, change it, write back only the changed columns alice, err := psql.Get[User](ctx, map[string]any{"Email": "alice@example.com"}) if err != nil { log.Fatal(err) } alice.Username = "alice.smith" err = psql.Update(ctx, alice) // Transactions (nested ones become savepoints) err = psql.Tx(ctx, func(ctx context.Context) error { return psql.Insert(ctx, &User{ID: 2, Username: "bob", Email: "bob@example.com"}) }) // Iterate over matches (Go 1.23 range-over-func) it, err := psql.Iter[User](ctx, map[string]any{"Username": &psql.Like{Like: "a%"}}, psql.Sort(psql.S("Username", "ASC"))) for u := range it { fmt.Println(u.Username) } // Query builder: renders ? or $1 placeholders depending on the engine query, args, err := psql.B().Select("Username").From("users"). Where(psql.Gt(psql.F("ID"), 1)).Limit(0, 10).RenderArgs(ctx) fmt.Println(query, args, err) }
On SQLite this prints SELECT "Username" FROM "users" WHERE ("ID">?) LIMIT 10 OFFSET 0 [1]; on PostgreSQL the placeholder becomes $1.
Swap the DSN for user:pass@tcp(host:3306)/db or postgres://user:pass@host/db and import the matching driver to run the same program elsewhere.
Read the guides for hooks, associations, scopes, soft delete, vectors and more.
Packages
Install the core plus the driver you need. Drivers register themselves on import and psql.New(dsn) picks the right one from the DSN.
Struct binding, query builder, transactions, associations, hooks and the engine abstraction.
go get github.com/portablesql/psqlMySQL and MariaDB via go-sql-driver/mysql (pure Go). DSN: user:pass@tcp(host:3306)/db.
go get github.com/portablesql/psql-mysqlPostgreSQL and CockroachDB via pgx (pgxpool). DSN: postgres://user:pass@host/db or host=... dbname=....
go get github.com/portablesql/psql-pgsqlSQLite via modernc.org/sqlite (pure Go, no CGO). DSN: :memory:, file:app.db or app.sqlite3.
go get github.com/portablesql/psql-sqlite