{"record":{"id":"89707b7c9df575b3","repo":"siyuan-note/siyuan","slug":"source-row-s-not-found-in-attribute-view-s","errorCode":null,"errorMessage":"source row [%s] not found in attribute view [%s]","messagePattern":"source row \\[(.+?)\\] not found in attribute view \\[(.+?)\\]","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view.go","lineNumber":2199,"sourceCode":"// DuplicateAttributeViewRow 创建源行的纯文本副本，复制除主键、Rollup、Created、Updated 外的所有字段值，\n// 双向关联同步更新目标属性视图的反向关联列。新行 ID 由调用方（前端）生成并通过 newRowID 传入，\n// 副本插入到 previousItemID（通常为最后选中的条目）之后。\nfunc DuplicateAttributeViewRow(tx *Transaction, avID, previousItemID, srcRowID, newRowID string) (err error) {\n\tattrView, err := av.ParseAttributeView(avID)\n\tif err != nil {\n\t\tlogging.LogErrorf(\"parse attribute view [%s] failed: %s\", avID, err)\n\t\treturn\n\t}\n\n\tblockKeyValues := attrView.GetBlockKeyValues()\n\tif nil == blockKeyValues {\n\t\terr = fmt.Errorf(\"attribute view [%s] has no block key\", avID)\n\t\treturn\n\t}\n\n\tsrcBlockVal := blockKeyValues.GetValue(srcRowID)\n\tif nil == srcBlockVal {\n\t\terr = fmt.Errorf(\"source row [%s] not found in attribute view [%s]\", srcRowID, avID)\n\t\treturn\n\t}\n\n\tif \"\" == newRowID {\n\t\tnewRowID = ast.NewNodeID()\n\t}\n\n\tnow := util.CurrentTimeMillis()\n\n\t// 创建副本的主键值，强制为纯文本（非绑定块）\n\tblockContent := \"\"\n\tif nil != srcBlockVal.Block {\n\t\tblockContent = srcBlockVal.Block.Content\n\t}\n\tnewBlockVal := &av.Value{\n\t\tID:         ast.NewNodeID(),\n\t\tKeyID:      blockKeyValues.Key.ID,\n\t\tBlockID:    newRowID,","sourceCodeStart":2181,"sourceCodeEnd":2217,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view.go#L2181-L2217","documentation":"When copying a row, the source row's block value is fetched with blockKeyValues.GetValue(srcRowID). If no value exists for that row ID in the block key, the source row does not exist in this attribute view and this error is returned. It prevents duplicating a non-existent or already-deleted row.","triggerScenarios":"Row-duplication operations invoked with a srcRowID that was deleted, belongs to another database, or was never a row of the given avID.","commonSituations":"Stale UI state after another client deleted the row; passing a document block ID rather than the database row ID; race conditions between sync and a local duplicate action.","solutions":["Re-fetch the database rows and use a currently existing row ID as srcRowID.","Confirm the srcRowID is the row's block ID within the same avID, not a block from elsewhere.","Refresh/sync the view and retry; if the row was deleted intentionally, discard the duplicate action."],"exampleFix":"// before\nawait duplicateRow(avID, \"20240101-deleted-row\");\n// after\nconst rows = await getAttrViewRows(avID);\nif (rows.some(r => r.id === srcRowID)) await duplicateRow(avID, srcRowID);","handlingStrategy":"validation","validationCode":"const rows = await getAttrViewRows(avID);\nif (!rows.some(r => r.id === srcRowID)) throw new Error(`source row ${srcRowID} missing from ${avID}`);","typeGuard":null,"tryCatchPattern":"try { await duplicateRow(avID, srcRowID); } catch (e) { if (String(e).includes(\"source row\") && String(e).includes(\"not found\")) await refreshRowsAndNotifyUser(); else throw e; }","preventionTips":["Validate the source row exists immediately before duplicating","Refresh local state after sync before row operations","Use row IDs from the database API, not document block IDs"],"tags":["attribute-view","row-not-found","duplicate-row"],"backgroundTag":"record-not-found","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"}