Skip to content

Commit

Permalink
Merge pull request #94 from dimitriBouteille/v4/33-transaction-commit
Browse files Browse the repository at this point in the history
[BUG] Fix open transaction internally and at the moment transaction is opened but it is never closed
  • Loading branch information
dimitriBouteille authored Oct 13, 2024
2 parents 50b0478 + f91c430 commit 57e4b28
Show file tree
Hide file tree
Showing 2 changed files with 203 additions and 1 deletion.
9 changes: 8 additions & 1 deletion src/Orm/Database.php
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,8 @@ public function affectingStatement($query, $bindings = []): int
public function unprepared($query): bool
{
return $this->run($query, [], function (string $query) {
return $this->db->query($query);
$result = $this->db->query($query);
return $this->lastRequestHasError() ? $result : true;
});
}

Expand Down Expand Up @@ -250,10 +251,16 @@ public function transaction(\Closure $callback, $attempts = 1)
{
$this->beginTransaction();
try {

// We'll simply execute the given callback within a try / catch block and if we
// catch any exception we can rollback this transaction so that none of this
// gets actually persisted to a database or stored in a permanent fashion.
$data = $callback();
$this->commit();
return $data;
} catch (\Exception $e) {

// If we catch an exception we'll rollback this transaction
$this->rollBack();
throw $e;
}
Expand Down
195 changes: 195 additions & 0 deletions tests/WordPress/Orm/DatabaseTransactionTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
<?php
/**
* Copyright © Dimitri BOUTEILLE (https://github.com/dimitriBouteille)
* See LICENSE.txt for license details.
*
* Author: Dimitri BOUTEILLE <[email protected]>
*/

namespace Dbout\WpOrm\Tests\WordPress\Orm;

use Dbout\WpOrm\Orm\AbstractModel;
use Dbout\WpOrm\Orm\Database;
use Dbout\WpOrm\Tests\WordPress\TestCase;
use Illuminate\Database\QueryException;

class DatabaseTransactionTest extends TestCase
{
private string $tableName = '';
private AbstractModel $model;
private Database $db;

/**
* @return void
*/
public static function setUpBeforeClass(): void
{
global $wpdb;

$tableName = $wpdb->prefix . 'document';
$sql = "CREATE TABLE $tableName (
id INT NOT NULL AUTO_INCREMENT,
name varchar(100) NOT NULL,
url varchar(55) DEFAULT '' NOT NULL,
PRIMARY KEY (id)
);";

require_once ABSPATH . 'wp-admin/includes/upgrade.php';
dbDelta($sql);
define('SAVEQUERIES', true);
}

/**
* @return void
*/
public function setUp(): void
{
$this->model = new class () extends AbstractModel {
protected $primaryKey = 'id';
public $timestamps = false;
protected $table = 'document';
};

global $wpdb;
$this->tableName = $wpdb->prefix . 'document';
$this->model::truncate();
$this->db = Database::getInstance();
}

/**
* @throws \Throwable
* @return void
* @covers Database::transaction
* @covers Database::insert
* @covers Database::commit
*/
public function testTransactionCommit(): void
{
$this->resetLogQueries();
$this->db->transaction(function () {
$query = sprintf('INSERT INTO %s (name, url) VALUES(?, ?);', $this->tableName);
$this->db->insert($query, ['Invoice #15', 'invoice-15']);
$this->db->insert($query, ['Invoice #16', 'invoice-16']);
});

$this->assertTransaction('commit');
$this->assertCount(2, $this->model::all()->toArray());
}

/**
* @throws \Throwable
* @return void
* @covers Database::transaction
* @covers Database::delete
* @covers Database::insert
* @covers Database::rollBack
*/
public function testTransactionRollback(): void
{
$query = sprintf('INSERT INTO %s (name, url) VALUES(?, ?);', $this->tableName);
$this->db->insert($query, ['Deposit #1', 'deposit-1']);
$this->db->insert($query, ['Deposit #2', 'deposit-2']);

$this->resetLogQueries();
try {
$this->db->transaction(function () use ($query) {
$this->db->insert($query, ['Deposit #99', 'deposit-99']);
$this->db->delete(sprintf('DELETE FROM %s;', $this->tableName));

/**
* Throw exception because fake_column is invalid column name.
*/
$this->db->delete(sprintf('DELETE FROM %s WHERE fake_column = %d;', $this->tableName, $query));
});
} catch (\Exception) {
// Off exception
}

$this->assertTransaction('rollback');

$items = $this->model::all();
$this->assertCount(2, $items->toArray(), 'There must be only 2 items because the transaction was rollback.');
$this->assertEquals(['deposit-1', 'deposit-2'], $items->pluck('url')->toArray());
}

/**
* @throws \Throwable
* @return void
* @covers Database::transaction
*/
public function testTransactionThrowsQueryException(): void
{
$this->expectException(QueryException::class);
$this->resetLogQueries();
$this->db->transaction(function () {
$this->db->delete('DELETE FROM fake_table;');
});

$this->assertTransaction('rollback');
}

/**
* @throws \Throwable
* @return void
* @covers Database::beginTransaction
*/
public function testBeginTransaction(): void
{
$this->db->beginTransaction();
$this->assertLastQueryEquals('START TRANSACTION;');
}

/**
* @throws \Throwable
* @return void
* @covers Database::rollBack
*/
public function testRollback(): void
{
$this->db->beginTransaction();
$this->db->rollBack();
$this->assertLastQueryEquals('ROLLBACK;');
}

/**
* @throws \Throwable
* @return void
* @covers Database::commit
*/
public function testCommit(): void
{
$this->db->beginTransaction();
$this->db->commit();
$this->assertLastQueryEquals('COMMIT;');
}

/**
* @param string $mode
* @return void
*/
private function assertTransaction(string $mode): void
{
global $wpdb;
$query = $wpdb->queries;

$firstQuery = reset($query)[0] ?? '';
$lastQuery = end($query)[0] ?? '';
$this->assertEquals('START TRANSACTION;', $firstQuery);
$this->assertEquals(0, $this->db->transactionCount);

if ($mode === 'commit') {
$this->assertEquals('COMMIT;', $lastQuery);
} elseif ($mode === 'rollback') {
$this->assertEquals('ROLLBACK;', $lastQuery);
}
}

/**
* @return void
*/
private function resetLogQueries(): void
{
global $wpdb;
$wpdb->queries = [];
}
}

0 comments on commit 57e4b28

Please sign in to comment.