mirror of
https://github.com/ncruces/go-sqlite3.git
synced 2026-01-11 21:49:13 +00:00
Towards database/sql.
This commit is contained in:
16
api.go
16
api.go
@@ -44,18 +44,24 @@ func newConn(ctx context.Context, module api.Module) (_ *Conn, err error) {
|
|||||||
step: getFun("sqlite3_step"),
|
step: getFun("sqlite3_step"),
|
||||||
exec: getFun("sqlite3_exec"),
|
exec: getFun("sqlite3_exec"),
|
||||||
clearBindings: getFun("sqlite3_clear_bindings"),
|
clearBindings: getFun("sqlite3_clear_bindings"),
|
||||||
|
bindCount: getFun("sqlite3_bind_parameter_count"),
|
||||||
bindInteger: getFun("sqlite3_bind_int64"),
|
bindInteger: getFun("sqlite3_bind_int64"),
|
||||||
bindFloat: getFun("sqlite3_bind_double"),
|
bindFloat: getFun("sqlite3_bind_double"),
|
||||||
bindText: getFun("sqlite3_bind_text64"),
|
bindText: getFun("sqlite3_bind_text64"),
|
||||||
bindBlob: getFun("sqlite3_bind_blob64"),
|
bindBlob: getFun("sqlite3_bind_blob64"),
|
||||||
bindZeroBlob: getFun("sqlite3_bind_zeroblob64"),
|
bindZeroBlob: getFun("sqlite3_bind_zeroblob64"),
|
||||||
bindNull: getFun("sqlite3_bind_null"),
|
bindNull: getFun("sqlite3_bind_null"),
|
||||||
|
columnCount: getFun("sqlite3_column_count"),
|
||||||
|
columnName: getFun("sqlite3_column_name"),
|
||||||
|
columnType: getFun("sqlite3_column_type"),
|
||||||
columnInteger: getFun("sqlite3_column_int64"),
|
columnInteger: getFun("sqlite3_column_int64"),
|
||||||
columnFloat: getFun("sqlite3_column_double"),
|
columnFloat: getFun("sqlite3_column_double"),
|
||||||
columnText: getFun("sqlite3_column_text"),
|
columnText: getFun("sqlite3_column_text"),
|
||||||
columnBlob: getFun("sqlite3_column_blob"),
|
columnBlob: getFun("sqlite3_column_blob"),
|
||||||
columnBytes: getFun("sqlite3_column_bytes"),
|
columnBytes: getFun("sqlite3_column_bytes"),
|
||||||
columnType: getFun("sqlite3_column_type"),
|
lastRowid: getFun("sqlite3_last_insert_rowid"),
|
||||||
|
changes: getFun("sqlite3_changes64"),
|
||||||
|
interrupt: getFun("sqlite3_interrupt"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -80,16 +86,22 @@ type sqliteAPI struct {
|
|||||||
step api.Function
|
step api.Function
|
||||||
exec api.Function
|
exec api.Function
|
||||||
clearBindings api.Function
|
clearBindings api.Function
|
||||||
|
bindCount api.Function
|
||||||
bindInteger api.Function
|
bindInteger api.Function
|
||||||
bindFloat api.Function
|
bindFloat api.Function
|
||||||
bindText api.Function
|
bindText api.Function
|
||||||
bindBlob api.Function
|
bindBlob api.Function
|
||||||
bindZeroBlob api.Function
|
bindZeroBlob api.Function
|
||||||
bindNull api.Function
|
bindNull api.Function
|
||||||
|
columnCount api.Function
|
||||||
|
columnName api.Function
|
||||||
|
columnType api.Function
|
||||||
columnInteger api.Function
|
columnInteger api.Function
|
||||||
columnFloat api.Function
|
columnFloat api.Function
|
||||||
columnText api.Function
|
columnText api.Function
|
||||||
columnBlob api.Function
|
columnBlob api.Function
|
||||||
columnBytes api.Function
|
columnBytes api.Function
|
||||||
columnType api.Function
|
lastRowid api.Function
|
||||||
|
changes api.Function
|
||||||
|
interrupt api.Function
|
||||||
}
|
}
|
||||||
|
|||||||
58
conn.go
58
conn.go
@@ -14,6 +14,9 @@ type Conn struct {
|
|||||||
mem memory
|
mem memory
|
||||||
arena arena
|
arena arena
|
||||||
handle uint32
|
handle uint32
|
||||||
|
|
||||||
|
waiter chan struct{}
|
||||||
|
done <-chan struct{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open calls [OpenFlags] with [OPEN_READWRITE] and [OPEN_CREATE].
|
// Open calls [OpenFlags] with [OPEN_READWRITE] and [OPEN_CREATE].
|
||||||
@@ -70,6 +73,8 @@ func (c *Conn) Close() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
c.SetInterrupt(nil)
|
||||||
|
|
||||||
r, err := c.api.close.Call(c.ctx, uint64(c.handle))
|
r, err := c.api.close.Call(c.ctx, uint64(c.handle))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -83,6 +88,57 @@ func (c *Conn) Close() error {
|
|||||||
return c.mem.mod.Close(c.ctx)
|
return c.mem.mod.Close(c.ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetInterrupt interrupts a long-running query when done is closed.
|
||||||
|
//
|
||||||
|
// Subsequent uses of the connection will return [INTERRUPT]
|
||||||
|
// until done is reset by another call to SetInterrupt.
|
||||||
|
//
|
||||||
|
// Typically, done is provided by [context.Context.Done]:
|
||||||
|
//
|
||||||
|
// ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
|
||||||
|
// conn.SetInterrupt(ctx.Done())
|
||||||
|
// defer cancel()
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/c3ref/interrupt.html
|
||||||
|
func (c *Conn) SetInterrupt(done <-chan struct{}) (old <-chan struct{}) {
|
||||||
|
// Is a waiter running?
|
||||||
|
if c.waiter != nil {
|
||||||
|
c.waiter <- struct{}{} // Cancel the waiter.
|
||||||
|
<-c.waiter // Wait for it to finish.
|
||||||
|
c.waiter = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
old = c.done
|
||||||
|
c.done = done
|
||||||
|
if done == nil {
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
|
waiter := make(chan struct{})
|
||||||
|
c.waiter = waiter
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-waiter:
|
||||||
|
// Waiter was cancelled.
|
||||||
|
case <-done:
|
||||||
|
// Done was closed.
|
||||||
|
|
||||||
|
// Because it doesn't touch the C stack,
|
||||||
|
// sqlite3_interrupt is safe to call from a goroutine.
|
||||||
|
_, err := c.api.interrupt.Call(c.ctx, uint64(c.handle))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for the next call to SetInterrupt.
|
||||||
|
<-waiter // Waiter was cancelled.
|
||||||
|
}
|
||||||
|
// Signal that the waiter is finished.
|
||||||
|
waiter <- struct{}{}
|
||||||
|
}()
|
||||||
|
return old
|
||||||
|
}
|
||||||
|
|
||||||
// Exec is a convenience function that allows an application to run
|
// Exec is a convenience function that allows an application to run
|
||||||
// multiple statements of SQL without having to use a lot of code.
|
// multiple statements of SQL without having to use a lot of code.
|
||||||
//
|
//
|
||||||
@@ -111,9 +167,9 @@ func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) {
|
|||||||
// https://www.sqlite.org/c3ref/prepare.html
|
// https://www.sqlite.org/c3ref/prepare.html
|
||||||
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) {
|
||||||
defer c.arena.reset()
|
defer c.arena.reset()
|
||||||
sqlPtr := c.arena.string(sql)
|
|
||||||
stmtPtr := c.arena.new(ptrlen)
|
stmtPtr := c.arena.new(ptrlen)
|
||||||
tailPtr := c.arena.new(ptrlen)
|
tailPtr := c.arena.new(ptrlen)
|
||||||
|
sqlPtr := c.arena.string(sql)
|
||||||
|
|
||||||
r, err := c.api.prepare.Call(c.ctx, uint64(c.handle),
|
r, err := c.api.prepare.Call(c.ctx, uint64(c.handle),
|
||||||
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
|
uint64(sqlPtr), uint64(len(sql)+1), uint64(flags),
|
||||||
|
|||||||
58
conn_test.go
58
conn_test.go
@@ -2,9 +2,11 @@ package sqlite3
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestConn_Close(t *testing.T) {
|
func TestConn_Close(t *testing.T) {
|
||||||
@@ -19,7 +21,7 @@ func TestConn_Close_BUSY(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
stmt, _, err := db.Prepare("BEGIN")
|
stmt, _, err := db.Prepare(`BEGIN`)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -41,6 +43,54 @@ func TestConn_Close_BUSY(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConn_Interrupt(t *testing.T) {
|
||||||
|
db, err := Open(":memory:")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer db.Close()
|
||||||
|
|
||||||
|
stmt, _, err := db.Prepare(`
|
||||||
|
WITH RECURSIVE
|
||||||
|
fibonacci (curr, next)
|
||||||
|
AS (
|
||||||
|
SELECT 0, 1
|
||||||
|
UNION ALL
|
||||||
|
SELECT next, curr + next FROM fibonacci
|
||||||
|
LIMIT 10e6
|
||||||
|
)
|
||||||
|
SELECT min(curr) FROM fibonacci
|
||||||
|
`)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
defer stmt.Close()
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.TODO(), 100*time.Millisecond)
|
||||||
|
db.SetInterrupt(ctx.Done())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
for stmt.Step() {
|
||||||
|
}
|
||||||
|
|
||||||
|
err = stmt.Err()
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("want error")
|
||||||
|
}
|
||||||
|
var serr *Error
|
||||||
|
if !errors.As(err, &serr) {
|
||||||
|
t.Fatalf("got %T, want sqlite3.Error", err)
|
||||||
|
}
|
||||||
|
if rc := serr.Code(); rc != INTERRUPT {
|
||||||
|
t.Errorf("got %d, want sqlite3.INTERRUPT", rc)
|
||||||
|
}
|
||||||
|
if got := err.Error(); got != `sqlite3: interrupted` {
|
||||||
|
t.Error("got message: ", got)
|
||||||
|
}
|
||||||
|
|
||||||
|
db.SetInterrupt(nil)
|
||||||
|
}
|
||||||
|
|
||||||
func TestConn_Prepare_Empty(t *testing.T) {
|
func TestConn_Prepare_Empty(t *testing.T) {
|
||||||
db, err := Open(":memory:")
|
db, err := Open(":memory:")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -48,7 +98,7 @@ func TestConn_Prepare_Empty(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer db.Close()
|
defer db.Close()
|
||||||
|
|
||||||
stmt, _, err := db.Prepare("")
|
stmt, _, err := db.Prepare(``)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -68,7 +118,7 @@ func TestConn_Prepare_Invalid(t *testing.T) {
|
|||||||
|
|
||||||
var serr *Error
|
var serr *Error
|
||||||
|
|
||||||
_, _, err = db.Prepare("SELECT")
|
_, _, err = db.Prepare(`SELECT`)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("want error")
|
t.Fatal("want error")
|
||||||
}
|
}
|
||||||
@@ -82,7 +132,7 @@ func TestConn_Prepare_Invalid(t *testing.T) {
|
|||||||
t.Error("got message: ", got)
|
t.Error("got message: ", got)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, _, err = db.Prepare("SELECT * FRM sqlite_schema")
|
_, _, err = db.Prepare(`SELECT * FRM sqlite_schema`)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatal("want error")
|
t.Fatal("want error")
|
||||||
}
|
}
|
||||||
|
|||||||
44
driver/driver.go
Normal file
44
driver/driver.go
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
//go:build todo
|
||||||
|
|
||||||
|
// Package driver provides a database/sql driver for SQLite.
|
||||||
|
package driver
|
||||||
|
|
||||||
|
import (
|
||||||
|
"database/sql"
|
||||||
|
"database/sql/driver"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
sql.Register("sqlite3", sqlite{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type sqlite struct{}
|
||||||
|
|
||||||
|
func (sqlite) Open(name string) (driver.Conn, error) {
|
||||||
|
c, err := sqlite3.OpenFlags(name, sqlite3.OPEN_READWRITE|sqlite3.OPEN_CREATE|sqlite3.OPEN_URI)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return conn{c}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type conn struct{ *sqlite3.Conn }
|
||||||
|
type stmt struct{ *sqlite3.Stmt }
|
||||||
|
|
||||||
|
func (c conn) Begin() (driver.Tx, error)
|
||||||
|
|
||||||
|
func (c conn) Prepare(query string) (driver.Stmt, error) {
|
||||||
|
s, _, err := c.Conn.Prepare(query)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return stmt{s}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s stmt) NumInput() int
|
||||||
|
|
||||||
|
func (s stmt) Exec(args []driver.Value) (driver.Result, error)
|
||||||
|
|
||||||
|
func (s stmt) Query(args []driver.Value) (driver.Rows, error)
|
||||||
@@ -28,15 +28,21 @@ zig cc --target=wasm32-wasi -flto -g0 -Os \
|
|||||||
-Wl,--export=sqlite3_step \
|
-Wl,--export=sqlite3_step \
|
||||||
-Wl,--export=sqlite3_exec \
|
-Wl,--export=sqlite3_exec \
|
||||||
-Wl,--export=sqlite3_clear_bindings \
|
-Wl,--export=sqlite3_clear_bindings \
|
||||||
|
-Wl,--export=sqlite3_bind_parameter_count \
|
||||||
-Wl,--export=sqlite3_bind_int64 \
|
-Wl,--export=sqlite3_bind_int64 \
|
||||||
-Wl,--export=sqlite3_bind_double \
|
-Wl,--export=sqlite3_bind_double \
|
||||||
-Wl,--export=sqlite3_bind_text64 \
|
-Wl,--export=sqlite3_bind_text64 \
|
||||||
-Wl,--export=sqlite3_bind_blob64 \
|
-Wl,--export=sqlite3_bind_blob64 \
|
||||||
-Wl,--export=sqlite3_bind_zeroblob64 \
|
-Wl,--export=sqlite3_bind_zeroblob64 \
|
||||||
-Wl,--export=sqlite3_bind_null \
|
-Wl,--export=sqlite3_bind_null \
|
||||||
|
-Wl,--export=sqlite3_column_count \
|
||||||
|
-Wl,--export=sqlite3_column_name \
|
||||||
|
-Wl,--export=sqlite3_column_type \
|
||||||
-Wl,--export=sqlite3_column_int64 \
|
-Wl,--export=sqlite3_column_int64 \
|
||||||
-Wl,--export=sqlite3_column_double \
|
-Wl,--export=sqlite3_column_double \
|
||||||
-Wl,--export=sqlite3_column_text \
|
-Wl,--export=sqlite3_column_text \
|
||||||
-Wl,--export=sqlite3_column_blob \
|
-Wl,--export=sqlite3_column_blob \
|
||||||
-Wl,--export=sqlite3_column_bytes \
|
-Wl,--export=sqlite3_column_bytes \
|
||||||
-Wl,--export=sqlite3_column_type \
|
-Wl,--export=sqlite3_last_insert_rowid \
|
||||||
|
-Wl,--export=sqlite3_changes64 \
|
||||||
|
-Wl,--export=sqlite3_interrupt \
|
||||||
|
|||||||
Binary file not shown.
@@ -5,6 +5,9 @@
|
|||||||
#define SQLITE_OS_OTHER 1
|
#define SQLITE_OS_OTHER 1
|
||||||
#define SQLITE_BYTEORDER 1234
|
#define SQLITE_BYTEORDER 1234
|
||||||
|
|
||||||
|
#define HAVE_STDINT_H 1
|
||||||
|
#define HAVE_INTTYPES_H 1
|
||||||
|
|
||||||
#define HAVE_ISNAN 1
|
#define HAVE_ISNAN 1
|
||||||
#define HAVE_USLEEP 1
|
#define HAVE_USLEEP 1
|
||||||
#define HAVE_LOCALTIME_S 1
|
#define HAVE_LOCALTIME_S 1
|
||||||
@@ -25,6 +28,16 @@
|
|||||||
#define SQLITE_OMIT_AUTOINIT
|
#define SQLITE_OMIT_AUTOINIT
|
||||||
#define SQLITE_USE_ALLOCA
|
#define SQLITE_USE_ALLOCA
|
||||||
|
|
||||||
|
// Recommended Extensions
|
||||||
|
|
||||||
|
// #define SQLITE_ENABLE_MATH_FUNCTIONS 1
|
||||||
|
// #define SQLITE_ENABLE_FTS3 1
|
||||||
|
// #define SQLITE_ENABLE_FTS3_PARENTHESIS 1
|
||||||
|
// #define SQLITE_ENABLE_FTS4 1
|
||||||
|
// #define SQLITE_ENABLE_FTS5 1
|
||||||
|
// #define SQLITE_ENABLE_RTREE 1
|
||||||
|
// #define SQLITE_ENABLE_GEOPOLY 1
|
||||||
|
|
||||||
// Need this to access WAL databases without the use of shared memory.
|
// Need this to access WAL databases without the use of shared memory.
|
||||||
#define SQLITE_DEFAULT_LOCKING_MODE 1
|
#define SQLITE_DEFAULT_LOCKING_MODE 1
|
||||||
|
|
||||||
|
|||||||
12
stmt.go
12
stmt.go
@@ -95,6 +95,18 @@ func (s *Stmt) Exec() error {
|
|||||||
return s.Reset()
|
return s.Reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// BindCount gets the number of SQL parameters in a prepared statement.
|
||||||
|
//
|
||||||
|
// https://www.sqlite.org/c3ref/bind_parameter_count.html
|
||||||
|
func (s *Stmt) BindCount() int {
|
||||||
|
r, err := s.c.api.bindCount.Call(s.c.ctx,
|
||||||
|
uint64(s.handle))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return int(r[0])
|
||||||
|
}
|
||||||
|
|
||||||
// BindBool binds a bool to the prepared statement.
|
// BindBool binds a bool to the prepared statement.
|
||||||
// The leftmost SQL parameter has an index of 1.
|
// The leftmost SQL parameter has an index of 1.
|
||||||
// SQLite does not have a separate boolean storage class.
|
// SQLite does not have a separate boolean storage class.
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ func TestStmt(t *testing.T) {
|
|||||||
}
|
}
|
||||||
defer stmt.Close()
|
defer stmt.Close()
|
||||||
|
|
||||||
|
if got := stmt.BindCount(); got != 1 {
|
||||||
|
t.Errorf("got %d, want 1", got)
|
||||||
|
}
|
||||||
|
|
||||||
err = stmt.BindBool(1, false)
|
err = stmt.BindBool(1, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
|
|||||||
Reference in New Issue
Block a user