siyuan-note/siyuan · error

related database entry

Error message

related database entry [%s] changed

What it means

When replaying an insert containing two-way relation values, each referenced target row must still exist in the destination database and carry a relation value whose back-link list can be extended. If the target row, its back-relation value, or that value's relation data is missing, the kernel cannot merge the back-link without losing the target's other relations, so it fails with this error.

Solutions

  1. Restore or re-create the missing related rows in the target database before replaying
  2. Undo the deletions that removed the referenced rows, then retry the transaction
  3. Re-add the relation links manually between the surviving rows
  4. Before scripted relation writes, verify each referenced row exists (GetBlockValue) and has a non-nil Relation value
Defensive patterns

Strategy: validation

Validate before calling

// confirm every related row exists with a relation value before linking
for (const relAvID of relationAvIDs) {
  const av = await getAttrView(relAvID);
  for (const id of relationBlockIDs) {
    const row = av.keyValues.flatMap(kv => kv.values).find(v => v.blockID === id);
    if (!row || !row.relation) throw new Error(`related entry ${id} missing`);
  }
}

Type guard

function hasRelationValue(row) { return !!row && !!row.relation && Array.isArray(row.relation.blockIDs); }

Try / catch

try { await undo(); } catch (e) { if (/related database entry \[.+\] changed/.test(e.message)) { recreateRelatedRows(); retry(); } else throw e; }

Prevention

When it happens

Trigger: Replaying doInsertAttrViewBlock whose relation values point to rows that were deleted from the target database, whose relation values were cleared, or whose relation value lost its Relation payload since the snapshot.

Common situations: Target rows removed after the original insert (detected on undo/redo); a plugin cleared relation values; sync propagated deletions of the referenced rows.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_items.go:356

			if dest == nil {
				dest, err = av.ParseAttributeViewForIndexInBox(kv.Key.Relation.AvID, state.BoxID)
				if err != nil {
					return err
				}
				if dest == nil {
					return av.ErrViewNotFound
				}
				changed[dest.ID] = dest
			}
			back, backErr := dest.GetKeyValues(kv.Key.Relation.BackKeyID)
			if backErr != nil || back.Key.Type != av.KeyTypeRelation || back.Key.Relation == nil ||
				!back.Key.Relation.IsTwoWay || back.Key.Relation.AvID != state.AvID || back.Key.Relation.BackKeyID != kv.Key.ID {
				return fmt.Errorf("database back relation field [%s] changed", kv.Key.Relation.BackKeyID)
			}
			for _, id := range value.Relation.BlockIDs {
				backValue := back.GetValue(id)
				if dest.GetBlockValue(id) == nil || backValue == nil || backValue.Relation == nil {
					return fmt.Errorf("related database entry [%s] changed", id)
				}
				if !slices.Contains(backValue.Relation.BlockIDs, value.BlockID) {
					backValue.Relation.BlockIDs = append(backValue.Relation.BlockIDs, value.BlockID)
				}
			}
		}
	}
	for _, view := range current.Views {
		view.ItemIDs = restoreAttributeViewItemOrder(view.ItemIDs, state.Orders[view.ID], state.ItemIDs)
		for _, group := range view.Groups {
			group.GroupItemIDs = restoreAttributeViewItemOrder(group.GroupItemIDs, state.Orders[group.ID], state.ItemIDs)
		}
	}
	for id, covers := range state.Covers {
		if covers != nil {
			if current.CardCoverPositions == nil {
				current.CardCoverPositions = map[string]map[string]*av.CardCoverPosition{}
			}

View on GitHub (pinned to 9f775e8a12)