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

34
DB.go
View File

@ -610,18 +610,30 @@ func (db *DB) Update(table string, data any, conditions string, args ...any) *Ex
}
func (db *DB) Delete(table string, conditions string, args ...any) *ExecResult {
if conditions != "" {
conditions = " where " + conditions
}
query := fmt.Sprintf("delete from %s%s", db.Quote(table), conditions)
r := baseExec(db.conn, nil, query, args...)
r.logger = db.logger
if r.Error != nil {
db.logger.LogQueryError(r.Error.Error(), query, args, r.usedTime)
} else {
if db.Config.LogSlow > 0 && r.usedTime >= float32(db.Config.LogSlow.TimeDuration()/time.Millisecond) {
db.logger.LogQuery(query, args, r.usedTime)
ts := db.getTable(table)
if !ts.HasShadowTable {
if conditions != "" {
conditions = " where " + conditions
}
query := fmt.Sprintf("delete from %s%s", db.Quote(table), conditions)
r := baseExec(db.conn, nil, query, args...)
r.logger = db.logger
if r.Error != nil {
db.logger.LogQueryError(r.Error.Error(), query, args, r.usedTime)
} else {
if db.Config.LogSlow > 0 && r.usedTime >= float32(db.Config.LogSlow.TimeDuration()/time.Millisecond) {
db.logger.LogQuery(query, args, r.usedTime)
}
}
return r
}
// Shadow delete
tx := db.Begin()
defer tx.CheckFinished()
r := tx.Delete(table, conditions, args...)
if r.Error == nil {
tx.Commit()
}
return r
}

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)
}
})
}