diff --git a/sqlite3_opt_vtable.go b/sqlite3_opt_vtable.go index 2c17d30..25ae3ed 100644 --- a/sqlite3_opt_vtable.go +++ b/sqlite3_opt_vtable.go @@ -609,8 +609,14 @@ func goVUpdate(pVTab unsafe.Pointer, argc C.int, argv **C.sqlite3_value, pRowid case argc > 1: // Per the xUpdate contract argv[0] identifies the row being - // updated while argv[1] is its (possibly changed) new rowid. - err = v.Update(vals[0], vals[2:]) + // updated while argv[1] is its new rowid. VTabUpdater has no + // way to convey a rowid change, so reject it instead of + // silently updating values under the old rowid. + if vals[0] != vals[1] { + err = fmt.Errorf("virtual %s table %sdoes not support changing the rowid", vt.module.name, tname) + } else { + err = v.Update(vals[0], vals[2:]) + } } } diff --git a/sqlite3_opt_vtable_test.go b/sqlite3_opt_vtable_test.go index 64511e2..3afa155 100644 --- a/sqlite3_opt_vtable_test.go +++ b/sqlite3_opt_vtable_test.go @@ -280,6 +280,19 @@ func TestVUpdate(t *testing.T) { t.Fatalf("expected table vt entry 1 to be [117 f e], instead: %v", vt.data[1]) } + // a rowid-changing update cannot be expressed via VTabUpdater and + // must be rejected instead of updating the wrong row + _, err = db.Exec(`update vt set rowid = rowid + 10 where f1 = 117`) + if err == nil { + t.Fatalf("expected error on rowid-changing update, got nil") + } + if !strings.Contains(err.Error(), "does not support changing the rowid") { + t.Fatalf("unexpected error on rowid-changing update: %v", err) + } + if !reflect.DeepEqual(vt.data[1], []any{int64(117), "f", "e"}) { + t.Fatalf("expected table vt entry 1 to be unchanged, instead: %v", vt.data[1]) + } + // delete from vt res, err = db.Exec(`delete from vt where f1 = 117`) if err != nil {