-
Notifications
You must be signed in to change notification settings - Fork 296
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
Postgresql constraint=
attribute support
#979
Postgresql constraint=
attribute support
#979
Conversation
, "AND constraint_name=?" | ||
] | ||
let ref = refName tableName' cname | ||
let sql = T.concat ["SELECT DISTINCT " |
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.
Due to the way postgresql stores the column metadata, I had to useDISTINCT
here in order to avoid duplicate rows being returned for composite keys.
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.
Looks great! Can you add a test to verify that it works?
case find (\f -> fk == foreignConstraintNameDBName f) fdefs of | ||
Just _ -> c { cReference = Nothing } | ||
Nothing -> c | ||
Nothing -> c) xs,ys) |
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.
On a purely stylistic note, I'd rather see this written as another named function so this definition could be:
excludeForeignKeys (xs, ys) = (map excludeForeignKey xs, ys)
excludeForeignKey c = case cReference f of
....
@@ -908,7 +920,7 @@ getAddReference :: [EntityDef] -> DBName -> DBName -> DBName -> Maybe (DBName, D | |||
getAddReference allDefs table reftable cname ref = | |||
case ref of | |||
Nothing -> Nothing | |||
Just (s, _) -> Just $ AlterColumn table (s, AddReference (refName table cname) [cname] id_) | |||
Just (s, constraintName) -> Just $ AlterColumn table (s, AddReference constraintName [cname] id_) |
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.
Nice
For sure! |
describe "custom constraint used in migration" $ do | ||
it "alter table uses custom constraint name" $ runDb $ do | ||
ms <- getMigration customConstraintMigrate | ||
let alter = find (== alterQuery) ms |
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 seems a bit hacky, but I couldn't think of a better way to ensure that the migration uses the correct constraint name. Below, we check the contraints table to ensure it is created with the correct name, but if that fails, it is not clear why. I figured the most common reason for a failure would be the alter table query using the wrong name.
@parsonsmatt Thoughts?
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 ended up removing this test case as it makes the tests non-idempotent. Now we just ensure the constraint exists after running the migration.
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.
Awesome!! One more change though - this is a modification to persistent-postgresql
, not persistent
, and the version bump and changelog entry should point there and not in persistent
.
persistent/ChangeLog.md
Outdated
## 2.10.5 | ||
|
||
* Added support for the `constraint=` attribute to the Postgresql backend. [#979](https://github.com/yesodweb/persistent/pull/979) | ||
|
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 go in persistent-postgresql/changelog.md
persistent/persistent.cabal
Outdated
@@ -1,5 +1,5 @@ | |||
name: persistent | |||
version: 2.10.4 | |||
version: 2.10.5 |
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 go in persistent-postgresql/persistent-postgresql.cabal
This reverts commit 142856d.
Once CI is passed I'll merge and release. Thanks so much! |
Awesome. Thank you for the prompt replies! |
Oh, heads up that this change seems to break certain foreign key setups. Still investigating, but for example for this table:
the query returns:
|
@MaxGabriel Good catch, I should be able to look into this tonight. |
@robbassi Were you able to look into it? If not, it's on my list today and I'm happy to dig in/help take a look. |
@jkeuhlen Ok great. I'm planning to work on it today, but any help would be appreciated! |
[] -> return Nothing | ||
[[PersistText table, PersistText constraint]] -> | ||
return $ Just (DBName table, DBName constraint) | ||
_ -> error "Postgresql.getColumn: error fetching constraints" |
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.
@robbassi So this is the place where the error is thrown. Is there a reason you assume a single constraint per table here and throw the error? A simple idea of a schema that this breaks is a table that records sales like:
Sales
buyer PersonId
seller PersonId
Person
name Text
@jkeuhlen I beleive the issue here is with multiple foreign key contraints for a single column, not the table. This was actually ported from the MySQL implementation (#937) for custom constraint names. I just verified that this issue also exists in the MySQL backend. To fix this we'll need to come up with a better way of fetching the column metadata when building migrations, let me know if you have any ideas. |
@robbassi Yeah that makes sense. I was just writing a test for this case, and it was surprised that it passed (since I was testing the table not the column). Would it be sufficient to change the definition of
Specifically, the column reference could be changed to |
@jkeuhlen I think something like that should work. Though, I'm trying to better understand the use case here. I don't believe persistent can generate tables like this through migrations. Which leads me to believe that this was some pre-exisiting table that @MaxGabriel was trying to run a migration against. @MaxGabriel could you let us know if this table was created through a persistent migration? |
oh @robbassi I work with @MaxGabriel so I can confirm (and test with his original issue). We aren't creating any of these through the persistent migrations. But we do check our table definitions against the models with persistent and that's where we run into the problem. |
@jkeuhlen Ok cool. So when you say you check the table definitions against the model, is that essentially running a migration and seeing what changes? |
@robbassi Exactly, we run a migration and capture the output, then do some tweaking internally since we know of a few mismatches, then fail if the generated migrations don't match the models (minus our expected mismatches). |
And the failure occurs during migration generation at the line I pointed out here: https://github.com/yesodweb/persistent/pull/979/files#r363880641 |
@jkeuhlen Ok got it. I'm going through the code now to see if there is an easier fix, otherwise I'll look into modifying the |
@robbassi Awesome, thanks! Let me know if I can help in any way (failing test cases maybe?). |
@jkeuhlen That would be great! I created a branch for the fix here: robbassi/persistent/tree/fix-migration-multiple-fk |
@robbassi So I have a pretty reliable way to make things break, but I don't really understand why they are breaking. Using this schema: share [mkPersist sqlSettings, mkMigrate "customConstraintMigrate"] [persistLowerCase|
CustomConstraint1
some_field Text
deriving Show
CustomConstraint2
cc_id CustomConstraint1Id constraint=custom_constraint
deriving Show
CustomConstraint3
cc_id1 CustomConstraint1Id constraint=cc_id1_custom_constraint1
cc_id2 CustomConstraint1Id constraint=cc_id2_custom_constraint2
deriving Show
|] This all seems to work fine actually. However, when the constraints aren't specified in the schema, things go awry. In this case, I just comment out the it "allows constraints not expressed in persistent" $ runDb $ do
runMigration customConstraintMigrate
rawExecute "ALTER TABLE \"custom_constraint3\" ADD CONSTRAINT \"cc_id1_custom_constraint1\" FOREIGN KEY(\"cc_id1\") REFERENCES \"custom_constraint1\"(\"id\")" []
rawExecute "ALTER TABLE \"custom_constraint3\" ADD CONSTRAINT \"cc_id2_custom_constraint1\" FOREIGN KEY(\"cc_id2\") REFERENCES \"custom_constraint1\"(\"id\")" []
vals <- getMigration customConstraintMigrate
liftIO $ print vals
pure () Leads to:
Any idea what's going on here? It seems like the manual (N.B. this is using the slightly improved error message from https://github.com/yesodweb/persistent/compare/postgresFixConstraint) |
@jkeuhlen It looks like |
@robbassi Yeah, it still shows up with an entirely fresh DB (I dropped |
@robbassi |
@jkeuhlen Ah, so if you changed:
to something like:
That would explain it. |
@robbassi Yup. I realized after the fact that persistent adds the first foreign key automatically. I think this test case is a good illustration of the problem then, even if it is a trivial example. I'll PR it over to your branch. The more complicated example would be like the one Max mentioned before where you have two different foreign keys on the same field because one is in a compound key. |
@jkeuhlen I updated my branch with the fix. I may still clean things up a bit, but the issue should be fixed now. I verified your test case passes now, but could you try out the original use case just to make sure we caught everything? |
@robbassi The original use case passes now, thanks so much for fixing this! |
@jkeuhlen Glad I could help! I'll make a PR for this shortly, would you mind creating an issue for this? Just want to make sure this gets tracked properly. |
This PR adds support for the
constraint=
attribute in the Postgresql backend. It addresses part of issue #939. Unfortunately, I wasn't able to figure out how to accomplish this with sqlite, and I will make another PR for that once I get a better understanding of sqlite internals.Before submitting your PR, check that you've:
After submitting your PR: