Haskell Esqueleto project subset of columns to list of custom records

和自甴很熟 提交于 2019-12-06 08:59:32

问题


In all the examples I have seen the results from esqueleto are projected into a list of tuples or to entities records.

For example:

previousLogItems <- select $ from $ \li -> do
        orderBy [desc (li ^. LogItemId)]
        limit 10
        return (li ^. LogItemId, li ^. LogItemTitle)

Is there any way in esqueleto to project a subset of the columns to custom records (different than the entity) instead of tuples? This being done without an extra projection from tuples to custom records.

As an example, let's say that it would be inefficient to get all the data from the database so we only want to project columns WindowTitle and BeginTime from the database into a custom record with adequate names for those columns.

Update

Example of not working code:

data Custom = Custom
  { title :: Text
  , id    :: Int
  } deriving (Eq, Show, Generic)

daily :: Servant.Handler [Custom]
daily = do
  lis <- liftIO $ runDB $
            select $ from $ \li -> do
                    orderBy [desc (li ^. LogItemId)]
                    limit 25
                    return (Custom (li ^. LogItemTitle) (li ^. LogItemId))
  return lis

Error:

• Couldn't match expected type ‘Text’
              with actual type ‘SqlExpr (Database.Esqueleto.Value Text)’
• In the first argument of ‘Custom’, namely ‘(li ^. LogItemTitle)’
  In the first argument of ‘return’, namely
    ‘(Custom (li ^. LogItemTitle) (li ^. LogItemId))’
  In a stmt of a 'do' block:
    return (Custom (li ^. LogItemTitle) (li ^. LogItemId))

Update

Example of not working code:

daily :: Servant.Handler [Custom]
daily = do
  lis <- liftIO $ runDB $
            select $ from $ \li -> do
                    orderBy [desc (li ^. LogItemId)]
                    limit 25
                    return (Custom <$> (li ^. LogItemTitle) <*> (li ^. LogItemId))
  return lis

Error:

• Couldn't match type ‘Database.Esqueleto.Value Text’ with ‘Text’
  Expected type: SqlExpr Text
    Actual type: SqlExpr (Database.Esqueleto.Value Text)
• In the second argument of ‘(<$>)’, namely ‘(li ^. LogItemTitle)’
  In the first argument of ‘(<*>)’, namely
    ‘Custom <$> (li ^. LogItemTitle)’
  In the first argument of ‘return’, namely
    ‘(Custom <$> (li ^. LogItemTitle) <*> (li ^. LogItemId))’


• Couldn't match type ‘Database.Esqueleto.Value (Key LogItem)’
                 with ‘Int’
  Expected type: SqlExpr Int
    Actual type: SqlExpr (Database.Esqueleto.Value (Key LogItem))
• In the second argument of ‘(<*>)’, namely ‘(li ^. LogItemId)’
  In the first argument of ‘return’, namely
    ‘(Custom <$> (li ^. LogItemTitle) <*> (li ^. LogItemId))’
  In a stmt of a 'do' block:
    return (Custom <$> (li ^. LogItemTitle) <*> (li ^. LogItemId))

回答1:


What esqueleto is actually doing here is a little complicated. Here is the type for select:

select :: (SqlSelect a r, MonadIO m) => SqlQuery a -> SqlReadT m [r]

This takes an SqlQuery a (a monad wrapping the value you return), and returns an SqlReadT m [r] (a monad wrapping a list of results). When you return your Custom type, the following happens:

  1. esqueleto converts your a type to persistent's internal SQL representation
  2. persistent executes the query and gets back a results list
  3. esqueleto converts the results list from persistent's internal SQL representation to [r]

To make this work for custom types, you'll need to instantiate SqlSelect, and define conversion functions to and from the persistent types:

data Custom' = Custom' (Value Text) (Value Int)

data Custom = Custom
  { title :: Text
  , id    :: Int
  }

instance SqlSelect Custom' Custom where
  sqlSelectCols esc (Custom' a b) = (mconcat [ta, ", ", tb], va ++ vb)
    where
      (ta, va) = sqlSelectCols esc a
      (tb, vb) = sqlSelectCols esc b
  sqlSelectColCount _ = 2
  sqlSelectProcessRow [PersistText a, PersistInt64 b] = Right $ Custom a b
  sqlSelectProcessRow _ = Left "Error: Incorrect rows to translate to Custom"

(note that I wasn't actually able to test any of the above, so it might have bugs)

One thing to note is that in the above example, the a and r types are not the same (Custom' vs Custom). This is because inside of a select, all of the values you're working with are of type Value, not their actual types.



来源:https://stackoverflow.com/questions/50143910/haskell-esqueleto-project-subset-of-columns-to-list-of-custom-records

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!