Skip to content

Commit

Permalink
Added ability to lock a table before and after the database transaction.
Browse files Browse the repository at this point in the history
  • Loading branch information
Nick Bedford committed Sep 23, 2021
1 parent 62bc7c3 commit 0ae66aa
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 0 deletions.
30 changes: 30 additions & 0 deletions src/Transaction.php
Expand Up @@ -23,6 +23,12 @@ abstract class Transaction
*/
protected ?string $event = null;

/**
* @var string|null $lockTableName Specifies the name of the table to
* optionally acquire a lock on for writing.
*/
protected ?string $lockTableName = null;

/**
* Performs the action.
* @throws Throwable
Expand All @@ -34,6 +40,25 @@ private function validateAndPerform(): self
$this->perform();
return $this;
}

/**
* @throws Throwable
*/
private function lockTableIfNecessary()
{
if (!$this->lockTableName)
return;

DB::raw("LOCK TABLES `$this->lockTableName` WRITE");
}

private function unlockTableIfNecessary()
{
if (!$this->lockTableName)
return;

DB::raw("UNLOCK TABLES");
}

/**
* Executes the transaction inside a database transaction context. If an
Expand All @@ -46,13 +71,18 @@ public function execute(): self
{
try
{
$this->lockTableIfNecessary();
DB::transaction(fn() => $this->validateAndPerform());
}
catch(Throwable $exception)
{
$this->cleanupAfterFailure();
throw $exception;
}
finally
{
$this->unlockTableIfNecessary();
}
$this->fireEvent();
return $this;
}
Expand Down
43 changes: 43 additions & 0 deletions tests/TableLockTransactonTests.php
@@ -0,0 +1,43 @@
<?php
/** @noinspection PhpUnhandledExceptionInspection */

namespace YetAnother\Tests;

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use YetAnother\Tests\Transactions\TableLockTransaction;

class TableLockTransactonTests extends TestCase
{
protected function setUp(): void
{
parent::setUp();

Schema::create(TableLockTransaction::TableName, function(Blueprint $table)
{
$table->integer('a');
$table->integer('b');
$table->integer('c');
});
}

protected function tearDown(): void
{
Schema::drop(TableLockTransaction::TableName);

parent::tearDown();
}

function testLockedTableInsertDoesNotThrow()
{
(new TableLockTransaction())->execute();

/** @noinspection SqlNoDataSourceInspection */
$row = DB::selectOne('SELECT * FROM `' . TableLockTransaction::TableName . '`');

$this->assertEquals(1, $row->a);
$this->assertEquals(2, $row->b);
$this->assertEquals(3, $row->c);
}
}
21 changes: 21 additions & 0 deletions tests/Transactions/TableLockTransaction.php
@@ -0,0 +1,21 @@
<?php
namespace YetAnother\Tests\Transactions;

use Illuminate\Support\Facades\DB;
use YetAnother\Laravel\Transaction;

class TableLockTransaction extends Transaction
{
const TableName = 'LockedTable';

public function __construct()
{
$this->lockTableName = self::TableName;
}

protected function perform(): void
{
/** @noinspection SqlNoDataSourceInspection */
DB::insert("INSERT INTO `$this->lockTableName` VALUES(1, 2, 3)");
}
}

0 comments on commit 0ae66aa

Please sign in to comment.