-
-
Notifications
You must be signed in to change notification settings - Fork 386
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
Migrations always generated for custom type with DBAL 4 #1441
Comments
@greg0ire I am tagging you as you authored some of the relevant changes and I know you have deep insight into removing comments in DBAL 4. I was digging into the code, the issue lies here: So
This function then calls Now check DBAL 3 code: This uses the comment to deduce the proper type and override whatever type is in DB. Without this now, DBAL considers any unknown DB type to be Now the new schema uses the So, since we removed comments in DBAL 4, how can the |
Please upgrade the ORM to 3.8.5: doctrine/dbal#6423 EDIT: I shouldn't answer so hastily |
@greg0ire I am using DBAL 4.0.4 which is based on 3.8.6 that includes the linked fix. ORM is at 3.2.1 and there is no newer version. And the issue is ONLY with DBAL 4 not DBAL 3 |
@michnovka It seems that when Doctrine reads the schema of your database, the
|
@berkut1 this is indeed a mistaken assumption imo, as getSQLDeclaration not being called is a consequence of not having the proper type. The type has to be known in order for that function to be called. And it is called, just for the new schema. The issue is that oldSchema is created based on database ONLY. It ignores PHP code. And thats good, as the php code defines the new schema, if it took the PHP code as a hint of the type, then migration has no point, as it would be the same type always. I honestly see no other way than to somehow store in DB the name of the custom type which was used. Which is exactly what the DB comment was used for. WDYT? |
@michnovka |
@berkut1 how are they useless when even in 3.9.x there is this code: And this is where it fails, this is where in 3.8.x version it assigned properly custom type, but in 4.0.x it assigns StringType (as the code assigning type based on comment is missing) And again, think about what I observed above - the oldSchema is created based on DB data ONLY. not PHP. Only DB. So if DB has no indication about the type, how can it be properly determined for nonstandard types?
This has no sense for migrations. If I had |
DBAL 3.x should have backward compatibility, that why it can works with DC2Types and without. there was added this https://github.com/doctrine/dbal/blob/893417fee2bc5a94a10a2010ae83cab927e21df3/src/Platforms/AbstractPlatform.php#L108 |
With DBAL 3.8.X and And of course it is. Read again where and why it happens, I explained exactly where the issue lies. oldSchema is created based on DB details only. If we dont use comments (either as they are removed in DBAL 4, or if we disable them manually in DBAL 3) then oldSchema will have no idea what type was used before. Thanks for your interest and will to help. |
Here, |
gives
and
gives
UPD: |
@michnovka it's unclear to me whether you know what platform-aware comparison is. In case you don't, since DBAL 3.2.0, platform-aware comparison allows to compare the generated SQL instead of comparing schemas. This means that 2 different schemas could result in an empty diff, if they result in the same SQL being generated. More on this here: https://www.doctrine-project.org/2021/11/26/dbal-3.2.0.html Sorry if you already know this, it's genuinely hard to tell for me. |
@greg0ire yes, I get this. The problem is that when the comparator tries to compare the schemas, it is working with oldSchema and newSchema. oldSchema is This means that when it tries to compare SQL generated between old and new schemas, it calls And this is expected. As if in DB there is no info about which custom type the column is, how can the Now the new schema is another |
Ah I get it now, thanks for explaining it again. I think this means the issue is that the DBAL does not understand |
yes, it all comes to a simple problem - the oldSchema is generated using ONLY DB introspection. And if DB contains no info about which type was used, then it cannot call proper And no, the problem does not simply limit to enum. I can make an example with another custom type which will have the same issue without enums. ALL custom types have this issue in fact, i.e. all custom types with custom |
Here, Doctrine is reading your database. Does it also read the type as a string here? |
This looks related: doctrine/dbal#5308 (comment) |
No, here it fetches it from DB like this:
But this is just one step before And @greg0ire this is where the issue lies: $column = new Column($tableColumn['field'], Type::getType($type), $options); specifically the |
Ah, I understand now. It calls mapping_types:
enum: ancestor_enum
types:
ancestor_enum : 'App\Model\AncestorEnumType' Unfortunately, I can't help with how to do this without Symfony. UPD: |
@greg0ire as a temporary workaround, how can I tell migration to ignore certain columns? Thanks! |
Maybe you can use asset filtering, but I don't think it works at the column level. |
So the issue is this:
And on the new schema, we get correct type for column and call its These do not match. Proposed solution: Why not leverage I dont understand why @greg0ire WDYT? |
Sounds good, feel free to give it a try, and see if anything breaks 👍 |
I tried to go this way and I am afraid this would create tons of unexpected issues. There are specific test cases that ensure that
so for whatever reason this is considered important. Even the comment in the So maybe a solution would be to add comments back so that the type can be deduced properly? |
I don't think that's the direction we want to go, no. Re-reading the cookbook you mentioned I see
It seems like that might have been fixed in doctrine/dbal#5224, which might make solution 1 the way to go here. |
@michnovka As I mentioned in my comment on the instructions, I solved the problem for myself in this way: doctrine:
dbal:
mapping_types:
inet: my_inet
types:
my_inet: { class: 'App\Model\InetType' } I want to clarify, as stated in the instructions at https://www.doctrine-project.org/projects/doctrine-orm/en/3.2/cookbook/mysql-enums.html, it is recommended to do it like this $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string'); but this solution works only for DBAL3. For DBAL4, you need to refer not to However, if you haven't tried this solution at all, you can first refer to |
@berkut1 Thanks for the suggestion, but this is not a solution for my case. It works in your case when you have 1:1 mapping between DB type But in my case, every |
Yes, I understand, which is why I initially suggested creating an abstract Enum class and registering it, then inheriting from it for custom types. In other words, you need to make Doctrine understand that it has a base Enum class for comparison, with which it will compare all the descendants of this class. |
But I am a bit shocked that you suggested it. We've been asking to keep a way for us to implement our own support for ENUM, and you suggest the exact opposite. I feel I am not being heard at all here. I get it that DBAL has no interest to support it out-of-the-box. That's fair. But it feels like you are actively trying to prevent us to achieve our goal, whereas there could be a reasonable compromise to be made, but you don't seem to consider them seriously at all. I would like to hear the opinion of other people too. Someone with much more insights that the both of us, such as @greg0ire or @derrabus ? would you accept a PR that avoid truncating type declaration for ENUM and SET ? |
@PowerKiKi Not exactly. As far as I understand, the main contributors are strongly against bringing back comment hints, or something similar. If they're against it, then why keep non-functional pieces of code at all? |
@berkut1 you cannot remove @PowerKiKi I think this thread has the underlying issue described to the very detail:
Any type which is not natively supported by DBAL will face this issue. We have a one-way function from PHP code -> DB representation, and we have no way to reverse this with accuracy, because
With ENUMs specifically it is further complicated by the fact that basically every
There is no way to specify But that still assumes that I do understand the reluctancy to bring back comments, but when the decision was made, I do not believe enough thought was given to the real implications. This is not just ENUMs. This is ANY nonstandard DBAL types. INET, SET etc. Exotic ones, yes, but 4.x breaks them. While I agree to discourage from using typehints, I think this option should be brought back for the few cases, where without them 4.x is broken without a fix where 3.x worked just fine. |
Maybe we need some kind of verbatim type that all unknown database types are being mapped to for the sole purpose of making two introspections comparable. |
@derrabus as long as |
Sure that needs to be solved along with it. |
It sounds like a way to say to DBAL "stop your magic, I'll handle everything from here". That's exactly what comment did. I suppose we can re-create the same concept with a different API. As long as I can use that new hypothetical API to implement a custom type to support ENUM and SET, I'd be happy. But at the same time, I can't help to wonder: why not just restore comments ? its code is simple, the concept is well-known and proven to work even (especially!) from user code. The only rationale we were given for comment removal was "DBAL core does not need it anymore". But I think we've proven that there are reasonable use-cases, implemented outside DBAL core, that requires it. What is the reason to block restoring comments ? |
@PowerKiKi comments were solving some cases, and were creating the same kind of issue (endless change) in other cases. |
@stof @derrabus should we use |
@derrabus, I'd be willing to help introduce a "verbatim type", but I'm not quite sure what you mean codewise. Would you be able to write some pseudo-code to help me understand ? or would you prefer to prototype it yourself ? |
Running into similar issues after upgrading. We had a DateTimeMicroseconds type as based upon various code snippets in doctrine/dbal#2873, but instead of replacing the datetime type completely, we complemented it so that we could have some columns that were just seconds-based, and other columns that supported microseconds. Now that DBAL doesn't support comments, this behaviour has been broken.
Similarly I could try to override The remaining potential options I have identified are: The comments stuff was useful. In my particular case, I could make the type decision if I had access to more column meta - via hypothetical method changes such as (N.B. Not a Doctrine expert - I only know what I've gleamed from reading comments here and digging through the code. If I'm missing the elephant in the room and what I'm trying to do is actually possible, please let me know! :D) |
If you're motivated to work on this, please go ahead. I will support you if I can. The idea is that during introspection DBAL maps column to this verbatim type if it cannot map it to a registered DBAL type. And since your custom enum type and the verbatim type should ideally generate the same SQL, no migration is generated.
Multiple attempts have been made to support |
Well - that's kind one of the angles I was looking at - having the DateTimeType react appropriately depending upon the precision, but The next hop up from that was where types are determined - i.e. So my understanding ended up being that implementing it would require a change in the API / interfaces. |
I think we need to introduce a new type that will just store the database data type as it is. For example, right now, if a type like https://github.com/doctrine/dbal/blob/4.1.x/src/Schema/MySQLSchemaManager.php#L121 It turns into just $conn->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'verbatim'); And then in the custom type, just do something like this: class EnumType extends VerbatimType
{
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
return "enum('apple','orange','pear')";
}
} VerbatimType probably will looks like this: class VerbatimType extends Type
{
public function getSQLDeclaration(array $column, AbstractPlatform $platform): string
{
return $column['сolumnDefinitionAsItIs']; //this seems not right :)
}
} However, we need to make it work for any unknown types, and this might require a lot of research on how different DBMSs store data types that are unknown to Doctrine, and whether we can simply save all of them in a P.S Of course, my idea might be wrong. UPD: doctrine:
dbal:
mapping_types:
inet: alias_name
types:
alias_name: { class: 'App\Entity\InetType' } Maybe, with a verbatim type, we can skip |
Setting the Enum case aside for a moment (as I think it is muddying the water WRT what I think is the underlying issue)… As of DBAL 4, the representation in the database is no longer an exact match for the schema definition from code, so when the comparator makes its comparison, there can be a difference even when nothing has changed. Take the example of a custom type that is an RGB hex value represented by an object with 3 integer properties. class RGB {
public int $red;
public int $green;
public int $blue;
} This is mapped to a varchar in the database using a custom type: e.g. At runtime, DBAL correctly maps the varchar data from the database to my custom RGB object using When using the comparator to look for schema updates:
Prior to the removal of database comments, the first step above would have resolved the column to the correct type. |
No. As long as both types generate the same DDL statements, they are to be be considered equal. |
But the comparator isn't comparing DDL statements, it's comparing i.e. it's Doctrine\DBAL\Types\StringType and not "VARCHAR" if this were comparing DDL statements, it would be working fine |
This is exactly the cause of my problems. My custom type works fine in use, but the comparator thinks that something has changed and causes schema-update / migration diff to perpetually recommend an "ALTER TABLE" on said column. Here I've dumped the $oldSchema and $newSchema when running It thinks they are different because it has two different DBAL types, whereas if it compared raw column types, it'd realise that the schema wants a DATETIME, and the database already has a DATETIME, and all would be fine! :) |
Yes, it does. Only if two columns are considered to be not equal, a |
Thanks @derrabus - that gives me somewhere further to dig to try to understand what is going on! :) |
I've looked into this further - thanks for the pointer! - and it's comparing the DDL statements generated based by the resolved Doctrine DBAL types. So essentially in my case: It's then raising a ColumnDiff as you suggested. However, what is actually in the database is indeed a Ultimately I've tracked this down to public function getDateTimeTypeDeclarationSQL(array $column): string
{
if (isset($column['version']) && $column['version'] === true) {
return 'TIMESTAMP';
}
return 'DATETIME';
} It therefore seems I could override this method to resolve my comparator woes? I could also make a quick patch for this issue - it looks pretty trivial - if this sounds correct to you? Also it's a bit odd, in that it's not comparing the actual old column definition, but rather looking at the old column definition, parsing it into bits, deciding upon a DBAL Type class, and then asking that Type class to generate what it thinks the column definition should look like based upon those parsed bits, and then using that. I guess there are nuanced reasons to do that, but on the face of it, it does seem rather around the houses. |
That could probably work.
You can have a look at the previous attempt (doctrine/dbal#5961) and try to complete it. The workaround that you need might be trivial. However, properly supporting DATETIME with variable precision is not. You could also open an issue on the DBAL repository first where we discuss the obstacles that you might run into. |
I've looked at it, and it looks like the original author has put a lot of work into this and as far as I can see has resolved every change request. I can see the entitled muppet (that's the polite form) throwing a spanner in the works there with his rudeness, but what further works needs to be done to it / how can people help to progress it? |
It has to be ported to the 4.2 branch at least. A lot of time has passed since then, I don't recall the remaining blockers right now. But please, let's discuss this on a separate issue on the DBAL repository. This is getting more and more off-topic. |
@derrabus What do you think about this idea for implementing the verbatim type #1441 (comment)? Or do you have a different approach in mind? |
@berkut1 There should be no need to register or extend that verbatim type. It is used only during introspection, if a DB column type is encountered that DBAL has no mapping for. |
This comment was marked as resolved.
This comment was marked as resolved.
@b3n3d1k7 No, it doesn't. Please read the full thread before commenting, sorry. |
We've had this issue for ages with our own custom enums and solved it with a patch for MySQLSchemaManager.php: diff --git a/src/Schema/MySQLSchemaManager.php b/src/Schema/MySQLSchemaManager.php
index bd383b012..556e3df4e 100644
--- a/src/Schema/MySQLSchemaManager.php
+++ b/src/Schema/MySQLSchemaManager.php
@@ -278,6 +278,8 @@ class MySQLSchemaManager extends AbstractSchemaManager
$column = new Column($tableColumn['field'], Type::getType($type), $options);
+ $column->setPlatformOption('hcCurrentTableColumnType', $tableColumn['type']);
+
if (isset($tableColumn['characterset'])) {
$column->setPlatformOption('charset', $tableColumn['characterset']);
} Then in our type we simply do this to return the actual current SQL declaration for comparism: public function getSQLDeclaration(array $column, AbstractPlatform $platform): string {
// This is patched at the end of MySQLSchemaManager::_getPortableTableColumnDefinition to allow diffs on enums:
if (isset($column['hcCurrentTableColumnType'])) {
return $column['hcCurrentTableColumnType'];
}
//... return your `enum(...)`, but this must be lower case for comparism! In case this helps anyone. |
Bug Report
Summary
I have issue with DBAL 4 and custom type, the migrations keep getting generated again and again. Furthermore, the down migration looks totally bogus. This is possibly related to #1435
I know that DBAL 4 dropped requiresSqlHint (in doctrine/dbal#5107 , afterwards some issues were found and fixed - doctrine/dbal#6257 )
So when I am using custom type, I expect the first migration diff to drop the
DC2Type
comments. However my tables have these fields already dropped and yet the migration is being generated.I then have my entity as
and in
Kernel.php
I set up type mapping insideKernel::process()
:Now I know that the types are assigned correctly, as migrations generate up like this:
but it is generated ALWAYS.
and the
down()
migration looks even weirder:Everything works fine with DBAL 3, which uses SQL comments
The text was updated successfully, but these errors were encountered: