Skip to content

Commit fa8a17b

Browse files
committedFeb 24, 2022
PostgresBuilder fixes for renamed config('database.connections.pgsql.search_path')
1. `PostgresBuilder::parseSchemaAndTable()` * Needs a fallback to <= 8.x `config('database.connections.pgsql.schema')` when 9.x renamed `config('database.connections.pgsql.search_path')` is missing. * Remove duplicate `$user` variable `config('database.connections.pgsql.username')` replacement handling already done by `parseSearchPath()`. 2. `PostgresBuilder::getAllTables()` + `getAllViews()` + `parseSchemaAndTable()` Apply the `parseSearchPath()` fixes applied to PostgresConnector: laravel#41088 3. `DatabasePostgresBuilderTest` Add more test cases and use terse method names instead of extensive comments to concisely communicate each case.
1 parent 6c0d272 commit fa8a17b

File tree

4 files changed

+98
-123
lines changed

4 files changed

+98
-123
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
namespace Illuminate\Database\Concerns;
4+
5+
trait ParsesSearchPath
6+
{
7+
/**
8+
* Parse the Postgres "search_path" configuration value into an array.
9+
*
10+
* @param string|array|null $searchPath
11+
* @return array
12+
*/
13+
protected function parseSearchPath($searchPath)
14+
{
15+
if (is_string($searchPath)) {
16+
preg_match_all('/[^\s,"\']+/', $searchPath, $matches);
17+
18+
$searchPath = $matches[0];
19+
}
20+
21+
$searchPath ??= [];
22+
23+
array_walk($searchPath, static function (&$schema) {
24+
$schema = trim($schema, '\'"');
25+
});
26+
27+
return $searchPath;
28+
}
29+
}

‎src/Illuminate/Database/Connectors/PostgresConnector.php

+3-23
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
namespace Illuminate\Database\Connectors;
44

5+
use Illuminate\Database\Concerns\ParsesSearchPath;
56
use PDO;
67

