persistent-postgresql 2.12.1.0 → 2.12.1.1
raw patch · 5 files changed
+343/−277 lines, 5 filesdep +hspec-expectations-liftedPVP: major bump suggested
API removals or changes: PVP suggests a major version bump
Dependencies added: hspec-expectations-lifted
API changes (from Hackage documentation)
- Database.Persist.Postgresql: upsertManyWhere :: forall record backend m. (backend ~ PersistEntityBackend record, BackendCompatible SqlBackend backend, PersistEntityBackend record ~ SqlBackend, PersistEntity record, MonadIO m) => [record] -> [HandleUpdateCollision record] -> [Update record] -> [Filter record] -> ReaderT backend m ()
+ Database.Persist.Postgresql: upsertManyWhere :: forall record backend m. (backend ~ PersistEntityBackend record, BackendCompatible SqlBackend backend, PersistEntityBackend record ~ SqlBackend, PersistEntity record, OnlyOneUniqueKey record, MonadIO m) => [record] -> [HandleUpdateCollision record] -> [Update record] -> [Filter record] -> ReaderT backend m ()
- Database.Persist.Postgresql: upsertWhere :: (backend ~ PersistEntityBackend record, PersistEntity record, PersistEntityBackend record ~ SqlBackend, MonadIO m, PersistStore backend, BackendCompatible SqlBackend backend) => record -> [Update record] -> [Filter record] -> ReaderT backend m ()
+ Database.Persist.Postgresql: upsertWhere :: (backend ~ PersistEntityBackend record, PersistEntity record, PersistEntityBackend record ~ SqlBackend, MonadIO m, PersistStore backend, BackendCompatible SqlBackend backend, OnlyOneUniqueKey record) => record -> [Update record] -> [Filter record] -> ReaderT backend m ()
Files
- ChangeLog.md +8/−0
- Database/Persist/Postgresql.hs +110/−80
- persistent-postgresql.cabal +2/−1
- test/PgInit.hs +66/−39
- test/UpsertWhere.hs +157/−157
ChangeLog.md view
@@ -1,5 +1,13 @@ # Changelog for persistent-postgresql +# 2.12.1.1++* [#1235](https://github.com/yesodweb/persistent/pull/1235)+ * `upsertWhere` and `upsertManyWhere` only worked in cases where a `Primary`+ key was defined on a record, and no other uniqueness constraints. They+ have been fixed to only work with records that have a single Uniqueness+ constraint defined.+ ## 2.12.1.0 * Added `upsertWhere` and `upsertManyWhere` to `persistent-postgresql`. [#1222](https://github.com/yesodweb/persistent/pull/1222).
Database/Persist/Postgresql.hs view
@@ -1817,6 +1817,7 @@ , MonadIO m , PersistStore backend , BackendCompatible SqlBackend backend+ , OnlyOneUniqueKey record ) => record -> [Update record]@@ -1825,83 +1826,98 @@ upsertWhere record updates filts = upsertManyWhere [record] [] updates filts --- | Exclude any record field if it doesn't match the filter record. Used only in `upsertWhere` and--- `upsertManyWhere`------ @since 2.12.1.0--- TODO: we could probably make a sum type for the `Filter` record that's passed into the `upserWhere` and--- `upsertManyWhere` methods that has similar behavior to the HandleCollisionUpdate type.-excludeNotEqualToOriginal ::- (PersistField typ- , PersistEntity rec) =>- EntityField rec typ ->- Filter rec-excludeNotEqualToOriginal field =- Filter- { filterField =- field,- filterFilter =- Ne,- filterValue =- UnsafeValue $- PersistLiteral_- Unescaped- bsForExcludedField- }- where- bsForExcludedField =- T.encodeUtf8 $- "EXCLUDED."- <> fieldName field- -- | Postgres specific 'upsertManyWhere'. This method does the following: -- It will insert a record if no matching unique key exists. -- If a unique key exists, it will update the relevant field with a user-supplied value, however, -- it will only do this update on a user-supplied condition. -- For example, here's how this method could be called like such: ----- upsertManyWhere [record] [recordField =. newValue] [recordField /= newValue]+-- upsertManyWhere [record] [recordField =. newValue] [recordField !=. newValue] -- -- Called thusly, this method will insert a new record (if none exists) OR update a recordField with a new value -- assuming the condition in the last block is met. ----- -- @since 2.12.1.0-upsertManyWhere ::- forall record backend m.- ( backend ~ PersistEntityBackend record,- BackendCompatible SqlBackend backend,- PersistEntityBackend record ~ SqlBackend,- PersistEntity record,- MonadIO m- ) =>- -- | A list of the records you want to insert, or update- [record] ->- -- | A list of the fields you want to copy over.- [HandleUpdateCollision record] ->- -- | A list of the updates to apply that aren't dependent on the record being inserted.- [Update record] ->- -- | A filter condition that dictates the scope of the updates- [Filter record] ->- ReaderT backend m ()+-- @since 2.12.1.0+upsertManyWhere+ :: forall record backend m.+ ( backend ~ PersistEntityBackend record+ , BackendCompatible SqlBackend backend+ , PersistEntityBackend record ~ SqlBackend+ , PersistEntity record+ , OnlyOneUniqueKey record+ , MonadIO m+ )+ => [record]+ -- ^ A list of the records you want to insert, or update+ -> [HandleUpdateCollision 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.+ -> [Filter record]+ -- ^ A filter condition that dictates the scope of the updates+ -> ReaderT backend m () upsertManyWhere [] _ _ _ = return () upsertManyWhere records fieldValues updates filters = do- conn <- asks projectBackend- uncurry rawExecute $- mkBulkUpsertQuery records conn fieldValues updates filters+ conn <- asks projectBackend+ let uniqDef = -- onlyOneUniqueDef (Nothing :: Maybe record)+ case entityUniques (entityDef (Nothing :: Maybe record)) of+ [uniq] -> uniq+ _ -> error "impossible due to OnlyOneUniqueKey constraint"+ -- TODO: use onlyOneUniqueDef when it's exported+ uncurry rawExecute $+ mkBulkUpsertQuery records conn fieldValues updates filters uniqDef +-- | Exclude any record field if it doesn't match the filter record. Used only in `upsertWhere` and+-- `upsertManyWhere`+--+-- TODO: we could probably make a sum type for the `Filter` record that's passed into the `upsertWhere` and+-- `upsertManyWhere` methods that has similar behavior to the HandleCollisionUpdate type.+--+-- @since 2.12.1.0+excludeNotEqualToOriginal+ :: (PersistField typ, PersistEntity rec)+ => EntityField rec typ+ -> Filter rec+excludeNotEqualToOriginal field =+ Filter+ { filterField =+ field+ , filterFilter =+ Ne+ , filterValue =+ UnsafeValue $+ PersistLiteral_+ Unescaped+ bsForExcludedField+ }+ where+ bsForExcludedField =+ T.encodeUtf8+ $ "EXCLUDED."+ <> fieldName field+ -- | This creates the query for 'upsertManyWhere'. If you -- provide an empty list of updates to perform, then it will generate -- a dummy/no-op update using the first field of the record. This avoids -- duplicate key exceptions. mkBulkUpsertQuery- :: (PersistEntity record, PersistEntityBackend record ~ SqlBackend)- => [record] -- ^ A list of the records you want to insert, or update+ :: (PersistEntity record, PersistEntityBackend record ~ SqlBackend, OnlyOneUniqueKey record)+ => [record]+ -- ^ A list of the records you want to insert, or update -> SqlBackend- -> [HandleUpdateCollision 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.- -> [Filter record] -- ^ A filter condition that dictates the scope of the updates+ -> [HandleUpdateCollision 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.+ -> [Filter record]+ -- ^ A filter condition that dictates the scope of the updates+ -> UniqueDef+ -- ^ The specific uniqueness constraint to use on the record. Postgres+ -- rquires that we use exactly one relevant constraint, and it can't do+ -- a catch-all. How frustrating! -> (Text, [PersistValue])-mkBulkUpsertQuery records conn fieldValues updates filters =+mkBulkUpsertQuery records conn fieldValues updates filters uniqDef = (q, recordValues <> updsValues <> copyUnlessValues <> whereVals) where mfieldDef x = case x of@@ -1910,7 +1926,8 @@ (fieldsToMaybeCopy, updateFieldNames) = partitionEithers $ map mfieldDef fieldValues fieldDbToText = escapeF . fieldDB entityDef' = entityDef records- conflictColumns = escapeF . fieldDB <$> entityKeyFields entityDef'+ conflictColumns =+ map (escapeF . snd) $ uniqueFields uniqDef firstField = case entityFieldNames of [] -> error "The entity you're trying to insert does not have any fields." (field:_) -> field@@ -1918,33 +1935,46 @@ nameOfTable = escapeE . entityDB $ entityDef' copyUnlessValues = map snd fieldsToMaybeCopy recordValues = concatMap (map toPersistValue . toPersistFields) records- recordPlaceholders = Util.commaSeparated $ map (Util.parenWrapped . Util.commaSeparated . map (const "?") . toPersistFields) records+ recordPlaceholders =+ Util.commaSeparated+ $ map (Util.parenWrapped . Util.commaSeparated . map (const "?") . toPersistFields)+ $ records mkCondFieldSet n _ =- T.concat- [ n- , "=COALESCE("- , "NULLIF("- , "EXCLUDED."- , n- , ","- , "?"- , ")"- , ","- , nameOfTable- , "."- , n- ,")"- ]+ T.concat+ [ n+ , "=COALESCE("+ , "NULLIF("+ , "EXCLUDED."+ , n+ , ","+ , "?"+ , ")"+ , ","+ , nameOfTable+ , "."+ , n+ ,")"+ ] condFieldSets = map (uncurry mkCondFieldSet) fieldsToMaybeCopy fieldSets = map (\n -> T.concat [n, "=EXCLUDED.", n, ""]) updateFieldNames upds = map (Util.mkUpdateText' (escapeF) (\n -> T.concat [nameOfTable, ".", n])) updates updsValues = map (\(Update _ val _) -> toPersistValue val) updates- (wher, whereVals) = if null filters- then ("", [])- else (filterClauseWithVals (Just PrefixTableName) conn filters)- updateText = case fieldSets <> upds <> condFieldSets of- [] -> T.concat [firstField, "=EXCLUDED.", firstField]- xs -> Util.commaSeparated xs+ (wher, whereVals) =+ if null filters+ then ("", [])+ else (filterClauseWithVals (Just PrefixTableName) conn filters)+ updateText =+ case fieldSets <> upds <> condFieldSets of+ [] ->+ -- This case is really annoying, and probably unlikely to be+ -- actually hit - someone would have had to call something like+ -- `upsertManyWhere [] [] []`, but that would have been caught+ -- by the prior case.+ -- Would be nice to have something like a `NonEmpty (These ...)`+ -- instead of multiple lists...+ T.concat [firstField, "=", nameOfTable, ".", firstField]+ xs ->+ Util.commaSeparated xs q = T.concat [ "INSERT INTO " , nameOfTable
persistent-postgresql.cabal view
@@ -1,5 +1,5 @@ name: persistent-postgresql-version: 2.12.1.0+version: 2.12.1.1 license: MIT license-file: LICENSE author: Felipe Lessa, Michael Snoyman <michael@snoyman.com>@@ -68,6 +68,7 @@ , HUnit , hspec >= 2.4 , hspec-expectations+ , hspec-expectations-lifted , monad-logger , QuickCheck , quickcheck-instances
test/PgInit.hs view
@@ -1,58 +1,85 @@-{-# LANGUAGE ScopedTypeVariables, OverloadedStrings #-}+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE ScopedTypeVariables #-} {-# OPTIONS_GHC -fno-warn-orphans #-} -module PgInit (- runConn- , runConn_- , runConnAssert- , runConnAssertUseConf+module PgInit+ ( runConn+ , runConn_+ , runConnAssert+ , runConnAssertUseConf - , MonadIO- , persistSettings- , MkPersistSettings (..)- , BackendKey(..)- , GenerateKey(..)+ , MonadIO+ , persistSettings+ , MkPersistSettings (..)+ , BackendKey(..)+ , GenerateKey(..) - -- re-exports- , module Control.Monad.Trans.Reader- , module Control.Monad- , module Database.Persist.Sql- , module Database.Persist- , module Database.Persist.Sql.Raw.QQ- , module Init- , module Test.Hspec- , module Test.HUnit- , BS.ByteString- , Int32, Int64- , liftIO- , mkPersist, mkMigrate, share, sqlSettings, persistLowerCase, persistUpperCase- , SomeException- , Text- , TestFn(..)- ) where+ -- re-exports+ , module Control.Monad.Trans.Reader+ , module Control.Monad+ , module Database.Persist.Sql+ , module Database.Persist+ , module Database.Persist.Sql.Raw.QQ+ , module Init+ , module Test.Hspec+ , module Test.Hspec.Expectations.Lifted+ , module Test.HUnit+ , BS.ByteString+ , Int32, Int64+ , liftIO+ , mkPersist, mkMigrate, share, sqlSettings, persistLowerCase, persistUpperCase+ , SomeException+ , Text+ , TestFn(..)+ , LoggingT+ , ResourceT+ ) where import Init- ( TestFn(..), truncateTimeOfDay, truncateUTCTime- , truncateToMicro, arbText, liftA2, GenerateKey(..)- , (@/=), (@==), (==@), MonadFail- , assertNotEqual, assertNotEmpty, assertEmpty, asIO- , isTravis, RunDb- )+ ( GenerateKey(..)+ , MonadFail+ , RunDb+ , TestFn(..)+ , arbText+ , asIO+ , assertEmpty+ , assertNotEmpty+ , assertNotEqual+ , isTravis+ , liftA2+ , truncateTimeOfDay+ , truncateToMicro+ , truncateUTCTime+ , (==@)+ , (@/=)+ , (@==)+ ) -- re-exports import Control.Exception (SomeException)-import UnliftIO-import Control.Monad (void, replicateM, liftM, when, forM_)+import Control.Monad (forM_, liftM, replicateM, void, when) import Control.Monad.Trans.Reader import Data.Aeson (Value(..))-import Database.Persist.TH (mkPersist, mkMigrate, share, sqlSettings, persistLowerCase, persistUpperCase, MkPersistSettings(..))+import Database.Persist.Postgresql.JSON () import Database.Persist.Sql.Raw.QQ-import Database.Persist.Postgresql.JSON()+import Database.Persist.TH+ ( MkPersistSettings(..)+ , mkMigrate+ , mkPersist+ , persistLowerCase+ , persistUpperCase+ , share+ , sqlSettings+ ) import Test.Hspec+ (Spec, afterAll_, before, beforeAll, describe, fdescribe, fit, it,+ before_, SpecWith, Arg, hspec)+import Test.Hspec.Expectations.Lifted import Test.QuickCheck.Instances ()+import UnliftIO -- testing-import Test.HUnit ((@?=),(@=?), Assertion, assertFailure, assertBool)+import Test.HUnit (Assertion, assertBool, assertFailure, (@=?), (@?=)) import Test.QuickCheck import Control.Monad (unless, (>=>))
test/UpsertWhere.hs view
@@ -1,178 +1,178 @@-{-# LANGUAGE DataKinds, FlexibleInstances, MultiParamTypeClasses, ExistentialQuantification #-}+{-# LANGUAGE DataKinds #-}+{-# LANGUAGE DerivingStrategies #-}+{-# LANGUAGE ExistentialQuantification #-}+{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE GeneralizedNewtypeDeriving #-}+{-# LANGUAGE MultiParamTypeClasses #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-}+{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-}+{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeFamilies #-} {-# LANGUAGE UndecidableInstances #-}-{-# LANGUAGE DerivingStrategies #-}-{-# LANGUAGE StandaloneDeriving #-} module UpsertWhere where -import Data.List (sort)+import PgInit import Database.Persist.Postgresql-import PgInit share [mkPersist sqlSettings, mkMigrate "upsertWhereMigrate"] [persistLowerCase|- Item- name Text sqltype=varchar(80)- description Text- price Double Maybe- quantity Int Maybe - Primary name- deriving Eq Show Ord+Item+ name Text sqltype=varchar(80)+ description Text+ price Double Maybe+ quantity Int Maybe + UniqueName name+ deriving Eq Show Ord+ |] +wipe :: IO ()+wipe = runConnAssert $ do+ deleteWhere ([] :: [Filter Item])++itDb :: String -> SqlPersistT (LoggingT (ResourceT IO)) a -> SpecWith (Arg (IO ()))+itDb msg action = it msg $ runConnAssert $ void action+ specs :: Spec specs = describe "UpsertWhere" $ do- let item1 = Item "item1" "" (Just 3) Nothing- item2 = Item "item2" "hello world" Nothing (Just 2)- items = [item1, item2]+ let item1 = Item "item1" "" (Just 3) Nothing+ item2 = Item "item2" "hello world" Nothing (Just 2)+ items = [item1, item2] - describe "upsertWhere" $ do- it "inserts appropriately" $ runConnAssert $ do- deleteWhere ([] :: [Filter Item])- upsertWhere item1 [ItemDescription =. "i am item 1"] []- Just item <- get (ItemKey "item1")- item @== item1- it "performs only updates given if record already exists" $ runConnAssert $ do- deleteWhere ([] :: [Filter Item])- let newDescription = "I am a new description"- insert_ item1- upsertWhere- (Item "item1" "i am inserted description" (Just 1) (Just 2))- [ItemDescription =. newDescription] - []- Just item <- get (ItemKey "item1")- item @== item1 { itemDescription = newDescription }+ describe "upsertWhere" $ before_ wipe $ do+ itDb "inserts appropriately" $ do+ upsertWhere item1 [ItemDescription =. "i am item 1"] []+ Just item <- fmap entityVal <$> getBy (UniqueName "item1")+ item `shouldBe` item1+ itDb "performs only updates given if record already exists" $ do+ let newDescription = "I am a new description"+ insert_ item1+ upsertWhere+ (Item "item1" "i am an inserted description" (Just 1) (Just 2))+ [ItemDescription =. newDescription]+ []+ Just item <- fmap entityVal <$> getBy (UniqueName "item1")+ item `shouldBe` item1 { itemDescription = newDescription } - describe "upsertManyWhere" $ do- it "inserts fresh records" $ runConnAssert $ do- deleteWhere ([] :: [Filter Item])- insertMany_ items- let newItem = Item "item3" "fresh" Nothing Nothing- upsertManyWhere- (newItem : items)- [copyField ItemDescription]- []- []- dbItems <- map entityVal <$> selectList [] []- sort dbItems @== sort (newItem : items)- it "updates existing records" $ runConnAssert $ do- deleteWhere ([] :: [Filter Item])- let postUpdate = map (\i -> i { itemQuantity = fmap (+1) (itemQuantity i) }) items- insertMany_ items- upsertManyWhere- items- []- [ItemQuantity +=. Just 1]- []- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort postUpdate- it "only copies passing values" $ runConnAssert $ do- deleteWhere ([] :: [Filter Item])- insertMany_ items- let newItems = map (\i -> i { itemQuantity = Just 0, itemPrice = fmap (*2) (itemPrice i) }) items- postUpdate = map (\i -> i { itemPrice = fmap (*2) (itemPrice i) }) items- upsertManyWhere- newItems- [ - copyUnlessEq ItemQuantity (Just 0)- , copyField ItemPrice- ]- []- []- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort postUpdate- it "inserts without modifying existing records if no updates specified" $ runConnAssert $ do- let newItem = Item "item3" "hi friends!" Nothing Nothing- deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- []- []- []- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : items)- it "inserts without modifying existing records if no updates specified and there's a filter with True condition" $- runConnAssert $ do- let newItem = Item "item3" "hi friends!" Nothing Nothing- deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- []- []- [ItemDescription ==. "hi friends!"]- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : items)- it "inserts without updating existing records if there are updates specified but there's a filter with a False condition" $- runConnAssert $ do- let newItem = Item "item3" "hi friends!" Nothing Nothing- deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- []- [ItemQuantity +=. Just 1]- [ItemDescription ==. "hi friends!"]- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : items)- it "inserts new records but does not update existing records if there are updates specified but the modification condition is False" $- runConnAssert $ do- let newItem = Item "item3" "hi friends!" Nothing Nothing- deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- []- [ItemQuantity +=. Just 1]- [excludeNotEqualToOriginal ItemDescription]- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : items)- it "inserts new records and updates existing records if there are updates specified and the modification condition is True (because it's empty)" $- runConnAssert $ do- let newItem = Item "item3" "hello world" Nothing Nothing- postUpdate = map (\i -> i {itemQuantity = fmap (+ 1) (itemQuantity i)}) items- deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- []- [ItemQuantity +=. Just 1]- []- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : postUpdate)- it "inserts new records and updates existing records if there are updates specified and the modification filter condition is triggered" $- runConnAssert $ do- let newItem = Item "item3" "hi friends!" Nothing Nothing- postUpdate = map (\i -> i {itemQuantity = fmap (+1) (itemQuantity i)}) items- deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- [ - copyUnlessEq ItemDescription "hi friends!"- , copyField ItemPrice- ]- [ItemQuantity +=. Just 1]- [ItemDescription !=. "bye friends!"]- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : postUpdate)- it "inserts an item and doesn't apply the update if the filter condition is triggered" $ - runConnAssert $ do- let newItem = Item "item3" "hello world" Nothing Nothing - deleteWhere ([] :: [Filter Item])- insertMany_ items- upsertManyWhere- (newItem : items)- []- [ItemQuantity +=. Just 1]- [excludeNotEqualToOriginal ItemDescription]- dbItems <- sort . fmap entityVal <$> selectList [] []- dbItems @== sort (newItem : items)+ describe "upsertManyWhere" $ do+ itDb "inserts fresh records" $ do+ insertMany_ items+ let newItem = Item "item3" "fresh" Nothing Nothing+ upsertManyWhere+ (newItem : items)+ [copyField ItemDescription]+ []+ []+ dbItems <- map entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : items)+ itDb "updates existing records" $ do+ let+ postUpdate =+ map (\i -> i { itemQuantity = fmap (+1) (itemQuantity i) }) items+ insertMany_ items+ upsertManyWhere+ items+ []+ [ItemQuantity +=. Just 1]+ []+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` postUpdate+ itDb "only copies passing values" $ do+ insertMany_ items+ let newItems = map (\i -> i { itemQuantity = Just 0, itemPrice = fmap (*2) (itemPrice i) }) items+ postUpdate = map (\i -> i { itemPrice = fmap (*2) (itemPrice i) }) items+ upsertManyWhere+ newItems+ [ copyUnlessEq ItemQuantity (Just 0)+ , copyField ItemPrice+ ]+ []+ []+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` postUpdate+ itDb "inserts without modifying existing records if no updates specified" $ do+ let newItem = Item "item3" "hi friends!" Nothing Nothing+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ []+ []+ []+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : items)+ itDb "inserts without modifying existing records if no updates specified and there's a filter with True condition" $+ do+ let newItem = Item "item3" "hi friends!" Nothing Nothing+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ []+ []+ [ItemDescription ==. "hi friends!"]+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : items)+ itDb "inserts without updating existing records if there are updates specified but there's a filter with a False condition" $+ do+ let newItem = Item "item3" "hi friends!" Nothing Nothing+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ []+ [ItemQuantity +=. Just 1]+ [ItemDescription ==. "hi friends!"]+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : items)+ itDb "inserts new records but does not update existing records if there are updates specified but the modification condition is False" $+ do+ let newItem = Item "item3" "hi friends!" Nothing Nothing+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ []+ [ItemQuantity +=. Just 1]+ [excludeNotEqualToOriginal ItemDescription]+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : items)+ itDb "inserts new records and updates existing records if there are updates specified and the modification condition is True (because it's empty)" $+ do+ let newItem = Item "item3" "hello world" Nothing Nothing+ postUpdate = map (\i -> i {itemQuantity = fmap (+ 1) (itemQuantity i)}) items+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ []+ [ItemQuantity +=. Just 1]+ []+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : postUpdate)+ itDb "inserts new records and updates existing records if there are updates specified and the modification filter condition is triggered" $+ do+ let newItem = Item "item3" "hi friends!" Nothing Nothing+ postUpdate = map (\i -> i {itemQuantity = fmap (+1) (itemQuantity i)}) items+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ [+ copyUnlessEq ItemDescription "hi friends!"+ , copyField ItemPrice+ ]+ [ItemQuantity +=. Just 1]+ [ItemDescription !=. "bye friends!"]+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : postUpdate)+ itDb "inserts an item and doesn't apply the update if the filter condition is triggered" $+ do+ let newItem = Item "item3" "hello world" Nothing Nothing+ insertMany_ items+ upsertManyWhere+ (newItem : items)+ []+ [ItemQuantity +=. Just 1]+ [excludeNotEqualToOriginal ItemDescription]+ dbItems <- fmap entityVal <$> selectList [] []+ dbItems `shouldMatchList` (newItem : items)