From a4ac97129650e5c2f8a4588e9057eed5725a98c6 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 2 Sep 2025 19:55:17 +0300 Subject: [PATCH 1/4] libsql: Reset statement in Statement::execute() We need to reset the statement to be able to reuse it. Let's call reset() after step() like Rusqlite does. Refs: #2135 --- libsql/src/local/statement.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libsql/src/local/statement.rs b/libsql/src/local/statement.rs index c31e751734..d55c4d79b0 100644 --- a/libsql/src/local/statement.rs +++ b/libsql/src/local/statement.rs @@ -124,6 +124,7 @@ impl Statement { pub fn execute(&self, params: &Params) -> Result { self.bind(params); let err = self.inner.step(); + self.inner.reset(); match err { crate::ffi::SQLITE_DONE => Ok(self.conn.changes()), crate::ffi::SQLITE_ROW => Err(Error::ExecuteReturnedRows), From ca1a70ceba796a250d4c4266aa4798097dfd6515 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 2 Sep 2025 19:57:08 +0300 Subject: [PATCH 2/4] libsql: Reset statement in Statement::run() We need to reset the statement to be able to reuse it. Let's call reset() after step() like Rusqlite does. Refs: #2135 --- libsql/src/local/rows.rs | 6 ++++++ libsql/src/local/statement.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/libsql/src/local/rows.rs b/libsql/src/local/rows.rs index 4d4e622c75..98a102d71f 100644 --- a/libsql/src/local/rows.rs +++ b/libsql/src/local/rows.rs @@ -82,6 +82,12 @@ impl AsRef for Rows { } } +impl Drop for Rows { + fn drop(&mut self) { + self.stmt.reset(); + } +} + pub struct RowsFuture { pub(crate) conn: Connection, pub(crate) sql: String, diff --git a/libsql/src/local/statement.rs b/libsql/src/local/statement.rs index d55c4d79b0..d4eeb3c450 100644 --- a/libsql/src/local/statement.rs +++ b/libsql/src/local/statement.rs @@ -53,6 +53,7 @@ impl Statement { pub fn run(&self, params: &Params) -> Result<()> { self.bind(params); let err = self.inner.step(); + self.inner.reset(); match err { crate::ffi::SQLITE_DONE => Ok(()), crate::ffi::SQLITE_ROW => Ok(()), From fbb1efb33ed43daacd689ca2ed849b8a66179066 Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Wed, 3 Sep 2025 12:43:59 +0300 Subject: [PATCH 3/4] libsql: Reset statement in Rows::next() --- libsql/src/local/rows.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/libsql/src/local/rows.rs b/libsql/src/local/rows.rs index 98a102d71f..f76e15a003 100644 --- a/libsql/src/local/rows.rs +++ b/libsql/src/local/rows.rs @@ -46,8 +46,14 @@ impl Rows { err_msg = errors::error_from_handle(self.stmt.conn.raw); } match err { - libsql_sys::ffi::SQLITE_OK => Ok(None), - libsql_sys::ffi::SQLITE_DONE => Ok(None), + libsql_sys::ffi::SQLITE_OK => { + self.stmt.reset(); + Ok(None) + } + libsql_sys::ffi::SQLITE_DONE => { + self.stmt.reset(); + Ok(None) + } libsql_sys::ffi::SQLITE_ROW => Ok(Some(Row { stmt: self.stmt.clone(), })), @@ -82,11 +88,6 @@ impl AsRef for Rows { } } -impl Drop for Rows { - fn drop(&mut self) { - self.stmt.reset(); - } -} pub struct RowsFuture { pub(crate) conn: Connection, From 99b37fa74154b88497332feea334787b103df7bc Mon Sep 17 00:00:00 2001 From: Pekka Enberg Date: Tue, 2 Sep 2025 20:02:26 +0300 Subject: [PATCH 4/4] libsql: Add integration tests for statement reset Refs: #2135 --- libsql/tests/integration_tests.rs | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/libsql/tests/integration_tests.rs b/libsql/tests/integration_tests.rs index 57addab948..32bf95bd53 100644 --- a/libsql/tests/integration_tests.rs +++ b/libsql/tests/integration_tests.rs @@ -854,3 +854,36 @@ fn assert_sqlite_error(res: Result, code: i32) { } } } + +#[tokio::test] +async fn test_prepared_statement_reset() { + // Test for issue #2135 + let db = Database::open(":memory:").unwrap(); + let conn = db.connect().unwrap(); + + conn.execute("CREATE TABLE domain (name TEXT)", ()) + .await + .unwrap(); + + let stmt = conn + .prepare("INSERT INTO domain VALUES (?1)") + .await + .unwrap(); + + let domains = ["example.com", "example.org", "example.net"]; + for domain in domains { + stmt.execute([domain]).await.unwrap(); + } + + let mut rows = conn + .query("SELECT name FROM domain ORDER BY name", ()) + .await + .unwrap(); + let mut results = Vec::new(); + while let Some(row) = rows.next().await.unwrap() { + let name: String = row.get(0).unwrap(); + results.push(name); + } + + assert_eq!(results, vec!["example.com", "example.net", "example.org"]); +}