【发布时间】:2018-02-15 06:08:01
【问题描述】:
我正在编写一个带有 Yesod 和 Persistent 的 webapp。我有一个带有多个表的 SQL 数据库,其中包含我的“项目”的特征。我有一个主表和带有多个值的选项的附加表与 id 链接。
用户应该能够选择他想要过滤的那些特征并指定过滤器值。如果用户过滤操作系统,SQL-Query 的许可证和编码将如下所示:
runquery :: (YesodPersist site, YesodPersistBackend site ~ SqlBackend) =>
String -> String -> String
-> HandlerT site IO [Entity Project]
runquery os license coding = runDB
$ select $ distinct
$ from $ \(p `InnerJoin` pl `InnerJoin` l `InnerJoin` pc
`InnerJoin` c `InnerJoin` o `InnerJoin` po) -> do
on $ p ^. ProjectId ==. pl ^. ProjectLicenseFkProjectId
on $ p ^. ProjectId ==. pc ^. ProjectCodingFkProjectId
on $ p ^. ProjectId ==. po ^. ProjectOsFkProjectId
on $ l ^. LicenseId ==. pl ^. ProjectLicenseFkLicenseId
on $ o ^. OsId ==. po ^. ProjectOsFkOsId
on $ c ^. CodingId ==. pc ^. ProjectCodingFkCodingId
where_ ( o ^. OsName ==. val (Just (pack os)))
where_ ( l ^. LicenseName ==. val (Just (pack license)))
where_ ( c ^. CodingName ==. val (Just (pack coding)))
limit 50
return p
但我不想总是加入所有表,因为当有很多表但用户只过滤少数几个时,这对性能非常不利。但我也不想为每个可查询功能的组合编写一个查询,因为这意味着要编写 N² 个大部分相同的查询。
“on”和“where”子句可以根据我们是否要过滤而动态完成。但是连接在 Lambda 函数的参数范围内。我发现没有办法建立这个依赖于外部变量。
所以我认为 Template Haskell 可能会成功……我开始学习 TH 并将查询的核心转换为 TH。但是现在我被困住了,不确定 TH 是否可以帮助我以及它是否是正确的方法?
以下是我在使用 Template Haskell 方面的进展:
foo os license coding = lamE [pat] (code)
where
p = mkName "p"
po = mkName "po"
pl = mkName "pc"
pc = mkName "pl"
pat = pat' [os, license, coding] [varP po, varP pl, varP pc]
pat' [] [] = varP p
pat' ((Just _):ws) (q:qs) = infixP q (mkName "InnerJoin") (pat' ws qs)
pat' (Nothing:ws) (q:qs) = pat' ws qs
code = do
case os of
Just _ -> [|
on $ $(varE p) ^. ProjectId ==. $(varE po) ^. ProjectOsFkProjectId
|]
Nothing -> [| return () |]
case license of
Just _ -> [|
on $ $(varE p) ^. ProjectId ==. $(varE pl) ^. ProjectLicenseFkProjectId
|]
Nothing -> [| return () |]
case coding of
Just _ -> [|
on $ $(varE p) ^. ProjectId ==. $(varE pc) ^. ProjectCodingFkProjectId
|]
Nothing -> [| return () |]
[| do
limit 50
return $(varE p) |]
所以我想请你帮忙:
- 我可以/应该用 Template Haskell 做这个吗?
- 如果是这样:如何使用参数调用函数 foo?
- 如果不是:正确的解决方案是什么?
【问题讨论】:
-
如果你打算用 TH 来做这件事,那么你不需要为整个
runquery函数编写一个单独的 TH 函数 - 你可以只用一个 TH 函数来产生一个模式.所以你可以有类似from $ \ $(mkInnerJoin ["l", "o", "c"]) -> ...的东西。 TH 可能不是做到这一点的最佳方式(这是做一些事情的最佳方式......);您可以先定义一个数据类型来表示查询的结构。
标签: haskell template-haskell esqueleto haskell-persistent