From d97e18cf65a221c3b57ab02f4af092cf65fda888 Mon Sep 17 00:00:00 2001 From: freedom-winds Date: Sat, 24 Oct 2026 23:41:36 +0800 Subject: [PATCH] sqld: fix copy-pasted check in legacy HTTP savepoint guard parse_queries() tested is_release() twice, so SAVEPOINT statements slipped through the guard that is supposed to forbid them: the batch reported success while the write stayed uncommitted inside a never-released savepoint and was rolled back when the connection closed at the end of the request. Check is_savepoint() as well, and cover the guard with unit tests. --- libsql-server/src/http/user/mod.rs | 41 +++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/libsql-server/src/http/user/mod.rs b/libsql-server/src/http/user/mod.rs index 1575a3574b..70e8e7d949 100644 --- a/libsql-server/src/http/user/mod.rs +++ b/libsql-server/src/http/user/mod.rs @@ -110,7 +110,7 @@ fn parse_queries(queries: Vec) -> crate::Result> { // forbid them instead. if out .iter() - .any(|q| q.stmt.kind.is_release() || q.stmt.kind.is_release()) + .any(|q| q.stmt.kind.is_release() || q.stmt.kind.is_savepoint()) { return Err(Error::QueryError( "savepoints are not supported in HTTP API, use hrana protocol instead".to_string(), @@ -153,6 +153,45 @@ async fn handle_query( Ok(res.into_response()) } +#[cfg(test)] +mod tests { + use super::parse_queries; + use crate::http::user::types::HttpQuery; + + fn parse_statements(statements: &[&str]) -> crate::Result> { + let body = serde_json::json!({ "statements": statements }); + let query: HttpQuery = serde_json::from_value(body).unwrap(); + parse_queries(query.statements) + } + + #[test] + fn savepoints_are_rejected() { + for q in ["SAVEPOINT sp", "savepoint sp"] { + let err = parse_statements(&[q, "SELECT 1"]).unwrap_err(); + assert!( + err.to_string().contains("savepoints are not supported"), + "unexpected error for `{q}`: {err}" + ); + } + } + + #[test] + fn release_statements_are_rejected() { + for q in ["RELEASE sp", "RELEASE SAVEPOINT sp", "ROLLBACK TO sp"] { + let err = parse_statements(&[q]).unwrap_err(); + assert!( + err.to_string().contains("savepoints are not supported"), + "unexpected error for `{q}`: {err}" + ); + } + } + + #[test] + fn plain_statements_are_accepted() { + parse_statements(&["SELECT 1", "CREATE TABLE t (x)", "INSERT INTO t VALUES (1)"]).unwrap(); + } +} + async fn show_console( AxumState(AppState { enable_console, .. }): AxumState, ) -> impl IntoResponse {