Skip to content

Return type for Insert and Select #224

@worm2fed

Description

@worm2fed

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.

  1. 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 TeamRoleR

but 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?

  1. Second concern is about Select
    Yes, it's seems natural to get list on Select query, 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 return Maybe - 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.exists we 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions