From 255009e705c6d99caefa907bfe527484b0e7d995 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 1 Oct 2024 21:26:20 +0330 Subject: [PATCH 1/4] improve has table performance --- src/Illuminate/Database/Schema/Builder.php | 3 +- .../Database/Schema/Grammars/MySqlGrammar.php | 17 +++++++++ .../Schema/Grammars/PostgresGrammar.php | 17 +++++++++ .../Schema/Grammars/SQLiteGrammar.php | 14 ++++++++ .../Schema/Grammars/SqlServerGrammar.php | 16 +++++++++ .../Database/Schema/MySqlBuilder.php | 17 +++++++++ .../Database/Schema/PostgresBuilder.php | 11 ++---- .../Database/Schema/SQLiteBuilder.php | 15 ++++++++ .../Database/Schema/SqlServerBuilder.php | 11 ++---- .../DatabaseMariaDbSchemaBuilderTest.php | 7 ++-- .../DatabaseMySQLSchemaBuilderTest.php | 7 ++-- .../Database/DatabasePostgresBuilderTest.php | 35 ++++++------------- .../DatabasePostgresSchemaBuilderTest.php | 7 ++-- 13 files changed, 119 insertions(+), 58 deletions(-) diff --git a/src/Illuminate/Database/Schema/Builder.php b/src/Illuminate/Database/Schema/Builder.php index 9f5dbaa5f0e0..472f8ab33171 100755 --- a/src/Illuminate/Database/Schema/Builder.php +++ b/src/Illuminate/Database/Schema/Builder.php @@ -144,8 +144,7 @@ public function hasTable($table) { $table = $this->connection->getTablePrefix().$table; - /** @phpstan-ignore arguments.count (SQLite accepts a withSize argument) */ - foreach ($this->getTables(false) as $value) { + foreach ($this->getTables() as $value) { if (strtolower($table) === strtolower($value['name'])) { return true; } diff --git a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php index 70812633a749..667a06ebe9f4 100755 --- a/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/MySqlGrammar.php @@ -76,6 +76,23 @@ public function compileDropDatabaseIfExists($name) ); } + /** + * Compile the query to determine if the given table exists. + * + * @param string $database + * @param string $table + * @return string + */ + public function compileTableExists($database, $table) + { + return sprintf( + 'select exists (select 1 from information_schema.tables where ' + ."table_schema = %s and table_name = %s and table_type in ('BASE TABLE', 'SYSTEM VERSIONED')) as `exists`", + $this->quoteString($database), + $this->quoteString($table) + ); + } + /** * Compile the query to determine the tables. * diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index a3ff8e8fc27a..c2db4b164d4b 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -68,6 +68,23 @@ public function compileDropDatabaseIfExists($name) ); } + /** + * Compile the query to determine if the given table exists. + * + * @param string $schema + * @param string $table + * @return string + */ + public function compileTableExists($schema, $table) + { + return sprintf( + 'select exists (select 1 from pg_class c, pg_namespace n where ' + ."n.nspname = %s and c.relname = %s and c.relkind in ('r', 'p') and n.oid = c.relnamespace) as 'exists'", + $this->quoteString($schema), + $this->quoteString($table) + ); + } + /** * Compile the query to determine the tables. * diff --git a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php index 19689ab6526e..8bd92e0c6ede 100644 --- a/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SQLiteGrammar.php @@ -68,6 +68,20 @@ public function compileDbstatExists() return "select exists (select 1 from pragma_compile_options where compile_options = 'ENABLE_DBSTAT_VTAB') as enabled"; } + /** + * Compile the query to determine if the given table exists. + * + * @param string $table + * @return string + */ + public function compileTableExists($table) + { + return sprintf( + 'select exists (select 1 from sqlite_master where name = %s and type = \'table\') as "exists"', + $this->quoteString(str_replace('.', '__', $table)) + ); + } + /** * Compile the query to determine the tables. * diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index fe8121b4f4fc..12035e5dbd85 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -76,6 +76,22 @@ public function compileDropDatabaseIfExists($name) ); } + /** + * Compile the query to determine if the given table exists. + * + * @param string $schema + * @param string $table + * @return string + */ + public function compileTableExists($schema, $table) + { + return sprintf( + 'select exists (select 1 from sys.tables where schema_id = schema_id(%s) and name = %s) as [exists]', + $this->quoteString($schema), + $this->quoteString($table) + ); + } + /** * Compile the query to determine the tables. * diff --git a/src/Illuminate/Database/Schema/MySqlBuilder.php b/src/Illuminate/Database/Schema/MySqlBuilder.php index 943ae9f4fadf..842130503595 100755 --- a/src/Illuminate/Database/Schema/MySqlBuilder.php +++ b/src/Illuminate/Database/Schema/MySqlBuilder.php @@ -30,6 +30,23 @@ public function dropDatabaseIfExists($name) ); } + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + $table = $this->connection->getTablePrefix().$table; + + $database = $this->connection->getDatabaseName(); + + return (bool) $this->connection->scalar( + $this->grammar->compileTableExists($database, $table) + ); + } + /** * Get the tables for the database. * diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index c8d7cd4c8b4e..7bb13a687a90 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -49,14 +49,9 @@ public function hasTable($table) $table = $this->connection->getTablePrefix().$table; - foreach ($this->getTables() as $value) { - if (strtolower($table) === strtolower($value['name']) - && strtolower($schema) === strtolower($value['schema'])) { - return true; - } - } - - return false; + return (bool) $this->connection->scalar( + $this->grammar->compileTableExists($schema, $table) + ); } /** diff --git a/src/Illuminate/Database/Schema/SQLiteBuilder.php b/src/Illuminate/Database/Schema/SQLiteBuilder.php index 5295584ea12f..fb352bf0dd04 100644 --- a/src/Illuminate/Database/Schema/SQLiteBuilder.php +++ b/src/Illuminate/Database/Schema/SQLiteBuilder.php @@ -31,6 +31,21 @@ public function dropDatabaseIfExists($name) : true; } + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + $table = $this->connection->getTablePrefix().$table; + + return (bool) $this->connection->scalar( + $this->grammar->compileTableExists($table) + ); + } + /** * Get the tables for the database. * diff --git a/src/Illuminate/Database/Schema/SqlServerBuilder.php b/src/Illuminate/Database/Schema/SqlServerBuilder.php index 2833b5098aba..0392aea2b6d8 100644 --- a/src/Illuminate/Database/Schema/SqlServerBuilder.php +++ b/src/Illuminate/Database/Schema/SqlServerBuilder.php @@ -45,14 +45,9 @@ public function hasTable($table) $schema ??= $this->getDefaultSchema(); $table = $this->connection->getTablePrefix().$table; - foreach ($this->getTables() as $value) { - if (strtolower($table) === strtolower($value['name']) - && strtolower($schema) === strtolower($value['schema'])) { - return true; - } - } - - return false; + return (bool) $this->connection->scalar( + $this->grammar->compileTableExists($schema, $table) + ); } /** diff --git a/tests/Database/DatabaseMariaDbSchemaBuilderTest.php b/tests/Database/DatabaseMariaDbSchemaBuilderTest.php index a49345a6c14b..d8d7a64c17cc 100755 --- a/tests/Database/DatabaseMariaDbSchemaBuilderTest.php +++ b/tests/Database/DatabaseMariaDbSchemaBuilderTest.php @@ -20,15 +20,12 @@ public function testHasTable() { $connection = m::mock(Connection::class); $grammar = m::mock(MariaDbGrammar::class); - $processor = m::mock(MariaDbProcessor::class); $connection->shouldReceive('getDatabaseName')->andReturn('db'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); $builder = new MariaDbBuilder($connection); - $grammar->shouldReceive('compileTables')->once()->andReturn('sql'); - $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'prefix_table']]); + $grammar->shouldReceive('compileTableExists')->once()->andReturn('sql'); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); - $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'prefix_table']]); + $connection->shouldReceive('scalar')->once()->with('sql')->andReturn(1); $this->assertTrue($builder->hasTable('table')); } diff --git a/tests/Database/DatabaseMySQLSchemaBuilderTest.php b/tests/Database/DatabaseMySQLSchemaBuilderTest.php index 53869e6d5604..c5fa3ea273f2 100755 --- a/tests/Database/DatabaseMySQLSchemaBuilderTest.php +++ b/tests/Database/DatabaseMySQLSchemaBuilderTest.php @@ -20,15 +20,12 @@ public function testHasTable() { $connection = m::mock(Connection::class); $grammar = m::mock(MySqlGrammar::class); - $processor = m::mock(MySqlProcessor::class); $connection->shouldReceive('getDatabaseName')->andReturn('db'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); $builder = new MySqlBuilder($connection); - $grammar->shouldReceive('compileTables')->once()->andReturn('sql'); - $processor->shouldReceive('processTables')->once()->andReturn([['name' => 'prefix_table']]); + $grammar->shouldReceive('compileTableExists')->once()->andReturn('sql'); $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); - $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql')->andReturn([['name' => 'prefix_table']]); + $connection->shouldReceive('scalar')->once()->with('sql')->andReturn(1); $this->assertTrue($builder->hasTable('table')); } diff --git a/tests/Database/DatabasePostgresBuilderTest.php b/tests/Database/DatabasePostgresBuilderTest.php index d3a280239d3a..a7c60e6c7267 100644 --- a/tests/Database/DatabasePostgresBuilderTest.php +++ b/tests/Database/DatabasePostgresBuilderTest.php @@ -52,14 +52,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathMissing() $connection->shouldReceive('getConfig')->with('search_path')->andReturn(null); $connection->shouldReceive('getConfig')->with('schema')->andReturn(null); $grammar = m::mock(PostgresGrammar::class); - $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'public', 'name' => 'foo']]); + $grammar->shouldReceive('compileTableExists')->andReturn('sql'); + $connection->shouldReceive('scalar')->with('sql')->andReturn(1); $connection->shouldReceive('getTablePrefix'); $builder = $this->getBuilder($connection); - $processor->shouldReceive('processTables')->andReturn([['schema' => 'public', 'name' => 'foo']]); $this->assertTrue($builder->hasTable('foo')); $this->assertTrue($builder->hasTable('public.foo')); @@ -70,14 +67,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathFilled() $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public'); $grammar = m::mock(PostgresGrammar::class); - $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]); + $grammar->shouldReceive('compileTableExists')->andReturn('sql'); + $connection->shouldReceive('scalar')->with('sql')->andReturn(1); $connection->shouldReceive('getTablePrefix'); $builder = $this->getBuilder($connection); - $processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]); $this->assertTrue($builder->hasTable('foo')); $this->assertTrue($builder->hasTable('myapp.foo')); @@ -89,14 +83,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathFallbackFilled() $connection->shouldReceive('getConfig')->with('search_path')->andReturn(null); $connection->shouldReceive('getConfig')->with('schema')->andReturn(['myapp', 'public']); $grammar = m::mock(PostgresGrammar::class); - $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]); + $grammar->shouldReceive('compileTableExists')->andReturn('sql'); + $connection->shouldReceive('scalar')->with('sql')->andReturn(1); $connection->shouldReceive('getTablePrefix'); $builder = $this->getBuilder($connection); - $processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]); $this->assertTrue($builder->hasTable('foo')); $this->assertTrue($builder->hasTable('myapp.foo')); @@ -108,14 +99,11 @@ public function testHasTableWhenSchemaUnqualifiedAndSearchPathIsUserVariable() $connection->shouldReceive('getConfig')->with('username')->andReturn('foouser'); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user'); $grammar = m::mock(PostgresGrammar::class); - $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'foouser', 'name' => 'foo']]); + $grammar->shouldReceive('compileTableExists')->andReturn('sql'); + $connection->shouldReceive('scalar')->with('sql')->andReturn(1); $connection->shouldReceive('getTablePrefix'); $builder = $this->getBuilder($connection); - $processor->shouldReceive('processTables')->andReturn([['schema' => 'foouser', 'name' => 'foo']]); $this->assertTrue($builder->hasTable('foo')); $this->assertTrue($builder->hasTable('foouser.foo')); @@ -126,14 +114,11 @@ public function testHasTableWhenSchemaQualifiedAndSearchPathMismatches() $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); $grammar = m::mock(PostgresGrammar::class); - $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); - $grammar->shouldReceive('compileTables')->andReturn('sql'); - $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn([['schema' => 'myapp', 'name' => 'foo']]); + $grammar->shouldReceive('compileTableExists')->andReturn('sql'); + $connection->shouldReceive('scalar')->with('sql')->andReturn(1); $connection->shouldReceive('getTablePrefix'); $builder = $this->getBuilder($connection); - $processor->shouldReceive('processTables')->andReturn([['schema' => 'myapp', 'name' => 'foo']]); $this->assertTrue($builder->hasTable('myapp.foo')); } diff --git a/tests/Database/DatabasePostgresSchemaBuilderTest.php b/tests/Database/DatabasePostgresSchemaBuilderTest.php index 5794ce44aeea..c38c7d183614 100755 --- a/tests/Database/DatabasePostgresSchemaBuilderTest.php +++ b/tests/Database/DatabasePostgresSchemaBuilderTest.php @@ -20,16 +20,13 @@ public function testHasTable() { $connection = m::mock(Connection::class); $grammar = m::mock(PostgresGrammar::class); - $processor = m::mock(PostgresProcessor::class); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); - $connection->shouldReceive('getPostProcessor')->andReturn($processor); $connection->shouldReceive('getConfig')->with('schema')->andReturn('schema'); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); $builder = new PostgresBuilder($connection); - $grammar->shouldReceive('compileTables')->twice()->andReturn('sql'); - $processor->shouldReceive('processTables')->twice()->andReturn([['schema' => 'public', 'name' => 'prefix_table']]); + $grammar->shouldReceive('compileTableExists')->twice()->andReturn('sql'); $connection->shouldReceive('getTablePrefix')->twice()->andReturn('prefix_'); - $connection->shouldReceive('selectFromWriteConnection')->twice()->with('sql')->andReturn([['schema' => 'public', 'name' => 'prefix_table']]); + $connection->shouldReceive('scalar')->twice()->with('sql')->andReturn(1); $this->assertTrue($builder->hasTable('table')); $this->assertTrue($builder->hasTable('public.table')); From 0340a710fec0ef279646186e46bb2fbfd26d6b0c Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 1 Oct 2024 22:27:41 +0330 Subject: [PATCH 2/4] fix tests --- .../Database/Schema/Grammars/PostgresGrammar.php | 2 +- .../Database/Schema/Grammars/SqlServerGrammar.php | 7 +++---- src/Illuminate/Database/Schema/SqlServerBuilder.php | 1 - 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php index c2db4b164d4b..85b57df62f50 100755 --- a/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/PostgresGrammar.php @@ -79,7 +79,7 @@ public function compileTableExists($schema, $table) { return sprintf( 'select exists (select 1 from pg_class c, pg_namespace n where ' - ."n.nspname = %s and c.relname = %s and c.relkind in ('r', 'p') and n.oid = c.relnamespace) as 'exists'", + ."n.nspname = %s and c.relname = %s and c.relkind in ('r', 'p') and n.oid = c.relnamespace)", $this->quoteString($schema), $this->quoteString($table) ); diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 12035e5dbd85..8078a912d4c8 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -79,16 +79,15 @@ public function compileDropDatabaseIfExists($name) /** * Compile the query to determine if the given table exists. * - * @param string $schema + * @param string|null $schema * @param string $table * @return string */ public function compileTableExists($schema, $table) { return sprintf( - 'select exists (select 1 from sys.tables where schema_id = schema_id(%s) and name = %s) as [exists]', - $this->quoteString($schema), - $this->quoteString($table) + 'select (case when object_id(%s, \'U\') is null then 0 else 1 end) as [exists]', + $this->quoteString($this->wrapTable($schema ? $schema.'.'.$table : $table)) ); } diff --git a/src/Illuminate/Database/Schema/SqlServerBuilder.php b/src/Illuminate/Database/Schema/SqlServerBuilder.php index 0392aea2b6d8..9b59ccc0ebd3 100644 --- a/src/Illuminate/Database/Schema/SqlServerBuilder.php +++ b/src/Illuminate/Database/Schema/SqlServerBuilder.php @@ -42,7 +42,6 @@ public function hasTable($table) { [$schema, $table] = $this->parseSchemaAndTable($table); - $schema ??= $this->getDefaultSchema(); $table = $this->connection->getTablePrefix().$table; return (bool) $this->connection->scalar( From 192d0b075d117b62d6d7fb9e5f60673b4310ce57 Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 1 Oct 2024 22:31:10 +0330 Subject: [PATCH 3/4] fix tests --- src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 8078a912d4c8..e52d3d1d22b4 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -87,7 +87,7 @@ public function compileTableExists($schema, $table) { return sprintf( 'select (case when object_id(%s, \'U\') is null then 0 else 1 end) as [exists]', - $this->quoteString($this->wrapTable($schema ? $schema.'.'.$table : $table)) + $this->quoteString($this->wrap($schema ? $schema.'.'.$table : $table)) ); } From 8cf67a7a5e55f51b3f8bf96139a08e84a568ec8b Mon Sep 17 00:00:00 2001 From: Hafez Divandari Date: Tue, 1 Oct 2024 22:39:09 +0330 Subject: [PATCH 4/4] fix tests --- src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index e52d3d1d22b4..81258f2e1036 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -87,7 +87,7 @@ public function compileTableExists($schema, $table) { return sprintf( 'select (case when object_id(%s, \'U\') is null then 0 else 1 end) as [exists]', - $this->quoteString($this->wrap($schema ? $schema.'.'.$table : $table)) + $this->quoteString($schema ? $schema.'.'.$table : $table) ); }