siyuan-note/siyuan · error

database back relation value is invalid

Error message

database back relation value is invalid

What it means

After locating or creating the back-relation value for a target row, the code requires value.Relation (the ValueRelation struct) to be non-nil before appending the restored block ID. A relation-typed value with a nil Relation payload is internally inconsistent — the row claims to be a relation but carries no relation data — so the rollback aborts rather than dereference a nil pointer.

Solutions

  1. Fix or delete the malformed relation value in the destination database (re-set the relation on that row via the UI or API) so its Relation payload is populated, then retry the rollback
  2. Remove the null/malformed Relation entry from the destination .av JSON and let the restore recreate it (the code path creates a fresh value when GetValue returns nil)
  3. Roll back to an earlier state of the destination database where its relation values were intact
  4. Rebuild the two-way relation field in the destination database to regenerate well-formed values
Defensive patterns

Strategy: type-guard

Validate before calling

// Go: check relation payload before appending
backValue := back.GetValue(targetID)
if backValue != nil && backValue.Relation == nil {
    // malformed value; repair before rollback
}

Type guard

if backValue != nil && backValue.Relation != nil {
    // safe to append block IDs
}

Try / catch

err := tx.restoreBoundAttributeViewHistory(tree, historyDir)
if err != nil && strings.Contains(err.Error(), "back relation value is invalid") {
    // repair the destination row's relation value, then retry
}

Prevention

When it happens

Trigger: RollbackDocHistory where the destination database's back-relation value for a target row exists (back.GetValue(targetID) != nil) but its Relation field is nil — i.e. corrupted or malformed value data in the live destination database, or a value created by a buggy writer that left the Relation struct empty.

Common situations: Destination database rows whose relation values were hand-edited or corrupted by crashes/sync; values written by older/plugin code paths that omitted the Relation payload; snapshots merged earlier with partially initialized relation values.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/500174ecf5a584e8. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_block_history.go:242

					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)
					}
				}
			}
		}
	}
	if len(restoredItems) == 0 {
		return nil
	}
	for _, view := range after {
		regenAttrViewGroups(view)
	}
	return tx.saveAttributeViewFieldChanges(&attributeViewFieldsSnapshot{boxID: boxID}, before, after)
}

View on GitHub (pinned to 9f775e8a12)