Skip to content
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

[persistent-mysql] Add support for conditional copying #693

Merged
merged 17 commits into from
Sep 20, 2017
49 changes: 41 additions & 8 deletions persistent-mysql/Database/Persist/MySQL.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ module Database.Persist.MySQL
, insertOnDuplicateKeyUpdate
, insertManyOnDuplicateKeyUpdate
, SomeField(..)
, copyUnlessNull
, copyUnlessEmpty
) where

import Control.Arrow
Expand All @@ -27,15 +29,16 @@ import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Except (runExceptT)
import Control.Monad.Trans.Reader (runReaderT)
import Control.Monad.Trans.Writer (runWriterT)
import Data.Either (partitionEithers)
import Data.Monoid ((<>))
import Data.Aeson
import Data.Aeson.Types (modifyFailure)
import Data.ByteString (ByteString)
import Data.Either (partitionEithers)
import Data.Fixed (Pico)
import Data.Function (on)
import Data.IORef
import Data.List (find, intercalate, sort, groupBy)
import Data.Monoid (Monoid(..))
import Data.Pool (Pool)
import Data.Text (Text, pack)
import qualified Data.Text.IO as T
Expand Down Expand Up @@ -1034,10 +1037,25 @@ insertOnDuplicateKeyUpdate
insertOnDuplicateKeyUpdate record =
insertManyOnDuplicateKeyUpdate [record] []

-- | This wraps values of an Entity's 'EntityField', making them have the same
-- type. This allows them to be put in lists.
-- | This type is used to determine how to update rows using MySQL's
-- @INSERT ON DUPLICATE KEY UPDATE@ functionality, exposed via
-- 'insertManyOnDuplicateKeyUpdate' in the library.
data SomeField record where
SomeField :: EntityField record typ -> SomeField record
-- ^ Copy the field directly from the record.
CopyUnlessEq :: PersistField typ => EntityField record typ -> typ -> SomeField record
-- ^ Only copy the field if it is not equal to the provided value.

-- | Copy the field into the database only if the value in the
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you bump the cabal version, update changelog and add the @since haddock syntax for all the functions you have added here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing! Is adding a constructor like this a minor version bump?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need to bump it to 2.6.2. More info here: http://www.snoyman.com/blog/2017/06/how-to-send-me-a-pull-request

-- corresponding record is non-@NULL@.
copyUnlessNull :: PersistField typ => EntityField record (Maybe typ) -> SomeField record
copyUnlessNull field = CopyUnlessEq field Nothing

-- | Copy the field into the database only if the value in the
-- corresponding record is non-empty, where "empty" means the Monoid
-- definition for 'mempty'. Useful for 'Text', 'String', 'ByteString', etc.
copyUnlessEmpty :: (Monoid typ, PersistField typ) => EntityField record typ -> SomeField record
copyUnlessEmpty field = CopyUnlessEq field mempty

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both of the introduced functions are only seem to be applicable for insertManyOnDuplicateKeyUpdate. Can you mention that in the haddock comments? Also can you add the example you mentioned here: #693 (comment) and explain it's usecase in a similar way we have done it here: persistent doc

-- | Do a bulk insert on the given records in the first parameter. In the event
-- that a key conflicts with a record currently in the database, the second and
Expand Down Expand Up @@ -1074,15 +1092,30 @@ mkBulkInsertQuery
-> [Update record] -- ^ A list of the updates to apply that aren't dependent on the record being inserted.
-> (Text, [PersistValue])
mkBulkInsertQuery records fieldValues updates =
(q, recordValues <> updsValues)
(q, recordValues <> updsValues <> copyUnlessValues)
where
fieldDefs = map (\x -> case x of SomeField rec -> persistFieldDef rec) fieldValues
updateFieldNames = map (T.pack . escapeDBName . fieldDB) fieldDefs
mfieldDef x = case x of
SomeField rec -> Right (fieldDbToText (persistFieldDef rec))
CopyUnlessEq rec val -> Left (fieldDbToText (persistFieldDef rec), toPersistValue val)
(fieldsToMaybeCopy, updateFieldNames) = partitionEithers $ map mfieldDef fieldValues
fieldDbToText = T.pack . escapeDBName . fieldDB
entityDef' = entityDef records
entityFieldNames = map (T.pack . escapeDBName . fieldDB) (entityFields entityDef')
entityFieldNames = map fieldDbToText (entityFields entityDef')
tableName = T.pack . escapeDBName . entityDB $ entityDef'
copyUnlessValues = map snd fieldsToMaybeCopy
recordValues = concatMap (map toPersistValue . toPersistFields) records
recordPlaceholders = commaSeparated $ map (parenWrapped . commaSeparated . map (const "?") . toPersistFields) records
mkCondFieldSet n _ = T.concat
[ n
, "=COALESCE("
, "NULLIF("
, "VALUES(", n, "),"
, "?"
, "),"
, n
, ")"
]
condFieldSets = map (uncurry mkCondFieldSet) fieldsToMaybeCopy
fieldSets = map (\n -> T.concat [n, "=VALUES(", n, ")"]) updateFieldNames
upds = map mkUpdateText updates
updsValues = map (\(Update _ val _) -> toPersistValue val) updates
Expand All @@ -1095,7 +1128,7 @@ mkBulkInsertQuery records fieldValues updates =
, " VALUES "
, recordPlaceholders
, " ON DUPLICATE KEY UPDATE "
, commaSeparated (fieldSets <> upds)
, commaSeparated (fieldSets <> upds <> condFieldSets)
]

-- | Vendored from @persistent@.
Expand Down