-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improve handling of schemas in SQL Server >= 2008 #3020
Conversation
The fix now adds support for custom schemas in table columns, too. I'll try to add some tests in the next days. |
Seems like Travis had some trouble yesterday: @Majkl578 Are you able to rerun the build? Seems like they just killed it. |
@stlrnz I restarted the build. Please see the code standards violations. |
@stlrnz is it possible to test the patch in a functional way? IMO, testing what SQL looks like is the least important thing. |
I've added a test for the modified table listing. I don't really know which functional test to add, because this PR isn't changing any functionality. It just makes some existing features available for schema-based SQL Server environments (like I use). The only thing I could do is duplicate some existing tests and add a schema name. But does this really makes sense? |
@morozov do you have some more feedback to this PR? |
@stlrnz sorry, I missed the update. I was asking for a functional test case which would have failed before the change and would describe the issue you're solving.
As far as I understand, this ↑ is the root cause which is fixed in
And this ↑ is the actual problem. Is it possible to create a functional test (the one which interacts with a real DB without mocking it) which initializes the schema, applies some changes to it and makes sure only the required changes are applied, not the entire schema is recreated? Ideally, this test should be applicable not only to SQL server but to an abstract DB platform, since we expect the schema updates to work the same on all platforms. |
$platform = $this->createMock(SQLServerPlatform::class); | ||
$connection = $this | ||
->getMockBuilder(Connection::class) | ||
->setMethods(['fetchAll']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO, a test like this doesn't bring much value. Mocking a DB connection means that we know for sure what a DB will return in our case which is often not true, especially given the variety of the supported platforms and their versions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your opinion in general.
However, this tests shows one special thing: The schema name "dbo" is getting ignored. This is done to increase compatibility to the old behaviour.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point. On the other hand, the part about not ignoring the non-dbo
namespace is already covered by the functional test. If ignoring the dbo
namespace is implemented in the same functional test, it will have two benefits:
- The logic of handling
dbo
and non-dbo
namespaces is tested in a cohesive way which make the test much more informative. - The test doesn't need to depend on the DB internal names. It will only test if a table was created in a non-default namespace, it will be returned with the namespace, otherwise, the namespace will be omitted. At this point,
dbo
will remain a platform-specific detail which the test will not care about.
@@ -579,14 +579,83 @@ public function testGetCreateSchemaSQL() | |||
self::assertEquals('CREATE SCHEMA ' . $schemaName, $sql); | |||
} | |||
|
|||
/** | |||
* @group DBAL-42 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Where does the 42 come from?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All tests relatet to column comments in AbstractPlatformTestCase.php are annotated with this group. I thought it would be important to keep it grouped. However, if thats not true I will remove it.
@morozov thank you for your feedback. I got your point and will try to implement a test like this. |
@morozov I pushed another test. I don't think it is 100% what you asked for, but let me explain the initial problem to make clear why it's hard for me to write a test. I use doctrine2 as ORM component inside a symfony application in combination with a SQL Server database. The database is organized using schemas and created by using the command doctrine:schema:create. Everything works fine until this point. So IMO the real problem is in the communication between doctrine2 and dbal. I fixed this by modifying the behaviour of dbal. In my (production) scenario this works very well. I think a good test scenario would consider the following steps:
IMO this is impossible to test in dbal because it involves doctrine2 functionality at least in step 3. |
|
||
$this->_sm->alterTable($tableDiff); | ||
|
||
$tableDetails = $this->_sm->listTableDetails('alter_table'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should alter_table
be testschema.my_table
instead? Otherwise, the last assertion fails since there's no such table at all.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OMG you are right! This assertion only did not fail in my test because one test before exactly this table was is created and modified in dbo.
} | ||
|
||
$myTable = $this->createTestTable('testschema.my_table'); | ||
self::assertTrue(in_array('testschema.my_table', $this->_sm->listTableNames(), true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be self::assertContains()
.
$this->markTestSkipped('Schema definition is not supported by this platform.'); | ||
} | ||
|
||
if (! in_array('testschema', $this->_sm->listNamespaceNames(), true)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of checking if the schema already exists, could the test create the schema unconditionally and drop it on teardown? Otherwise, if a previous test execution didn't exit cleanly (e.g. the schema was created but the table wasn't), the subsequent executions will always fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I trusted on the fact that the database is dropped on the beginning of the test. This would remove the schema, too. However, I'll try to implement it more specific.
@@ -570,6 +572,44 @@ public function testAlterTableScenario() | |||
} | |||
} | |||
|
|||
|
|||
public function testAlterTableInNamespace() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the functional testing could be reduced to making sure that if the platform supports schemas, then the table names returned by listTableNames()
are fully qualified (i.e. are formatted as schema-name.table.name
). The same seems already true for PostgreSQL.
At the same time, for the SQL Server specifically, if a table belongs to the dbo
schema, it should be returned without the schema prefix for BC.
Does it fairly describe the fix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, that's correct. I'll implement it in that way.
@@ -706,12 +726,21 @@ protected function getAlterColumnCommentSQL($tableName, $columnName, $comment) | |||
*/ | |||
protected function getDropColumnCommentSQL($tableName, $columnName) | |||
{ | |||
if (strpos($tableName, '.') !== false) { | |||
list($schema, $table) = explode('.', $tableName); | |||
$schema = $this->quoteStringLiteral($schema); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's better to name the variables as $tableSQL
and $schemaSQL
since they are not names but SQL expressions.
|
||
$myTable = $this->createTestTable('testschema.my_table'); | ||
self::assertTrue(in_array('testschema.my_table', $this->_sm->listTableNames(), true)); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After this point, the rest of test could be removed as not really relevant to the fix. Covering dbo
and non-dbo
tables will be enough to test the changes.
$platform = $this->createMock(SQLServerPlatform::class); | ||
$connection = $this | ||
->getMockBuilder(Connection::class) | ||
->setMethods(['fetchAll']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I see your point. On the other hand, the part about not ignoring the non-dbo
namespace is already covered by the functional test. If ignoring the dbo
namespace is implemented in the same functional test, it will have two benefits:
- The logic of handling
dbo
and non-dbo
namespaces is tested in a cohesive way which make the test much more informative. - The test doesn't need to depend on the DB internal names. It will only test if a table was created in a non-default namespace, it will be returned with the namespace, otherwise, the namespace will be omitted. At this point,
dbo
will remain a platform-specific detail which the test will not care about.
@morozov thank you for your patience and the helpful feedback. I've updated the tests according to your comments. However, there is one nasty thing. The SchemaDiff does not handle dropping of namespaces. So for now, i had to hardcode the SQL. |
{ | ||
parent::tearDown(); | ||
|
||
if ($this->getName() !== 'testTableInNamespace' || ! $this->_sm->getDatabasePlatform()->supportsSchemas()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I understand, this is in order to prevent the exception from dropTable()
if the table doesn't exist. This is not the best solution since the table may not exist if the test failed and didn't manage to create it.
Please use $this->_sm->tryMethod()
and get rid of the conditional logic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, this is done to reduce the attempts to drop the table / the namespace.
However, I think performance is no requirement on functional tests. So, I will remove it.
But there will be a special handling for the DROP SCHEMA
left.
|
||
//foreach ($diff->toSql($this->_sm->getDatabasePlatform()) as $sql) { | ||
// $this->_conn->exec($sql); | ||
//} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove commented out lines.
@stlrnz we don't merge upstream - only rebases are really allowed here. Also, we now merged 7.2-only on AppVeyor, so maybe the results may be different :) |
This is okay. We can fix it later if needed. @Ocramius does schema manager not support dropping schemas by design or it's a missing feature? |
It's something that @deeky666 started handling, but it leads to a lot of tricky scenarios where a DB connection still needs an existing DB (depends on the DB, but take SQLite, for example), so it was likely never improved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🚢 thanks @stlrnz
@stlrnz some failures in https://ci.appveyor.com/project/doctrine/dbal/build/1.0.15/job/rjfn6vs2bmmlox3y - can you check if they are related? |
Meanwhile: restarted the build. |
Thank you for approving! I seems that AppVeyor randomly fails on SQL Server 2008 because of "Cannot open database "doctrine_tests" requested by the login. The login failed.". I cannot see any regularity. So I think its a Problem on AppVeyors side. SQL Server 2012 and 2017 never fails the test. |
I could've sworn I've seen this multiple times but the only other place I can find is build 1.0.22 where the actual error was (see #3061 (comment)):
Builds 1.0.6 and 1.0.36 have the same symptoms. I wouldn't be surprised if this was a manifestation of some other inconsistently reproducible issue (e.g. caused by an unlucky sequence of the tests which is non-deterministic in our case). One more restart fixed the build. |
SQL Server issue should be fixed by #3071. |
I did a rebase & squased those 25 commits. Original tree is kept in my fork for now if needed: https://github.com/Majkl578/doctrine-dbal/tree/stlrnz/mssql-schema |
|
||
$this->_sm->tryMethod('dropTable', 'testschema.my_table_in_namespace'); | ||
|
||
//TODO: SchemaDiff does not drop removed namespaces? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need adressing before merge?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it’s OK.
Table listings do not consider schema names for the SQL Server platform.
This fix adds the schema name to SQL Server >= 2008 table listingss using SQL Sever built-in function SCHEMA_NAME() applied to column uid.
Funtion reference: https://docs.microsoft.com/en-us/sql/t-sql/functions/schema-name-transact-sql
Column reference: https://docs.microsoft.com/en-us/sql/relational-databases/system-compatibility-views/sys-sysobjects-transact-sql
Without this fix doctrine will always try to create the whole schema, even if you only triggered an update. This is because the state of the current schema stored in database is not read correctly.
Note: This fix might also be necessary for SQL Server platforms older than 2008. Unfortunately, I have no possibility to test it.
If anyone wants to backport this fix, be aware that the built-in function SCHEMA_NAME is only available since SQL Server 2008. Maybe the function USER_NAME will help: https://social.msdn.microsoft.com/Forums/sqlserver/en-US/135d51e5-2c22-4ce9-a308-fbb958f3362e/how-to-get-schema-name-from-uid-column-in-dbosysobjects-in-sql-server-2000?forum=transactsql