diff --git a/conn.go b/conn.go index 6b838b8..d74b314 100644 --- a/conn.go +++ b/conn.go @@ -53,13 +53,18 @@ func OpenFlags(filename string, flags OpenFlag) (conn *Conn, err error) { return c, nil } -// Close closes a database connection. +// Close closes the database connection. +// // If the database connection is associated with unfinalized prepared statements, // open blob handles, and/or unfinished backup objects, // Close will leave the database connection open and return [BUSY]. // // https://www.sqlite.org/c3ref/close.html func (c *Conn) Close() error { + if c == nil { + return nil + } + r, err := c.api.close.Call(c.ctx, uint64(c.handle)) if err != nil { return err @@ -68,6 +73,8 @@ func (c *Conn) Close() error { if err := c.error(r[0]); err != nil { return err } + + c.handle = 0 return c.mem.mod.Close(c.ctx) } @@ -86,17 +93,17 @@ func (c *Conn) Exec(sql string) error { return c.error(r[0]) } -// Prepare calls [PrepareFlags] with no flags. +// Prepare calls [Conn.PrepareFlags] with no flags. func (c *Conn) Prepare(sql string) (stmt *Stmt, tail string, err error) { return c.PrepareFlags(sql, 0) } -// PrepareFlags compiles the first statement in sql; +// PrepareFlags compiles the first SQL statement in sql; // tail is left pointing to what remains uncompiled. // If the input text contains no SQL (if the input is an empty string or a comment), -// both stmt and err will be nil +// both stmt and err will be nil. // -// https://www.sqlite.org/c3ref/exec.html +// https://www.sqlite.org/c3ref/prepare.html func (c *Conn) PrepareFlags(sql string, flags PrepareFlag) (stmt *Stmt, tail string, err error) { sqlPtr := c.newString(sql) stmtPtr := c.new(ptrlen) diff --git a/conn_test.go b/conn_test.go index bb3a1ef..07c2689 100644 --- a/conn_test.go +++ b/conn_test.go @@ -121,3 +121,8 @@ func TestConn_free(t *testing.T) { db.free(ptr) } + +func TestConn_Close(t *testing.T) { + var conn *Conn + conn.Close() +} diff --git a/go.mod b/go.mod index 5b76b5c..058db3a 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ncruces/go-sqlite3 go 1.19 require ( - github.com/ncruces/julianday v0.1.4 + github.com/ncruces/julianday v0.1.5 github.com/tetratelabs/wazero v1.0.0-pre.8 golang.org/x/sync v0.1.0 golang.org/x/sys v0.5.0 diff --git a/go.sum b/go.sum index 36eab7a..31688c7 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,5 @@ -github.com/ncruces/julianday v0.1.4 h1:PFv147rE9ZApvAJW8yBcASI104HyNtBTnNsZpJB4tAI= -github.com/ncruces/julianday v0.1.4/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= +github.com/ncruces/julianday v0.1.5 h1:hDJ9ejiMp3DHsoZ5KW4c1lwfMjbARS7u/gbYcd0FBZk= +github.com/ncruces/julianday v0.1.5/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/tetratelabs/wazero v1.0.0-pre.8 h1:Ir82PWj79WCppH+9ny73eGY2qv+oCnE3VwMY92cBSyI= github.com/tetratelabs/wazero v1.0.0-pre.8/go.mod h1:u8wrFmpdrykiFK0DFPiFm5a4+0RzsdmXYVtijBKqUVo= golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= diff --git a/stmt.go b/stmt.go index a7e4daf..2d75ff3 100644 --- a/stmt.go +++ b/stmt.go @@ -4,13 +4,23 @@ import ( "math" ) +// Stmt is a prepared statement object. +// +// https://www.sqlite.org/c3ref/stmt.html type Stmt struct { c *Conn handle uint32 err error } +// Close destroys the prepared statement object. +// +// https://www.sqlite.org/c3ref/finalize.html func (s *Stmt) Close() error { + if s == nil { + return nil + } + r, err := s.c.api.finalize.Call(s.c.ctx, uint64(s.handle)) if err != nil { return err @@ -20,14 +30,38 @@ func (s *Stmt) Close() error { return s.c.error(r[0]) } +// Reset resets the prepared statement object. +// +// https://www.sqlite.org/c3ref/reset.html func (s *Stmt) Reset() error { r, err := s.c.api.reset.Call(s.c.ctx, uint64(s.handle)) if err != nil { return err } + s.err = nil + return s.c.error(r[0]) +} + +// ClearBindings resets all bindings on the prepared statement. +// +// https://www.sqlite.org/c3ref/clear_bindings.html +func (s *Stmt) ClearBindings() error { + r, err := s.c.api.clearBindings.Call(s.c.ctx, uint64(s.handle)) + if err != nil { + return err + } return s.c.error(r[0]) } +// Step evaluates the SQL statement. +// If the SQL statement being executed returns any data, +// then true is returned each time a new row of data is ready for processing by the caller. +// The values may be accessed using the Column access functions. +// Step is called again to retrieve the next row of data. +// If an error has occurred, Step returns false; +// call [Stmt.Err] or [Stmt.Reset] to get the error. +// +// https://www.sqlite.org/c3ref/step.html func (s *Stmt) Step() bool { r, err := s.c.api.step.Call(s.c.ctx, uint64(s.handle)) if err != nil { @@ -45,20 +79,28 @@ func (s *Stmt) Step() bool { return false } +// Err gets the last error occurred during [Stmt.Step]. +// Err returns nil after [Stmt.Reset] is called. +// +// https://www.sqlite.org/c3ref/step.html func (s *Stmt) Err() error { return s.err } +// Exec is a convenience function that repeatedly calls [Stmt.Step] until it returns false, +// then calls [Stmt.Reset] to reset the statement and get any error that occurred. func (s *Stmt) Exec() error { for s.Step() { } - err := s.Err() - if rerr := s.Reset(); err == nil { - err = rerr - } - return err + return s.Reset() } +// BindBool binds a bool to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// SQLite does not have a separate boolean storage class. +// Instead, boolean values are stored as integers 0 (false) and 1 (true). +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindBool(param int, value bool) error { if value { return s.BindInt64(param, 1) @@ -66,10 +108,18 @@ func (s *Stmt) BindBool(param int, value bool) error { return s.BindInt64(param, 0) } +// BindInt binds an int to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindInt(param int, value int) error { return s.BindInt64(param, int64(value)) } +// BindInt64 binds an int64 to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindInt64(param int, value int64) error { r, err := s.c.api.bindInteger.Call(s.c.ctx, uint64(s.handle), uint64(param), uint64(value)) @@ -79,6 +129,10 @@ func (s *Stmt) BindInt64(param int, value int64) error { return s.c.error(r[0]) } +// BindFloat binds a float64 to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindFloat(param int, value float64) error { r, err := s.c.api.bindFloat.Call(s.c.ctx, uint64(s.handle), uint64(param), math.Float64bits(value)) @@ -88,6 +142,10 @@ func (s *Stmt) BindFloat(param int, value float64) error { return s.c.error(r[0]) } +// BindText binds a string to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindText(param int, value string) error { ptr := s.c.newString(value) r, err := s.c.api.bindText.Call(s.c.ctx, @@ -100,6 +158,11 @@ func (s *Stmt) BindText(param int, value string) error { return s.c.error(r[0]) } +// BindBlob binds a []byte to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// Binding a nil slice is the same as calling [Stmt.BindNull]. +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindBlob(param int, value []byte) error { ptr := s.c.newBytes(value) r, err := s.c.api.bindBlob.Call(s.c.ctx, @@ -112,6 +175,10 @@ func (s *Stmt) BindBlob(param int, value []byte) error { return s.c.error(r[0]) } +// BindNull binds a NULL to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindNull(param int) error { r, err := s.c.api.bindNull.Call(s.c.ctx, uint64(s.handle), uint64(param)) @@ -121,6 +188,10 @@ func (s *Stmt) BindNull(param int) error { return s.c.error(r[0]) } +// ColumnType returns the initial [Datatype] of the result column. +// The leftmost column of the result set has the index 0. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnType(col int) Datatype { r, err := s.c.api.columnType.Call(s.c.ctx, uint64(s.handle), uint64(col)) @@ -130,6 +201,13 @@ func (s *Stmt) ColumnType(col int) Datatype { return Datatype(r[0]) } +// ColumnBool returns the value of the result column as a bool. +// The leftmost column of the result set has the index 0. +// SQLite does not have a separate boolean storage class. +// Instead, boolean values are retrieved as integers, +// with 0 converted to false and any other value to true. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnBool(col int) bool { if i := s.ColumnInt64(col); i != 0 { return true @@ -137,10 +215,18 @@ func (s *Stmt) ColumnBool(col int) bool { return false } +// ColumnInt returns the value of the result column as an int. +// The leftmost column of the result set has the index 0. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnInt(col int) int { return int(s.ColumnInt64(col)) } +// ColumnInt64 returns the value of the result column as an int64. +// The leftmost column of the result set has the index 0. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnInt64(col int) int64 { r, err := s.c.api.columnInteger.Call(s.c.ctx, uint64(s.handle), uint64(col)) @@ -150,6 +236,10 @@ func (s *Stmt) ColumnInt64(col int) int64 { return int64(r[0]) } +// ColumnFloat returns the value of the result column as a float64. +// The leftmost column of the result set has the index 0. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnFloat(col int) float64 { r, err := s.c.api.columnFloat.Call(s.c.ctx, uint64(s.handle), uint64(col)) @@ -159,6 +249,10 @@ func (s *Stmt) ColumnFloat(col int) float64 { return math.Float64frombits(r[0]) } +// ColumnText returns the value of the result column as a string. +// The leftmost column of the result set has the index 0. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnText(col int) string { r, err := s.c.api.columnText.Call(s.c.ctx, uint64(s.handle), uint64(col)) @@ -186,6 +280,11 @@ func (s *Stmt) ColumnText(col int) string { return string(mem) } +// ColumnBlob appends to buf and returns +// the value of the result column as a []byte. +// The leftmost column of the result set has the index 0. +// +// https://www.sqlite.org/c3ref/column_blob.html func (s *Stmt) ColumnBlob(col int, buf []byte) []byte { r, err := s.c.api.columnBlob.Call(s.c.ctx, uint64(s.handle), uint64(col)) diff --git a/stmt_test.go b/stmt_test.go index cf6dc2c..3acc319 100644 --- a/stmt_test.go +++ b/stmt_test.go @@ -33,6 +33,16 @@ func TestStmt(t *testing.T) { t.Fatal(err) } + err = stmt.ClearBindings() + if err != nil { + t.Fatal(err) + } + + err = stmt.Exec() + if err != nil { + t.Fatal(err) + } + err = stmt.BindBool(1, true) if err != nil { t.Fatal(err) @@ -118,7 +128,7 @@ func TestStmt(t *testing.T) { t.Fatal(err) } - // The table should have: 0, 1, 2, π, NULL, "", "text", `blob`, NULL + // The table should have: 0, NULL, 1, 2, π, NULL, "", "text", `blob`, NULL stmt, _, err = db.Prepare(`SELECT col FROM test`) if err != nil { t.Fatal(err) @@ -145,6 +155,27 @@ func TestStmt(t *testing.T) { } } + if stmt.Step() { + if got := stmt.ColumnType(0); got != NULL { + t.Errorf("got %v, want NULL", got) + } + if got := stmt.ColumnBool(0); got != false { + t.Errorf("got %v, want false", got) + } + if got := stmt.ColumnInt(0); got != 0 { + t.Errorf("got %v, want zero", got) + } + if got := stmt.ColumnFloat(0); got != 0 { + t.Errorf("got %v, want zero", got) + } + if got := stmt.ColumnText(0); got != "" { + t.Errorf("got %q, want empty", got) + } + if got := stmt.ColumnBlob(0, nil); got != nil { + t.Errorf("got %q, want nil", got) + } + } + if stmt.Step() { if got := stmt.ColumnType(0); got != INTEGER { t.Errorf("got %v, want INTEGER", got) @@ -323,3 +354,8 @@ func TestStmt(t *testing.T) { t.Fatal(err) } } + +func TestStmt_Close(t *testing.T) { + var stmt *Stmt + stmt.Close() +}