{"record":{"id":"500174ecf5a584e8","repo":"siyuan-note/siyuan","slug":"database-back-relation-value-is-invalid","errorCode":null,"errorMessage":"database back relation value is invalid","messagePattern":"database back relation value is invalid","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/model/attribute_view_block_history.go","lineNumber":242,"sourceCode":"\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\tback, err := dest.GetKeyValues(relation.BackKeyID)\n\t\t\t\tif err != nil || back.Key.Relation == nil || !back.Key.Relation.IsTwoWay ||\n\t\t\t\t\tback.Key.Relation.AvID != id || back.Key.Relation.BackKeyID != kv.Key.ID {\n\t\t\t\t\treturn errors.New(\"database back relation changed since document deletion\")\n\t\t\t\t}\n\t\t\t\tfor _, targetID := range value.Relation.BlockIDs {\n\t\t\t\t\tif dest.GetBlockValue(targetID) == nil {\n\t\t\t\t\t\treturn fmt.Errorf(\"related database entry [%s] is missing\", targetID)\n\t\t\t\t\t}\n\t\t\t\t\tbackValue := back.GetValue(targetID)\n\t\t\t\t\tif backValue == nil {\n\t\t\t\t\t\tbackValue = &av.Value{ID: ast.NewNodeID(), KeyID: back.Key.ID, BlockID: targetID,\n\t\t\t\t\t\t\tType: av.KeyTypeRelation, Relation: &av.ValueRelation{}}\n\t\t\t\t\t\tback.Values = append(back.Values, backValue)\n\t\t\t\t\t}\n\t\t\t\t\tif backValue.Relation == nil {\n\t\t\t\t\t\treturn errors.New(\"database back relation value is invalid\")\n\t\t\t\t\t}\n\t\t\t\t\tif !slices.Contains(backValue.Relation.BlockIDs, value.BlockID) {\n\t\t\t\t\t\tbackValue.Relation.BlockIDs = append(backValue.Relation.BlockIDs, value.BlockID)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\tif len(restoredItems) == 0 {\n\t\treturn nil\n\t}\n\tfor _, view := range after {\n\t\tregenAttrViewGroups(view)\n\t}\n\treturn tx.saveAttributeViewFieldChanges(&attributeViewFieldsSnapshot{boxID: boxID}, before, after)\n}\n","sourceCodeStart":224,"sourceCodeEnd":259,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/model/attribute_view_block_history.go#L224-L259","documentation":"After locating or creating the back-relation value for a target row, the code requires value.Relation (the ValueRelation struct) to be non-nil before appending the restored block ID. A relation-typed value with a nil Relation payload is internally inconsistent — the row claims to be a relation but carries no relation data — so the rollback aborts rather than dereference a nil pointer.","triggerScenarios":"RollbackDocHistory where the destination database's back-relation value for a target row exists (back.GetValue(targetID) != nil) but its Relation field is nil — i.e. corrupted or malformed value data in the live destination database, or a value created by a buggy writer that left the Relation struct empty.","commonSituations":"Destination database rows whose relation values were hand-edited or corrupted by crashes/sync; values written by older/plugin code paths that omitted the Relation payload; snapshots merged earlier with partially initialized relation values.","solutions":["Fix or delete the malformed relation value in the destination database (re-set the relation on that row via the UI or API) so its Relation payload is populated, then retry the rollback","Remove the null/malformed Relation entry from the destination .av JSON and let the restore recreate it (the code path creates a fresh value when GetValue returns nil)","Roll back to an earlier state of the destination database where its relation values were intact","Rebuild the two-way relation field in the destination database to regenerate well-formed values"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"// Go: check relation payload before appending\nbackValue := back.GetValue(targetID)\nif backValue != nil && backValue.Relation == nil {\n    // malformed value; repair before rollback\n}","typeGuard":"if backValue != nil && backValue.Relation != nil {\n    // safe to append block IDs\n}","tryCatchPattern":"err := tx.restoreBoundAttributeViewHistory(tree, historyDir)\nif err != nil && strings.Contains(err.Error(), \"back relation value is invalid\") {\n    // repair the destination row's relation value, then retry\n}","preventionTips":["Never hand-edit .av JSON value payloads","Repair rows with empty relation values via the UI before rollback","Guard plugin/API writes so relation values always carry a Relation struct","Run integrity checks after crash recovery"],"tags":["database","history","attribute-view","relation","corruption"],"backgroundTag":"internal-invariant-violation","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"}