siyuan-note/siyuan · error

database entry [ ] has newer field values

Error message

database entry [%s] has newer field values

What it means

In the same replay check, prepareAttributeViewItemRemoval walks the live database's KeyValues and, for every row scheduled for removal (op.attributeViewItems.ItemIDs), requires that the current value deep-equals the value recorded in the snapshot. If the live row carries values newer than the snapshot (row was edited after the deletion snapshot captured the 'older' state), it fails with 'database entry [%s] has newer field values' rather than deleting fresher data.

Solutions

  1. Resolve the newer edits first (keep or merge them), then re-issue the removal without the stale snapshot
  2. Undo the row edits before undoing the deletion so the snapshot matches again
  3. Refresh the snapshot by performing the deletion anew on the current data

Example fix

// before
tx.removeAttrViewBlock(avID, itemIDs) // stale snapshot deletes newer edits
// after
for _, kv := range current.KeyValues {
    for _, v := range kv.Values {
        if slices.Contains(itemIDs, v.BlockID) && !reflect.DeepEqual(v, snapValue) {
            // abort and re-capture snapshot from current data
            return nil
        }
    }
}
Defensive patterns

Strategy: validation

Validate before calling

for _, kv := range current.KeyValues {
    for _, v := range kv.Values {
        if slices.Contains(itemIDs, v.BlockID) && !reflect.DeepEqual(v, av.GetValue(snap.Keys, kv.Key.ID, v.BlockID)) {
            return fmt.Errorf("row %s has newer values; re-capture snapshot", v.BlockID)
        }
    }
}

Type guard

func rowsUnchangedSince(current *av.AttributeView, snap *model.AttributeViewItemsSnapshot, ids []string) bool {
    for _, kv := range current.KeyValues {
        for _, v := range kv.Values {
            if slices.Contains(ids, v.BlockID) && !reflect.DeepEqual(v, av.GetValue(snap.Keys, kv.Key.ID, v.BlockID)) { return false }
        }
    }
    return true
}

Try / catch

if err := tx.prepareAttributeViewItemRemoval(op); err != nil {
    if strings.Contains(err.Error(), "has newer field values") {
        // keep newer data: re-issue deletion from current state instead of stale undo
        return nil
    }
    return err
}

Prevention

When it happens

Trigger: Undo replay of doRemoveAttrViewBlock where a row present in ItemIDs was modified after the deletion snapshot was taken, so the current cell differs from op.attributeViewItems.Keys (checked only for rows in ItemIDs, complementing errorIndex 957 which checks snapshot values against current).

Common situations: User deleted a row via the database UI while a background process/plugin kept updating that row's cells; sync applied remote edits to the row after local deletion; undo stack replayed partially across sessions.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_items.go:62

		current, err := tx.readAttributeViewItems(op.attributeViewItems)
		if err != nil {
			return err
		}
		if err = validateAttributeViewItemKeys(current, op.attributeViewItems); err != nil {
			return err
		}
		for _, kv := range op.attributeViewItems.Keys {
			for _, value := range kv.Values {
				if !reflect.DeepEqual(value, av.GetValue(current.KeyValues, kv.Key.ID, value.BlockID)) {
					return fmt.Errorf("database entry [%s] changed since deletion was undone", value.BlockID)
				}
			}
		}
		for _, kv := range current.KeyValues {
			for _, value := range kv.Values {
				if slices.Contains(op.attributeViewItems.ItemIDs, value.BlockID) &&
					!reflect.DeepEqual(value, av.GetValue(op.attributeViewItems.Keys, kv.Key.ID, value.BlockID)) {
					return fmt.Errorf("database entry [%s] has newer field values", value.BlockID)
				}
			}
		}
		if err = tx.prepareAttributeViewItemMutation(current, op.attributeViewItems); err != nil {
			return err
		}
		return tx.replayAttributeViewItemConfiguration(op.attributeViewItems, false)
	}
	if tx.isReplay || !tx.fromAPI || len(tx.UndoOperations) == 0 {
		return nil
	}
	current, err := avParseView(op.AvID, op.BlockID)
	if err != nil {
		return err
	}
	if current == nil {
		return av.ErrViewNotFound
	}

View on GitHub (pinned to 9f775e8a12)