siyuan-note/siyuan · error

database back relation field

Error message

database back relation field [%s] changed

What it means

During replay of an insert operation containing two-way relation values, the kernel checks the target database's back-relation field: it must still be a relation key, still two-way, and still point back to this database and key (matching AvID and BackKeyID). If the relation pair was edited, unlinked, or reconfigured since the snapshot, replaying would write into a mismatched field, so it aborts.

Solutions

  1. Restore the relation field's two-way configuration (same AvID/BackKeyID) before replaying the operation
  2. Undo the field-configuration change that broke the relation pair, then retry
  3. Re-create the two-way relation between the two databases and re-add the entries manually instead of replaying
  4. Check both databases' key definitions (Type, Relation.IsTwoWay, AvID, BackKeyID) for consistency before scripted edits
Defensive patterns

Strategy: validation

Validate before calling

// verify the back-relation field config before writing relations
const target = await getAttrView(relAvID);
const backKey = target.keyTypes[relBackKeyID];
if (!backKey || backKey.type !== 'relation' || !backKey.relation?.isTwoWay || backKey.relation.avID !== srcAvID || backKey.relation.backKeyID !== srcKeyID) throw new Error('back relation field changed');

Type guard

function isTwoWayBackRelation(k, srcAvID, srcKeyID) { return !!k && k.type === 'relation' && !!k.relation && k.relation.isTwoWay === true && k.relation.avID === srcAvID && k.relation.backKeyID === srcKeyID; }

Try / catch

try { await insertAttrViewBlock(avID, blockID, items); } catch (e) { if (/back relation field \[.+\] changed/.test(e.message)) { restoreRelationConfig(); retry(); } else throw e; }

Prevention

When it happens

Trigger: Replaying doInsertAttrViewBlock with two-way relation values after the back-relation field in the target database was deleted, converted to another type, changed to one-way, or re-pointed to a different key/AV.

Common situations: User edited relation column configuration between the original operation and undo/redo; a plugin or script rewired two-way relation metadata; sync merged divergent relation field definitions.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — 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/8926d1d9f32f7a77. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_items.go:351

		for _, value := range kv.Values {
			if value.Relation == nil || len(value.Relation.BlockIDs) == 0 {
				continue
			}
			dest := changed[kv.Key.Relation.AvID]
			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)
		}
	}

View on GitHub (pinned to 9f775e8a12)