diff --git a/backup.go b/backup.go index 603e51d..e4a40e3 100644 --- a/backup.go +++ b/backup.go @@ -120,8 +120,7 @@ func (b *Backup) Step(nPage int) (done bool, err error) { // // https://sqlite.org/c3ref/backup_finish.html#sqlite3backupremaining func (b *Backup) Remaining() int { - n := int32(b.c.wrp.Xsqlite3_backup_remaining(int32(b.handle))) - return int(n) + return int(b.c.wrp.Xsqlite3_backup_remaining(int32(b.handle))) } // PageCount returns the total number of pages in the source database @@ -129,6 +128,5 @@ func (b *Backup) Remaining() int { // // https://sqlite.org/c3ref/backup_finish.html#sqlite3backuppagecount func (b *Backup) PageCount() int { - n := int32(b.c.wrp.Xsqlite3_backup_pagecount(int32(b.handle))) - return int(n) + return int(b.c.wrp.Xsqlite3_backup_pagecount(int32(b.handle))) } diff --git a/config.go b/config.go index efb6bfb..2098ecc 100644 --- a/config.go +++ b/config.go @@ -146,7 +146,7 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro rc = res_t(c.wrp.Xsqlite3_file_control( int32(c.handle), int32(schemaPtr), int32(op), int32(ptr))) - ret = uint32(c.wrp.Read32(ptr)) + ret = c.wrp.Read32(ptr) case FCNTL_LOCKSTATE: rc = res_t(c.wrp.Xsqlite3_file_control( @@ -193,8 +193,7 @@ func (c *Conn) FileControl(schema string, op FcntlOpcode, arg ...any) (any, erro // // https://sqlite.org/c3ref/limit.html func (c *Conn) Limit(id LimitCategory, value int) int { - v := int32(c.wrp.Xsqlite3_limit(int32(c.handle), int32(id), int32(value))) - return int(v) + return int(c.wrp.Xsqlite3_limit(int32(c.handle), int32(id), int32(value))) } // SetAuthorizer registers an authorizer callback with the database connection. diff --git a/conn.go b/conn.go index c9e2e72..7384e66 100644 --- a/conn.go +++ b/conn.go @@ -34,6 +34,7 @@ type Conn struct { update func(AuthorizerActionCode, string, string, int64) commit func() bool rollback func() + preupdate func(PreUpdateData) busy1st time.Time busylst time.Time @@ -506,7 +507,7 @@ func (c *Conn) errorFor(handle ptr_t, rc res_t, sql ...string) error { } if len(sql) != 0 { - if i := int32(c.wrp.Xsqlite3_error_offset(int32(handle))); i != -1 { + if i := c.wrp.Xsqlite3_error_offset(int32(handle)); i != -1 { query = sql[0][i:] } } diff --git a/context.go b/context.go index 12fb4cd..e2c97d8 100644 --- a/context.go +++ b/context.go @@ -217,6 +217,6 @@ func (ctx Context) ResultSubtype(t uint) { // // https://sqlite.org/c3ref/vtab_nochange.html func (ctx Context) VTabNoChange() bool { - b := int32(ctx.c.wrp.Xsqlite3_vtab_nochange(int32(ctx.handle))) + b := ctx.c.wrp.Xsqlite3_vtab_nochange(int32(ctx.handle)) return b != 0 } diff --git a/go.mod b/go.mod index c4138c4..cf134d0 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/ncruces/go-sqlite3 go 1.25.0 require ( - github.com/ncruces/go-sqlite3-wasm/v3 v3.0.35302 + github.com/ncruces/go-sqlite3-wasm/v3 v3.0.35303-0.20260608094803-e16536efb9dc github.com/ncruces/julianday v1.0.0 github.com/ncruces/sort v1.0.0 github.com/ncruces/wbt v1.0.0 diff --git a/go.sum b/go.sum index e2abd21..f60a29f 100644 --- a/go.sum +++ b/go.sum @@ -2,8 +2,8 @@ github.com/dchest/siphash v1.2.3 h1:QXwFc8cFOR2dSa/gE6o/HokBMWtLUaNDVd+22aKHeEA= github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/ncruces/go-sqlite3-wasm/v3 v3.0.35302 h1:pNeiMr2t0AIXxmiJ04C8JunmlNaPFLN9YoKF9LustUQ= -github.com/ncruces/go-sqlite3-wasm/v3 v3.0.35302/go.mod h1:WwFR2jWecdTSNSADwPfY34FeZ6OJIpDEzi6QZAoXd4w= +github.com/ncruces/go-sqlite3-wasm/v3 v3.0.35303-0.20260608094803-e16536efb9dc h1:rERx6R19XjRPF6LWou0KpsEPE5OkyQtZQwTMU7t8TBA= +github.com/ncruces/go-sqlite3-wasm/v3 v3.0.35303-0.20260608094803-e16536efb9dc/go.mod h1:WwFR2jWecdTSNSADwPfY34FeZ6OJIpDEzi6QZAoXd4w= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g= github.com/ncruces/sort v1.0.0 h1:tXeitUzE3B8I2z+rsqbKUBThBlJJ34glqCDP0XReJmk= diff --git a/stmt.go b/stmt.go index 17359b2..894df9a 100644 --- a/stmt.go +++ b/stmt.go @@ -72,7 +72,7 @@ func (s *Stmt) ExpandedSQL() string { // // https://sqlite.org/c3ref/stmt_readonly.html func (s *Stmt) ReadOnly() bool { - b := int32(s.c.wrp.Xsqlite3_stmt_readonly(int32(s.handle))) + b := s.c.wrp.Xsqlite3_stmt_readonly(int32(s.handle)) return b != 0 } @@ -151,9 +151,7 @@ func (s *Stmt) Status(op StmtStatus, reset bool) int { if reset { i = 1 } - n := int32(s.c.wrp.Xsqlite3_stmt_status(int32(s.handle), - int32(op), i)) - return int(n) + return int(s.c.wrp.Xsqlite3_stmt_status(int32(s.handle), int32(op), i)) } // ClearBindings resets all bindings on the prepared statement. @@ -168,9 +166,7 @@ func (s *Stmt) ClearBindings() error { // // https://sqlite.org/c3ref/bind_parameter_count.html func (s *Stmt) BindCount() int { - n := int32(s.c.wrp.Xsqlite3_bind_parameter_count( - int32(s.handle))) - return int(n) + return int(s.c.wrp.Xsqlite3_bind_parameter_count(int32(s.handle))) } // BindIndex returns the index of a parameter in the prepared statement @@ -180,9 +176,7 @@ func (s *Stmt) BindCount() int { func (s *Stmt) BindIndex(name string) int { defer s.c.arena.Mark()() namePtr := s.c.arena.String(name) - i := int32(s.c.wrp.Xsqlite3_bind_parameter_index( - int32(s.handle), int32(namePtr))) - return int(i) + return int(s.c.wrp.Xsqlite3_bind_parameter_index(int32(s.handle), int32(namePtr))) } // BindName returns the name of a parameter in the prepared statement. @@ -365,18 +359,14 @@ func (s *Stmt) BindValue(param int, value Value) error { // // https://sqlite.org/c3ref/data_count.html func (s *Stmt) DataCount() int { - n := int32(s.c.wrp.Xsqlite3_data_count( - int32(s.handle))) - return int(n) + return int(s.c.wrp.Xsqlite3_data_count(int32(s.handle))) } // ColumnCount returns the number of columns in a result set. // // https://sqlite.org/c3ref/column_count.html func (s *Stmt) ColumnCount() int { - n := int32(s.c.wrp.Xsqlite3_column_count( - int32(s.handle))) - return int(n) + return int(s.c.wrp.Xsqlite3_column_count(int32(s.handle))) } // ColumnName returns the name of the result column. @@ -568,8 +558,7 @@ func (s *Stmt) columnRawBytes(col int, ptr ptr_t, nul int32) []byte { return nil } - n := int32(s.c.wrp.Xsqlite3_column_bytes( - int32(s.handle), int32(col))) + n := s.c.wrp.Xsqlite3_column_bytes(int32(s.handle), int32(col)) return s.c.wrp.Bytes(ptr, int64(n+nul))[:n] } diff --git a/tests/txn_test.go b/tests/txn_test.go index 254ea0a..ec9b320 100644 --- a/tests/txn_test.go +++ b/tests/txn_test.go @@ -22,6 +22,23 @@ func TestConn_Transaction_exec(t *testing.T) { db.RollbackHook(func() {}) db.CommitHook(func() bool { return true }) db.UpdateHook(func(sqlite3.AuthorizerActionCode, string, string, int64) {}) + db.PreUpdateHook(func(pud sqlite3.PreUpdateData) { + if got := pud.Count(); got != 1 { + t.Errorf("got %d, want 1", got) + } + if got := pud.Depth(); got != 0 { + t.Errorf("got %d, want 0", got) + } + if got := pud.BlobWrite(); got != -1 { + t.Errorf("got %d, want -1", got) + } + if _, err := pud.New(0); err != nil { + t.Error(err) + } + if _, err := pud.Old(0); !errors.Is(err, sqlite3.MISUSE) { + t.Error(err) + } + }) err = db.Exec(`CREATE TABLE test (col)`) if err != nil { diff --git a/txn.go b/txn.go index 63aa520..58d7583 100644 --- a/txn.go +++ b/txn.go @@ -255,7 +255,7 @@ func (c *Conn) RollbackHook(cb func()) { // whenever a row is updated, inserted or deleted in a rowid table. // // https://sqlite.org/c3ref/update_hook.html -func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table string, rowid int64)) { +func (c *Conn) UpdateHook(cb func(op AuthorizerActionCode, schema, table string, rowid int64)) { var enable int32 if cb != nil { enable = 1 @@ -264,6 +264,19 @@ func (c *Conn) UpdateHook(cb func(action AuthorizerActionCode, schema, table str c.update = cb } +// PreUpdateHook registers a callback function that is invoked prior +// to each INSERT, UPDATE, and DELETE operation on a database table. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +func (c *Conn) PreUpdateHook(cb func(PreUpdateData)) { + var enable int32 + if cb != nil { + enable = 1 + } + c.wrp.Xsqlite3_preupdate_hook_go(int32(c.handle), enable) + c.preupdate = cb +} + func (e *env) Xgo_commit_hook(pDB int32) (rollback int32) { if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.commit != nil { if !c.commit() { @@ -279,11 +292,24 @@ func (e *env) Xgo_rollback_hook(pDB int32) { } } -func (e *env) Xgo_update_hook(pDB, action, zSchema, zTabName int32, rowid int64) { +func (e *env) Xgo_update_hook(pDB, op, zSchema, zTabName int32, rowid int64) { if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.update != nil { schema := e.ReadString(ptr_t(zSchema), _MAX_NAME) table := e.ReadString(ptr_t(zTabName), _MAX_NAME) - c.update(AuthorizerActionCode(action), schema, table, rowid) + c.update(AuthorizerActionCode(op), schema, table, rowid) + } +} + +func (e *env) Xgo_preupdate_hook(_, pDB, op, zSchema, zTabName int32, oldRowID, newRowID int64) { + if c, ok := e.DB.(*Conn); ok && c.handle == ptr_t(pDB) && c.preupdate != nil { + c.preupdate(PreUpdateData{ + c: c, + Op: AuthorizerActionCode(op), + Schema: e.ReadString(ptr_t(zSchema), _MAX_NAME), + Table: e.ReadString(ptr_t(zTabName), _MAX_NAME), + OldRowID: oldRowID, + NewRowID: newRowID, + }) } } @@ -294,3 +320,55 @@ func (c *Conn) CacheFlush() error { rc := res_t(c.wrp.Xsqlite3_db_cacheflush(int32(c.handle))) return c.error(rc) } + +// PreUpdateData provides information about a preupdate event. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +type PreUpdateData struct { + c *Conn + Op AuthorizerActionCode + Schema string + Table string + OldRowID int64 + NewRowID int64 +} + +// Conn returns the database connection associated with the preupdate event. +func (pud *PreUpdateData) Conn() *Conn { + return pud.c +} + +// Count returns the number of columns in the row that is being inserted, updated, or deleted. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +func (pud *PreUpdateData) Count() int { + return int(pud.c.wrp.Xsqlite3_preupdate_count(int32(pud.c.handle))) +} + +// Depth returns the trigger depth of the insert, update, or delete operation. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +func (pud *PreUpdateData) Depth() int { + return int(pud.c.wrp.Xsqlite3_preupdate_depth(int32(pud.c.handle))) +} + +// BlobWrite returns the index of the column being written to using [Blob]. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +func (pud *PreUpdateData) BlobWrite() int { + return int(pud.c.wrp.Xsqlite3_preupdate_blobwrite(int32(pud.c.handle))) +} + +// Old returns the value of a column of the table row before it is updated. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +func (pud *PreUpdateData) Old(column int) (Value, error) { + return pud.c.columnValue(pud.c.wrp.Xsqlite3_preupdate_old, pud.c.handle, column) +} + +// New returns the value of a column of the table row after it is updated. +// +// https://sqlite.org/c3ref/preupdate_blobwrite.html +func (pud *PreUpdateData) New(column int) (Value, error) { + return pud.c.columnValue(pud.c.wrp.Xsqlite3_preupdate_new, pud.c.handle, column) +} diff --git a/value.go b/value.go index 03b2cc8..07b9c4d 100644 --- a/value.go +++ b/value.go @@ -147,7 +147,7 @@ func (v Value) rawBytes(ptr ptr_t, nul int32) []byte { return nil } - n := int32(v.c.wrp.Xsqlite3_value_bytes(int32(v.handle))) + n := v.c.wrp.Xsqlite3_value_bytes(int32(v.handle)) return v.c.wrp.Bytes(ptr, int64(n+nul))[:n] } @@ -163,7 +163,7 @@ func (v Value) Pointer() any { // // https://sqlite.org/c3ref/value_blob.html func (v Value) NoChange() bool { - b := int32(v.c.wrp.Xsqlite3_value_nochange(int32(v.handle))) + b := v.c.wrp.Xsqlite3_value_nochange(int32(v.handle)) return b != 0 } @@ -171,7 +171,7 @@ func (v Value) NoChange() bool { // // https://sqlite.org/c3ref/value_blob.html func (v Value) FromBind() bool { - b := int32(v.c.wrp.Xsqlite3_value_frombind(int32(v.handle))) + b := v.c.wrp.Xsqlite3_value_frombind(int32(v.handle)) return b != 0 } @@ -180,16 +180,7 @@ func (v Value) FromBind() bool { // // https://sqlite.org/c3ref/vtab_in_first.html func (v Value) InFirst() (Value, error) { - defer v.c.arena.Mark()() - valPtr := v.c.arena.New(ptrlen) - rc := res_t(v.c.wrp.Xsqlite3_vtab_in_first(int32(v.handle), int32(valPtr))) - if err := v.c.error(rc); err != nil { - return Value{}, err - } - return Value{ - c: v.c, - handle: ptr_t(v.c.wrp.Read32(valPtr)), - }, nil + return v.c.returnValue(v.c.wrp.Xsqlite3_vtab_in_first, v.handle) } // InNext returns the next element @@ -197,14 +188,25 @@ func (v Value) InFirst() (Value, error) { // // https://sqlite.org/c3ref/vtab_in_first.html func (v Value) InNext() (Value, error) { - defer v.c.arena.Mark()() - valPtr := v.c.arena.New(ptrlen) - rc := res_t(v.c.wrp.Xsqlite3_vtab_in_next(int32(v.handle), int32(valPtr))) - if err := v.c.error(rc); err != nil { + return v.c.returnValue(v.c.wrp.Xsqlite3_vtab_in_next, v.handle) +} + +func (c *Conn) returnValue(fn func(_, _ int32) int32, handle ptr_t) (Value, error) { + defer c.arena.Mark()() + valPtr := c.arena.New(ptrlen) + rc := res_t(fn(int32(handle), int32(valPtr))) + if err := c.error(rc); err != nil { return Value{}, err } - return Value{ - c: v.c, - handle: ptr_t(v.c.wrp.Read32(valPtr)), - }, nil + return Value{c: c, handle: ptr_t(c.wrp.Read32(valPtr))}, nil +} + +func (c *Conn) columnValue(fn func(_, _, _ int32) int32, handle ptr_t, column int) (Value, error) { + defer c.arena.Mark()() + valPtr := c.arena.New(ptrlen) + rc := res_t(fn(int32(handle), int32(column), int32(valPtr))) + if err := c.error(rc); err != nil { + return Value{}, err + } + return Value{c: c, handle: ptr_t(c.wrp.Read32(valPtr))}, nil } diff --git a/vtab.go b/vtab.go index f0b7cc5..165421b 100644 --- a/vtab.go +++ b/vtab.go @@ -299,17 +299,7 @@ type IndexConstraintUsage struct { // // https://sqlite.org/c3ref/vtab_rhs_value.html func (idx *IndexInfo) RHSValue(column int) (Value, error) { - defer idx.c.arena.Mark()() - valPtr := idx.c.arena.New(ptrlen) - rc := res_t(idx.c.wrp.Xsqlite3_vtab_rhs_value(int32(idx.handle), - int32(column), int32(valPtr))) - if err := idx.c.error(rc); err != nil { - return Value{}, err - } - return Value{ - c: idx.c, - handle: ptr_t(idx.c.wrp.Read32(valPtr)), - }, nil + return idx.c.columnValue(idx.c.wrp.Xsqlite3_vtab_rhs_value, idx.handle, column) } // Collation returns the name of the collation for a virtual table constraint. @@ -325,16 +315,15 @@ func (idx *IndexInfo) Collation(column int) string { // // https://sqlite.org/c3ref/vtab_distinct.html func (idx *IndexInfo) Distinct() int { - i := int32(idx.c.wrp.Xsqlite3_vtab_distinct(int32(idx.handle))) - return int(i) + return int(idx.c.wrp.Xsqlite3_vtab_distinct(int32(idx.handle))) } // In identifies and handles IN constraints. // // https://sqlite.org/c3ref/vtab_in.html func (idx *IndexInfo) In(column, handle int) bool { - b := int32(idx.c.wrp.Xsqlite3_vtab_in(int32(idx.handle), - int32(column), int32(handle))) + b := idx.c.wrp.Xsqlite3_vtab_in(int32(idx.handle), + int32(column), int32(handle)) return b != 0 }