Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion src/AdvisoryLockServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ public function register(): void

public function boot(TransactionEventHub $hub): void
{
TransactionEventHub::setResolver(fn () => $hub);
TransactionEventHub::setResolver(static fn () => $hub);
}
}
4 changes: 2 additions & 2 deletions src/ConnectionServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ final class ConnectionServiceProvider extends ServiceProvider
*/
public function register(): void
{
Connection::resolverFor('mysql', fn (...$args) => new MySqlConnection(...$args));
Connection::resolverFor('pgsql', fn (...$args) => new PostgresConnection(...$args));
Connection::resolverFor('mysql', static fn (...$args) => new MySqlConnection(...$args));
Connection::resolverFor('pgsql', static fn (...$args) => new PostgresConnection(...$args));
}
}
4 changes: 1 addition & 3 deletions src/Contracts/InvalidTransactionLevelException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@
*
* You can't use TransactionLocker outside of transaction.
*/
class InvalidTransactionLevelException extends BadMethodCallException
{
}
class InvalidTransactionLevelException extends BadMethodCallException {}
4 changes: 1 addition & 3 deletions src/Contracts/UnsupportedDriverException.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,4 @@
*
* Requested operation is not supported on the driver.
*/
class UnsupportedDriverException extends DomainException
{
}
class UnsupportedDriverException extends DomainException {}
4 changes: 2 additions & 2 deletions src/LockerFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,7 @@ class LockerFactory implements Contracts\LockerFactory

public function __construct(
protected Connection $connection,
) {
}
) {}

public function forTransaction(): TransactionLocker
{
Expand All @@ -40,6 +39,7 @@ public function forSession(): SessionLocker
if ($this->connection instanceof PostgresConnection) {
return $this->session ??= new PostgresSessionLocker($this->connection);
}

// @codeCoverageIgnoreStart
throw new UnsupportedDriverException('SessionLocker is not supported');
// @codeCoverageIgnoreEnd
Expand Down
3 changes: 1 addition & 2 deletions src/MySqlSessionLock.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,7 @@ public function __construct(
private MySqlConnection $connection,
private WeakMap $locks,
private string $key,
) {
}
) {}

