-
Notifications
You must be signed in to change notification settings - Fork 292
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
MySQL on duplicate key update #674
MySQL on duplicate key update #674
Conversation
=> [record] -- ^ A list of the records you want to insert, or update | ||
-> [SomeField record] -- ^ A list of the fields you want to copy over. | ||
-> [Update record] -- ^ A list of the updates to apply that aren't dependent on the record being inserted. | ||
-> SqlPersistT m () |
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 function would be used like:
bulkInsertOnDuplicateKeyUpdate
[ {- a big ol' list of records you want to insert/update -} ]
[ SomeField UserName, SomeField UserEmail ] -- this copies the values that are being inserted to existing records
[ UserModified =. now, UserEncounted +=. 1 ] -- this update is performed for any field that matches the inserted records
Travis CI is pointint to some import issues on older versions |
@gregwebs Alright, CI is building now 😄 Any comments on the API or implementation? |
Minor comment: I would prefer the name inserManyOnDuplicateKeyUpdate instead of the current one as that is roughly equivalent to the current naming convention if I'm not wrong. |
👍 Good call @psibi. I've implemented that change. |
-- | ||
-- The third parameter is a list of updates to perform that are independent of | ||
-- the value that is provided. You can use this to increment a counter value. | ||
-- These updates only occur if the original record is present in the database. |
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.
The documentation for the third parameter is kind of confusing to me
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 a little difficult to explain. If you pass something like
insertManyOnDuplicateKeyUpdate _ [SomeField UserName] [UserAge +=. 1]
then it generates the following clause in the Update:
INSERT INTO ... VALUES ...
ON DUPLICATE KEY UPDATE ...
`user`.`name` = VALUES(`user`.`name`)
`user`.`age` = `user`.`age` + 1
So the name gets copied from whatever record was being inserted and had a key collision, and the age gets incremented by 1.
Since the Update
field requires a specific value, that means that we can't make it dependent on the record that we're updating. MySQL would support something like
ON DUPLICATE KEY UPDATE
user.age = VALUES(user.age) + 2
which would add 2 to the record that hit the duplicate key. There's not a good way to make the update have more complicated values, unfortunately, so this feature would be difficult to incorporate as-is.
Multiply -> T.concat [n, "=", n, "*?"] | ||
Divide -> T.concat [n, "=", n, "/?"] | ||
BackendSpecificUpdate up -> | ||
error . T.unpack $ "BackendSpecificUpdate" <> up <> "not supported" |
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 there be a space after BackendSpecificUpdate
and before not supported
?
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.
Probably! It's vendored from part of the update
function in persistent
. I could push it further upstream and refactor that code to use it as well.
commaSeparated :: [Text] -> Text | ||
commaSeparated = T.intercalate ", " | ||
|
||
parenWrapped :: Text -> Text |
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.
These functions make the code much more readable, nice idea
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! I can use them in the other instances of these if you'd like.
commaSeparated = T.intercalate ", " | ||
|
||
parenWrapped :: Text -> Text | ||
parenWrapped = ("(" <>) . (<> ")") |
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.
Not that it's a huge deal since this function is so small, but is point free really better here? It's longer than just parenWrapped t = "(" <> t <> ")"
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.
Whenever I get the chance, I try to make smiley faces in code. Tbh T.concat ["(", t, ")"]
is the tiniest bit cleaner and probably more efficient.
@@ -720,6 +724,7 @@ showColumn (Column n nu t def _defConstraintName maxLen ref) = concat | |||
Just s -> -- Avoid DEFAULT NULL, since it is always unnecessary, and is an error for text/blob fields | |||
if T.toUpper s == "NULL" then "" | |||
else " DEFAULT " ++ T.unpack s | |||
{-# LANGUAGE GADTs #-} |
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.
Is this intentionally here?
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.
oh, no, that is definitely a mistake
LGTM |
awesome! Can this be used to fill out |
Unfortunately not, as this doesn't return the updated records. We'd need something like |
That could make sense to add |
Ooh, I think that would work. I'm not sure it'd be any more efficient than the current default implementation which does a |
@parsonsmatt I just looked on the code. Is there any reason why native upsert for mysql has been done like this instead of going via the typeclass route: https://www.stackage.org/haddock/lts-8.17/persistent-2.6.1/Database-Persist-Sql.html#t:SqlBackend The function |
@psibi The type class function |
@psibi I am in favor of adding |
Yeah, |
@psibi can you merge and release this as is? |
|
MySQL has a native upsert in
INSERT ... ON DUPLICATE KEY UPDATE ...
. This PR introduces that functionality topersistent-mysql
.It also introduces a
bulkInsertOnDuplicateKeyUpdate
function, which has an API I'd like feedback on.