Skip to content

Commit 6cbf849

Browse files
committed
wip: execute_arc
1 parent c393e53 commit 6cbf849

File tree

3 files changed

+83
-29
lines changed

3 files changed

+83
-29
lines changed

odbc-api/src/connection.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use std::{
1515
fmt::{self, Debug, Display},
1616
mem::ManuallyDrop,
1717
str,
18+
sync::Arc,
1819
thread::panicking,
1920
};
2021

@@ -298,6 +299,25 @@ impl<'c> Connection<'c> {
298299
Ok(Some(cursor))
299300
}
300301

302+
/// Like [`Connection::execute`], but takes ownership of an `Arc<Self>`.
303+
pub fn execute_arc(
304+
self: Arc<Self>,
305+
query: &str,
306+
params: impl ParameterCollectionRef,
307+
query_timeout_sec: Option<usize>,
308+
) -> Result<Option<CursorImpl<StatementConnection<Arc<Connection<'c>>>>>, Error> {
309+
let query = SqlText::new(query);
310+
let lazy_statement = move || {
311+
let mut stmt = self.allocate_statement()?;
312+
if let Some(query_timeout_sec) = query_timeout_sec {
313+
stmt.set_query_timeout_sec(query_timeout_sec)
314+
.into_result(&stmt)?;
315+
}
316+
Ok(stmt)
317+
};
318+
execute_with_parameters(lazy_statement, Some(&query), params)
319+
}
320+
301321
/// Prepares an SQL statement. This is recommended for repeated execution of similar queries.
302322
///
303323
/// Should your use case require you to execute the same query several times with different

odbc-api/src/statement_connection.rs

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::sync::Arc;
2+
13
use odbc_sys::{HStmt, Handle, HandleType};
24

