Skip to content

Commit 889afc0

Browse files
[11.x] Fix inconsistent database parsing on PostgreSQL (#49148)
* fix inconsistent database parsing on PostgreSQL * fix on getIndexes * fix on getForeignKeys
1 parent 562557f commit 889afc0

File tree

5 files changed

+77
-71
lines changed

5 files changed

+77
-71
lines changed

src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public function compileDropDatabaseIfExists($name)
7171
/**
7272
* Compile the query to determine if a table exists.
7373
*
74+
* @deprecated Will be removed in a future Laravel version.
75+
*
7476
* @return string
7577
*/
7678
public function compileTableExists()
@@ -160,12 +162,11 @@ public function compileColumnListing()
160162
/**
161163
* Compile the query to determine the columns.
162164
*
163-
* @param string $database
164165
* @param string $schema
165166
* @param string $table
166167
* @return string
167168
*/
168-
public function compileColumns($database, $schema, $table)
169+
public function compileColumns($schema, $table)
169170
{
170171
return sprintf(
171172
'select quote_ident(a.attname) as name, t.typname as type_name, format_type(a.atttypid, a.atttypmod) as type, '

src/Illuminate/Database/Schema/PostgresBuilder.php

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace Illuminate\Database\Schema;
44

55
use Illuminate\Database\Concerns\ParsesSearchPath;
6+
use InvalidArgumentException;
67

78
class PostgresBuilder extends Builder
89
{
@@ -44,13 +45,18 @@ public function dropDatabaseIfExists($name)
4445
*/
4546
public function hasTable($table)
4647
{
47-
[$database, $schema, $table] = $this->parseSchemaAndTable($table);
48+
[$schema, $table] = $this->parseSchemaAndTable($table);
4849

4950
$table = $this->connection->getTablePrefix().$table;
5051

51-
return count($this->connection->selectFromWriteConnection(
52-
$this->grammar->compileTableExists(), [$database, $schema, $table]
53-
)) > 0;
52+
foreach ($this->getTables() as $value) {
53+
if (strtolower($table) === strtolower($value['name'])
54+
&& strtolower($schema) === strtolower($value['schema'])) {
55+
return true;
56+
}
57+
}
58+
59+
return false;
5460
}
5561

5662
/**
@@ -213,12 +219,12 @@ public function dropAllTypes()
213219
*/
214220
public function getColumns($table)
215221
{
216-
[$database, $schema, $table] = $this->parseSchemaAndTable($table);
222+
[$schema, $table] = $this->parseSchemaAndTable($table);
217223

218224
$table = $this->connection->getTablePrefix().$table;
219225

220226
$results = $this->connection->selectFromWriteConnection(
221-
$this->grammar->compileColumns($database, $schema, $table)
227+
$this->grammar->compileColumns($schema, $table)
222228
);
223229

224230
return $this->connection->getPostProcessor()->processColumns($results);
@@ -232,7 +238,7 @@ public function getColumns($table)
232238
*/
233239
public function getIndexes($table)
234240
{
235-
[, $schema, $table] = $this->parseSchemaAndTable($table);
241+
[$schema, $table] = $this->parseSchemaAndTable($table);
236242

237243
$table = $this->connection->getTablePrefix().$table;
238244

@@ -249,7 +255,7 @@ public function getIndexes($table)
249255
*/
250256
public function getForeignKeys($table)
251257
{
252-
[, $schema, $table] = $this->parseSchemaAndTable($table);
258+
[$schema, $table] = $this->parseSchemaAndTable($table);
253259

254260
$table = $this->connection->getTablePrefix().$table;
255261

@@ -271,7 +277,7 @@ protected function getSchemas()
271277
}
272278

273279
/**
274-
* Parse the database object reference and extract the database, schema, and table.
280+
* Parse the database object reference and extract the schema and table.
275281
*
276282
* @param string $reference
277283
* @return array
@@ -280,14 +286,10 @@ protected function parseSchemaAndTable($reference)
280286
{
281287
$parts = explode('.', $reference);
282288

283-
$database = $this->connection->getConfig('database');
284-
285-
// If the reference contains a database name, we will use that instead of the
286-
// default database name for the connection. This allows the database name
287-
// to be specified in the query instead of at the full connection level.
288-
if (count($parts) === 3) {
289+
if (count($parts) > 2) {
289290
$database = $parts[0];
290-
array_shift($parts);
291+
292+
throw new InvalidArgumentException("Using 3-parts reference is not supported, you may use `Schema::connection('$database')` instead.");
291293
}
292294

293295
// We will use the default schema unless the schema has been specified in the
@@ -300,7 +302,7 @@ protected function parseSchemaAndTable($reference)
300302
array_shift($parts);
301303
}
302304

303-
return [$database, $schema, $parts[0]];
305+
return [$schema, $parts[0]];
304306
}
305307

306308
/**

tests/Database/DatabasePostgresBuilderTest.php

Lines changed: 46 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -52,29 +52,35 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathMissing()
5252
$connection->shouldReceive('getConfig')->with('search_path')->andReturn(null);
5353
$connection->shouldReceive('getConfig')->with('schema')->andReturn(null);
5454
$grammar = m::mock(PostgresGrammar::class);
55+
$processor = m::mock(PostgresProcessor::class);
5556
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
56-
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
57-
$connection->shouldReceive('selectFromWriteConnection')->with("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'", ['laravel', 'public', 'foo'])->andReturn(['countable_result']);
57+
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
58+
$grammar->shouldReceive('compileTables')->andReturn('sql');
59+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'public', 'name' => 'foo']]);
5860
$connection->shouldReceive('getTablePrefix');
59-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
6061
$builder = $this->getBuilder($connection);
62+
$processor->shouldReceive('processTables')->andReturn([['schema' => 'public', 'name' => 'foo']]);
6163

62-
$builder->hasTable('foo');
64+
$this->assertTrue($builder->hasTable('foo'));
65+
$this->assertTrue($builder->hasTable('public.foo'));
6366
}
6467

6568
public function testHasTableWhenSchemaUnqualifiedAndSearchPathFilled()
6669
{
6770
$connection = $this->getConnection();
6871
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public');
6972
$grammar = m::mock(PostgresGrammar::class);
73+
$processor = m::mock(PostgresProcessor::class);
7074
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
71-
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
72-
$connection->shouldReceive('selectFromWriteConnection')->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']);
75+
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
76+
$grammar->shouldReceive('compileTables')->andReturn('sql');
77+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
7378
$connection->shouldReceive('getTablePrefix');
74-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
7579
$builder = $this->getBuilder($connection);
80+
$processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
7681

77-
$builder->hasTable('foo');
82+
$this->assertTrue($builder->hasTable('foo'));
83+
$this->assertTrue($builder->hasTable('myapp.foo'));
7884
}
7985

8086
public function testHasTableWhenSchemaUnqualifiedAndSearchPathFallbackFilled()
@@ -83,14 +89,17 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathFallbackFilled()
8389
$connection->shouldReceive('getConfig')->with('search_path')->andReturn(null);
8490
$connection->shouldReceive('getConfig')->with('schema')->andReturn(['myapp', 'public']);
8591
$grammar = m::mock(PostgresGrammar::class);
92+
$processor = m::mock(PostgresProcessor::class);
8693
$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('selectFromWriteConnection')->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']);
94+
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
95+
$grammar->shouldReceive('compileTables')->andReturn('sql');
96+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
8997
$connection->shouldReceive('getTablePrefix');
90-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
9198
$builder = $this->getBuilder($connection);
99+
$processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
92100

93-
$builder->hasTable('foo');
101+
$this->assertTrue($builder->hasTable('foo'));
102+
$this->assertTrue($builder->hasTable('myapp.foo'));
94103
}
95104

96105
public function testHasTableWhenSchemaUnqualifiedAndSearchPathIsUserVariable()
@@ -99,41 +108,43 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathIsUserVariable()
99108
$connection->shouldReceive('getConfig')->with('username')->andReturn('foouser');
100109
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user');
101110
$grammar = m::mock(PostgresGrammar::class);
111+
$processor = m::mock(PostgresProcessor::class);
102112
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
103-
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
104-
$connection->shouldReceive('selectFromWriteConnection')->with("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'", ['laravel', 'foouser', 'foo'])->andReturn(['countable_result']);
113+
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
114+
$grammar->shouldReceive('compileTables')->andReturn('sql');
115+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'foouser', 'name' => 'foo']]);
105116
$connection->shouldReceive('getTablePrefix');
106-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
107117
$builder = $this->getBuilder($connection);
118+
$processor->shouldReceive('processTables')->andReturn([['schema' => 'foouser', 'name' => 'foo']]);
108119

109-
$builder->hasTable('foo');
120+
$this->assertTrue($builder->hasTable('foo'));
121+
$this->assertTrue($builder->hasTable('foouser.foo'));
110122
}
111123

112124
public function testHasTableWhenSchemaQualifiedAndSearchPathMismatches()
113125
{
114126
$connection = $this->getConnection();
115127
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
116128
$grammar = m::mock(PostgresGrammar::class);
129+
$processor = m::mock(PostgresProcessor::class);
117130
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
118-
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
119-
$connection->shouldReceive('selectFromWriteConnection')->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']);
131+
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
132+
$grammar->shouldReceive('compileTables')->andReturn('sql');
133+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
120134
$connection->shouldReceive('getTablePrefix');
121-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
122135
$builder = $this->getBuilder($connection);
136+
$processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
123137

124-
$builder->hasTable('myapp.foo');
138+
$this->assertTrue($builder->hasTable('myapp.foo'));
125139
}
126140

127141
public function testHasTableWhenDatabaseAndSchemaQualifiedAndSearchPathMismatches()
128142
{
143+
$this->expectException(\InvalidArgumentException::class);
144+
129145
$connection = $this->getConnection();
130-
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
131146
$grammar = m::mock(PostgresGrammar::class);
132147
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
133-
$grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'");
134-
$connection->shouldReceive('selectFromWriteConnection')->with("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'", ['mydatabase', 'myapp', 'foo'])->andReturn(['countable_result']);
135-
$connection->shouldReceive('getTablePrefix');
136-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
137148
$builder = $this->getBuilder($connection);
138149

139150
$builder->hasTable('mydatabase.myapp.foo');
@@ -146,10 +157,9 @@ public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathMissing()
146157
$connection->shouldReceive('getConfig')->with('schema')->andReturn(null);
147158
$grammar = m::mock(PostgresGrammar::class);
148159
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
149-
$grammar->shouldReceive('compileColumns')->with('laravel', 'public', 'foo')->andReturn('sql');
150-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']);
160+
$grammar->shouldReceive('compileColumns')->with('public', 'foo')->andReturn('sql');
161+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]);
151162
$connection->shouldReceive('getTablePrefix');
152-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
153163
$processor = m::mock(PostgresProcessor::class);
154164
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
155165
$processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]);
@@ -164,10 +174,9 @@ public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathFilled()
164174
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public');
165175
$grammar = m::mock(PostgresGrammar::class);
166176
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
167-
$grammar->shouldReceive('compileColumns')->with('laravel', 'myapp', 'foo')->andReturn('sql');
168-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']);
177+
$grammar->shouldReceive('compileColumns')->with('myapp', 'foo')->andReturn('sql');
178+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]);
169179
$connection->shouldReceive('getTablePrefix');
170-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
171180
$processor = m::mock(PostgresProcessor::class);
172181
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
173182
$processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]);
@@ -183,10 +192,9 @@ public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathIsUserVari
183192
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user');
184193
$grammar = m::mock(PostgresGrammar::class);
185194
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
186-
$grammar->shouldReceive('compileColumns')->with('laravel', 'foouser', 'foo')->andReturn('sql');
187-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']);
195+
$grammar->shouldReceive('compileColumns')->with('foouser', 'foo')->andReturn('sql');
196+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]);
188197
$connection->shouldReceive('getTablePrefix');
189-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
190198
$processor = m::mock(PostgresProcessor::class);
191199
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
192200
$processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]);
@@ -201,10 +209,9 @@ public function testGetColumnListingWhenSchemaQualifiedAndSearchPathMismatches()
201209
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
202210
$grammar = m::mock(PostgresGrammar::class);
203211
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
204-
$grammar->shouldReceive('compileColumns')->with('laravel', 'myapp', 'foo')->andReturn('sql');
205-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']);
212+
$grammar->shouldReceive('compileColumns')->with('myapp', 'foo')->andReturn('sql');
213+
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'some_column']]);
206214
$connection->shouldReceive('getTablePrefix');
207-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
208215
$processor = m::mock(PostgresProcessor::class);
209216
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
210217
$processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]);
@@ -215,17 +222,12 @@ public function testGetColumnListingWhenSchemaQualifiedAndSearchPathMismatches()
215222

216223
public function testGetColumnWhenDatabaseAndSchemaQualifiedAndSearchPathMismatches()
217224
{
225+
$this->expectException(\InvalidArgumentException::class);
226+
218227
$connection = $this->getConnection();
219228
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
220229
$grammar = m::mock(PostgresGrammar::class);
221230
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
222-
$grammar->shouldReceive('compileColumns')->with('mydatabase', 'myapp', 'foo')->andReturn('sql');
223-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']);
224-
$connection->shouldReceive('getTablePrefix');
225-
$connection->shouldReceive('getConfig')->with('database')->andReturn('laravel');
226-
$processor = m::mock(PostgresProcessor::class);
227-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
228-
$processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]);
229231
$builder = $this->getBuilder($connection);
230232

231233
$builder->getColumnListing('mydatabase.myapp.foo');

0 commit comments

Comments
 (0)