From 1cb0b32bd1bf2bfb9a54dac7576016f073ad7e06 Mon Sep 17 00:00:00 2001 From: Oleksii Date: Wed, 10 Jun 2026 06:53:50 -0300 Subject: [PATCH] refactor(storage): build mobile-command ack IN-lists with sqlx QueryBuilder MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces format!-assembled placeholder SQL with QueryBuilder push_bind in both backends — values were already bound, so this is hardening and consistency, not a live injection fix. Co-Authored-By: Claude Fable 5 --- orch8-storage/src/postgres/mobile_sync.rs | 20 +++++++++----------- orch8-storage/src/sqlite/mod.rs | 16 +++++++++------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/orch8-storage/src/postgres/mobile_sync.rs b/orch8-storage/src/postgres/mobile_sync.rs index 31b48e8c..ec19d704 100644 --- a/orch8-storage/src/postgres/mobile_sync.rs +++ b/orch8-storage/src/postgres/mobile_sync.rs @@ -571,20 +571,18 @@ impl crate::MobileSyncStore for PostgresStorage { if command_ids.is_empty() { return Ok(0); } - let placeholders: Vec = command_ids - .iter() - .enumerate() - .map(|(i, _)| format!("${}", i + 2)) - .collect(); - let sql = format!( - "UPDATE mobile_commands SET acked_at = now() WHERE device_id = $1 AND id IN ({})", - placeholders.join(",") + let mut qb = sqlx::QueryBuilder::new( + "UPDATE mobile_commands SET acked_at = now() WHERE device_id = ", ); - let mut query = sqlx::query(&sql).bind(device_id); + qb.push_bind(device_id); + qb.push(" AND id IN ("); + let mut separated = qb.separated(", "); for id in command_ids { - query = query.bind(id); + separated.push_bind(id); } - let result = query + separated.push_unseparated(")"); + let result = qb + .build() .execute(&self.pool) .await .map_err(|e| StorageError::Query(e.to_string()))?; diff --git a/orch8-storage/src/sqlite/mod.rs b/orch8-storage/src/sqlite/mod.rs index a3cb32ae..8d725385 100644 --- a/orch8-storage/src/sqlite/mod.rs +++ b/orch8-storage/src/sqlite/mod.rs @@ -2673,16 +2673,18 @@ impl crate::MobileSyncStore for SqliteStorage { if command_ids.is_empty() { return Ok(0); } - let placeholders: Vec<&str> = command_ids.iter().map(|_| "?").collect(); - let sql = format!( - "UPDATE mobile_commands SET acked_at = datetime('now') WHERE device_id = ? AND id IN ({})", - placeholders.join(",") + let mut qb: sqlx::QueryBuilder<'_, sqlx::Sqlite> = sqlx::QueryBuilder::new( + "UPDATE mobile_commands SET acked_at = datetime('now') WHERE device_id = ", ); - let mut query = sqlx::query(&sql).bind(device_id); + qb.push_bind(device_id); + qb.push(" AND id IN ("); + let mut separated = qb.separated(", "); for id in command_ids { - query = query.bind(id); + separated.push_bind(id); } - let result = query + separated.push_unseparated(")"); + let result = qb + .build() .execute(&self.pool) .await .map_err(|e| StorageError::Query(e.to_string()))?;