{"record":{"id":"2f59f14adb930a35","repo":"siyuan-note/siyuan","slug":"database-field-s-changed-since-entries-were-deleted","errorCode":null,"errorMessage":"database field [%s] changed since entries were deleted","messagePattern":"database field \\[(.+?)\\] changed since entries were deleted","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_items.go","lineNumber":420,"sourceCode":"\t\tattrs[av.NodeAttrNameAvs] = strings.Trim(strings.Join(ids, \",\"), \",\")\n\t\tattrs[av.NodeAttrViewNames] = getAvNames(attrs[av.NodeAttrNameAvs])\n\t\tif err = setNodeAttrsWithTx(tx, node, tree, attrs); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\trefreshRelatedSrcAvsInBlock(state.AvID, state.BlockID, tx)\n\tif err = tx.replayAttributeViewItemConfiguration(state, true); err != nil {\n\t\treturn err\n\t}\n\top.RetData = &insertAttrViewBlockResult{InsertedItemIDs: append([]string(nil), state.ItemIDs...)}\n\treturn nil\n}\n\nfunc validateAttributeViewItemKeys(current *av.AttributeView, state *attributeViewItemsSnapshot) error {\n\tfor _, kv := range state.Keys {\n\t\tkey, err := current.GetKey(kv.Key.ID)\n\t\tif err != nil || key.Type != kv.Key.Type || !reflect.DeepEqual(key.Relation, kv.Key.Relation) {\n\t\t\treturn fmt.Errorf(\"database field [%s] changed since entries were deleted\", kv.Key.ID)\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc restoreAttributeViewItemOrder(current, original, itemIDs []string) []string {\n\tfor index, id := range original {\n\t\tif !slices.Contains(itemIDs, id) || slices.Contains(current, id) {\n\t\t\tcontinue\n\t\t}\n\t\tposition := -1\n\t\tfor previous := index - 1; previous >= 0; previous-- {\n\t\t\tif found := slices.Index(current, original[previous]); found >= 0 {\n\t\t\t\tposition = found + 1\n\t\t\t\tbreak\n\t\t\t}\n\t\t}\n\t\tif position < 0 {","sourceCodeStart":402,"sourceCodeEnd":438,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_items.go#L402-L438","documentation":"validateAttributeViewItemKeys compares each key snapshot (taken when entries were removed or inserted) against the current database: the key must still exist with the same type and an identical Relation config (compared with reflect.DeepEqual). If the field was deleted, retyped, or its relation configuration changed, restoring the saved entries would write into a structurally different field, so the replay aborts.","triggerScenarios":"Replaying prepareAttributeViewItemRemoval or restoreAttributeViewItems after the captured fields were deleted, type-converted (e.g. text to number), or had their Relation (AvID/BackKeyID/IsTwoWay) reconfigured since the snapshot was taken.","commonSituations":"Undoing an entry removal after later editing the column type or relation wiring; a plugin renamed/retyped columns via API between operations; sync merged a schema change into the database.","solutions":["Undo the column type/relation change so the field matches the snapshot before replaying entry operations","Re-create a field with the same ID, type, and relation config if it was deleted","Re-add the removed entries manually instead of replaying the old transaction","When scripting schema changes, check for pending undo history on the database first and avoid interleaving type changes with row edits"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// compare current field type/relation against a saved snapshot before restoring entries\nconst cur = await getAttrView(avID);\nfor (const k of snapshotKeys) {\n  const now = cur.keyTypes[k.id];\n  if (!now || now.type !== k.type || JSON.stringify(now.relation) !== JSON.stringify(k.relation)) throw new Error(`field ${k.id} changed`);\n}","typeGuard":null,"tryCatchPattern":"try { await restoreEntries(snapshot); } catch (e) { if (/changed since entries were deleted/.test(e.message)) { await undoSchemaChange(); retryRestore(); } else throw e; }","preventionTips":["Finish entry add/remove replays before changing column types or relation wiring","Capture fresh snapshots instead of reusing stale ones after schema edits","Keep schema migrations and row edits in separate undo units"],"tags":["database","schema-change","transaction-replay","attribute-view"],"backgroundTag":"schema-validation-failed","analyzedSha":"9f775e8a12daef8255556097396f9b2739078892","analyzedAt":"2026-09-19T03:17:15.984Z","contentChangedAt":"2026-09-19T03:17:15.984Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}