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
9 changes: 7 additions & 2 deletions src/Cache/Lock/PdoLockProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ private static function advisoryKeys(string $value): array
];
}

private static function isDatabaseTrue(mixed $value): bool
{
return $value === true || $value === 1 || $value === '1' || $value === 't';
}

private static function signedHex32(string $value): int
{
$unsigned = (int) hexdec($value);
Expand Down Expand Up @@ -150,7 +155,7 @@ private function acquirePgsql(string $key, float $waitSeconds, float $leaseSecon
$stmt = $this->pdo->prepare('SELECT pg_try_advisory_lock(:k1, :k2)');
$stmt->execute([':k1' => $advisoryKeys[0], ':k2' => $advisoryKeys[1]]);
$result = $stmt->fetchColumn();
if ($result === 1 || $result === 't' || $result === '1') {
if (self::isDatabaseTrue($result)) {
$this->activeTokens[$lockKey] = $token;

return new LockHandle($lockKey, $token, $advisoryKeys, $leaseSeconds);
Expand Down Expand Up @@ -207,7 +212,7 @@ private function releasePgsql(LockHandle $handle): bool
$stmt->execute([':k1' => $advisoryKeys[0], ':k2' => $advisoryKeys[1]]);
$result = $stmt->fetchColumn();

return $result === 1 || $result === 't' || $result === '1';
return self::isDatabaseTrue($result);
} catch (Throwable) {
return false;
}
Expand Down
61 changes: 61 additions & 0 deletions tests/Cache/LockProviderTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,64 @@
}
}
});

test('PostgreSQL PDO locks accept native boolean results', function (): void {
$pdo = new class () extends PDO {
public function __construct()
{
}

public function getAttribute(int $attribute): mixed
{
unset($attribute);

return 'pgsql';
}

public function prepare(string $query, array $options = []): PDOStatement|false
{
unset($query, $options);

return $this->successfulStatement();
}

public function query(string $query, ?int $fetchMode = null, mixed ...$fetchModeArgs): PDOStatement|false
{
unset($query, $fetchMode, $fetchModeArgs);

return $this->successfulStatement();
}

private function successfulStatement(): PDOStatement
{
return new class () extends PDOStatement {
public function __construct()
{
}

public function execute(?array $params = null): bool
{
unset($params);

return true;
}

public function fetchColumn(int $column = 0): mixed
{
unset($column);

return true;
}
};
}
};
$provider = new PdoLockProvider($pdo);
$handle = $provider->acquire('worker:postgres', 0.0, 10.0);

expect($handle)->not->toBeNull()
->and($provider->refresh($handle, 10.0))->toBeTrue();

$provider->release($handle);

expect($provider->refresh($handle, 10.0))->toBeFalse();
});
22 changes: 22 additions & 0 deletions tests/Cache/PdoPgsqlCachePoolTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
declare(strict_types=1);

use Infocyph\CacheLayer\Cache\Cache;
use Infocyph\CacheLayer\Cache\Lock\PdoLockProvider;

if (! in_array('pgsql', PDO::getAvailableDrivers(), true)) {
test('PostgreSQL PDO driver not present')->skip();
Expand Down Expand Up @@ -56,3 +57,24 @@
->and($this->cache->get('a'))->toBeNull()
->and($this->cache->get('b'))->toBe('B');
});

test('PostgreSQL advisory locks accept native PDO booleans and release ownership', function () use ($dsn, $user, $pass) {
$connect = static function () use ($dsn, $user, $pass): PDO {
return new PDO($dsn, $user, $pass, [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]);
};
$holder = new PdoLockProvider($connect(), 'cachelayer:test:');
$contender = new PdoLockProvider($connect(), 'cachelayer:test:');
$key = 'postgres-lock-' . bin2hex(random_bytes(8));
$handle = $holder->acquire($key, 0.0, 5.0);

expect($handle)->not->toBeNull()
->and($holder->refresh($handle, 5.0))->toBeTrue()
->and($contender->acquire($key, 0.0, 5.0))->toBeNull();

$holder->release($handle);

$replacement = $contender->acquire($key, 0.0, 5.0);
expect($holder->refresh($handle, 5.0))->toBeFalse()
->and($replacement)->not->toBeNull();
$contender->release($replacement);
});