{"record":{"id":"3e001895c39607b6","repo":"siyuan-note/siyuan","slug":"database-entry-s-was-removed-after-the-field","errorCode":null,"errorMessage":"database entry [%s] was removed after the field","messagePattern":"database entry \\[(.+?)\\] was removed after the field","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_fields.go","lineNumber":287,"sourceCode":"\t\tpatched, _, err := state.changes[id].apply(data, true, op.attributeViewFieldUndo)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"database [%s]: %w\", id, err)\n\t\t}\n\t\tencoded, err := json.Marshal(patched)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\trestored := &av.AttributeView{}\n\t\tif err = json.Unmarshal(encoded, restored); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor _, kv := range restored.KeyValues {\n\t\t\tif _, keyErr := current.GetKey(kv.Key.ID); keyErr == nil {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, value := range kv.Values {\n\t\t\t\tif restored.GetBlockValue(value.BlockID) == nil {\n\t\t\t\t\treturn fmt.Errorf(\"database entry [%s] was removed after the field\", value.BlockID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tbefore[id], after[id] = current, restored\n\t}\n\treturn tx.saveAttributeViewFieldChanges(state, before, after)\n}\n\nfunc (tx *Transaction) saveAttributeViewFieldChanges(state *attributeViewFieldsSnapshot, before, after map[string]*av.AttributeView) error {\n\tif err := tx.rememberAttributeViewMutationTree(state.blockID); err != nil {\n\t\treturn err\n\t}\n\tids := sortedAttributeViewFieldKeys(after)\n\tfor _, id := range ids {\n\t\tkey := state.boxID + \"/\" + id\n\t\tif tx.attributeViewRollback.views[key] == nil {\n\t\t\ttx.attributeViewRollback.views[key] = before[id]\n\t\t}","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_fields.go#L269-L305","documentation":"After applying the recorded patch, replayAttributeViewFields checks that any field values which are re-added during replay still have their owning rows: for every restored KeyValues entry whose field is not yet present in the live database, each value's BlockID must still exist as a block value (restored.GetBlockValue). If a row (database entry) that existed when the field was removed has since been deleted, replaying would resurrect orphaned field values, so the kernel refuses with this error.","triggerScenarios":"Undoing a column deletion whose snapshot contains field values for rows that were themselves removed later (row deleted by user, plugin, or sync between the field deletion and the undo replay).","commonSituations":"User deleted rows right after removing a column and then hit undo repeatedly; sync merged a row deletion from another device; a plugin pruned detached rows while the undo stack still referenced them.","solutions":["Re-create (or restore via document history) the deleted rows before undoing the field removal","Skip this undo step; re-add the field and re-enter the surviving values manually","Adjust the snapshot/undo ordering so row deletions are undone before the field deletion"],"exampleFix":"// before\ntx.undo(removeColumnOp) // rows already deleted -> error\n// after\nfor _, rowID := range removedRowIDs {\n    if currentAv.GetBlockValue(rowID) == nil {\n        // restore row first (history/insertAttrViewBlock) before undoing the column\n        restoreRow(rowID)\n    }\n}","handlingStrategy":"validation","validationCode":"for _, kv := range snapshotRestored.KeyValues {\n    if _, err := current.GetKey(kv.Key.ID); err == nil { continue }\n    for _, v := range kv.Values {\n        if current.GetBlockValue(v.BlockID) == nil {\n            return fmt.Errorf(\"row %s missing; restore rows before undoing field deletion\", v.BlockID)\n        }\n    }\n}","typeGuard":"func rowsExist(av *av.AttributeView, ids []string) bool {\n    for _, id := range ids { if av.GetBlockValue(id) == nil { return false } }\n    return true\n}","tryCatchPattern":"if err := tx.replayAttributeViewFields(op); err != nil {\n    if strings.Contains(err.Error(), \"was removed after the field\") {\n        // restore rows from history first, then retry undo\n        return nil\n    }\n    return err\n}","preventionTips":["Undo deletions in reverse order (rows before columns)","Use document/database history to restore removed rows before schema undos","Avoid pruning rows while older column deletions remain on the undo stack"],"tags":["database","attribute-view","undo-redo","referential-integrity"],"backgroundTag":"referential-integrity-violation","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"}