Skip to content

Commit 00b97f9

Browse files
committed
feat: Enhance ODBC execution with parameterized SQL handling
This commit introduces a new function for executing SQL commands with parameters in the ODBC connection worker. It improves the handling of SQL execution by allowing for parameterized queries, enhancing flexibility and security. Additionally, it refines the argument conversion process for better integration with the ODBC API.
1 parent f42f69f commit 00b97f9

File tree

3 files changed

+79
-50
lines changed

3 files changed

+79
-50
lines changed

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

Lines changed: 13 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@ impl<'c> Executor<'c> for &'c mut OdbcConnection {
2626
let sql = _query.sql().to_string();
2727
let mut args = _query.take_arguments();
2828
Box::pin(try_stream! {
29-
let rx = if let Some(a) = args.take() {
30-
let new_sql = interpolate_sql_with_odbc_args(&sql, &a.values);
31-
self.worker.execute_stream(&new_sql).await?
29+
let rx = if let Some(mut a) = args.take() {
30+
let vals: Vec<OdbcArgumentValue<'static>> = std::mem::take(&mut a.values)
31+
.into_iter()
32+
.map(|v| match v {
33+
OdbcArgumentValue::Text(s) => OdbcArgumentValue::Text(s),
34+
OdbcArgumentValue::Bytes(b) => OdbcArgumentValue::Bytes(b),
35+
OdbcArgumentValue::Int(i) => OdbcArgumentValue::Int(i),
36+
OdbcArgumentValue::Float(f) => OdbcArgumentValue::Float(f),
37+
OdbcArgumentValue::Null => OdbcArgumentValue::Null,
38+
OdbcArgumentValue::Phantom(_) => OdbcArgumentValue::Null,
39+
})
40+
.collect();
41+
self.worker.execute_stream_with_args(&sql, vals).await?
3242
} else {
3343
self.worker.execute_stream(&sql).await?
3444
};
@@ -84,43 +94,3 @@ impl<'c> Executor<'c> for &'c mut OdbcConnection {
8494
Box::pin(async move { Err(Error::Protocol("ODBC describe not implemented".into())) })
8595
}
8696
}
87-
88-
fn interpolate_sql_with_odbc_args(sql: &str, args: &[OdbcArgumentValue<'_>]) -> String {
89-
let mut result = String::with_capacity(sql.len() + args.len() * 8);
90-
let mut arg_iter = args.iter();
91-
for ch in sql.chars() {
92-
if ch == '?' {
93-
if let Some(arg) = arg_iter.next() {
94-
match arg {
95-
OdbcArgumentValue::Int(i) => result.push_str(&i.to_string()),
96-
OdbcArgumentValue::Float(f) => result.push_str(&format!("{}", f)),
97-
OdbcArgumentValue::Text(s) => {
98-
result.push('\'');
99-
for c in s.chars() {
100-
if c == '\'' {
101-
result.push('\'');
102-
}
103-
result.push(c);
104-
}
105-
result.push('\'');
106-
}
107-
OdbcArgumentValue::Bytes(b) => {
108-
result.push_str("X'");
109-
for byte in b {
110-
result.push_str(&format!("{:02X}", byte));
111-
}
112-
result.push('\'');
113-
}
114-
OdbcArgumentValue::Null | OdbcArgumentValue::Phantom(_) => {
115-
result.push_str("NULL")
116-
}
117-
}
118-
} else {
119-
result.push('?');
120-
}
121-
} else {
122-
result.push(ch);
123-
}
124-
}
125-
result
126-
}

sqlx-core/src/odbc/connection/worker.rs

Lines changed: 64 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use crate::odbc::{
1111
#[allow(unused_imports)]
1212
use crate::row::Row as SqlxRow;
1313
use either::Either;
14-
use odbc_api::{Cursor, CursorRow, ResultSetMetadata};
14+
use odbc_api::{Cursor, CursorRow, IntoParameter, ResultSetMetadata};
1515

1616
#[derive(Debug)]
1717
pub(crate) struct ConnectionWorker {
@@ -115,12 +115,10 @@ impl ConnectionWorker {
115115
Command::Execute { sql, tx } => {
116116
with_conn(&shared, |conn| execute_sql(conn, &sql, &tx));
117117
}
118-
Command::ExecuteWithArgs {
119-
sql,
120-
args: _args,
121-
tx,
122-
} => {
123-
with_conn(&shared, |conn| execute_sql(conn, &sql, &tx));
118+
Command::ExecuteWithArgs { sql, args, tx } => {
119+
with_conn(&shared, |conn| {
120+
execute_sql_with_params(conn, &sql, args, &tx)
121+
});
124122
}
125123
}
126124
}
@@ -254,6 +252,65 @@ fn execute_sql(
254252
}
255253
}
256254

255+
fn execute_sql_with_params(
256+
conn: &odbc_api::Connection<'static>,
257+
sql: &str,
258+
args: Vec<OdbcArgumentValue<'static>>,
259+
tx: &flume::Sender<Result<Either<OdbcQueryResult, OdbcRow>, Error>>,
260+
) {
261+
if args.is_empty() {
262+
dispatch_execute(conn, sql, (), tx);
263+
return;
264+
}
265+
266+
let mut params: Vec<Box<dyn odbc_api::parameter::InputParameter>> =
267+
Vec::with_capacity(args.len());
268+
for a in args {
269+
params.push(to_param(a));
270+
}
271+
dispatch_execute(conn, sql, &params[..], tx);
272+
}
273+
274+
fn to_param(
275+
arg: OdbcArgumentValue<'static>,
276+
) -> Box<dyn odbc_api::parameter::InputParameter + 'static> {
277+
match arg {
278+
OdbcArgumentValue::Int(i) => Box::new(i.into_parameter()),
279+
OdbcArgumentValue::Float(f) => Box::new(f.into_parameter()),
280+
OdbcArgumentValue::Text(s) => Box::new(s.into_parameter()),
281+
OdbcArgumentValue::Bytes(b) => Box::new(b.into_parameter()),
282+
OdbcArgumentValue::Null | OdbcArgumentValue::Phantom(_) => {
283+
Box::new(Option::<i32>::None.into_parameter())
284+
}
285+
}
286+
}
287+
288+
fn dispatch_execute<P>(
289+
conn: &odbc_api::Connection<'static>,
290+
sql: &str,
291+
params: P,
292+
tx: &flume::Sender<Result<Either<OdbcQueryResult, OdbcRow>, Error>>,
293+
) where
294+
P: odbc_api::ParameterCollectionRef,
295+
{
296+
match conn.execute(sql, params, None) {
297+
Ok(Some(mut cursor)) => {
298+
let columns = collect_columns(&mut cursor);
299+
if let Err(e) = stream_rows(&mut cursor, &columns, tx) {
300+
let _ = tx.send(Err(e));
301+
return;
302+
}
303+
let _ = tx.send(Ok(Either::Left(OdbcQueryResult { rows_affected: 0 })));
304+
}
305+
Ok(None) => {
306+
let _ = tx.send(Ok(Either::Left(OdbcQueryResult { rows_affected: 0 })));
307+
}
308+
Err(e) => {
309+
let _ = tx.send(Err(Error::from(e)));
310+
}
311+
}
312+
}
313+
257314
fn collect_columns<C>(cursor: &mut C) -> Vec<OdbcColumn>
258315
where
259316
C: ResultSetMetadata,

test.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,5 @@ docker compose -f tests/docker-compose.yml run -it -p 3306:3306 --name mysql_8 m
99
DATABASE_URL='mysql://root:password@localhost/sqlx' cargo test --features any,mysql,macros,all-types,runtime-actix-rustls --
1010

1111
DATABASE_URL='sqlite://./tests/sqlite/sqlite.db' cargo test --features any,sqlite,macros,all-types,runtime-actix-rustls --
12+
13+
ATABASE_URL='DSN=SQLX_PG_55432;UID=postgres;PWD=password' cargo test --no-default-features --features odbc,macros,runtime-tokio-rustls --test odbc

0 commit comments

Comments
 (0)