diff --git a/esqueleto.cabal b/esqueleto.cabal index 2d2c77a..ad008f6 100644 --- a/esqueleto.cabal +++ b/esqueleto.cabal @@ -47,6 +47,7 @@ library , aeson >=1.0 , blaze-html , bytestring + , containers >=0.6 , conduit >=1.3 , monad-logger , persistent >=2.10.0 && <2.11 diff --git a/src/Database/Esqueleto/Internal/Internal.hs b/src/Database/Esqueleto/Internal/Internal.hs index d6d165b..34fcab6 100644 --- a/src/Database/Esqueleto/Internal/Internal.hs +++ b/src/Database/Esqueleto/Internal/Internal.hs @@ -50,6 +50,7 @@ import qualified Data.ByteString as B import qualified Data.Conduit as C import qualified Data.Conduit.List as CL import qualified Data.HashSet as HS +import qualified Data.Map.Strict as Map import qualified Data.Text as T import qualified Data.Text.Lazy as TL import qualified Data.Text.Lazy.Builder as TLB @@ -2884,3 +2885,27 @@ insertSelect = void . insertSelectCount insertSelectCount :: (MonadIO m, PersistEntity a) => SqlQuery (SqlExpr (Insertion a)) -> SqlWriteT m Int64 insertSelectCount = rawEsqueleto INSERT_INTO . fmap EInsertFinal + +-- | Avoid N+1 queries and join entities into a map structure +-- @ +-- getFoosAndNestedBarsFromParent :: ParentId -> (Map (Key Foo) (Foo, [Entity Bar])) +-- getFoosAndNestedBarsFromParent parentId = 'fmap' associateJoin $ 'select' $ +-- 'from' $ \\(foo `'LeftOuterJoin`` bar) -> do +-- 'on' (bar '^.' BarFooId '==.' foo '^.' FooId) +-- 'where_' (foo '^.' FooParentId '==.' 'val' parentId) +-- 'pure' (foo, bar) +-- @ + +associateJoin + :: forall e1 e0 + . Ord (Key e0) + => [(Entity e0, e1)] + -> Map.Map (Key e0) (e0, [e1]) +associateJoin = foldr f start + where + start = Map.empty + f (one, many) = + Map.insertWith + (\(oneOld, manyOld) (_, manyNew) -> (oneOld, manyNew ++ manyOld )) + (entityKey one) + (entityVal one, [many]) diff --git a/src/Database/Esqueleto/Internal/Language.hs b/src/Database/Esqueleto/Internal/Language.hs index dfa4985..254362c 100644 --- a/src/Database/Esqueleto/Internal/Language.hs +++ b/src/Database/Esqueleto/Internal/Language.hs @@ -55,7 +55,7 @@ module Database.Esqueleto.Internal.Language , subList_select, valList, justList , in_, notIn, exists, notExists , set, (=.), (+=.), (-=.), (*=.), (/=.) - , case_, toBaseId, (<#), (<&>) + , case_, toBaseId, (<#), (<&>), associateJoin ) where import Database.Esqueleto.Internal.PersistentImport