{"record":{"id":"8926d1d9f32f7a77","repo":"siyuan-note/siyuan","slug":"database-back-relation-field-s-changed","errorCode":null,"errorMessage":"database back relation field [%s] changed","messagePattern":"database back relation field \\[(.+?)\\] changed","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_items.go","lineNumber":351,"sourceCode":"\t\tfor _, value := range kv.Values {\n\t\t\tif value.Relation == nil || len(value.Relation.BlockIDs) == 0 {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tdest := changed[kv.Key.Relation.AvID]\n\t\t\tif dest == nil {\n\t\t\t\tdest, err = av.ParseAttributeViewForIndexInBox(kv.Key.Relation.AvID, state.BoxID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tif dest == nil {\n\t\t\t\t\treturn av.ErrViewNotFound\n\t\t\t\t}\n\t\t\t\tchanged[dest.ID] = dest\n\t\t\t}\n\t\t\tback, backErr := dest.GetKeyValues(kv.Key.Relation.BackKeyID)\n\t\t\tif backErr != nil || back.Key.Type != av.KeyTypeRelation || back.Key.Relation == nil ||\n\t\t\t\t!back.Key.Relation.IsTwoWay || back.Key.Relation.AvID != state.AvID || back.Key.Relation.BackKeyID != kv.Key.ID {\n\t\t\t\treturn fmt.Errorf(\"database back relation field [%s] changed\", kv.Key.Relation.BackKeyID)\n\t\t\t}\n\t\t\tfor _, id := range value.Relation.BlockIDs {\n\t\t\t\tbackValue := back.GetValue(id)\n\t\t\t\tif dest.GetBlockValue(id) == nil || backValue == nil || backValue.Relation == nil {\n\t\t\t\t\treturn fmt.Errorf(\"related database entry [%s] changed\", id)\n\t\t\t\t}\n\t\t\t\tif !slices.Contains(backValue.Relation.BlockIDs, value.BlockID) {\n\t\t\t\t\tbackValue.Relation.BlockIDs = append(backValue.Relation.BlockIDs, value.BlockID)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tfor _, view := range current.Views {\n\t\tview.ItemIDs = restoreAttributeViewItemOrder(view.ItemIDs, state.Orders[view.ID], state.ItemIDs)\n\t\tfor _, group := range view.Groups {\n\t\t\tgroup.GroupItemIDs = restoreAttributeViewItemOrder(group.GroupItemIDs, state.Orders[group.ID], state.ItemIDs)\n\t\t}\n\t}","sourceCodeStart":333,"sourceCodeEnd":369,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_items.go#L333-L369","documentation":"During replay of an insert operation containing two-way relation values, the kernel checks the target database's back-relation field: it must still be a relation key, still two-way, and still point back to this database and key (matching AvID and BackKeyID). If the relation pair was edited, unlinked, or reconfigured since the snapshot, replaying would write into a mismatched field, so it aborts.","triggerScenarios":"Replaying doInsertAttrViewBlock with two-way relation values after the back-relation field in the target database was deleted, converted to another type, changed to one-way, or re-pointed to a different key/AV.","commonSituations":"User edited relation column configuration between the original operation and undo/redo; a plugin or script rewired two-way relation metadata; sync merged divergent relation field definitions.","solutions":["Restore the relation field's two-way configuration (same AvID/BackKeyID) before replaying the operation","Undo the field-configuration change that broke the relation pair, then retry","Re-create the two-way relation between the two databases and re-add the entries manually instead of replaying","Check both databases' key definitions (Type, Relation.IsTwoWay, AvID, BackKeyID) for consistency before scripted edits"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// verify the back-relation field config before writing relations\nconst target = await getAttrView(relAvID);\nconst backKey = target.keyTypes[relBackKeyID];\nif (!backKey || backKey.type !== 'relation' || !backKey.relation?.isTwoWay || backKey.relation.avID !== srcAvID || backKey.relation.backKeyID !== srcKeyID) throw new Error('back relation field changed');","typeGuard":"function isTwoWayBackRelation(k, srcAvID, srcKeyID) { return !!k && k.type === 'relation' && !!k.relation && k.relation.isTwoWay === true && k.relation.avID === srcAvID && k.relation.backKeyID === srcKeyID; }","tryCatchPattern":"try { await insertAttrViewBlock(avID, blockID, items); } catch (e) { if (/back relation field \\[.+\\] changed/.test(e.message)) { restoreRelationConfig(); retry(); } else throw e; }","preventionTips":["Freeze relation field configuration while scripts manipulate related entries","After editing a relation column, verify its pair field still points back correctly","Avoid merging schema changes from sync while undo history is pending"],"tags":["database","relation","two-way-relation","transaction-replay"],"backgroundTag":"schema-validation-failed","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"}