How can I compose Database.Esqueleto queries in a modular way such that after defining a \"base\" query and the corresponding result set, I can restrict the result set by ad
For LIMIT
and COUNT
, hammar's answer is entirely correct so I'll not delve into them. I'll just reiterate that once you use select
you'll not be able to change the query in any way again.
For JOIN
s, currently you are not able to do a INNER JOIN
with a query that was defined in a different from
(nor (FULL|LEFT|RIGHT) OUTER JOIN
s). However, you can do implicit joins. For example, if you have defined:
baseQuery =
from $ \(p `InnerJoin` b) -> do
on (p ^. PersonId ==. b ^. BlogPostAuthorId)
where_ (p ^. PersonName `like` val "J%")
return (p, b)
Then you may just say:
commentsQuery =
from $ \c -> do
(p, b) <- baseQuery
where_ (b ^. BlogPostId ==. c ^. CommentBlogPostId)
return (p, b, c)
Esqueleto then will generate something along the lines of:
SELECT ...
FROM Comment, Person INNER JOIN BlogPost
ON Person.id = BlogPost.authorId
WHERE Person.name LIKE "J%"
AND BlogPost.id = Comment.blogPostId
Not pretty but gets the job done for INNER JOIN
s. If you need to do a OUTER JOIN
then you'll have to refactor your code so that all the OUTER JOIN
s are in the same from
(note that you can do an implicit join between OUTER JOIN
s just fine).
Looking at the documentation and the type of select
:
select :: (...) => SqlQuery a -> SqlPersistT m [r]
It's clear that upon calling select
, we leave the world of pure composable queries (SqlQuery a
) and enter the world of side effects (SqlPersistT m [r]
). So we simply need to compose before we select
.
let baseQuery = from $ \(p `InnerJoin` b) -> do
on (p ^. PersonId ==. b ^. BlogPostAuthorId)
where_ (p ^. PersonName `like` (val "J%"))
return (p,b)
let baseQueryLimited = do r <- baseQuery; limit 2; return r
let countingQuery = do baseQuery; return countRows
somePosts <- select baseQueryLimited
count <- select countingQuery
This works for limiting and counting. I haven't figured out how to do it for joins yet, but it looks like it should be possible.