public function release(): bool
{
Expand Down
8 changes: 4 additions & 4 deletions src/PostgresSessionLocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\LockFailedException;
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\SessionLock;
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\SessionLocker;
use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTryLockLoopEmulator;
use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTimeoutEmulator;
use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\Selector;
use WeakMap;

Expand All @@ -36,10 +36,10 @@ public function __construct(
public function lockOrFail(string $key, int $timeout = 0): SessionLock
{
if ($timeout > 0) {
// Positive timeout can be emulated through repeating sleep and retry
$emulator = new PostgresTryLockLoopEmulator($this->connection);
// Positive timeout can be performed through temporary function
$emulator = new PostgresTimeoutEmulator($this->connection);
$sql = $emulator->sql($timeout, false);
$result = $emulator->performTryLockLoop($key, $timeout);
$result = $emulator->performWithTimeout($key, $timeout);
} else {
// Negative timeout means infinite wait
// Zero timeout means no wait
Expand Down
11 changes: 5 additions & 6 deletions src/PostgresTransactionLocker.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\InvalidTransactionLevelException;
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\LockFailedException;
use Mpyw\LaravelDatabaseAdvisoryLock\Contracts\TransactionLocker;
use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTryLockLoopEmulator;
use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\PostgresTimeoutEmulator;
use Mpyw\LaravelDatabaseAdvisoryLock\Utilities\Selector;

final class PostgresTransactionLocker implements TransactionLocker
Expand All @@ -18,8 +18,7 @@ final class PostgresTransactionLocker implements TransactionLocker

public function __construct(
protected PostgresConnection $connection,
) {
}
) {}

public function lockOrFail(string $key, int $timeout = 0): void
{
Expand All @@ -28,10 +27,10 @@ public function lockOrFail(string $key, int $timeout = 0): void
}

if ($timeout > 0) {
// Positive timeout can be emulated through repeating sleep and retry
$emulator = new PostgresTryLockLoopEmulator($this->connection);
// Positive timeout can be performed through temporary function
$emulator = new PostgresTimeoutEmulator($this->connection);
$sql = $emulator->sql($timeout, false);
$result = $emulator->performTryLockLoop($key, $timeout, true);
$result = $emulator->performWithTimeout($key, $timeout, true);
} else {
// Negative timeout means infinite wait
// Zero timeout means no wait
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,23 @@
use function preg_replace;

/**
* class PostgresTryLockLoopEmulator
* class PostgresTimeoutEmulator
*
* @internal
*/
final class PostgresTryLockLoopEmulator
final class PostgresTimeoutEmulator
{
public function __construct(
private PostgresConnection $connection,
) {
}
) {}

/**
* Perform a time-limited lock acquisition.
*
* @phpstan-param positive-int $timeout
* @throws QueryException
*/
public function performTryLockLoop(string $key, int $timeout, bool $forTransaction = false): bool
public function performWithTimeout(string $key, int $timeout, bool $forTransaction = false): bool
{
// Binding parameters to procedures is only allowed when PDOStatement emulation is enabled.
return PDOStatementEmulator::emulated(
Expand All @@ -45,33 +44,24 @@ public function performTryLockLoop(string $key, int $timeout, bool $forTransacti
public function sql(int $timeout, bool $forTransaction): string
{
$suffix = $forTransaction ? '_xact' : '';
$modifier = $forTransaction ? 'LOCAL' : 'SESSION';

$sql = <<<EOD
CREATE OR REPLACE FUNCTION
pg_temp.laravel_pg_try_advisory{$suffix}_lock_timeout(key text, timeout interval)
pg_temp.laravel_pg_try_advisory{$suffix}_lock_timeout(key text, timeout text)
RETURNS boolean
SET lock_timeout FROM CURRENT
AS $$
DECLARE
result boolean;
start timestamp with time zone;
now timestamp with time zone;
BEGIN
start := clock_timestamp();
LOOP
SELECT pg_try_advisory{$suffix}_lock(hashtext(key)) INTO result;
IF result THEN
RETURN true;
END IF;
now := clock_timestamp();
IF now - start > timeout THEN
RETURN false;
END IF;
PERFORM pg_sleep(0.5);
END LOOP;
EXECUTE format('SET {$modifier} lock_timeout TO %L;', timeout);
PERFORM pg_advisory{$suffix}_lock(hashtext(key));
RETURN true;
EXCEPTION
WHEN lock_not_available OR deadlock_detected THEN RETURN false;
END
$$
LANGUAGE plpgsql;
SELECT pg_temp.laravel_pg_try_advisory{$suffix}_lock_timeout(?, interval '{$timeout} seconds');
SELECT pg_temp.laravel_pg_try_advisory{$suffix}_lock_timeout(?, '{$timeout}s');
EOD;

return (string)preg_replace('/\s++/', ' ', $sql);
Expand Down
3 changes: 1 addition & 2 deletions src/Utilities/Selector.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,7 @@ final class Selector
{
public function __construct(
private ConnectionInterface $connection,
) {
}
) {}

/**
* Run query to get a single value from the result.
Expand Down
4 changes: 2 additions & 2 deletions tests/ReconnectionToleranceTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ public function testReconnectionWithoutActiveLocks(string $name): void
DB::connection($name)
->advisoryLocker()
->forSession()
->withLocking('', fn () => null);
->withLocking('', static fn () => null);
} catch (QueryException) {
}
$this->endListening();
Expand Down Expand Up @@ -113,7 +113,7 @@ public function testReconnectionWithActiveLocks(string $name): void
$conn
->advisoryLocker()
->forSession()
->withLocking('', fn () => null);
->withLocking('', static fn () => null);
} catch (QueryException) {
}
$this->endListening();
Expand Down
16 changes: 8 additions & 8 deletions tests/SessionLockerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ public function testDifferentKeysOnDifferentConnections(string $name): void
DB::connection($name)
->advisoryLocker()
->forSession()
->withLocking('foo', function () use ($name, &$passed): void {
->withLocking('foo', static function () use ($name, &$passed): void {
DB::connection("{$name}2")
->advisoryLocker()
->forSession()
->withLocking('bar', function () use (&$passed): void {
->withLocking('bar', static function () use (&$passed): void {
$passed = true;
});
});
Expand All @@ -50,7 +50,7 @@ public function testSameKeysOnDifferentConnections(string $name): void
DB::connection("{$name}2")
->advisoryLocker()
->forSession()
->withLocking('foo', function () use (&$passed): void {
->withLocking('foo', static function () use (&$passed): void {
$passed = true;
});
});
Expand All @@ -68,11 +68,11 @@ public function testDifferentKeysOnSameConnections(string $name): void
DB::connection($name)
->advisoryLocker()
->forSession()
->withLocking('foo', function (ConnectionInterface $conn) use (&$passed): void {
->withLocking('foo', static function (ConnectionInterface $conn) use (&$passed): void {
$conn
->advisoryLocker()
->forSession()
->withLocking('bar', function () use (&$passed): void {
->withLocking('bar', static function () use (&$passed): void {
$passed = true;
});
});
Expand All @@ -90,11 +90,11 @@ public function testSameKeysOnSameConnections(string $name): void
DB::connection($name)
->advisoryLocker()
->forSession()
->withLocking('foo', function (ConnectionInterface $conn) use (&$passed): void {
->withLocking('foo', static function (ConnectionInterface $conn) use (&$passed): void {
$conn
->advisoryLocker()
->forSession()
->withLocking('foo', function () use (&$passed): void {
->withLocking('foo', static function () use (&$passed): void {
$passed = true;
});
});
Expand Down Expand Up @@ -190,7 +190,7 @@ public function testFiniteTimeoutSuccessConsecutive(string $name): void
$conn->advisoryLocker()->forSession()->tryLock('baz', 1),
$conn->advisoryLocker()->forSession()->tryLock('qux', 1),
];
$result_booleans = array_map(fn ($result) => $result !== null, $results);
$result_booleans = array_map(static fn ($result) => $result !== null, $results);
$this->assertSame(0, $proc1->wait());
$this->assertSame(0, $proc2->wait());
$this->assertSame([false, true, false, true], $result_booleans);
Expand Down
2 changes: 1 addition & 1 deletion tests/TableTestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ protected function setUp(): void
$schema = Schema::connection('pgsql');

$schema->dropIfExists('users');
$schema->create('users', function (Blueprint $table): void {
$schema->create('users', static function (Blueprint $table): void {
$table->unsignedBigInteger('id')->unique();
});
}
Expand Down
8 changes: 4 additions & 4 deletions tests/TestCase.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,22 +45,22 @@ protected function getEnvironmentSetUp($app): void
]);
}

public function connectionsAll(): array
public static function connectionsAll(): array
{
return ['postgres' => ['pgsql'], 'mysql' => ['mysql'], 'mariadb' => ['mariadb']];
}

public function connectionsMysql(): array
public static function connectionsMysql(): array
{
return ['mysql' => ['mysql']];
}

public function connectionsMysqlLike(): array
public static function connectionsMysqlLike(): array
{
return ['mysql' => ['mysql'], 'mariadb' => ['mariadb']];
}

public function connectionsPostgres(): array
public static function connectionsPostgres(): array
{
return ['postgres' => ['pgsql']];
}
Expand Down
18 changes: 9 additions & 9 deletions tests/TransactionLockerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ public function testDifferentKeysOnDifferentConnections(string $name): void
{
$passed = false;

DB::connection($name)->transaction(function (ConnectionInterface $conn) use ($name, &$passed): void {
DB::connection($name)->transaction(static function (ConnectionInterface $conn) use ($name, &$passed): void {
$conn
->advisoryLocker()
->forTransaction()
->lockOrFail('foo');

DB::connection("{$name}2")->transaction(function (ConnectionInterface $conn): void {
DB::connection("{$name}2")->transaction(static function (ConnectionInterface $conn): void {
$conn
->advisoryLocker()
->forTransaction()
Expand Down Expand Up @@ -56,7 +56,7 @@ public function testSameKeysOnDifferentConnections(string $name): void
$this->expectException(LockFailedException::class);
$this->expectExceptionMessage('Failed to acquire lock: foo');

DB::connection("{$name}2")->transaction(function (ConnectionInterface $conn): void {
DB::connection("{$name}2")->transaction(static function (ConnectionInterface $conn): void {
$conn
->advisoryLocker()
->forTransaction()
Expand All @@ -75,7 +75,7 @@ public function testDifferentKeysOnSameConnections(string $name): void
{
$passed = false;

DB::connection($name)->transaction(function (ConnectionInterface $conn) use (&$passed): void {
DB::connection($name)->transaction(static function (ConnectionInterface $conn) use (&$passed): void {
$conn
->advisoryLocker()
->forTransaction()
Expand All @@ -100,7 +100,7 @@ public function testSameKeysOnSameConnections(string $name): void
{
$passed = false;

DB::connection($name)->transaction(function (ConnectionInterface $conn) use (&$passed): void {
DB::connection($name)->transaction(static function (ConnectionInterface $conn) use (&$passed): void {
$conn
->advisoryLocker()
->forTransaction()
Expand Down Expand Up @@ -142,7 +142,7 @@ public function testFiniteTimeoutSuccess(string $name): void
sleep(1);

try {
$result = DB::connection($name)->transaction(function (ConnectionInterface $conn) {
$result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) {
return $conn->advisoryLocker()->forTransaction()->tryLock('foo', 3);
});

Expand All @@ -164,7 +164,7 @@ public function testFinitePostgresTimeoutSuccessConsecutive(string $name): void
sleep(1);

try {
$result = DB::connection($name)->transaction(function (ConnectionInterface $conn) {
$result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) {
return [
$conn->advisoryLocker()->forTransaction()->tryLock('foo', 1),
$conn->advisoryLocker()->forTransaction()->tryLock('bar', 1),
Expand All @@ -191,7 +191,7 @@ public function testFinitePostgresTimeoutExceeded(string $name): void
sleep(1);

try {
$result = DB::connection($name)->transaction(function (ConnectionInterface $conn) {
$result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) {
return $conn->advisoryLocker()->forTransaction()->tryLock('foo', 1);
});

Expand All @@ -212,7 +212,7 @@ public function testInfinitePostgresTimeoutSuccess(string $name): void
sleep(1);

try {
$result = DB::connection($name)->transaction(function (ConnectionInterface $conn) {
$result = DB::connection($name)->transaction(static function (ConnectionInterface $conn) {
return $conn->advisoryLocker()->forTransaction()->tryLock('foo', -1);
});

Expand Down