J'écris une webapp avec Yesod & Persistant. J'ai une base de données SQL avec plusieurs tables, contenant les caractéristiques de mes "projets". J'ai une table principale et pour l'option avec plusieurs valeurs des tables supplémentaires liées à l'id.Construire dynamiquement des requêtes SQL avec Esqueleto et Template Haskell?
L'utilisateur doit pouvoir choisir parmi les caractéristiques qu'il souhaite filtrer et spécifier la valeur du filtre. Si les filtres utilisateur pour le système d'exploitation, la licence et le codage de la requête SQL se présente comme suit:
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
mais je ne veux pas toujours rejoindre toutes les tables, car ce serait très mauvais pour les performances lorsque il y a beaucoup de tables mais l'utilisateur ne filtre que sur quelques tables. Mais je ne veux pas non plus écrire une requête pour chaque combinaison d'entités pouvant faire l'objet d'une requête, car cela signifierait écrire N² des requêtes presque identiques.
Les clauses 'on' et 'where' peuvent être exécutées dynamiquement selon que l'on souhaite filtrer ou non. Mais les jointures sont dans les paramètres de la fonction Lambda. Je n'ai trouvé aucun moyen de construire cette dépendance sur les variables externes. J'ai donc pensé que Template Haskell pourrait faire l'affaire ... J'ai commencé à apprendre TH et à convertir le cœur de la requête en TH. Mais maintenant je suis coincé et je ne sais pas si TH peut m'aider et si c'est la bonne façon?
Voici donc mes progrès avec modèle 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) |]
Je voudrais donc vous aider:
- Can/Dois-je faire avec modèle Haskell?
- Si oui: comment puis-je appeler la fonction foo avec des arguments?
- Sinon: quelle est la bonne solution?
Si vous allez faire avec TH, alors vous n'avez pas besoin d'écrire une fonction unique de TH pour l'ensemble de la fonction 'runquery' - vous pouvez avoir juste une fonction TH qui produit un motif. Donc vous pouvez avoir quelque chose comme 'from $ \ $ (mkInnerJoin [" l "," o "," c "]) -> ...'. TH n'est probablement pas le meilleur moyen de le faire (c'est la meilleure façon de faire peu de choses ...); vous pourriez commencer par définir un type de données pour représenter la structure de votre requête. – user2407038