Skip to content

Commit 66b377d

Browse files
committed
feat: Cursors can now have shared ownership over Connections using
`Connection::execute_arc`.
1 parent bc57466 commit 66b377d

File tree

3 files changed

+96
-16
lines changed

3 files changed

+96
-16
lines changed

odbc-api/src/connection.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use std::{
1111
fmt::{self, Debug, Display},
1212
mem::ManuallyDrop,
1313
str,
14+
sync::Arc,
1415
thread::panicking,
1516
};
1617

@@ -294,6 +295,38 @@ impl<'c> Connection<'c> {
294295
Ok(Some(cursor))
295296
}
296297

298+
/// Like [`Connection::execute`], but takes ownership of an `Arc<Self>`.
299+
pub fn execute_arc(
300+
self: Arc<Self>,
301+
query: &str,
302+
params: impl ParameterCollectionRef,
303+
query_timeout_sec: Option<usize>,
304+
) -> Result<Option<CursorImpl<StatementConnection<Arc<Connection<'c>>>>>, Error> {
305+
// With the current Rust version the borrow checker needs some convincing, so that it allows
306+
// us to return the Connection, even though the Result of execute borrows it.
307+
let mut error = None;
308+
let mut cursor = None;
309+
match self.execute(query, params, query_timeout_sec) {
310+
Ok(Some(c)) => cursor = Some(c),
311+
Ok(None) => return Ok(None),
312+
Err(e) => error = Some(e),
313+
};
314+
if let Some(e) = error {
315+
drop(cursor);
316+
return Err(e);
317+
}
318+
let cursor = cursor.unwrap();
319+
// The rust compiler needs some help here. It assumes otherwise that the lifetime of the
320+
// resulting cursor would depend on the lifetime of `params`.
321+
let mut cursor = ManuallyDrop::new(cursor);
322+
let handle = cursor.as_sys();
323+
// Safe: `handle` is a valid statement, and we are giving up ownership of `self`.
324+
let statement = unsafe { StatementConnection::new(handle, self) };
325+
// Safe: `statement is in the cursor state`.
326+
let cursor = unsafe { CursorImpl::new(statement) };
327+
Ok(Some(cursor))
328+
}
329+
297330
/// Prepares an SQL statement. This is recommended for repeated execution of similar queries.
298331
///
299332
/// Should your use case require you to execute the same query several times with different
@@ -759,14 +792,24 @@ impl Debug for Connection<'_> {
759792
}
760793
}
761794

762-
/// We need to implement ConnectionOwner in order to be able to use Connection together with
763-
/// [`StatementConnection`].
795+
/// We need to implement ConnectionOwner for [`Connection`] in order to express ownership of a
796+
/// connection for a statement handle. This is e.g. needed for [`Connection::into_cursor`].
764797
///
765798
/// # Safety:
766799
///
767800
/// Connection wraps an open Connection. It keeps the handle alive and valid during its lifetime.
768801
unsafe impl ConnectionOwner for Connection<'_> {}
769802

