siyuan-note/siyuan · error
database back relation changed since document deletion
Error message
database back relation changed since document deletion
What it means
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.
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
Defensive patterns
Strategy: try-catch
Validate before calling
// Go: verify two-way relation integrity before rollback
back, err := dest.GetKeyValues(relation.BackKeyID)
valid := err == nil && back.Key.Relation != nil && back.Key.Relation.IsTwoWay &&
back.Key.Relation.AvID == srcAvID && back.Key.Relation.BackKeyID == srcKeyID Try / catch
err := tx.restoreBoundAttributeViewHistory(tree, historyDir)
if err != nil && strings.Contains(err.Error(), "back relation changed") {
// recreate the two-way back-relation field, then retry
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- database back relation value is invalid
- related database entry
- database back relation field
- database back relation values
- database entry [ ] has a different binding
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/82c9ff3df7314980.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/model/attribute_view_block_history.go:229
// 先合并所有主键,再恢复双向关联,允许同一文档内的条目互相关联。
for id, itemIDs := range restoredItems {
for _, kv := range after[id].KeyValues {
relation := kv.Key.Relation
if relation == nil || !relation.IsTwoWay {
continue
}
for _, value := range kv.Values {
if !slices.Contains(itemIDs, value.BlockID) || value.Relation == nil || len(value.Relation.BlockIDs) == 0 {
continue
}
dest, err := load(relation.AvID)
if err != nil {
return err
}
back, err := dest.GetKeyValues(relation.BackKeyID)
if err != nil || back.Key.Relation == nil || !back.Key.Relation.IsTwoWay ||
back.Key.Relation.AvID != id || back.Key.Relation.BackKeyID != kv.Key.ID {
return errors.New("database back relation changed since document deletion")
}
for _, targetID := range value.Relation.BlockIDs {
if dest.GetBlockValue(targetID) == nil {
return fmt.Errorf("related database entry [%s] is missing", targetID)
}
backValue := back.GetValue(targetID)
if backValue == nil {
backValue = &av.Value{ID: ast.NewNodeID(), KeyID: back.Key.ID, BlockID: targetID,
Type: av.KeyTypeRelation, Relation: &av.ValueRelation{}}
back.Values = append(back.Values, backValue)
}
if backValue.Relation == nil {
return errors.New("database back relation value is invalid")
}
if !slices.Contains(backValue.Relation.BlockIDs, value.BlockID) {
backValue.Relation.BlockIDs = append(backValue.Relation.BlockIDs, value.BlockID)
}
}View on GitHub (pinned to 9f775e8a12)