Skip to content

Commit bc170de

Browse files
authored
feature: postgresql - support modification connection parameters (#2120)
1 parent 468ef74 commit bc170de

File tree

4 files changed

+448
-67
lines changed

4 files changed

+448
-67
lines changed

src/lib/postgresql/src/Flow/PostgreSql/Client/ConnectionParameters.php

Lines changed: 199 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,17 @@
66

77
final readonly class ConnectionParameters
88
{
9+
/**
10+
* @param array<string, string> $options
11+
*/
912
private function __construct(
10-
public string $connectionString,
13+
private string $host,
14+
private int $port,
15+
private string $database,
16+
private ?string $user,
17+
#[\SensitiveParameter]
18+
private ?string $password,
19+
private array $options,
1120
) {
1221
}
1322

@@ -25,44 +34,212 @@ public static function fromParams(
2534
?string $password = null,
2635
array $options = [],
2736
) : self {
28-
$parts = [
29-
\sprintf('host=%s', $host),
30-
\sprintf('port=%d', $port),
31-
\sprintf('dbname=%s', $database),
32-
];
37+
return new self(
38+
host: $host,
39+
port: $port,
40+
database: $database,
41+
user: $user,
42+
password: $password,
43+
options: $options,
44+
);
45+
}
3346

34-
if ($user !== null) {
35-
$parts[] = \sprintf('user=%s', $user);
36-
}
47+
/**
48+
* Create from a PostgreSQL libpq connection string.
49+
*
50+
* Parses connection strings in libpq format: key=value pairs separated by spaces.
51+
* Supports quoted values for values containing spaces (e.g., password='my secret').
52+
*
53+
* @throws \InvalidArgumentException if dbname is missing
54+
*/
55+
public static function fromString(#[\SensitiveParameter] string $connectionString) : self
56+
{
57+
$parts = [];
58+
$pattern = '/(\w+)=(?:\'([^\']*)\'|([^\s]*))/';
59+
\preg_match_all($pattern, $connectionString, $matches, \PREG_SET_ORDER);
3760

38-
if ($password !== null) {
39-
$parts[] = \sprintf('password=%s', $password);
61+
foreach ($matches as $match) {
62+
$key = $match[1];
63+
$quotedValue = $match[2] ?? '';
64+
$unquotedValue = $match[3] ?? '';
65+
$value = $quotedValue !== '' ? $quotedValue : $unquotedValue;
66+
$parts[$key] = $value;
4067
}
4168

42-
foreach ($options as $key => $value) {
43-
$parts[] = \sprintf('%s=%s', $key, $value);
69+
if (!isset($parts['dbname'])) {
70+
throw new \InvalidArgumentException('Missing dbname in connection string');
4471
}
4572

46-
return new self(\implode(' ', $parts));
73+
return new self(
74+
host: $parts['host'] ?? 'localhost',
75+
port: isset($parts['port']) ? (int) $parts['port'] : 5432,
76+
database: $parts['dbname'],
77+
user: $parts['user'] ?? null,
78+
password: $parts['password'] ?? null,
79+
options: \array_diff_key($parts, \array_flip(['host', 'port', 'dbname', 'user', 'password'])),
80+
);
4781
}
4882

4983
/**
50-
* Create from a PostgreSQL connection string.
84+
* Mask password in debug output to prevent accidental exposure.
85+
*
86+
* @return array<string, mixed>
5187
*/
52-
public static function fromString(#[\SensitiveParameter] string $connectionString) : self
88+
public function __debugInfo() : array
89+
{
90+
return [
91+
'host' => $this->host,
92+
'port' => $this->port,
93+
'database' => $this->database,
94+
'user' => $this->user,
95+
'password' => $this->password !== null ? '***' : null,
96+
'options' => $this->options,
97+
];
98+
}
99+
100+
public function database() : string
101+
{
102+
return $this->database;
103+
}
104+
105+
public function host() : string
53106
{
54-
return new self($connectionString);
107+
return $this->host;
55108
}
56109

57110
/**
58-
* Mask password in debug output to prevent accidental exposure.
59-
*
60111
* @return array<string, string>
61112
*/
62-
public function __debugInfo() : array
113+
public function options() : array
63114
{
64-
return [
65-
'connectionString' => \preg_replace('/password=[^\s]+/', 'password=***', $this->connectionString) ?? $this->connectionString,
115+
return $this->options;
116+
}
117+
118+
public function password() : ?string
119+
{
120+
return $this->password;
121+
}
122+
123+
public function port() : int
124+
{
125+
return $this->port;
126+
}
127+
128+
/**
129+
* Convert connection parameters to a libpq connection string.
130+
*/
131+
public function toString() : string
132+
{
133+
$parts = [
134+
\sprintf('host=%s', $this->host),
135+
\sprintf('port=%d', $this->port),
136+
\sprintf('dbname=%s', $this->database),
66137
];
138+
139+
if ($this->user !== null) {
140+
$parts[] = \sprintf('user=%s', $this->user);
141+
}
142+
143+
if ($this->password !== null) {
144+
$parts[] = \sprintf('password=%s', $this->password);
145+
}
146+
147+
foreach ($this->options as $key => $value) {
148+
$parts[] = \sprintf('%s=%s', $key, $value);
149+
}
150+
151+
return \implode(' ', $parts);
152+
}
153+
154+
public function user() : ?string
155+
{
156+
return $this->user;
157+
}
158+
159+
public function withDatabase(string $database) : self
160+
{
161+
return new self(
162+
host: $this->host,
163+
port: $this->port,
164+
database: $database,
165+
user: $this->user,
166+
password: $this->password,
167+
options: $this->options,
168+
);
169+
}
170+
171+
public function withHost(string $host) : self
172+
{
173+
return new self(
174+
host: $host,
175+
port: $this->port,
176+
database: $this->database,
177+
user: $this->user,
178+
password: $this->password,
179+
options: $this->options,
180+
);
181+
}
182+
183+
public function withOption(string $key, string $value) : self
184+
{
185+
return new self(
186+
host: $this->host,
187+
port: $this->port,
188+
database: $this->database,
189+
user: $this->user,
190+
password: $this->password,
191+
options: \array_merge($this->options, [$key => $value]),
192+
);
193+
}
194+
195+
/**
196+
* @param array<string, string> $options
197+
*/
198+
public function withOptions(array $options) : self
199+
{
200+
return new self(
201+
host: $this->host,
202+
port: $this->port,
203+
database: $this->database,
204+
user: $this->user,
205+
password: $this->password,
206+
options: $options,
207+
);
208+
}
209+
210+
public function withPassword(#[\SensitiveParameter] ?string $password) : self
211+
{
212+
return new self(
213+
host: $this->host,
214+
port: $this->port,
215+
database: $this->database,
216+
user: $this->user,
217+
password: $password,
218+
options: $this->options,
219+
);
220+
}
221+
222+
public function withPort(int $port) : self
223+
{
224+
return new self(
225+
host: $this->host,
226+
port: $port,
227+
database: $this->database,
228+
user: $this->user,
229+
password: $this->password,
230+
options: $this->options,
231+
);
232+
}
233+
234+
public function withUser(?string $user) : self
235+
{
236+
return new self(
237+
host: $this->host,
238+
port: $this->port,
239+
database: $this->database,
240+
user: $user,
241+
password: $this->password,
242+
options: $this->options,
243+
);
67244
}
68245
}

src/lib/postgresql/src/Flow/PostgreSql/Client/Infrastructure/PgSql/PgSqlClient.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static function connect(
4242
}
4343

4444
\error_clear_last();
45-
$connection = @\pg_connect($params->connectionString);
45+
$connection = @\pg_connect($params->toString());
4646

4747
if ($connection === false) {
4848
$error = \error_get_last();

0 commit comments

Comments
 (0)