{"record":{"id":"a66599c35a5963bf","repo":"siyuan-note/siyuan","slug":"encrypted-box-db-not-opened-for-box-s","errorCode":null,"errorMessage":"encrypted box db not opened for box %s","messagePattern":"encrypted box db not opened for box (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/sql/stmt_validate.go","lineNumber":181,"sourceCode":"// 注意：若字符串里在语法上还有第二条及以后的语句，本函数只针对「首条」对应的 stmt 做判断，\n// 不会拒绝多语句。与 CheckSingleStatement 组合即可得到「单条 + 只读」策略。\n// 仅允许 SELECT 和 WITH 查询，避免 SQLite 将 ATTACH、DETACH 和事务控制语句标记为只读后放行。\nfunc CheckReadonlyStatement(stmt string) error {\n\treturn checkReadonlyStatement(stmt, db)\n}\n\n// CheckAssetContentReadonlyStatement 在资源文件内容数据库连接上检查 SQL 是否只读。\nfunc CheckAssetContentReadonlyStatement(stmt string) error {\n\treturn checkReadonlyStatement(stmt, assetContentDB)\n}\n\n// CheckReadonlyStatementInBox 在指定笔记本对应的数据库连接上检查 SQL 是否只读。\nfunc CheckReadonlyStatementInBox(stmt, boxID string) error {\n\ttargetDB := db\n\tif boxDB := GetEncryptedDB(boxID); nil != boxDB {\n\t\ttargetDB = boxDB\n\t} else if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {\n\t\treturn errors.New(\"encrypted box db not opened for box \" + boxID)\n\t}\n\treturn checkReadonlyStatement(stmt, targetDB)\n}\n\nfunc checkReadonlyStatement(stmt string, targetDB *sql.DB) error {\n\tif strings.TrimSpace(stmt) == \"\" {\n\t\treturn errors.New(\"SQL statement is empty\")\n\t}\n\tif !isReadonlyQueryStatement(stmt) {\n\t\treturn errors.New(\"SQL statement is not a read-only query\")\n\t}\n\tif nil == targetDB {\n\t\treturn errors.New(\"database is nil\")\n\t}\n\tctx := context.Background()\n\tconn, err := targetDB.Conn(ctx)\n\tif err != nil {\n\t\treturn err","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/sql/stmt_validate.go#L163-L199","documentation":"CheckReadonlyStatementInBox validates a SQL statement on the database connection belonging to a specific notebook (box). If the box is marked encrypted but its per-box SQLite database handle has not been opened (GetEncryptedDB returned nil), validation cannot proceed safely and the function fails with this error instead of falling back to the main (non-encrypted) connection.","triggerScenarios":"Calling CheckReadonlyStatementInBox(stmt, boxID) where IsEncryptedBoxFn(boxID) is true but GetEncryptedDB(boxID) is nil — i.e. the encrypted box's DB was never opened (e.g. before unlock/initialization or after it was closed) (kernel/sql/stmt_validate.go:181).","commonSituations":"Querying an encrypted notebook before the user unlocked it; the encrypted box DB was closed after an error and not reopened; a race where a query arrives during box load; referencing a box ID for an encrypted notebook whose key material is unavailable.","solutions":["Ensure the encrypted box is unlocked/opened first so GetEncryptedDB(boxID) returns a handle before validating SQL","Re-trigger opening of the box DB (reload the notebook) and retry the query","Verify the box ID is correct and the notebook is actually loaded in this workspace","If you control the code, check GetEncryptedDB(boxID) != nil (or skip validation for closed boxes) before calling CheckReadonlyStatementInBox"],"exampleFix":"// before\nif err := sql.CheckReadonlyStatementInBox(stmt, boxID); err != nil { ... }\n// after\nif sql.GetEncryptedDB(boxID) == nil {\n\treturn fmt.Errorf(\"box %s is not open yet\", boxID)\n}\nif err := sql.CheckReadonlyStatementInBox(boxID, stmt); err != nil { ... }","handlingStrategy":"validation","validationCode":"// Go: only validate when the encrypted box DB is actually open\nif sql.GetEncryptedDB(boxID) == nil {\n\treturn errors.New(\"encrypted box not open: \" + boxID)\n}\nerr := sql.CheckReadonlyStatementInBox(stmt, boxID)","typeGuard":"func encryptedBoxReady(boxID string) bool { return sql.GetEncryptedDB(boxID) != nil }","tryCatchPattern":"if err := sql.CheckReadonlyStatementInBox(stmt, boxID); err != nil {\n\tif strings.HasPrefix(err.Error(), \"encrypted box db not opened\") {\n\t\t// prompt unlock / reopen the notebook, then retry\n\t}\n}","preventionTips":["Unlock/load encrypted notebooks before issuing SQL against them","Check GetEncryptedDB(boxID) in preconditions","Retry after notebook reload instead of failing hard","Never fall back to the main DB for encrypted boxes"],"tags":["sql","encryption","database","state","go"],"backgroundTag":"encrypted-db-not-opened","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}