78
class PostgresConnector extends Connector implements ConnectorInterface
89
{
10+
use ParsesSearchPath;
11+
912
/**
1013
* The default PDO connection options.
1114
*
@@ -118,29 +121,6 @@ protected function configureSearchPath($connection, $config)
118121
}
119122
}
120123

121-
/**
122-
* Parse the "search_path" configuration value into an array.
123-
*
124-
* @param string|array $searchPath
125-
* @return array
126-
*/
127-
protected function parseSearchPath($searchPath)
128-
{
129-
if (is_string($searchPath)) {
130-
preg_match_all('/[^\s,"\']+/', $searchPath, $matches);
131-
132-
$searchPath = $matches[0];
133-
}
134-
135-
$searchPath ??= [];
136-
137-
array_walk($searchPath, function (&$schema) {
138-
$schema = trim($schema, '\'"');
139-
});
140-
141-
return $searchPath;
142-
}
143-
144124
/**
145125
* Format the search path for the DSN.
146126
*

‎src/Illuminate/Database/Schema/PostgresBuilder.php

+11-15
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,14 @@
22

33
namespace Illuminate\Database\Schema;
44

5+
use Illuminate\Database\Concerns\ParsesSearchPath;
6+
57
class PostgresBuilder extends Builder
68
{
9+
use ParsesSearchPath {
10+
parseSearchPath as baseParseSearchPath;
11+
}
12+
713
/**
814
* Create a database in the schema.
915
*
@@ -197,7 +203,7 @@ public function getColumnListing($table)
197203
protected function parseSchemaAndTable($reference)
198204
{
199205
$searchPath = $this->parseSearchPath(
200-
$this->connection->getConfig('search_path') ?: 'public'
206+
$this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') ?: 'public'
201207
);
202208

203209
$parts = explode('.', $reference);
@@ -215,9 +221,7 @@ protected function parseSchemaAndTable($reference)
215221
// We will use the default schema unless the schema has been specified in the
216222
// query. If the schema has been specified in the query then we can use it
217223
// instead of a default schema configured in the connection search path.
218-
$schema = $searchPath[0] === '$user'
219-
? $this->connection->getConfig('username')
220-
: $searchPath[0];
224+
$schema = $searchPath[0];
221225

222226
if (count($parts) === 2) {
223227
$schema = $parts[0];
@@ -228,24 +232,16 @@ protected function parseSchemaAndTable($reference)
228232
}
229233

230234
/**
231-
* Parse the "search_path" value into an array.
235+
* Parse the "search_path" configuration value into an array.
232236
*
233-
* @param string|array $searchPath
237+
* @param string|array|null $searchPath
234238
* @return array
235239
*/
236240
protected function parseSearchPath($searchPath)
237241
{
238-
if (is_string($searchPath)) {
239-
preg_match_all('/[a-zA-z0-9$]{1,}/i', $searchPath, $matches);
240-
241-
$searchPath = $matches[0];
242-
}
243-
244-
$searchPath ??= [];
242+
$searchPath = $this->baseParseSearchPath($searchPath);
245243

246244
array_walk($searchPath, function (&$schema) {
247-
$schema = trim($schema, '\'"');
248-
249245
$schema = $schema === '$user'
250246
? $this->connection->getConfig('username')
251247
: $schema;

‎tests/Database/DatabasePostgresBuilderTest.php

+55-85
Original file line numberDiff line numberDiff line change
@@ -46,16 +46,11 @@ public function testDropDatabaseIfExists()
4646
$builder->dropDatabaseIfExists('my_database_a');
4747
}
4848

49-
/**
50-
* Ensure that when the reference is unqualified (i.e., does not contain a
51-
* database name or a schema), and the search_path is empty, the database
52-
* specified on the connection is used, and the default schema ('public')
53-
* is used.
54-
*/
55-
public function testWhenSearchPathEmptyHasTableWithUnqualifiedReferenceIsCorrect()
49+
public function testHasTableWhenSchemaUnqualifiedAndSearchPathMissing()
5650
{
5751
$connection = $this->getConnection();
5852
$connection->shouldReceive('getConfig')->with('search_path')->andReturn(null);
53+
$connection->shouldReceive('getConfig')->with('schema')->andReturn(null);
5954
$grammar = m::mock(PostgresGrammar::class);
6055
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
6156
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
@@ -67,13 +62,7 @@ public function testWhenSearchPathEmptyHasTableWithUnqualifiedReferenceIsCorrect
6762
$builder->hasTable('foo');
6863
}
6964

70-
/**
71-
* Ensure that when the reference is unqualified (i.e., does not contain a
72-
* database name or a schema), and the first schema in the search_path is
73-
* NOT the default ('public'), the database specified on the connection is
74-
* used, and the first schema in the search_path is used.
75-
*/
76-
public function testWhenSearchPathNotEmptyHasTableWithUnqualifiedSchemaReferenceIsCorrect()
65+
public function testHasTableWhenSchemaUnqualifiedAndSearchPathFilled()
7766
{
7867
$connection = $this->getConnection();
7968
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public');
@@ -88,14 +77,23 @@ public function testWhenSearchPathNotEmptyHasTableWithUnqualifiedSchemaReference
8877
$builder->hasTable('foo');
8978
}
9079

91-
/**
92-
* Ensure that when the reference is unqualified (i.e., does not contain a
93-
* database name or a schema), and the first schema in the search_path is
94-
* the special variable '$user', the database specified on the connection is
95-
* used, the first schema in the search_path is used, and the variable
96-
* resolves to the username specified on the connection.
97-
*/
98-
public function testWhenFirstSchemaInSearchPathIsVariableHasTableWithUnqualifiedSchemaReferenceIsCorrect()
80+
public function testHasTableWhenSchemaUnqualifiedAndSearchPathFallbackFilled()
81+
{
82+
$connection = $this->getConnection();
83+
$connection->shouldReceive('getConfig')->with('search_path')->andReturn(null);
84+
$connection->shouldReceive('getConfig')->with('schema')->andReturn(['myapp', 'public']);
85+
$grammar = m::mock(PostgresGrammar::class);
86+
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
87+
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
88+
$connection->shouldReceive('select')->with("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'", ['laravel', 'myapp', 'foo'])->andReturn(['countable_result']);
89+
$connection->shouldReceive('getTablePrefix');
90+
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
91+
$builder = $this->getBuilder($connection);
92+
93+
$builder->hasTable('foo');
94+
}
95+
96+
public function testHasTableWhenSchemaUnqualifiedAndSearchPathIsUserVariable()
9997
{
10098
$connection = $this->getConnection();
10199
$connection->shouldReceive('getConfig')->with('username')->andReturn('foouser');
@@ -111,12 +109,7 @@ public function testWhenFirstSchemaInSearchPathIsVariableHasTableWithUnqualified
111109
$builder->hasTable('foo');
112110
}
113111

114-
/**
115-
* Ensure that when the reference is qualified only with a schema, that
116-
* the database specified on the connection is used, and the specified
117-
* schema is used, even if it is not within the search_path.
118-
*/
119-
public function testWhenSchemaNotInSearchPathHasTableWithQualifiedSchemaReferenceIsCorrect()
112+
public function testHasTableWhenSchemaQualifiedAndSearchPathMismatches()
120113
{
121114
$connection = $this->getConnection();
122115
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
@@ -131,12 +124,7 @@ public function testWhenSchemaNotInSearchPathHasTableWithQualifiedSchemaReferenc
131124
$builder->hasTable('myapp.foo');
132125
}
133126

134-
/**
135-
* Ensure that when the reference is qualified with a database AND a schema,
136-
* and the database is NOT the database configured for the connection, the
137-
* specified database is used instead.
138-
*/
139-
public function testWhenDatabaseNotDefaultHasTableWithFullyQualifiedReferenceIsCorrect()
127+
public function testHasTableWhenDatabaseAndSchemaQualifiedAndSearchPathMismatches()
140128
{
141129
$connection = $this->getConnection();
142130
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
@@ -151,16 +139,11 @@ public function testWhenDatabaseNotDefaultHasTableWithFullyQualifiedReferenceIsC
151139
$builder->hasTable('mydatabase.myapp.foo');
152140
}
153141

154-
/**
155-
* Ensure that when the reference is unqualified (i.e., does not contain a
156-
* database name or a schema), and the search_path is empty, the database
157-
* specified on the connection is used, and the default schema ('public')
158-
* is used.
159-
*/
160-
public function testWhenSearchPathEmptyGetColumnListingWithUnqualifiedReferenceIsCorrect()
142+
public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathMissing()
161143
{
162144
$connection = $this->getConnection();
163145
$connection->shouldReceive('getConfig')->with('search_path')->andReturn(null);
146+
$connection->shouldReceive('getConfig')->with('schema')->andReturn(null);
164147
$grammar = m::mock(PostgresGrammar::class);
165148
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
166149
$grammar->shouldReceive('compileColumnListing')->andReturn('select column_name from information_schema.columns where table_catalog = ? and table_schema = ? and table_name = ?');
@@ -175,13 +158,7 @@ public function testWhenSearchPathEmptyGetColumnListingWithUnqualifiedReferenceI
175158
$builder->getColumnListing('foo');
176159
}
177160

178-
/**
179-
* Ensure that when the reference is unqualified (i.e., does not contain a
180-
* database name or a schema), and the first schema in the search_path is
181-
* NOT the default ('public'), the database specified on the connection is
182-
* used, and the first schema in the search_path is used.
183-
*/
184-
public function testWhenSearchPathNotEmptyGetColumnListingWithUnqualifiedSchemaReferenceIsCorrect()
161+
public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathFilled()
185162
{
186163
$connection = $this->getConnection();
187164
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public');
@@ -199,14 +176,7 @@ public function testWhenSearchPathNotEmptyGetColumnListingWithUnqualifiedSchemaR
199176
$builder->getColumnListing('foo');
200177
}
201178

202-
/**
203-
* Ensure that when the reference is unqualified (i.e., does not contain a
204-
* database name or a schema), and the first schema in the search_path is
205-
* the special variable '$user', the database specified on the connection is
206-
* used, the first schema in the search_path is used, and the variable
207-
* resolves to the username specified on the connection.
208-
*/
209-
public function testWhenFirstSchemaInSearchPathIsVariableGetColumnListingWithUnqualifiedSchemaReferenceIsCorrect()
179+
public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathIsUserVariable()
210180
{
211181
$connection = $this->getConnection();
212182
$connection->shouldReceive('getConfig')->with('username')->andReturn('foouser');
@@ -225,12 +195,7 @@ public function testWhenFirstSchemaInSearchPathIsVariableGetColumnListingWithUnq
225195
$builder->getColumnListing('foo');
226196
}
227197

228-
/**
229-
* Ensure that when the reference is qualified only with a schema, that
230-
* the database specified on the connection is used, and the specified
231-
* schema is used, even if it is not within the search_path.
232-
*/
233-
public function testWhenSchemaNotInSearchPathGetColumnListingWithQualifiedSchemaReferenceIsCorrect()
198+
public function testGetColumnListingWhenSchemaQualifiedAndSearchPathMismatches()
234199
{
235200
$connection = $this->getConnection();
236201
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
@@ -248,12 +213,7 @@ public function testWhenSchemaNotInSearchPathGetColumnListingWithQualifiedSchema
248213
$builder->getColumnListing('myapp.foo');
249214
}
250215

251-
/**
252-
* Ensure that when the reference is qualified with a database AND a schema,
253-
* and the database is NOT the database configured for the connection, the
254-
* specified database is used instead.
255-
*/
256-
public function testWhenDatabaseNotDefaultGetColumnListingWithFullyQualifiedReferenceIsCorrect()
216+
public function testGetColumnWhenDatabaseAndSchemaQualifiedAndSearchPathMismatches()
257217
{
258218
$connection = $this->getConnection();
259219
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
@@ -271,12 +231,7 @@ public function testWhenDatabaseNotDefaultGetColumnListingWithFullyQualifiedRefe
271231
$builder->getColumnListing('mydatabase.myapp.foo');
272232
}
273233

274-
/**
275-
* Ensure that when the search_path contains just one schema, only that
276-
* schema is passed into the query that is executed to acquire the list
277-
* of tables to be dropped.
278-
*/
279-
public function testDropAllTablesWithOneSchemaInSearchPath()
234+
public function testDropAllTablesWhenSearchPathIsString()
280235
{
281236
$connection = $this->getConnection();
282237
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
@@ -292,23 +247,38 @@ public function testDropAllTablesWithOneSchemaInSearchPath()
292247
$builder->dropAllTables();
293248
}
294249

295-
/**
296-
* Ensure that when the search_path contains more than one schema, both
297-
* schemas are passed into the query that is executed to acquire the list
298-
* of tables to be dropped. Furthermore, ensure that the special '$user'
299-
* variable is resolved to the username specified on the database connection
300-
* in the process.
301-
*/
302-
public function testDropAllTablesWithMoreThanOneSchemaInSearchPath()
250+
public function testDropAllTablesWhenSearchPathIsStringOfMany()
251+
{
252+
$connection = $this->getConnection();
253+
$connection->shouldReceive('getConfig')->with('username')->andReturn('foouser');
254+
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('"$user", public, foo_bar-Baz.Áüõß');
255+
$connection->shouldReceive('getConfig')->with('dont_drop')->andReturn(['foo']);
256+
$grammar = m::mock(PostgresGrammar::class);
257+
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
258+
$grammar->shouldReceive('compileGetAllTables')->with(['foouser', 'public', 'foo_bar-Baz.Áüõß'])->andReturn("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','public','foo_bar-Baz.Áüõß')");
259+
$connection->shouldReceive('select')->with("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','public','foo_bar-Baz.Áüõß')")->andReturn(['users', 'users']);
260+
$grammar->shouldReceive('compileDropAllTables')->with(['users', 'users'])->andReturn('drop table "'.implode('","', ['users', 'users']).'" cascade');
261+
$connection->shouldReceive('statement')->with('drop table "'.implode('","', ['users', 'users']).'" cascade');
262+
$builder = $this->getBuilder($connection);
263+
264+
$builder->dropAllTables();
265+
}
266+
267+
public function testDropAllTablesWhenSearchPathIsArrayOfMany()
303268
{
304269
$connection = $this->getConnection();
305270
$connection->shouldReceive('getConfig')->with('username')->andReturn('foouser');
306-
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('"$user", public');
271+
$connection->shouldReceive('getConfig')->with('search_path')->andReturn([
272+
'$user',
273+
'"dev"',
274+
"'test'",
275+
'spaced schema',
276+
]);
307277
$connection->shouldReceive('getConfig')->with('dont_drop')->andReturn(['foo']);
308278
$grammar = m::mock(PostgresGrammar::class);
309279
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
310-
$grammar->shouldReceive('compileGetAllTables')->with(['foouser', 'public'])->andReturn("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','public')");
311-
$connection->shouldReceive('select')->with("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','public')")->andReturn(['users', 'users']);
280+
$grammar->shouldReceive('compileGetAllTables')->with(['foouser', 'dev', 'test', 'spaced schema'])->andReturn("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','dev','test','spaced schema')");
281+
$connection->shouldReceive('select')->with("select tablename from pg_catalog.pg_tables where schemaname in ('foouser','dev','test','spaced schema')")->andReturn(['users', 'users']);
312282
$grammar->shouldReceive('compileDropAllTables')->with(['users', 'users'])->andReturn('drop table "'.implode('","', ['users', 'users']).'" cascade');
313283
$connection->shouldReceive('statement')->with('drop table "'.implode('","', ['users', 'users']).'" cascade');
314284
$builder = $this->getBuilder($connection);

0 commit comments

Comments
 (0)
Please sign in to comment.