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

  1. Reduce the SQL to a single statement, removing any semicolon-separated statements after the first
  2. Remove trailing semicolons/empty statements if the validator counts them as extra statements
  3. 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

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


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)