siyuan-note/siyuan · error
database back relation values
Error message
database back relation values [%s] changed after field deletion
What it means
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.
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
Example fix
// before
tx.undo(op) // assumes back-relation values untouched
// after
kv, _ := current.GetKeyValues(backKeyID)
if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
// refresh snapshot or skip this undo step
return nil
} Defensive patterns
Strategy: validation
Validate before calling
kv, err := current.GetKeyValues(backKeyID)
if err != nil { return err }
if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
return fmt.Errorf("back relation values drifted; re-capture before undo")
} Type guard
func relationValuesMatch(kv *av.KeyValues, expected map[string][]string) bool {
return reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected)
} Try / catch
if err := tx.replayAttributeViewFields(op); err != nil {
if strings.Contains(err.Error(), "back relation values") {
// abort this undo step; re-create field manually if needed
return nil
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- database entry [ ] changed since deletion was undone
- database entry [ ] has newer field values
- database field [ ] changed after the operation
- database back relation changed since document deletion
- database back relation field
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/2492a44bd6176a39.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_fields.go:262
for _, id := range sortedAttributeViewFieldKeys(state.changes) {
current, err := tx.readAttributeViewForMutation(id, state.blockID, state.boxID)
if err != nil {
return err
}
for keyID, typ := range state.fieldTypes[id] {
key, keyErr := current.GetKey(keyID)
if keyErr != nil || key.Type != typ {
return fmt.Errorf("database field [%s] changed after the operation", keyID)
}
}
// 恢复双向定义时,另一端的单向值必须仍与删除时一致。
for keyID, expected := range state.relationValues[id] {
kv, keyErr := current.GetKeyValues(keyID)
if keyErr != nil {
return keyErr
}
if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
return fmt.Errorf("database back relation values [%s] changed after field deletion", keyID)
}
}
data, err := attributeViewFieldJSON(current)
if err != nil {
return err
}
patched, _, err := state.changes[id].apply(data, true, op.attributeViewFieldUndo)
if err != nil {
return fmt.Errorf("database [%s]: %w", id, err)
}
encoded, err := json.Marshal(patched)
if err != nil {
return err
}
restored := &av.AttributeView{}
if err = json.Unmarshal(encoded, restored); err != nil {
return err
}View on GitHub (pinned to 9f775e8a12)