siyuan-note/siyuan · error

SQL statement is not single

Error message

SQL statement is not single

What it means

CheckSingleStatement rejects an SQL string whose text contains more than one statement — containsMultipleStatements detected a `;` followed by non-whitespace, non-comment content. This guards the SQL query API (used by the embedded SQLite / SiYuan SQL endpoint) against statement stacking/injection. It is a hard validation, not a runtime fault.

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 251596fc0d)

Solutions

  1. Send only one SQL statement per request; remove the second statement.
  2. If a trailing `;` is the only extra content, the validator allows trailing whitespace/comments — verify there is no real second statement after it.
  3. For batch needs, issue separate API calls per statement rather than stacking.

Example fix

// before
stmt := "SELECT * FROM blocks LIMIT 1; SELECT * FROM blocks LIMIT 2"
// after
stmt := "SELECT * FROM blocks LIMIT 1"
Defensive patterns

Strategy: validation

Validate before calling

// Caller side: reject multi-statement SQL before calling the kernel API.
function isSingleStatement(stmt: string): boolean {
  // crude guard: no ';' except possibly one trailing
  const trimmed = stmt.replace(/--[^
]*
/g, ' ').replace(/\/\*[\s\S]*?\*\//g, ' ').trim()
  if (!trimmed.endsWith(';')) return trimmed.split(';').length === 1
  return trimmed.slice(0, -1).split(';').filter(s => s.trim()).length <= 1
}

Try / catch

try { await api.querySQL(stmt) }
catch (e) {
  if (/not single/i.test(String(e))) console.warn('split into one statement per request')
  else throw e
}

Prevention

When it happens

Trigger: Submitting a query like `SELECT * FROM blocks; DROP TABLE blocks;` or `SELECT 1; SELECT 2` to the SQL API (/api/query/sql or CLI sql). Any `;` that is not trailing-whitespace-or-comment triggers it.

Common situations: User pastes multiple queries into the SQL box; a script concatenates queries with `;`; SQL client semicolon-terminates even single queries where the trailing content is another statement.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/13883d5ffad209cb. Report an issue: GitHub.