Skip to content

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

Open
wants to merge 102 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
102 commits
Select commit Hold shift + click to select a range
e54408a
add transaction support, requires mongodb version V4.0 or more and de…
klinson Jan 8, 2020
9a01d23
format code
klinson Jan 8, 2020
64b4156
add mongodb(replica set) environment into docker configuration
klinson Jan 9, 2020
34b1a98
must be compatible with Illuminate\Database\Connection::rollBack($toL…
klinson Jan 9, 2020
84850a9
add transaction usage into readme.md
klinson Jan 9, 2020
bedaaa8
update .travis.yml
klinson Jan 10, 2020
b030914
update database.conf
klinson Jan 13, 2020
163b41d
update .travis.yml
klinson Jan 13, 2020
92c1e07
update docker-compose.yml
klinson Jan 13, 2020
caa61ea
Update src/Jenssegers/Mongodb/Connection.php
klinson Jan 17, 2020
7d96c3e
fixbug Connection.php
klinson Feb 7, 2020
776492d
Merge remote-tracking branch 'remotes/upstream/master'
klinson Feb 7, 2020
10bda05
add transaction into README.md
klinson Feb 7, 2020
85a0be8
add replset mongodb tag
klinson Feb 7, 2020
b39251c
Remove travis from PR please
klinson Feb 9, 2020
fe61056
remove author tag and comments in Chinese language
klinson Feb 13, 2020
3029e36
improved function docblocks of transaction
klinson Feb 13, 2020
4ce523f
merge update
klinson Feb 15, 2020
44cb7a7
Merge remote-tracking branch 'upstream/master'
klinson Jun 28, 2020
7221417
add another testsuite for transactional tests in phpunit.xml
klinson Jun 28, 2020
1b90047
optimized code
klinson Jul 1, 2020
a5d0858
update ci and tests configs
klinson Jul 21, 2020
add9516
update tests configs
klinson Jul 21, 2020
14b3ad7
update tests configs
klinson Jul 21, 2020
0c22e4a
update ci.yml
klinson Jul 21, 2020
0b840db
update tests
klinson Jul 21, 2020
cf88a03
update tests
klinson Jul 21, 2020
2664bd5
update tests
klinson Jul 21, 2020
44bd081
update tests
klinson Jul 21, 2020
850d034
Merge branch 'master' into master
klinson Jul 21, 2020
83f45c9
delete links of docker-compose.yaml
klinson Jul 23, 2020
5eb6f42
Merge remote-tracking branch 'origin/master'
klinson Jul 23, 2020
84186f1
Merge branch 'master' into master
divine Jul 23, 2020
7741acb
optimize code
klinson Jul 23, 2020
72dbdcb
Merge remote-tracking branch 'origin/master'
klinson Jul 23, 2020
c14bd44
Merge branch 'master' into master
divine Aug 28, 2020
5773597
Merge branch 'master' into master
divine Sep 9, 2020
9e0cfd2
Merge branch 'master' into master
divine Sep 16, 2020
b2d1740
Merge branch 'master' into master
divine Oct 14, 2020
8cc01d5
Merge branch 'master' into master
klinson Nov 20, 2020
dafee61
remove testInsertWithId
klinson Nov 20, 2020
0fd27ba
update build-ci.yml
klinson Nov 23, 2020
6e89c8b
add return info to phpdoc
klinson Nov 23, 2020
5645fc7
format code
klinson Feb 19, 2021
9cd8bb2
update README.md
klinson Feb 19, 2021
551c184
Merge remote-tracking branch 'upstream/master'
klinson Feb 19, 2021
49307e2
remove excess $option
klinson Mar 27, 2021
59020fb
divide testUpdate on two methods testUpdateWithRollback and testUpda…
klinson Mar 27, 2021
97f9b4b
update ci part
klinson Mar 27, 2021
9c90125
update ci part
klinson Mar 27, 2021
041d02b
Merge remote-tracking branch 'upstream/master'
klinson Mar 27, 2021
8ff2eec
Add WithTransaction function
Oct 7, 2022
a174832
remove unnecessary error handleing
Oct 7, 2022
a58919e
Restrict user to create nested transactions
Oct 10, 2022
25e9d92
Move transaction related functions to TransactionManager trait
Oct 10, 2022
a3b1d82
fix PR comments
Oct 11, 2022
c444491
Add Tranasactions
Oct 11, 2022
180bdf3
Change function description
Oct 11, 2022
2a9e2d5
Fix comments
Oct 17, 2022
fcb6a2c
fix comments
Oct 17, 2022
673eecf
Remove TransactionBuilderTest.php
Oct 17, 2022
b03614b
make __call method compatible with Illuminate\Database\Connection::__…
Oct 17, 2022
87cb7e6
Fix cs fixer
Oct 17, 2022
17e1aa9
make correction in docker setup
Oct 18, 2022
eb7b84a
Change mongo to mongosh
Oct 18, 2022
a191667
Remove Repl host
Oct 18, 2022
f31fb2a
Remove mongo 6.0 from jobs
Oct 18, 2022
298646f
Change MongoDB host in coverage xml file
Oct 18, 2022
9ca9074
Change hard coded configurations
Oct 18, 2022
115ce15
Change hard coded configurations
Oct 18, 2022
f80f7bc
Change hard coded configurations
Oct 18, 2022
ab0903e
Change hard coded configurations
Oct 18, 2022
a953e84
Change connection string
Oct 18, 2022
318b30c
set mongo host to local
Oct 18, 2022
4615895
set mongo host to local
Oct 18, 2022
a558781
set mongo host to local
Oct 19, 2022
3d4280e
set mongo host to local
Oct 19, 2022
a46b3ac
set mongo host to container name
Oct 19, 2022
6aebb34
Change call to Attribute class in Model
Oct 19, 2022
c245261
check port usage
Oct 19, 2022
880ad4c
set env to default
Oct 19, 2022
539f7a5
revert test changes
Oct 19, 2022
67bd2e9
revert test changes
Oct 19, 2022
06a6d95
set all connections to local
Oct 19, 2022
a0a8339
check connection
Oct 19, 2022
8505a2f
check connection
Oct 19, 2022
788c467
check connection
Oct 19, 2022
18b7a52
check connection
Oct 19, 2022
9eef2fe
check connection
Oct 19, 2022
9361058
check connection
Oct 19, 2022
b7c6e6f
check connection
Oct 19, 2022
50c14cd
check connection
Oct 19, 2022
ff01de7
check connection
Oct 19, 2022
f3d1553
check connection
Oct 19, 2022
16e95a7
Update transaction documentation and deleted remaining comments
Oct 19, 2022
f61fa07
Revert test changes
Oct 19, 2022
2316afd
Return exception if user call commit or rollback before start transac…
Oct 19, 2022
5ac9b7a
Add Return types
Oct 19, 2022
40a8ea7
Add option to dsn conncetion
Oct 19, 2022
301f979
Add option to dsn conncetion
Oct 20, 2022
cf66129
Cs fixer
Oct 20, 2022
5b1f3a4
Fix comments
Oct 21, 2022
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
22 changes: 14 additions & 8 deletions .github/workflows/build-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,18 +36,14 @@ jobs:
matrix:
include:
- { os: ubuntu-latest, php: 8.0, mongodb: '4.0', experimental: false }
- { os: ubuntu-latest, php: 8.0, mongodb: 4.2, experimental: false }
- { os: ubuntu-latest, php: 8.0, mongodb: 4.4, experimental: false }
- { os: ubuntu-latest, php: 8.0, mongodb: '4.2', experimental: false }
- { os: ubuntu-latest, php: 8.0, mongodb: '4.4', experimental: false }
- { os: ubuntu-latest, php: 8.0, mongodb: '5.0', experimental: false }
- { os: ubuntu-latest, php: 8.1, mongodb: '4.0', experimental: false }
- { os: ubuntu-latest, php: 8.1, mongodb: 4.2, experimental: false }
- { os: ubuntu-latest, php: 8.1, mongodb: 4.4, experimental: false }
- { os: ubuntu-latest, php: 8.1, mongodb: '4.2', experimental: false }
- { os: ubuntu-latest, php: 8.1, mongodb: '4.4', experimental: false }
- { os: ubuntu-latest, php: 8.1, mongodb: '5.0', experimental: false }
services:
mongo:
image: mongo:${{ matrix.mongodb }}
ports:
- 27017:27017
mysql:
image: mysql:5.7
ports:
Expand All @@ -59,6 +55,15 @@ jobs:

steps:
- uses: actions/checkout@v2
- name: Creating MongoDB replica
run: |
docker run --name mongodb -p 27017:27017 -e MONGO_INITDB_DATABASE=unittest --detach mongo:${{ matrix.mongodb }} mongod --replSet rs
until docker exec --tty mongodb mongo 127.0.0.1:27017 --eval "db.serverStatus()"; do
sleep 1
done
sudo docker exec --tty mongodb mongo 127.0.0.1:27017 --eval "rs.initiate({\"_id\":\"rs\",\"members\":[{\"_id\":0,\"host\":\"127.0.0.1:27017\" }]})"
env:
MONGO_HOST: 0.0.0.0
- name: "Installing php"
uses: shivammathur/setup-php@v2
with:
Expand Down Expand Up @@ -88,6 +93,7 @@ jobs:
run: |
./vendor/bin/phpunit --coverage-clover coverage.xml
env:
MONGO_VERSION: ${{ matrix.mongodb }})
MONGO_HOST: 0.0.0.0
MYSQL_HOST: 0.0.0.0
MYSQL_PORT: 3307
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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();
Copy link

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() after DB::commit()? Or is this intended to be an alternative to the commit() call? For reference, drivers can only call abortTransaction() 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.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Done

