siyuan-note/siyuan · error

source row [%s] not found in attribute view [%s]

Error message

source row [%s] not found in attribute view [%s]

What it means

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.

Source

Thrown at kernel/model/attribute_view.go:2052

// DuplicateAttributeViewRow 创建源行的纯文本副本,复制除主键、Rollup、Created、Updated 外的所有字段值,
// 双向关联同步更新目标属性视图的反向关联列。新行 ID 由调用方(前端)生成并通过 newRowID 传入,
// 副本插入到 previousItemID(通常为最后选中的条目)之后。
func DuplicateAttributeViewRow(tx *Transaction, avID, previousItemID, srcRowID, newRowID string) (err error) {
	attrView, err := av.ParseAttributeView(avID)
	if err != nil {
		logging.LogErrorf("parse attribute view [%s] failed: %s", avID, err)
		return
	}

	blockKeyValues := attrView.GetBlockKeyValues()
	if nil == blockKeyValues {
		err = fmt.Errorf("attribute view [%s] has no block key", avID)
		return
	}

	srcBlockVal := blockKeyValues.GetValue(srcRowID)
	if nil == srcBlockVal {
		err = fmt.Errorf("source row [%s] not found in attribute view [%s]", srcRowID, avID)
		return
	}

	if "" == newRowID {
		newRowID = ast.NewNodeID()
	}

	now := util.CurrentTimeMillis()

	// 创建副本的主键值,强制为纯文本(非绑定块)
	blockContent := ""
	if nil != srcBlockVal.Block {
		blockContent = srcBlockVal.Block.Content
	}
	newBlockVal := &av.Value{
		ID:         ast.NewNodeID(),
		KeyID:      blockKeyValues.Key.ID,
		BlockID:    newRowID,

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Confirm srcRowID exists in the AV's block key values before invoking DuplicateAttributeViewRow.
  2. Refresh the row list on the client when the AV is updated externally (sync, another window) and invalidate cached row IDs.
  3. Surface a user-facing 'row no longer exists' message instead of silently failing.

Example fix

// before
DuplicateAttributeViewRow(tx, avID, prevID, maybeDeletedRowID, newRowID)
// after
rows := attrView.GetBlockKeyValues()
if rows.GetValue(srcRowID) == nil {
  return fmt.Errorf("source row %s no longer exists", srcRowID)
}
DuplicateAttributeViewRow(tx, avID, prevID, srcRowID, newRowID)
Defensive patterns

Strategy: validation

Validate before calling

const blockVals = av.getBlockKeyValues()
const srcExists = blockVals && blockVals.values.some(v => v.blockID === srcRowID)
if (!srcExists) { /* source row is gone; do not call DuplicateAttributeViewRow */ }

Try / catch

try { await duplicateRow(avID, prevID, srcRowID, newRowID) } catch (e) {
  if (/source row .* not found/.test(String(e.message ?? e))) { refreshRows(avID) } else { throw e }
}

Prevention

When it happens

Trigger: 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.

Common situations: Stale selection state in the frontend after a delete/sync; programmatic callers reusing a row ID cached from a previous session.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/89707b7c9df575b3. Report an issue: GitHub.