diff --git a/src/Illuminate/Database/Concerns/UsesSchemaAwareTables.php b/src/Illuminate/Database/Concerns/UsesSchemaAwareTables.php new file mode 100644 index 000000000000..b61c24017043 --- /dev/null +++ b/src/Illuminate/Database/Concerns/UsesSchemaAwareTables.php @@ -0,0 +1,87 @@ +parseSchemaAndTable($table); + + $table = $this->connection->getTablePrefix().$table; + + $results = $this->connection->selectFromWriteConnection( + $this->grammar->compileColumns($database, $schema, $table) + ); + + return $this->connection->getPostProcessor()->processColumns($results); + } + + /** + * Determine if the given table exists. + * + * @param string $table + * @return bool + */ + public function hasTable($table) + { + [, $schema, $table] = $this->parseSchemaAndTable($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; + } + + /** + * Parse the database object reference and extract the database, schema, and table. + * + * @param string $reference + * @return array + */ + protected function parseSchemaAndTable($reference) + { + $parts = explode('.', $reference); + + // In order to be fully backward compatibel with previous version where users + // may have used square brackets with the identifiers in SQLServer grammar + // e.g. "schema.[table1]". We shall trim the parts for their occurrence. + $parts = array_map(function ($part) { + return trim($part, '[]'); + }, $parts); + + $database = $this->connection->getConfig('database'); + + // If the reference contains a database name, we will use that instead of the + // default database name for the connection. This allows the database name + // to be specified in the query instead of at the full connection level. + if (count($parts) === 3) { + $database = $parts[0]; + array_shift($parts); + } + + // We will use the default schema unless the schema has been specified in the + // query. If the schema has been specified in the query then we can use it + // instead of using the Postgres configuration or SQL database default. + $schema = $this->getDefaultSchema(); + + if (count($parts) === 2) { + $schema = $parts[0]; + array_shift($parts); + } + + return [$database, $schema, $parts[0]]; + } +} diff --git a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php index 5af6631be2b7..abe509d78605 100755 --- a/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php +++ b/src/Illuminate/Database/Schema/Grammars/SqlServerGrammar.php @@ -145,26 +145,33 @@ public function compileColumnListing($table) /** * Compile the query to determine the columns. * + * @param string $database + * @param string $schema * @param string $table * @return string */ - public function compileColumns($table) - { - return sprintf( - 'select col.name, type.name as type_name, ' - .'col.max_length as length, col.precision as precision, col.scale as places, ' - .'col.is_nullable as nullable, def.definition as [default], ' - .'col.is_identity as autoincrement, col.collation_name as collation, ' - .'cast(prop.value as nvarchar(max)) as comment ' - .'from sys.columns as col ' - .'join sys.types as type on col.user_type_id = type.user_type_id ' - .'join sys.objects as obj on col.object_id = obj.object_id ' - .'join sys.schemas as scm on obj.schema_id = scm.schema_id ' - .'left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id ' - ."left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' " - ."where obj.type in ('U', 'V') and obj.name = %s and scm.name = SCHEMA_NAME() " - .'order by col.column_id', + public function compileColumns($database, $schema, $table) + { + $sql = <<<'SQL' +select col.name, type.name as type_name, +col.max_length as length, col.precision as precision, col.scale as places, +col.is_nullable as nullable, def.definition as [default], +col.is_identity as autoincrement, col.collation_name as collation, +cast(prop.value as nvarchar(max)) as comment +from sys.columns as col +join sys.types as type on col.user_type_id = type.user_type_id +join sys.objects as obj on col.object_id = obj.object_id +join sys.schemas as scm on obj.schema_id = scm.schema_id +left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id +left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' +where obj.type in ('U', 'V') +and obj.[name] = %s +and scm.[name] = %s +SQL; + + return sprintf($sql, $this->quoteString($table), + $this->quoteString($schema), ); } diff --git a/src/Illuminate/Database/Schema/PostgresBuilder.php b/src/Illuminate/Database/Schema/PostgresBuilder.php index 5b62187b45f0..23565960901c 100755 --- a/src/Illuminate/Database/Schema/PostgresBuilder.php +++ b/src/Illuminate/Database/Schema/PostgresBuilder.php @@ -3,10 +3,11 @@ namespace Illuminate\Database\Schema; use Illuminate\Database\Concerns\ParsesSearchPath; +use Illuminate\Database\Concerns\UsesSchemaAwareTables; class PostgresBuilder extends Builder { - use ParsesSearchPath { + use ParsesSearchPath, UsesSchemaAwareTables { parseSearchPath as baseParseSearchPath; } @@ -36,23 +37,6 @@ public function dropDatabaseIfExists($name) ); } - /** - * Determine if the given table exists. - * - * @param string $table - * @return bool - */ - public function hasTable($table) - { - [$database, $schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - return count($this->connection->selectFromWriteConnection( - $this->grammar->compileTableExists(), [$database, $schema, $table] - )) > 0; - } - /** * Get the user-defined types that belong to the database. * @@ -206,22 +190,13 @@ public function dropAllTypes() } /** - * Get the columns for a given table. + * Get the default schema for the connection. * - * @param string $table - * @return array + * @return string */ - public function getColumns($table) + public function getDefaultSchema() { - [$database, $schema, $table] = $this->parseSchemaAndTable($table); - - $table = $this->connection->getTablePrefix().$table; - - $results = $this->connection->selectFromWriteConnection( - $this->grammar->compileColumns($database, $schema, $table) - ); - - return $this->connection->getPostProcessor()->processColumns($results); + return $this->getSchemas()[0]; } /** @@ -270,39 +245,6 @@ protected function getSchemas() ); } - /** - * Parse the database object reference and extract the database, schema, and table. - * - * @param string $reference - * @return array - */ - protected function parseSchemaAndTable($reference) - { - $parts = explode('.', $reference); - - $database = $this->connection->getConfig('database'); - - // If the reference contains a database name, we will use that instead of the - // default database name for the connection. This allows the database name - // to be specified in the query instead of at the full connection level. - if (count($parts) === 3) { - $database = $parts[0]; - array_shift($parts); - } - - // We will use the default schema unless the schema has been specified in the - // query. If the schema has been specified in the query then we can use it - // instead of a default schema configured in the connection search path. - $schema = $this->getSchemas()[0]; - - if (count($parts) === 2) { - $schema = $parts[0]; - array_shift($parts); - } - - return [$database, $schema, $parts[0]]; - } - /** * Parse the "search_path" configuration value into an array. * diff --git a/src/Illuminate/Database/Schema/SqlServerBuilder.php b/src/Illuminate/Database/Schema/SqlServerBuilder.php index e7717534f803..c7493bcc00a8 100644 --- a/src/Illuminate/Database/Schema/SqlServerBuilder.php +++ b/src/Illuminate/Database/Schema/SqlServerBuilder.php @@ -2,8 +2,12 @@ namespace Illuminate\Database\Schema; +use Illuminate\Database\Concerns\UsesSchemaAwareTables; + class SqlServerBuilder extends Builder { + use UsesSchemaAwareTables; + /** * Create a database in the schema. * @@ -53,7 +57,7 @@ public function dropAllViews() } /** - * Drop all tables from the database. + * Get all tables from the database. * * @deprecated Will be removed in a future Laravel version. * @@ -79,4 +83,14 @@ public function getAllViews() $this->grammar->compileGetAllViews() ); } + + /** + * Get the default schema for the connection. + * + * @return string + */ + public function getDefaultSchema() + { + return $this->connection->getConfig('default_schema') ?: 'dbo'; + } } diff --git a/tests/Database/DatabasePostgresBuilderTest.php b/tests/Database/DatabasePostgresBuilderTest.php index 4de2c649ebdd..211373137898 100644 --- a/tests/Database/DatabasePostgresBuilderTest.php +++ b/tests/Database/DatabasePostgresBuilderTest.php @@ -48,95 +48,154 @@ public function testDropDatabaseIfExists() public function testHasTableWhenSchemaUnqualifiedAndSearchPathMissing() { + $tables = [ + ['name' => 'foo', 'schema' => 'public'], + ]; + $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn(null); $connection->shouldReceive('getConfig')->with('schema')->andReturn(null); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); - $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']); + + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processTables')->andReturn($tables); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); $builder = $this->getBuilder($connection); - $builder->hasTable('foo'); + $this->assertTrue($builder->hasTable('foo')); + $this->assertFalse($builder->hasTable('bar')); } public function testHasTableWhenSchemaUnqualifiedAndSearchPathFilled() { + $tables = [ + ['name' => 'foo', 'schema' => 'myapp'], + ]; $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('myapp,public'); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); - $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']); + + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processTables')->andReturn($tables); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); $builder = $this->getBuilder($connection); - $builder->hasTable('foo'); + $this->assertTrue($builder->hasTable('foo')); + $this->assertFalse($builder->hasTable('bar')); } public function testHasTableWhenSchemaUnqualifiedAndSearchPathFallbackFilled() { + $tables = [ + ['name' => 'foo', 'schema' => 'myapp'], + ]; $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn(null); $connection->shouldReceive('getConfig')->with('schema')->andReturn(['myapp', 'public']); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); - $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']); + + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processTables')->andReturn($tables); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); $builder = $this->getBuilder($connection); - $builder->hasTable('foo'); + $this->assertTrue($builder->hasTable('foo')); + $this->assertFalse($builder->hasTable('bar')); } public function testHasTableWhenSchemaUnqualifiedAndSearchPathIsUserVariable() { + $tables = [ + ['name' => 'foo', 'schema' => 'foouser'], + ]; + $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('username')->andReturn('foouser'); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('$user'); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); - $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']); + + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processTables')->andReturn($tables); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); $builder = $this->getBuilder($connection); - $builder->hasTable('foo'); + $this->assertTrue($builder->hasTable('foo')); + $this->assertFalse($builder->hasTable('bar')); } public function testHasTableWhenSchemaQualifiedAndSearchPathMismatches() { + $tables = [ + ['name' => 'foo', 'schema' => 'myapp'], + ]; + $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); - $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']); + + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processTables')->andReturn($tables); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); $builder = $this->getBuilder($connection); - $builder->hasTable('myapp.foo'); + $this->assertTrue($builder->hasTable('myapp.foo')); + $this->assertFalse($builder->hasTable('public.foo')); } + // this test may be unnecessary. Postgres does not allow to run queries over databases. public function testHasTableWhenDatabaseAndSchemaQualifiedAndSearchPathMismatches() { + $tables = [ + ['name' => 'foo', 'schema' => 'myapp'], + ]; + $connection = $this->getConnection(); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); $grammar = m::mock(PostgresGrammar::class); $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); - $grammar->shouldReceive('compileTableExists')->andReturn("select * from information_schema.tables where table_catalog = ? and table_schema = ? and table_name = ? and table_type = 'BASE TABLE'"); - $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']); + + $processor = m::mock(PostgresProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processTables')->andReturn($tables); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); $builder = $this->getBuilder($connection); - $builder->hasTable('mydatabase.myapp.foo'); + $this->assertTrue($builder->hasTable('mydatabase.myapp.foo')); + $this->assertFalse($builder->hasTable('mydatabase.laravel.foo')); } public function testGetColumnListingWhenSchemaUnqualifiedAndSearchPathMissing() diff --git a/tests/Database/DatabasePostgresSchemaBuilderTest.php b/tests/Database/DatabasePostgresSchemaBuilderTest.php index 448b41138a84..f0edf0e8b7a4 100755 --- a/tests/Database/DatabasePostgresSchemaBuilderTest.php +++ b/tests/Database/DatabasePostgresSchemaBuilderTest.php @@ -18,17 +18,26 @@ protected function tearDown(): void public function testHasTable() { + $tables = [ + ['name' => 'prefix_table', 'schema' => 'public'], + ]; + $connection = m::mock(Connection::class); $grammar = m::mock(PostgresGrammar::class); + $processor = m::mock(PostgresProcessor::class); + $processor->shouldReceive('processTables')->andReturn($tables); + + $connection->shouldReceive('getPostProcessor')->andReturn($processor); $connection->shouldReceive('getDatabaseName')->andReturn('db'); $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); $connection->shouldReceive('getConfig')->with('database')->andReturn('db'); $connection->shouldReceive('getConfig')->with('schema')->andReturn('schema'); $connection->shouldReceive('getConfig')->with('search_path')->andReturn('public'); $builder = new PostgresBuilder($connection); - $grammar->shouldReceive('compileTableExists')->once()->andReturn('sql'); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix')->once()->andReturn('prefix_'); - $connection->shouldReceive('selectFromWriteConnection')->once()->with('sql', ['db', 'public', 'prefix_table'])->andReturn(['prefix_table']); $this->assertTrue($builder->hasTable('table')); } diff --git a/tests/Database/DatabaseSqlServerBuilderTest.php b/tests/Database/DatabaseSqlServerBuilderTest.php new file mode 100644 index 000000000000..2fd9ad28df31 --- /dev/null +++ b/tests/Database/DatabaseSqlServerBuilderTest.php @@ -0,0 +1,365 @@ +shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $connection->shouldReceive('statement')->once()->with( + 'create database "my_temporary_database"' + )->andReturn(true); + + $builder = $this->getBuilder($connection); + $builder->createDatabase('my_temporary_database'); + } + + public function testDropDatabaseIfExists() + { + $grammar = new SqlServerGrammar; + + $connection = m::mock(Connection::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $connection->shouldReceive('statement')->once()->with( + 'drop database if exists "my_database_a"' + )->andReturn(true); + + $builder = $this->getBuilder($connection); + + $builder->dropDatabaseIfExists('my_database_a'); + } + + public function testHasTableDefaultSchemaFallback() + { + $tables = [ + ['name' => 'my_table1', 'schema' => 'dbo'], + ['name' => 'my_table1', 'schema' => 'my_schema'], + ['name' => 'my_table2', 'schema' => 'my_schema'], + ]; + + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn(null); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn(null); + $processor = m::mock(SqlServerProcessor::class); + $processor->shouldReceive('processTables') + ->with($tables) + ->andReturn($tables); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $builder = $this->getBuilder($connection); + + $this->assertFalse($builder->hasTable('foo')); + $this->assertFalse($builder->hasTable('[foo]')); + $this->assertFalse($builder->hasTable('my_table2')); + $this->assertFalse($builder->hasTable('my_schema.my_table3')); + $this->assertFalse($builder->hasTable('my_schema2.my_table1')); + + $this->assertTrue($builder->hasTable('my_table1')); + $this->assertTrue($builder->hasTable('my_schema.my_table1')); + $this->assertTrue($builder->hasTable('dbo.my_table1')); + $this->assertTrue($builder->hasTable('my_schema.my_table2')); + $this->assertTrue($builder->hasTable('[my_schema].[my_table2]')); + $this->assertTrue($builder->hasTable('[dbo].my_table1')); + } + + public function testHasTableDefaultSchema() + { + $tables = [ + ['name' => 'my_table1', 'schema' => 'dbo'], + ['name' => 'my_table1', 'schema' => 'my_schema'], + ['name' => 'my_table2', 'schema' => 'my_schema'], + ['name' => 'my_table3', 'schema' => 'my_default_schema'], + ]; + + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileTables')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn($tables); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn(null); + $processor = m::mock(SqlServerProcessor::class); + $processor->shouldReceive('processTables') + ->with($tables) + ->andReturn($tables); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $builder = $this->getBuilder($connection); + + $this->assertFalse($builder->hasTable('foo')); + $this->assertFalse($builder->hasTable('[foo]')); + $this->assertFalse($builder->hasTable('my_table2')); + $this->assertFalse($builder->hasTable('my_schema.my_table3')); + $this->assertFalse($builder->hasTable('my_schema2.my_table1')); + $this->assertFalse($builder->hasTable('my_table1')); + + $this->assertTrue($builder->hasTable('my_schema.my_table1')); + $this->assertTrue($builder->hasTable('dbo.my_table1')); + $this->assertTrue($builder->hasTable('my_schema.my_table2')); + $this->assertTrue($builder->hasTable('[my_schema].[my_table2]')); + $this->assertTrue($builder->hasTable('[dbo].my_table1')); + $this->assertTrue($builder->hasTable('[my_default_schema].my_table3')); + $this->assertTrue($builder->hasTable('my_table3')); + } + + public function testGetColumnListingWhenSchemaUnqualifiedAndDefaultSchemaMissing() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn(null); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileColumns')->with('laravel', 'dbo', 'foo')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]); + $builder = $this->getBuilder($connection); + + $builder->getColumnListing('foo'); + } + + public function testGetColumnListingWhenSchemaUnqualifiedAndDefaultSchemaFilled() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileColumns')->with('laravel', 'my_default_schema', 'foo')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]); + $builder = $this->getBuilder($connection); + + $builder->getColumnListing('foo'); + } + + public function testGetColumnListingWhenSchemaQualifiedAndDefaultSchemaIsDifferent() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileColumns')->with('laravel', 'my_schema', 'foo')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]); + $builder = $this->getBuilder($connection); + + $builder->getColumnListing('my_schema.foo'); + } + + public function testGetColumnListingWhenSchemaQualifiedAndEscapedAndDefaultSchemaIsDifferent() + { + // escaped schema and table name by using brackets [] + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileColumns')->with('laravel', 'my_schema', 'foo')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]); + $builder = $this->getBuilder($connection); + + $builder->getColumnListing('[my_schema].[foo]'); + } + + public function testGetColumnWhenDatabaseAndSchemaQualifiedAndDefaultSchemaIsDifferent() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $grammar->shouldReceive('compileColumns')->with('my_database', 'my_schema', 'foo')->andReturn('sql'); + $connection->shouldReceive('selectFromWriteConnection')->with('sql')->andReturn(['countable_result']); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([['name' => 'some_column']]); + $builder = $this->getBuilder($connection); + + $builder->getColumnListing('my_database.my_schema.foo'); + } + + public function testDropAllTables() + { + $connection = $this->getConnection(); + $grammar = m::mock(SqlServerGrammar::class)->makePartial(); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getSchemaGrammar')->once()->andReturn($grammar); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $connection->shouldReceive('statement')->with( + "DECLARE @sql NVARCHAR(MAX) = N''; + SELECT @sql += 'ALTER TABLE ' + + QUOTENAME(OBJECT_SCHEMA_NAME(parent_object_id)) + '.' + + QUOTENAME(OBJECT_NAME(parent_object_id)) + + ' DROP CONSTRAINT ' + QUOTENAME(name) + ';' + FROM sys.foreign_keys; + + EXEC sp_executesql @sql;" + ); + $connection->shouldReceive('statement')->with("EXEC sp_msforeachtable 'DROP TABLE ?'"); + $builder = $this->getBuilder($connection); + + $builder->dropAllTables(); + } + + public function testHasColumnWithDefaultSchemaMissing() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn(null); + $grammar = m::mock(SqlServerGrammar::class)->makePartial(); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + + $expectedSql = <<<'SQL' +select col.name, type.name as type_name, +col.max_length as length, col.precision as precision, col.scale as places, +col.is_nullable as nullable, def.definition as [default], +col.is_identity as autoincrement, col.collation_name as collation, +cast(prop.value as nvarchar(max)) as comment +from sys.columns as col +join sys.types as type on col.user_type_id = type.user_type_id +join sys.objects as obj on col.object_id = obj.object_id +join sys.schemas as scm on obj.schema_id = scm.schema_id +left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id +left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' +where obj.type in ('U', 'V') +and obj.[name] = N'foo' +and scm.[name] = N'dbo' +SQL; + + $connection->shouldReceive('selectFromWriteConnection') + ->with($expectedSql) + ->andReturn([['name' => 'bar']]); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([['name' => 'bar']]); + $builder = $this->getBuilder($connection); + + $this->assertTrue($builder->hasColumn('foo', 'bar')); + } + + public function testHasColumnWithDefaultSchemaNoColumnsFound() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class)->makePartial(); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + + $expectedSql = <<<'SQL' +select col.name, type.name as type_name, +col.max_length as length, col.precision as precision, col.scale as places, +col.is_nullable as nullable, def.definition as [default], +col.is_identity as autoincrement, col.collation_name as collation, +cast(prop.value as nvarchar(max)) as comment +from sys.columns as col +join sys.types as type on col.user_type_id = type.user_type_id +join sys.objects as obj on col.object_id = obj.object_id +join sys.schemas as scm on obj.schema_id = scm.schema_id +left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id +left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' +where obj.type in ('U', 'V') +and obj.[name] = N'foo' +and scm.[name] = N'my_default_schema' +SQL; + + $connection->shouldReceive('selectFromWriteConnection') + ->with($expectedSql) + ->andReturn([]); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([]); + $builder = $this->getBuilder($connection); + + $this->assertFalse($builder->hasColumn('foo', 'bar')); + } + + public function testHasColumnWithQualifiedSchemaAndDefaultSchemaNoColumnsFound() + { + $connection = $this->getConnection(); + $connection->shouldReceive('getConfig')->with('default_schema')->andReturn('my_default_schema'); + $grammar = m::mock(SqlServerGrammar::class)->makePartial(); + $connection->shouldReceive('getSchemaGrammar')->andReturn($grammar); + $connection->shouldReceive('getTablePrefix'); + $connection->shouldReceive('getConfig')->with('database')->andReturn('laravel'); + + $expectedSql = <<<'SQL' +select col.name, type.name as type_name, +col.max_length as length, col.precision as precision, col.scale as places, +col.is_nullable as nullable, def.definition as [default], +col.is_identity as autoincrement, col.collation_name as collation, +cast(prop.value as nvarchar(max)) as comment +from sys.columns as col +join sys.types as type on col.user_type_id = type.user_type_id +join sys.objects as obj on col.object_id = obj.object_id +join sys.schemas as scm on obj.schema_id = scm.schema_id +left join sys.default_constraints def on col.default_object_id = def.object_id and col.object_id = def.parent_object_id +left join sys.extended_properties as prop on obj.object_id = prop.major_id and col.column_id = prop.minor_id and prop.name = 'MS_Description' +where obj.type in ('U', 'V') +and obj.[name] = N'foo' +and scm.[name] = N'my_schema' +SQL; + + $connection->shouldReceive('selectFromWriteConnection') + ->with($expectedSql) + ->andReturn([]); + $processor = m::mock(SqlServerProcessor::class); + $connection->shouldReceive('getPostProcessor')->andReturn($processor); + $processor->shouldReceive('processColumns')->andReturn([]); + $builder = $this->getBuilder($connection); + + $this->assertFalse($builder->hasColumn('my_schema.foo', 'bar')); + } + + protected function tearDown(): void + { + m::close(); + } + + protected function getConnection() + { + return m::mock(Connection::class); + } + + protected function getBuilder($connection) + { + return new SqlServerBuilder($connection); + } + + protected function getGrammar() + { + return new SqlServerGrammar; + } +}