feat: implement smart delete with shadow table support

This commit is contained in:
AI Engineer 2026-05-03 23:02:31 +08:00
parent 8d75cf7be5
commit d84495af2e
3 changed files with 92 additions and 13 deletions

12
DB.go
View File

@ -610,6 +610,8 @@ func (db *DB) Update(table string, data any, conditions string, args ...any) *Ex
}
func (db *DB) Delete(table string, conditions string, args ...any) *ExecResult {
ts := db.getTable(table)
if !ts.HasShadowTable {
if conditions != "" {
conditions = " where " + conditions
}
@ -624,6 +626,16 @@ func (db *DB) Delete(table string, conditions string, args ...any) *ExecResult {
}
}
return r
}
// Shadow delete
tx := db.Begin()
defer tx.CheckFinished()
r := tx.Delete(table, conditions, args...)
if r.Error == nil {
tx.Commit()
}
return r
}
func (db *DB) getTable(table string) *TableStruct {

17
Tx.go
View File

@ -165,10 +165,23 @@ func (tx *Tx) Update(table string, data any, conditions string, args ...any) *Ex
}
func (tx *Tx) Delete(table string, conditions string, args ...any) *ExecResult {
ts := tx.db.getTable(table)
where := ""
if conditions != "" {
conditions = " where " + conditions
where = " where " + conditions
}
query := fmt.Sprintf("delete from %s%s", tx.Quote(table), conditions)
if ts.HasShadowTable {
// Move to shadow table
moveQuery := fmt.Sprintf("insert into %s select * from %s%s", tx.Quote(table+"_deleted"), tx.Quote(table), where)
r := baseExec(nil, tx.conn, moveQuery, args...)
if r.Error != nil {
tx.logger.LogQueryError(r.Error.Error(), moveQuery, args, r.usedTime)
return r
}
}
query := fmt.Sprintf("delete from %s%s", tx.Quote(table), where)
tx.lastSql = &query
tx.lastArgs = args
r := baseExec(nil, tx.conn, query, args...)

54
delete_test.go Normal file
View File

@ -0,0 +1,54 @@
package db_test
import (
"testing"
"apigo.cc/go/db"
_ "modernc.org/sqlite"
)
func TestSmartDelete(t *testing.T) {
dbInst := db.GetDB("sqlite://:memory:", nil)
// Create table and shadow table
dbInst.Exec("CREATE TABLE orders (id INTEGER PRIMARY KEY, item TEXT)")
dbInst.Exec("CREATE TABLE orders_deleted (id INTEGER PRIMARY KEY, item TEXT)")
t.Run("ShadowDelete", func(t *testing.T) {
dbInst.Exec("INSERT INTO orders (id, item) VALUES (1, 'Phone')")
res := dbInst.Delete("orders", "id = 1")
if res.Error != nil {
t.Fatalf("Delete failed: %v", res.Error)
}
if res.Changes() != 1 {
t.Errorf("Expected 1 change, got %d", res.Changes())
}
// Verify it's gone from main table
qr := dbInst.Query("SELECT COUNT(*) FROM orders WHERE id = 1")
count, _ := db.ToValue[int](qr)
if count != 0 {
t.Errorf("Expected 0 records in main table, got %d", count)
}
// Verify it's in shadow table
qr2 := dbInst.Query("SELECT COUNT(*) FROM orders_deleted WHERE id = 1")
countDeleted, _ := db.ToValue[int](qr2)
if countDeleted != 1 {
t.Errorf("Expected 1 record in shadow table, got %d", countDeleted)
}
})
t.Run("PhysicalDelete", func(t *testing.T) {
dbInst.Exec("CREATE TABLE logs (id INTEGER PRIMARY KEY, msg TEXT)")
dbInst.Exec("INSERT INTO logs (id, msg) VALUES (1, 'Login')")
dbInst.Delete("logs", "id = 1")
qr := dbInst.Query("SELECT COUNT(*) FROM logs WHERE id = 1")
count, _ := db.ToValue[int](qr)
if count != 0 {
t.Errorf("Expected 0 records in logs, got %d", count)
}
})
}