Skip to content

Commit def4e66

Browse files
committed
[FIX] PostgreSQL support.
1 parent 7c27727 commit def4e66

5 files changed

Lines changed: 127 additions & 25 deletions

File tree

internal/engine/install/engine.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1774,15 +1774,46 @@ try {
17741774
new \PDO("sqlite:".$name, null, null, $timeout);
17751775
echo json_encode(["ok"=>true]); exit(0);
17761776
}
1777+
if ($type === "pgsql") {
1778+
$maintenanceOk = false;
1779+
$maintenanceErr = null;
1780+
foreach (["postgres", "template1"] as $db) {
1781+
try {
1782+
$dsnMaint = $port > 0 ? "pgsql:host={$host};port={$port};dbname={$db}" : "pgsql:host={$host};dbname={$db}";
1783+
new \PDO($dsnMaint, $user, $pass, $timeout);
1784+
$maintenanceOk = true;
1785+
break;
1786+
} catch (\Throwable $e) {
1787+
$maintenanceErr = $e;
1788+
}
1789+
}
1790+
1791+
$targetOk = false;
1792+
$targetErr = null;
1793+
if ($name !== "") {
1794+
try {
1795+
$dsn = $port > 0 ? "pgsql:host={$host};port={$port};dbname={$name}" : "pgsql:host={$host};dbname={$name}";
1796+
new \PDO($dsn, $user, $pass, $timeout);
1797+
$targetOk = true;
1798+
} catch (\Throwable $e) {
1799+
$targetErr = $e;
1800+
}
1801+
}
1802+
1803+
if (!$maintenanceOk && !$targetOk) {
1804+
throw $targetErr ?? $maintenanceErr ?? new \Exception("PostgreSQL connection failed.");
1805+
}
1806+
1807+
echo json_encode(["ok"=>true]); exit(0);
1808+
}
1809+
17771810
$dsnNoDb = match($type) {
1778-
"pgsql" => $port > 0 ? "pgsql:host={$host};port={$port};dbname=postgres" : "pgsql:host={$host};dbname=postgres",
17791811
"sqlsrv" => $port > 0 ? "sqlsrv:Server={$host},{$port}" : "sqlsrv:Server={$host}",
17801812
default => "mysql:host={$host};port={$port};charset=utf8mb4",
17811813
};
17821814
new \PDO($dsnNoDb, $user, $pass, $timeout);
17831815
if ($name !== "") {
17841816
$dsn = match($type) {
1785-
"pgsql" => "pgsql:host={$host};port={$port};dbname={$name}",
17861817
"sqlsrv" => ($port > 0 ? "sqlsrv:Server={$host},{$port};Database={$name}" : "sqlsrv:Server={$host};Database={$name}"),
17871818
default => "mysql:host={$host};port={$port};dbname={$name};charset=utf8mb4",
17881819
};

internal/engine/install/engine_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ func TestCmpSemver(t *testing.T) {
6565
func TestDbConnectionTestScriptUsesPostgresMaintenanceDb(t *testing.T) {
6666
t.Parallel()
6767

68-
if !strings.Contains(dbConnectionTestScript, "dbname=postgres") {
69-
t.Fatalf("dbConnectionTestScript does not contain PostgreSQL maintenance dbname")
68+
if !strings.Contains(dbConnectionTestScript, `"postgres"`) || !strings.Contains(dbConnectionTestScript, `"template1"`) {
69+
t.Fatalf("dbConnectionTestScript does not include expected PostgreSQL maintenance database candidates")
7070
}
7171
}

src/Commands/InstallCommand.php

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -474,15 +474,37 @@ protected function testDatabaseConnection(array $config): bool
474474
return false;
475475
}
476476
$this->createConnection($config);
477+
} elseif ($type === 'pgsql') {
478+
// PostgreSQL requires a database name; otherwise it defaults to using the username.
479+
// To support environments where `postgres` is missing/restricted, accept either:
480+
// - successful connect to a maintenance DB (postgres/template1), or
481+
// - successful connect to the target DB (if only that one is accessible).
482+
$maintenanceOk = false;
483+
$maintenanceErr = null;
484+
485+
$configForOps = $config;
486+
unset($configForOps['name']);
487+
$dbh = $this->createPostgresMaintenanceConnection($configForOps, $maintenanceErr);
488+
$maintenanceOk = $dbh !== null;
489+
490+
$targetOk = false;
491+
$targetErr = null;
492+
if (!empty($config['name'])) {
493+
try {
494+
$this->createConnection($config, true);
495+
$targetOk = true;
496+
} catch (PDOException $e) {
497+
$targetErr = $e;
498+
}
499+
}
500+
501+
if (!$maintenanceOk && !$targetOk) {
502+
throw $targetErr ?? $maintenanceErr ?? new PDOException('PostgreSQL connection failed.');
503+
}
477504
} else {
478505
// For server-based databases, try to connect without database name first
479506
$configWithoutDb = $config;
480507
unset($configWithoutDb['name']);
481-
if ($type === 'pgsql') {
482-
// PostgreSQL requires a database name; otherwise it defaults to using the username.
483-
// Use a maintenance database for credential/host validation.
484-
$configWithoutDb['name'] = 'postgres';
485-
}
486508
$this->createConnection($configWithoutDb);
487509

488510
// If database name is provided, try to connect to it
@@ -528,6 +550,7 @@ protected function askRetryDatabaseConnection(): bool
528550
{
529551
$options = ['Exit installation', 'Try again'];
530552
$active = 0;
553+
$buffer = '';
531554

532555
// Display question in cyan without icon
533556
$this->tui->addLog('Would you like to try again or exit installation?', 'ask');
@@ -540,31 +563,42 @@ protected function askRetryDatabaseConnection(): bool
540563
$this->setSttyMode('-icanon -echo');
541564
try {
542565
while (true) {
543-
$key = fread(STDIN, 3);
544-
if ($key === '' || $key === false) {
566+
$chunk = fread(STDIN, 3);
567+
if ($chunk === '' || $chunk === false) {
545568
usleep(20000);
546569
continue;
547570
}
548571

549-
switch ($key) {
572+
$buffer .= $chunk;
573+
if (strlen($buffer) > 3) {
574+
$buffer = substr($buffer, -3);
575+
}
576+
577+
$prevActive = $active;
578+
switch ($buffer) {
550579
case "\033[D": // ←
551580
case "\033[A": // ↑
552581
$active = max(0, $active - 1);
582+
$buffer = '';
553583
break;
554584

555585
case "\033[C": // →
556586
case "\033[B": // ↓
557587
$active = min(count($options) - 1, $active + 1);
588+
$buffer = '';
558589
break;
559590

560591
case "\n": // Enter
592+
case "\r": // Enter (some terminals)
561593
$active
562594
? $this->tui->replaceLastLogs('Reinput DB connections.', 2)
563595
: $this->tui->replaceLastLogs('Exit ...', 3);
564596
return $active;
565597
}
566598

567-
$this->tui->replaceLastLog($this->tui->renderRadio($options, $active));
599+
if ($active !== $prevActive) {
600+
$this->tui->replaceLastLog($this->tui->renderRadio($options, $active));
601+
}
568602
}
569603
} finally {
570604
$this->setSttyMode('sane');

src/Concerns/ConfiguresDatabase.php

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,10 @@ trait ConfiguresDatabase
1212
* Create database connection.
1313
*
1414
* @param array $config
15+
* @param bool $quiet
1516
* @return PDO
1617
*/
17-
protected function createConnection(array $config): PDO
18+
protected function createConnection(array $config, bool $quiet = false): PDO
1819
{
1920
$type = $config['type'];
2021
$host = $config['host'] ?? '';
@@ -39,11 +40,35 @@ protected function createConnection(array $config): PDO
3940

4041
return $dbh;
4142
} catch (PDOException $e) {
42-
Console::error("Database connection failed: " . $e->getMessage());
43+
if (!$quiet) {
44+
Console::error("Database connection failed: " . $e->getMessage());
45+
}
4346
throw $e;
4447
}
4548
}
4649

50+
/**
51+
* Try to connect to a PostgreSQL maintenance database.
52+
*
53+
* Some environments remove/restrict access to the default `postgres` DB.
54+
* `template1` is expected to exist on all PostgreSQL instances.
55+
*/
56+
protected function createPostgresMaintenanceConnection(array $config, ?PDOException &$lastError = null): ?PDO
57+
{
58+
$candidates = ['postgres', 'template1'];
59+
foreach ($candidates as $dbName) {
60+
$cfg = $config;
61+
$cfg['name'] = $dbName;
62+
try {
63+
$lastError = null;
64+
return $this->createConnection($cfg, true);
65+
} catch (PDOException $e) {
66+
$lastError = $e;
67+
}
68+
}
69+
return null;
70+
}
71+
4772
/**
4873
* Build DSN string.
4974
*
@@ -183,14 +208,17 @@ protected function createDatabase(array $config, string $collation): bool
183208
// MySQL / PostgreSQL
184209
$configWithoutDb = $config;
185210
unset($configWithoutDb['name']);
186-
if ($type === 'pgsql') {
187-
// PostgreSQL requires a database name; otherwise it defaults to using the username.
188-
// Use a maintenance database for CREATE DATABASE operations.
189-
$configWithoutDb['name'] = 'postgres';
190-
}
191211

192212
try {
193-
$dbh = $this->createConnection($configWithoutDb);
213+
if ($type === 'pgsql') {
214+
$last = null;
215+
$dbh = $this->createPostgresMaintenanceConnection($configWithoutDb, $last);
216+
if (!$dbh) {
217+
throw $last ?? new PDOException('Unable to connect to PostgreSQL maintenance database.');
218+
}
219+
} else {
220+
$dbh = $this->createConnection($configWithoutDb);
221+
}
194222
$charset = $this->getCharsetFromCollation($collation);
195223

196224
if ($type === 'pgsql') {

src/Presets/EvolutionPreset.php

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,9 +126,19 @@ protected function setupDatabase(string $name, array $options): void
126126
// Test connection first (without database)
127127
$dbConfigWithoutDb = $dbConfig;
128128
unset($dbConfigWithoutDb['name']);
129-
130-
if (!$this->testConnection($dbConfigWithoutDb)) {
131-
throw new \RuntimeException("Cannot connect to database server. Please check your credentials.");
129+
$dbh = null;
130+
if (($dbConfigWithoutDb['type'] ?? '') === 'pgsql') {
131+
$last = null;
132+
$dbh = $this->createPostgresMaintenanceConnection($dbConfigWithoutDb, $last);
133+
if (!$dbh) {
134+
$msg = $last ? $last->getMessage() : 'Cannot connect to PostgreSQL maintenance database.';
135+
throw new \RuntimeException("Cannot connect to database server. {$msg}");
136+
}
137+
} else {
138+
if (!$this->testConnection($dbConfigWithoutDb)) {
139+
throw new \RuntimeException("Cannot connect to database server. Please check your credentials.");
140+
}
141+
$dbh = $this->createConnection($dbConfigWithoutDb);
132142
}
133143

134144
// Create database if needed
@@ -137,7 +147,6 @@ protected function setupDatabase(string $name, array $options): void
137147
}
138148

139149
// Resolve collation before creating database
140-
$dbh = $this->createConnection($dbConfigWithoutDb);
141150
$serverVersion = null;
142151
if ($dbConfig['type'] === 'mysql') {
143152
try {

0 commit comments

Comments
 (0)