siyuan-note/siyuan · error
SQL statement is not single
Error message
SQL statement is not single
What it means
CheckSingleStatement validates that a user-supplied SQL string contains exactly one statement. containsMultipleStatements (semicolon-aware parsing) detects more than one statement (e.g. `SELECT 1; DROP TABLE x`), which the read-only query path forbids because multi-statement input could smuggle writes.
Solutions
- Reduce the SQL to a single statement, removing any semicolon-separated statements after the first
- Remove trailing semicolons/empty statements if the validator counts them as extra statements
- Use only one SELECT per API call
Example fix
// before const q = "SELECT * FROM blocks LIMIT 10; SELECT count(*) FROM blocks;" // after const q = "SELECT * FROM blocks LIMIT 10;"
Defensive patterns
Strategy: validation
Validate before calling
const stmtCount = sql.split(";").map(s => s.trim()).filter(Boolean).length;
if (stmtCount > 1) throw new Error("Only a single SQL statement is allowed"); Try / catch
if err := sql.CheckSingleStatement(userSQL); err != nil {
return showSqlValidationError(err)
} Prevention
- Strip or reject extra semicolon-separated statements in user input before submitting
- Validate SQL client-side before calling query APIs
- Never concatenate multiple statements when building queries programmatically
When it happens
Trigger: Calling CheckSingleStatement via sqlQuery, searchAssetContentBySQL, validateDocumentStatQuery, QueryAssetContentNoLimit, or SelectAssetContentsRawStmt with SQL containing multiple semicolon-separated statements.
Common situations: Users pasting multi-statement SQL into SQL query panels; generated SQL that appends a trailing extra statement; copy-pasted snippets ending with two statements instead of one.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Argon2id Iterations too low (minimum 3)
- asset path escapes data directory
- asset path escapes data directory
- encrypted box db not opened for box
- export path is outside export directory
AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19).
Data as JSON: /api/errors/13883d5ffad209cb.
Report an issue: GitHub.
Appendix: source
Thrown at kernel/sql/stmt_validate.go:155
i++
case '/' == ch && next == '*':
inBlockComment = true
i++
case ';' == ch:
tail := string(runes[i+1:])
if tailIsOnlyWhitespaceOrSQLComments(tail) {
// 分号后仅有空白与 SQL 注释时,SQLite 仍视为同一条语句末尾,不应判为多语句。
continue
}
return true
}
}
return false
}
func CheckSingleStatement(stmt string) error {
if containsMultipleStatements(stmt) {
return errors.New("SQL statement is not single")
}
return nil
}
// CheckReadonlyStatement 对整段 SQL 做 prepare(不执行),用 sqlite3_stmt_readonly 判断首条语句是否只读。
// 见 https://sqlite.org/c3ref/stmt_readonly.html
//
// 注意:若字符串里在语法上还有第二条及以后的语句,本函数只针对「首条」对应的 stmt 做判断,
// 不会拒绝多语句。与 CheckSingleStatement 组合即可得到「单条 + 只读」策略。
// 仅允许 SELECT 和 WITH 查询,避免 SQLite 将 ATTACH、DETACH 和事务控制语句标记为只读后放行。
func CheckReadonlyStatement(stmt string) error {
return checkReadonlyStatement(stmt, db)
}
// CheckAssetContentReadonlyStatement 在资源文件内容数据库连接上检查 SQL 是否只读。
func CheckAssetContentReadonlyStatement(stmt string) error {
return checkReadonlyStatement(stmt, assetContentDB)
}View on GitHub (pinned to 9f775e8a12)