diff options
Diffstat (limited to 'internal/sqlitedrv')
| -rw-r--r-- | internal/sqlitedrv/driver.go | 263 |
1 files changed, 0 insertions, 263 deletions
diff --git a/internal/sqlitedrv/driver.go b/internal/sqlitedrv/driver.go deleted file mode 100644 index c9efff4..0000000 --- a/internal/sqlitedrv/driver.go +++ /dev/null @@ -1,263 +0,0 @@ -// Package sqlitedrv registers a minimal "sqlite" driver for database/sql -// that wraps the system libsqlite3 via CGO. -// Import with: _ "nebbet.no/internal/sqlitedrv" -package sqlitedrv - -/* -#cgo pkg-config: sqlite3 -#include <sqlite3.h> -#include <stdlib.h> - -static int bind_text(sqlite3_stmt *s, int i, const char *v) { - return sqlite3_bind_text(s, i, v, -1, SQLITE_TRANSIENT); -} -static void enable_wal(sqlite3 *db) { - sqlite3_exec(db, "PRAGMA journal_mode=WAL", NULL, NULL, NULL); - sqlite3_exec(db, "PRAGMA synchronous=NORMAL", NULL, NULL, NULL); -} -*/ -import "C" - -import ( - "database/sql" - "database/sql/driver" - "errors" - "fmt" - "io" - "time" - "unsafe" -) - -func init() { - sql.Register("sqlite", &sqliteDriver{}) -} - -// ── Driver ──────────────────────────────────────────────────────────────────── - -type sqliteDriver struct{} - -func (*sqliteDriver) Open(name string) (driver.Conn, error) { - cname := C.CString(name) - defer C.free(unsafe.Pointer(cname)) - - var db *C.sqlite3 - flags := C.int(C.SQLITE_OPEN_READWRITE | C.SQLITE_OPEN_CREATE | C.SQLITE_OPEN_FULLMUTEX) - if rc := C.sqlite3_open_v2(cname, &db, flags, nil); rc != C.SQLITE_OK { - msg := C.GoString(C.sqlite3_errmsg(db)) - C.sqlite3_close(db) - return nil, fmt.Errorf("sqlite open %s: %s", name, msg) - } - C.enable_wal(db) - return &conn{db: db}, nil -} - -// ── Conn ───────────────────────────────────────────────────────────────────── - -type conn struct{ db *C.sqlite3 } - -func (c *conn) Close() error { - C.sqlite3_close(c.db) - return nil -} - -func (c *conn) Begin() (driver.Tx, error) { - if err := c.execRaw("BEGIN"); err != nil { - return nil, err - } - return &tx{c}, nil -} - -// Exec implements driver.Execer so multi-statement DDL (no args) works. -// database/sql calls this when args is empty before falling back to Prepare. -func (c *conn) Exec(query string, args []driver.Value) (driver.Result, error) { - if len(args) == 0 { - cq := C.CString(query) - defer C.free(unsafe.Pointer(cq)) - var cerr *C.char - if rc := C.sqlite3_exec(c.db, cq, nil, nil, &cerr); rc != C.SQLITE_OK { - msg := C.GoString(cerr) - C.sqlite3_free(unsafe.Pointer(cerr)) - return nil, errors.New(msg) - } - return &result{ - lastID: int64(C.sqlite3_last_insert_rowid(c.db)), - affected: int64(C.sqlite3_changes(c.db)), - }, nil - } - st, err := c.Prepare(query) - if err != nil { - return nil, err - } - defer st.Close() - return st.Exec(args) -} - -func (c *conn) Prepare(query string) (driver.Stmt, error) { - cq := C.CString(query) - defer C.free(unsafe.Pointer(cq)) - var s *C.sqlite3_stmt - if rc := C.sqlite3_prepare_v2(c.db, cq, -1, &s, nil); rc != C.SQLITE_OK { - return nil, fmt.Errorf("prepare: %s", C.GoString(C.sqlite3_errmsg(c.db))) - } - return &stmt{c: c, s: s}, nil -} - -func (c *conn) execRaw(q string) error { - cq := C.CString(q) - defer C.free(unsafe.Pointer(cq)) - var cerr *C.char - if rc := C.sqlite3_exec(c.db, cq, nil, nil, &cerr); rc != C.SQLITE_OK { - msg := C.GoString(cerr) - C.sqlite3_free(unsafe.Pointer(cerr)) - return errors.New(msg) - } - return nil -} - -// ── Tx ─────────────────────────────────────────────────────────────────────── - -type tx struct{ c *conn } - -func (t *tx) Commit() error { return t.c.execRaw("COMMIT") } -func (t *tx) Rollback() error { return t.c.execRaw("ROLLBACK") } - -// ── Stmt ───────────────────────────────────────────────────────────────────── - -type stmt struct { - c *conn - s *C.sqlite3_stmt -} - -func (st *stmt) Close() error { - C.sqlite3_finalize(st.s) - return nil -} - -func (st *stmt) NumInput() int { return int(C.sqlite3_bind_parameter_count(st.s)) } - -func (st *stmt) Exec(args []driver.Value) (driver.Result, error) { - C.sqlite3_reset(st.s) - if err := st.bind(args); err != nil { - return nil, err - } - rc := C.sqlite3_step(st.s) - if rc != C.SQLITE_DONE && rc != C.SQLITE_ROW { - return nil, fmt.Errorf("exec: %s", C.GoString(C.sqlite3_errmsg(st.c.db))) - } - return &result{ - lastID: int64(C.sqlite3_last_insert_rowid(st.c.db)), - affected: int64(C.sqlite3_changes(st.c.db)), - }, nil -} - -func (st *stmt) Query(args []driver.Value) (driver.Rows, error) { - C.sqlite3_reset(st.s) - if err := st.bind(args); err != nil { - return nil, err - } - ncols := int(C.sqlite3_column_count(st.s)) - cols := make([]string, ncols) - for i := range cols { - cols[i] = C.GoString(C.sqlite3_column_name(st.s, C.int(i))) - } - return &rows{st: st, cols: cols}, nil -} - -func (st *stmt) bind(args []driver.Value) error { - for i, arg := range args { - n := C.int(i + 1) - var rc C.int - switch v := arg.(type) { - case nil: - rc = C.sqlite3_bind_null(st.s, n) - case int64: - rc = C.sqlite3_bind_int64(st.s, n, C.sqlite3_int64(v)) - case float64: - rc = C.sqlite3_bind_double(st.s, n, C.double(v)) - case bool: - b := C.int(0) - if v { - b = 1 - } - rc = C.sqlite3_bind_int(st.s, n, b) - case string: - cs := C.CString(v) - rc = C.bind_text(st.s, n, cs) - C.free(unsafe.Pointer(cs)) - case []byte: - if len(v) == 0 { - rc = C.sqlite3_bind_null(st.s, n) - } else { - rc = C.sqlite3_bind_blob(st.s, n, - unsafe.Pointer(&v[0]), C.int(len(v)), C.SQLITE_TRANSIENT) - } - case time.Time: - s := v.UTC().Format(time.RFC3339) - cs := C.CString(s) - rc = C.bind_text(st.s, n, cs) - C.free(unsafe.Pointer(cs)) - default: - return fmt.Errorf("unsupported bind type %T at index %d", arg, i) - } - if rc != C.SQLITE_OK { - return fmt.Errorf("bind[%d]: %s", i, C.GoString(C.sqlite3_errmsg(st.c.db))) - } - } - return nil -} - -// ── Rows ───────────────────────────────────────────────────────────────────── - -type rows struct { - st *stmt - cols []string -} - -func (r *rows) Columns() []string { return r.cols } - -func (r *rows) Close() error { - C.sqlite3_reset(r.st.s) - return nil -} - -func (r *rows) Next(dest []driver.Value) error { - rc := C.sqlite3_step(r.st.s) - if rc == C.SQLITE_DONE { - return io.EOF - } - if rc != C.SQLITE_ROW { - return fmt.Errorf("next: %s", C.GoString(C.sqlite3_errmsg(r.st.c.db))) - } - for i := range dest { - switch C.sqlite3_column_type(r.st.s, C.int(i)) { - case C.SQLITE_INTEGER: - dest[i] = int64(C.sqlite3_column_int64(r.st.s, C.int(i))) - case C.SQLITE_FLOAT: - dest[i] = float64(C.sqlite3_column_double(r.st.s, C.int(i))) - case C.SQLITE_TEXT: - dest[i] = C.GoString((*C.char)(unsafe.Pointer( - C.sqlite3_column_text(r.st.s, C.int(i))))) - case C.SQLITE_BLOB: - sz := int(C.sqlite3_column_bytes(r.st.s, C.int(i))) - b := make([]byte, sz) - if sz > 0 { - ptr := C.sqlite3_column_blob(r.st.s, C.int(i)) - copy(b, (*[1 << 28]byte)(ptr)[:sz:sz]) - } - dest[i] = b - default: // SQLITE_NULL - dest[i] = nil - } - } - return nil -} - -// ── Result ──────────────────────────────────────────────────────────────────── - -type result struct { - lastID int64 - affected int64 -} - -func (r *result) LastInsertId() (int64, error) { return r.lastID, nil } -func (r *result) RowsAffected() (int64, error) { return r.affected, nil } |
