1{-# LANGUAGE DataKinds #-} 2{-# LANGUAGE ScopedTypeVariables #-} 3{-# LANGUAGE TypeOperators #-} 4 5module Database.Persist.Class.PersistUnique 6 ( PersistUniqueRead(..) 7 , PersistUniqueWrite(..) 8 , OnlyOneUniqueKey(..) 9 , onlyOneUniqueDef 10 , AtLeastOneUniqueKey(..) 11 , atLeastOneUniqueDef 12 , NoUniqueKeysError 13 , MultipleUniqueKeysError 14 , getByValue 15 , getByValueUniques 16 , insertBy 17 , insertUniqueEntity 18 , replaceUnique 19 , checkUnique 20 , onlyUnique 21 , defaultPutMany 22 , persistUniqueKeyValues 23 ) 24 where 25 26import Control.Monad (liftM) 27import Control.Monad.IO.Class (MonadIO) 28import Control.Monad.Trans.Reader (ReaderT) 29import Data.Function (on) 30import Data.List ((\\), deleteFirstsBy) 31import Data.List.NonEmpty (NonEmpty(..)) 32import qualified Data.List.NonEmpty as NEL 33import qualified Data.Map as Map 34import Data.Maybe (catMaybes) 35import Data.Text (Text) 36import GHC.TypeLits (ErrorMessage(..)) 37 38import Database.Persist.Types 39import Database.Persist.Class.PersistStore 40import Database.Persist.Class.PersistEntity 41 42-- | Queries against 'Unique' keys (other than the id 'Key'). 43-- 44-- Please read the general Persistent documentation to learn how to create 45-- 'Unique' keys. 46-- 47-- Using this with an Entity without a Unique key leads to undefined 48-- behavior. A few of these functions require a /single/ 'Unique', so using 49-- an Entity with multiple 'Unique's is also undefined. In these cases 50-- persistent's goal is to throw an exception as soon as possible, but 51-- persistent is still transitioning to that. 52-- 53-- SQL backends automatically create uniqueness constraints, but for MongoDB 54-- you must manually place a unique index on a field to have a uniqueness 55-- constraint. 56-- 57class (PersistCore backend, PersistStoreRead backend) => 58 PersistUniqueRead backend where 59 -- | Get a record by unique key, if available. Returns also the identifier. 60 -- 61 -- === __Example usage__ 62 -- 63 -- With <#schema-persist-unique-1 schema-1> and <#dataset-persist-unique-1 dataset-1>: 64 -- 65 -- > getBySpjName :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User)) 66 -- > getBySpjName = getBy $ UniqueUserName "SPJ" 67 -- 68 -- > mSpjEnt <- getBySpjName 69 -- 70 -- The above query when applied on <#dataset-persist-unique-1 dataset-1>, will get this entity: 71 -- 72 -- > +----+------+-----+ 73 -- > | id | name | age | 74 -- > +----+------+-----+ 75 -- > | 1 | SPJ | 40 | 76 -- > +----+------+-----+ 77 getBy 78 :: (MonadIO m, PersistRecordBackend record backend) 79 => Unique record -> ReaderT backend m (Maybe (Entity record)) 80 81-- | Some functions in this module ('insertUnique', 'insertBy', and 82-- 'replaceUnique') first query the unique indexes to check for 83-- conflicts. You could instead optimistically attempt to perform the 84-- operation (e.g. 'replace' instead of 'replaceUnique'). However, 85-- 86-- * there is some fragility to trying to catch the correct exception and 87-- determing the column of failure; 88-- 89-- * an exception will automatically abort the current SQL transaction. 90class (PersistUniqueRead backend, PersistStoreWrite backend) => 91 PersistUniqueWrite backend where 92 -- | Delete a specific record by unique key. Does nothing if no record 93 -- matches. 94 -- 95 -- === __Example usage__ 96 -- 97 -- With <#schema-persist-unique-1 schema-1> and <#dataset-persist-unique-1 dataset-1>, 98 -- 99 -- > deleteBySpjName :: MonadIO m => ReaderT SqlBackend m () 100 -- > deleteBySpjName = deleteBy UniqueUserName "SPJ" 101 -- 102 -- The above query when applied on <#dataset-persist-unique-1 dataset-1>, will produce this: 103 -- 104 -- > +-----+------+-----+ 105 -- > |id |name |age | 106 -- > +-----+------+-----+ 107 -- > |2 |Simon |41 | 108 -- > +-----+------+-----+ 109 deleteBy 110 :: (MonadIO m, PersistRecordBackend record backend) 111 => Unique record -> ReaderT backend m () 112 113 -- | Like 'insert', but returns 'Nothing' when the record 114 -- couldn't be inserted because of a uniqueness constraint. 115 -- 116 -- === __Example usage__ 117 -- 118 -- With <#schema-persist-unique-1 schema-1> and <#dataset-persist-unique-1 dataset-1>, we try to insert the following two records: 119 -- 120 -- > linusId <- insertUnique $ User "Linus" 48 121 -- > spjId <- insertUnique $ User "SPJ" 90 122 -- 123 -- > +-----+------+-----+ 124 -- > |id |name |age | 125 -- > +-----+------+-----+ 126 -- > |1 |SPJ |40 | 127 -- > +-----+------+-----+ 128 -- > |2 |Simon |41 | 129 -- > +-----+------+-----+ 130 -- > |3 |Linus |48 | 131 -- > +-----+------+-----+ 132 -- 133 -- Linus's record was inserted to <#dataset-persist-unique-1 dataset-1>, while SPJ wasn't because SPJ already exists in <#dataset-persist-unique-1 dataset-1>. 134 insertUnique 135 :: (MonadIO m, PersistRecordBackend record backend) 136 => record -> ReaderT backend m (Maybe (Key record)) 137 insertUnique datum = do 138 conflict <- checkUnique datum 139 case conflict of 140 Nothing -> Just `liftM` insert datum 141 Just _ -> return Nothing 142 143 -- | Update based on a uniqueness constraint or insert: 144 -- 145 -- * insert the new record if it does not exist; 146 -- * If the record exists (matched via it's uniqueness constraint), then update the existing record with the parameters which is passed on as list to the function. 147 -- 148 -- === __Example usage__ 149 -- 150 -- First, we try to explain 'upsert' using <#schema-persist-unique-1 schema-1> and <#dataset-persist-unique-1 dataset-1>. 151 -- 152 -- > upsertSpj :: MonadIO m => [Update User] -> ReaderT SqlBackend m (Maybe (Entity User)) 153 -- > upsertSpj updates = upsert (User "SPJ" 999) upadtes 154 -- 155 -- > mSpjEnt <- upsertSpj [UserAge +=. 15] 156 -- 157 -- The above query when applied on <#dataset-persist-unique-1 dataset-1>, will produce this: 158 -- 159 -- > +-----+-----+--------+ 160 -- > |id |name |age | 161 -- > +-----+-----+--------+ 162 -- > |1 |SPJ |40 -> 55| 163 -- > +-----+-----+--------+ 164 -- > |2 |Simon|41 | 165 -- > +-----+-----+--------+ 166 -- 167 -- > upsertX :: MonadIO m => [Update User] -> ReaderT SqlBackend m (Maybe (Entity User)) 168 -- > upsertX updates = upsert (User "X" 999) updates 169 -- 170 -- > mXEnt <- upsertX [UserAge +=. 15] 171 -- 172 -- The above query when applied on <#dataset-persist-unique-1 dataset-1>, will produce this: 173 -- 174 -- > +-----+-----+--------+ 175 -- > |id |name |age | 176 -- > +-----+-----+--------+ 177 -- > |1 |SPJ |40 | 178 -- > +-----+-----+--------+ 179 -- > |2 |Simon|41 | 180 -- > +-----+-----+--------+ 181 -- > |3 |X |999 | 182 -- > +-----+-----+--------+ 183 -- 184 -- Next, what if the schema has two uniqueness constraints? 185 -- Let's check it out using <#schema-persist-unique-2 schema-2>: 186 -- 187 -- > mSpjEnt <- upsertSpj [UserAge +=. 15] 188 -- 189 -- This fails with a compile-time type error alerting us to the fact 190 -- that this record has multiple unique keys, and suggests that we look for 191 -- 'upsertBy' to select the unique key we want. 192 upsert 193 :: (MonadIO m, PersistRecordBackend record backend, OnlyOneUniqueKey record) 194 => record 195 -- ^ new record to insert 196 -> [Update record] 197 -- ^ updates to perform if the record already exists 198 -> ReaderT backend m (Entity record) 199 -- ^ the record in the database after the operation 200 upsert record updates = do 201 uniqueKey <- onlyUnique record 202 upsertBy uniqueKey record updates 203 204 -- | Update based on a given uniqueness constraint or insert: 205 -- 206 -- * insert the new record if it does not exist; 207 -- * update the existing record that matches the given uniqueness constraint. 208 -- 209 -- === __Example usage__ 210 -- 211 -- We try to explain 'upsertBy' using <#schema-persist-unique-2 schema-2> and <#dataset-persist-unique-1 dataset-1>. 212 -- 213 -- > upsertBySpjName :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User) 214 -- > upsertBySpjName record updates = upsertBy (UniqueUserName "SPJ") record updates 215 -- 216 -- > mSpjEnt <- upsertBySpjName (Person "X" 999) [PersonAge += .15] 217 -- 218 -- The above query will alter <#dataset-persist-unique-1 dataset-1> to: 219 -- 220 -- > +-----+-----+--------+ 221 -- > |id |name |age | 222 -- > +-----+-----+--------+ 223 -- > |1 |SPJ |40 -> 55| 224 -- > +-----+-----+--------+ 225 -- > |2 |Simon|41 | 226 -- > +-----+-----+--------+ 227 -- 228 -- > upsertBySimonAge :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User) 229 -- > upsertBySimonAge record updates = upsertBy (UniqueUserName "SPJ") record updates 230 -- 231 -- > mPhilipEnt <- upsertBySimonAge (User "X" 999) [UserName =. "Philip"] 232 -- 233 -- The above query will alter <#dataset-persist-unique-1 dataset-1> to: 234 -- 235 -- > +----+-----------------+-----+ 236 -- > | id | name | age | 237 -- > +----+-----------------+-----+ 238 -- > | 1 | SPJ | 40 | 239 -- > +----+-----------------+-----+ 240 -- > | 2 | Simon -> Philip | 41 | 241 -- > +----+-----------------+-----+ 242 -- 243 -- > upsertByUnknownName :: MonadIO m => User -> [Update User] -> ReaderT SqlBackend m (Entity User) 244 -- > upsertByUnknownName record updates = upsertBy (UniqueUserName "Unknown") record updates 245 -- 246 -- > mXEnt <- upsertByUnknownName (User "X" 999) [UserAge +=. 15] 247 -- 248 -- This query will alter <#dataset-persist-unique-1 dataset-1> to: 249 -- 250 -- > +-----+-----+-----+ 251 -- > |id |name |age | 252 -- > +-----+-----+-----+ 253 -- > |1 |SPJ |40 | 254 -- > +-----+-----+-----+ 255 -- > |2 |Simon|41 | 256 -- > +-----+-----+-----+ 257 -- > |3 |X |999 | 258 -- > +-----+-----+-----+ 259 upsertBy 260 :: (MonadIO m, PersistRecordBackend record backend) 261 => Unique record 262 -- ^ uniqueness constraint to find by 263 -> record 264 -- ^ new record to insert 265 -> [Update record] 266 -- ^ updates to perform if the record already exists 267 -> ReaderT backend m (Entity record) 268 -- ^ the record in the database after the operation 269 upsertBy uniqueKey record updates = do 270 mrecord <- getBy uniqueKey 271 maybe (insertEntity record) (`updateGetEntity` updates) mrecord 272 where 273 updateGetEntity (Entity k _) upds = 274 (Entity k) `liftM` (updateGet k upds) 275 276 -- | Put many records into db 277 -- 278 -- * insert new records that do not exist (or violate any unique constraints) 279 -- * replace existing records (matching any unique constraint) 280 -- 281 -- @since 2.8.1 282 putMany 283 :: 284 ( MonadIO m 285 , PersistRecordBackend record backend 286 ) 287 => [record] 288 -- ^ A list of the records you want to insert or replace. 289 -> ReaderT backend m () 290 putMany = defaultPutMany 291 292-- | This class is used to ensure that 'upsert' is only called on records 293-- that have a single 'Unique' key. The quasiquoter automatically generates 294-- working instances for appropriate records, and generates 'TypeError' 295-- instances for records that have 0 or multiple unique keys. 296-- 297-- @since 2.10.0 298class PersistEntity record => OnlyOneUniqueKey record where 299 onlyUniqueP :: record -> Unique record 300 301-- | Given a proxy for a 'PersistEntity' record, this returns the sole 302-- 'UniqueDef' for that entity. 303-- 304-- @since 2.10.0 305onlyOneUniqueDef 306 :: (OnlyOneUniqueKey record, Monad proxy) 307 => proxy record 308 -> UniqueDef 309onlyOneUniqueDef prxy = 310 case entityUniques (entityDef prxy) of 311 [uniq] -> uniq 312 _ -> error "impossible due to OnlyOneUniqueKey constraint" 313 314-- | This is an error message. It is used when writing instances of 315-- 'OnlyOneUniqueKey' for an entity that has no unique keys. 316-- 317-- @since 2.10.0 318type NoUniqueKeysError ty = 319 'Text "The entity " 320 ':<>: 'ShowType ty 321 ':<>: 'Text " does not have any unique keys." 322 ':$$: 'Text "The function you are trying to call requires a unique key " 323 ':<>: 'Text "to be defined on the entity." 324 325-- | This is an error message. It is used when an entity has multiple 326-- unique keys, and the function expects a single unique key. 327-- 328-- @since 2.10.0 329type MultipleUniqueKeysError ty = 330 'Text "The entity " 331 ':<>: 'ShowType ty 332 ':<>: 'Text " has multiple unique keys." 333 ':$$: 'Text "The function you are trying to call requires only a single " 334 ':<>: 'Text "unique key." 335 ':$$: 'Text "There is probably a variant of the function with 'By' " 336 ':<>: 'Text "appended that will allow you to select a unique key " 337 ':<>: 'Text "for the operation." 338 339-- | This class is used to ensure that functions requring at least one 340-- unique key are not called with records that have 0 unique keys. The 341-- quasiquoter automatically writes working instances for appropriate 342-- entities, and generates 'TypeError' instances for records that have 343-- 0 unique keys. 344-- 345-- @since 2.10.0 346class PersistEntity record => AtLeastOneUniqueKey record where 347 requireUniquesP :: record -> NonEmpty (Unique record) 348 349-- | Given a proxy for a record that has an instance of 350-- 'AtLeastOneUniqueKey', this returns a 'NonEmpty' list of the 351-- 'UniqueDef's for that entity. 352-- 353-- @since 2.10.0 354atLeastOneUniqueDef 355 :: (AtLeastOneUniqueKey record, Monad proxy) 356 => proxy record 357 -> NonEmpty UniqueDef 358atLeastOneUniqueDef prxy = 359 case entityUniques (entityDef prxy) of 360 (x:xs) -> x :| xs 361 _ -> 362 error "impossible due to AtLeastOneUniqueKey record constraint" 363 364-- | Insert a value, checking for conflicts with any unique constraints. If a 365-- duplicate exists in the database, it is returned as 'Left'. Otherwise, the 366-- new 'Key is returned as 'Right'. 367-- 368-- === __Example usage__ 369-- 370-- With <#schema-persist-unique-2 schema-2> and <#dataset-persist-unique-1 dataset-1>, we have following lines of code: 371-- 372-- > l1 <- insertBy $ User "SPJ" 20 373-- > l2 <- insertBy $ User "XXX" 41 374-- > l3 <- insertBy $ User "SPJ" 40 375-- > r1 <- insertBy $ User "XXX" 100 376-- 377-- First three lines return 'Left' because there're duplicates in given record's uniqueness constraints. While the last line returns a new key as 'Right'. 378insertBy 379 :: 380 ( MonadIO m 381 , PersistUniqueWrite backend 382 , PersistRecordBackend record backend 383 , AtLeastOneUniqueKey record 384 ) 385 => record -> ReaderT backend m (Either (Entity record) (Key record)) 386insertBy val = do 387 res <- getByValue val 388 case res of 389 Nothing -> Right `liftM` insert val 390 Just z -> return $ Left z 391 392-- | Like 'insertEntity', but returns 'Nothing' when the record 393-- couldn't be inserted because of a uniqueness constraint. 394-- 395-- @since 2.7.1 396-- 397-- === __Example usage__ 398-- 399-- We use <#schema-persist-unique-2 schema-2> and <#dataset-persist-unique-1 dataset-1> here. 400-- 401-- > insertUniqueSpjEntity :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User)) 402-- > insertUniqueSpjEntity = insertUniqueEntity $ User "SPJ" 50 403-- 404-- > mSpjEnt <- insertUniqueSpjEntity 405-- 406-- The above query results 'Nothing' as SPJ already exists. 407-- 408-- > insertUniqueAlexaEntity :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User)) 409-- > insertUniqueAlexaEntity = insertUniqueEntity $ User "Alexa" 3 410-- 411-- > mAlexaEnt <- insertUniqueSpjEntity 412-- 413-- Because there's no such unique keywords of the given record, the above query when applied on <#dataset-persist-unique-1 dataset-1>, will produce this: 414-- 415-- > +----+-------+-----+ 416-- > | id | name | age | 417-- > +----+-------+-----+ 418-- > | 1 | SPJ | 40 | 419-- > +----+-------+-----+ 420-- > | 2 | Simon | 41 | 421-- > +----+-------+-----+ 422-- > | 3 | Alexa | 3 | 423-- > +----+-------+-----+ 424 425insertUniqueEntity 426 :: (MonadIO m 427 ,PersistRecordBackend record backend 428 ,PersistUniqueWrite backend) 429 => record -> ReaderT backend m (Maybe (Entity record)) 430insertUniqueEntity datum = 431 fmap (\key -> Entity key datum) `liftM` insertUnique datum 432 433-- | Return the single unique key for a record. 434-- 435-- === __Example usage__ 436-- 437-- We use shcema-1 and <#dataset-persist-unique-1 dataset-1> here. 438-- 439-- > onlySimonConst :: MonadIO m => ReaderT SqlBackend m (Unique User) 440-- > onlySimonConst = onlyUnique $ User "Simon" 999 441-- 442-- > mSimonConst <- onlySimonConst 443-- 444-- @mSimonConst@ would be Simon's uniqueness constraint. Note that 445-- @onlyUnique@ doesn't work if there're more than two constraints. It will 446-- fail with a type error instead. 447onlyUnique 448 :: 449 ( MonadIO m 450 , PersistUniqueWrite backend 451 , PersistRecordBackend record backend 452 , OnlyOneUniqueKey record 453 ) 454 => record -> ReaderT backend m (Unique record) 455onlyUnique = pure . onlyUniqueP 456 457-- | A modification of 'getBy', which takes the 'PersistEntity' itself instead 458-- of a 'Unique' record. Returns a record matching /one/ of the unique keys. This 459-- function makes the most sense on entities with a single 'Unique' 460-- constructor. 461-- 462-- === __Example usage__ 463-- 464-- With <#schema-persist-unique-1 schema-1> and <#dataset-persist-unique-1 dataset-1>, 465-- 466-- getBySpjValue :: MonadIO m => ReaderT SqlBackend m (Maybe (Entity User)) 467-- getBySpjValue = getByValue $ User "SPJ" 999 468-- 469-- > mSpjEnt <- getBySpjValue 470-- 471-- The above query when applied on <#dataset-persist-unique-1 dataset-1>, will get this record: 472-- 473-- > +----+------+-----+ 474-- > | id | name | age | 475-- > +----+------+-----+ 476-- > | 1 | SPJ | 40 | 477-- > +----+------+-----+ 478getByValue 479 :: forall record m backend. 480 ( MonadIO m 481 , PersistUniqueRead backend 482 , PersistRecordBackend record backend 483 , AtLeastOneUniqueKey record 484 ) 485 => record -> ReaderT backend m (Maybe (Entity record)) 486getByValue record = do 487 let uniqs = requireUniquesP record 488 getByValueUniques (NEL.toList uniqs) 489 490-- | Retrieve a record from the database using the given unique keys. It 491-- will attempt to find a matching record for each 'Unique' in the list, 492-- and returns the first one that has a match. 493-- 494-- Returns 'Nothing' if you provide an empty list ('[]') or if no value 495-- matches in the database. 496-- 497-- @since 2.10.0 498getByValueUniques 499 :: 500 ( MonadIO m 501 , PersistUniqueRead backend 502 , PersistRecordBackend record backend 503 ) 504 => [Unique record] 505 -> ReaderT backend m (Maybe (Entity record)) 506getByValueUniques uniqs = 507 checkUniques uniqs 508 where 509 checkUniques [] = return Nothing 510 checkUniques (x:xs) = do 511 y <- getBy x 512 case y of 513 Nothing -> checkUniques xs 514 Just z -> return $ Just z 515 516-- | Attempt to replace the record of the given key with the given new record. 517-- First query the unique fields to make sure the replacement maintains 518-- uniqueness constraints. 519-- 520-- Return 'Nothing' if the replacement was made. 521-- If uniqueness is violated, return a 'Just' with the 'Unique' violation 522-- 523-- @since 1.2.2.0 524replaceUnique 525 :: ( MonadIO m 526 , Eq (Unique record) 527 , PersistRecordBackend record backend 528 , PersistUniqueWrite backend ) 529 => Key record -> record -> ReaderT backend m (Maybe (Unique record)) 530replaceUnique key datumNew = getJust key >>= replaceOriginal 531 where 532 uniqueKeysNew = persistUniqueKeys datumNew 533 replaceOriginal original = do 534 conflict <- checkUniqueKeys changedKeys 535 case conflict of 536 Nothing -> replace key datumNew >> return Nothing 537 (Just conflictingKey) -> return $ Just conflictingKey 538 where 539 changedKeys = uniqueKeysNew \\ uniqueKeysOriginal 540 uniqueKeysOriginal = persistUniqueKeys original 541 542-- | Check whether there are any conflicts for unique keys with this entity and 543-- existing entities in the database. 544-- 545-- Returns 'Nothing' if the entity would be unique, and could thus safely be inserted. 546-- on a conflict returns the conflicting key 547-- 548-- === __Example usage__ 549-- 550-- We use <#schema-persist-unique-1 schema-1> and <#dataset-persist-unique-1 dataset-1> here. 551-- 552-- This would be 'Nothing': 553-- 554-- > mAlanConst <- checkUnique $ User "Alan" 70 555-- 556-- While this would be 'Just' because SPJ already exists: 557-- 558-- > mSpjConst <- checkUnique $ User "SPJ" 60 559checkUnique 560 :: (MonadIO m 561 ,PersistRecordBackend record backend 562 ,PersistUniqueRead backend) 563 => record -> ReaderT backend m (Maybe (Unique record)) 564checkUnique = checkUniqueKeys . persistUniqueKeys 565 566checkUniqueKeys 567 :: (MonadIO m 568 ,PersistUniqueRead backend 569 ,PersistRecordBackend record backend) 570 => [Unique record] -> ReaderT backend m (Maybe (Unique record)) 571checkUniqueKeys [] = return Nothing 572checkUniqueKeys (x:xs) = do 573 y <- getBy x 574 case y of 575 Nothing -> checkUniqueKeys xs 576 Just _ -> return (Just x) 577 578-- | The slow but generic 'putMany' implemetation for any 'PersistUniqueRead'. 579-- * Lookup corresponding entities (if any) for each record using 'getByValue' 580-- * For pre-existing records, issue a 'replace' for each old key and new record 581-- * For new records, issue a bulk 'insertMany_' 582defaultPutMany 583 ::( PersistEntityBackend record ~ BaseBackend backend 584 , PersistEntity record 585 , MonadIO m 586 , PersistStoreWrite backend 587 , PersistUniqueRead backend 588 ) 589 => [record] 590 -> ReaderT backend m () 591defaultPutMany [] = return () 592defaultPutMany rsD@(e:_) = do 593 case persistUniqueKeys e of 594 [] -> insertMany_ rsD 595 _ -> go 596 where 597 go = do 598 -- deduplicate the list of records in Haskell by unique key. The 599 -- previous implementation used Data.List.nubBy which is O(n^2) 600 -- complexity. 601 let rs = map snd 602 . Map.toList 603 . Map.fromList 604 . map (\r -> (persistUniqueKeyValues r, r)) 605 $ rsD 606 607 -- lookup record(s) by their unique key 608 mEsOld <- mapM (getByValueUniques . persistUniqueKeys) rs 609 610 -- find pre-existing entities and corresponding (incoming) records 611 let merge (Just x) y = Just (x, y) 612 merge _ _ = Nothing 613 let mEsOldAndRs = zipWith merge mEsOld rs 614 let esOldAndRs = catMaybes mEsOldAndRs 615 616 -- determine records to insert 617 let esOld = fmap fst esOldAndRs 618 let rsOld = fmap entityVal esOld 619 let rsNew = deleteFirstsBy ((==) `on` persistUniqueKeyValues) rs rsOld 620 621 -- determine records to update 622 let rsUpd = fmap snd esOldAndRs 623 let ksOld = fmap entityKey esOld 624 let krs = zip ksOld rsUpd 625 626 -- insert `new` records 627 insertMany_ rsNew 628 -- replace existing records 629 mapM_ (uncurry replace) krs 630 631-- | This function returns a list of 'PersistValue' that correspond to the 632-- 'Unique' keys on that record. This is useful for comparing two @record@s 633-- for equality only on the basis of their 'Unique' keys. 634persistUniqueKeyValues :: PersistEntity record => record -> [PersistValue] 635persistUniqueKeyValues = concatMap persistUniqueToValues . persistUniqueKeys 636