siyuan-note/siyuan · error

database entry [ ] has a different binding

Error message

database entry [%s] has a different binding

What it means

During document rollback, restoreBoundAttributeViewHistory merges missing rows from the historical database snapshot back into the current database. For each historical bound (non-detached) row whose block ID is missing from the current data, it requires that no other row in the current database is already bound to that block (GetBlockValue(primary.BlockID) == nil). If a different row already occupies that block binding, the historical row cannot be re-attached and the whole restore is aborted with this error.

Solutions

  1. Remove the conflicting row currently bound to that block ID from the database (or unbind it) so the historical row can be restored, then retry the rollback
  2. Choose an earlier history version where no conflicting binding existed
  3. If the current row is the desired one, exclude the database merge by restoring only the document tree (ensure the snapshot lacks the conflicting rows or skip attribute-view restore)
  4. Manually recreate the deleted rows instead of rollback if bindings are heavily diverged
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: before rollback, detect block-binding conflicts
for _, primary := range historical.GetBlockKeyValues().Values {
    if primary.Block == nil || primary.IsDetached { continue }
    if current.GetBlockValue(primary.BlockID) != nil {
        // conflicting binding exists; resolve before rollback
    }
}

Type guard

if current.GetBlockValueByBoundID(blockID) == nil && current.GetBlockValue(blockID) == nil {
    // safe to restore this row
}

Try / catch

err := tx.restoreBoundAttributeViewHistory(tree, historyDir)
if err != nil {
    var conflictErr *fmt.Errorf
    if strings.Contains(err.Error(), "has a different binding") {
        // unbind/delete the conflicting row, then retry
    }
    return err
}

Prevention

When it happens

Trigger: RollbackDocHistory on a document that contained bound attribute-view rows, where since the deletion someone created a new row in the same database bound to the same document block ID (e.g. via addAttrViewBlock with a blockID, or a re-add after partial restore), while the historical snapshot still lists the original row for that block.

Common situations: User re-added the block to the database manually after deleting the document, then rolled back the document; concurrent edits between deletion and rollback; partial restores that left stale bindings; undo/redo interleaving with history rollback.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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

Appendix: source

Thrown at kernel/model/attribute_view_block_history.go:170

			continue
		}
		if err != nil {
			return err
		}
		current, err := load(id)
		if err != nil {
			return err
		}
		var itemIDs []string
		for _, primary := range historical.GetBlockKeyValues().Values {
			if primary.IsDetached || primary.Block == nil {
				continue
			}
			if _, exists := bound[id][primary.Block.ID]; !exists || current.GetBlockValueByBoundID(primary.Block.ID) != nil {
				continue
			}
			if current.GetBlockValue(primary.BlockID) != nil {
				return fmt.Errorf("database entry [%s] has a different binding", primary.BlockID)
			}
			itemIDs = append(itemIDs, primary.BlockID)
		}
		if len(itemIDs) == 0 {
			continue
		}
		for _, kv := range historical.KeyValues {
			for _, value := range kv.Values {
				if !slices.Contains(itemIDs, value.BlockID) {
					continue
				}
				field, err := current.GetKeyValues(kv.Key.ID)
				if err != nil || field.Key.Type != kv.Key.Type || !reflect.DeepEqual(field.Key.Relation, kv.Key.Relation) {
					return fmt.Errorf("database field [%s] changed since document deletion", kv.Key.ID)
				}
				if field.GetValue(value.BlockID) == nil {
					field.Values = append(field.Values, value.Clone())
				}

View on GitHub (pinned to 9f775e8a12)