{"record":{"id":"c683f8f20e8ff5e1","repo":"siyuan-note/siyuan","slug":"database-entry-s-changed-since-deletion-was-undone","errorCode":null,"errorMessage":"database entry [%s] changed since deletion was undone","messagePattern":"database entry \\[(.+?)\\] changed since deletion was undone","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_items.go","lineNumber":54,"sourceCode":"\tKeys                 []*av.KeyValues\n\tOrders               map[string][]string\n\tCovers               map[string]map[string]*av.CardCoverPosition\n\trelatedChanges       *attributeViewFieldsSnapshot\n}\n\nfunc (tx *Transaction) prepareAttributeViewItemRemoval(op *Operation) error {\n\tif tx.isReplay && nil != op.attributeViewItems {\n\t\tcurrent, err := tx.readAttributeViewItems(op.attributeViewItems)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err = validateAttributeViewItemKeys(current, op.attributeViewItems); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, kv := range op.attributeViewItems.Keys {\n\t\t\tfor _, value := range kv.Values {\n\t\t\t\tif !reflect.DeepEqual(value, av.GetValue(current.KeyValues, kv.Key.ID, value.BlockID)) {\n\t\t\t\t\treturn fmt.Errorf(\"database entry [%s] changed since deletion was undone\", value.BlockID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfor _, kv := range current.KeyValues {\n\t\t\tfor _, value := range kv.Values {\n\t\t\t\tif slices.Contains(op.attributeViewItems.ItemIDs, value.BlockID) &&\n\t\t\t\t\t!reflect.DeepEqual(value, av.GetValue(op.attributeViewItems.Keys, kv.Key.ID, value.BlockID)) {\n\t\t\t\t\treturn fmt.Errorf(\"database entry [%s] has newer field values\", value.BlockID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif err = tx.prepareAttributeViewItemMutation(current, op.attributeViewItems); err != nil {\n\t\t\treturn err\n\t\t}\n\t\treturn tx.replayAttributeViewItemConfiguration(op.attributeViewItems, false)\n\t}\n\tif tx.isReplay || !tx.fromAPI || len(tx.UndoOperations) == 0 {\n\t\treturn nil","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_items.go#L36-L72","documentation":"prepareAttributeViewItemRemoval, in replay (undo) mode, verifies that the row values recorded in the deletion snapshot (op.attributeViewItems.Keys) still match the live database exactly via reflect.DeepEqual before re-inserting them. If a recorded value differs from the current cell — meaning the entry was modified after the deletion was (or is being) undone — the kernel aborts with 'database entry [%s] changed since deletion was undone' to avoid clobbering newer data.","triggerScenarios":"Undo replay of doRemoveAttrViewBlock when, after the deletion snapshot was captured, one of the snapshot's values for a row no longer deep-equals the live value (row edited between original delete and undo, or the snapshot was replayed once already and the row changed).","commonSituations":"User deleted a row, edited remaining cells, then tried to undo the deletion; sync merged cell edits from another device into the same rows; a plugin updated values outside the snapshot's awareness.","solutions":["Undo in order: first undo the edits made after the deletion, then undo the row deletion","Accept the current values and skip restoring the stale snapshot (re-add the row and re-enter lost values manually)","Refresh/sync to a state consistent with the snapshot before replaying the undo"],"exampleFix":"// before\ntx.undo(removeBlockOp) // assumes values unchanged since delete\n// after\nlive := av.GetValue(current.KeyValues, kv.Key.ID, value.BlockID)\nif !reflect.DeepEqual(value, live) {\n    // handle divergence: skip or merge before replaying the deletion undo\n    return nil\n}","handlingStrategy":"validation","validationCode":"for _, kv := range snap.Keys {\n    for _, v := range kv.Values {\n        if !reflect.DeepEqual(v, av.GetValue(current.KeyValues, kv.Key.ID, v.BlockID)) {\n            return fmt.Errorf(\"entry %s diverged from snapshot; handle edits first\", v.BlockID)\n        }\n    }\n}","typeGuard":"func snapshotFresh(current *av.AttributeView, snap *model.AttributeViewItemsSnapshot) bool {\n    for _, kv := range snap.Keys {\n        for _, v := range kv.Values {\n            if !reflect.DeepEqual(v, av.GetValue(current.KeyValues, kv.Key.ID, v.BlockID)) { return false }\n        }\n    }\n    return true\n}","tryCatchPattern":"if err := tx.prepareAttributeViewItemRemoval(op); err != nil {\n    if strings.Contains(err.Error(), \"changed since deletion was undone\") {\n        // merge newer edits manually or skip the undo step\n        return nil\n    }\n    return err\n}","preventionTips":["Undo deletions before making further edits to the same rows","Check for pending remote sync changes before replaying undos","Avoid editing database cells via plugins while an undo is pending"],"tags":["database","attribute-view","undo-redo","conflict"],"backgroundTag":"conflicting-concurrent-edit","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}