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

  1. 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
  2. Choose an earlier history version from before the relation was modified
  3. Convert the relation back to two-way with matching BackKeyID if it was changed to one-way
  4. 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

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


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)