diff --git a/module.go b/module.go index cf6358e..0c4a554 100644 --- a/module.go +++ b/module.go @@ -156,6 +156,12 @@ func newModule(mod api.Module) (m *module, err error) { lastRowid: getFun("sqlite3_last_insert_rowid"), autocommit: getFun("sqlite3_get_autocommit"), createCollation: getFun("sqlite3_create_go_collation"), + valueType: getFun("sqlite3_value_type"), + valueInteger: getFun("sqlite3_value_int64"), + valueFloat: getFun("sqlite3_value_double"), + valueText: getFun("sqlite3_value_text"), + valueBlob: getFun("sqlite3_value_blob"), + valueBytes: getFun("sqlite3_value_bytes"), } if err != nil { return nil, err @@ -352,5 +358,11 @@ type sqliteAPI struct { lastRowid api.Function autocommit api.Function createCollation api.Function + valueType api.Function + valueInteger api.Function + valueFloat api.Function + valueText api.Function + valueBlob api.Function + valueBytes api.Function destructor uint32 } diff --git a/stmt.go b/stmt.go index 2fae0b4..6614252 100644 --- a/stmt.go +++ b/stmt.go @@ -374,18 +374,7 @@ func (s *Stmt) ColumnBlob(col int, buf []byte) []byte { func (s *Stmt) ColumnRawText(col int) []byte { r := s.c.call(s.c.api.columnText, uint64(s.handle), uint64(col)) - - ptr := uint32(r) - if ptr == 0 { - r = s.c.call(s.c.api.errcode, uint64(s.c.handle)) - s.err = s.c.error(r) - return nil - } - - r = s.c.call(s.c.api.columnBytes, - uint64(s.handle), uint64(col)) - - return util.View(s.c.mod, ptr, r) + return s.columnRawBytes(col, uint32(r)) } // ColumnRawBlob returns the value of the result column as a []byte. @@ -397,17 +386,18 @@ func (s *Stmt) ColumnRawText(col int) []byte { func (s *Stmt) ColumnRawBlob(col int) []byte { r := s.c.call(s.c.api.columnBlob, uint64(s.handle), uint64(col)) + return s.columnRawBytes(col, uint32(r)) +} - ptr := uint32(r) +func (s *Stmt) columnRawBytes(col int, ptr uint32) []byte { if ptr == 0 { - r = s.c.call(s.c.api.errcode, uint64(s.c.handle)) + r := s.c.call(s.c.api.errcode, uint64(s.c.handle)) s.err = s.c.error(r) return nil } - r = s.c.call(s.c.api.columnBytes, + r := s.c.call(s.c.api.columnBytes, uint64(s.handle), uint64(col)) - return util.View(s.c.mod, ptr, r) } diff --git a/value.go b/value.go new file mode 100644 index 0000000..db61819 --- /dev/null +++ b/value.go @@ -0,0 +1,131 @@ +package sqlite3 + +import ( + "math" + "time" + + "github.com/ncruces/go-sqlite3/internal/util" +) + +// Value is any value that can be stored in a database table. +// +// https://www.sqlite.org/c3ref/value.html +type Value struct { + c *Conn + handle uint32 +} + +// Type returns the initial [Datatype] of the value. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Type() Datatype { + r := v.c.call(v.c.api.valueType, uint64(v.handle)) + return Datatype(r) +} + +// Bool returns the value as a bool. +// 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/value_blob.html +func (v *Value) Bool() bool { + if i := v.Int64(); i != 0 { + return true + } + return false +} + +// Int returns the value as an int. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Int() int { + return int(v.Int64()) +} + +// Int64 returns the value as an int64. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Int64() int64 { + r := v.c.call(v.c.api.valueInteger, uint64(v.handle)) + return int64(r) +} + +// Float returns the value as a float64. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Float() float64 { + r := v.c.call(v.c.api.valueFloat, uint64(v.handle)) + return math.Float64frombits(r) +} + +// Time returns the value as a [time.Time]. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Time(format TimeFormat) (time.Time, error) { + var t any + var err error + switch v.Type() { + case INTEGER: + t = v.Int64() + case FLOAT: + t = v.Float() + case TEXT, BLOB: + t, err = v.Text() + if err != nil { + return time.Time{}, err + } + case NULL: + return time.Time{}, nil + default: + panic(util.AssertErr()) + } + return format.Decode(t) +} + +// Text returns the value as a string. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Text() (string, error) { + r, err := v.RawText() + return string(r), err +} + +// Blob appends to buf and returns +// the value as a []byte. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) Blob(buf []byte) ([]byte, error) { + r, err := v.RawBlob() + return append(buf, r...), err +} + +// RawText returns the value as a []byte. +// The []byte is owned by SQLite and may be invalidated by +// subsequent calls to [Value] methods. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) RawText() ([]byte, error) { + r := v.c.call(v.c.api.valueText, uint64(v.handle)) + return v.rawBytes(uint32(r)) +} + +// RawBlob returns the value as a []byte. +// The []byte is owned by SQLite and may be invalidated by +// subsequent calls to [Value] methods. +// +// https://www.sqlite.org/c3ref/value_blob.html +func (v *Value) RawBlob() ([]byte, error) { + r := v.c.call(v.c.api.valueBlob, uint64(v.handle)) + return v.rawBytes(uint32(r)) +} + +func (v *Value) rawBytes(ptr uint32) ([]byte, error) { + if ptr == 0 { + r := v.c.call(v.c.api.errcode, uint64(v.c.handle)) + return nil, v.c.error(r) + } + + r := v.c.call(v.c.api.valueBytes, uint64(v.handle)) + return util.View(v.c.mod, ptr, r), nil +}