{"record":{"id":"b708ff2a838b9e25","repo":"siyuan-note/siyuan","slug":"database-field-s-changed-after-the-operation","errorCode":null,"errorMessage":"database field [%s] changed after the operation","messagePattern":"database field \\[(.+?)\\] changed after the operation","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_fields.go","lineNumber":252,"sourceCode":"\nfunc (tx *Transaction) replayAttributeViewFields(op *Operation) error {\n\tstate := op.attributeViewFields\n\tif !tx.isReplay || op.AvID != state.avID || op.ID != state.keyID {\n\t\treturn errors.New(\"invalid database field replay\")\n\t}\n\tif len(state.changes) == 0 {\n\t\treturn nil\n\t}\n\tbefore, after := map[string]*av.AttributeView{}, map[string]*av.AttributeView{}\n\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 {","sourceCodeStart":234,"sourceCodeEnd":270,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_fields.go#L234-L270","documentation":"During field-change replay, replayAttributeViewFields re-reads each affected database and compares every recorded field type (state.fieldTypes) against the live attribute view. If a recorded field is missing (GetKey fails) or its type differs, the database has moved on since the snapshot was captured and applying the change could destroy newer edits, so it fails with this error naming the changed field.","triggerScenarios":"Undo/redo replay of add/remove/update-column, restore-deleted-blocks, or sort-item operations when, between snapshot capture and replay, the field was deleted or its type changed (e.g. text converted to select) by a concurrent edit, another device's sync, or a manually composed transaction.","commonSituations":"User edited the database on another device while an undo was pending; sync merged divergent database definitions; a plugin changed a field type inside the same transaction that also deletes it, so the snapshot no longer matches.","solutions":["Reload the database and retry the undo once no other edits are in flight (close other editors / pause sync)","Accept that undo cannot proceed across divergent edits and skip the conflicting undo step","Re-create the field with its original type if you intentionally want the replayed state"],"exampleFix":"// before\n// blind replay despite field type change\nreplay(tx, op)\n// after\nkey, err := currentAv.GetKey(fieldID)\nif err != nil || key.Type != expectedType {\n    // abort or refresh snapshot before replaying\n    return nil\n}","handlingStrategy":"validation","validationCode":"av, err := model.ParseAttributeView(avID)\nif err != nil { return err }\nfor fieldID, expectedType := range recordedFieldTypes {\n    key, err := av.GetKey(fieldID)\n    if err != nil || key.Type != expectedType {\n        return fmt.Errorf(\"field %s diverged; refresh snapshot before undo\", fieldID)\n    }\n}","typeGuard":"func fieldTypesUnchanged(av *av.AttributeView, recorded map[string]av.KeyType) bool {\n    for id, t := range recorded {\n        if k, err := av.GetKey(id); err != nil || k.Type != t { return false }\n    }\n    return true\n}","tryCatchPattern":"if err := tx.replayAttributeViewFields(op); err != nil {\n    if strings.Contains(err.Error(), \"changed after the operation\") {\n        // skip or re-capture the undo step\n        return nil\n    }\n    return err\n}","preventionTips":["Pause sync / close other editors while performing undo of schema changes","Batch field-type changes and deletions deliberately, not interleaved with pending undos","Verify field types after sync merges that touch the same database"],"tags":["database","attribute-view","conflict","undo-redo"],"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"}