You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
114 lines
3.6 KiB
114 lines
3.6 KiB
<?php |
|
|
|
namespace App\Services; |
|
|
|
use App\Contracts\DatabaseListerInterface; |
|
use Illuminate\Support\Facades\DB; |
|
use InvalidArgumentException; |
|
use Throwable; |
|
|
|
class PgsqlDatabaseService implements DatabaseListerInterface |
|
{ |
|
private const PROTECTED = ['postgres', 'template0', 'template1']; |
|
|
|
private const QUERY = <<<'SQL' |
|
SELECT |
|
d.datname AS db_name, |
|
'—' AS created, |
|
0 AS active_days, |
|
CASE WHEN d.datallowconn THEN 'ONLINE' ELSE 'RESTRICTED' END AS state, |
|
COALESCE(to_char(s.stats_reset, 'YYYY-MM-DD HH24:MI:SS'), '—') AS last_index_activity, |
|
'—' AS last_backup |
|
FROM pg_database d |
|
LEFT JOIN pg_stat_database s ON s.datname = d.datname |
|
WHERE d.datistemplate = false |
|
SQL; |
|
|
|
private string $connection = 'pgsql'; |
|
|
|
public function using(string $connection): self |
|
{ |
|
$clone = clone $this; |
|
$clone->connection = $connection; |
|
|
|
return $clone; |
|
} |
|
|
|
public function listDatabases(string $sort, string $dir, string $search = ''): array |
|
{ |
|
try { |
|
$rows = collect(DB::connection($this->connection)->select(self::QUERY)) |
|
->map(fn ($row) => (array) $row); |
|
|
|
if ($search !== '') { |
|
$needle = mb_strtolower($search); |
|
$rows = $rows->filter(fn (array $row) => str_contains(mb_strtolower($row['db_name'] ?? ''), $needle) |
|
|| str_contains(mb_strtolower($row['state'] ?? ''), $needle)); |
|
} |
|
|
|
$rows = $rows->sortBy(fn (array $row) => $row[$sort] ?? '', SORT_REGULAR, $dir === 'desc'); |
|
|
|
return [ |
|
'rows' => $rows->values()->all(), |
|
'meta' => [ |
|
'total' => $rows->count(), |
|
'server' => config('database.connections.' . $this->connection . '.host'), |
|
'driver' => 'pgsql', |
|
'sql_started' => null, |
|
'uptime_days' => null, |
|
], |
|
'error' => null, |
|
]; |
|
} catch (Throwable) { |
|
return [ |
|
'rows' => [], |
|
'meta' => [ |
|
'total' => 0, |
|
'server' => config('database.connections.' . $this->connection . '.host'), |
|
'driver' => 'pgsql', |
|
'sql_started' => null, |
|
'uptime_days' => null, |
|
], |
|
'error' => 'Unable to load PostgreSQL databases. Check connectivity and credentials.', |
|
]; |
|
} |
|
} |
|
|
|
public function makeOffline(string $databaseName): void |
|
{ |
|
$databaseName = $this->validateName($databaseName); |
|
|
|
if ($this->isProtected($databaseName)) { |
|
throw new InvalidArgumentException('System databases cannot be restricted.'); |
|
} |
|
|
|
$quoted = '"' . str_replace('"', '""', $databaseName) . '"'; |
|
|
|
DB::connection($this->connection)->statement("ALTER DATABASE {$quoted} CONNECTION LIMIT 0"); |
|
DB::connection($this->connection)->statement( |
|
"SELECT pg_terminate_backend(pid) FROM pg_stat_activity WHERE datname = ? AND pid <> pg_backend_pid()", |
|
[$databaseName] |
|
); |
|
} |
|
|
|
public function isProtected(string $databaseName): bool |
|
{ |
|
return in_array(mb_strtolower($databaseName), self::PROTECTED, true); |
|
} |
|
|
|
public function supportsOffline(): bool |
|
{ |
|
return true; |
|
} |
|
|
|
private function validateName(string $databaseName): string |
|
{ |
|
$databaseName = trim($databaseName); |
|
|
|
if (! preg_match('/^[a-zA-Z0-9_]+$/', $databaseName)) { |
|
throw new InvalidArgumentException('Invalid database name.'); |
|
} |
|
|
|
return $databaseName; |
|
} |
|
}
|
|
|