Skip to content

Commit a8ce83a

Browse files
authored
[9.x] Fix parsing config('database.connections.pgsql.search_path') (#41088)
The given PostgresConnector regex doesn't consider the full range of characters allowed in a schema or variable name - specifically '-' and accented characters. e.g., 'test-db' was being parsed as `set search_path to "test", "db"` instead of `set search_path to "test-db"` Replace the 'search_path' regex allowlist of characters with a blocklist of delimiters when config('database.connections.pgsql.search_path') is a string value. Technically Postgres _does_ allow our config delimiter characters (spaces, comma, quotes) in symbols so an array configuration can instead be used for such schema names. However single/double quote characters in such array configs aren't supported. --- * Roll methods testPostgresSearchPathCommaSeparatedValueSupported() & testPostgresSearchPathVariablesSupported() into testPostgresSearchPathIsSet() with the provideSearchPaths() data set. * testPostgresSearchPathArraySupported() is repurposed to show config('database.connections.pgsql.schema') from versions < 9.x is used when the 'search_path' config key is absent. * Fix PostgresConnector::quoteSearchPath() docblock since passing in a string value would throw an exception for being un-Countable. Its only use is being given parseSearchPath()'s return value which is an array.
1 parent e4c93c4 commit a8ce83a

File tree

2 files changed

+83
-39
lines changed

2 files changed

+83
-39
lines changed

src/Illuminate/Database/Connectors/PostgresConnector.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ protected function configureSearchPath($connection, $config)
127127
protected function parseSearchPath($searchPath)
128128
{
129129
if (is_string($searchPath)) {
130-
preg_match_all('/[a-zA-z0-9$]{1,}/i', $searchPath, $matches);
130+
preg_match_all('/[^\s,"\']+/', $searchPath, $matches);
131131

132132
$searchPath = $matches[0];
133133
}
@@ -144,7 +144,7 @@ protected function parseSearchPath($searchPath)
144144
/**
145145
* Format the search path for the DSN.
146146
*
147-
* @param array|string $searchPath
147+
* @param array $searchPath
148148
* @return string
149149
*/
150150
protected function quoteSearchPath($searchPath)

tests/Database/DatabaseConnectorTest.php

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -89,27 +89,103 @@ public function testPostgresConnectCallsCreateConnectionWithProperArguments()
8989
$this->assertSame($result, $connection);
9090
}
9191

92-
public function testPostgresSearchPathIsSet()
92+
/**
93+
* @dataProvider provideSearchPaths
94+
*
95+
* @param string $searchPath
96+
* @param string $expectedSql
97+
*/
98+
public function testPostgresSearchPathIsSet($searchPath, $expectedSql)
9399
{
94100
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
95-
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => 'public', 'charset' => 'utf8'];
101+
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => $searchPath, 'charset' => 'utf8'];
96102
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
97103
$connection = m::mock(stdClass::class);
98104
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
99105
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
100106
$statement = m::mock(PDOStatement::class);
101107
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($statement);
102-
$connection->shouldReceive('prepare')->once()->with('set search_path to "public"')->andReturn($statement);
108+
$connection->shouldReceive('prepare')->once()->with($expectedSql)->andReturn($statement);
103109
$statement->shouldReceive('execute')->twice();
104110
$result = $connector->connect($config);
105111

106112
$this->assertSame($result, $connection);
107113
}
108114