803+
/// We need to implement ConnectionOwner for `Arc<Connection>` in order to be able to express
804+
/// ownership of a shared connection from a statement handle. This is e.g. needed for
805+
/// [`Connection::execute_arc`].
806+
///
807+
/// # Safety:
808+
///
809+
/// Arc<Connection> wraps an open Connection. It keeps the handle alive and valid during its
810+
/// lifetime.
811+
unsafe impl ConnectionOwner for Arc<Connection<'_>> {}
812+
770813
/// Options to be passed then opening a connection to a datasource.
771814
#[derive(Default, Clone, Copy)]
772815
pub struct ConnectionOptions {

odbc-api/src/handles/statement_connection.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ impl<C> StatementConnection<C>
1414
where
1515
C: ConnectionOwner,
1616
{
17+
/// # Safety
18+
///
19+
/// Handle must be a valid statement handle and the parent connection must be valid for the
20+
/// lifetime of parent.
1721
pub(crate) unsafe fn new(handle: HStmt, parent: C) -> Self {
1822
Self {
1923
_parent: parent,
@@ -97,4 +101,4 @@ where
97101
fn as_stmt_ref(&mut self) -> StatementRef<'_> {
98102
self.as_stmt_ref()
99103
}
100-
}
104+
}

odbc-api/tests/integration.rs

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ use std::{
4141
iter,
4242
num::NonZeroUsize,
4343
ptr::null_mut,
44-
str, thread,
44+
str,
45+
sync::Arc,
46+
thread,
4547
time::{Duration, Instant},
4648
};
4749

@@ -571,6 +573,37 @@ fn into_cursor(profile: &Profile) {
571573
assert_eq!(expected, actual);
572574
}
573575

576+
/// We want to be able to create multiple statements with the utilizing the same connection, yet we
577+
/// in some cases it might be hard to keep track of the ownership of the connection separately. So
578+
/// essentially we want our statements to be owning an `Arc` to connection.
579+
#[test_case(MSSQL; "Microsoft SQL Server")]
580+
#[test_case(MARIADB; "Maria DB")]
581+
#[test_case(SQLITE_3; "SQLite 3")]
582+
#[test_case(POSTGRES; "PostgreSQL")]
583+
fn shared_ownership_of_connections_by_statement(profile: &Profile) {
584+
// Given
585+
let table_name = table_name!();
586+
let (conn, table) = Given::new(&table_name)
587+
.column_types(&["INT"])
588+
.values_by_column(&[&[Some("42")]])
589+
.build(profile)
590+
.unwrap();
591+
592+
// When
593+
let conn = Arc::new(conn);
594+
let cursor = conn
595+
.clone()
596+
.execute_arc(&table.sql_all_ordered_by_id(), (), None)
597+
.unwrap()
598+
.unwrap();
599+
// We can drop the connection, even though, the cursor still exists.
600+
drop(conn);
601+
602+
// Then
603+
let expected = "42";
604+
assert_eq!(expected, cursor_to_string(cursor));
605+
}
606+
574607
/// Strong exception safety for `into_cursor`. Our first query will fail, because it will query a
575608
/// non-existing table, but our second one using the same connection will succeed. This is one
576609
/// scenario in which it is useful not to "swallow" the connection in case of an error.
@@ -1483,20 +1516,20 @@ fn var_char_slice_mut_as_input_output_parameter(profile: &Profile) {
14831516
let conn = profile.connection().unwrap();
14841517
conn.execute(
14851518
r#"
1486-
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestInOutText')
1487-
DROP PROCEDURE TestInOutText
1519+
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestInOutText')
1520+
DROP PROCEDURE TestInOutText
14881521
"#,
14891522
(),
14901523
None,
14911524
)
14921525
.unwrap();
14931526

14941527
conn.execute(
1495-
r#"CREATE PROCEDURE TestInOutText
1496-
@OutParm VARCHAR(15) OUTPUT
1528+
r#"CREATE PROCEDURE TestInOutText
1529+
@OutParm VARCHAR(15) OUTPUT
14971530
AS
1498-
SELECT @OutParm = 'Hello, World!'
1499-
RETURN 99
1531+
SELECT @OutParm = 'Hello, World!'
1532+
RETURN 99
15001533
"#,
15011534
(),
15021535
None,
@@ -2822,20 +2855,20 @@ fn output_parameter(profile: &Profile) {
28222855
let conn = profile.connection().unwrap();
28232856
conn.execute(
28242857
r#"
2825-
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestOutputParam')
2826-
DROP PROCEDURE TestOutputParam
2858+
IF EXISTS (SELECT name FROM sysobjects WHERE name = 'TestOutputParam')
2859+
DROP PROCEDURE TestOutputParam
28272860
"#,
28282861
(),
28292862
None,
28302863
)
28312864
.unwrap();
28322865

28332866
conn.execute(
2834-
r#"CREATE PROCEDURE TestOutputParam
2835-
@OutParm int OUTPUT
2867+
r#"CREATE PROCEDURE TestOutputParam
2868+
@OutParm int OUTPUT
28362869
AS
2837-
SELECT @OutParm = @OutParm + 5
2838-
RETURN 99
2870+
SELECT @OutParm = @OutParm + 5
2871+
RETURN 99
28392872
"#,
28402873
(),
28412874
None,

0 commit comments

Comments
 (0)