Skip to content

Commit d613ca0

Browse files
committed
fix(odbc): populate the statement cache on the query path
`Executor::fetch_many` only ever read `stmt_cache`, so `statement_cache_capacity` and `Execute::persistent()` were empty and all queries ran through SQLExecDirect. `execute_stream` cannot borrow `&mut self`, so prepared satements weren't processed correclty. Now `execute_steam` is async and a query with arguments runs as a prepared statement, which fixes drivers taht ignore values bound before `SQLExecDirect`. Raw SQL still executes directly.
1 parent 394c059 commit d613ca0

3 files changed

Lines changed: 127 additions & 38 deletions

File tree

‎sqlx-core/src/odbc/connection/executor.rs‎

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ use crate::odbc::{Odbc, OdbcConnection, OdbcQueryResult, OdbcRow, OdbcStatement,
55
use either::Either;
66
use futures_core::future::BoxFuture;
77
use futures_core::stream::BoxStream;
8-
use futures_util::TryStreamExt;
8+
use futures_util::{TryFutureExt, TryStreamExt};
99

1010
impl<'c> Executor<'c> for &'c mut OdbcConnection {
1111
type Database = Odbc;
@@ -18,8 +18,15 @@ impl<'c> Executor<'c> for &'c mut OdbcConnection {
1818
'c: 'e,
1919
E: Execute<'q, Self::Database> + 'q,
2020
{
21+
let sql = query.sql();
2122
let args = query.take_arguments();
22-
Box::pin(self.execute_stream(query.sql(), args).into_stream())
23+
let persistent = query.persistent();
24+
25+
Box::pin(
26+
self.execute_stream(sql, args, persistent)
27+
.map_ok(flume::Receiver::into_stream)
28+
.try_flatten_stream(),
29+
)
2330
}
2431

2532
fn fetch_optional<'e, 'q: 'e, E>(

‎sqlx-core/src/odbc/connection/mod.rs‎

Lines changed: 43 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -216,20 +216,25 @@ impl OdbcConnection {
216216
}
217217

218218
/// Launches a background task to execute the SQL statement and send the results to the returned channel.
219-
pub(crate) fn execute_stream(
219+
///
220+
/// A query that carries arguments runs as a prepared statement: binding parameters and then
221+
/// calling `SQLExecDirect` is valid ODBC, but some drivers only send bound values on the
222+
/// `SQLPrepare`/`SQLExecute` path.
223+
pub(crate) async fn execute_stream(
220224
&mut self,
221225
sql: &str,
222226
args: Option<OdbcArguments>,
223-
) -> flume::Receiver<Result<Either<OdbcQueryResult, OdbcRow>, Error>> {
224-
let (tx, rx) = flume::bounded(64);
225-
226-
let sql_owned = sql.to_string();
227-
let maybe_prepared = if let Some(prepared) = self.stmt_cache.get_mut(sql) {
228-
MaybePrepared::Prepared(Arc::clone(prepared))
227+
persistent: bool,
228+
) -> Result<flume::Receiver<Result<Either<OdbcQueryResult, OdbcRow>, Error>>, Error> {
229+
let statement = if args.is_some() {
230+
MaybePrepared::Prepared(self.prepared_statement(sql, persistent).await?)
229231
} else {
230-
MaybePrepared::NotPrepared(sql_owned.clone())
232+
MaybePrepared::NotPrepared(sql.to_string())
231233
};
232234

235+
let (tx, rx) = flume::bounded(64);
236+
237+
let sql_owned = sql.to_string();
233238
let conn = Arc::clone(&self.conn);
234239
let buffer_settings = self.buffer_settings;
235240
let log_settings = self.log_settings.clone();
@@ -241,7 +246,7 @@ impl OdbcConnection {
241246
.and_then(|mut conn| {
242247
execute_sql(
243248
&mut conn,
244-
maybe_prepared,
249+
statement,
245250
args,
246251
&tx,
247252
buffer_settings,
@@ -254,7 +259,29 @@ impl OdbcConnection {
254259
}
255260
}));
256261

257-
rx
262+
Ok(rx)
263+
}
264+
265+
async fn prepared_statement(
266+
&mut self,
267+
sql: &str,
268+
store_to_cache: bool,
269+
) -> Result<SharedPreparedStatement, Error> {
270+
if let Some(cached) = self.stmt_cache.get_mut(sql) {
271+
return Ok(Arc::clone(cached));
272+
}
273+
274+
let conn = Arc::clone(&self.conn);
275+
let sql_owned = sql.to_string();
276+
let prepared =
277+
spawn_blocking(move || conn.into_prepared(&sql_owned).map_err(Error::from)).await?;
278+
let prepared = Arc::new(Mutex::new(prepared));
279+
280+
if store_to_cache && self.stmt_cache.is_enabled() {
281+
self.stmt_cache.insert(sql, Arc::clone(&prepared));
282+
}
283+
284+
Ok(prepared)
258285
}
259286

260287
pub(crate) async fn clear_cached_statements(&mut self) -> Result<(), Error> {
@@ -268,35 +295,16 @@ impl OdbcConnection {
268295
store_to_cache: bool,
269296
allow_deferred_result_columns: bool,
270297
) -> Result<OdbcStatement<'a>, Error> {
271-
let sql_owned = sql.to_string();
272-
let cached = self
273-
.stmt_cache
274-
.get_mut(sql)
275-
.map(|prepared| Arc::clone(prepared));
298+
let prepared = self.prepared_statement(sql, false).await?;
276299

277-
if let Some(prepared) = cached {
278-
let metadata = spawn_blocking(move || {
300+
let (metadata, metadata_complete) = spawn_blocking({
301+
let prepared = Arc::clone(&prepared);
302+
move || {
279303
let mut prepared = prepared.lock().map_err(|_| {
280304
Error::Protocol("ODBC prepare: failed to lock prepared statement".into())
281305
})?;
282306
collect_statement_metadata(&mut prepared, allow_deferred_result_columns)
283-
.map(|(metadata, _)| metadata)
284-
})
285-
.await?;
286-
287-
return Ok(OdbcStatement {
288-
sql: Cow::Borrowed(sql),
289-
metadata,
290-
});
291-
}
292-
293-
let conn = Arc::clone(&self.conn);
294-
let sql_clone = sql_owned.clone();
295-
let (prepared, metadata, metadata_complete) = spawn_blocking(move || {
296-
let mut prepared = conn.into_prepared(&sql_clone)?;
297-
let metadata =
298-
collect_statement_metadata(&mut prepared, allow_deferred_result_columns)?;
299-
Ok::<_, Error>((prepared, metadata.0, metadata.1))
307+
}
300308
})
301309
.await?;
302310

@@ -307,8 +315,7 @@ impl OdbcConnection {
307315
}
308316

309317
if store_to_cache && metadata_complete && self.stmt_cache.is_enabled() {
310-
self.stmt_cache
311-
.insert(&sql_owned, Arc::new(Mutex::new(prepared)));
318+
self.stmt_cache.insert(sql, prepared);
312319
}
313320

314321
Ok(OdbcStatement {

‎tests/odbc/odbc.rs‎

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,81 @@ async fn it_bounds_statement_cache() -> anyhow::Result<()> {
8080
Ok(())
8181
}
8282

83+
#[tokio::test]
84+
async fn it_caches_statements_across_repeated_queries() -> anyhow::Result<()> {
85+
let mut conn = new::<Odbc>().await?;
86+
87+
for _ in 0..10 {
88+
let row = sqlx_oldapi::query(PARAMETERIZED_SELECT_WITH_COLUMN)
89+
.bind(42_i32)
90+
.fetch_one(&mut conn)
91+
.await?;
92+
assert_eq!(row.try_get_raw(0)?.to_owned().decode::<i64>(), 42);
93+
}
94+
95+
assert_eq!(conn.cached_statements_size(), 1);
96+
97+
Ok(())
98+
}
99+
100+
#[tokio::test]
101+
async fn it_does_not_cache_non_persistent_queries() -> anyhow::Result<()> {
102+
let mut conn = new::<Odbc>().await?;
103+
104+
let row = sqlx_oldapi::query(PARAMETERIZED_SELECT_WITH_COLUMN)
105+
.bind(42_i32)
106+
.persistent(false)
107+
.fetch_one(&mut conn)
108+
.await?;
109+
110+
assert_eq!(row.try_get_raw(0)?.to_owned().decode::<i64>(), 42);
111+
assert_eq!(conn.cached_statements_size(), 0);
112+
113+
Ok(())
114+
}
115+
116+
#[tokio::test]
117+
async fn it_does_not_cache_statements_without_arguments() -> anyhow::Result<()> {
118+
let mut conn = new::<Odbc>().await?;
119+
120+
conn.execute("SELECT 1").await?;
121+
conn.execute("CREATE TEMPORARY TABLE sqlx_odbc_uncached (id INTEGER NOT NULL)")
122+
.await?;
123+
124+
assert_eq!(conn.cached_statements_size(), 0);
125+
126+
Ok(())
127+
}
128+
129+
#[tokio::test]
130+
async fn it_reuses_cached_statements_after_partial_reads() -> anyhow::Result<()> {
131+
let mut conn = new::<Odbc>().await?;
132+
133+
conn.execute("CREATE TEMPORARY TABLE sqlx_odbc_partial_reads (id INTEGER NOT NULL)")
134+
.await?;
135+
let rows = (1..=200)
136+
.map(|id| format!("({id})"))
137+
.collect::<Vec<_>>()
138+
.join(", ");
139+
conn.execute(&*format!(
140+
"INSERT INTO sqlx_odbc_partial_reads (id) VALUES {rows}"
141+
))
142+
.await?;
143+
144+
for _ in 0..3 {
145+
let row =
146+
sqlx_oldapi::query("SELECT id FROM sqlx_odbc_partial_reads WHERE id > ? ORDER BY id")
147+
.bind(0_i32)
148+
.fetch_one(&mut conn)
149+
.await?;
150+
assert_eq!(row.try_get_raw(0)?.to_owned().decode::<i64>(), 1);
151+
}
152+
153+
assert_eq!(conn.cached_statements_size(), 1);
154+
155+
Ok(())
156+
}
157+
83158
#[tokio::test]
84159
async fn it_rolls_back_dropped_transaction() -> anyhow::Result<()> {
85160
let mut conn = new::<Odbc>().await?;

0 commit comments

Comments
 (0)