siyuan-note/siyuan · error

database binding [ ] already exists

Error message

database binding [%s] already exists

What it means

restoreDeletedAttributeViewBlocks is the undo operation (inverse of insertAttrViewBlock) that re-inserts database rows deleted in the same transaction and re-binds them to their document blocks. Before replaying, it verifies that no live row in the database is already bound to each block ID (GetBlockValueByBoundID(id) == nil). If a row is already bound to that block, re-inserting would create a duplicate binding, so the undo fails with the block ID.

Solutions

  1. Delete the row currently bound to that block ID in the database so the undo can restore the original row, then retry the undo
  2. Undo again from an earlier point in the history panel instead of transactional undo
  3. Verify the existing binding points to the intended row; if it is already correct, no undo of the deletion is needed for that row — remove its entry from the undo operation
  4. Check for duplicate bindings via the database UI and clean up stale rows before retrying
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: before undoing a row deletion, check the binding is free
if current.GetBlockValueByBoundID(blockID) != nil {
    // a row is already bound; resolve before undo
}

Type guard

if current.GetBlockValueByBoundID(id) == nil {
    // safe to re-insert and bind
}

Try / catch

err := tx.restoreDeletedAttributeViewBlocks(op)
if err != nil && strings.Contains(err.Error(), "already exists") {
    // remove the conflicting row or skip this Src entry, then retry
}

Prevention

When it happens

Trigger: Undoing a transaction that deleted attribute-view rows, where a row bound to the same document block was meanwhile re-created in the same database (e.g. the user re-added the block to the database, or a prior undo/redo partially restored bindings), so current.GetBlockValueByBoundID(id) returns non-nil.

Common situations: Redo/undo cycles interleaved with manual re-adds of the block to the database; repeated undo invocations after a partially applied undo; concurrent clients (sync or multiple windows) re-creating rows for the same block.

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/3127e258a41bd7ed. Report an issue: GitHub.

Appendix: source

Thrown at kernel/model/attribute_view_block_undo.go:120

func (tx *Transaction) restoreDeletedAttributeViewBlocks(op *Operation) error {
	state := op.attributeViewFields
	if err := tx.rememberAttributeViewMutationTree(state.blockID); err != nil {
		return err
	}
	for _, src := range op.Srcs {
		id, _ := src["id"].(string)
		if err := tx.rememberAttributeViewMutationTree(id); err != nil {
			return err
		}
	}
	current, err := tx.readAttributeViewForMutation(op.AvID, state.blockID, state.boxID)
	if err != nil {
		return err
	}
	for _, src := range op.Srcs {
		id, _ := src["id"].(string)
		if current.GetBlockValueByBoundID(id) != nil {
			return fmt.Errorf("database binding [%s] already exists", id)
		}
		node, tree, err := getNodeByBlockID(tx, id)
		if err != nil {
			return err
		}
		if node == nil {
			return fmt.Errorf("restored database binding [%s] is missing", id)
		}
		if err = validateAttributeViewBinding(op.AvID, tree); err != nil {
			return err
		}
	}
	if err := tx.replayAttributeViewFields(op); err != nil {
		return err
	}
	restored, err := tx.readAttributeViewForMutation(op.AvID, state.blockID, state.boxID)
	if err != nil {
		return err

View on GitHub (pinned to 9f775e8a12)