{"record":{"id":"e11065f041786535","repo":"siyuan-note/siyuan","slug":"sql-statement-is-empty","errorCode":null,"errorMessage":"SQL statement is empty","messagePattern":"SQL statement is empty","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/sql/stmt_validate.go","lineNumber":200,"sourceCode":"\t\treturn err\n\t}\n\treturn CheckReadonlyStatementInBox(stmt, boxID)\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\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)","sourceCodeStart":182,"sourceCodeEnd":218,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/9f775e8a12daef8255556097396f9b2739078892/kernel/sql/stmt_validate.go#L182-L218","documentation":"checkReadonlyStatement validates that a SQL statement handed to a read-only query path is a non-empty SELECT before it is prepared against SQLite. The kernel throws this when the statement is empty or contains only whitespace, because preparing an empty statement cannot yield a result set. It is a defensive pre-flight check that guards all read-only statement entry points.","triggerScenarios":"Calling CheckReadonlyStatement, CheckAssetContentReadonlyStatement, or CheckReadonlyStatementInBox with an empty string, a string of spaces/tabs/newlines, or an argument that was never assigned (e.g. an empty config field or unmarshalled empty JSON string).","commonSituations":"An API caller passes an empty 'stmt' parameter; a frontend query builder produces '' when no SQL was composed; a stored query config field is blank after a version migration.","solutions":["Ensure the SQL string is non-empty before calling any Check*Readonly* function","Validate user/API input at the endpoint boundary and return a 400 with a clear message instead of forwarding an empty stmt","Log the raw stmt value at the call site to find where the empty string originates"],"exampleFix":"// before\nerr := sql.CheckReadonlyStatement(stmt)\n// after\nif strings.TrimSpace(stmt) == \"\" {\n    return errors.New(\"query is empty: provide a SELECT statement\")\n}\nerr := sql.CheckReadonlyStatement(stmt)","handlingStrategy":"validation","validationCode":"function canQuery(stmt) { return typeof stmt === \"string\" && stmt.trim().length > 0; }\nif (!canQuery(stmt)) throw new Error(\"SQL statement required\");","typeGuard":"function isNonEmptyString(v) { return typeof v === \"string\" && v.trim().length > 0; }","tryCatchPattern":"try {\n  await runQuery(stmt);\n} catch (e) {\n  if (String(e.message) === \"SQL statement is empty\") showUserError(\"Provide a SELECT statement\");\n  else throw e;\n}","preventionTips":["Always trim and check SQL strings before submitting to query APIs","Default-fill query fields in configs so they are never empty","Log the statement at the call site to catch empty-value regressions early"],"tags":["sql","validation","kernel"],"backgroundTag":"empty-required-field","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"}