Skip to content

Commit d9ecee5

Browse files
[11.x] Improve Schema::hasTable() performance (#53006)
* improve has table performance * fix tests * fix tests * fix tests
1 parent 2880c30 commit d9ecee5

13 files changed

+118
-59
lines changed

src/Illuminate/Database/Schema/Builder.php

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -144,8 +144,7 @@ public function hasTable($table)
144144
{
145145
$table = $this->connection->getTablePrefix().$table;
146146

147-
/** @phpstan-ignore arguments.count (SQLite accepts a withSize argument) */
148-
foreach ($this->getTables(false) as $value) {
147+
foreach ($this->getTables() as $value) {
149148
if (strtolower($table) === strtolower($value['name'])) {
150149
return true;
151150
}

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,23 @@ public function compileDropDatabaseIfExists($name)
7676
);
7777
}
7878

79+
/**
80+
* Compile the query to determine if the given table exists.
81+
*
82+
* @param string $database
83+
* @param string $table
84+
* @return string
85+
*/
86+
public function compileTableExists($database, $table)
87+
{
88+
return sprintf(
89+
'select exists (select 1 from information_schema.tables where '
90+
."table_schema = %s and table_name = %s and table_type in ('BASE TABLE', 'SYSTEM VERSIONED')) as `exists`",
91+
$this->quoteString($database),
92+
$this->quoteString($table)
93+
);
94+
}
95+
7996
/**
8097
* Compile the query to determine the tables.
8198
*

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,23 @@ public function compileDropDatabaseIfExists($name)
6868
);
6969
}
7070

71+
/**
72+
* Compile the query to determine if the given table exists.
73+
*
74+
* @param string $schema
75+
* @param string $table
76+
* @return string
77+
*/
78+
public function compileTableExists($schema, $table)
79+
{
80+
return sprintf(
81+
'select exists (select 1 from pg_class c, pg_namespace n where '
82+
."n.nspname = %s and c.relname = %s and c.relkind in ('r', 'p') and n.oid = c.relnamespace)",
83+
$this->quoteString($schema),
84+
$this->quoteString($table)
85+
);
86+
}
87+
7188
/**
7289
* Compile the query to determine the tables.
7390
*

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

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,20 @@ public function compileDbstatExists()
6868
return "select exists (select 1 from pragma_compile_options where compile_options = 'ENABLE_DBSTAT_VTAB') as enabled";
6969
}
7070

71+
/**
72+
* Compile the query to determine if the given table exists.
73+
*
74+
* @param string $table
75+
* @return string
76+
*/
77+
public function compileTableExists($table)
78+
{
79+
return sprintf(
80+
'select exists (select 1 from sqlite_master where name = %s and type = \'table\') as "exists"',
81+
$this->quoteString(str_replace('.', '__', $table))
82+
);
83+
}
84+
7185
/**
7286
* Compile the query to determine the tables.
7387
*

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

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,21 @@ public function compileDropDatabaseIfExists($name)
7676
);
7777
}
7878

79+
/**
80+
* Compile the query to determine if the given table exists.
81+
*
82+
* @param string|null $schema
83+
* @param string $table
84+
* @return string
85+
*/
86+
public function compileTableExists($schema, $table)
87+
{
88+
return sprintf(
89+
'select (case when object_id(%s, \'U\') is null then 0 else 1 end) as [exists]',
90+
$this->quoteString($schema ? $schema.'.'.$table : $table)
91+
);
92+
}
93+
7994
/**
8095
* Compile the query to determine the tables.
8196
*

src/Illuminate/Database/Schema/MySqlBuilder.php

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,23 @@ public function dropDatabaseIfExists($name)
3030
);
3131
}
3232

33+
/**
34+
* Determine if the given table exists.
35+
*
36+
* @param string $table
37+
* @return bool
38+
*/
39+
public function hasTable($table)
40+
{
41+
$table = $this->connection->getTablePrefix().$table;
42+
43+
$database = $this->connection->getDatabaseName();
44+
45+
return (bool) $this->connection->scalar(
46+
$this->grammar->compileTableExists($database, $table)
47+
);
48+
}
49+
3350
/**
3451
* Get the tables for the database.
3552
*

src/Illuminate/Database/Schema/PostgresBuilder.php

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,9 @@ public function hasTable($table)
4949

5050
$table = $this->connection->getTablePrefix().$table;
5151

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;
52+
return (bool) $this->connection->scalar(
53+
$this->grammar->compileTableExists($schema, $table)
54+
);
6055
}
6156

6257
/**

src/Illuminate/Database/Schema/SQLiteBuilder.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,21 @@ public function dropDatabaseIfExists($name)
3131
: true;
3232
}
3333

34+
/**
35+
* Determine if the given table exists.
36+
*
37+
* @param string $table
38+
* @return bool
39+
*/
40+
public function hasTable($table)
41+
{
42+
$table = $this->connection->getTablePrefix().$table;
43+
44+
return (bool) $this->connection->scalar(
45+
$this->grammar->compileTableExists($table)
46+
);
47+
}
48+
3449
/**
3550
* Get the tables for the database.
3651
*

src/Illuminate/Database/Schema/SqlServerBuilder.php

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -42,17 +42,11 @@ public function hasTable($table)
4242
{
4343
[$schema, $table] = $this->parseSchemaAndTable($table);
4444

45-
$schema ??= $this->getDefaultSchema();
4645
$table = $this->connection->getTablePrefix().$table;
4746

48-
foreach ($this->getTables() as $value) {
49-
if (strtolower($table) === strtolower($value['name'])
50-
&& strtolower($schema) === strtolower($value['schema'])) {
51-
return true;
52-
}
53-
}
54-
55-
return false;
47+
return (bool) $this->connection->scalar(
48+
$this->grammar->compileTableExists($schema, $table)
49+
);
5650
}
5751

5852
/**

tests/Database/DatabaseMariaDbSchemaBuilderTest.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@ public function testHasTable()
2020
{
2121
$connection = m::mock(Connection::class);
2222
$grammar = m::mock(MariaDbGrammar::class);
23-
$processor = m::mock(MariaDbProcessor::class);
2423
$connection->shouldReceive('getDatabaseName')->andReturn('db');
2524
$connection->shouldReceive('getSchemaGrammar')->andReturn($grammar);
26-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
2725
$builder = new MariaDbBuilder($connection);
28-
$grammar->shouldReceive('compileTables')->once()->andReturn('sql');
29-
$processor->shouldReceive('processTables')->once()->andReturn([['name' => 'prefix_table']]);
26+
$grammar->shouldReceive('compileTableExists')->once()->andReturn('sql');
3027
$connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_');
31-
$connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'prefix_table']]);
28+
$connection->shouldReceive('scalar')->once()->with('sql')->andReturn(1);
3229

3330
$this->assertTrue($builder->hasTable('table'));
3431
}

tests/Database/DatabaseMySQLSchemaBuilderTest.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,15 +20,12 @@ public function testHasTable()
2020
{
2121
$connection = m::mock(Connection::class);
2222
$grammar = m::mock(MySqlGrammar::class);
23-
$processor = m::mock(MySqlProcessor::class);
2423
$connection->shouldReceive('getDatabaseName')->andReturn('db');
2524
$connection->shouldReceive('getSchemaGrammar')->andReturn($grammar);
26-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
2725
$builder = new MySqlBuilder($connection);
28-
$grammar->shouldReceive('compileTables')->once()->andReturn('sql');
29-
$processor->shouldReceive('processTables')->once()->andReturn([['name' => 'prefix_table']]);
26+
$grammar->shouldReceive('compileTableExists')->once()->andReturn('sql');
3027
$connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_');
31-
$connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'prefix_table']]);
28+
$connection->shouldReceive('scalar')->once()->with('sql')->andReturn(1);
3229

3330
$this->assertTrue($builder->hasTable('table'));
3431
}

tests/Database/DatabasePostgresBuilderTest.php

Lines changed: 10 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -52,14 +52,11 @@ 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);
5655
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
57-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
58-
$grammar->shouldReceive('compileTables')->andReturn('sql');
59-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'public', 'name' => 'foo']]);
56+
$grammar->shouldReceive('compileTableExists')->andReturn('sql');
57+
$connection->shouldReceive('scalar')->with('sql')->andReturn(1);
6058
$connection->shouldReceive('getTablePrefix');
6159
$builder = $this->getBuilder($connection);
62-
$processor->shouldReceive('processTables')->andReturn([['schema' => 'public', 'name' => 'foo']]);
6360

6461
$this->assertTrue($builder->hasTable('foo'));
6562
$this->assertTrue($builder->hasTable('public.foo'));
@@ -70,14 +67,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathFilled()
7067
$connection = $this->getConnection();
7168
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public');
7269
$grammar = m::mock(PostgresGrammar::class);
73-
$processor = m::mock(PostgresProcessor::class);
7470
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
75-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
76-
$grammar->shouldReceive('compileTables')->andReturn('sql');
77-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
71+
$grammar->shouldReceive('compileTableExists')->andReturn('sql');
72+
$connection->shouldReceive('scalar')->with('sql')->andReturn(1);
7873
$connection->shouldReceive('getTablePrefix');
7974
$builder = $this->getBuilder($connection);
80-
$processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
8175

8276
$this->assertTrue($builder->hasTable('foo'));
8377
$this->assertTrue($builder->hasTable('myapp.foo'));
@@ -89,14 +83,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathFallbackFilled()
8983
$connection->shouldReceive('getConfig')->with('search_path')->andReturn(null);
9084
$connection->shouldReceive('getConfig')->with('schema')->andReturn(['myapp', 'public']);
9185
$grammar = m::mock(PostgresGrammar::class);
92-
$processor = m::mock(PostgresProcessor::class);
9386
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
94-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
95-
$grammar->shouldReceive('compileTables')->andReturn('sql');
96-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
87+
$grammar->shouldReceive('compileTableExists')->andReturn('sql');
88+
$connection->shouldReceive('scalar')->with('sql')->andReturn(1);
9789
$connection->shouldReceive('getTablePrefix');
9890
$builder = $this->getBuilder($connection);
99-
$processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
10091

10192
$this->assertTrue($builder->hasTable('foo'));
10293
$this->assertTrue($builder->hasTable('myapp.foo'));
@@ -108,14 +99,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathIsUserVariable()
10899
$connection->shouldReceive('getConfig')->with('username')->andReturn('foouser');
109100
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user');
110101
$grammar = m::mock(PostgresGrammar::class);
111-
$processor = m::mock(PostgresProcessor::class);
112102
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
113-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
114-
$grammar->shouldReceive('compileTables')->andReturn('sql');
115-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'foouser', 'name' => 'foo']]);
103+
$grammar->shouldReceive('compileTableExists')->andReturn('sql');
104+
$connection->shouldReceive('scalar')->with('sql')->andReturn(1);
116105
$connection->shouldReceive('getTablePrefix');
117106
$builder = $this->getBuilder($connection);
118-
$processor->shouldReceive('processTables')->andReturn([['schema' => 'foouser', 'name' => 'foo']]);
119107

120108
$this->assertTrue($builder->hasTable('foo'));
121109
$this->assertTrue($builder->hasTable('foouser.foo'));
@@ -126,14 +114,11 @@ public function testHasTableWhenSchemaQualifiedAndSearchPathMismatches()
126114
$connection = $this->getConnection();
127115
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
128116
$grammar = m::mock(PostgresGrammar::class);
129-
$processor = m::mock(PostgresProcessor::class);
130117
$connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar);
131-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
132-
$grammar->shouldReceive('compileTables')->andReturn('sql');
133-
$connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
118+
$grammar->shouldReceive('compileTableExists')->andReturn('sql');
119+
$connection->shouldReceive('scalar')->with('sql')->andReturn(1);
134120
$connection->shouldReceive('getTablePrefix');
135121
$builder = $this->getBuilder($connection);
136-
$processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]);
137122

138123
$this->assertTrue($builder->hasTable('myapp.foo'));
139124
}

tests/Database/DatabasePostgresSchemaBuilderTest.php

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,13 @@ public function testHasTable()
2020
{
2121
$connection = m::mock(Connection::class);
2222
$grammar = m::mock(PostgresGrammar::class);
23-
$processor = m::mock(PostgresProcessor::class);
2423
$connection->shouldReceive('getSchemaGrammar')->andReturn($grammar);
25-
$connection->shouldReceive('getPostProcessor')->andReturn($processor);
2624
$connection->shouldReceive('getConfig')->with('schema')->andReturn('schema');
2725
$connection->shouldReceive('getConfig')->with('search_path')->andReturn('public');
2826
$builder = new PostgresBuilder($connection);
29-
$grammar->shouldReceive('compileTables')->twice()->andReturn('sql');
30-
$processor->shouldReceive('processTables')->twice()->andReturn([['schema' => 'public', 'name' => 'prefix_table']]);
27+
$grammar->shouldReceive('compileTableExists')->twice()->andReturn('sql');
3128
$connection->shouldReceive('getTablePrefix')->twice()->andReturn('prefix_');
32-
$connection->shouldReceive('selectFromWriteConnection')->twice()->with('sql')->andReturn([['schema' => 'public', 'name' => 'prefix_table']]);
29+
$connection->shouldReceive('scalar')->twice()->with('sql')->andReturn(1);
3330

3431
$this->assertTrue($builder->hasTable('table'));
3532
$this->assertTrue($builder->hasTable('public.table'));

0 commit comments

Comments
 (0)