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
- 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
- Choose an earlier history version where no conflicting binding existed
- 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)
- 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
- Do not re-add deleted blocks to the database before rolling back
- Check existing bindings before manually recreating rows
- Avoid concurrent edits during history rollback
- Prefer full history rollback over partial row re-creation
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
- database back relation changed since document deletion
- database back relation value is invalid
- database binding [ ] already exists
- database block [ ] is already bound
- database field [ ] changed since document deletion
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)