packages feed

esqueleto 2.2.6 → 2.2.7

raw patch · 5 files changed

+124/−19 lines, 5 filesPVP ok

version bump matches the API change (PVP)

API changes (from Hackage documentation)

+ Database.Esqueleto: ForShare :: LockingKind
+ Database.Esqueleto: ForUpdate :: LockingKind
+ Database.Esqueleto: LockInShareMode :: LockingKind
+ Database.Esqueleto: data LockingKind
+ Database.Esqueleto: locking :: Esqueleto query expr backend => LockingKind -> query ()
+ Database.Esqueleto.Internal.Language: ForShare :: LockingKind
+ Database.Esqueleto.Internal.Language: ForUpdate :: LockingKind
+ Database.Esqueleto.Internal.Language: LockInShareMode :: LockingKind
+ Database.Esqueleto.Internal.Language: data LockingKind
+ Database.Esqueleto.Internal.Language: locking :: Esqueleto query expr backend => LockingKind -> query ()

Files

esqueleto.cabal view
@@ -1,5 +1,5 @@ name:                esqueleto-version:             2.2.6+version:             2.2.7 synopsis:            Type-safe EDSL for SQL queries on persistent backends. homepage:            https://github.com/prowdsponsor/esqueleto license:             BSD3
src/Database/Esqueleto.hs view
@@ -39,7 +39,7 @@      -- * @esqueleto@'s Language     Esqueleto( where_, on, groupBy, orderBy, rand, asc, desc, limit, offset-             , distinct, distinctOn, don, distinctOnOrderBy, having+             , distinct, distinctOn, don, distinctOnOrderBy, having, locking              , sub_select, sub_selectDistinct, (^.), (?.)              , val, isNothing, just, nothing, joinV, countRows, count, not_              , (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)@@ -61,6 +61,7 @@   , ValueList(..)   , OrderBy   , DistinctOn+  , LockingKind(..)     -- ** Joins   , InnerJoin(..)   , CrossJoin(..)
src/Database/Esqueleto/Internal/Language.hs view
@@ -30,6 +30,7 @@   , DistinctOn   , Update   , Insertion+  , LockingKind(..)     -- * The guts   , JoinKind(..)   , IsJoinKind(..)@@ -273,6 +274,15 @@   -- /Since: 1.2.2/   having :: expr (Value Bool) -> query () +  -- | Add a locking clause to the query.  Please read+  -- 'LockingKind' documentation and your RDBMS manual.+  --+  -- If multiple calls to 'locking' are made on the same query,+  -- the last one is used.+  --+  -- /Since: 2.2.7/+  locking :: LockingKind -> query ()+   -- | Execute a subquery @SELECT@ in an expression.  Returns a   -- simple value so should be used only when the @SELECT@ query   -- is guaranteed to return just one row.@@ -701,6 +711,31 @@  -- | Phantom type used by 'insertSelect'. data Insertion a+++-- | Different kinds of locking clauses supported by 'locking'.+--+-- Note that each RDBMS has different locking support.  The+-- constructors of this datatype specify only the /syntax/ of the+-- locking mechanism, not its /semantics/.  For example, even+-- though both MySQL and PostgreSQL support 'ForUpdate', there+-- are no guarantees that they will behave the same.+--+-- /Since: 2.2.7/+data LockingKind =+    ForUpdate+    -- ^ @FOR UPDATE@ syntax.  Supported by MySQL, Oracle and+    -- PostgreSQL.+    --+    -- /Since: 2.2.7/+  | ForShare+    -- ^ @FOR SHARE@ syntax.  Supported by PostgreSQL.+    --+    -- /Since: 2.2.7/+  | LockInShareMode+    -- ^ @LOCK IN SHARE MODE@ syntax.  Supported by MySQL.+    --+    -- /Since: 2.2.7/   -- | @FROM@ clause: bring entities into scope.
src/Database/Esqueleto/Internal/Sql.hs view
@@ -58,7 +58,7 @@ import qualified Control.Monad.Trans.Reader as R import Data.Int (Int64) import Data.List (intersperse)-import Data.Monoid (Monoid(..), (<>))+import Data.Monoid (Last(..), Monoid(..), (<>)) import Data.Proxy (Proxy(..)) import Database.Esqueleto.Internal.PersistentImport import Database.Persist.Sql.Util (@@ -111,12 +111,13 @@                          , sdHavingClause   :: !HavingClause                          , sdOrderByClause  :: ![OrderByClause]                          , sdLimitClause    :: !LimitClause+                         , sdLockingClause  :: !LockingClause                          }  instance Monoid SideData where-  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')+  mempty = SideData mempty mempty mempty mempty mempty mempty mempty mempty mempty+  SideData d f s w g h o l k `mappend` SideData d' f' s' w' g' h' o' l' k' =+    SideData (d <> d') (f <> f') (s <> s') (w <> w') (g <> g') (h <> h') (o <> o') (l <> l') (k <> k')   -- | The @DISTINCT@ "clause".@@ -211,6 +212,10 @@     -- "reversed" arguments.  +-- | A locking clause.+type LockingClause = Last LockingKind++ ----------------------------------------------------------------------  @@ -392,6 +397,8 @@    having expr = Q $ W.tell mempty { sdHavingClause = Where expr } +  locking kind = Q $ W.tell mempty { sdLockingClause = Last (Just kind) }+   orderBy exprs = Q $ W.tell mempty { sdOrderByClause = exprs }   asc  = EOrderBy ASC   desc = EOrderBy DESC@@ -956,7 +963,8 @@                groupByClause                havingClause                orderByClauses-               limitClause = sd+               limitClause+               lockingClause = sd       -- Pass the finalIdentState (containing all identifiers       -- that were used) to the subsequent calls.  This ensures       -- that no name clashes will occur on subqueries that may@@ -972,6 +980,7 @@       , makeHaving     info havingClause       , makeOrderBy    info orderByClauses       , makeLimit      info limitClause orderByClauses+      , makeLocking         lockingClause       ]  @@ -1110,6 +1119,15 @@       hasOrderClause = not (null orderByClauses)       v = maybe 0 fromIntegral   in (TLB.fromText limitRaw, mempty)+++makeLocking :: LockingClause -> (TLB.Builder, [PersistValue])+makeLocking = flip (,) [] . maybe mempty toTLB . getLast+  where+    toTLB ForUpdate       = "\nFOR UPDATE"+    toTLB ForShare        = "\nFOR SHARE"+    toTLB LockInShareMode = "\nLOCK IN SHARE MODE"+   parens :: TLB.Builder -> TLB.Builder
test/Test.hs view
@@ -26,7 +26,9 @@ import Control.Monad.Logger (MonadLogger(..), runStderrLoggingT, runNoLoggingT) import Control.Monad.Trans.Control (MonadBaseControl(..)) import Control.Monad.Trans.Reader (ReaderT)+import Data.Char (toLower, toUpper) import Data.List (sortBy)+import Data.Monoid ((<>)) import Data.Ord (comparing) import Database.Esqueleto #if   defined (WITH_POSTGRESQL)@@ -48,9 +50,10 @@ import Test.Hspec  import qualified Control.Monad.Trans.Resource as R-import qualified Data.Set as S import qualified Data.List as L-import Data.Char (toLower, toUpper)+import qualified Data.Set as S+import qualified Data.Text.Lazy.Builder as TLB+import qualified Database.Esqueleto.Internal.Sql as EI   -- Test schema@@ -1268,7 +1271,49 @@            liftIO $ ret `shouldBe` [ Value (3) ] +    describe "locking" $ do+      -- The locking clause is the last one, so try to use many+      -- others to test if it's at the right position.  We don't+      -- care about the text of the rest, nor with the RDBMS'+      -- reaction to the clause.+      let sanityCheck kind syntax = do+            let complexQuery =+                  from $ \(p1 `InnerJoin` p2) -> do+                  on (p1 ^. PersonName ==. p2 ^. PersonName)+                  where_ (p1 ^. PersonFavNum >. val 2)+                  orderBy [desc (p2 ^. PersonAge)]+                  limit 3+                  offset 9+                  groupBy (p1 ^. PersonId)+                  having (countRows <. val (0 :: Int))+                  return (p1, p2)+                queryWithClause1 = do+                  r <- complexQuery+                  locking kind+                  return r+                queryWithClause2 = do+                  locking ForUpdate+                  r <- complexQuery+                  locking ForShare+                  locking kind+                  return r+                queryWithClause3 = do+                  locking kind+                  complexQuery+                toText conn q =+                  let (tlb, _) = EI.toRawSql EI.SELECT (conn, EI.initialIdentState) q+                  in TLB.toLazyText tlb+            [complex, with1, with2, with3] <-+              runNoLoggingT $ withConn $ \conn -> return $+                map (toText conn) [complexQuery, queryWithClause1, queryWithClause2, queryWithClause3]+            let expected = complex <> "\n" <> syntax+            (with1, with2, with3) `shouldBe` (expected, expected, expected) +      it "looks sane for ForUpdate"       $ sanityCheck ForUpdate       "FOR UPDATE"+      it "looks sane for ForShare"        $ sanityCheck ForShare        "FOR SHARE"+      it "looks sane for LockInShareMode" $ sanityCheck LockInShareMode "LOCK IN SHARE MODE"++ ----------------------------------------------------------------------  @@ -1321,23 +1366,29 @@   run_worker :: RunDbMonad m => SqlPersistT (R.ResourceT m) a -> m a-run_worker act =+run_worker act = withConn $ runSqlConn (migrateIt >> act)+++migrateIt :: RunDbMonad m => SqlPersistT (R.ResourceT m) ()+migrateIt = do+  void $ runMigrationSilent migrateAll+#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)+  cleanDB+#endif+++withConn :: RunDbMonad m => (SqlBackend -> R.ResourceT m a) -> m a+withConn =   R.runResourceT . #if defined(WITH_POSTGRESQL)-  withPostgresqlConn "host=localhost port=5432 user=test dbname=test" .+  withPostgresqlConn "host=localhost port=5432 user=test dbname=test" #elif defined (WITH_MYSQL)   withMySQLConn defaultConnectInfo     { connectHost     = "localhost"     , connectUser     = "test"     , connectPassword = "test"     , connectDatabase = "test"-    } .-#else-  withSqliteConn ":memory:" .-#endif-  runSqlConn .-#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)-  (runMigrationSilent migrateAll >>) $ (cleanDB >> act)+    } #else-  (runMigrationSilent migrateAll >>) $ act+  withSqliteConn ":memory:" #endif