yesodweb / yesodweb/persistent
Improvements to connUpsertSql
Open
Nobody has claimed this yet.
- Dominant language
- Haskell
- Stars
- 486
- Forks
- 306
- PR merge metrics
- No merged PRs in 30d
Description
connUpsertSql currently has the following type:
connUpsertSql :: Maybe (EntityDef -> NonEmpty (HaskellName,DBName) -> Text -> Text)
- I don't see why the
HaskellNameis passed to this function. TheHaskellNameof a row seems irrelevant for generating SQL. It appears unused by the Postgres code. - The third argument,
Textis the SQL for updating columns. In the case where there are no updates,upsertBymust fallback todefaultUpsertBy.defaultUpsertBydoes more SQL queries, and is also prone to race conditions. Instead, I believeconnUpsertSqlshould allow for no updates itself, and in that case doON CONFLICT DO NOTHING.
Unfortunately, I think fixing this would require a breaking change.
-- | The slow but generic 'upsertBy' implementation for any 'PersistUniqueRead'.
-- * Lookup corresponding entities (if any) 'getBy'.
-- * If the record exists, update using 'updateGet'.
-- * If it does not exist, insert using 'insertEntity'.
-- @since 2.11
defaultUpsertBy
:: ( PersistEntityBackend record ~ BaseBackend backend
, PersistEntity record
, MonadIO m
, PersistStoreWrite backend
, PersistUniqueRead backend
)
=> Unique record -- ^ uniqueness constraint to find by
-> record -- ^ new record to insert
-> [Update record] -- ^ updates to perform if the record already exists
-> ReaderT backend m (Entity record) -- ^ the record in the database after the operation
defaultUpsertBy uniqueKey record updates = do
mrecord <- getBy uniqueKey
maybe (insertEntity record) (`updateGetEntity` updates) mrecord
where
updateGetEntity (Entity k _) upds =
(Entity k) `liftM` (updateGet k upds)
upsertSql' :: EntityDef -> NonEmpty (HaskellName, DBName) -> Text -> Text
upsertSql' ent uniqs updateVal =
T.concat
[ "INSERT INTO "
, escape (entityDB ent)
, "("
, T.intercalate "," fieldNames
, ") VALUES ("
, T.intercalate "," placeholders
, ") ON CONFLICT ("
, T.intercalate "," $ map (escape . snd) (NEL.toList uniqs)
, ") DO UPDATE SET "
, updateVal
, " WHERE "
, wher
, " RETURNING ??"
]
where
(fieldNames, placeholders) = unzip (Util.mkInsertPlaceholders ent escape)
wher = T.intercalate " AND " $ map (singleClause . snd) $ NEL.toList uniqs
singleClause :: DBName -> Text
singleClause field = escape (entityDB ent) <> "." <> (escape field) <> " =?"
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing connUpsertSql and its uses, then compare the backend behavior with defaultUpsertBy and upsertSql'. Check how the PostgreSQL implementation handles HaskellName and empty update SQL. Done means defining the intended breaking API change and supporting no-update upserts without the generic fallback.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- haskell
- Domain
- backend, databases
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100