Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -181,14 +181,20 @@ jobs:
-Zcrate-attr='feature(non_exhaustive_omitted_patterns_lint)' \
-Zcrate-attr='allow(unused_features)'
- name: Test (Default Features)
env:
RUST_BACKTRACE: "1"
working-directory: rust
run: >
cargo test --all-targets --workspace
- name: Test (All Features)
env:
RUST_BACKTRACE: "1"
working-directory: rust
run: >
cargo test --all-targets --all-features --workspace
- name: Doctests
env:
RUST_BACKTRACE: "1"
working-directory: rust
run: >
cargo test --doc --all-features --workspace
Expand Down
53 changes: 48 additions & 5 deletions rust/core/src/sync.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use arrow_array::{RecordBatch, RecordBatchReader};
use arrow_schema::Schema;

use crate::PartitionedResult;
use crate::error::Result;
use crate::error::{Error, Result, Status};
use crate::options::{self, OptionConnection, OptionDatabase, OptionStatement, OptionValue};

/// Ability to configure an object by setting/getting options.
Expand All @@ -44,6 +44,28 @@ pub trait Optionable {
fn get_option_double(&self, key: Self::Option) -> Result<f64>;
}

/// A handle to cancel an in-progress operation.
///
/// This is a separated handle because otherwise it would be impossible to
/// safely call a `cancel` method on a database/connection/statement itself
/// due to the borrow checker.
pub trait CancelHandle: Send + Sync {
/// Attempt to cancel the in-progress operation (best-effort).
fn try_cancel(&self) -> Result<()>;
}

/// A cancellation handle that does nothing (because cancellation is unsupported).
pub struct NoOpCancellationHandle;

impl CancelHandle for NoOpCancellationHandle {
fn try_cancel(&self) -> Result<()> {
Err(Error::with_message_and_status(
"cancellation not implemented",
Status::Unknown,
))
}
}

/// A handle to an ADBC driver.
pub trait Driver {
type DatabaseType: Database;
Expand Down Expand Up @@ -76,6 +98,11 @@ pub trait Database: Optionable<Option = OptionDatabase> {
&self,
opts: impl IntoIterator<Item = (options::OptionConnection, OptionValue)>,
) -> Result<Self::ConnectionType>;

/// Get a handle to cancel operations on this database.
fn get_cancel_handle(&self) -> Box<dyn CancelHandle> {
Box::new(NoOpCancellationHandle {})
}
Comment on lines +103 to +105

@abonander abonander Feb 24, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you going to introduce AdbcDatabaseCancel() to the C API? Otherwise I don't see why this needs to exist.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am: #3910

}

/// A handle to an ADBC connection.
Expand All @@ -95,7 +122,15 @@ pub trait Connection: Optionable<Option = OptionConnection> {
fn new_statement(&mut self) -> Result<Self::StatementType>;

/// Cancel the in-progress operation on a connection.
fn cancel(&mut self) -> Result<()>;
#[deprecated(since = "0.25.0", note = "Use get_cancel_handle() instead")]
fn cancel(&mut self) -> Result<()> {
self.get_cancel_handle().try_cancel()
}

/// Get a handle to cancel operations on this connection.
fn get_cancel_handle(&self) -> Box<dyn CancelHandle> {
Box::new(NoOpCancellationHandle {})
}

/// Get metadata about the database/driver.
///
Expand Down Expand Up @@ -456,12 +491,20 @@ pub trait Statement: Optionable<Option = OptionStatement> {
fn set_substrait_plan(&mut self, plan: impl AsRef<[u8]>) -> Result<()>;

/// Cancel execution of an in-progress query.
#[deprecated(since = "0.25.0", note = "Use get_cancel_handle() instead")]
fn cancel(&mut self) -> Result<()> {
self.get_cancel_handle().try_cancel()
}

/// Get a handle to cancel operations on this statement.
///
/// This can be called during [Statement::execute] (or similar), or while
/// consuming a result set returned from such.
/// The resulting handle can be called during [Statement::execute] (or
/// similar), or while consuming a result set returned from such.
///
/// # Since
///
/// ADBC API revision 1.1.0
fn cancel(&mut self) -> Result<()>;
fn get_cancel_handle(&self) -> Box<dyn CancelHandle> {
Box::new(NoOpCancellationHandle {})
}
}
20 changes: 20 additions & 0 deletions rust/driver/dummy/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,26 @@ impl Connection for DummyConnection {
Err(error)
}

/// This method is used to test that errors round-trip correctly.
fn get_cancel_handle(&self) -> Box<dyn adbc_core::CancelHandle> {
struct CancelHandle;

impl adbc_core::CancelHandle for CancelHandle {
fn try_cancel(&self) -> Result<()> {
let mut error = Error::with_message_and_status("message", Status::Cancelled);
error.vendor_code = constants::ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA;
error.sqlstate = [1, 2, 3, 4, 5];
error.details = Some(vec![
("key1".into(), b"AAA".into()),
("key2".into(), b"ZZZZZ".into()),
]);
Err(error)
}
}

Box::new(CancelHandle)
}

fn commit(&mut self) -> Result<()> {
Ok(())
}
Expand Down
35 changes: 27 additions & 8 deletions rust/driver/dummy/tests/driver_exporter_dummy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -465,11 +465,14 @@ fn test_connection_get_info_ignores_unrecognized_codes() {

#[test]
fn test_connection_cancel() {
let (_, _, mut exported_connection, _) = get_exported();
let (_, _, mut native_connection, _) = get_native();
let (_, _, exported_connection, _) = get_exported();
let (_, _, native_connection, _) = get_native();

let exported_error = exported_connection.cancel().unwrap_err();
let native_error = native_connection.cancel().unwrap_err();
let exported_handle = exported_connection.get_cancel_handle();
let native_handle = native_connection.get_cancel_handle();

let exported_error = exported_handle.try_cancel().unwrap_err();
let native_error = native_handle.try_cancel().unwrap_err();

assert_eq!(exported_error, native_error);
}
Expand Down Expand Up @@ -668,11 +671,27 @@ fn test_statement_bind_stream() {

#[test]
fn test_statement_cancel() {
let (_, _, _, mut exported_statement) = get_exported();
let (_, _, _, mut native_statement) = get_native();
let (_, _, _, exported_statement) = get_exported();
let (_, _, _, native_statement) = get_native();

let exported_handle = exported_statement.get_cancel_handle();
let native_handle = native_statement.get_cancel_handle();

exported_statement.cancel().unwrap();
native_statement.cancel().unwrap();
let res = exported_handle.try_cancel();
assert!(res.is_err());
assert!(
res.unwrap_err()
.to_string()
.contains("cancellation not implemented")
);

let res = native_handle.try_cancel();
assert!(res.is_err());
assert!(
res.unwrap_err()
.to_string()
.contains("cancellation not implemented")
);
}

#[test]
Expand Down
Loading
Loading