sqlite3

package module
v0.0.0-...-b07796a Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 18, 2026 License: BSD-3-Clause Imports: 6 Imported by: 0

README

go-ruby-sqlite3/sqlite3

sqlite3 — go-ruby-sqlite3

Docs License Go Coverage

A pure-Go (no cgo) reimplementation of the Ruby sqlite3 gem's SQLite3 API. Upstream, sqlite3-ruby is a C extension linking libsqlite3; this module binds modernc.org/sqlite instead — a CGO-free transpilation of the real SQLite engine into pure Go — and exposes the gem's SQLite3::Database / SQLite3::Statement surface on top. The result is a genuine, embedded, file-backed SQLite that links statically with CGO_ENABLED=0 on every 64-bit target the go-* ecosystem supports.

It is the SQLite backend for go-embedded-ruby, but is a standalone, reusable module — a sibling of go-ruby-regexp (the Onigmo engine), go-ruby-erb (the ERB compiler), and go-ruby-yaml (the Psych port).

What it is — and isn't. The database engine, the SQL, and the SQLite↔Ruby type coercions are fully deterministic and need no interpreter, so they live here as pure Go. Turning a returned row into live Ruby objects (a String, an Integer, an Array/Hash) is the host's job; this library hands back a small, explicit value model (int64, float64, string, []byte, nil) the host maps to and from its own objects, plus an Error that names the exact SQLite3::Exception subclass to raise.

Features

