-
Notifications
You must be signed in to change notification settings - Fork 1.5k
Add transaction support #1904
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
Add transaction support #1904
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
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 | ||||
---|---|---|---|---|---|---|
|
@@ -36,6 +36,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) | ||||||
|
@@ -968,6 +969,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 CREATE/INSERT/UPDATE/DELETE 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 | ||||||
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.
Suggested change
|
||||||
DB::commit(); | ||||||
|
||||||
// you can also rollback them | ||||||
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.
Suggested change
|
||||||
//DB::rollBack(); | ||||||
``` | ||||||
|
||||||
**NOTE:** Transaction supports infinite-level of nested transactions, but outside transaction rollbacks do not affect the commits of inside transactions. | ||||||
```php | ||||||
DB::beginTransaction(); | ||||||
User::create(['name' => 'john', 'age' => 20, 'title' => 'admin']); | ||||||
DB::transaction(function () { | ||||||
DB::collection('users')->where('name', 'john')->update(['age' => 20]); | ||||||
}); | ||||||
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,39 @@ | ||
#!/bin/bash | ||
mongodb1=`getent hosts ${MONGO1} | awk '{ print $1 }'` | ||
|
||
port=${PORT:-27018} | ||
|
||
echo "Waiting for startup.." | ||
until mongo --host ${mongodb1}:${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" ` | ||
mongo --host ${mongodb1}:${port} <<EOF | ||
var cfg = { | ||
"_id": "${RS}", | ||
"protocolVersion": 1, | ||
"members": [ | ||
{ | ||
"_id": 0, | ||
"host": "${MONGO1}:${port}", | ||
"priority": 2 | ||
}, | ||
{ | ||
"_id": 1, | ||
"host": "${MONGO2}:${port}", | ||
"priority": 0 | ||
}, | ||
{ | ||
"_id": 2, | ||
"host": "${MONGO3}:${port}", | ||
"priority": 0 | ||
} | ||
] | ||
}; | ||
rs.initiate(cfg, { force: true }); | ||
rs.reconfig(cfg, { force: true }); | ||
EOF |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -6,6 +6,9 @@ | |||||
use Illuminate\Support\Arr; | ||||||
use InvalidArgumentException; | ||||||
use MongoDB\Client; | ||||||
use MongoDB\Driver\ReadConcern; | ||||||
use MongoDB\Driver\ReadPreference; | ||||||
use MongoDB\Driver\WriteConcern; | ||||||
|
||||||
class Connection extends BaseConnection | ||||||
{ | ||||||
|
@@ -21,6 +24,17 @@ class Connection extends BaseConnection | |||||
*/ | ||||||
protected $connection; | ||||||
|
||||||
/** | ||||||
* A random unique id for identification of transaction. | ||||||
* @var string | ||||||
*/ | ||||||
protected $session_key; | ||||||
/** | ||||||
* A list of transaction sessions. | ||||||
* @var array | ||||||
*/ | ||||||
protected $sessions = []; | ||||||
|
||||||
/** | ||||||
* Create a new database connection instance. | ||||||
* @param array $config | ||||||
|
@@ -277,4 +291,78 @@ public function __call($method, $parameters) | |||||
{ | ||||||
return call_user_func_array([$this->db, $method], $parameters); | ||||||
} | ||||||
|
||||||
/** | ||||||
* create a session and start a transaction in session | ||||||
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.
Suggested change
|
||||||
* | ||||||
* 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. | ||||||
* To use transactions on MongoDB 4.2 deployments(replica sets and sharded clusters), clients must use MongoDB drivers updated for MongoDB 4.2. | ||||||
* | ||||||
* @see https://docs.mongodb.com/manual/core/transactions/ | ||||||
* @return void | ||||||
*/ | ||||||
public function beginTransaction() | ||||||
{ | ||||||
$this->session_key = uniqid(); | ||||||
$this->sessions[$this->session_key] = $this->connection->startSession(); | ||||||
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. Given that the session from the connection instance is applied automatically to all operations sent through the query builder, I'd refrain from starting multiple sessions. Looking at the transaction logic used for PDO, the first call to |
||||||
|
||||||
$this->sessions[$this->session_key]->startTransaction([ | ||||||
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY), | ||||||
'writeConcern' => new WriteConcern(1), | ||||||
'readConcern' => new ReadConcern(ReadConcern::LOCAL) | ||||||
Comment on lines
+311
to
+313
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. I would avoid hardcoding these options this way, especially with the given values. There is no prior art for passing options (as most methods in the query builder don't accept any options), but an optional |
||||||
]); | ||||||
} | ||||||
|
||||||
/** | ||||||
* commit transaction in this session and close this session | ||||||
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.
Suggested change
|
||||||
* @return void | ||||||
*/ | ||||||
public function commit() | ||||||
{ | ||||||
if ($session = $this->getSession()) { | ||||||
$session->commitTransaction(); | ||||||
$this->setLastSession(); | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* rollback transaction in this session and close this session | ||||||
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.
Suggested change
|
||||||
* @return void | ||||||
*/ | ||||||
public function rollBack($toLevel = null) | ||||||
{ | ||||||
if ($session = $this->getSession()) { | ||||||
$session->abortTransaction(); | ||||||
$this->setLastSession(); | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* close this session and get last session key to session_key | ||||||
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.
Suggested change
|
||||||
* Why do it ? Because nested transactions | ||||||
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.
Suggested change
|
||||||
* @return void | ||||||
*/ | ||||||
protected function setLastSession() | ||||||
{ | ||||||
if ($session = $this->getSession()) { | ||||||
$session->endSession(); | ||||||
unset($this->sessions[$this->session_key]); | ||||||
if (empty($this->sessions)) { | ||||||
$this->session_key = null; | ||||||
} else { | ||||||
end($this->sessions); | ||||||
$this->session_key = key($this->sessions); | ||||||
} | ||||||
} | ||||||
} | ||||||
|
||||||
/** | ||||||
* get now session if it has session | ||||||
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.
Suggested change
|
||||||
* @return \MongoDB\Driver\Session|null | ||||||
*/ | ||||||
public function getSession() | ||||||
{ | ||||||
return $this->sessions[$this->session_key] ?? null; | ||||||
} | ||||||
} |
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.