packages feed

esqueleto 2.2.3 → 2.2.4

raw patch · 5 files changed

+261/−79 lines, 5 filesPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

API changes (from Hackage documentation)

- Database.Esqueleto.Internal.Sql: SELECT_DISTINCT :: Mode
+ Database.Esqueleto: data DistinctOn
+ Database.Esqueleto: distinct :: Esqueleto query expr backend => query a -> query a
+ Database.Esqueleto: distinctOn :: Esqueleto query expr backend => [expr DistinctOn] -> query a -> query a
+ Database.Esqueleto: distinctOnOrderBy :: Esqueleto query expr backend => [expr OrderBy] -> query a -> query a
+ Database.Esqueleto: don :: Esqueleto query expr backend => expr (Value a) -> expr DistinctOn
+ Database.Esqueleto.Internal.Language: data DistinctOn
+ Database.Esqueleto.Internal.Language: distinct :: Esqueleto query expr backend => query a -> query a
+ Database.Esqueleto.Internal.Language: distinctOn :: Esqueleto query expr backend => [expr DistinctOn] -> query a -> query a
+ Database.Esqueleto.Internal.Language: distinctOnOrderBy :: Esqueleto query expr backend => [expr OrderBy] -> query a -> query a
+ Database.Esqueleto.Internal.Language: don :: Esqueleto query expr backend => expr (Value a) -> expr DistinctOn
+ Database.Esqueleto.Internal.Sql: instance Monoid DistinctClause
- Database.Esqueleto.Internal.Sql: INSERT_INTO :: Mode -> Mode
+ Database.Esqueleto.Internal.Sql: INSERT_INTO :: Mode

Files

