{"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":2052,"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":2034,"sourceCodeEnd":2070,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/251596fc0de2f9528c00c224252fd073a99973f4/kernel/model/attribute_view.go#L2034-L2070","documentation":"Thrown by DuplicateAttributeViewRow when blockKeyValues.GetValue(srcRowID) returns nil, i.e. the row selected for duplication does not have a value in the primary (block) key column. The source row must exist in the AV before it can be copied.","triggerScenarios":"Passing a srcRowID that was already deleted, belongs to another AV, or was never committed; calling duplicate during a sync that has not yet pulled the row.","commonSituations":"Stale selection state in the frontend after a delete/sync; programmatic callers reusing a row ID cached from a previous session.","solutions":["Confirm srcRowID exists in the AV's block key values before invoking DuplicateAttributeViewRow.","Refresh the row list on the client when the AV is updated externally (sync, another window) and invalidate cached row IDs.","Surface a user-facing 'row no longer exists' message instead of silently failing."],"exampleFix":"// before\nDuplicateAttributeViewRow(tx, avID, prevID, maybeDeletedRowID, newRowID)\n// after\nrows := attrView.GetBlockKeyValues()\nif rows.GetValue(srcRowID) == nil {\n  return fmt.Errorf(\"source row %s no longer exists\", srcRowID)\n}\nDuplicateAttributeViewRow(tx, avID, prevID, srcRowID, newRowID)","handlingStrategy":"validation","validationCode":"const blockVals = av.getBlockKeyValues()\nconst srcExists = blockVals && blockVals.values.some(v => v.blockID === srcRowID)\nif (!srcExists) { /* source row is gone; do not call DuplicateAttributeViewRow */ }","typeGuard":null,"tryCatchPattern":"try { await duplicateRow(avID, prevID, srcRowID, newRowID) } catch (e) {\n  if (/source row .* not found/.test(String(e.message ?? e))) { refreshRows(avID) } else { throw e }\n}","preventionTips":["Invalidate cached row IDs when the AV is modified externally.","Confirm the source row exists immediately before invoking duplicate."],"tags":["attribute-view","row-not-found","row-duplicate","transaction"],"backgroundTag":null,"analyzedSha":"251596fc0de2f9528c00c224252fd073a99973f4","analyzedAt":"2026-08-12T21:18:37.123Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}