35
use crate::{
@@ -12,8 +14,20 @@ pub struct StatementConnection<C> {
1214
_parent: C,
1315
}
1416

15-
impl<'env> StatementConnection<Connection<'env>> {
16-
pub(crate) unsafe fn new(handle: HStmt, parent: Connection<'env>) -> Self {
17+
impl<C> Drop for StatementConnection<C> {
18+
fn drop(&mut self) {
19+
unsafe {
20+
drop_handle(self.handle as Handle, HandleType::Stmt);
21+
}
22+
}
23+
}
24+
25+
impl<C> StatementConnection<C> {
26+
/// # Safety
27+
///
28+
/// Handle must be a valid statement handle and the parent connection must be valid for the
29+
/// lifetime of parent.
30+
pub(crate) unsafe fn new(handle: HStmt, parent: C) -> Self {
1731
Self {
1832
_parent: parent,
1933
handle,
@@ -25,14 +39,6 @@ impl<'env> StatementConnection<Connection<'env>> {
2539
}
2640
}
2741

28-
impl<C> Drop for StatementConnection<C> {
29-
fn drop(&mut self) {
30-
unsafe {
31-
drop_handle(self.handle as Handle, HandleType::Stmt);
32-
}
33-
}
34-
}
35-
3642
/// According to the ODBC documentation this is safe. See:
3743
/// <https://docs.microsoft.com/en-us/sql/odbc/reference/develop-app/multithreading>
3844
///
@@ -77,3 +83,10 @@ impl AsStatementRef for StatementConnection<Connection<'_>> {
7783
self.as_stmt_ref()
7884
}
7985
}
86+
87+
impl AsStatementRef for StatementConnection<Arc<Connection<'_>>> {
88+
fn as_stmt_ref(&mut self) -> StatementRef<'_> {
89+
todo!();
90+
self.as_stmt_ref()
91+
}
92+
}

odbc-api/tests/integration.rs

Lines changed: 40 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,7 @@ use odbc_api::{
3636
use widestring::Utf16String;
3737

3838
use std::{
39-
ffi::CString,
40-
io::{self, Write},
41-
iter,
42-
num::NonZeroUsize,
43-
ptr::null_mut,
44-
str, thread,
45-
time::{Duration, Instant},
39+
ffi::CString, io::{self, Write}, iter, num::NonZeroUsize, ptr::null_mut, str, sync::Arc, thread, time::{Duration, Instant}
4640
};
4741

4842
const MSSQL: &Profile = &Profile {
@@ -571,6 +565,33 @@ fn into_cursor(profile: &Profile) {
571565
assert_eq!(expected, actual);
572566
}
573567

568+
/// We want to be able to create multiple statements with the utilizing the same connection, yet we
569+
/// in some cases it might be hard to keep track of the ownership of the connection separately. So
570+
/// essentially we want our statements to be owning an `Arc` to connection.
571+
#[test_case(MSSQL; "Microsoft SQL Server")]
572+
#[test_case(MARIADB; "Maria DB")]
573+
#[test_case(SQLITE_3; "SQLite 3")]
574+
#[test_case(POSTGRES; "PostgreSQL")]
575+
fn shared_ownership_of_connections_by_statement(profile: &Profile) {
576+
// Given
577+
let table_name = table_name!();
578+
let (conn, table) = Given::new(&table_name)
579+
.column_types(&["INT"])
580+
.values_by_column(&[
581+
&[
582+
Some("42")
583+
],
584+
])
585+
.build(profile)
586+
.unwrap();
587+
588+
// When
589+
let conn = Arc::new(conn);
590+
let cursor_1 = conn.clone().execute_arc(&table.sql_all_ordered_by_id(), (), None).unwrap().unwrap();
591+
let cursor_2 = conn.execute_arc(&table.sql_all_ordered_by_id(), (), None).unwrap().unwrap();
592+
// drop(conn); // This line would not compile without the `Arc` around the connection type
593+
}
594+
574595
/// Strong exception safety for `into_cursor`. Our first query will fail, because it will query a
575596
/// non-existing table, but our second one using the same connection will succeed. This is one
576597
/// scenario in which it is useful not to "swallow" the connection in case of an error.
@@ -1483,20 +1504,20 @@ fn var_char_slice_mut_as_input_output_parameter(profile: &Profile) {
14831504
let conn = profile.connection().unwrap();
14841505
conn.execute(
14851506
r#"
1486-
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestInOutText')
1487-
DROP PROCEDURE TestInOutText
1507+
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestInOutText')
1508+
DROP PROCEDURE TestInOutText
14881509
"#,
14891510
(),
14901511
None,
14911512
)
14921513
.unwrap();
14931514

14941515
conn.execute(
1495-
r#"CREATE PROCEDURE TestInOutText
1496-
@OutParm VARCHAR(15) OUTPUT
1516+
r#"CREATE PROCEDURE TestInOutText
1517+
@OutParm VARCHAR(15) OUTPUT
14971518
AS
1498-
SELECT @OutParm = 'Hello, World!'
1499-
RETURN 99
1519+
SELECT @OutParm = 'Hello, World!'
1520+
RETURN 99
15001521
"#,
15011522
(),
15021523
None,
@@ -2822,20 +2843,20 @@ fn output_parameter(profile: &Profile) {
28222843
let conn = profile.connection().unwrap();
28232844
conn.execute(
28242845
r#"
2825-
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestOutputParam')
2826-
DROP PROCEDURE TestOutputParam
2846+
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestOutputParam')
2847+
DROP PROCEDURE TestOutputParam
28272848
"#,
28282849
(),
28292850
None,
28302851
)
28312852
.unwrap();
28322853

28332854
conn.execute(
2834-
r#"CREATE PROCEDURE TestOutputParam
2835-
@OutParm int OUTPUT
2855+
r#"CREATE PROCEDURE TestOutputParam
2856+
@OutParm int OUTPUT
28362857
AS
2837-
SELECT @OutParm = @OutParm + 5
2838-
RETURN 99
2858+
SELECT @OutParm = @OutParm + 5
2859+
RETURN 99
28392860
"#,
28402861
(),
28412862
None,

0 commit comments

Comments
 (0)