{"record":{"id":"82c9ff3df7314980","repo":"siyuan-note/siyuan","slug":"database-back-relation-changed-since-document-deletion","errorCode":null,"errorMessage":"database back relation changed since document deletion","messagePattern":"database back relation changed since document deletion","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_block_history.go","lineNumber":229,"sourceCode":"\t// 先合并所有主键，再恢复双向关联，允许同一文档内的条目互相关联。\n\tfor id, itemIDs := range restoredItems {\n\t\tfor _, kv := range after[id].KeyValues {\n\t\t\trelation := kv.Key.Relation\n\t\t\tif relation == nil || !relation.IsTwoWay {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tfor _, value := range kv.Values {\n\t\t\t\tif !slices.Contains(itemIDs, value.BlockID) || value.Relation == nil || len(value.Relation.BlockIDs) == 0 {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tdest, err := load(relation.AvID)\n\t\t\t\tif err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tback, err := dest.GetKeyValues(relation.BackKeyID)\n\t\t\t\tif err != nil || back.Key.Relation == nil || !back.Key.Relation.IsTwoWay ||\n\t\t\t\t\tback.Key.Relation.AvID != id || back.Key.Relation.BackKeyID != kv.Key.ID {\n\t\t\t\t\treturn errors.New(\"database back relation changed since document deletion\")\n\t\t\t\t}\n\t\t\t\tfor _, targetID := range value.Relation.BlockIDs {\n\t\t\t\t\tif dest.GetBlockValue(targetID) == nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"related database entry [%s] is missing\", targetID)\n\t\t\t\t\t}\n\t\t\t\t\tbackValue := back.GetValue(targetID)\n\t\t\t\t\tif backValue == nil {\n\t\t\t\t\t\tbackValue = &av.Value{ID: ast.NewNodeID(), KeyID: back.Key.ID, BlockID: targetID,\n\t\t\t\t\t\t\tType: av.KeyTypeRelation, Relation: &av.ValueRelation{}}\n\t\t\t\t\t\tback.Values = append(back.Values, backValue)\n\t\t\t\t\t}\n\t\t\t\t\tif backValue.Relation == nil {\n\t\t\t\t\t\treturn errors.New(\"database back relation value is invalid\")\n\t\t\t\t\t}\n\t\t\t\t\tif !slices.Contains(backValue.Relation.BlockIDs, value.BlockID) {\n\t\t\t\t\t\tbackValue.Relation.BlockIDs = append(backValue.Relation.BlockIDs, value.BlockID)\n\t\t\t\t\t}\n\t\t\t\t}","sourceCodeStart":211,"sourceCodeEnd":247,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_block_history.go#L211-L247","documentation":"After primary keys are merged, two-way relations from the restored rows are written into the destination database's back-relation key. The code validates that the back key still exists and its Relation still points back to the source database/key (IsTwoWay, matching AvID and BackKeyID). If the back-relation field was deleted, converted to one-way, or re-pointed since the document deletion, the symmetric link cannot be reconstructed and the rollback aborts.","triggerScenarios":"RollbackDocHistory restoring rows whose two-way relation values reference another database, where that destination database's back-relation key (relation.BackKeyID) was deleted, its IsTwoWay flag turned off, or its AvID/BackKeyID changed after the document was deleted.","commonSituations":"User removed or re-created the relation field in the target database between deletion and rollback; relation reconfigured from two-way to one-way; databases copied/re-keyed so BackKeyID no longer matches; plugin or API operations modifying relation metadata.","solutions":["Recreate or restore the back-relation field in the target database with the exact key ID and two-way settings pointing back to the source key, then retry the rollback","Choose an earlier history version from before the relation was modified","Convert the relation back to two-way with matching BackKeyID if it was changed to one-way","Rebuild the relation links manually after restoring the document instead of relying on automatic merge"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Go: verify two-way relation integrity before rollback\nback, err := dest.GetKeyValues(relation.BackKeyID)\nvalid := err == nil && back.Key.Relation != nil && back.Key.Relation.IsTwoWay &&\n    back.Key.Relation.AvID == srcAvID && back.Key.Relation.BackKeyID == srcKeyID","typeGuard":null,"tryCatchPattern":"err := tx.restoreBoundAttributeViewHistory(tree, historyDir)\nif err != nil && strings.Contains(err.Error(), \"back relation changed\") {\n    // recreate the two-way back-relation field, then retry\n}","preventionTips":["Keep two-way relations two-way while documents pending rollback","Never delete the back-relation key in the target database","Avoid converting relations one-way before rollback operations","Snapshot relation metadata before schema edits"],"tags":["database","history","attribute-view","relation"],"backgroundTag":"invalid-state-transition","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"}