Skip to content

Commit 870878f

Browse files
authored
DOCSP-31789: transaction return type (#751)
* DOCSP-31789: transaction return type v6 * draft * fix build errors * draft * note * fix * small fix to use include * NB tech review * wip * CC PR fixes 1 + restructuring * missed save * small fix * list test * improvements * reorder bc of merge * CC PR fixes 3 * save fix * IA * add simple examples * cleanup * cleanup * cleanup * code comment cleanup * line highlighting * vale fixes * CC PR fixes cont. * CC suggestions * line highlighting
1 parent deecb9a commit 870878f

File tree

11 files changed

+786
-305
lines changed

11 files changed

+786
-305
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { MongoError, MongoClient } from "mongodb";
2+
3+
// begin-core
4+
async function coreTest(client) {
5+
const session = client.startSession();
6+
try {
7+
session.startTransaction();
8+
9+
const savingsColl = client.db("bank").collection("savings_accounts");
10+
await savingsColl.findOneAndUpdate(
11+
{account_id: "9876"},
12+
{$inc: {amount: -100 }},
13+
{ session });
14+
15+
const checkingColl = client.db("bank").collection("checking_accounts");
16+
await checkingColl.findOneAndUpdate(
17+
{account_id: "9876"},
18+
{$inc: {amount: 100 }},
19+
{ session });
20+
21+
// ... perform other operations
22+
23+
await session.commitTransaction();
24+
console.log("Transaction committed.");
25+
} catch (error) {
26+
console.log("An error occurred during the transaction:" + error);
27+
await session.abortTransaction();
28+
} finally {
29+
await session.endSession();
30+
}
31+
}
32+
// end-core
33+
34+
// begin-conv
35+
async function convTest(client) {
36+
let txnRes = await client.withSession(async (session) =>
37+
session.withTransaction(async (session) => {
38+
const savingsColl = client.db("bank").collection("savings_accounts");
39+
await savingsColl.findOneAndUpdate(
40+
{account_id: "9876"},
41+
{$inc: {amount: -100 }},
42+
{ session });
43+
44+
const checkingColl = client.db("bank").collection("checking_accounts");
45+
await checkingColl.findOneAndUpdate(
46+
{account_id: "9876"},
47+
{$inc: {amount: 100 }},
48+
{ session });
49+
50+
// ... perform other operations
51+
52+
return "Transaction committed.";
53+
}, null)
54+
);
55+
console.log(txnRes);
56+
}
57+
// end-conv
58+
59+
async function run() {
60+
const uri =
61+
"<connection string>";
62+
const client = new MongoClient(uri);
63+
64+
try {
65+
await coreTest(client);
66+
await convTest(client);
67+
} finally {
68+
await client.close();
69+
}
70+
}
71+
run().catch(console.dir);
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { MongoError, MongoClient, ObjectId } from "mongodb";
2+
3+
const uri =
4+
"<connection string uri>";
5+
const client = new MongoClient(uri);
6+
await client.connect();
7+
8+
await client
9+
.db("testdb")
10+
.collection("inventory")
11+
.insertMany([
12+
// start-inventory
13+
{ item: "sunblock", qty: 85, price: 6.0 },
14+
{ item: "beach chair", qty: 30, price: 25.0 }
15+
// end-inventory
16+
]);
17+
18+
const order1 = [
19+
// start-order-successful
20+
{ item: "sunblock", qty: 3 },
21+
{ item: "beach chair", qty: 1 }
22+
// end-order-successful
23+
];
24+
25+
const order2 = [
26+
// start-order-fail
27+
{ item: "volleyball", qty: 1 }
28+
// end-order-fail
29+
];
30+
31+
let order = order1;
32+
33+
// start-transaction
34+
const txnResult = await client.withSession(async (session) =>
35+
session
36+
.withTransaction(async (session) => {
37+
const invColl = client.db("testdb").collection("inventory");
38+
const recColl = client.db("testdb").collection("orders");
39+
40+
let total = 0;
41+
for (const item of order) {
42+
/* Update the inventory for the purchased items. End the
43+
transaction if the quantity of an item in the inventory is
44+
insufficient to complete the purchase. */
45+
const inStock = await invColl.findOneAndUpdate(
46+
{
47+
item: item.item,
48+
qty: { $gte: item.qty },
49+
},
50+
{ $inc: { qty: -item.qty } },
51+
{ session }
52+
);
53+
if (inStock === null) {
54+
await session.abortTransaction();
55+
return "Item not found or insufficient quantity.";
56+
}
57+
const subTotal = item.qty * inStock.price;
58+
total = total + subTotal;
59+
}
60+
61+
// Create a record of the purchase
62+
const receipt = {
63+
date: new Date(),
64+
items: order,
65+
total: total,
66+
};
67+
await recColl.insertOne(receipt, { session });
68+
return (
69+
"Order successfully completed and recorded!\nReceipt:\n" +
70+
JSON.stringify(receipt, null, 1)
71+
);
72+
}, null)
73+
.finally(async () => await client.close())
74+
);
75+
76+
console.log(txnResult);
77+
// end-transaction

source/code-snippets/transactions/txn-core.js

Lines changed: 28 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
const { MongoError, MongoClient } = require('mongodb');
44

5-
// Drop the "customers", "inventory", and "orders" collections from the "testdb" database
5+
/* Drop the "customers", "inventory", and "orders" collections from the
6+
"testdb" database */
67
async function cleanUp(client) {
78
await Promise.all( ['customers', 'inventory', 'orders'].map(async c => {
89
try {
@@ -22,8 +23,8 @@ async function setup(client) {
2223

2324
// Insert inventory data for "sunblock" and "beach towel"
2425
await inventoryColl.insertMany([
25-
{ name: 'sunblock', sku: 5432, qty: 85 },
26-
{ name: 'beach towel', sku: 7865, qty: 41 },
26+
{ item: 'sunblock', item_id: 5432, qty: 85 },
27+
{ item: 'beach towel', item_id: 7865, qty: 41 },
2728
]);
2829
} catch (e) {
2930
// Print the exception if one was thrown
@@ -42,14 +43,13 @@ async function queryData() {
4243
}, client));
4344

4445
} finally {
45-
// Close the database connection
46+
// Close your connection
4647
client.close();
4748
}
4849
}
4950

5051
// start placeOrder
5152
async function placeOrder(client, cart, payment) {
52-
// Specify readConcern, writeConcern, and readPreference transaction options
5353
const transactionOptions = {
5454
readConcern: { level: 'snapshot' },
5555
writeConcern: { w: 'majority' },
@@ -63,8 +63,8 @@ async function placeOrder(client, cart, payment) {
6363
session.startTransaction(transactionOptions);
6464

6565
const ordersCollection = client.db('testdb').collection('orders');
66-
// Within the session, insert an order that contains information about the
67-
// customer, items purchased, and the total payment.
66+
/* Within the session, insert an order that contains information about the
67+
customer, items purchased, and the total payment */
6868
const orderResult = await ordersCollection.insertOne(
6969
{
7070
customer: payment.customer,
@@ -75,36 +75,27 @@ async function placeOrder(client, cart, payment) {
7575
);
7676

7777
const inventoryCollection = client.db('testdb').collection('inventory');
78-
79-
// Within the session, for each item purchased, decrement the purchased quantity in the "inventory" collection.
80-
// Cancel the transaction when you have insufficient inventory or if the item SKU does not exist.
81-
for (let i=0; i<cart.length; i++) {
82-
const item = cart[i];
83-
84-
// Retrieve the inventory information for the item
85-
const checkInventory = await inventoryCollection.findOne(
78+
79+
for (const item of order) {
80+
/* Update the inventory for the purchased items. End the
81+
transaction if the quantity of an item in the inventory is
82+
insufficient to complete the purchase. */
83+
const inStock = await inventoryCollection.findOneAndUpdate(
8684
{
87-
sku: item.sku,
88-
qty: { $gte: item.qty }
85+
item_id: item.item_id,
86+
item_id: { $gte: item.qty }
8987
},
88+
{ $inc: { 'qty': -item.qty }},
9089
{ session }
9190
)
92-
// Throw an exception if the item lacks sufficient quantity or SKU does not exist.
93-
if (checkInventory === null) {
94-
throw new Error('Insufficient quantity or SKU not found.');
91+
if (inStock === null) {
92+
throw new Error('Insufficient quantity or item ID not found.');
9593
}
96-
97-
// Decrement the inventory of the item by the amount specified in the order.
98-
await inventoryCollection.updateOne(
99-
{ sku: item.sku },
100-
{ $inc: { 'qty': -item.qty }},
101-
{ session }
102-
);
10394
}
10495

10596
const customerCollection = client.db('testdb').collection('customers');
10697

107-
// Within the session, add the order details to the "orders" array of the customer document.
98+
// Within the session, add the order details to the "orders" array of the customer document
10899
await customerCollection.updateOne(
109100
{ _id: payment.customer },
110101
{ $push: { orders: orderResult.insertedId }},
@@ -121,16 +112,16 @@ async function placeOrder(client, cart, payment) {
121112
transaction. Roll back all the updates performed in the transaction.
122113
*/
123114
if (error instanceof MongoError && error.hasErrorLabel('UnknownTransactionCommitResult')) {
124-
// add your logic to retry or handle the error
115+
// Add your logic to retry or handle the error
125116
}
126117
else if (error instanceof MongoError && error.hasErrorLabel('TransientTransactionError')) {
127-
// add your logic to retry or handle the error
118+
// Add your logic to retry or handle the error
128119
} else {
129120
console.log('An error occured in the transaction, performing a data rollback:' + error);
130121
}
131122
await session.abortTransaction();
132123
} finally {
133-
// End the session so that no further calls can be made on it
124+
// End the session
134125
await session.endSession();
135126
}
136127
}
@@ -148,13 +139,14 @@ async function run() {
148139
// Call a method that creates sample inventory data for this example
149140
await setup(client);
150141

151-
// Create sample data for a customer's shopping cart that includes "sunblock" and "beach towel" items
142+
// Create sample data for a customer's shopping cart
152143
const cart = [
153-
{ name: 'sunblock', sku: 5432, qty: 1, price: 5.19 },
154-
{ name: 'beach towel', sku: 7865, qty: 2, price: 15.99 }
144+
{ item: 'sunblock', item_id: 5432, qty: 1, price: 5.19 },
145+
{ item: 'beach towel', item_id: 7865, qty: 2, price: 15.99 }
155146
];
156147

157-
// Create sample data for a customer's payment, calculated from the contents of their cart
148+
/* Create sample data for a customer's payment based on the contents
149+
of their cart */
158150
const payment = { customer: 98765, total: 37.17 };
159151

160152
try {
@@ -164,7 +156,7 @@ async function run() {
164156
// Call a method that removes data from prior runs of this example
165157
await cleanUp(client);
166158

167-
// Close the database connection
159+
// Close your connection
168160
await client.close();
169161
}
170162
}

source/fundamentals/crud/compound-operations.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,8 @@ configurable :ref:`sort <node-fundamentals-sort>` and
5151
of these methods.
5252

5353
Starting in version 6.0, this setting defaults to ``false``, which
54-
means that each method returns the matched document, or, if no
55-
document is matched, it returns ``null``. If you set
54+
means that each method returns the matched document. If no
55+
document is matched, each method returns ``null``. If you set
5656
``includeResultMetadata`` to ``true``, the method returns a
5757
``ModifyResult`` type that contains the found document and additional
5858
metadata.

0 commit comments

Comments
 (0)