Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 36 additions & 34 deletions persistent-postgresql/Database/Persist/Postgresql.hs
Original file line number Diff line number Diff line change
Expand Up @@ -1817,6 +1817,7 @@ upsertWhere
, MonadIO m
, PersistStore backend
, BackendCompatible SqlBackend backend
, OnlyOneUniqueKey record
)
=> record
-> [Update record]
Expand All @@ -1825,11 +1826,43 @@ upsertWhere
upsertWhere record updates filts =
upsertManyWhere [record] [] updates filts

-- | 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]
--
-- 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
, 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

-- | 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
-- 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.
excludeNotEqualToOriginal ::
(PersistField typ
Expand All @@ -1854,43 +1887,12 @@ excludeNotEqualToOriginal field =
"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]
--
-- 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
) =>
[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

-- | 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)
:: (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.
Expand All @@ -1906,7 +1908,7 @@ mkBulkUpsertQuery records conn fieldValues updates filters =
(fieldsToMaybeCopy, updateFieldNames) = partitionEithers $ map mfieldDef fieldValues
fieldDbToText = escapeF . fieldDB
entityDef' = entityDef records
conflictColumns = escapeF . fieldDB <$> entityKeyFields entityDef'
conflictColumns = (escapeF . fieldDB <$> entityKeyFields entityDef') ++ concatMap (map (escapeF . snd) . uniqueFields) (entityUniques entityDef')
firstField = case entityFieldNames of
[] -> error "The entity you're trying to insert does not have any fields."
(field:_) -> field
Expand Down
3 changes: 2 additions & 1 deletion persistent-postgresql/test/UpsertWhere.hs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import PgInit
share [mkPersist sqlSettings, mkMigrate "upsertWhereMigrate"] [persistLowerCase|
Item
name Text sqltype=varchar(80)
UniqueName name
description Text
price Double Maybe
quantity Int Maybe
Expand All @@ -44,7 +45,7 @@ specs = describe "UpsertWhere" $ do
let newDescription = "I am a new description"
insert_ item1
upsertWhere
(Item "item1" "i am inserted description" (Just 1) (Just 2))
(Item "item1" "i am an inserted description" (Just 1) (Just 2))
[ItemDescription =. newDescription]
[]
Just item <- get (ItemKey "item1")
Expand Down