siyuan-note/siyuan · error

database back relation values

Error message

database back relation values [%s] changed after field deletion

What it means

When a field deletion is undone, replayAttributeViewFields also verifies that the mirrored (back-relation) side's stored relation values still equal what was recorded at deletion time (state.relationValues, compared via reflect.DeepEqual of the block-ID lists). If the opposite database's relation cell values changed in the meantime, the undo would restore an outdated view of them, so the kernel aborts with this error.

Solutions

  1. Refresh the undo snapshot by re-performing the field deletion so relationValues reflects current data, then undo
  2. Restore the expected relation values in the destination database manually before undoing
  3. Give up the undo of that step and re-create the relation field plus values by hand

Example fix

// before
tx.undo(op) // assumes back-relation values untouched
// after
kv, _ := current.GetKeyValues(backKeyID)
if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
    // refresh snapshot or skip this undo step
    return nil
}
Defensive patterns

Strategy: validation

Validate before calling

kv, err := current.GetKeyValues(backKeyID)
if err != nil { return err }
if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
    return fmt.Errorf("back relation values drifted; re-capture before undo")
}

Type guard

func relationValuesMatch(kv *av.KeyValues, expected map[string][]string) bool {
    return reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected)
}

Try / catch

if err := tx.replayAttributeViewFields(op); err != nil {
    if strings.Contains(err.Error(), "back relation values") {
        // abort this undo step; re-create field manually if needed
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Undoing doRemoveAttrViewColumn of a (two-way) relation field when rows in the destination database gained/lost/edited relation values after the deletion snapshot was taken.

Common situations: User or sync edited rows in the target database between the field deletion and the undo attempt; another plugin batch-updated relation cells; undo history spans a sync merge.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_fields.go:262

	for _, id := range sortedAttributeViewFieldKeys(state.changes) {
		current, err := tx.readAttributeViewForMutation(id, state.blockID, state.boxID)
		if err != nil {
			return err
		}
		for keyID, typ := range state.fieldTypes[id] {
			key, keyErr := current.GetKey(keyID)
			if keyErr != nil || key.Type != typ {
				return fmt.Errorf("database field [%s] changed after the operation", keyID)
			}
		}
		// 恢复双向定义时,另一端的单向值必须仍与删除时一致。
		for keyID, expected := range state.relationValues[id] {
			kv, keyErr := current.GetKeyValues(keyID)
			if keyErr != nil {
				return keyErr
			}
			if !reflect.DeepEqual(attributeViewRelationValueIDs(kv.Values), expected) {
				return fmt.Errorf("database back relation values [%s] changed after field deletion", keyID)
			}
		}
		data, err := attributeViewFieldJSON(current)
		if err != nil {
			return err
		}
		patched, _, err := state.changes[id].apply(data, true, op.attributeViewFieldUndo)
		if err != nil {
			return fmt.Errorf("database [%s]: %w", id, err)
		}
		encoded, err := json.Marshal(patched)
		if err != nil {
			return err
		}
		restored := &av.AttributeView{}
		if err = json.Unmarshal(encoded, restored); err != nil {
			return err
		}

View on GitHub (pinned to 9f775e8a12)