siyuan-note/siyuan · error

SQL statement is empty

Error message

SQL statement is empty

What it means

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.

Solutions

  1. Ensure the SQL string is non-empty before calling any Check*Readonly* function
  2. Validate user/API input at the endpoint boundary and return a 400 with a clear message instead of forwarding an empty stmt
  3. Log the raw stmt value at the call site to find where the empty string originates

Example fix

// before
err := sql.CheckReadonlyStatement(stmt)
// after
if strings.TrimSpace(stmt) == "" {
    return errors.New("query is empty: provide a SELECT statement")
}
err := sql.CheckReadonlyStatement(stmt)
Defensive patterns

Strategy: validation

Validate before calling

function canQuery(stmt) { return typeof stmt === "string" && stmt.trim().length > 0; }
if (!canQuery(stmt)) throw new Error("SQL statement required");

Type guard

function isNonEmptyString(v) { return typeof v === "string" && v.trim().length > 0; }

Try / catch

try {
  await runQuery(stmt);
} catch (e) {
  if (String(e.message) === "SQL statement is empty") showUserError("Provide a SELECT statement");
  else throw e;
}

Prevention

When it happens

Trigger: 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).

Common situations: 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.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of siyuan-note/siyuan@9f775e8a12 (2026-09-19). Data as JSON: /api/errors/e11065f041786535. Report an issue: GitHub.

Appendix: source

Thrown at kernel/sql/stmt_validate.go:200

		return err
	}
	return CheckReadonlyStatementInBox(stmt, boxID)
}

// CheckReadonlyStatementInBox 在指定笔记本对应的数据库连接上检查 SQL 是否只读。
func CheckReadonlyStatementInBox(stmt, boxID string) error {
	targetDB := db
	if boxDB := GetEncryptedDB(boxID); nil != boxDB {
		targetDB = boxDB
	} else if IsEncryptedBoxFn != nil && IsEncryptedBoxFn(boxID) {
		return errors.New("encrypted box db not opened for box " + boxID)
	}
	return checkReadonlyStatement(stmt, targetDB)
}

func checkReadonlyStatement(stmt string, targetDB *sql.DB) error {
	if strings.TrimSpace(stmt) == "" {
		return errors.New("SQL statement is empty")
	}
	if !isReadonlyQueryStatement(stmt) {
		return errors.New("SQL statement is not a read-only query")
	}
	if nil == targetDB {
		return errors.New("database is nil")
	}
	ctx := context.Background()
	conn, err := targetDB.Conn(ctx)
	if err != nil {
		return err
	}
	defer conn.Close()

	return conn.Raw(func(dc any) error {
		sqliteConn, ok := dc.(*sqlite3.SQLiteConn)
		if !ok {
			return fmt.Errorf("SQL driver connection type is unexpected: %T", dc)

View on GitHub (pinned to 9f775e8a12)