Skip to content

Commit bd17679

Browse files
[9.x] Address confusion between PostgreSQL concepts "schema" and "search_path" (#35463)
* Fix terminology in respect to 'schema' vs 'search_path' * Added support for Comma separated search paths with added test * Added support for Postgres Variables in search paths with added test * Fix schema quoting issue and update tests to verify behavior More specifically, fix issue whereby individual schema paths in an array were quoted twice. Also, update arguments in tests to verify schema name parsing with vs. without quotes, for both string and array notations. * Update method to use new "search_path" config key name Co-authored-by: poppabear8883 <servnx@gmail.com>
1 parent 63fdefb commit bd17679

File tree

3 files changed

+52
-18
lines changed

3 files changed

+52
-18
lines changed

src/Illuminate/Database/Connectors/PostgresConnector.php

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public function connect(array $config)
4040
// database. Setting this DB timezone is an optional configuration item.
4141
$this->configureTimezone($connection, $config);
4242

43-
$this->configureSchema($connection, $config);
43+
$this->configureSearchPath($connection, $config);
4444

4545
// Postgres allows an application_name to be set by the user and this name is
4646
// used to when monitoring the application with pg_stat_activity. So we'll
@@ -85,38 +85,40 @@ protected function configureTimezone($connection, array $config)
8585
}
8686

8787
/**
88-
* Set the schema on the connection.
88+
* Set the search_path on the connection.
8989
*
9090
* @param \PDO $connection
9191
* @param array $config
9292
* @return void
9393
*/
94-
protected function configureSchema($connection, $config)
94+
protected function configureSearchPath($connection, $config)
9595
{
96-
if (isset($config['schema'])) {
97-
$schema = $this->formatSchema($config['schema']);
96+
if (isset($config['search_path'])) {
97+
$searchPath = $this->formatSearchPath($config['search_path']);
9898

99-
$connection->prepare("set search_path to {$schema}")->execute();
99+
$connection->prepare("set search_path to {$searchPath}")->execute();
100100
}
101101
}
102102

103103
/**
104-
* Format the schema for the DSN.
104+
* Format the search path for the DSN.
105105
*
106-
* @param array|string $schema
106+
* @param array|string $searchPath
107107
* @return string
108108
*/
109-
protected function formatSchema($schema)
109+
protected function formatSearchPath($searchPath)
110110
{
111-
if (is_array($schema)) {
112-
return '"'.implode('", "', $schema).'"';
111+
if (is_array($searchPath)) {
112+
$searchPath = '"'.implode('", "', $searchPath).'"';
113113
}
114114

115-
return '"'.$schema.'"';
115+
preg_match_all('/[a-zA-z0-9$]{1,}/i', $searchPath, $matches);
116+
117+
return '"'.implode('", "', $matches[0]).'"';
116118
}
117119

118120
/**
119-
* Set the schema on the connection.
121+
* Set the application name on the connection.
120122
*
121123
* @param \PDO $connection
122124
* @param array $config

src/Illuminate/Database/Schema/PostgresBuilder.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -164,12 +164,12 @@ protected function parseSchemaAndTable($table)
164164
{
165165
$table = explode('.', $table);
166166

167-
if (is_array($schema = $this->connection->getConfig('schema'))) {
168-
if (in_array($table[0], $schema)) {
167+
if (is_array($searchPath = $this->connection->getConfig('search_path'))) {
168+
if (in_array($table[0], $searchPath)) {
169169
return [array_shift($table), implode('.', $table)];
170170
}
171171

172-
$schema = head($schema);
172+
$schema = head($searchPath);
173173
}
174174

175175
return [$schema ?: 'public', implode('.', $table)];

tests/Database/DatabaseConnectorTest.php

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ public function testPostgresConnectCallsCreateConnectionWithProperArguments()
8888
public function testPostgresSearchPathIsSet()
8989
{
9090
$dsn = 'pgsql:host=foo;dbname=bar';
91-
$config = ['host' => 'foo', 'database' => 'bar', 'schema' => 'public', 'charset' => 'utf8'];
91+
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => 'public', 'charset' => 'utf8'];
9292
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
9393
$connection = m::mock(stdClass::class);
9494
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
@@ -104,7 +104,7 @@ public function testPostgresSearchPathIsSet()
104104
public function testPostgresSearchPathArraySupported()
105105
{
106106
$dsn = 'pgsql:host=foo;dbname=bar';
107-
$config = ['host' => 'foo', 'database' => 'bar', 'schema' => ['public', 'user'], 'charset' => 'utf8'];
107+
$config = ['host' => 'foo', 'database' => 'bar', 'search_path' => ['public', '"user"'], 'charset' => 'utf8'];
108108
$connector = $this->getMockBuilder(PostgresConnector::class)->onlyMethods(['createConnection', 'getOptions'])->getMock();
109109
$connection = m::mock(stdClass::class);
110110
$connector->expects($this->once())->method('getOptions')->with($this->equalTo($config))->willReturn(['options']);
@@ -117,6 +117,38 @@ public function testPostgresSearchPathArraySupported()
117117
$this->assertSame($result, $connection);
118118
}
119119

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

0 commit comments

Comments
 (0)