```
**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.
Expand Down
26 changes: 20 additions & 6 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,16 @@ services:
- .:/code
working_dir: /code
depends_on:
- mongodb
- mysql
- mongodb
- mongo_repl_init
- mysql
stdin_open: true
tty: true

mysql:
container_name: mysql
image: mysql:5.7
platform: "linux/amd64"
ports:
- 3306:3306
environment:
Expand All @@ -27,8 +31,18 @@ services:

mongodb:
container_name: mongodb
image: mongo
image: mongo:6.0
ports:
- 27017:27017
logging:
driver: none
- "27017:27017"
entrypoint: [ "/usr/bin/mongod", "--quiet", "--bind_ip_all", "--replSet", "rs" ]

mongo_repl_init:
image: mongo:6.0
depends_on:
- mongodb
environment:
- MONGO=mongodb
- RS=rs
volumes:
- ./:/scripts
entrypoint: [ "sh", "-c", "./scripts/mongo-replset-init.sh" ]
27 changes: 27 additions & 0 deletions mongo-replset-init.sh
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 = {
Copy link

Choose a reason for hiding this comment

The 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()

enableTestCommands probably isn't necessary (I doubt this library is using fail points), but I might suggest tuning transactionLifetimeLimitSeconds to a smaller value to avoid blocking. The server default of 60 seconds would likely exhaust PHP's own socket timeout and needlessly delay CI jobs if a conflict occurs.

Copy link

Choose a reason for hiding this comment

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

It doesn't look like transactionLifetimeLimitSeconds was ever added to the configuration. I remember the three of us spoke about that in a meeting sometime after I left this comment, and @alcaeus agreed that it should be added.

Additionally, this entire mongosh command should be reduced to rs.initiate(), which is much more concise. If the replica set configuration is ever modified down the line, we can always change this later -- although it'd probably be easier to use rs.add() instead of writing a verbose configuration like we have here.

"_id": ${rs},
"protocolVersion": 1,
"members": [
{
"_id": 0,
"host": "${mongodbHost}:${port}"
},
]
};
rs.initiate(cfg, { force: true });
EOF
5 changes: 4 additions & 1 deletion phpunit.xml.dist
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@
<file>tests/QueryBuilderTest.php</file>
<file>tests/QueryTest.php</file>
</testsuite>
<testsuite name="transaction">
<file>tests/TransactionTest.php</file>
</testsuite>
<testsuite name="model">
<file>tests/ModelTest.php</file>
<file>tests/RelationsTest.php</file>
Expand All @@ -35,7 +38,7 @@
</testsuite>
</testsuites>
<php>
<env name="MONGO_HOST" value="mongodb"/>
<env name="MONGO_HOST" value="localhost"/>
<env name="MONGO_DATABASE" value="unittest"/>
<env name="MONGO_PORT" value="27017"/>
<env name="MYSQL_HOST" value="mysql"/>
Expand Down
104 changes: 104 additions & 0 deletions src/Concerns/TransactionManager.php
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) {
Copy link

Choose a reason for hiding this comment

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

Here's a comment suggestion to explain this logic

The logic in this callback makes the with_transaction helper compatible with the expected behaviour of the Laravel transaction functionality. Notable differences:

  • with_transaction does not limit the number of times a callback is invoked
  • with_transaction can retry just the commit in certain cases
  • The DB::transaction helper returns the last return value of the callback; MongoDB\with_transaction doesn't.

To emulate this behaviour, this callback keeps track of how often it was invoked and aborts the transaction if the given number of attempts has been exceeded. Aborting a transaction signals the with_transaction helper to cancel its retry loop and return immediately.

Copy link

Choose a reason for hiding this comment

The 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();
Copy link

Choose a reason for hiding this comment

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

Looking back at the README examples, transaction() seems like an alternative API to manually calling the begin/commit/abort methods. Am I correct in assuming this is just a wrapper for with_transaction()?

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 Session::abortTransaction() here. If there's an actual edge case for doing so, let's document that in a comment. But if not, this seems like redundant code better removed.


return;
}

$callbackResult = $callback();
};

with_transaction($session, $callbackFunction, $options);

return $callbackResult;
Copy link

Choose a reason for hiding this comment

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

@alcaeus: This seems related to Jeff's inquiry about allowing with_transaction() to return its callback's last return value. That might make this obsolete.

Copy link

Choose a reason for hiding this comment

The 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 $callbackResult by reference in the callback. Fixing this in the driver would indeed make this obsolete.

Copy link

Choose a reason for hiding this comment

The 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.

}
}
Loading