Skip to content

Commit 5998853

Browse files
[11.x] Fix schema names on DatabaseTruncation trait (PostgreSQL and SQLServer) (#53787)
* fix database truncation * fix tests * fix tests * wip * wip * optimize * add tests * fix tests * fix tests
1 parent a53d179 commit 5998853

File tree

4 files changed

+285
-57
lines changed

4 files changed

+285
-57
lines changed

src/Illuminate/Database/Schema/PostgresBuilder.php

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,17 +97,15 @@ public function dropAllTables()
9797
{
9898
$tables = [];
9999

100-
$excludedTables = $this->grammar->escapeNames(
101-
$this->connection->getConfig('dont_drop') ?? ['spatial_ref_sys']
102-
);
100+
$excludedTables = $this->connection->getConfig('dont_drop') ?? ['spatial_ref_sys'];
103101

104-
$schemas = $this->grammar->escapeNames($this->getSchemas());
102+
$schemas = $this->getSchemas();
105103

106104
foreach ($this->getTables() as $table) {
107105
$qualifiedName = $table['schema'].'.'.$table['name'];
108106

109-
if (empty(array_intersect($this->grammar->escapeNames([$table['name'], $qualifiedName]), $excludedTables))
110-
&& in_array($this->grammar->escapeNames([$table['schema']])[0], $schemas)) {
107+
if (in_array($table['schema'], $schemas) &&
108+
empty(array_intersect([$table['name'], $qualifiedName], $excludedTables))) {
111109
$tables[] = $qualifiedName;
112110
}
113111
}
@@ -130,10 +128,10 @@ public function dropAllViews()
130128
{
131129
$views = [];
132130

133-
$schemas = $this->grammar->escapeNames($this->getSchemas());
131+
$schemas = $this->getSchemas();
134132

135133
foreach ($this->getViews() as $view) {
136-
if (in_array($this->grammar->escapeNames([$view['schema']])[0], $schemas)) {
134+
if (in_array($view['schema'], $schemas)) {
137135
$views[] = $view['schema'].'.'.$view['name'];
138136
}
139137
}
@@ -157,10 +155,10 @@ public function dropAllTypes()
157155
$types = [];
158156
$domains = [];
159157

160-
$schemas = $this->grammar->escapeNames($this->getSchemas());
158+
$schemas = $this->getSchemas();
161159

162160
foreach ($this->getTypes() as $type) {
163-
if (! $type['implicit'] && in_array($this->grammar->escapeNames([$type['schema']])[0], $schemas)) {
161+
if (! $type['implicit'] && in_array($type['schema'], $schemas)) {
164162
if ($type['type'] === 'domain') {
165163
$domains[] = $type['schema'].'.'.$type['name'];
166164
} else {
@@ -236,7 +234,7 @@ public function getForeignKeys($table)
236234
*
237235
* @return array
238236
*/
239-
protected function getSchemas()
237+
public function getSchemas()
240238
{
241239
return $this->parseSearchPath(
242240
$this->connection->getConfig('search_path') ?: $this->connection->getConfig('schema') ?: 'public'

src/Illuminate/Foundation/Testing/DatabaseTruncation.php

Lines changed: 62 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
use Illuminate\Contracts\Console\Kernel;
66
use Illuminate\Database\ConnectionInterface;
7+
use Illuminate\Database\Query\Expression;
8+
use Illuminate\Database\Schema\PostgresBuilder;
79
use Illuminate\Foundation\Testing\Traits\CanConfigureMigrationCommands;
810
use Illuminate\Support\Collection;
911

@@ -84,32 +86,58 @@ protected function truncateTablesForConnection(ConnectionInterface $connection,
8486

8587
$connection->unsetEventDispatcher();
8688

87-
(new Collection(static::$allTables[$name] ??= $connection->getSchemaBuilder()->getTableListing()))
89+
(new Collection($this->getAllTablesForConnection($connection, $name)))
8890
->when(
89-
property_exists($this, 'tablesToTruncate'),
90-
fn ($tables) => $tables->intersect($this->tablesToTruncate),
91-
fn ($tables) => $tables->diff($this->exceptTables($name))
91+
$this->tablesToTruncate($connection, $name),
92+
function (Collection $tables, array $tablesToTruncate) {
93+
return $tables->filter(fn (array $table) => $this->tableExistsIn($table, $tablesToTruncate));
94+
},
95+
function (Collection $tables) use ($connection, $name) {
96+
$exceptTables = $this->exceptTables($connection, $name);
97+
98+
return $tables->filter(fn (array $table) => ! $this->tableExistsIn($table, $exceptTables));
99+
}
92100
)
93-
->filter(fn ($table) => $connection->table($this->withoutTablePrefix($connection, $table))->exists())
94-
->each(fn ($table) => $connection->table($this->withoutTablePrefix($connection, $table))->truncate());
101+
->each(function (array $table) use ($connection) {
102+
$table = $connection->table(
103+
new Expression($table['schema'] ? $table['schema'].'.'.$table['name'] : $table['name'])
104+
);
105+
106+
if ($table->exists()) {
107+
$table->truncate();
108+
}
109+
});
95110

96111
$connection->setEventDispatcher($dispatcher);
97112
}
98113

99114
/**
100-
* Remove the table prefix from a table name, if it exists.
101-
*
102-
* @param \Illuminate\Database\ConnectionInterface $connection
103-
* @param string $table
104-
* @return string
115+
* Get all the tables that belong to the connection.
105116
*/
106-
protected function withoutTablePrefix(ConnectionInterface $connection, string $table)
117+
protected function getAllTablesForConnection(ConnectionInterface $connection, ?string $name): array
107118
{
108-
$prefix = $connection->getTablePrefix();
119+
if (isset(static::$allTables[$name])) {
120+
return static::$allTables[$name];
121+
}
122+
123+
$schema = $connection->getSchemaBuilder();
109124

110-
return str_starts_with($table, $prefix)
111-
? substr($table, strlen($prefix))
112-
: $table;
125+
return static::$allTables[$name] = (new Collection($schema->getTables()))->when(
126+
$schema instanceof PostgresBuilder ? $schema->getSchemas() : null,
127+
fn (Collection $tables, array $schemas) => $tables->filter(
128+
fn (array $table) => in_array($table['schema'], $schemas)
129+
)
130+
)->all();
131+
}
132+
133+
/**
134+
* Determine if a table exists in the given list, with or without its schema.
135+
*/
136+
protected function tableExistsIn(array $table, array $tables): bool
137+
{
138+
return $table['schema']
139+
? ! empty(array_intersect([$table['name'], $table['schema'].'.'.$table['name']], $tables))
140+
: in_array($table['name'], $tables);
113141
}
114142

115143
/**
@@ -123,33 +151,32 @@ protected function connectionsToTruncate(): array
123151
? $this->connectionsToTruncate : [null];
124152
}
125153

154+
/**
155+
* Get the tables that should be truncated.
156+
*/
157+
protected function tablesToTruncate(ConnectionInterface $connection, ?string $connectionName): ?array
158+
{
159+
return property_exists($this, 'tablesToTruncate') && is_array($this->tablesToTruncate)
160+
? $this->tablesToTruncate[$connectionName] ?? $this->tablesToTruncate
161+
: null;
162+
}
163+
126164
/**
127165
* Get the tables that should not be truncated.
128-
*
129-
* @param string|null $connectionName
130-
* @return array
131166
*/
132-
protected function exceptTables(?string $connectionName): array
167+
protected function exceptTables(ConnectionInterface $connection, ?string $connectionName): array
133168
{
134169
$migrations = $this->app['config']->get('database.migrations');
135170

136-
$migrationsTable = is_array($migrations) ? ($migrations['table'] ?? null) : $migrations;
137-
138-
if (property_exists($this, 'exceptTables')) {
139-
if (array_is_list($this->exceptTables ?? [])) {
140-
return array_merge(
141-
$this->exceptTables ?? [],
142-
[$migrationsTable],
143-
);
144-
}
171+
$migrationsTable = is_array($migrations) ? ($migrations['table'] ?? 'migrations') : $migrations;
172+
$migrationsTable = $connection->getTablePrefix().$migrationsTable;
145173

146-
return array_merge(
147-
$this->exceptTables[$connectionName] ?? [],
174+
return property_exists($this, 'exceptTables') && is_array($this->exceptTables)
175+
? array_merge(
176+
$this->exceptTables[$connectionName] ?? $this->exceptTables,
148177
[$migrationsTable],
149-
);
150-
}
151-
152-
return [$migrationsTable];
178+
)
179+
: [$migrationsTable];
153180
}
154181

155182
/**

tests/Database/DatabasePostgresBuilderTest.php

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -230,9 +230,6 @@ public function testDropAllTablesWhenSearchPathIsString()
230230
$grammar->shouldReceive('compileTables')->andReturn('sql');
231231
$processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'public']]);
232232
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'public']]);
233-
$grammar->shouldReceive('escapeNames')->with(['public'])->andReturn(['"public"']);
234-
$grammar->shouldReceive('escapeNames')->with(['foo'])->andReturn(['"foo"']);
235-
$grammar->shouldReceive('escapeNames')->with(['users', 'public.users'])->andReturn(['"users"', '"public"."users"']);
236233
$grammar->shouldReceive('compileDropAllTables')->with(['public.users'])->andReturn('drop table "public"."users" cascade');
237234
$connection->shouldReceive('statement')->with('drop table "public"."users" cascade');
238235
$builder = $this->getBuilder($connection);
@@ -253,10 +250,6 @@ public function testDropAllTablesWhenSearchPathIsStringOfMany()
253250
$processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'foouser']]);
254251
$grammar->shouldReceive('compileTables')->andReturn('sql');
255252
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'foouser']]);
256-
$grammar->shouldReceive('escapeNames')->with(['foouser', 'public', 'foo_bar-Baz.Áüõß'])->andReturn(['"foouser"', '"public"', '"foo_bar-Baz"."Áüõß"']);
257-
$grammar->shouldReceive('escapeNames')->with(['foo'])->andReturn(['"foo"']);
258-
$grammar->shouldReceive('escapeNames')->with(['foouser'])->andReturn(['"foouser"']);
259-
$grammar->shouldReceive('escapeNames')->with(['users', 'foouser.users'])->andReturn(['"users"', '"foouser"."users"']);
260253
$grammar->shouldReceive('compileDropAllTables')->with(['foouser.users'])->andReturn('drop table "foouser"."users" cascade');
261254
$connection->shouldReceive('statement')->with('drop table "foouser"."users" cascade');
262255
$builder = $this->getBuilder($connection);
@@ -282,10 +275,6 @@ public function testDropAllTablesWhenSearchPathIsArrayOfMany()
282275
$processor->shouldReceive('processTables')->once()->andReturn([['name' => 'users', 'schema' => 'foouser']]);
283276
$grammar->shouldReceive('compileTables')->andReturn('sql');
284277
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['name' => 'users', 'schema' => 'foouser']]);
285-
$grammar->shouldReceive('escapeNames')->with(['foouser', 'dev', 'test', 'spaced schema'])->andReturn(['"foouser"', '"dev"', '"test"', '"spaced schema"']);
286-
$grammar->shouldReceive('escapeNames')->with(['foo'])->andReturn(['"foo"']);
287-
$grammar->shouldReceive('escapeNames')->with(['users', 'foouser.users'])->andReturn(['"users"', '"foouser"."users"']);
288-
$grammar->shouldReceive('escapeNames')->with(['foouser'])->andReturn(['"foouser"']);
289278
$grammar->shouldReceive('compileDropAllTables')->with(['foouser.users'])->andReturn('drop table "foouser"."users" cascade');
290279
$connection->shouldReceive('statement')->with('drop table "foouser"."users" cascade');
291280
$builder = $this->getBuilder($connection);

0 commit comments

Comments
 (0)