Skip to content

Commit

Permalink
introduce transaction support (when using PDO adapter)
Browse files Browse the repository at this point in the history
still basic test coverage, Store implementation checks required.

related to #110
  • Loading branch information
k00ni committed Jun 15, 2018
1 parent 81520dd commit b40ac9d
Show file tree
Hide file tree
Showing 3 changed files with 197 additions and 0 deletions.
44 changes: 44 additions & 0 deletions src/ARC2/Store/Adapter/PDOAdapter.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
class PDOAdapter extends AbstractAdapter
{
protected $transactionCounter;

public function checkRequirements()
{
if (false == \extension_loaded('pdo_mysql')) {
Expand Down Expand Up @@ -322,4 +324,46 @@ public function deleteQuery($sql)
$affectedRows = $this->db->exec($sql);
return $affectedRows;
}

/*
* Transaction related
*
* the following implementation prevents problems, if you have sub-transactions.
* thanks to http://php.net/manual/de/pdo.begintransaction.php#109753
*/

public function beginTransaction()
{
// if not already in a transaction
if (false === $this->db->inTransaction()) {
if (0 == $this->transactionCounter) {
return $this->db->beginTransaction();
}
return $this->transactionCounter >= 0;
}
}

public function inTransaction()
{
return $this->db->inTransaction();
}

public function commit()
{
if(0 >= --$this->transactionCounter) {
return $this->db->commit();
}
return $this->transactionCounter >= 0;
}

public function rollback()
{
if($this->transactionCounter >= 0) {
$this->transactionCounter = 0;
return $this->db->rollback();
}

$this->transactionCounter = 0;
return false;
}
}
44 changes: 44 additions & 0 deletions store/ARC2_Store.php
Original file line number Diff line number Diff line change
Expand Up @@ -883,4 +883,48 @@ public function logQuery($q)
fwrite($fp, date('Y-m-d\TH:i:s\Z', time()).' : '.$q.''."\n\n");
fclose($fp);
}

/*
* Transaction support
* FYI: https://github.com/semsol/arc2/issues/110
*
* Functions only work when using PDO as adapter.
* When calling these with mysqli an exception will be thrown.
*/

public function beginTransaction()
{
if ('pdo' !== $this->db->getAdapterName()) {
throw new \Exception('Please use the PDO adapter if you want transaction support.');
}

$this->db->beginTransaction();
}

public function inTransaction()
{
if ('pdo' !== $this->db->getAdapterName()) {
throw new \Exception('Please use the PDO adapter if you want transaction support.');
}

return $this->db->inTransaction();
}

public function commit()
{
if ('pdo' !== $this->db->getAdapterName()) {
throw new \Exception('Please use the PDO adapter if you want transaction support.');
}

return $this->db->commit();
}

public function rollback()
{
if ('pdo' !== $this->db->getAdapterName()) {
throw new \Exception('Please use the PDO adapter if you want transaction support.');
}

return $this->db->rollback();
}
}
109 changes: 109 additions & 0 deletions tests/db_adapter_depended/src/ARC2/Store/Adapter/PDOAdapterTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
<?php

namespace Tests\db_adapter_depended\src\ARC2\Store\Adapter;

use ARC2\Store\Adapter\PDOAdapter;
use Tests\ARC2_TestCase;

class PDOAdapterTest extends ARC2_TestCase
{
public function setUp()
{
parent::setUp();

if ('pdo' !== $this->dbConfig['db_adapter']) {
$this->markTestSkipped('Only runs when DB_ADAPTER=pdo.');
}

$this->fixture = new PDOAdapter($this->dbConfig);
$this->fixture->connect();

// remove all tables
$tables = $this->fixture->fetchList('SHOW TABLES');
foreach($tables as $table) {
$this->fixture->simpleQuery('DROP TABLE '. $table['Tables_in_'.$this->dbConfig['db_name']]);
}
}

/*
* Transaction related tests
*/

public function testSimpleTransactionHandling()
{
$this->assertFalse($this->fixture->inTransaction());

$this->fixture->beginTransaction();

$this->assertTrue($this->fixture->inTransaction());

$this->fixture->rollback();
}

public function testTransactionCommit()
{
$this->fixture->simpleQuery('CREATE TABLE transactionTest (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL
)');

$this->fixture->beginTransaction();

/*
* transaction started
*/

$this->fixture->simpleQuery('INSERT INTO transactionTest (id, name) VALUES (1, "foo")');
$this->fixture->simpleQuery('INSERT INTO transactionTest (id, name) VALUES (2, "bar")');

$this->assertEquals(1, \count($this->fixture->fetchList('SHOW TABLES LIKE "transactionTest"')));

$this->assertEquals(2, \count($this->fixture->fetchList('SELECT * FROM transactionTest')));

/*
* end transaction, commit CHANGES
*/

$this->fixture->commit();

$this->assertEquals(1, \count($this->fixture->fetchList('SHOW TABLES LIKE "transactionTest"')));
$this->assertEquals(2, \count($this->fixture->fetchList('SELECT * FROM transactionTest')));

// disconnect, force a commit/rollback by PDO
// connect again and ask the DB for our latest changes
$this->fixture->disconnect();
$this->fixture->connect();

$this->assertEquals(1, \count($this->fixture->fetchList('SHOW TABLES LIKE "transactionTest"')));
$this->assertEquals(2, \count($this->fixture->fetchList('SELECT * FROM transactionTest')));
}

public function testTransactionRollback()
{
$this->fixture->simpleQuery('CREATE TABLE transactionTest (
id INT(6) UNSIGNED AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(30) NOT NULL
)');

$this->fixture->beginTransaction();

/*
* transaction started
*/

$this->fixture->simpleQuery('INSERT INTO transactionTest (id, name) VALUES (1, "foo")');
$this->fixture->simpleQuery('INSERT INTO transactionTest (id, name) VALUES (2, "bar")');

$this->assertEquals(1, \count($this->fixture->fetchList('SHOW TABLES LIKE "transactionTest"')));

$this->assertEquals(2, \count($this->fixture->fetchList('SELECT * FROM transactionTest')));

/*
* end transaction, rollback and REVERT ALL CHANGES
*/

$this->fixture->rollback();

$this->assertEquals(0, \count($this->fixture->fetchList('SELECT * FROM transactionTest')));
}
}

0 comments on commit b40ac9d

Please sign in to comment.