Faithful port of the gem's SQLite3::Database / SQLite3::Statement, validated against the C-ext sqlite3 gem on every platform that has it:

  • DatabaseOpen / New (.new / .open), Close (idempotent), :memory: and real file databases, Path, Closed.
  • QueryExecute (positional rows), ExecuteBlock (the block form), ExecuteHash (results_as_hash), Execute2 (column-name header row), ExecuteBatch, Query (a stepping Statement), GetFirstRow, GetFirstValue.
  • Prepared statementsPrepareStatement with BindParam / BindParams, Execute / ExecuteHash, Step / Next, Columns, Types, Reset, ClearBindings, Close.
  • Parameters — positional ?, indexed ?NNN, and named :name / $name / @name, mixing positional and named binds.
  • TransactionsBegin(mode) (Deferred / Immediate / Exclusive), Commit, Rollback, InTransaction, and the block form Transaction that commits on success and rolls back on error or panic.
  • IntrospectionLastInsertRowID, Changes, TotalChanges, BusyTimeout, SetResultsAsHash / SetTypeTranslation.
  • Exceptions — the full SQLite3::Exception hierarchy (SQLException, BusyException, ConstraintException, …) mapped from SQLite result codes, with ResultCode / PrimaryCode (the gem's #code).

CGO-free, 100% test coverage, gofmt + go vet clean, and green across the six 64-bit Go targets (amd64, arm64, riscv64, loong64, ppc64le, s390x) — the whole stack, including the transpiled SQLite engine, builds and runs pure-Go.

Install

go get github.com/go-ruby-sqlite3/sqlite3

Usage

package main

import (
	"database/sql"
	"fmt"

	"github.com/go-ruby-sqlite3/sqlite3"
)

func main() {
	db, _ := sqlite3.Open(":memory:") // SQLite3::Database.new(":memory:")
	defer db.Close()

	db.Execute("CREATE TABLE hosts (id INTEGER PRIMARY KEY, name TEXT)", nil)
	db.Execute("INSERT INTO hosts (name) VALUES (?)", []sqlite3.Value{"web"})
	db.Execute("INSERT INTO hosts (name) VALUES (:n)",
		[]sqlite3.Value{sql.Named("n", "db")}) // named parameter

	rows, _ := db.Execute("SELECT id, name FROM hosts ORDER BY id", nil)
	for _, r := range rows {
		fmt.Println(r[0], r[1]) // 1 web / 2 db  (int64, string)
	}

	// Prepared statement, stepped like SQLite3::Statement.
	st, _ := db.Prepare("SELECT name FROM hosts WHERE id > ?")
	defer st.Close()
	st.BindParam(1, 0)
	for {
		row, ok, _ := st.Next()
		if !ok {
			break
		}
		fmt.Println(row[0])
	}
}

Type mapping

Values crossing the boundary use the gem's SQLite↔Ruby coercions:

SQLite Go (this package) Ruby (host binding)
INTEGER int64 Integer
REAL float64 Float
TEXT string String (UTF-8)
BLOB []byte String (ASCII-8BIT)
NULL nil nil

On bind, int / int32 / float32 / bool are normalised to the SQLite storage classes above, and a []byte binds a BLOB.

Exceptions

Errors are *sqlite3.Error, carrying the SQLite result code and the mapped Ruby class the host should raise — the same status2klass table the C ext uses:

_, err := db.Execute("INSERT INTO t VALUES (1)", nil) // duplicate PK
var e *sqlite3.Error
if errors.As(err, &e) {
	fmt.Println(e.Class)        // SQLite3::ConstraintException
	fmt.Println(e.ResultCode()) // 1555 (SQLITE_CONSTRAINT_PRIMARYKEY)
	fmt.Println(e.PrimaryCode())// 19   (SQLITE_CONSTRAINT)
}

SQLITE_ERROR → SQLException, SQLITE_BUSY → BusyException, SQLITE_CONSTRAINT → ConstraintException, SQLITE_READONLY → ReadOnlyException, … and every other code down to NotADatabaseException, with unknown codes falling back to the base SQLite3::Exception.

Backend & architectures

The engine is modernc.org/sqlite — real SQLite, transpiled to Go, no cgo. Every arch below builds and tests with CGO_ENABLED=0:

arch CGO=0 build notes
amd64 native CI lane
arm64 native CI lane
riscv64 qemu-user CI lane
loong64 qemu-user CI lane
ppc64le qemu-user CI lane (big set)
s390x qemu-user CI lane (big-endian)

The -race host lane keeps the default toolchain (cgo on for the race detector); the backend is pure-Go either way.

Tests & coverage

The suite pairs deterministic, gem-free tests (which alone hold coverage at 100%, so the qemu cross-arch and Windows lanes pass the gate) with a differential oracle versus the C-ext sqlite3 gem (gated on RUBY_VERSION >= "4.0" and the gem being installed): identical SQL scripts — CREATE / INSERT / SELECT, aggregates, expressions, NULLs, and error-provoking statements — run through both engines, and the rendered rows and raised result codes are compared. The oracle scripts $stdout.binmode so Windows text-mode never pollutes the bytes, and skip themselves where the gem or a 4.0+ ruby is absent.

COVERPKG=$(go list ./... | paste -sd, -)
go test -race -coverpkg="$COVERPKG" -coverprofile=cover.out ./...
go tool cover -func=cover.out | tail -1   # 100.0%

License

BSD-3-Clause — see LICENSE. Copyright the go-ruby-sqlite3/sqlite3 authors.

WebAssembly

Unlike the rest of the go-ruby family, this library does not target WebAssembly: its backing engine — the SQLite engine (memory-mapped pages + OS file syscalls) — relies on mmap and native filesystem syscalls that the js/wasm and wasip1/wasm sandboxes do not provide. It ships for the six 64-bit native/qemu arches only.

Documentation

Overview

Package sqlite3 is a pure-Go (CGO=0) reimplementation of the Ruby sqlite3-ruby gem's SQLite3 API. Upstream, sqlite3-ruby is a C extension binding libsqlite3; this package instead binds modernc.org/sqlite — a pure-Go, CGO-free transpilation of the real SQLite engine — so the whole stack links statically with CGO_ENABLED=0 on every 64-bit target the go-* ecosystem supports (amd64, arm64, riscv64, loong64, ppc64le, s390x).

The API mirrors SQLite3::Database and SQLite3::Statement:

db, _ := sqlite3.Open(":memory:")
defer db.Close()
db.Execute("CREATE TABLE t (a, b)", nil)
db.Execute("INSERT INTO t VALUES (?, ?)", []Value{1, "x"})
rows, _ := db.Execute("SELECT * FROM t", nil)   // [][]Value{{int64(1), "x"}}

Values crossing the boundary use the gem's SQLite<->Ruby type mapping:

SQLite    Go (this package)      Ruby (rbgo binding)
INTEGER   int64                  Integer
REAL      float64                Float
TEXT      string                 String (UTF-8)
BLOB      []byte                 String (ASCII-8BIT)
NULL      nil                    nil

A Value is any of the Go types above (plus the int / float32 / bool conveniences normalised on bind). Errors map SQLite result codes to the SQLite3::Exception hierarchy via the Error type and its ResultCode.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Database

type Database struct {
	// contains filtered or unexported fields
}

Database is a handle to a SQLite database, mirroring SQLite3::Database. It wraps a modernc.org/sqlite connection. A path of ":memory:" opens a private in-memory database (the gem's behaviour); any other path is a real file the engine creates or opens.

func New

func New(path string) (*Database, error)

New is an alias for Open, matching SQLite3::Database.new.

func Open

func Open(path string) (*Database, error)

Open opens (creating if necessary) the SQLite database at path and returns a *Database, mirroring SQLite3::Database.new / .open. Use ":memory:" for a transient in-memory database.

func (*Database) Begin

func (d *Database) Begin(mode TransactionMode) error

Begin starts a transaction with the given mode (SQLite3::Database#transaction without a block). Pass an empty mode for SQLite's default (deferred). Commit or Rollback ends it.

func (*Database) BusyTimeout

func (d *Database) BusyTimeout(ms int) error

BusyTimeout sets how long the engine waits on a locked database before returning SQLITE_BUSY, in milliseconds (SQLite3::Database#busy_timeout=). It issues a `PRAGMA busy_timeout` on the connection.

func (*Database) Changes

func (d *Database) Changes() (int64, error)

Changes returns the number of rows modified by the most recent statement (SQLite3::Database#changes), via changes().

func (*Database) Close

func (d *Database) Close() error

Close closes the database (SQLite3::Database#close). Closing an already-closed database is a no-op, matching the gem's idempotent close.

func (*Database) Closed

func (d *Database) Closed() bool

Closed reports whether the database has been closed (SQLite3::Database#closed?).

func (*Database) Commit

func (d *Database) Commit() error

Commit commits the current transaction (SQLite3::Database#commit).

func (*Database) Execute

func (d *Database) Execute(query string, binds []Value) ([]Row, error)

Execute runs sql with the given binds and returns the result rows as [][]Value (SQLite3::Database#execute). binds may be nil. Rows are positional slices; use ExecuteHash for the results_as_hash shape. A non-query statement (INSERT / UPDATE / DDL) runs and returns an empty slice.

func (*Database) Execute2

func (d *Database) Execute2(query string, binds []Value) ([]Row, error)

Execute2 runs sql and returns the rows with the column-name header row prepended, matching SQLite3::Database#execute2 ("always return at least one row — the names of the columns"). The first element is the []Value of column names; the rest are data rows.

func (*Database) ExecuteBatch

func (d *Database) ExecuteBatch(query string, binds []Value) error

ExecuteBatch runs every statement in sql sequentially, applying binds to each, and returns nothing but any error (SQLite3::Database#execute_batch). modernc's driver accepts multiple ';'-separated statements in a single Exec.

func (*Database) ExecuteBlock

func (d *Database) ExecuteBlock(query string, binds []Value, fn func(Row) error) error

ExecuteBlock runs sql and calls fn once per result row, mirroring the block form of SQLite3::Database#execute. It stops and returns the first error fn returns. Non-query statements invoke fn zero times.

func (*Database) ExecuteHash

func (d *Database) ExecuteHash(query string, binds []Value) ([]HashRow, error)

ExecuteHash runs sql and returns rows as name-keyed maps, the shape SQLite3::Database#execute yields when results_as_hash is true. It works regardless of the results_as_hash flag; the flag governs which shape the gem's #execute picks.

func (*Database) GetFirstRow

func (d *Database) GetFirstRow(query string, binds []Value) (Row, error)

GetFirstRow runs sql and returns only its first row (SQLite3::Database#get_first_row), or nil if there are no rows.

func (*Database) GetFirstValue

func (d *Database) GetFirstValue(query string, binds []Value) (Value, error)

GetFirstValue runs sql and returns the first column of its first row (SQLite3::Database#get_first_value), or nil if there are no rows.

func (*Database) InTransaction

func (d *Database) InTransaction() (bool, error)

InTransaction reports whether a transaction is currently open (SQLite3::Database#transaction_active?).

func (*Database) LastInsertRowID

func (d *Database) LastInsertRowID() (int64, error)

LastInsertRowID returns the rowid of the most recent successful INSERT (SQLite3::Database#last_insert_row_id). It is evaluated on the connection via last_insert_rowid().

func (*Database) Path

func (d *Database) Path() string

Path returns the filename the database was opened with (SQLite3::Database#filename).

func (*Database) Prepare

func (d *Database) Prepare(query string) (*Statement, error)

Prepare compiles sql into a *Statement without running it (SQLite3::Database#prepare). Bind parameters with BindParam / BindParams, then call Execute (or step with Next).

func (*Database) Query

func (d *Database) Query(query string, binds []Value) (*Statement, error)

Query prepares and runs sql, returning a *Statement positioned before the first row (SQLite3::Database#query). The caller steps it with Statement.Next / Statement.Step and must Close it. Binds are applied immediately.

func (*Database) ResultsAsHash

func (d *Database) ResultsAsHash() bool

ResultsAsHash reports the current results_as_hash setting.

func (*Database) Rollback

func (d *Database) Rollback() error

Rollback rolls back the current transaction (SQLite3::Database#rollback).

func (*Database) SetResultsAsHash

func (d *Database) SetResultsAsHash(v bool)

SetResultsAsHash sets whether hash-aware helpers key rows by column name (SQLite3::Database#results_as_hash=).

func (*Database) SetTypeTranslation

func (d *Database) SetTypeTranslation(v bool)

SetTypeTranslation sets the advisory type_translation flag (SQLite3::Database#type_translation=). Retained for API parity.

func (*Database) TotalChanges

func (d *Database) TotalChanges() (int64, error)

TotalChanges returns the total rows modified since the connection was opened (SQLite3::Database#total_changes), via total_changes().

func (*Database) Transaction

func (d *Database) Transaction(mode TransactionMode, fn func() error) (err error)

Transaction runs fn inside a transaction, committing on success and rolling back if fn returns an error or panics (the block form of SQLite3::Database#transaction). mode selects the BEGIN mode.

func (*Database) TypeTranslation

func (d *Database) TypeTranslation() bool

TypeTranslation reports the type_translation flag.

type Error

type Error struct {
	// Code is the full SQLite result code, including any extended bits (e.g.
	// 1555 for SQLITE_CONSTRAINT_PRIMARYKEY). Ruby's #code returns this.
	Code int
	// Class is the SQLite3::Exception subclass this code maps to.
	Class ExceptionClass
	// Message is the human-readable SQLite message.
	Message string
}

Error is a database error carrying the SQLite result code and the mapped SQLite3::Exception subclass. It corresponds to a raised SQLite3::Exception in the gem; ResultCode is the value exposed as Ruby's exception#code / #result_code, and Class names the Ruby class the rbgo binding raises.

func (*Error) Error

func (e *Error) Error() string

Error implements the error interface.

func (*Error) PrimaryCode

func (e *Error) PrimaryCode() int

PrimaryCode returns the primary (low 8 bits) result code, dropping any extended-code bits.

func (*Error) ResultCode

func (e *Error) ResultCode() int

ResultCode returns the SQLite result code (gem: SQLite3::Exception#result_code / #code). It includes extended-code bits when SQLite reported them.

type ExceptionClass

type ExceptionClass string

ExceptionClass is the Ruby SQLite3::Exception subclass a result code maps to. The rbgo binding raises the matching Ruby class; from Go it identifies the error category without string matching. Every Error carries one.

const (
	ExcException              ExceptionClass = "SQLite3::Exception"
	ExcSQLException           ExceptionClass = "SQLite3::SQLException"
	ExcInternalException      ExceptionClass = "SQLite3::InternalException"
	ExcPermissionException    ExceptionClass = "SQLite3::PermissionException"
	ExcAbortException         ExceptionClass = "SQLite3::AbortException"
	ExcBusyException          ExceptionClass = "SQLite3::BusyException"
	ExcLockedException        ExceptionClass = "SQLite3::LockedException"
	ExcMemoryException        ExceptionClass = "SQLite3::MemoryException"
	ExcReadOnlyException      ExceptionClass = "SQLite3::ReadOnlyException"
	ExcInterruptException     ExceptionClass = "SQLite3::InterruptException"
	ExcIOException            ExceptionClass = "SQLite3::IOException"
	ExcCorruptException       ExceptionClass = "SQLite3::CorruptException"
	ExcNotFoundException      ExceptionClass = "SQLite3::NotFoundException"
	ExcFullException          ExceptionClass = "SQLite3::FullException"
	ExcCantOpenException      ExceptionClass = "SQLite3::CantOpenException"
	ExcProtocolException      ExceptionClass = "SQLite3::ProtocolException"
	ExcEmptyException         ExceptionClass = "SQLite3::EmptyException"
	ExcSchemaChangedException ExceptionClass = "SQLite3::SchemaChangedException"
	ExcTooBigException        ExceptionClass = "SQLite3::TooBigException"
	ExcConstraintException    ExceptionClass = "SQLite3::ConstraintException"
	ExcMismatchException      ExceptionClass = "SQLite3::MismatchException"
	ExcMisuseException        ExceptionClass = "SQLite3::MisuseException"
	ExcUnsupportedException   ExceptionClass = "SQLite3::UnsupportedException"
	ExcAuthorizationException ExceptionClass = "SQLite3::AuthorizationException"
	ExcFormatException        ExceptionClass = "SQLite3::FormatException"
	ExcRangeException         ExceptionClass = "SQLite3::RangeException"
	ExcNotADatabaseException  ExceptionClass = "SQLite3::NotADatabaseException"
)

The SQLite3::Exception hierarchy, as raised by the gem. Names match the Ruby class names exactly so the rbgo binding is a direct lookup.

type HashRow

type HashRow = map[string]Value

HashRow is a single result row keyed by column name, the shape yielded when SQLite3::Database#results_as_hash= is true. Positional access is still available through the parallel Row returned alongside it by the hash-aware helpers; this map preserves the gem's string-keyed access.

type Row

type Row = []Value

Row is a single result row as a slice of column Values, in column order. This is the shape SQLite3::Database#execute yields when results_as_hash is false (the default).

type Statement

type Statement struct {
	// contains filtered or unexported fields
}

Statement is a prepared SQL statement, mirroring SQLite3::Statement. It holds a *sql.Stmt from the modernc backend plus the pending bindings; Execute (or a Query on the owning Database) materialises a result cursor the caller advances with Next / Step and reads with Row. Reset clears the cursor so the statement can run again with new binds; Close releases it.

func (*Statement) BindParam

func (s *Statement) BindParam(key any, val Value)

BindParam binds one parameter (SQLite3::Statement#bind_param). key selects the slot: an int is a 1-based positional index; a string is a named parameter, with or without a leading ':' , '$', or '@' sigil (all three SQLite name sigils are accepted, matching the gem). A purely numeric string binds the corresponding ?NNN slot.

func (*Statement) BindParams

func (s *Statement) BindParams(binds []Value)

BindParams binds a batch of parameters (SQLite3::Statement#bind_params). A positional slice binds 1..N in order; pass named parameters individually with BindParam. Passing nil binds nothing.

func (*Statement) ClearBindings

func (s *Statement) ClearBindings()

ClearBindings drops all accumulated positional and named bindings (SQLite3::Statement#clear_bindings!).

func (*Statement) Close

func (s *Statement) Close() error

Close releases the prepared statement (SQLite3::Statement#close). Closing an already-closed statement is a no-op.

func (*Statement) Closed

func (s *Statement) Closed() bool

Closed reports whether the statement has been closed (SQLite3::Statement#closed?).

func (*Statement) Columns

func (s *Statement) Columns() ([]string, error)

Columns returns the result column names (SQLite3::Statement#columns). It executes the statement if it has not run yet so the column list is available.

func (*Statement) Execute

func (s *Statement) Execute() ([]Row, error)

Execute runs the statement and returns all result rows (SQLite3::Statement#execute). For a non-query statement it returns an empty slice. Bindings set beforehand are applied; call Reset before executing again with new binds.

func (*Statement) ExecuteHash

func (s *Statement) ExecuteHash() ([]HashRow, error)

ExecuteHash runs the statement and returns its rows keyed by column name, the results_as_hash shape.

func (*Statement) Next

func (s *Statement) Next() (Row, bool, error)

Next is an alias for Step with the gem-idiomatic (row, ok) shape callers use to drive a step loop.

func (*Statement) Reset

func (s *Statement) Reset() error

Reset clears the result cursor so the statement can execute again (SQLite3::Statement#reset). Bindings are retained; call ClearBindings to drop them too.

func (*Statement) SQL

func (s *Statement) SQL() string

SQL returns the statement's source text (SQLite3::Statement#sql).

func (*Statement) Step

func (s *Statement) Step() (Row, bool, error)

Step advances to the next result row and returns it, or nil when the rows are exhausted (SQLite3::Statement#step). It executes the statement lazily on the first call. The returned bool reports whether a row was produced.

func (*Statement) Types

func (s *Statement) Types() ([]string, error)

Types returns the declared column type names, one per column (SQLite3::Statement#types). SQLite reports the declared type of each result column; an expression column with no declared type yields an empty string (SQLite's dynamic typing), matching the gem which returns nil there.

type TransactionMode

type TransactionMode string

TransactionMode selects the BEGIN mode, mirroring SQLite3::Database#transaction's :deferred / :immediate / :exclusive keyword.

const (
	Deferred  TransactionMode = "DEFERRED"
	Immediate TransactionMode = "IMMEDIATE"
	Exclusive TransactionMode = "EXCLUSIVE"
)

The three SQLite transaction modes. Deferred is SQLite's default.

type Value

type Value = any

Value is a datum crossing the Go<->SQLite boundary. On results it is always one of: nil, int64, float64, string, or []byte. On binds this package also accepts int, int32, float32, and bool for convenience and normalises them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL