Ich schreibe eine Webapp mit Jessod & Persistent. Ich habe eine SQL-Datenbank mit mehreren Tabellen, die Eigenschaften meiner "Projekte" enthalten. Ich habe eine Haupttabelle und für die Option mit mehreren Werten zusätzliche Tabellen, die mit der ID verknüpft sind.Dynamisch SQL-Abfragen mit Esqueleto und Template Haskell erstellen?
Der Benutzer sollte in der Lage sein, die Eigenschaften auszuwählen, die er filtern und den Filterwert angeben möchte. Wenn die Benutzer Filter für das Betriebssystem, die Lizenz und die Codierung der SQL-Abfrage würde wie folgt aussehen:
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
aber ich will nicht immer alle Tabellen verbinden, denn das ist sehr schlecht wäre für die Leistung, wenn Es gibt viele Tabellen, aber der Benutzer filtert nur auf ein paar. Aber ich möchte auch nicht für jede Kombination abfragbarer Features eine Abfrage schreiben, da das Schreiben von N² meist identische Abfragen bedeuten würde.
Die 'on' und 'where'-Klauseln können dynamisch ausgeführt werden, abhängig davon, ob wir filtern wollen oder nicht. Die Joins liegen jedoch innerhalb der Parameter der Lambda-Funktion. Ich habe keine Möglichkeit gefunden, dies abhängig von äußeren Variablen aufzubauen.
Also dachte ich Template Haskell könnte den Trick tun ... Ich begann TH zu lernen und den Kern der Abfrage in TH zu konvertieren. Aber jetzt bin ich fest und nicht sicher, ob TH mir helfen kann und ob es der richtige Weg ist?
So, hier ist meine Fortschritte mit 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) |]
So würde Ich mag Sie Hilfe sind:
- Kann/Soll ich dies tun, mit Template Haskell?
- Wenn ja: Wie kann ich die Funktion foo mit Argumenten aufrufen?
- Wenn nicht: Was ist die richtige Lösung?
Wenn Sie vorhaben, dies mit TH zu tun, dann brauchen Sie nicht eine einzige TH-Funktion für die gesamte 'runquery' Funktion zu schreiben - Sie nur eine TH-Funktion haben kann, die erzeugt ein Muster. Sie können also etwas wie 'from $ \ $ (mkInnerJoin [" l "," o "," c "]) -> ...' haben. TH ist wahrscheinlich nicht der beste Weg, dies zu tun (es ist der beste Weg, um ein paar Dinge zu tun ...); Sie könnten damit beginnen, einen Datentyp zu definieren, der die Struktur Ihrer Abfrage darstellt. – user2407038