Skip to content

Commit

Permalink
Merge 72dbdcb into 828982f
Browse files Browse the repository at this point in the history
  • Loading branch information
klinson committed Jul 23, 2020
2 parents 828982f + 72dbdcb commit 02be548
Show file tree
Hide file tree
Showing 11 changed files with 742 additions and 21 deletions.
25 changes: 15 additions & 10 deletions .github/workflows/build-ci.yml
Expand Up @@ -28,18 +28,21 @@ jobs:
MYSQL_DATABASE: 'unittest'
MYSQL_ROOT_PASSWORD:
name: PHP v${{ matrix.php }} with Mongo v${{ matrix.mongodb }}

steps:
- uses: actions/checkout@v2
- name: "Installing php"
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: curl,mbstring,xdebug
coverage: xdebug
tools: composer
- uses: actions/checkout@v1
- name: Creating MongoDB replica
if: matrix.mongodb == '4.0' || matrix.mongodb == '4.2'
run: |
docker run --name mongodb_repl -e MONGO_INITDB_DATABASE=unittest --publish 27018:27018 --detach mongo:${{ matrix.mongodb }} mongod --port 27018 --replSet rs
until docker exec --tty mongodb_repl mongo 127.0.0.1:27018 --eval "db.serverStatus()"; do
sleep 1
done
sudo docker exec --tty mongodb_repl mongo 127.0.0.1:27018 --eval "rs.initiate({\"_id\":\"rs\",\"members\":[{\"_id\":0,\"host\":\"127.0.0.1:27018\" }]})"
env:
MONGO_HOST: 0.0.0.0
MONGO_REPL_HOST: 0.0.0.0
- name: Show PHP version
run: php -v && composer -V
run: php${{ matrix.php }} -v && composer -V
- name: Show Docker version
run: if [[ "$DEBUG" == "true" ]]; then docker version && env; fi
env:
Expand All @@ -60,7 +63,9 @@ jobs:
run: |
./vendor/bin/phpunit --coverage-clover coverage.xml
env:
MONGO_VERSION: ${{ matrix.mongodb }})
MONGO_HOST: 0.0.0.0
MONGO_REPL_HOST: 0.0.0.0
MYSQL_HOST: 0.0.0.0
MYSQL_PORT: 3307
- name: Send coveralls
Expand Down
41 changes: 41 additions & 0 deletions README.md
Expand Up @@ -35,6 +35,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 @@ -950,6 +951,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 V4.0 or more and deployment replica sets or sharded clusters.You can look at its speciality [in the MongoDB docs](https://docs.mongodb.com/manual/core/transactions/)

### Basic Usage

Transaction supports create/insert,update,delete,etc operation.

```php
DB::transaction(function () {
User::create(['name' => 'klinson', 'age' => 19, 'title' => 'admin', 'email' => 'klinsonup@gmail.com']);
DB::collection('users')->where('name', 'klinson')->update(['age' => 20]);
DB::collection('users')->where('name', 'klinson')->delete();
});
```

```php
// begin a transaction
DB::beginTransaction();
User::create(['name' => 'klinson', 'age' => 19, 'title' => 'admin', 'email' => 'klinsonup@gmail.com']);
DB::collection('users')->where('name', 'klinson')->update(['age' => 20]);
DB::collection('users')->where('name', 'klinson')->delete();

// you can commit your changes
DB::commit();

// you can also rollback them
//DB::rollBack();
```

Transaction supports infinite-level nested transactions, but outside transaction rollbacks do not affect the commit of inside transactions.
```php
DB::beginTransaction();
User::create(['name' => 'klinson', 'age' => 20, 'title' => 'admin']);
DB::transaction(function () {
DB::collection('users')->where('name', 'klinson')->update(['age' => 20]);
});
DB::rollBack();
```

Schema
------
The database driver also has (limited) schema builder support. You can easily manipulate collections and set indexes.
Expand Down
62 changes: 60 additions & 2 deletions docker-compose.yml
Expand Up @@ -10,8 +10,14 @@ services:
- .:/code
working_dir: /code
depends_on:
- mongodb
- mysql
- mongodb
- mongodb_repl
- mongodb_repl_2
- mongodb_repl_3
- mongo_repl_init
- mysql
stdin_open: true
tty: true

mysql:
container_name: mysql
Expand All @@ -30,3 +36,55 @@ services:
- 27017:27017
logging:
driver: none

mongodb_repl:
container_name: mongodb_repl
image: mongo:4.2.5
restart: always
ports:
- "27018:27018"
entrypoint: [ "/usr/bin/mongod", "--quiet", "--bind_ip_all", "--port", "27018", "--replSet", "rs" ]
depends_on:
- mongodb_repl_2
- mongodb_repl_3
logging:
driver: none

mongodb_repl_2:
container_name: mongodb_repl_2
image: mongo:4.2.5
restart: always
ports:
- "27019:27018"
entrypoint: [ "/usr/bin/mongod", "--quiet", "--bind_ip_all", "--port", "27018", "--replSet", "rs" ]
depends_on:
- mongodb_repl_3
logging:
driver: none

mongodb_repl_3:
container_name: mongodb_repl_3
image: mongo:4.2.5
restart: always
ports:
- "27020:27018"
entrypoint: [ "/usr/bin/mongod", "--quiet", "--bind_ip_all", "--port", "27018", "--replSet", "rs" ]
logging:
driver: none

mongo_repl_init:
image: mongo:4.2.5
depends_on:
- mongodb_repl
- mongodb_repl_2
- mongodb_repl_3
environment:
- MONGO1=mongodb_repl
- MONGO2=mongodb_repl_2
- MONGO3=mongodb_repl_3
- RS=rs
volumes:
- ./:/scripts
entrypoint: [ "sh", "-c", "/scripts/mongo-repl-init.sh" ]
logging:
driver: none
39 changes: 39 additions & 0 deletions mongo-replset-init.sh
@@ -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
5 changes: 5 additions & 0 deletions phpunit.xml.dist
Expand Up @@ -25,6 +25,10 @@
<file>tests/QueryBuilderTest.php</file>
<file>tests/QueryTest.php</file>
</testsuite>
<testsuite name="transaction">
<file>tests/TransactionBuilderTest.php</file>
<file>tests/TransactionTest.php</file>
</testsuite>
<testsuite name="model">
<file>tests/ModelTest.php</file>
<file>tests/RelationsTest.php</file>
Expand All @@ -47,6 +51,7 @@
</filter>
<php>
<env name="MONGO_HOST" value="mongodb"/>
<env name="MONGO_REPL_HOST" value="mongodb_repl"/>
<env name="MONGO_DATABASE" value="unittest"/>
<env name="MONGO_PORT" value="27017"/>
<env name="MYSQL_HOST" value="mysql"/>
Expand Down
76 changes: 76 additions & 0 deletions src/Jenssegers/Mongodb/Connection.php
Expand Up @@ -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
{
Expand All @@ -21,6 +24,9 @@ class Connection extends BaseConnection
*/
protected $connection;

protected $session_key;
protected $sessions = [];

/**
* Create a new database connection instance.
* @param array $config
Expand Down Expand Up @@ -277,4 +283,74 @@ public function __call($method, $parameters)
{
return call_user_func_array([$this->db, $method], $parameters);
}

/**
* create a 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.
* 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/
*/
public function beginTransaction()
{
$this->session_key = uniqid();
$this->sessions[$this->session_key] = $this->connection->startSession();

$this->sessions[$this->session_key]->startTransaction([
'readPreference' => new ReadPreference(ReadPreference::RP_PRIMARY),
'writeConcern' => new WriteConcern(1),
'readConcern' => new ReadConcern(ReadConcern::LOCAL)
]);
}

/**
* commit transaction in this session and close this session
*/
public function commit()
{
if ($session = $this->getSession()) {
$session->commitTransaction();
$this->setLastSession();
}
}

/**
* rollback transaction in this session and close this session
*/
public function rollBack($toLevel = null)
{
if ($session = $this->getSession()) {
$session->abortTransaction();
$this->setLastSession();
}
}

/**
* close this session and get last session key to session_key
* Why do it ? Because nested transactions
*/
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
* @return \MongoDB\Driver\Session|null
*/
public function getSession()
{
return $this->sessions[$this->session_key] ?? null;
}
}

0 comments on commit 02be548

Please sign in to comment.