109-
public function testPostgresSearchPathArraySupported()
115+
public function provideSearchPaths()
116+
{
117+
return [
118+
'all-lowercase' => [
119+
'public',
120+
'set search_path to "public"',
121+
],
122+
'case-sensitive' => [
123+
'Public',
124+
'set search_path to "Public"',
125+
],
126+
'special characters' => [
127+
'¡foo_bar-Baz!.Áüõß',
128+
'set search_path to "¡foo_bar-Baz!.Áüõß"',
129+
],
130+
'single-quoted' => [
131+
"'public'",
132+
'set search_path to "public"',
133+
],
134+
'double-quoted' => [
135+
'"public"',
136+
'set search_path to "public"',
137+
],
138+
'variable' => [
139+
'$user',
140+
'set search_path to "$user"',
141+
],
142+
'delimit space' => [
143+
'public user',
144+
'set search_path to "public", "user"',
145+
],
146+
'delimit newline' => [
147+
"public\nuser\r\n\ttest",
148+
'set search_path to "public", "user", "test"',
149+
],
150+
'delimit comma' => [
151+
'public,user',
152+
'set search_path to "public", "user"',
153+
],
154+
'delimit comma and space' => [
155+
'public, user',
156+
'set search_path to "public", "user"',
157+
],
158+
'single-quoted many' => [
159+
"'public', 'user'",
160+
'set search_path to "public", "user"',
161+
],
162+
'double-quoted many' => [
163+
'"public", "user"',
164+
'set search_path to "public", "user"',
165+
],
166+
'quoted space is unsupported in string' => [
167+
'"public user"',
168+
'set search_path to "public", "user"',
169+
],
170+
'array' => [
171+
['public', 'user'],
172+
'set search_path to "public", "user"',
173+
],
174+
'array with variable' => [
175+
['public', '$user'],
176+
'set search_path to "public", "$user"',
177+
],
178+
'array with delimiter characters' => [
179+
['public', '"user"', "'test'", 'spaced schema'],
180+
'set search_path to "public", "user", "test", "spaced schema"',
181+
],
182+
];
183+
}
184+
185+
public function testPostgresSearchPathFallbackToConfigKeySchema()
110186
{
111187
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
112-
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => ['public', '"user"'], 'charset' => 'utf8'];
188+
$config = ['host' => 'foo', 'database' => 'bar', 'schema' => ['public', '"user"'], 'charset' => 'utf8'];
113189
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
114190
$connection = m::mock(stdClass::class);
115191
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
@@ -123,38 +199,6 @@ public function testPostgresSearchPathArraySupported()
123199
$this->assertSame($result, $connection);
124200
}
125201

126-
public function testPostgresSearchPathCommaSeparatedValueSupported()
127-
{
128-
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
129-
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => 'public, "user"', 'charset' => 'utf8'];
130-
$connector = $this->getMockBuilder('Illuminate\Database\Connectors\PostgresConnector')->setMethods(['createConnection', 'getOptions'])->getMock();
131-
$connection = m::mock('stdClass');
132-
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
133-
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
134-
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($connection);
135-
$connection->shouldReceive('prepare')->once()->with('set search_path to "public", "user"')->andReturn($connection);
136-
$connection->shouldReceive('execute')->twice();
137-
$result = $connector->connect($config);
138-
139-
$this->assertSame($result, $connection);
140-
}
141-
142-
public function testPostgresSearchPathVariablesSupported()
143-
{
144-
$dsn = 'pgsql:host=foo;dbname=\'bar\'';
145-
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => '"$user", public, user', 'charset' => 'utf8'];
146-
$connector = $this->getMockBuilder('Illuminate\Database\Connectors\PostgresConnector')->setMethods(['createConnection', 'getOptions'])->getMock();
147-
$connection = m::mock('stdClass');
148-
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
149-
$connector->expects($this->once())->method('createConnection')->with($this->equalTo($dsn), $this->equalTo($config), $this->equalTo(['options']))->willReturn($connection);
150-
$connection->shouldReceive('prepare')->once()->with('set names \'utf8\'')->andReturn($connection);
151-
$connection->shouldReceive('prepare')->once()->with('set search_path to "$user", "public", "user"')->andReturn($connection);
152-
$connection->shouldReceive('execute')->twice();
153-
$result = $connector->connect($config);
154-
155-
$this->assertSame($result, $connection);
156-
}
157-
158202
public function testPostgresApplicationNameIsSet()
159203
{
160204
$dsn = 'pgsql:host=foo;dbname=\'bar\'';

0 commit comments

Comments
 (0)