-
Notifications
You must be signed in to change notification settings - Fork 42
Description
Hello.
I started to use rel8 not so long time ago and there is still a lot of new for me. Maybe I just didn't found an answer in ;documentation or maybe there is no such option for now.
But I have feeling that what I do should be in a bit different way. So, hope you can make it clear for me.
- Often I want to insert a single row to table and I want my function to return that created row:
createTeamRoleQ :: TeamR -> TeamRoleName -> Sql.Transaction TeamRoleRbut by default what I can get is
createTeamRoleQ :: TeamR -> TeamRoleName -> Sql.Transaction [TeamRoleR]so, what I usually do - is just applying listToMaybe to result like this:
createTeamRoleQ :: TeamR -> TeamRoleName -> Sql.Transaction (Maybe TeamRoleR)
createTeamRoleQ Team{tTeamPk} name =
fmap listToMaybe . Sql.statement () . Sql.insert $
Sql.Insert
{ into = teamRoleSchema
, rows =
Sql.values
[ TeamRole
{ trTeamRolePk = Sql.nextPk teamRoleSchema
, trTeamPk = Sql.lit tTeamPk
, trName = Sql.lit name
}
]
, onConflict = Sql.Abort
, returning = Sql.Projection id
}I need Maybe here, because createTeamRoleQ is part of transaction:
createTeam
:: (WithDb m, WithError m)
=> AccountPk
-> TeamScopePk
-> TeamName
-> m TeamR
createTeam aPk teamScopePk name =
-- throw error on top level of computation
throwOnNothingM Logger.Error (ApiError "can't get team in response")
. Db.runTransaction
$ runMaybeT
( do
-- create team
team <- MaybeT insertTeam
-- create default team roles
roles <- mapM (MaybeT . createTeamRoleQ team) defaultTeamRoleNames
guard $ length defaultTeamRoleNames == length roles
-- assign creator to created roles
assignees <- mapM (MaybeT . assignToTeamRoleQ aPk) roles
guard $ length defaultTeamRoleNames == length assignees
pure team
)
-- rollback transaction in case on any step Nothing occurs
>>= maybe (Sql.condemn >> pure Nothing) (pure . pure)
where
insertTeam :: Sql.Transaction (Maybe TeamR)
insertTeam =
fmap listToMaybe
. Sql.statement ()
. Sql.insert
$ Sql.Insert
{ into = teamSchema
, rows =
Sql.values
[ Team
{ tTeamPk = Sql.nextPk teamSchema
, tTeamScopePk = Sql.lit teamScopePk
, tInitiatorPk = Sql.lit aPk
, tName = Sql.lit name
}
]
, onConflict = Sql.Abort
, returning = Sql.Projection id
}In this example I apply listToMaybe en each Insert statement to convert list to Maybe value, and in case on some step there is Nothing - I rollback whole transaction.
In terms of types it seems correct, but in fact I want to get not even Maybe TeamRoleR but single TeamRoleR, this way I do not need to apply all this listToMaybe and there is no need in MaybeT.
I've tried to replace Sql.values with pure, but I still have list in result type.
What do you think about it?
- Second concern is about
Select
Yes, it's seems natural to get list onSelectquery, but there is some cases:
- when we use
Sql.limit 1
This case we want to get single value, so more natural here will be to returnMaybe- I solved it by defining helper
selectOne
:: forall exprs a
. (Serializable exprs a)
=> Query exprs
-> Statement () (Maybe a)
selectOne = fmap listToMaybe . Sql.limit 1
{-# INLINE selectOne #-}- when we use
Sql.existswe expect that
Checks if a query returns at least one row.
and then to execute it we use Sql.select again and receive list of Bool
I solved it by defining another helper:
-- | Helper to check existence.
selectExists :: Query exprs -> Statement () Bool
selectExists = fmap Relude.and . Sql.select . Sql.exists
{-# INLINE selectExists #-}Maybe I'm missing something but I want to understand whether I'm using your library in right way. Thanks.