Turso Database is currently in BETA. It may contain bugs and unexpected behavior. Use caution with production data and ensure you have backups.
The tursogo package registers a "turso" driver for Go’s standard database/sql package. It uses purego to call the Turso native library from Go without CGO.
Installation
go get turso.tech/database/tursogo
Connecting to a database
Import the tursogo package for its side-effect of registering the "turso" driver, then open a connection with sql.Open().
file-based database
in-memory database
package main
import (
" database/sql "
" fmt "
" os "
_ " turso.tech/database/tursogo "
)
func main () {
db , err := sql . Open ( "turso" , "sqlite.db" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer db . Close ()
}
The driver name is "turso". Use ":memory:" as the data source name for a transient in-memory database.
Executing queries
Use db.Exec() for statements that do not return rows and db.Query() for SELECT statements.
package main
import (
" database/sql "
" fmt "
" os "
_ " turso.tech/database/tursogo "
)
func main () {
db , err := sql . Open ( "turso" , ":memory:" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer db . Close ()
// Create a table
_ , err = db . Exec ( "CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT)" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
// Insert rows
_ , err = db . Exec (
"INSERT INTO users (name, email) VALUES (?, ?)" ,
"Alice" , "[email protected] " ,
)
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
// Query rows
rows , err := db . Query ( "SELECT id, name, email FROM users" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer rows . Close ()
for rows . Next () {
var id int
var name , email string
if err := rows . Scan ( & id , & name , & email ); err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
fmt . Printf ( "id= %d name= %s email= %s \n " , id , name , email )
}
}
Prepared statements
Use db.Prepare() to compile a SQL statement once and execute it multiple times.
package main
import (
" database/sql "
" fmt "
" os "
_ " turso.tech/database/tursogo "
)
func main () {
db , err := sql . Open ( "turso" , ":memory:" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer db . Close ()
_ , _ = db . Exec ( "CREATE TABLE products (id INTEGER PRIMARY KEY, name TEXT, price REAL)" )
// Prepare an insert statement
insert , err := db . Prepare ( "INSERT INTO products (name, price) VALUES (?, ?)" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer insert . Close ()
insert . Exec ( "Widget" , 9.99 )
insert . Exec ( "Gadget" , 19.99 )
// Prepare a select
sel , err := db . Prepare ( "SELECT id, name FROM products WHERE price < ?" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer sel . Close ()
rows , _ := sel . Query ( 15.0 )
defer rows . Close ()
for rows . Next () {
var id int
var name string
rows . Scan ( & id , & name )
fmt . Printf ( "id= %d name= %s \n " , id , name )
}
}
Parameter binding
Parameters are bound positionally with ?. Pass values as variadic arguments to Exec() or Query():
// Positional binding
db . Exec (
"INSERT INTO events (type, score) VALUES (?, ?)" ,
"click" , 10 ,
)
// Named binding is also supported
rows , _ := db . Query (
"SELECT * FROM events WHERE type = :type AND score > :min" ,
sql . Named ( "type" , "click" ),
sql . Named ( "min" , 5 ),
)
Reading results
Iterate over *sql.Rows with rows.Next() and use rows.Scan() to read values into Go variables.
package main
import (
" database/sql "
" fmt "
" os "
_ " turso.tech/database/tursogo "
)
func main () {
db , err := sql . Open ( "turso" , ":memory:" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer db . Close ()
db . Exec ( "CREATE TABLE logs (id INTEGER, msg TEXT)" )
db . Exec ( "INSERT INTO logs VALUES (1, 'start'), (2, 'stop')" )
rows , err := db . Query ( "SELECT id, msg FROM logs ORDER BY id" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer rows . Close ()
// Inspect column names
cols , _ := rows . Columns ()
fmt . Println ( "Columns:" , cols )
for rows . Next () {
var id int
var msg string
if err := rows . Scan ( & id , & msg ); err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
fmt . Printf ( " %d : %s \n " , id , msg )
}
// Check for iteration errors
if err := rows . Err (); err != nil {
fmt . Println ( err )
}
}
Use db.QueryRow() when you expect exactly one row:
var count int
db . QueryRow ( "SELECT COUNT(*) FROM logs" ). Scan ( & count )
fmt . Println ( "Count:" , count )
Transactions
Use db.Begin() to start a transaction. Call Commit() to persist or Rollback() to abort.
package main
import (
" database/sql "
" fmt "
" os "
_ " turso.tech/database/tursogo "
)
func main () {
db , err := sql . Open ( "turso" , ":memory:" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer db . Close ()
db . Exec ( "CREATE TABLE accounts (id INTEGER PRIMARY KEY, balance INTEGER)" )
db . Exec ( "INSERT INTO accounts VALUES (1, 1000), (2, 500)" )
tx , err := db . Begin ()
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
_ , err = tx . Exec ( "UPDATE accounts SET balance = balance - 100 WHERE id = 1" )
if err != nil {
tx . Rollback ()
fmt . Println ( "Rollback:" , err )
os . Exit ( 1 )
}
_ , err = tx . Exec ( "UPDATE accounts SET balance = balance + 100 WHERE id = 2" )
if err != nil {
tx . Rollback ()
fmt . Println ( "Rollback:" , err )
os . Exit ( 1 )
}
if err := tx . Commit (); err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
fmt . Println ( "Transfer complete" )
}
Error handling
The tursogo driver returns standard Go errors. Check err != nil after each call. Package-level sentinel errors are also exported:
Sentinel Meaning turso.ErrTursoConnClosedOperation on a closed connection turso.ErrTursoStmtClosedOperation on a closed statement turso.ErrTursoRowsClosedAdvancing a closed rows cursor turso.ErrTursoTxDoneCommit or rollback on a finished transaction
import turso " turso.tech/database/tursogo "
if err == turso . ErrTursoConnClosed {
// reconnect or exit
}
Complete example
package main
import (
" database/sql "
" fmt "
" os "
_ " turso.tech/database/tursogo "
)
func main () {
db , err := sql . Open ( "turso" , ":memory:" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer db . Close ()
_ , err = db . Exec ( `
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
body TEXT
)
` )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
insert , _ := db . Prepare ( "INSERT INTO posts (title, body) VALUES (?, ?)" )
defer insert . Close ()
insert . Exec ( "Hello, Turso!" , "This is the first post." )
insert . Exec ( "Second post" , "More content here." )
rows , err := db . Query ( "SELECT id, title FROM posts ORDER BY id" )
if err != nil {
fmt . Println ( err )
os . Exit ( 1 )
}
defer rows . Close ()
for rows . Next () {
var id int
var title string
rows . Scan ( & id , & title )
fmt . Printf ( " %d : %s \n " , id , title )
}
}