Pre-update hook.
This commit is contained in:
@@ -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)))
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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:]
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=
|
||||
|
||||
@@ -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]
|
||||
}
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user