{"record":{"id":"2492a44bd6176a39","repo":"siyuan-note/siyuan","slug":"database-back-relation-values-s-changed-after-field-deletion","errorCode":null,"errorMessage":"database back relation values [%s] changed after field deletion","messagePattern":"database back relation values \\[(.+?)\\] changed after field deletion","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_fields.go","lineNumber":262,"sourceCode":"\tfor _, id := range sortedAttributeViewFieldKeys(state.changes) {\n\t\tcurrent, err := tx.readAttributeViewForMutation(id, state.blockID, state.boxID)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tfor keyID, typ := range state.fieldTypes[id] {\n\t\t\tkey, keyErr := current.GetKey(keyID)\n\t\t\tif keyErr != nil || key.Type != typ {\n\t\t\t\treturn fmt.Errorf(\"database field [%s] changed after the operation\", keyID)\n\t\t\t}\n\t\t}\n\t\t// 恢复双向定义时，另一端的单向值必须仍与删除时一致。\n\t\tfor keyID, expected := range state.relationValues[id] {\n\t\t\tkv, keyErr := current.GetKeyValues(keyID)\n\t\t\tif keyErr != nil {\n\t\t\t\treturn keyErr\n\t\t\t}\n\t\t\tif !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {\n\t\t\t\treturn fmt.Errorf(\"database back relation values [%s] changed after field deletion\", keyID)\n\t\t\t}\n\t\t}\n\t\tdata, err := attributeViewFieldJSON(current)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\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}","sourceCodeStart":244,"sourceCodeEnd":280,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_fields.go#L244-L280","documentation":"When a field deletion is undone, replayAttributeViewFields also verifies that the mirrored (back-relation) side's stored relation values still equal what was recorded at deletion time (state.relationValues, compared via reflect.DeepEqual of the block-ID lists). If the opposite database's relation cell values changed in the meantime, the undo would restore an outdated view of them, so the kernel aborts with this error.","triggerScenarios":"Undoing doRemoveAttrViewColumn of a (two-way) relation field when rows in the destination database gained/lost/edited relation values after the deletion snapshot was taken.","commonSituations":"User or sync edited rows in the target database between the field deletion and the undo attempt; another plugin batch-updated relation cells; undo history spans a sync merge.","solutions":["Refresh the undo snapshot by re-performing the field deletion so relationValues reflects current data, then undo","Restore the expected relation values in the destination database manually before undoing","Give up the undo of that step and re-create the relation field plus values by hand"],"exampleFix":"// before\ntx.undo(op) // assumes back-relation values untouched\n// after\nkv, _ := current.GetKeyValues(backKeyID)\nif !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {\n    // refresh snapshot or skip this undo step\n    return nil\n}","handlingStrategy":"validation","validationCode":"kv, err := current.GetKeyValues(backKeyID)\nif err != nil { return err }\nif !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {\n    return fmt.Errorf(\"back relation values drifted; re-capture before undo\")\n}","typeGuard":"func relationValuesMatch(kv *av.KeyValues, expected map[string][]string) bool {\n    return reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected)\n}","tryCatchPattern":"if err := tx.replayAttributeViewFields(op); err != nil {\n    if strings.Contains(err.Error(), \"back relation values\") {\n        // abort this undo step; re-create field manually if needed\n        return nil\n    }\n    return err\n}","preventionTips":["Undo field deletions promptly, before further row edits","Avoid editing relation cells in the target database while a deletion-undo is pending","Check sync state before undoing cross-database relation changes"],"tags":["database","attribute-view","relation","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"}