diff --git a/embed/exports.txt b/embed/exports.txt index 1d20aae..a98d79c 100644 --- a/embed/exports.txt +++ b/embed/exports.txt @@ -26,6 +26,7 @@ sqlite3_bind_text64 sqlite3_bind_blob64 sqlite3_bind_zeroblob64 sqlite3_bind_pointer_go +sqlite3_bind_value sqlite3_column_count sqlite3_column_name sqlite3_column_type @@ -34,6 +35,7 @@ sqlite3_column_double sqlite3_column_text sqlite3_column_blob sqlite3_column_bytes +sqlite3_column_value sqlite3_blob_open sqlite3_blob_close sqlite3_blob_reopen diff --git a/embed/sqlite3.wasm b/embed/sqlite3.wasm index b9568c1..54e9162 100755 Binary files a/embed/sqlite3.wasm and b/embed/sqlite3.wasm differ diff --git a/sqlite.go b/sqlite.go index 3f4c2c8..b51f701 100644 --- a/sqlite.go +++ b/sqlite.go @@ -133,6 +133,7 @@ func instantiateSQLite() (sqlt *sqlite, err error) { bindBlob: getFun("sqlite3_bind_blob64"), bindZeroBlob: getFun("sqlite3_bind_zeroblob64"), bindPointer: getFun("sqlite3_bind_pointer_go"), + bindValue: getFun("sqlite3_bind_value"), columnCount: getFun("sqlite3_column_count"), columnName: getFun("sqlite3_column_name"), columnType: getFun("sqlite3_column_type"), @@ -141,6 +142,7 @@ func instantiateSQLite() (sqlt *sqlite, err error) { columnText: getFun("sqlite3_column_text"), columnBlob: getFun("sqlite3_column_blob"), columnBytes: getFun("sqlite3_column_bytes"), + columnValue: getFun("sqlite3_column_value"), blobOpen: getFun("sqlite3_blob_open"), blobClose: getFun("sqlite3_blob_close"), blobReopen: getFun("sqlite3_blob_reopen"), @@ -367,6 +369,7 @@ type sqliteAPI struct { bindBlob api.Function bindZeroBlob api.Function bindPointer api.Function + bindValue api.Function columnCount api.Function columnName api.Function columnType api.Function @@ -375,6 +378,7 @@ type sqliteAPI struct { columnText api.Function columnBlob api.Function columnBytes api.Function + columnValue api.Function blobOpen api.Function blobClose api.Function blobReopen api.Function diff --git a/stmt.go b/stmt.go index 38cfd72..a0b06a3 100644 --- a/stmt.go +++ b/stmt.go @@ -297,6 +297,19 @@ func (s *Stmt) BindJSON(param int, value any) error { return s.BindRawText(param, data) } +// BindValue binds a copy of value to the prepared statement. +// The leftmost SQL parameter has an index of 1. +// +// https://sqlite.org/c3ref/bind_blob.html +func (s *Stmt) BindValue(param int, value Value) error { + if value.sqlite != s.c.sqlite { + return MISUSE + } + r := s.c.call(s.c.api.bindValue, + uint64(s.handle), uint64(param), uint64(value.handle)) + return s.c.error(r) +} + // ColumnCount returns the number of columns in a result set. // // https://sqlite.org/c3ref/column_count.html @@ -475,6 +488,20 @@ func (s *Stmt) ColumnJSON(col int, ptr any) error { return json.Unmarshal(data, ptr) } +// ColumnValue returns the unprotected value of the result column. +// The leftmost column of the result set has the index 0. +// +// https://sqlite.org/c3ref/column_blob.html +func (s *Stmt) ColumnValue(col int) Value { + r := s.c.call(s.c.api.columnValue, + uint64(s.handle), uint64(col)) + return Value{ + unprot: true, + sqlite: s.c.sqlite, + handle: uint32(r), + } +} + // Return true if stmt is an empty SQL statement. // This is used as an optimization. // It's OK to always return false here. diff --git a/value.go b/value.go index 4c7f304..1fae899 100644 --- a/value.go +++ b/value.go @@ -15,13 +15,21 @@ import ( type Value struct { *sqlite handle uint32 + unprot bool +} + +func (v Value) protected() uint64 { + if v.unprot { + panic(util.ValueErr) + } + return uint64(v.handle) } // Type returns the initial [Datatype] of the value. // // https://sqlite.org/c3ref/value_blob.html func (v Value) Type() Datatype { - r := v.call(v.api.valueType, uint64(v.handle)) + r := v.call(v.api.valueType, v.protected()) return Datatype(r) } @@ -49,7 +57,7 @@ func (v Value) Int() int { // // https://sqlite.org/c3ref/value_blob.html func (v Value) Int64() int64 { - r := v.call(v.api.valueInteger, uint64(v.handle)) + r := v.call(v.api.valueInteger, v.protected()) return int64(r) } @@ -57,7 +65,7 @@ func (v Value) Int64() int64 { // // https://sqlite.org/c3ref/value_blob.html func (v Value) Float() float64 { - r := v.call(v.api.valueFloat, uint64(v.handle)) + r := v.call(v.api.valueFloat, v.protected()) return math.Float64frombits(r) } @@ -103,7 +111,7 @@ func (v Value) Blob(buf []byte) []byte { // // https://sqlite.org/c3ref/value_blob.html func (v Value) RawText() []byte { - r := v.call(v.api.valueText, uint64(v.handle)) + r := v.call(v.api.valueText, v.protected()) return v.rawBytes(uint32(r)) } @@ -113,7 +121,7 @@ func (v Value) RawText() []byte { // // https://sqlite.org/c3ref/value_blob.html func (v Value) RawBlob() []byte { - r := v.call(v.api.valueBlob, uint64(v.handle)) + r := v.call(v.api.valueBlob, v.protected()) return v.rawBytes(uint32(r)) } @@ -122,14 +130,14 @@ func (v Value) rawBytes(ptr uint32) []byte { return nil } - r := v.call(v.api.valueBytes, uint64(v.handle)) + r := v.call(v.api.valueBytes, v.protected()) return util.View(v.mod, ptr, r) } // Pointer gets the pointer associated with this value, // or nil if it has no associated pointer. func (v Value) Pointer() any { - r := v.call(v.api.valuePointer, uint64(v.handle)) + r := v.call(v.api.valuePointer, v.protected()) return util.GetHandle(v.ctx, uint32(r)) } diff --git a/vtab.go b/vtab.go index 4d452ef..d260043 100644 --- a/vtab.go +++ b/vtab.go @@ -254,15 +254,15 @@ type IndexConstraintUsage struct { // if the right-hand operand is known. // // https://sqlite.org/c3ref/vtab_rhs_value.html -func (idx *IndexInfo) RHSValue(column int) (*Value, error) { +func (idx *IndexInfo) RHSValue(column int) (Value, error) { defer idx.c.arena.mark()() valPtr := idx.c.arena.new(ptrlen) r := idx.c.call(idx.c.api.vtabRHSValue, uint64(idx.handle), uint64(column), uint64(valPtr)) if err := idx.c.error(r); err != nil { - return nil, err + return Value{}, err } - return &Value{ + return Value{ sqlite: idx.c.sqlite, handle: util.ReadUint32(idx.c.mod, valPtr), }, nil