Skip to content

Commit 8092036

Browse files
RUST-813 Add documentation examples for transactions (#349)
1 parent e7d7573 commit 8092036

File tree

6 files changed

+153
-7
lines changed

6 files changed

+153
-7
lines changed

src/client/options/resolver_config.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ impl ResolverConfig {
1313
/// Creates a default configuration, using 1.1.1.1, 1.0.0.1 and 2606:4700:4700::1111,
1414
/// 2606:4700:4700::1001 (thank you, Cloudflare).
1515
///
16-
/// Please see: https://www.cloudflare.com/dns/
16+
/// Please see: <https://www.cloudflare.com/dns/>
1717
pub fn cloudflare() -> Self {
1818
ResolverConfig {
1919
inner: TrustDnsResolverConfig::cloudflare(),
@@ -34,7 +34,7 @@ impl ResolverConfig {
3434
/// Creates a configuration, using 9.9.9.9, 149.112.112.112 and 2620:fe::fe, 2620:fe::fe:9, the
3535
/// “secure” variants of the quad9 settings (thank you, Quad9).
3636
///
37-
/// Please see: https://www.quad9.net/faq/
37+
/// Please see: <https://www.quad9.net/faq/>
3838
pub fn quad9() -> Self {
3939
ResolverConfig {
4040
inner: TrustDnsResolverConfig::quad9(),

src/client/session/mod.rs

Lines changed: 74 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,67 @@ lazy_static! {
3737
///
3838
/// `ClientSession` instances are not thread safe or fork safe. They can only be used by one thread
3939
/// or process at a time.
40+
///
41+
/// ## Transactions
42+
/// Transactions are used to execute a series of operations across multiple documents and
43+
/// collections atomically. For more information about when and how to use transactions in MongoDB,
44+
/// see the [manual](https://docs.mongodb.com/manual/core/transactions/).
45+
///
46+
/// Replica set transactions are supported on MongoDB 4.0+. Transactions are associated with a
47+
/// `ClientSession`. To begin a transaction, call [`ClientSession::start_transaction`] on a
48+
/// `ClientSession`. The `ClientSession` must be passed to operations to be executed within the
49+
/// transaction.
50+
///
51+
/// ```rust
52+
/// use mongodb::{
53+
/// bson::doc,
54+
/// error::{Result, TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT},
55+
/// options::{Acknowledgment, ReadConcern, TransactionOptions, WriteConcern},
56+
/// # Client,
57+
/// ClientSession,
58+
/// Collection,
59+
/// };
60+
///
61+
/// # async fn do_stuff() -> Result<()> {
62+
/// # let client = Client::with_uri_str("mongodb://example.com").await?;
63+
/// # let coll = client.database("foo").collection("bar");
64+
/// let mut session = client.start_session(None).await?;
65+
/// let options = TransactionOptions::builder()
66+
/// .read_concern(ReadConcern::majority())
67+
/// .write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build())
68+
/// .build();
69+
/// session.start_transaction(options).await?;
70+
/// // A "TransientTransactionError" label indicates that the entire transaction can be retried
71+
/// // with a reasonable expectation that it will succeed.
72+
/// while let Err(error) = execute_transaction(&coll, &mut session).await {
73+
/// if !error.contains_label(TRANSIENT_TRANSACTION_ERROR) {
74+
/// break;
75+
/// }
76+
/// }
77+
/// # Ok(())
78+
/// # }
79+
///
80+
/// async fn execute_transaction(coll: &Collection, session: &mut ClientSession) -> Result<()> {
81+
/// coll.insert_one_with_session(doc! { "x": 1 }, None, session).await?;
82+
/// coll.delete_one_with_session(doc! { "y": 2 }, None, session).await?;
83+
/// // An "UnknownTransactionCommitResult" label indicates that it is unknown whether the
84+
/// // commit has satisfied the write concern associated with the transaction. If an error
85+
/// // with this label is returned, it is safe to retry the commit until the write concern is
86+
/// // satisfied or an error without the label is returned.
87+
/// loop {
88+
/// let result = session.commit_transaction().await;
89+
/// if let Err(ref error) = result {
90+
/// if error.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) {
91+
/// continue;
92+
/// }
93+
/// }
94+
/// result?
95+
/// }
96+
/// }
97+
/// ```
98+
// TODO RUST-122 Remove this note and adjust the above description to indicate that sharded
99+
// transactions are supported on 4.2+
100+
/// Note: the driver does not currently support transactions on sharded clusters.
40101
#[derive(Clone, Debug)]
41102
pub struct ClientSession {
42103
cluster_time: Option<ClusterTime>,
@@ -193,6 +254,10 @@ impl ClientSession {
193254
/// be passed into each operation within the transaction; otherwise, the operation will be
194255
/// executed outside of the transaction.
195256
///
257+
/// Errors returned from operations executed within a transaction may include a
258+
/// [`crate::error::TRANSIENT_TRANSACTION_ERROR`] label. This label indicates that the entire
259+
/// transaction can be retried with a reasonable expectation that it will succeed.
260+
///
196261
/// Transactions are supported on MongoDB 4.0+. The Rust driver currently only supports
197262
/// transactions on replica sets.
198263
///
@@ -276,6 +341,13 @@ impl ClientSession {
276341

277342
/// Commits the transaction that is currently active on this session.
278343
///
344+
///
345+
/// This method may return an error with a [`crate::error::UNKNOWN_TRANSACTION_COMMIT_RESULT`]
346+
/// label. This label indicates that it is unknown whether the commit has satisfied the write
347+
/// concern associated with the transaction. If an error with this label is returned, it is
348+
/// safe to retry the commit until the write concern is satisfied or an error without the label
349+
/// is returned.
350+
///
279351
/// ```rust
280352
/// # use mongodb::{bson::{doc, Document}, error::Result, Client, ClientSession};
281353
/// #
@@ -344,14 +416,14 @@ impl ClientSession {
344416
/// # let coll = client.database("foo").collection::<Document>("bar");
345417
/// # let mut session = client.start_session(None).await?;
346418
/// session.start_transaction(None).await?;
347-
/// match execute_transaction(coll, &mut session).await {
419+
/// match execute_transaction(&coll, &mut session).await {
348420
/// Ok(_) => session.commit_transaction().await?,
349421
/// Err(_) => session.abort_transaction().await?,
350422
/// }
351423
/// # Ok(())
352424
/// # }
353425
///
354-
/// async fn execute_transaction(coll: Collection, session: &mut ClientSession) -> Result<()> {
426+
/// async fn execute_transaction(coll: &Collection, session: &mut ClientSession) -> Result<()> {
355427
/// coll.insert_one_with_session(doc! { "x": 1 }, None, session).await?;
356428
/// coll.delete_one_with_session(doc! { "y": 2 }, None, session).await?;
357429
/// Ok(())

src/runtime/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ impl AsyncRuntime {
146146
}
147147

148148
/// Create a new `Interval` that yields with interval of `duration`.
149-
/// See: https://docs.rs/tokio/latest/tokio/time/fn.interval.html
149+
/// See: <https://docs.rs/tokio/latest/tokio/time/fn.interval.html>
150150
pub(crate) fn interval(self, duration: Duration) -> Interval {
151151
match self {
152152
#[cfg(feature = "tokio-runtime")]

src/test/spec/initial_dns_seedlist_discovery.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ async fn run() {
4444

4545
// "encoded-userinfo-and-db.json" specifies a database name with a question mark which is
4646
// disallowed on Windows. See
47-
// https://docs.mongodb.com/manual/reference/limits/#restrictions-on-db-names
47+
// <https://docs.mongodb.com/manual/reference/limits/#restrictions-on-db-names>
4848
if let Some(ref mut options) = test_file.parsed_options {
4949
if options.db.as_deref() == Some("mydb?") && cfg!(target_os = "windows") {
5050
options.db = Some("mydb".to_string());

src/test/util/failpoint.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ impl FailPoint {
2323
}
2424

2525
/// Create a failCommand failpoint.
26-
/// See https://github.com/mongodb/mongo/wiki/The-%22failCommand%22-fail-point for more info.
26+
/// See <https://github.com/mongodb/mongo/wiki/The-%22failCommand%22-fail-point> for more info.
2727
pub fn fail_command(
2828
fail_commands: &[&str],
2929
mode: FailPointMode,

tests/transaction_examples.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
#![allow(dead_code)]
2+
#![cfg(not(feature = "sync"))]
3+
4+
// START TRANSACTIONS EXAMPLE
5+
use mongodb::{
6+
bson::{doc, Document},
7+
error::{Result, TRANSIENT_TRANSACTION_ERROR, UNKNOWN_TRANSACTION_COMMIT_RESULT},
8+
options::{Acknowledgment, ReadConcern, TransactionOptions, WriteConcern},
9+
ClientSession,
10+
};
11+
12+
async fn update_employee_info(session: &mut ClientSession) -> Result<()> {
13+
let transaction_options = TransactionOptions::builder()
14+
.read_concern(ReadConcern::snapshot())
15+
.write_concern(WriteConcern::builder().w(Acknowledgment::Majority).build())
16+
.build();
17+
session.start_transaction(transaction_options).await?;
18+
19+
execute_transaction_with_retry(session).await
20+
}
21+
22+
async fn execute_transaction_with_retry(session: &mut ClientSession) -> Result<()> {
23+
while let Err(err) = execute_employee_info_transaction(session).await {
24+
println!("Transaction aborted. Error returned during transaction.");
25+
if err.contains_label(TRANSIENT_TRANSACTION_ERROR) {
26+
println!("Encountered TransientTransactionError, retrying transaction.");
27+
continue;
28+
} else {
29+
return Err(err);
30+
}
31+
}
32+
Ok(())
33+
}
34+
35+
async fn execute_employee_info_transaction(session: &mut ClientSession) -> Result<()> {
36+
let client = session.client();
37+
let employees = client.database("hr").collection::<Document>("employees");
38+
let events = client
39+
.database("reporting")
40+
.collection::<Document>("events");
41+
42+
employees
43+
.update_one_with_session(
44+
doc! { "employee": 3 },
45+
doc! { "$set": { "status": "Inactive" } },
46+
None,
47+
session,
48+
)
49+
.await?;
50+
events
51+
.insert_one_with_session(
52+
doc! { "employee": 3, "status": { "new": "Inactive", "old": "Active" } },
53+
None,
54+
session,
55+
)
56+
.await?;
57+
58+
commit_with_retry(session).await
59+
}
60+
61+
async fn commit_with_retry(session: &mut ClientSession) -> Result<()> {
62+
while let Err(err) = session.commit_transaction().await {
63+
if err.contains_label(UNKNOWN_TRANSACTION_COMMIT_RESULT) {
64+
println!("Encountered UnknownTransactionCommitResult, retrying commit operation.");
65+
continue;
66+
} else {
67+
println!("Encountered non-retryable error during commit.");
68+
return Err(err);
69+
}
70+
}
71+
println!("Transaction committed.");
72+
Ok(())
73+
}
74+
// END TRANSACTIONS EXAMPLE

0 commit comments

Comments
 (0)