Merge branch 'master' of github.com:meteficha/esqueleto into HEAD
Conflicts: esqueleto.cabal src/Database/Esqueleto/Internal/Sql.hs
This commit is contained in:
commit
2685cdb7f7
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,2 +1,4 @@
|
|||||||
/dist*
|
/dist*
|
||||||
*~
|
*~
|
||||||
|
.cabal-sandbox/
|
||||||
|
cabal.sandbox.config
|
||||||
|
|||||||
@ -1,38 +1,37 @@
|
|||||||
name: esqueleto
|
name: esqueleto
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
synopsis: Bare bones, type-safe EDSL for SQL queries on persistent backends.
|
synopsis: Type-safe EDSL for SQL queries on persistent backends.
|
||||||
homepage: https://github.com/meteficha/esqueleto
|
homepage: https://github.com/meteficha/esqueleto
|
||||||
license: BSD3
|
license: BSD3
|
||||||
license-file: LICENSE
|
license-file: LICENSE
|
||||||
author: Felipe Lessa
|
author: Felipe Lessa
|
||||||
maintainer: felipe.lessa@gmail.com
|
maintainer: felipe.lessa@gmail.com
|
||||||
copyright: (c) 2012 Felipe Almeida Lessa
|
copyright: (c) 2012-2014 Felipe Almeida Lessa
|
||||||
category: Database
|
category: Database
|
||||||
build-type: Simple
|
build-type: Simple
|
||||||
cabal-version: >=1.8
|
cabal-version: >=1.8
|
||||||
description:
|
description:
|
||||||
@persistent@ is a library for type-safe data serialization. It
|
|
||||||
has many kinds of backends, such as SQL backends
|
|
||||||
(@persistent-mysql@, @persistent-postgresql@,
|
|
||||||
@persistent-sqlite@) and NoSQL backends (@persistent-mongoDB@).
|
|
||||||
.
|
|
||||||
While @persistent@ is a nice library for storing and retrieving
|
|
||||||
records, currently it has a poor interface for SQL backends
|
|
||||||
compared to SQL itself. For example, it's extremely hard to do
|
|
||||||
a type-safe @JOIN@ on a many-to-one relation, and simply
|
|
||||||
impossible to do any other kinds of @JOIN@s (including for the
|
|
||||||
very common many-to-many relations). Users have the option of
|
|
||||||
writing raw SQL, but that's error prone and not type-checked.
|
|
||||||
.
|
|
||||||
@esqueleto@ is a bare bones, type-safe EDSL for SQL queries
|
@esqueleto@ is a bare bones, type-safe EDSL for SQL queries
|
||||||
that works with unmodified @persistent@ SQL backends. Its
|
that works with unmodified @persistent@ SQL backends. Its
|
||||||
language closely resembles SQL, so (a) you don't have to learn
|
language closely resembles SQL, so you don't have to learn
|
||||||
new concepts, just new syntax, and (b) it's fairly easy to
|
new concepts, just new syntax, and it's fairly easy to
|
||||||
predict the generated SQL and optimize it for your backend.
|
predict the generated SQL and optimize it for your backend.
|
||||||
Most kinds of errors committed when writing SQL are caught as
|
Most kinds of errors committed when writing SQL are caught as
|
||||||
compile-time errors---although it is possible to write
|
compile-time errors---although it is possible to write
|
||||||
type-checked @esqueleto@ queries that fail at runtime.
|
type-checked @esqueleto@ queries that fail at runtime.
|
||||||
.
|
.
|
||||||
|
@persistent@ is a library for type-safe data serialization. It
|
||||||
|
has many kinds of backends, such as SQL backends
|
||||||
|
(@persistent-mysql@, @persistent-postgresql@,
|
||||||
|
@persistent-sqlite@) and NoSQL backends (@persistent-mongoDB@).
|
||||||
|
While @persistent@ is a nice library for storing and retrieving
|
||||||
|
records, including with filters, it does not try to support
|
||||||
|
some of the features that are specific to SQL backends. In
|
||||||
|
particular, @esqueleto@ is the recommended library for
|
||||||
|
type-safe @JOIN@s on @persistent@ SQL backends. (The
|
||||||
|
alternative is using raw SQL, but that's error prone and does
|
||||||
|
not offer any composability.)
|
||||||
|
.
|
||||||
Currently, @SELECT@s, @UPDATE@s, @INSERT@s and @DELETE@s are supported.
|
Currently, @SELECT@s, @UPDATE@s, @INSERT@s and @DELETE@s are supported.
|
||||||
Not all SQL features are available, but most of them can be easily added
|
Not all SQL features are available, but most of them can be easily added
|
||||||
(especially functions), so please open an issue or send a pull request if
|
(especially functions), so please open an issue or send a pull request if
|
||||||
@ -63,15 +62,15 @@ library
|
|||||||
other-modules:
|
other-modules:
|
||||||
Database.Esqueleto.Internal.PersistentImport
|
Database.Esqueleto.Internal.PersistentImport
|
||||||
build-depends:
|
build-depends:
|
||||||
base >= 4.5 && < 4.7
|
base >= 4.5 && < 4.8
|
||||||
, text == 0.11.*
|
, text >= 0.11
|
||||||
, persistent >= 2.0 && < 2.1
|
, persistent >= 2.0 && < 2.1
|
||||||
, transformers >= 0.2
|
, transformers >= 0.2
|
||||||
, unordered-containers >= 0.2
|
, unordered-containers >= 0.2
|
||||||
, tagged >= 0.2
|
, tagged >= 0.2
|
||||||
|
|
||||||
, monad-logger
|
, monad-logger
|
||||||
, conduit
|
, conduit >= 1.1
|
||||||
, resourcet >= 1.1
|
, resourcet >= 1.1
|
||||||
hs-source-dirs: src/
|
hs-source-dirs: src/
|
||||||
ghc-options: -Wall
|
ghc-options: -Wall
|
||||||
@ -84,13 +83,14 @@ test-suite test
|
|||||||
build-depends:
|
build-depends:
|
||||||
-- Library dependencies used on the tests. No need to
|
-- Library dependencies used on the tests. No need to
|
||||||
-- specify versions since they'll use the same as above.
|
-- specify versions since they'll use the same as above.
|
||||||
base, persistent, transformers, conduit, text
|
base, persistent, transformers, resourcet, text
|
||||||
|
|
||||||
-- Test-only dependencies
|
-- Test-only dependencies
|
||||||
|
, conduit >= 1.1
|
||||||
, containers
|
, containers
|
||||||
, HUnit
|
, HUnit
|
||||||
, QuickCheck
|
, QuickCheck
|
||||||
, hspec >= 1.3 && < 1.8
|
, hspec >= 1.8
|
||||||
, persistent-sqlite >= 2.0 && < 2.1
|
, persistent-sqlite >= 2.0 && < 2.1
|
||||||
, persistent-template >= 2.0 && < 2.1
|
, persistent-template >= 2.0 && < 2.1
|
||||||
, monad-control
|
, monad-control
|
||||||
@ -114,4 +114,3 @@ test-suite test
|
|||||||
, persistent-mysql >= 2.0
|
, persistent-mysql >= 2.0
|
||||||
|
|
||||||
cpp-options: -DWITH_MYSQL
|
cpp-options: -DWITH_MYSQL
|
||||||
|
|
||||||
|
|||||||
@ -38,7 +38,7 @@ module Database.Esqueleto
|
|||||||
-- $gettingstarted
|
-- $gettingstarted
|
||||||
|
|
||||||
-- * @esqueleto@'s Language
|
-- * @esqueleto@'s Language
|
||||||
Esqueleto( where_, on, groupBy, orderBy, asc, desc, limit, offset, having
|
Esqueleto( where_, on, groupBy, orderBy, rand, asc, desc, limit, offset, having
|
||||||
, sub_select, sub_selectDistinct, (^.), (?.)
|
, sub_select, sub_selectDistinct, (^.), (?.)
|
||||||
, val, isNothing, just, nothing, joinV, countRows, count, not_
|
, val, isNothing, just, nothing, joinV, countRows, count, not_
|
||||||
, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)
|
, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)
|
||||||
@ -51,6 +51,7 @@ module Database.Esqueleto
|
|||||||
, set, (=.), (+=.), (-=.), (*=.), (/=.) )
|
, set, (=.), (+=.), (-=.), (*=.), (/=.) )
|
||||||
, from
|
, from
|
||||||
, Value(..)
|
, Value(..)
|
||||||
|
, unValue
|
||||||
, ValueList(..)
|
, ValueList(..)
|
||||||
, OrderBy
|
, OrderBy
|
||||||
-- ** Joins
|
-- ** Joins
|
||||||
@ -239,7 +240,7 @@ import qualified Database.Persist
|
|||||||
--
|
--
|
||||||
-- Since @age@ is an optional @Person@ field, we use 'just' lift
|
-- Since @age@ is an optional @Person@ field, we use 'just' lift
|
||||||
-- @val 18 :: SqlExpr (Value Int)@ into @just (val 18) ::
|
-- @val 18 :: SqlExpr (Value Int)@ into @just (val 18) ::
|
||||||
-- SqlExpr (Value (Just Int))@.
|
-- SqlExpr (Value (Maybe Int))@.
|
||||||
--
|
--
|
||||||
-- Implicit joins are represented by tuples. For example, to get
|
-- Implicit joins are represented by tuples. For example, to get
|
||||||
-- the list of all blog posts and their authors, we could write:
|
-- the list of all blog posts and their authors, we could write:
|
||||||
|
|||||||
@ -16,6 +16,7 @@ module Database.Esqueleto.Internal.Language
|
|||||||
Esqueleto(..)
|
Esqueleto(..)
|
||||||
, from
|
, from
|
||||||
, Value(..)
|
, Value(..)
|
||||||
|
, unValue
|
||||||
, ValueList(..)
|
, ValueList(..)
|
||||||
, SomeValue(..)
|
, SomeValue(..)
|
||||||
, ToSomeValues(..)
|
, ToSomeValues(..)
|
||||||
@ -174,6 +175,11 @@ class (Functor query, Applicative query, Monad query) =>
|
|||||||
-- | @OFFSET@. Usually used with 'limit'.
|
-- | @OFFSET@. Usually used with 'limit'.
|
||||||
offset :: Int64 -> query ()
|
offset :: Int64 -> query ()
|
||||||
|
|
||||||
|
-- | @ORDER BY random()@ clause.
|
||||||
|
--
|
||||||
|
-- /Since: 1.3.10/
|
||||||
|
rand :: expr OrderBy
|
||||||
|
|
||||||
-- | @HAVING@.
|
-- | @HAVING@.
|
||||||
--
|
--
|
||||||
-- /Since: 1.2.2/
|
-- /Since: 1.2.2/
|
||||||
@ -237,12 +243,12 @@ class (Functor query, Applicative query, Monad query) =>
|
|||||||
(*.) :: PersistField a => expr (Value a) -> expr (Value a) -> expr (Value a)
|
(*.) :: PersistField a => expr (Value a) -> expr (Value a) -> expr (Value a)
|
||||||
|
|
||||||
|
|
||||||
random_ :: PersistField a => expr (Value a)
|
random_ :: (PersistField a, Num a) => expr (Value a)
|
||||||
round_ :: (PersistField a, PersistField b) => expr (Value a) -> expr (Value b)
|
round_ :: (PersistField a, Num a, PersistField b, Num b) => expr (Value a) -> expr (Value b)
|
||||||
ceiling_ :: (PersistField a, PersistField b) => expr (Value a) -> expr (Value b)
|
ceiling_ :: (PersistField a, Num a, PersistField b, Num b) => expr (Value a) -> expr (Value b)
|
||||||
floor_ :: (PersistField a, PersistField b) => expr (Value a) -> expr (Value b)
|
floor_ :: (PersistField a, Num a, PersistField b, Num b) => expr (Value a) -> expr (Value b)
|
||||||
|
|
||||||
sum_ :: (PersistField a) => expr (Value a) -> expr (Value (Maybe a))
|
sum_ :: (PersistField a, PersistField b) => expr (Value a) -> expr (Value (Maybe b))
|
||||||
min_ :: (PersistField a) => expr (Value a) -> expr (Value (Maybe a))
|
min_ :: (PersistField a) => expr (Value a) -> expr (Value (Maybe a))
|
||||||
max_ :: (PersistField a) => expr (Value a) -> expr (Value (Maybe a))
|
max_ :: (PersistField a) => expr (Value a) -> expr (Value (Maybe a))
|
||||||
avg_ :: (PersistField a, PersistField b) => expr (Value a) -> expr (Value (Maybe b))
|
avg_ :: (PersistField a, PersistField b) => expr (Value a) -> expr (Value (Maybe b))
|
||||||
@ -329,13 +335,22 @@ infixr 2 ||., `InnerJoin`, `CrossJoin`, `LeftOuterJoin`, `RightOuterJoin`, `Full
|
|||||||
-- @('^.')@ or @('?.')@ to get a 'Value' from an 'Entity'.
|
-- @('^.')@ or @('?.')@ to get a 'Value' from an 'Entity'.
|
||||||
data Value a = Value a deriving (Eq, Ord, Show, Typeable)
|
data Value a = Value a deriving (Eq, Ord, Show, Typeable)
|
||||||
-- Note: because of GHC bug #6124 we use @data@ instead of @newtype@.
|
-- Note: because of GHC bug #6124 we use @data@ instead of @newtype@.
|
||||||
|
-- <https://ghc.haskell.org/trac/ghc/ticket/6124>
|
||||||
|
|
||||||
|
|
||||||
-- | A list of single values. There's a limited set of funcitons
|
-- | Unwrap a 'Value'.
|
||||||
|
--
|
||||||
|
-- /Since: 1.4.1/
|
||||||
|
unValue :: Value a -> a
|
||||||
|
unValue (Value a) = a
|
||||||
|
|
||||||
|
|
||||||
|
-- | A list of single values. There's a limited set of functions
|
||||||
-- able to work with this data type (such as 'subList_select',
|
-- able to work with this data type (such as 'subList_select',
|
||||||
-- 'valList', 'in_' and 'exists').
|
-- 'valList', 'in_' and 'exists').
|
||||||
data ValueList a = ValueList a deriving (Eq, Ord, Show, Typeable)
|
data ValueList a = ValueList a deriving (Eq, Ord, Show, Typeable)
|
||||||
-- Note: because of GHC bug #6124 we use @data@ instead of @newtype@.
|
-- Note: because of GHC bug #6124 we use @data@ instead of @newtype@.
|
||||||
|
-- <https://ghc.haskell.org/trac/ghc/ticket/6124>
|
||||||
|
|
||||||
|
|
||||||
-- | A wrapper type for for any @expr (Value a)@ for all a.
|
-- | A wrapper type for for any @expr (Value a)@ for all a.
|
||||||
|
|||||||
@ -1,4 +1,5 @@
|
|||||||
{-# LANGUAGE ConstraintKinds
|
{-# LANGUAGE ConstraintKinds
|
||||||
|
, EmptyDataDecls
|
||||||
, FlexibleContexts
|
, FlexibleContexts
|
||||||
, FlexibleInstances
|
, FlexibleInstances
|
||||||
, FunctionalDependencies
|
, FunctionalDependencies
|
||||||
@ -29,6 +30,7 @@ module Database.Esqueleto.Internal.Sql
|
|||||||
, unsafeSqlBinOp
|
, unsafeSqlBinOp
|
||||||
, unsafeSqlValue
|
, unsafeSqlValue
|
||||||
, unsafeSqlFunction
|
, unsafeSqlFunction
|
||||||
|
, unsafeSqlExtractSubField
|
||||||
, UnsafeSqlFunctionArgument
|
, UnsafeSqlFunctionArgument
|
||||||
, rawSelectSource
|
, rawSelectSource
|
||||||
, runSource
|
, runSource
|
||||||
@ -40,6 +42,7 @@ module Database.Esqueleto.Internal.Sql
|
|||||||
, IdentInfo
|
, IdentInfo
|
||||||
, SqlSelect(..)
|
, SqlSelect(..)
|
||||||
, veryUnsafeCoerceSqlExprValue
|
, veryUnsafeCoerceSqlExprValue
|
||||||
|
, veryUnsafeCoerceSqlExprValueList
|
||||||
) where
|
) where
|
||||||
|
|
||||||
import Control.Applicative (Applicative(..), (<$>), (<$))
|
import Control.Applicative (Applicative(..), (<$>), (<$))
|
||||||
@ -48,13 +51,12 @@ import Control.Exception (throw, throwIO)
|
|||||||
import Control.Monad (ap, MonadPlus(..), liftM)
|
import Control.Monad (ap, MonadPlus(..), liftM)
|
||||||
import Control.Monad.IO.Class (MonadIO(..))
|
import Control.Monad.IO.Class (MonadIO(..))
|
||||||
import Control.Monad.Trans.Class (lift)
|
import Control.Monad.Trans.Class (lift)
|
||||||
import qualified Control.Monad.Trans.Resource as Res
|
import qualified Control.Monad.Trans.Reader as R
|
||||||
import Data.Int (Int64)
|
import Data.Int (Int64)
|
||||||
import Data.List (intersperse)
|
import Data.List (intersperse)
|
||||||
import Data.Monoid (Monoid(..), (<>))
|
import Data.Monoid (Monoid(..), (<>))
|
||||||
import Data.Proxy (Proxy(..))
|
import Data.Proxy (Proxy(..))
|
||||||
import Database.Esqueleto.Internal.PersistentImport
|
import Database.Esqueleto.Internal.PersistentImport
|
||||||
import qualified Control.Monad.Trans.Reader as R
|
|
||||||
import qualified Control.Monad.Trans.State as S
|
import qualified Control.Monad.Trans.State as S
|
||||||
import qualified Control.Monad.Trans.Writer as W
|
import qualified Control.Monad.Trans.Writer as W
|
||||||
import qualified Data.Conduit as C
|
import qualified Data.Conduit as C
|
||||||
@ -63,7 +65,6 @@ import qualified Data.HashSet as HS
|
|||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Data.Text.Lazy as TL
|
import qualified Data.Text.Lazy as TL
|
||||||
import qualified Data.Text.Lazy.Builder as TLB
|
import qualified Data.Text.Lazy.Builder as TLB
|
||||||
import qualified Data.Text.Lazy.Builder.Int as TLBI
|
|
||||||
import Data.Acquire (with, allocateAcquire, Acquire)
|
import Data.Acquire (with, allocateAcquire, Acquire)
|
||||||
import Control.Monad.Trans.Resource (MonadResource)
|
import Control.Monad.Trans.Resource (MonadResource)
|
||||||
|
|
||||||
@ -256,6 +257,7 @@ data SqlExpr a where
|
|||||||
|
|
||||||
-- A 'SqlExpr' accepted only by 'orderBy'.
|
-- A 'SqlExpr' accepted only by 'orderBy'.
|
||||||
EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy
|
EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy
|
||||||
|
EOrderRandom :: SqlExpr OrderBy
|
||||||
|
|
||||||
-- A 'SqlExpr' accepted only by 'set'.
|
-- A 'SqlExpr' accepted only by 'set'.
|
||||||
ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val)
|
ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val)
|
||||||
@ -265,6 +267,10 @@ data SqlExpr a where
|
|||||||
|
|
||||||
-- Used by 'insertSelect'.
|
-- Used by 'insertSelect'.
|
||||||
EInsert :: Proxy a -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a)
|
EInsert :: Proxy a -> (IdentInfo -> (TLB.Builder, [PersistValue])) -> SqlExpr (Insertion a)
|
||||||
|
EInsertFinal :: PersistEntity a => SqlExpr (Insertion a) -> SqlExpr InsertFinal
|
||||||
|
|
||||||
|
-- | Phantom type used to mark a @INSERT INTO@ query.
|
||||||
|
data InsertFinal
|
||||||
|
|
||||||
data NeedParens = Parens | Never
|
data NeedParens = Parens | Never
|
||||||
|
|
||||||
@ -318,6 +324,8 @@ instance Esqueleto SqlQuery SqlExpr SqlBackend where
|
|||||||
asc = EOrderBy ASC
|
asc = EOrderBy ASC
|
||||||
desc = EOrderBy DESC
|
desc = EOrderBy DESC
|
||||||
|
|
||||||
|
rand = EOrderRandom
|
||||||
|
|
||||||
limit n = Q $ W.tell mempty { sdLimitClause = Limit (Just n) Nothing }
|
limit n = Q $ W.tell mempty { sdLimitClause = Limit (Just n) Nothing }
|
||||||
offset n = Q $ W.tell mempty { sdLimitClause = Limit Nothing (Just n) }
|
offset n = Q $ W.tell mempty { sdLimitClause = Limit Nothing (Just n) }
|
||||||
|
|
||||||
@ -418,7 +426,7 @@ setAux field mkVal = ESet $ \ent -> unsafeSqlBinOp " = " name (mkVal ent)
|
|||||||
where name = ERaw Never $ \info -> (fieldName info field, mempty)
|
where name = ERaw Never $ \info -> (fieldName info field, mempty)
|
||||||
|
|
||||||
sub :: PersistField a => Mode -> SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a)
|
sub :: PersistField a => Mode -> SqlQuery (SqlExpr (Value a)) -> SqlExpr (Value a)
|
||||||
sub mode query = ERaw Parens $ \info -> toRawSql mode pureQuery info query
|
sub mode query = ERaw Parens $ \info -> toRawSql mode info query
|
||||||
|
|
||||||
fromDBName :: IdentInfo -> DBName -> TLB.Builder
|
fromDBName :: IdentInfo -> DBName -> TLB.Builder
|
||||||
fromDBName (conn, _) = TLB.fromText . connEscapeName conn
|
fromDBName (conn, _) = TLB.fromText . connEscapeName conn
|
||||||
@ -476,6 +484,19 @@ unsafeSqlFunction name arg =
|
|||||||
uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList arg
|
uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList arg
|
||||||
in (name <> parens argsTLB, argsVals)
|
in (name <> parens argsTLB, argsVals)
|
||||||
|
|
||||||
|
-- | (Internal) An unsafe SQL function to extract a subfield from a compound
|
||||||
|
-- field, e.g. datetime. See 'unsafeSqlBinOp' for warnings.
|
||||||
|
--
|
||||||
|
-- Since: 1.3.6.
|
||||||
|
unsafeSqlExtractSubField :: UnsafeSqlFunctionArgument a =>
|
||||||
|
TLB.Builder -> a -> SqlExpr (Value b)
|
||||||
|
unsafeSqlExtractSubField subField arg =
|
||||||
|
ERaw Never $ \info ->
|
||||||
|
let (argsTLB, argsVals) =
|
||||||
|
uncommas' $ map (\(ERaw _ f) -> f info) $ toArgList arg
|
||||||
|
in ("EXTRACT" <> parens (subField <> " FROM " <> argsTLB), argsVals)
|
||||||
|
|
||||||
|
|
||||||
class UnsafeSqlFunctionArgument a where
|
class UnsafeSqlFunctionArgument a where
|
||||||
toArgList :: a -> [SqlExpr (Value ())]
|
toArgList :: a -> [SqlExpr (Value ())]
|
||||||
instance (a ~ Value b) => UnsafeSqlFunctionArgument (SqlExpr a) where
|
instance (a ~ Value b) => UnsafeSqlFunctionArgument (SqlExpr a) where
|
||||||
@ -500,6 +521,7 @@ instance ( UnsafeSqlFunctionArgument a
|
|||||||
toArgList = toArgList . from4
|
toArgList = toArgList . from4
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
-- | (Internal) Coerce a value's type from 'SqlExpr (Value a)' to
|
-- | (Internal) Coerce a value's type from 'SqlExpr (Value a)' to
|
||||||
-- 'SqlExpr (Value b)'. You should /not/ use this function
|
-- 'SqlExpr (Value b)'. You should /not/ use this function
|
||||||
-- unless you know what you're doing!
|
-- unless you know what you're doing!
|
||||||
@ -536,7 +558,7 @@ rawSelectSource mode query =
|
|||||||
run conn =
|
run conn =
|
||||||
uncurry rawQueryRes $
|
uncurry rawQueryRes $
|
||||||
first builderToText $
|
first builderToText $
|
||||||
toRawSql mode pureQuery (conn, initialIdentState) query
|
toRawSql mode (conn, initialIdentState) query
|
||||||
|
|
||||||
massage = do
|
massage = do
|
||||||
mrow <- C.await
|
mrow <- C.await
|
||||||
@ -649,15 +671,15 @@ runSource src = src C.$$ CL.consume
|
|||||||
|
|
||||||
-- | (Internal) Execute an @esqueleto@ statement inside
|
-- | (Internal) Execute an @esqueleto@ statement inside
|
||||||
-- @persistent@'s 'SqlPersistT' monad.
|
-- @persistent@'s 'SqlPersistT' monad.
|
||||||
rawEsqueleto :: ( MonadIO m )
|
rawEsqueleto :: ( MonadIO m, SqlSelect a r )
|
||||||
=> Mode
|
=> Mode
|
||||||
-> SqlQuery ()
|
-> SqlQuery a
|
||||||
-> SqlPersistT m Int64
|
-> SqlPersistT m Int64
|
||||||
rawEsqueleto mode query = do
|
rawEsqueleto mode query = do
|
||||||
conn <- R.ask
|
conn <- R.ask
|
||||||
uncurry rawExecuteCount $
|
uncurry rawExecuteCount $
|
||||||
first builderToText $
|
first builderToText $
|
||||||
toRawSql mode pureQuery (conn, initialIdentState) query
|
toRawSql mode (conn, initialIdentState) query
|
||||||
|
|
||||||
|
|
||||||
-- | Execute an @esqueleto@ @DELETE@ query inside @persistent@'s
|
-- | Execute an @esqueleto@ @DELETE@ query inside @persistent@'s
|
||||||
@ -737,8 +759,8 @@ builderToText = TL.toStrict . TLB.toLazyTextWith defaultChunkSize
|
|||||||
-- @esqueleto@, instead of manually using this function (which is
|
-- @esqueleto@, instead of manually using this function (which is
|
||||||
-- possible but tedious), you may just turn on query logging of
|
-- possible but tedious), you may just turn on query logging of
|
||||||
-- @persistent@.
|
-- @persistent@.
|
||||||
toRawSql :: SqlSelect a r => Mode -> QueryType a -> IdentInfo -> SqlQuery a -> (TLB.Builder, [PersistValue])
|
toRawSql :: SqlSelect a r => Mode -> IdentInfo -> SqlQuery a -> (TLB.Builder, [PersistValue])
|
||||||
toRawSql mode qt (conn, firstIdentState) query =
|
toRawSql mode (conn, firstIdentState) query =
|
||||||
let ((ret, sd), finalIdentState) =
|
let ((ret, sd), finalIdentState) =
|
||||||
flip S.runState firstIdentState $
|
flip S.runState firstIdentState $
|
||||||
W.runWriterT $
|
W.runWriterT $
|
||||||
@ -756,36 +778,27 @@ toRawSql mode qt (conn, firstIdentState) query =
|
|||||||
-- appear on the expressions below.
|
-- appear on the expressions below.
|
||||||
info = (conn, finalIdentState)
|
info = (conn, finalIdentState)
|
||||||
in mconcat
|
in mconcat
|
||||||
[ makeInsert qt ret
|
[ makeInsertInto info mode ret
|
||||||
, makeSelect info mode ret
|
, makeSelect info mode ret
|
||||||
, makeFrom info mode fromClauses
|
, makeFrom info mode fromClauses
|
||||||
, makeSet info setClauses
|
, makeSet info setClauses
|
||||||
, makeWhere info whereClauses
|
, makeWhere info whereClauses
|
||||||
, makeGroupBy info groupByClause
|
, makeGroupBy info groupByClause
|
||||||
, makeHaving info havingClause
|
, makeHaving info havingClause
|
||||||
, makeOrderBy info orderByClauses
|
, makeOrderBy info orderByClauses
|
||||||
, makeLimit info limitClause
|
, makeLimit info limitClause orderByClauses
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
-- | (Internal) Mode of query being converted by 'toRawSql'.
|
-- | (Internal) Mode of query being converted by 'toRawSql'.
|
||||||
data Mode = SELECT | SELECT_DISTINCT | DELETE | UPDATE
|
data Mode =
|
||||||
|
SELECT
|
||||||
|
| SELECT_DISTINCT
|
||||||
|
| DELETE
|
||||||
|
| UPDATE
|
||||||
|
| INSERT_INTO Mode
|
||||||
|
-- ^ 'Mode' should be either 'SELECT' or 'SELECT_DISTINCT'.
|
||||||
|
|
||||||
newtype QueryType a = QueryType { unQueryType :: a -> TLB.Builder }
|
|
||||||
|
|
||||||
pureQuery :: QueryType a
|
|
||||||
pureQuery = QueryType (const mempty)
|
|
||||||
|
|
||||||
insertQuery :: PersistEntity a => QueryType (SqlExpr (Insertion a))
|
|
||||||
insertQuery = QueryType $ \(EInsert p _)->
|
|
||||||
let def = entityDef p
|
|
||||||
unName = TLB.fromText . unDBName
|
|
||||||
fields = uncommas $ map (unName . fieldDB) (entityFields def)
|
|
||||||
table = unName . entityDB . entityDef $ p
|
|
||||||
in "INSERT INTO " <> table <> parens fields <> "\n"
|
|
||||||
|
|
||||||
makeInsert :: QueryType a -> a -> (TLB.Builder, [PersistValue])
|
|
||||||
makeInsert q a = (unQueryType q a, [])
|
|
||||||
|
|
||||||
uncommas :: [TLB.Builder] -> TLB.Builder
|
uncommas :: [TLB.Builder] -> TLB.Builder
|
||||||
uncommas = mconcat . intersperse ", " . filter (/= mempty)
|
uncommas = mconcat . intersperse ", " . filter (/= mempty)
|
||||||
@ -794,14 +807,21 @@ uncommas' :: Monoid a => [(TLB.Builder, a)] -> (TLB.Builder, a)
|
|||||||
uncommas' = (uncommas *** mconcat) . unzip
|
uncommas' = (uncommas *** mconcat) . unzip
|
||||||
|
|
||||||
|
|
||||||
|
makeInsertInto :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue])
|
||||||
|
makeInsertInto info (INSERT_INTO _) ret = sqlInsertInto info ret
|
||||||
|
makeInsertInto _ _ _ = mempty
|
||||||
|
|
||||||
|
|
||||||
makeSelect :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue])
|
makeSelect :: SqlSelect a r => IdentInfo -> Mode -> a -> (TLB.Builder, [PersistValue])
|
||||||
makeSelect info mode ret =
|
makeSelect info mode_ ret = process mode_
|
||||||
case mode of
|
|
||||||
SELECT -> withCols "SELECT "
|
|
||||||
SELECT_DISTINCT -> withCols "SELECT DISTINCT "
|
|
||||||
DELETE -> plain "DELETE "
|
|
||||||
UPDATE -> plain "UPDATE "
|
|
||||||
where
|
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)
|
withCols v = first (v <>) (sqlSelectCols info ret)
|
||||||
plain v = (v, [])
|
plain v = (v, [])
|
||||||
|
|
||||||
@ -873,27 +893,19 @@ makeOrderBy :: IdentInfo -> [OrderByClause] -> (TLB.Builder, [PersistValue])
|
|||||||
makeOrderBy _ [] = mempty
|
makeOrderBy _ [] = mempty
|
||||||
makeOrderBy info os = first ("\nORDER BY " <>) $ uncommas' (map mk os)
|
makeOrderBy info os = first ("\nORDER BY " <>) $ uncommas' (map mk os)
|
||||||
where
|
where
|
||||||
|
mk :: OrderByClause -> (TLB.Builder, [PersistValue])
|
||||||
mk (EOrderBy t (ERaw p f)) = first ((<> orderByType t) . parensM p) (f info)
|
mk (EOrderBy t (ERaw p f)) = first ((<> orderByType t) . parensM p) (f info)
|
||||||
|
mk EOrderRandom = first ((<> "RANDOM()")) mempty
|
||||||
orderByType ASC = " ASC"
|
orderByType ASC = " ASC"
|
||||||
orderByType DESC = " DESC"
|
orderByType DESC = " DESC"
|
||||||
|
|
||||||
|
|
||||||
makeLimit :: IdentInfo -> LimitClause -> (TLB.Builder, [PersistValue])
|
makeLimit :: IdentInfo -> LimitClause -> [OrderByClause] -> (TLB.Builder, [PersistValue])
|
||||||
makeLimit _ (Limit Nothing Nothing) = mempty
|
makeLimit (conn,_) (Limit ml mo) orderByClauses =
|
||||||
makeLimit _ (Limit Nothing (Just 0)) = mempty
|
let limitRaw = connLimitOffset conn (v ml, v mo) hasOrderClause "\n"
|
||||||
makeLimit info (Limit ml mo) = (ret, mempty)
|
hasOrderClause = not (null orderByClauses)
|
||||||
where
|
v = maybe 0 fromIntegral
|
||||||
ret = TLB.singleton '\n' <> (limitTLB <> offsetTLB)
|
in (TLB.fromText limitRaw, mempty)
|
||||||
|
|
||||||
limitTLB =
|
|
||||||
case ml of
|
|
||||||
Just l -> "LIMIT " <> TLBI.decimal l
|
|
||||||
Nothing -> TLB.fromText (connNoLimit $ fst info)
|
|
||||||
|
|
||||||
offsetTLB =
|
|
||||||
case mo of
|
|
||||||
Just o -> " OFFSET " <> TLBI.decimal o
|
|
||||||
Nothing -> mempty
|
|
||||||
|
|
||||||
|
|
||||||
parens :: TLB.Builder -> TLB.Builder
|
parens :: TLB.Builder -> TLB.Builder
|
||||||
@ -921,14 +933,25 @@ class SqlSelect a r | a -> r, r -> a where
|
|||||||
-- | Transform a row of the result into the data type.
|
-- | Transform a row of the result into the data type.
|
||||||
sqlSelectProcessRow :: [PersistValue] -> Either T.Text r
|
sqlSelectProcessRow :: [PersistValue] -> Either T.Text r
|
||||||
|
|
||||||
|
-- | Create @INSERT INTO@ clause instead.
|
||||||
|
sqlInsertInto :: IdentInfo -> a -> (TLB.Builder, [PersistValue])
|
||||||
|
sqlInsertInto = error "Type does not support sqlInsertInto."
|
||||||
|
|
||||||
-- | You may return an insertion of some PersistEntity
|
|
||||||
instance PersistEntity a => SqlSelect (SqlExpr (Insertion a)) (Insertion a) where
|
-- | @INSERT INTO@ hack.
|
||||||
sqlSelectCols info (EInsert _ f) = f info
|
instance SqlSelect (SqlExpr InsertFinal) InsertFinal where
|
||||||
sqlSelectColCount = const 0
|
sqlInsertInto info (EInsertFinal (EInsert p _)) =
|
||||||
|
let fields = uncommas $
|
||||||
|
map (fromDBName info . fieldDB) $
|
||||||
|
entityFields $
|
||||||
|
entityDef p
|
||||||
|
table = fromDBName info . entityDB . entityDef $ p
|
||||||
|
in ("INSERT INTO " <> table <> parens fields <> "\n", [])
|
||||||
|
sqlSelectCols info (EInsertFinal (EInsert _ f)) = f info
|
||||||
|
sqlSelectColCount = const 0
|
||||||
sqlSelectProcessRow = const (Right (error msg))
|
sqlSelectProcessRow = const (Right (error msg))
|
||||||
where
|
where
|
||||||
msg = "sqlSelectProcessRow/SqlSelect (SqlExpr (Insertion a)) (Insertion a): never here"
|
msg = "sqlSelectProcessRow/SqlSelect/InsertionFinal: never here"
|
||||||
|
|
||||||
|
|
||||||
-- | Not useful for 'select', but used for 'update' and 'delete'.
|
-- | Not useful for 'select', but used for 'update' and 'delete'.
|
||||||
@ -1480,19 +1503,18 @@ to16 ((a,b),(c,d),(e,f),(g,h),(i,j),(k,l),(m,n),(o,p)) = (a,b,c,d,e,f,g,h,i,j,k,
|
|||||||
|
|
||||||
|
|
||||||
-- | Insert a 'PersistField' for every selected value.
|
-- | Insert a 'PersistField' for every selected value.
|
||||||
insertSelect :: (MonadIO m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) =>
|
insertSelect :: (MonadIO m, PersistEntity a) =>
|
||||||
SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
|
SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
|
||||||
insertSelect = insertGeneralSelect SELECT
|
insertSelect = insertGeneralSelect SELECT
|
||||||
|
|
||||||
|
|
||||||
-- | Insert a 'PersistField' for every unique selected value.
|
-- | Insert a 'PersistField' for every unique selected value.
|
||||||
insertSelectDistinct :: (MonadIO m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) =>
|
insertSelectDistinct :: (MonadIO m, PersistEntity a) =>
|
||||||
SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
|
SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
|
||||||
insertSelectDistinct = insertGeneralSelect SELECT_DISTINCT
|
insertSelectDistinct = insertGeneralSelect SELECT_DISTINCT
|
||||||
|
|
||||||
|
|
||||||
insertGeneralSelect :: (MonadIO m, SqlSelect (SqlExpr (Insertion a)) r, PersistEntity a) =>
|
insertGeneralSelect :: (MonadIO m, PersistEntity a) =>
|
||||||
Mode -> SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
|
Mode -> SqlQuery (SqlExpr (Insertion a)) -> SqlPersistT m ()
|
||||||
insertGeneralSelect mode query = do
|
insertGeneralSelect mode =
|
||||||
conn <- R.ask
|
liftM (const ()) . rawEsqueleto (INSERT_INTO mode) . fmap EInsertFinal
|
||||||
uncurry rawExecute $ first builderToText $ toRawSql mode insertQuery (conn, initialIdentState) query
|
|
||||||
|
|||||||
77
test/Test.hs
77
test/Test.hs
@ -21,7 +21,6 @@ import Control.Monad.Logger (MonadLogger(..), runStderrLoggingT, runNoLoggingT)
|
|||||||
import Control.Monad.Trans.Control (MonadBaseControl(..))
|
import Control.Monad.Trans.Control (MonadBaseControl(..))
|
||||||
import Control.Monad.Trans.Reader (ReaderT)
|
import Control.Monad.Trans.Reader (ReaderT)
|
||||||
import Database.Esqueleto
|
import Database.Esqueleto
|
||||||
import Database.Persist.Sqlite (withSqliteConn)
|
|
||||||
#if defined (WITH_POSTGRESQL)
|
#if defined (WITH_POSTGRESQL)
|
||||||
import Database.Persist.Postgresql (withPostgresqlConn)
|
import Database.Persist.Postgresql (withPostgresqlConn)
|
||||||
#elif defined (WITH_MYSQL)
|
#elif defined (WITH_MYSQL)
|
||||||
@ -31,11 +30,13 @@ import Database.Persist.MySQL ( withMySQLConn
|
|||||||
, connectUser
|
, connectUser
|
||||||
, connectPassword
|
, connectPassword
|
||||||
, defaultConnectInfo)
|
, defaultConnectInfo)
|
||||||
|
#else
|
||||||
|
import Database.Persist.Sqlite (withSqliteConn)
|
||||||
#endif
|
#endif
|
||||||
import Database.Persist.TH
|
import Database.Persist.TH
|
||||||
import Test.Hspec
|
import Test.Hspec
|
||||||
|
|
||||||
import qualified Data.Conduit as C
|
import qualified Control.Monad.Trans.Resource as R
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
import qualified Data.List as L
|
import qualified Data.List as L
|
||||||
|
|
||||||
@ -122,8 +123,8 @@ main = do
|
|||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
p1k <- insert p1
|
||||||
p2k <- insert p2
|
p2k <- insert p2
|
||||||
f1k <- insert (Follow p1k p2k)
|
_f1k <- insert (Follow p1k p2k)
|
||||||
f2k <- insert (Follow p2k p1k)
|
_f2k <- insert (Follow p2k p1k)
|
||||||
ret <- select $
|
ret <- select $
|
||||||
from $ \followA -> do
|
from $ \followA -> do
|
||||||
let subquery =
|
let subquery =
|
||||||
@ -138,8 +139,8 @@ main = do
|
|||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
p1k <- insert p1
|
||||||
p2k <- insert p2
|
p2k <- insert p2
|
||||||
f1k <- insert (Follow p1k p2k)
|
_f1k <- insert (Follow p1k p2k)
|
||||||
f2k <- insert (Follow p2k p1k)
|
_f2k <- insert (Follow p2k p1k)
|
||||||
ret <- select $
|
ret <- select $
|
||||||
from $ \followA -> do
|
from $ \followA -> do
|
||||||
where_ $ exists $
|
where_ $ exists $
|
||||||
@ -324,7 +325,13 @@ main = do
|
|||||||
ret <- select $
|
ret <- select $
|
||||||
from $ \p->
|
from $ \p->
|
||||||
return $ joinV $ sum_ (p ^. PersonAge)
|
return $ joinV $ sum_ (p ^. PersonAge)
|
||||||
|
#if defined(WITH_POSTGRESQL)
|
||||||
|
liftIO $ ret `shouldBe` [ Value $ Just (36 + 17 + 17 :: Rational ) ]
|
||||||
|
#elif defined(WITH_MYSQL)
|
||||||
|
liftIO $ ret `shouldBe` [ Value $ Just (36 + 17 + 17 :: Double ) ]
|
||||||
|
#else
|
||||||
liftIO $ ret `shouldBe` [ Value $ Just (36 + 17 + 17 :: Int) ]
|
liftIO $ ret `shouldBe` [ Value $ Just (36 + 17 + 17 :: Int) ]
|
||||||
|
#endif
|
||||||
|
|
||||||
it "works with avg_" $
|
it "works with avg_" $
|
||||||
run $ do
|
run $ do
|
||||||
@ -362,9 +369,9 @@ main = do
|
|||||||
it "works with random_" $
|
it "works with random_" $
|
||||||
run $ do
|
run $ do
|
||||||
#if defined(WITH_POSTGRESQL) || defined(WITH_MYSQL)
|
#if defined(WITH_POSTGRESQL) || defined(WITH_MYSQL)
|
||||||
ret <- select $ return (random_ :: SqlExpr (Value Double))
|
_ <- select $ return (random_ :: SqlExpr (Value Double))
|
||||||
#else
|
#else
|
||||||
ret <- select $ return (random_ :: SqlExpr (Value Int))
|
_ <- select $ return (random_ :: SqlExpr (Value Int))
|
||||||
#endif
|
#endif
|
||||||
return ()
|
return ()
|
||||||
|
|
||||||
@ -524,10 +531,10 @@ main = do
|
|||||||
|
|
||||||
it "works with asc random_" $
|
it "works with asc random_" $
|
||||||
run $ do
|
run $ do
|
||||||
p1e <- insert' p1
|
_p1e <- insert' p1
|
||||||
p2e <- insert' p2
|
_p2e <- insert' p2
|
||||||
p3e <- insert' p3
|
_p3e <- insert' p3
|
||||||
p4e <- insert' p4
|
_p4e <- insert' p4
|
||||||
rets <-
|
rets <-
|
||||||
fmap S.fromList $
|
fmap S.fromList $
|
||||||
replicateM 11 $
|
replicateM 11 $
|
||||||
@ -674,7 +681,7 @@ main = do
|
|||||||
it "GROUP BY works with HAVING" $
|
it "GROUP BY works with HAVING" $
|
||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
p1k <- insert p1
|
||||||
p2k <- insert p2
|
_p2k <- insert p2
|
||||||
p3k <- insert p3
|
p3k <- insert p3
|
||||||
replicateM_ 3 (insert $ BlogPost "" p1k)
|
replicateM_ 3 (insert $ BlogPost "" p1k)
|
||||||
replicateM_ 7 (insert $ BlogPost "" p3k)
|
replicateM_ 7 (insert $ BlogPost "" p3k)
|
||||||
@ -694,7 +701,7 @@ main = do
|
|||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
p1k <- insert p1
|
||||||
p2k <- insert p2
|
p2k <- insert p2
|
||||||
p3k <- insert p3
|
_p3k <- insert p3
|
||||||
ret <- select $
|
ret <- select $
|
||||||
from $ \p -> do
|
from $ \p -> do
|
||||||
where_ (p ^. PersonName `in_` valList (personName <$> [p1, p2]))
|
where_ (p ^. PersonName `in_` valList (personName <$> [p1, p2]))
|
||||||
@ -704,9 +711,9 @@ main = do
|
|||||||
|
|
||||||
it "IN works for valList (null list)" $
|
it "IN works for valList (null list)" $
|
||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
_p1k <- insert p1
|
||||||
p2k <- insert p2
|
_p2k <- insert p2
|
||||||
p3k <- insert p3
|
_p3k <- insert p3
|
||||||
ret <- select $
|
ret <- select $
|
||||||
from $ \p -> do
|
from $ \p -> do
|
||||||
where_ (p ^. PersonName `in_` valList [])
|
where_ (p ^. PersonName `in_` valList [])
|
||||||
@ -716,7 +723,7 @@ main = do
|
|||||||
it "IN works for subList_select" $
|
it "IN works for subList_select" $
|
||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
p1k <- insert p1
|
||||||
p2k <- insert p2
|
_p2k <- insert p2
|
||||||
p3k <- insert p3
|
p3k <- insert p3
|
||||||
_ <- insert (BlogPost "" p1k)
|
_ <- insert (BlogPost "" p1k)
|
||||||
_ <- insert (BlogPost "" p3k)
|
_ <- insert (BlogPost "" p3k)
|
||||||
@ -750,7 +757,7 @@ main = do
|
|||||||
it "EXISTS works for subList_select" $
|
it "EXISTS works for subList_select" $
|
||||||
run $ do
|
run $ do
|
||||||
p1k <- insert p1
|
p1k <- insert p1
|
||||||
p2k <- insert p2
|
_p2k <- insert p2
|
||||||
p3k <- insert p3
|
p3k <- insert p3
|
||||||
_ <- insert (BlogPost "" p1k)
|
_ <- insert (BlogPost "" p1k)
|
||||||
_ <- insert (BlogPost "" p3k)
|
_ <- insert (BlogPost "" p3k)
|
||||||
@ -786,9 +793,29 @@ main = do
|
|||||||
_ <- insert p3
|
_ <- insert p3
|
||||||
insertSelect $ from $ \p -> do
|
insertSelect $ from $ \p -> do
|
||||||
return $ BlogPost <# val "FakePost" <&> (p ^. PersonId)
|
return $ BlogPost <# val "FakePost" <&> (p ^. PersonId)
|
||||||
ret <- select $ from (\(b::(SqlExpr (Entity BlogPost))) -> return countRows)
|
ret <- select $ from (\(_::(SqlExpr (Entity BlogPost))) -> return countRows)
|
||||||
liftIO $ ret `shouldBe` [Value (3::Int)]
|
liftIO $ ret `shouldBe` [Value (3::Int)]
|
||||||
|
|
||||||
|
describe "rand works" $ do
|
||||||
|
it "returns result in random order" $
|
||||||
|
run $ do
|
||||||
|
replicateM_ 20 $ do
|
||||||
|
_ <- insert p1
|
||||||
|
_ <- insert p2
|
||||||
|
_ <- insert p3
|
||||||
|
_ <- insert p4
|
||||||
|
_ <- insert $ Person "Jane" Nothing
|
||||||
|
_ <- insert $ Person "Mark" Nothing
|
||||||
|
_ <- insert $ Person "Sarah" Nothing
|
||||||
|
insert $ Person "Paul" Nothing
|
||||||
|
ret1 <- fmap (map unValue) $ select $ from $ \p -> do
|
||||||
|
orderBy [rand]
|
||||||
|
return (p ^. PersonId)
|
||||||
|
ret2 <- fmap (map unValue) $ select $ from $ \p -> do
|
||||||
|
orderBy [rand]
|
||||||
|
return (p ^. PersonId)
|
||||||
|
|
||||||
|
liftIO $ (ret1 == ret2) `shouldBe` False
|
||||||
|
|
||||||
----------------------------------------------------------------------
|
----------------------------------------------------------------------
|
||||||
|
|
||||||
@ -802,7 +829,7 @@ insert' v = flip Entity v <$> insert v
|
|||||||
|
|
||||||
|
|
||||||
type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m
|
type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m
|
||||||
, C.MonadUnsafeIO m, C.MonadThrow m )
|
, R.MonadThrow m )
|
||||||
|
|
||||||
#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)
|
#if defined (WITH_POSTGRESQL) || defined (WITH_MYSQL)
|
||||||
-- With SQLite and in-memory databases, a separate connection implies a
|
-- With SQLite and in-memory databases, a separate connection implies a
|
||||||
@ -811,7 +838,7 @@ type RunDbMonad m = ( MonadBaseControl IO m, MonadIO m, MonadLogger m
|
|||||||
-- TODO: there is certainly a better way...
|
-- TODO: there is certainly a better way...
|
||||||
cleanDB
|
cleanDB
|
||||||
:: (forall m. RunDbMonad m
|
:: (forall m. RunDbMonad m
|
||||||
=> SqlPersistT (C.ResourceT m) ())
|
=> SqlPersistT (R.ResourceT m) ())
|
||||||
cleanDB = do
|
cleanDB = do
|
||||||
delete $ from $ \(blogpost :: SqlExpr (Entity BlogPost))-> return ()
|
delete $ from $ \(blogpost :: SqlExpr (Entity BlogPost))-> return ()
|
||||||
delete $ from $ \(follow :: SqlExpr (Entity Follow)) -> return ()
|
delete $ from $ \(follow :: SqlExpr (Entity Follow)) -> return ()
|
||||||
@ -819,7 +846,7 @@ cleanDB = do
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
||||||
run, runSilent, runVerbose :: (forall m. RunDbMonad m => SqlPersistT (C.ResourceT m) a) -> IO a
|
run, runSilent, runVerbose :: (forall m. RunDbMonad m => SqlPersistT (R.ResourceT m) a) -> IO a
|
||||||
runSilent act = runNoLoggingT $ run_worker act
|
runSilent act = runNoLoggingT $ run_worker act
|
||||||
runVerbose act = runStderrLoggingT $ run_worker act
|
runVerbose act = runStderrLoggingT $ run_worker act
|
||||||
run =
|
run =
|
||||||
@ -832,9 +859,9 @@ verbose :: Bool
|
|||||||
verbose = True
|
verbose = True
|
||||||
|
|
||||||
|
|
||||||
run_worker :: RunDbMonad m => SqlPersistT (C.ResourceT m) a -> m a
|
run_worker :: RunDbMonad m => SqlPersistT (R.ResourceT m) a -> m a
|
||||||
run_worker act =
|
run_worker act =
|
||||||
C.runResourceT .
|
R.runResourceT .
|
||||||
#if defined(WITH_POSTGRESQL)
|
#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)
|
#elif defined (WITH_MYSQL)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user