diff --git a/context.go b/context.go new file mode 100644 index 0000000..7e7f252 --- /dev/null +++ b/context.go @@ -0,0 +1,121 @@ +package sqlite3 + +import ( + "math" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Context is the context in which an SQL function executes. +// +// https://www.sqlite.org/c3ref/context.html +type Context struct { + c *Conn + handle uint32 +} + +// ResultBool sets the result of the function to a bool. +// 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/result_blob.html +func (c *Context) ResultBool(value bool) { + var i int64 + if value { + i = 1 + } + c.ResultInt64(i) +} + +// ResultInt sets the result of the function to an int. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultInt(value int) { + c.ResultInt64(int64(value)) +} + +// ResultInt64 sets the result of the function to an int64. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultInt64(value int64) { + c.c.call(c.c.api.resultInteger, + uint64(c.handle), uint64(value)) +} + +// ResultFloat sets the result of the function to a float64. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultFloat(value float64) { + c.c.call(c.c.api.resultFloat, + uint64(c.handle), math.Float64bits(value)) +} + +// ResultText sets the result of the function to a string. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultText(value string) { + ptr := c.c.newString(value) + c.c.call(c.c.api.resultText, + uint64(c.handle), uint64(ptr), uint64(len(value)), + uint64(c.c.api.destructor), _UTF8) +} + +// ResultBlob sets the result of the function to a []byte. +// Returning a nil slice is the same as calling [Context.ResultNull]. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultBlob(value []byte) { + ptr := c.c.newBytes(value) + c.c.call(c.c.api.resultBlob, + uint64(c.handle), uint64(ptr), uint64(len(value)), + uint64(c.c.api.destructor)) +} + +// BindZeroBlob sets the result of the function to a zero-filled, length n BLOB. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultZeroBlob(n int64) { + c.c.call(c.c.api.resultZeroBlob, + uint64(c.handle), uint64(n)) +} + +// ResultNull sets the result of the function to NULL. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultNull() { + c.c.call(c.c.api.resultNull, + uint64(c.handle)) +} + +// ResultTime sets the result of the function to a [time.Time]. +// +// https://www.sqlite.org/c3ref/result_blob.html +func (c *Context) ResultTime(value time.Time, format TimeFormat) { + if format == TimeFormatDefault { + c.resultRFC3339Nano(value) + return + } + switch v := format.Encode(value).(type) { + case string: + c.ResultText(v) + case int64: + c.ResultInt64(v) + case float64: + c.ResultFloat(v) + default: + panic(util.AssertErr()) + } +} + +func (c *Context) resultRFC3339Nano(value time.Time) { + const maxlen = uint64(len(time.RFC3339Nano)) + + ptr := c.c.new(maxlen) + buf := util.View(c.c.mod, ptr, maxlen) + buf = value.AppendFormat(buf[:0], time.RFC3339Nano) + + c.c.call(c.c.api.resultText, + uint64(c.handle), uint64(ptr), uint64(len(buf)), + uint64(c.c.api.destructor), _UTF8) +} diff --git a/embed/exports.txt b/embed/exports.txt index d14ae18..70e5e3e 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -33,10 +33,10 @@ sqlite3_column_blob sqlite3_column_bytes sqlite3_blob_open sqlite3_blob_close +sqlite3_blob_reopen sqlite3_blob_bytes sqlite3_blob_read sqlite3_blob_write -sqlite3_blob_reopen sqlite3_backup_init sqlite3_backup_step sqlite3_backup_finish diff --git a/module.go b/module.go index 0c4a554..45f9660 100644 --- a/module.go +++ b/module.go @@ -162,6 +162,16 @@ func newModule(mod api.Module) (m *module, err error) { valueText: getFun("sqlite3_value_text"), valueBlob: getFun("sqlite3_value_blob"), valueBytes: getFun("sqlite3_value_bytes"), + resultNull: getFun("sqlite3_result_null"), + resultInteger: getFun("sqlite3_result_int64"), + resultFloat: getFun("sqlite3_result_double"), + resultText: getFun("sqlite3_result_text64"), + resultBlob: getFun("sqlite3_result_blob64"), + resultZeroBlob: getFun("sqlite3_result_zeroblob64"), + resultError: getFun("sqlite3_result_error"), + resultErrorCode: getFun("sqlite3_result_error_code"), + resultErrorMem: getFun("sqlite3_result_error_nomem"), + resultErrorBig: getFun("sqlite3_result_error_toobig"), } if err != nil { return nil, err @@ -326,10 +336,10 @@ type sqliteAPI struct { step api.Function exec api.Function clearBindings api.Function - bindNull api.Function bindCount api.Function bindIndex api.Function bindName api.Function + bindNull api.Function bindInteger api.Function bindFloat api.Function bindText api.Function @@ -364,5 +374,15 @@ type sqliteAPI struct { valueText api.Function valueBlob api.Function valueBytes api.Function + resultNull api.Function + resultInteger api.Function + resultFloat api.Function + resultText api.Function + resultBlob api.Function + resultZeroBlob api.Function + resultError api.Function + resultErrorCode api.Function + resultErrorMem api.Function + resultErrorBig api.Function destructor uint32 } diff --git a/stmt.go b/stmt.go index 6614252..c26de44 100644 --- a/stmt.go +++ b/stmt.go @@ -131,10 +131,11 @@ func (s *Stmt) BindName(param int) string { // // https://www.sqlite.org/c3ref/bind_blob.html func (s *Stmt) BindBool(param int, value bool) error { + var i int64 if value { - return s.BindInt64(param, 1) + i = 1 } - return s.BindInt64(param, 0) + return s.BindInt64(param, i) } // BindInt binds an int to the prepared statement.