esqueleto.cabal view
@@ -1,5 +1,5 @@ name:                esqueleto-version:             2.2.3+version:             2.2.4 synopsis:            Type-safe EDSL for SQL queries on persistent backends. homepage:            https://github.com/prowdsponsor/esqueleto license:             BSD3
src/Database/Esqueleto.hs view
@@ -38,7 +38,8 @@     -- $gettingstarted      -- * @esqueleto@'s Language-    Esqueleto( where_, on, groupBy, orderBy, rand, asc, desc, limit, offset, having+    Esqueleto( where_, on, groupBy, orderBy, rand, asc, desc, limit, offset+             , distinct, distinctOn, don, distinctOnOrderBy, having              , sub_select, sub_selectDistinct, (^.), (?.)              , val, isNothing, just, nothing, joinV, countRows, count, not_              , (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)@@ -59,6 +60,7 @@   , unValue   , ValueList(..)   , OrderBy+  , DistinctOn     -- ** Joins   , InnerJoin(..)   , CrossJoin(..)
src/Database/Esqueleto/Internal/Language.hs view
@@ -27,6 +27,7 @@   , FullOuterJoin(..)   , OnClauseWithoutMatchingJoinException(..)   , OrderBy+  , DistinctOn   , Update   , Insertion     -- * The guts@@ -179,6 +180,86 @@   -- | @OFFSET@.  Usually used with 'limit'.   offset :: Int64 -> query () +  -- | @DISTINCT@.  Change the current @SELECT@ into @SELECT+  -- DISTINCT@.  For example:+  --+  -- @+  -- select $ distinct $+  --   'from' \\foo -> do+  --   ...+  -- @+  --+  -- Note that this also has the same effect:+  --+  -- @+  -- select $+  --   'from' \\foo -> do+  --   distinct (return ())+  --   ...+  -- @+  --+  -- /Since: 2.2.4/+  distinct :: query a -> query a++  -- | @DISTINCT ON@.  Change the current @SELECT@ into+  -- @SELECT DISTINCT ON (expressions)@.  For example:+  --+  -- @+  -- select $+  --   'from' \\foo ->+  --   'distinctOn' ['don' (foo ^. FooName), 'don' (foo ^. FooState)] $ do+  --   ...+  -- @+  --+  -- You can also chain different calls to 'distinctOn'.  The+  -- above is equivalent to:+  --+  -- @+  -- select $+  --   'from' \\foo ->+  --   'distinctOn' ['don' (foo ^. FooName)] $+  --   'distinctOn' ['don' (foo ^. FooState)] $ do+  --   ...+  -- @+  --+  -- Each call to 'distinctOn' adds more expressions.  Calls to+  -- 'distinctOn' override any calls to 'distinct'.+  --+  -- Note that PostgreSQL requires the expressions on @DISTINCT+  -- ON@ to be the first ones to appear on a @ORDER BY@.  This is+  -- not managed automatically by esqueleto, keeping its spirit+  -- of trying to be close to raw SQL.+  --+  -- Supported by PostgreSQL only.+  --+  -- /Since: 2.2.4/+  distinctOn :: [expr DistinctOn] -> query a -> query a++  -- | Erase an expression's type so that it's suitable to+  -- be used by 'distinctOn'.+  --+  -- /Since: 2.2.4/+  don :: expr (Value a) -> expr DistinctOn++  -- | A convenience function that calls both 'distinctOn' and+  -- 'orderBy'.  In other words,+  --+  -- @+  -- 'distinctOnOrderBy' [asc foo, desc bar, desc quux] $ do+  --   ...+  -- @+  --+  -- is the same as:+  --+  -- @+  -- 'distinctOn' [don foo, don  bar, don  quux] $ do+  --   'orderBy'  [asc foo, desc bar, desc quux]+  --   ...+  -- @+  --+  -- /Since: 2.2.4/+  distinctOnOrderBy :: [expr OrderBy] -> query a -> query a+   -- | @ORDER BY random()@ clause.   --   -- /Since: 1.3.10/@@ -390,6 +471,10 @@   -- /Since: 2.1.2/   case_ :: PersistField a => [(expr (Value Bool), expr (Value a))] -> expr (Value a) -> expr (Value a) +{-# DEPRECATED sub_selectDistinct "Since 2.2.4: use 'sub_select' and 'distinct'." #-}+{-# DEPRECATED subList_selectDistinct "Since 2.2.4: use 'subList_select' and 'distinct'." #-}++ -- Fixity declarations infixl 9 ^. infixl 7 *., /.@@ -600,6 +685,10 @@  -- | Phantom type used by 'orderBy', 'asc' and 'desc'. data OrderBy+++-- | Phantom type used by 'distinctOn' and 'don'.+data DistinctOn   -- | Phantom type for a @SET@ operation on an entity of the given
src/Database/Esqueleto/Internal/Sql.hs view
@@ -103,21 +103,36 @@   -- | Side data written by 'SqlQuery'.-data SideData = SideData { sdFromClause    :: ![FromClause]-                         , sdSetClause     :: ![SetClause]-                         , sdWhereClause   :: !WhereClause-                         , sdGroupByClause :: !GroupByClause-                         , sdHavingClause  :: !HavingClause-                         , sdOrderByClause :: ![OrderByClause]-                         , sdLimitClause   :: !LimitClause+data SideData = SideData { sdDistinctClause :: !DistinctClause+                         , sdFromClause     :: ![FromClause]+                         , sdSetClause      :: ![SetClause]+                         , sdWhereClause    :: !WhereClause+                         , sdGroupByClause  :: !GroupByClause+                         , sdHavingClause   :: !HavingClause+                         , sdOrderByClause  :: ![OrderByClause]+                         , sdLimitClause    :: !LimitClause                          }  instance Monoid SideData where-  mempty = SideData mempty mempty mempty mempty mempty mempty mempty-  SideData f s w g h o l `mappend` SideData f' s' w' g' h' o' l' =-    SideData (f <> f') (s <> s') (w <> w') (g <> g') (h <> h') (o <> o') (l <> l')+  mempty = SideData mempty mempty mempty mempty mempty mempty mempty mempty+  SideData d f s w g h o l `mappend` SideData d' f' s' w' g' h' o' l' =+    SideData (d <> d') (f <> f') (s <> s') (w <> w') (g <> g') (h <> h') (o <> o') (l <> l')  +-- | The @DISTINCT@ "clause".+data DistinctClause =+    DistinctAll                     -- ^ The default, everything.+  | DistinctStandard                -- ^ Only @DISTINCT@, SQL standard.+  | DistinctOn [SqlExpr DistinctOn] -- ^ @DISTINCT ON@, PostgreSQL extension.++instance Monoid DistinctClause where+  mempty = DistinctAll+  DistinctOn a     `mappend` DistinctOn b = DistinctOn (a <> b)+  DistinctOn a     `mappend` _            = DistinctOn a+  DistinctStandard `mappend` _            = DistinctStandard+  DistinctAll      `mappend` b            = b++ -- | A part of a @FROM@ clause. data FromClause =     FromStart Ident EntityDef@@ -313,6 +328,9 @@   EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy   EOrderRandom :: SqlExpr OrderBy +  -- A 'SqlExpr' accepted only by 'distinctOn'.+  EDistinctOn :: SqlExpr (Value a) -> SqlExpr DistinctOn+   -- A 'SqlExpr' accepted only by 'set'.   ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val) @@ -383,8 +401,19 @@   limit  n = Q $ W.tell mempty { sdLimitClause = Limit (Just n) Nothing  }   offset n = Q $ W.tell mempty { sdLimitClause = Limit Nothing  (Just n) } +  distinct         act = Q (W.tell mempty { sdDistinctClause = DistinctStandard }) >> act+  distinctOn exprs act = Q (W.tell mempty { sdDistinctClause = DistinctOn exprs }) >> act+  don = EDistinctOn+  distinctOnOrderBy exprs act =+    distinctOn (toDistinctOn <$> exprs) $ do+      orderBy exprs+      act+    where+      toDistinctOn :: SqlExpr OrderBy -> SqlExpr DistinctOn+      toDistinctOn (EOrderBy _ f) = EDistinctOn f+   sub_select         = sub SELECT-  sub_selectDistinct = sub SELECT_DISTINCT+  sub_selectDistinct = sub_select . distinct    (^.) :: forall val typ. (PersistEntity val, PersistField typ)        => SqlExpr (Entity val) -> EntityField val typ -> SqlExpr (Value typ)@@ -451,7 +480,7 @@   (++.)   = unsafeSqlBinOp    " || "    subList_select         = EList . sub_select-  subList_selectDistinct = EList . sub_selectDistinct+  subList_selectDistinct = subList_select . distinct    valList []   = EEmptyList   valList vals = EList $ ERaw Parens $ const ( uncommas ("?" <$ vals)@@ -801,11 +830,8 @@      , MonadResource m )   => SqlQuery a   -> C.Source (SqlPersistT m) r-selectDistinctSource query = do-    src <- lift $ do-        res <- rawSelectSource SELECT_DISTINCT query-        fmap snd $ allocateAcquire res-    src+selectDistinctSource = selectSource . distinct+{-# DEPRECATED selectDistinctSource "Since 2.2.4: use 'selectSource' and 'distinct'." #-}   -- | Execute an @esqueleto@ @SELECT DISTINCT@ query inside@@ -813,10 +839,8 @@ selectDistinct :: ( SqlSelect a r                   , MonadIO m )                => SqlQuery a -> SqlPersistT m [r]-selectDistinct query = do-    res <- rawSelectSource SELECT_DISTINCT query-    conn <- R.ask-    liftIO $ with res $ flip R.runReaderT conn . runSource+selectDistinct = select . distinct+{-# DEPRECATED selectDistinct "Since 2.2.4: use 'select' and 'distinct'." #-}   -- | (Internal) Run a 'C.Source' of rows.@@ -925,7 +949,8 @@         flip S.runState firstIdentState $         W.runWriterT $         unQ query-      SideData fromClauses+      SideData distinctClause+               fromClauses                setClauses                whereClauses                groupByClause@@ -939,7 +964,7 @@       info = (conn, finalIdentState)   in mconcat       [ makeInsertInto info mode ret-      , makeSelect     info mode ret+      , makeSelect     info mode distinctClause ret       , makeFrom       info mode fromClauses       , makeSet        info setClauses       , makeWhere      info whereClauses@@ -953,11 +978,9 @@ -- | (Internal) Mode of query being converted by 'toRawSql'. data Mode =     SELECT-  | SELECT_DISTINCT   | DELETE   | UPDATE-  | INSERT_INTO Mode-    -- ^ 'Mode' should be either 'SELECT' or 'SELECT_DISTINCT'.+  | INSERT_INTO   uncommas :: [TLB.Builder] -> TLB.Builder@@ -971,21 +994,27 @@   makeInsertInto :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue])-makeInsertInto info (INSERT_INTO _) ret = sqlInsertInto info ret-makeInsertInto _    _               _   = mempty+makeInsertInto info INSERT_INTO ret = sqlInsertInto info ret+makeInsertInto _    _           _   = mempty  -makeSelect :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue])-makeSelect info mode_ ret = process mode_+makeSelect :: SqlSelect a r => IdentInfo -> Mode -> DistinctClause -> a -> (TLB.Builder, [PersistValue])+makeSelect info mode_ distinctClause ret = process mode_   where     process mode =       case mode of-        SELECT            -> withCols "SELECT "-        SELECT_DISTINCT   -> withCols "SELECT DISTINCT "-        DELETE            -> plain "DELETE "-        UPDATE            -> plain "UPDATE "-        INSERT_INTO mode' -> process mode'-    withCols v = first (v <>) (sqlSelectCols info ret)+        SELECT      -> withCols selectKind+        DELETE      -> plain "DELETE "+        UPDATE      -> plain "UPDATE "+        INSERT_INTO -> process SELECT+    selectKind =+      case distinctClause of+        DistinctAll      -> ("SELECT ", [])+        DistinctStandard -> ("SELECT DISTINCT ", [])+        DistinctOn exprs -> first (("SELECT DISTINCT ON (" <>) . (<> ") ")) $+                            uncommas' (processExpr <$> exprs)+      where processExpr (EDistinctOn f) = materializeExpr info f+    withCols v = v <> (sqlSelectCols info ret)     plain    v = (v, [])  @@ -1176,17 +1205,22 @@ -- | You may return any single value (i.e. a single column) from -- a 'select' query. instance PersistField a => SqlSelect (SqlExpr (Value a)) (Value a) where-  sqlSelectCols info (ERaw p f) =-    let (b, vals) = f info-    in (parensM p b, vals)-  sqlSelectCols info (ECompositeKey f) =-    let bs = f info-    in (uncommas $ map (parensM Parens) bs, [])+  sqlSelectCols = materializeExpr   sqlSelectColCount = const 1   sqlSelectProcessRow [pv] = Value <$> fromPersistValue pv   sqlSelectProcessRow pvs  = Value <$> fromPersistValue (PersistList pvs)  +-- | Materialize a @SqlExpr (Value a)@.+materializeExpr :: IdentInfo -> SqlExpr (Value a) -> (TLB.Builder, [PersistValue])+materializeExpr info (ERaw p f) =+  let (b, vals) = f info+  in (parensM p b, vals)+materializeExpr info (ECompositeKey f) =+  let bs = f info+  in (uncommas $ map (parensM Parens) bs, [])++ -- | You may return tuples (up to 16-tuples) and tuples of tuples -- from a 'select' query. instance ( SqlSelect a ra@@ -1681,16 +1715,11 @@ -- | Insert a 'PersistField' for every selected value. insertSelect :: (MonadIO m, PersistEntity a) =>   SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()-insertSelect = insertGeneralSelect SELECT+insertSelect = liftM (const ()) . rawEsqueleto INSERT_INTO . fmap EInsertFinal   -- | Insert a 'PersistField' for every unique selected value. insertSelectDistinct :: (MonadIO m, PersistEntity a) =>   SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()-insertSelectDistinct = insertGeneralSelect SELECT_DISTINCT---insertGeneralSelect :: (MonadIO m, PersistEntity a) =>-  Mode -> SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()-insertGeneralSelect mode =-  liftM (const ()) . rawEsqueleto (INSERT_INTO mode) . fmap EInsertFinal+insertSelectDistinct = insertSelect . distinct+{-# DEPRECATED insertSelectDistinct "Since 2.2.4: use 'insertSelect' and 'distinct'." #-}
test/Test.hs view
@@ -1,3 +1,4 @@+{-# OPTIONS_GHC -fno-warn-unused-binds  #-} {-# LANGUAGE ConstraintKinds            , EmptyDataDecls            , FlexibleContexts@@ -18,12 +19,15 @@ module Main (main) where  import Control.Applicative ((<$>))+import Control.Arrow ((&&&)) import Control.Exception (IOException)-import Control.Monad (replicateM, replicateM_)+import Control.Monad (replicateM, replicateM_, void) import Control.Monad.IO.Class (MonadIO(liftIO)) import Control.Monad.Logger (MonadLogger(..), runStderrLoggingT, runNoLoggingT) import Control.Monad.Trans.Control (MonadBaseControl(..)) import Control.Monad.Trans.Reader (ReaderT)+import Data.List (sortBy)+import Data.Ord (comparing) import Database.Esqueleto #if   defined (WITH_POSTGRESQL) import Database.Persist.Postgresql (withPostgresqlConn)@@ -448,12 +452,12 @@        it "respects the associativity of joins" $         run $ do-            insert' p1+            void $ insert p1             ps <- select . from $                       \((p :: SqlExpr (Entity Person))                        `LeftOuterJoin`-                        (( q :: SqlExpr (Entity Person))-                         `InnerJoin` (r :: SqlExpr (Entity Person)))) -> do+                        ((_q :: SqlExpr (Entity Person))+                         `InnerJoin` (_r :: SqlExpr (Entity Person)))) -> do                 on (val False) -- Inner join is empty                 on (val True)                 return p@@ -560,18 +564,18 @@           p2e@(Entity _ bob) <- insert' $ Person "bob" (Just 36) Nothing   1            -- lower(name) == 'john'-          ret <- select $-                 from $ \p-> do-                 where_ (lower_ (p ^. PersonName) ==. val (map toLower $ personName p1))-                 return p-          liftIO $ ret `shouldBe` [ p1e ]+          ret1 <- select $+                  from $ \p-> do+                  where_ (lower_ (p ^. PersonName) ==. val (map toLower $ personName p1))+                  return p+          liftIO $ ret1 `shouldBe` [ p1e ]            -- name == lower('BOB')-          ret <- select $-                 from $ \p-> do-                 where_ (p ^. PersonName ==. lower_ (val $ map toUpper $ personName bob))-                 return p-          liftIO $ ret `shouldBe` [ p2e ]+          ret2 <- select $+                  from $ \p-> do+                  where_ (p ^. PersonName ==. lower_ (val $ map toUpper $ personName bob))+                  return p+          liftIO $ ret2 `shouldBe` [ p2e ]        it "works with random_" $         run $ do@@ -779,19 +783,76 @@           liftIO $ map entityVal eps `shouldBe` reverse ps  -    describe "selectDistinct" $-      it "works on a simple example" $+    describe "SELECT DISTINCT" $ do+      let selDistTest+            :: (   forall m. RunDbMonad m+                => SqlQuery (SqlExpr (Value String))+                -> SqlPersistT (R.ResourceT m) [Value String])+            -> IO ()+          selDistTest q =+            run $ do+              p1k <- insert p1+              let (t1, t2, t3) = ("a", "b", "c")+              mapM_ (insert . flip BlogPost p1k) [t1, t3, t2, t2, t1]+              ret <- q $+                     from $ \b -> do+                     let title = b ^. BlogPostTitle+                     orderBy [asc title]+                     return title+              liftIO $ ret `shouldBe` [ Value t1, Value t2, Value t3 ]+      it "works on a simple example (selectDistinct)" $+        selDistTest selectDistinct++      it "works on a simple example (select . distinct)" $+        selDistTest (select . distinct)++      it "works on a simple example (distinct (return ()))" $+        selDistTest (\act -> select $ distinct (return ()) >> act)++#if defined(WITH_POSTGRESQL)+    describe "SELECT DISTINCT ON" $ do+      it "works on a simple example" $ do         run $ do-          p1k <- insert p1-          let (t1, t2, t3) = ("a", "b", "c")-          mapM_ (insert . flip BlogPost p1k) [t1, t3, t2, t2, t1]-          ret <- selectDistinct $-                 from $ \b -> do-                 let title = b ^. BlogPostTitle-                 orderBy [asc title]-                 return title-          liftIO $ ret `shouldBe` [ Value t1, Value t2, Value t3 ]+          [p1k, p2k, _] <- mapM insert [p1, p2, p3]+          [_, bpB, bpC] <- mapM insert'+            [ BlogPost "A" p1k+            , BlogPost "B" p1k+            , BlogPost "C" p2k ]+          ret <- select $+                 from $ \bp ->+                 distinctOn [don (bp ^. BlogPostAuthorId)] $ do+                 orderBy [asc (bp ^. BlogPostAuthorId), desc (bp ^. BlogPostTitle)]+                 return bp+          liftIO $ ret `shouldBe` sortBy (comparing (blogPostAuthorId . entityVal)) [bpB, bpC] +      let slightlyLessSimpleTest q =+            run $ do+              [p1k, p2k, _] <- mapM insert [p1, p2, p3]+              [bpA, bpB, bpC] <- mapM insert'+                [ BlogPost "A" p1k+                , BlogPost "B" p1k+                , BlogPost "C" p2k ]+              ret <- select $+                     from $ \bp ->+                     q bp $ return bp+              let cmp = (blogPostAuthorId &&& blogPostTitle) . entityVal+              liftIO $ ret `shouldBe` sortBy (comparing cmp) [bpA, bpB, bpC]+      it "works on a slightly less simple example (two distinctOn calls, orderBy)" $+        slightlyLessSimpleTest $ \bp act ->+          distinctOn [don (bp ^. BlogPostAuthorId)] $+          distinctOn [don (bp ^. BlogPostTitle)] $ do+            orderBy [asc (bp ^. BlogPostAuthorId), asc (bp ^. BlogPostTitle)]+            act+      it "works on a slightly less simple example (one distinctOn call, orderBy)" $ do+        slightlyLessSimpleTest $ \bp act ->+          distinctOn [don (bp ^. BlogPostAuthorId), don (bp ^. BlogPostTitle)] $ do+            orderBy [asc (bp ^. BlogPostAuthorId), asc (bp ^. BlogPostTitle)]+            act+      it "works on a slightly less simple example (distinctOnOrderBy)" $ do+        slightlyLessSimpleTest $ \bp ->+          distinctOnOrderBy [asc (bp ^. BlogPostAuthorId), asc (bp ^. BlogPostTitle)]+#endif+     describe "coalesce/coalesceDefault" $ do       it "works on a simple example" $         run $ do@@ -878,7 +939,7 @@ #if defined(WITH_POSTGRESQL)       it "ilike, (%) and (++.) work on a simple example on PostgreSQL" $          run $ do-           [p1e, p2e, p3e, p4e, p5e] <- mapM insert' [p1, p2, p3, p4, p5]+           [p1e, _, p3e, _, p5e] <- mapM insert' [p1, p2, p3, p4, p5]            let nameContains t expected = do                  ret <- select $                         from $ \p -> do@@ -1098,6 +1159,7 @@                  where_ $ exists $                           from $ \bp -> do                           where_ (bp ^. BlogPostAuthorId ==. p ^. PersonId)+                 orderBy [asc (p ^. PersonName)]                  return p           liftIO $ ret `shouldBe` [ Entity p1k p1                                   , Entity p3k p3 ]@@ -1274,7 +1336,7 @@ #endif   runSqlConn . #if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)-  (runMigration migrateAll >>) $ (cleanDB >> act)+  (runMigrationSilent migrateAll >>) $ (cleanDB >> act) #else   (runMigrationSilent migrateAll >>) $ act #endif