{"record":{"id":"15b07efb41947662","repo":"siyuan-note/siyuan","slug":"database-is-nil","errorCode":null,"errorMessage":"database is nil","messagePattern":"database is nil","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/sql/stmt_validate.go","lineNumber":194,"sourceCode":"func 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\n\t}\n\tdefer conn.Close()\n\n\treturn conn.Raw(func(dc any) error {\n\t\tsqliteConn, ok := dc.(*sqlite3.SQLiteConn)\n\t\tif !ok {\n\t\t\treturn fmt.Errorf(\"SQL driver connection type is unexpected: %T\", dc)\n\t\t}\n\t\tds, err := sqliteConn.Prepare(stmt)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tdefer ds.Close()","sourceCodeStart":176,"sourceCodeEnd":212,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/sql/stmt_validate.go#L176-L212","documentation":"CheckReadonlyStatement validates that a SQL statement is safe to run and that a target database handle was supplied. After the statement passes emptiness and read-only checks, it verifies the *sql.DB pointer is non-nil. A nil handle means the caller asked to validate/execute against a database that was never opened or has been closed.","triggerScenarios":"Calling CheckReadonlyStatement, CheckAssetContentReadonlyStatement, or CheckReadonlyStatementInBox with targetDB == nil — typically when sql.InitDatabase has not run yet, the database was closed, or a nil box/asset-content DB handle was passed in.","commonSituations":"Kernel startup ordering issues (query before InitDatabase), calling the /api/query/sql API before workspace init, or tests that construct the validator without opening a DB.","solutions":["Ensure the workspace database is initialized (sql.InitDatabase / database open) before calling CheckReadonlyStatement.","Check that the correct DB handle (global db, asset-content db, or box db) is passed, not nil.","If a box was expected, verify the box is not encrypted-locked and its DB was opened via the encrypted-db path."],"exampleFix":"// before\nif err := sql.CheckReadonlyStatement(nil, stmt); err != nil { ... }\n// after\ntargetDB := sql.GetDB()\nif targetDB == nil {\n    return errors.New(\"database not initialized\")\n}\nif err := sql.CheckReadonlyStatement(targetDB, stmt); err != nil { ... }","handlingStrategy":"type-guard","validationCode":"if (typeof targetDB === 'undefined' || targetDB === null) { throw new Error('target database handle is not initialized') }","typeGuard":"function hasDB(db) { return db != null && typeof db.query === 'function' }","tryCatchPattern":"try { checkReadonly(db, stmt) } catch (e) { if (String(e.message).includes('database is nil')) { await initDatabase(); retry() } else { throw e } }","preventionTips":["Always initialize/open the database before running SQL validation","Centralize DB handle retrieval in one accessor that lazy-initializes","In tests, assert the DB handle is non-nil in setup"],"tags":["database","sql","null-check","initialization"],"backgroundTag":"null-argument","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}