-
Notifications
You must be signed in to change notification settings - Fork 0
Transactions #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Transactions #3
Changes from all commits
e54408a
9a01d23
64b4156
34b1a98
84850a9
bedaaa8
b030914
163b41d
92c1e07
caa61ea
7d96c3e
776492d
10bda05
85a0be8
b39251c
fe61056
3029e36
4ce523f
44cb7a7
7221417
1b90047
a5d0858
add9516
14b3ad7
0c22e4a
0b840db
cf88a03
2664bd5
44bd081
850d034
83f45c9
5eb6f42
84186f1
7741acb
72dbdcb
c14bd44
5773597
9e0cfd2
b2d1740
8cc01d5
dafee61
0fd27ba
6e89c8b
5645fc7
9cd8bb2
551c184
49307e2
59020fb
97f9b4b
9c90125
041d02b
8ff2eec
a174832
a58919e
25e9d92
a3b1d82
c444491
180bdf3
2a9e2d5
fcb6a2c
673eecf
b03614b
87cb7e6
17e1aa9
eb7b84a
a191667
f31fb2a
298646f
9ca9074
115ce15
f80f7bc
ab0903e
a953e84
318b30c
4615895
a558781
3d4280e
a46b3ac
6aebb34
c245261
880ad4c
539f7a5
67bd2e9
06a6d95
a0a8339
8505a2f
788c467
18b7a52
9eef2fe
9361058
b7c6e6f
50c14cd
ff01de7
f3d1553
16e95a7
f61fa07
2316afd
5ac9b7a
40a8ea7
301f979
cf66129
5b1f3a4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -37,6 +37,7 @@ This package adds functionalities to the Eloquent model and Query builder for Mo | |
- [Query Builder](#query-builder) | ||
- [Basic Usage](#basic-usage-2) | ||
- [Available operations](#available-operations) | ||
- [Transaction](#transaction) | ||
- [Schema](#schema) | ||
- [Basic Usage](#basic-usage-3) | ||
- [Geospatial indexes](#geospatial-indexes) | ||
|
@@ -979,6 +980,46 @@ If you are familiar with [Eloquent Queries](http://laravel.com/docs/queries), th | |
### Available operations | ||
To see the available operations, check the [Eloquent](#eloquent) section. | ||
|
||
Transaction | ||
------- | ||
Transaction requires MongoDB version ^4.0 as well as deployment of replica set or sharded clusters. You can find more information [in the MongoDB docs](https://docs.mongodb.com/manual/core/transactions/) | ||
|
||
### Basic Usage | ||
|
||
Transaction supports all operations. | ||
|
||
```php | ||
DB::transaction(function () { | ||
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']); | ||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||
DB::collection('users')->where('name', 'john')->delete(); | ||
}); | ||
``` | ||
|
||
```php | ||
// begin a transaction | ||
DB::beginTransaction(); | ||
User::create(['name' => 'john', 'age' => 19, 'title' => 'admin', 'email' => '[email protected]']); | ||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||
DB::collection('users')->where('name', 'john')->delete(); | ||
|
||
// you can commit your changes | ||
DB::commit(); | ||
|
||
// you can also rollback them | ||
//DB::rollBack(); | ||
``` | ||
**NOTE:** The Transactions in MongoDb does not support nested transactions. DB::beginTransaction() function will start new transactions in a new created or existing session and will raise the RuntimeException when transactions already exist. See more in MongoDB official docs [Transactions and Sessions](https://www.mongodb.com/docs/manual/core/transactions/#transactions-and-sessions) | ||
```php | ||
// This code will rise RuntimeException | ||
DB::beginTransaction(); | ||
User::create(['name' => 'john', 'age' => 20, 'title' => 'admin']); | ||
DB::beginTransaction() | ||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||
DB::commit() | ||
DB::rollBack(); | ||
``` | ||
|
||
Schema | ||
------ | ||
The database driver also has (limited) schema builder support. You can easily manipulate collections and set indexes. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
#!/bin/bash | ||
mongodbHost="${MONGO}" | ||
rs=${RS} | ||
port=${PORT:-27017} | ||
|
||
echo "Waiting for startup.." | ||
until mongo --host ${mongodbHost}:${port} --eval 'quit(db.runCommand({ ping: 1 }).ok ? 0 : 2)' &>/dev/null; do | ||
printf '.' | ||
sleep 1 | ||
done | ||
|
||
echo "Started.." | ||
|
||
echo setup.sh time now: `date +"%T" ` | ||
mongosh --host ${mongodbHost}:${port} <<EOF | ||
var cfg = { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See my earlier comment about reducing the replica set to a single primary. It shouldn't be necessary to write a complete configuration. For example, I use the following shell script to quickly spawn a single-member replica set for testing: rm -rf /tmp/mongo60
mkdir /tmp/mongo60
6.0.*/bin/mongod --port 27060 --dbpath /tmp/mongo60 --logpath /tmp/mongo60.log --replSet rs0 --setParameter enableTestCommands=1 --setParameter transactionLifetimeLimitSeconds=5 --fork
mongosh*/bin/mongosh --port 27060 --eval 'rs.initiate()
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't look like Additionally, this entire |
||
"_id": ${rs}, | ||
"protocolVersion": 1, | ||
"members": [ | ||
{ | ||
"_id": 0, | ||
"host": "${mongodbHost}:${port}" | ||
}, | ||
] | ||
}; | ||
rs.initiate(cfg, { force: true }); | ||
EOF |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
<?php | ||
|
||
namespace Jenssegers\Mongodb\Concerns; | ||
|
||
use Closure; | ||
use MongoDB\Driver\Exception\RuntimeException; | ||
use MongoDB\Driver\Session; | ||
use function MongoDB\with_transaction; | ||
|
||
trait TransactionManager | ||
{ | ||
/** | ||
* A list of transaction session. | ||
*/ | ||
protected ?Session $session = null; | ||
|
||
/** | ||
* Get the existing session or null. | ||
*/ | ||
public function getSession(): ?Session | ||
{ | ||
return $this->session; | ||
} | ||
|
||
private function getSessionOrThrow(): Session | ||
{ | ||
$session = $this->getSession(); | ||
|
||
if ($session === null) { | ||
throw new RuntimeException('There is no active session.'); | ||
} | ||
|
||
return $session; | ||
} | ||
|
||
/** | ||
* Use the existing or create new session and start a transaction in session. | ||
* | ||
* In version 4.0, MongoDB supports multi-document transactions on replica sets. | ||
* In version 4.2, MongoDB introduces distributed transactions, which adds support for multi-document transactions on sharded clusters and incorporates the existing support for multi-document transactions on replica sets. | ||
* | ||
* @see https://docs.mongodb.com/manual/core/transactions/ | ||
*/ | ||
public function beginTransaction(array $options = []): void | ||
{ | ||
$session = $this->getSession(); | ||
|
||
if ($session === null) { | ||
$session = $this->connection->startSession(); | ||
$this->session = $session; | ||
} | ||
|
||
$session->startTransaction($options); | ||
} | ||
|
||
/** | ||
* Commit transaction in this session and close this session. | ||
*/ | ||
public function commit(): void | ||
{ | ||
$this->getSessionOrThrow()->commitTransaction(); | ||
} | ||
|
||
/** | ||
* Rollback transaction in this session and close this session. | ||
*/ | ||
public function rollBack($toLevel = null): void | ||
{ | ||
$this->getSessionOrThrow()->abortTransaction(); | ||
} | ||
|
||
/** | ||
* Static transaction function realize the with_transaction functionality provided by MongoDB. | ||
* | ||
* @param int $attempts | ||
*/ | ||
public function transaction(Closure $callback, $attempts = 1, array $options = []): mixed | ||
{ | ||
$attemptsLeft = $attempts; | ||
$callbackResult = null; | ||
$session = $this->getSession(); | ||
|
||
if ($session === null) { | ||
$session = $this->connection->startSession(); | ||
$this->session = $session; | ||
} | ||
|
||
$callbackFunction = function (Session $session) use ($callback, &$attemptsLeft, &$callbackResult) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here's a comment suggestion to explain this logic
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This comment should be added to the source code, as it's helpful context for anyone that comes across this method definition in the future. |
||
$attemptsLeft--; | ||
|
||
if ($attemptsLeft < 0) { | ||
$session->abortTransaction(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looking back at the README examples, If so, I don't believe the implementation of WithTransaction will ever return with an in progress transaction. It should either have been successfully committed or aborted. In fact, the entire point of WithTransaction is that users don't have to think about calling the transaction methods -- they need only remember to pass the session to operations executed within the callback. Given that, I don't understand why we'd need to call |
||
|
||
return; | ||
} | ||
|
||
$callbackResult = $callback(); | ||
}; | ||
|
||
with_transaction($session, $callbackFunction, $options); | ||
|
||
return $callbackResult; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @alcaeus: This seems related to Jeff's inquiry about allowing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Correct. The default Laravel behaviour is to return the callback result, hence the workaround with using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This thread can be resolved. I don't have access to do so. |
||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this example suggesting that users can call
DB::rollBack()
afterDB::commit()
? Or is this intended to be an alternative to thecommit()
call? For reference, drivers can only callabortTransaction()
when the transaction is in a "starting" or "in progress" state.My confusion may just stem from this all appearing in the same code block, so breaking it out into a separate block might be clearer. That said, we can probably hold off on any documentation changes until we're certain the implementation itself is complete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done