New functions limit and offset.

This commit is contained in:
Felipe Lessa 2012-09-09 14:28:28 -03:00
parent dfd4b4860c
commit 94b19267ee
4 changed files with 144 additions and 60 deletions

View File

@ -18,7 +18,7 @@ module Database.Esqueleto
-- $gettingstarted -- $gettingstarted
-- * @esqueleto@'s Language -- * @esqueleto@'s Language
Esqueleto( where_, on, orderBy, asc, desc Esqueleto( where_, on, orderBy, asc, desc, limit, offset
, sub_select, sub_selectDistinct, (^.), (?.) , sub_select, sub_selectDistinct, (^.), (?.)
, val, isNothing, just, nothing, countRows, not_ , val, isNothing, just, nothing, countRows, not_
, (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.) , (==.), (>=.), (>.), (<=.), (<.), (!=.), (&&.), (||.)

View File

@ -33,6 +33,7 @@ module Database.Esqueleto.Internal.Language
import Control.Applicative (Applicative(..), (<$>)) import Control.Applicative (Applicative(..), (<$>))
import Control.Exception (Exception) import Control.Exception (Exception)
import Data.Int (Int64)
import Data.String (IsString) import Data.String (IsString)
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
import Database.Persist.GenericSql import Database.Persist.GenericSql
@ -136,6 +137,12 @@ class (Functor query, Applicative query, Monad query) =>
-- | Descending order of this field or expression. -- | Descending order of this field or expression.
desc :: PersistField a => expr (Value a) -> expr OrderBy desc :: PersistField a => expr (Value a) -> expr OrderBy
-- | @LIMIT@. Limit the number of returned rows.
limit :: Int64 -> query ()
-- | @OFFSET@. Usually used with 'limit'.
offset :: Int64 -> query ()
-- | Execute a subquery @SELECT@ in an expression. -- | Execute a subquery @SELECT@ in an expression.
sub_select :: PersistField a => query (expr (Value a)) -> expr (Value a) sub_select :: PersistField a => query (expr (Value a)) -> expr (Value a)

View File

@ -31,7 +31,6 @@ module Database.Esqueleto.Internal.Sql
, rawExecute , rawExecute
, toRawSql , toRawSql
, Mode(..) , Mode(..)
, Escape
, SqlSelect , SqlSelect
, veryUnsafeCoerceSqlExprValue , veryUnsafeCoerceSqlExprValue
) where ) where
@ -44,11 +43,12 @@ import Control.Monad.IO.Class (MonadIO(..))
import Control.Monad.Logger (MonadLogger) import Control.Monad.Logger (MonadLogger)
import Control.Monad.Trans.Class (lift) import Control.Monad.Trans.Class (lift)
import Control.Monad.Trans.Resource (MonadResourceBase) import Control.Monad.Trans.Resource (MonadResourceBase)
import Data.Int (Int64)
import Data.List (intersperse) import Data.List (intersperse)
import Data.Monoid (Monoid(..), (<>)) import Data.Monoid (Monoid(..), (<>))
import Database.Persist.EntityDef import Database.Persist.EntityDef
import Database.Persist.GenericSql import Database.Persist.GenericSql
import Database.Persist.GenericSql.Internal (Connection(escapeName)) import Database.Persist.GenericSql.Internal (Connection(escapeName, noLimit))
import Database.Persist.GenericSql.Raw (withStmt, execute) import Database.Persist.GenericSql.Raw (withStmt, execute)
import Database.Persist.Store hiding (delete) import Database.Persist.Store hiding (delete)
import qualified Control.Monad.Trans.Reader as R import qualified Control.Monad.Trans.Reader as R
@ -60,6 +60,7 @@ 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 Database.Esqueleto.Internal.Language import Database.Esqueleto.Internal.Language
@ -93,12 +94,13 @@ data SideData = SideData { sdFromClause :: ![FromClause]
, sdSetClause :: ![SetClause] , sdSetClause :: ![SetClause]
, sdWhereClause :: !WhereClause , sdWhereClause :: !WhereClause
, sdOrderByClause :: ![OrderByClause] , sdOrderByClause :: ![OrderByClause]
, sdLimitClause :: !LimitClause
} }
instance Monoid SideData where instance Monoid SideData where
mempty = SideData mempty mempty mempty mempty mempty = SideData mempty mempty mempty mempty mempty
SideData f s w o `mappend` SideData f' s' w' o' = SideData f s w o l `mappend` SideData f' s' w' o' l' =
SideData (f <> f') (s <> s') (w <> w') (o <> o') SideData (f <> f') (s <> s') (w <> w') (o <> o') (l <> l')
-- | A part of a @FROM@ clause. -- | A part of a @FROM@ clause.
@ -155,6 +157,18 @@ instance Monoid WhereClause where
type OrderByClause = SqlExpr OrderBy type OrderByClause = SqlExpr OrderBy
-- | A @LIMIT@ clause.
data LimitClause = Limit (Maybe Int64) (Maybe Int64)
instance Monoid LimitClause where
mempty = Limit mzero mzero
Limit l1 o1 `mappend` Limit l2 o2 =
Limit (l2 `mplus` l1) (o2 `mplus` o1)
-- More than one 'limit' or 'offset' is issued, we want to
-- keep the latest one. That's why we use mplus with
-- "reversed" arguments.
---------------------------------------------------------------------- ----------------------------------------------------------------------
@ -193,8 +207,8 @@ newIdentFor = Q . lift . try . unDBName
-- | Use an identifier. -- | Use an identifier.
useIdent :: Escape -> Ident -> TLB.Builder useIdent :: Connection -> Ident -> TLB.Builder
useIdent esc (I ident) = esc (DBName ident) useIdent conn (I ident) = fromDBName conn $ DBName ident
---------------------------------------------------------------------- ----------------------------------------------------------------------
@ -204,7 +218,7 @@ useIdent esc (I ident) = esc (DBName ident)
data SqlExpr a where data SqlExpr a where
EEntity :: Ident -> SqlExpr (Entity val) EEntity :: Ident -> SqlExpr (Entity val)
EMaybe :: SqlExpr a -> SqlExpr (Maybe a) EMaybe :: SqlExpr a -> SqlExpr (Maybe a)
ERaw :: NeedParens -> (Escape -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a) ERaw :: NeedParens -> (Connection -> (TLB.Builder, [PersistValue])) -> SqlExpr (Value a)
EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy EOrderBy :: OrderByType -> SqlExpr (Value a) -> SqlExpr OrderBy
ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val) ESet :: (SqlExpr (Entity val) -> SqlExpr (Value ())) -> SqlExpr (Update val)
EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a) EPreprocessedFrom :: a -> FromClause -> SqlExpr (PreprocessedFrom a)
@ -217,9 +231,6 @@ parensM Parens = parens
data OrderByType = ASC | DESC data OrderByType = ASC | DESC
-- | (Internal) Backend-specific function that escapes a 'DBName'.
type Escape = DBName -> TLB.Builder
instance Esqueleto SqlQuery SqlExpr SqlPersist where instance Esqueleto SqlQuery SqlExpr SqlPersist where
fromStart = x fromStart = x
@ -260,11 +271,14 @@ instance Esqueleto SqlQuery SqlExpr SqlPersist where
asc = EOrderBy ASC asc = EOrderBy ASC
desc = EOrderBy DESC desc = EOrderBy DESC
limit n = Q $ W.tell mempty { sdLimitClause = Limit (Just n) Nothing }
offset n = Q $ W.tell mempty { sdLimitClause = Limit Nothing (Just n) }
sub_select = sub SELECT sub_select = sub SELECT
sub_selectDistinct = sub SELECT_DISTINCT sub_selectDistinct = sub SELECT_DISTINCT
EEntity ident ^. field = EEntity ident ^. field =
ERaw Never $ \esc -> (useIdent esc ident <> ("." <> fieldName esc field), []) ERaw Never $ \conn -> (useIdent conn ident <> ("." <> fieldName conn field), [])
EMaybe r ?. field = maybelize (r ^. field) EMaybe r ?. field = maybelize (r ^. field)
where where
@ -278,8 +292,8 @@ instance Esqueleto SqlQuery SqlExpr SqlPersist where
nothing = unsafeSqlValue "NULL" nothing = unsafeSqlValue "NULL"
countRows = unsafeSqlValue "COUNT(*)" countRows = unsafeSqlValue "COUNT(*)"
not_ (ERaw p f) = ERaw Never $ \esc -> let (b, vals) = f esc not_ (ERaw p f) = ERaw Never $ \conn -> let (b, vals) = f conn
in ("NOT " <> parensM p b, vals) in ("NOT " <> parensM p b, vals)
(==.) = unsafeSqlBinOp " = " (==.) = unsafeSqlBinOp " = "
(>=.) = unsafeSqlBinOp " >= " (>=.) = unsafeSqlBinOp " >= "
@ -311,18 +325,18 @@ instance Esqueleto SqlQuery SqlExpr SqlPersist where
fieldName :: (PersistEntity val, PersistField typ) fieldName :: (PersistEntity val, PersistField typ)
=> Escape -> EntityField val typ -> TLB.Builder => Connection -> EntityField val typ -> TLB.Builder
fieldName esc = esc . fieldDB . persistFieldDef fieldName conn = fromDBName conn . fieldDB . persistFieldDef
setAux :: (PersistEntity val, PersistField typ) setAux :: (PersistEntity val, PersistField typ)
=> EntityField val typ => EntityField val typ
-> (SqlExpr (Entity val) -> SqlExpr (Value typ)) -> (SqlExpr (Entity val) -> SqlExpr (Value typ))
-> SqlExpr (Update val) -> SqlExpr (Update val)
setAux field mkVal = ESet $ \ent -> unsafeSqlBinOp " = " name (mkVal ent) setAux field mkVal = ESet $ \ent -> unsafeSqlBinOp " = " name (mkVal ent)
where name = ERaw Never $ \esc -> (fieldName esc field, mempty) where name = ERaw Never $ \conn -> (fieldName conn 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 $ \esc -> first parens (toRawSql mode esc query) sub mode query = ERaw Parens $ \conn -> first parens (toRawSql mode conn query)
fromDBName :: Connection -> DBName -> TLB.Builder fromDBName :: Connection -> DBName -> TLB.Builder
fromDBName conn = TLB.fromText . escapeName conn fromDBName conn = TLB.fromText . escapeName conn
@ -346,10 +360,10 @@ fromDBName conn = TLB.fromText . escapeName conn
unsafeSqlBinOp :: TLB.Builder -> SqlExpr (Value a) -> SqlExpr (Value b) -> SqlExpr (Value c) unsafeSqlBinOp :: TLB.Builder -> SqlExpr (Value a) -> SqlExpr (Value b) -> SqlExpr (Value c)
unsafeSqlBinOp op (ERaw p1 f1) (ERaw p2 f2) = ERaw Parens f unsafeSqlBinOp op (ERaw p1 f1) (ERaw p2 f2) = ERaw Parens f
where where
f esc = let (b1, vals1) = f1 esc f conn = let (b1, vals1) = f1 conn
(b2, vals2) = f2 esc (b2, vals2) = f2 conn
in ( parensM p1 b1 <> op <> parensM p2 b2 in ( parensM p1 b1 <> op <> parensM p2 b2
, vals1 <> vals2 ) , vals1 <> vals2 )
{-# INLINE unsafeSqlBinOp #-} {-# INLINE unsafeSqlBinOp #-}
@ -365,9 +379,9 @@ unsafeSqlValue v = ERaw Never $ \_ -> (v, mempty)
unsafeSqlFunction :: UnsafeSqlFunctionArgument a => unsafeSqlFunction :: UnsafeSqlFunctionArgument a =>
TLB.Builder -> a -> SqlExpr (Value b) TLB.Builder -> a -> SqlExpr (Value b)
unsafeSqlFunction name arg = unsafeSqlFunction name arg =
ERaw Never $ \esc -> ERaw Never $ \conn ->
let (argsTLB, argsVals) = let (argsTLB, argsVals) =
uncommas' $ map (\(ERaw _ f) -> f esc) $ toArgList arg uncommas' $ map (\(ERaw _ f) -> f conn) $ toArgList arg
in (name <> parens argsTLB, argsVals) in (name <> parens argsTLB, argsVals)
class UnsafeSqlFunctionArgument a where class UnsafeSqlFunctionArgument a where
@ -421,7 +435,7 @@ rawSelectSource mode query = src
run conn = run conn =
uncurry withStmt $ uncurry withStmt $
first builderToText $ first builderToText $
toRawSql mode (fromDBName conn) query toRawSql mode conn query
massage = do massage = do
mrow <- C.await mrow <- C.await
@ -533,7 +547,7 @@ rawExecute mode query = do
conn <- SqlPersist R.ask conn <- SqlPersist R.ask
uncurry execute $ uncurry execute $
first builderToText $ first builderToText $
toRawSql mode (fromDBName conn) query toRawSql mode conn query
-- | Execute an @esqueleto@ @DELETE@ query inside @persistent@'s -- | Execute an @esqueleto@ @DELETE@ query inside @persistent@'s
@ -600,18 +614,19 @@ 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 -> Escape -> SqlQuery a -> (TLB.Builder, [PersistValue]) toRawSql :: SqlSelect a r => Mode -> Connection -> SqlQuery a -> (TLB.Builder, [PersistValue])
toRawSql mode esc query = toRawSql mode conn query =
let (ret, SideData fromClauses setClauses whereClauses orderByClauses) = let (ret, SideData fromClauses setClauses whereClauses orderByClauses limitClause) =
flip S.evalState initialIdentState $ flip S.evalState initialIdentState $
W.runWriterT $ W.runWriterT $
unQ query unQ query
in mconcat in mconcat
[ makeSelect esc mode ret [ makeSelect conn mode ret
, makeFrom esc mode fromClauses , makeFrom conn mode fromClauses
, makeSet esc setClauses , makeSet conn setClauses
, makeWhere esc whereClauses , makeWhere conn whereClauses
, makeOrderBy esc orderByClauses , makeOrderBy conn orderByClauses
, makeLimit conn limitClause
] ]
-- | (Internal) Mode of query being converted by 'toRawSql'. -- | (Internal) Mode of query being converted by 'toRawSql'.
@ -625,8 +640,8 @@ uncommas' :: Monoid a => [(TLB.Builder, a)] -> (TLB.Builder, a)
uncommas' = (uncommas *** mconcat) . unzip uncommas' = (uncommas *** mconcat) . unzip
makeSelect :: SqlSelect a r => Escape -> Mode -> a -> (TLB.Builder, [PersistValue]) makeSelect :: SqlSelect a r => Connection -> Mode -> a -> (TLB.Builder, [PersistValue])
makeSelect esc mode ret = first (s <>) (sqlSelectCols esc ret) makeSelect conn mode ret = first (s <>) (sqlSelectCols conn ret)
where where
s = case mode of s = case mode of
SELECT -> "SELECT " SELECT -> "SELECT "
@ -635,9 +650,9 @@ makeSelect esc mode ret = first (s <>) (sqlSelectCols esc ret)
UPDATE -> "UPDATE " UPDATE -> "UPDATE "
makeFrom :: Escape -> Mode -> [FromClause] -> (TLB.Builder, [PersistValue]) makeFrom :: Connection -> Mode -> [FromClause] -> (TLB.Builder, [PersistValue])
makeFrom _ _ [] = mempty makeFrom _ _ [] = mempty
makeFrom esc mode fs = ret makeFrom conn mode fs = ret
where where
ret = case collectOnClauses fs of ret = case collectOnClauses fs of
Left expr -> throw $ mkExc expr Left expr -> throw $ mkExc expr
@ -658,8 +673,8 @@ makeFrom esc mode fs = ret
base ident@(I identText) def = base ident@(I identText) def =
let db@(DBName dbText) = entityDB def let db@(DBName dbText) = entityDB def
in ( if dbText == identText in ( if dbText == identText
then esc db then fromDBName conn db
else esc db <> (" AS " <> useIdent esc ident) else fromDBName conn db <> (" AS " <> useIdent conn ident)
, mempty ) , mempty )
fromKind InnerJoinKind = " INNER JOIN " fromKind InnerJoinKind = " INNER JOIN "
@ -668,35 +683,53 @@ makeFrom esc mode fs = ret
fromKind RightOuterJoinKind = " RIGHT OUTER JOIN " fromKind RightOuterJoinKind = " RIGHT OUTER JOIN "
fromKind FullOuterJoinKind = " FULL OUTER JOIN " fromKind FullOuterJoinKind = " FULL OUTER JOIN "
makeOnClause (ERaw _ f) = first (" ON " <>) (f esc) makeOnClause (ERaw _ f) = first (" ON " <>) (f conn)
mkExc :: SqlExpr (Value Bool) -> OnClauseWithoutMatchingJoinException mkExc :: SqlExpr (Value Bool) -> OnClauseWithoutMatchingJoinException
mkExc (ERaw _ f) = mkExc (ERaw _ f) =
OnClauseWithoutMatchingJoinException $ OnClauseWithoutMatchingJoinException $
TL.unpack $ TLB.toLazyText $ fst (f esc) TL.unpack $ TLB.toLazyText $ fst (f conn)
makeSet :: Escape -> [SetClause] -> (TLB.Builder, [PersistValue]) makeSet :: Connection -> [SetClause] -> (TLB.Builder, [PersistValue])
makeSet _ [] = mempty makeSet _ [] = mempty
makeSet esc os = first ("\nSET " <>) $ uncommas' (map mk os) makeSet conn os = first ("\nSET " <>) $ uncommas' (map mk os)
where where
mk (SetClause (ERaw _ f)) = f esc mk (SetClause (ERaw _ f)) = f conn
makeWhere :: Escape -> WhereClause -> (TLB.Builder, [PersistValue]) makeWhere :: Connection -> WhereClause -> (TLB.Builder, [PersistValue])
makeWhere _ NoWhere = mempty makeWhere _ NoWhere = mempty
makeWhere esc (Where (ERaw _ f)) = first ("\nWHERE " <>) (f esc) makeWhere conn (Where (ERaw _ f)) = first ("\nWHERE " <>) (f conn)
makeOrderBy :: Escape -> [OrderByClause] -> (TLB.Builder, [PersistValue]) makeOrderBy :: Connection -> [OrderByClause] -> (TLB.Builder, [PersistValue])
makeOrderBy _ [] = mempty makeOrderBy _ [] = mempty
makeOrderBy esc os = first ("\nORDER BY " <>) $ uncommas' (map mk os) makeOrderBy conn os = first ("\nORDER BY " <>) $ uncommas' (map mk os)
where where
mk (EOrderBy t (ERaw _ f)) = first (<> orderByType t) (f esc) mk (EOrderBy t (ERaw _ f)) = first (<> orderByType t) (f conn)
orderByType ASC = " ASC" orderByType ASC = " ASC"
orderByType DESC = " DESC" orderByType DESC = " DESC"
makeLimit :: Connection -> LimitClause -> (TLB.Builder, [PersistValue])
makeLimit _ (Limit Nothing Nothing) = mempty
makeLimit _ (Limit Nothing (Just 0)) = mempty
makeLimit conn (Limit ml mo) = (ret, mempty)
where
ret = TLB.singleton '\n' <> (limitTLB <> offsetTLB)
limitTLB =
case ml of
Just l -> "LIMIT " <> TLBI.decimal l
Nothing -> TLB.fromText (noLimit conn)
offsetTLB =
case mo of
Just o -> " OFFSET " <> TLBI.decimal o
Nothing -> mempty
parens :: TLB.Builder -> TLB.Builder parens :: TLB.Builder -> TLB.Builder
parens b = "(" <> (b <> ")") parens b = "(" <> (b <> ")")
@ -714,7 +747,7 @@ class SqlSelect a r | a -> r, r -> a where
-- | Creates the variable part of the @SELECT@ query and -- | Creates the variable part of the @SELECT@ query and
-- returns the list of 'PersistValue's that will be given to -- returns the list of 'PersistValue's that will be given to
-- 'withStmt'. -- 'withStmt'.
sqlSelectCols :: Escape -> a -> (TLB.Builder, [PersistValue]) sqlSelectCols :: Connection -> a -> (TLB.Builder, [PersistValue])
-- | Number of columns that will be consumed. Must be -- | Number of columns that will be consumed. Must be
-- non-strict on the argument. -- non-strict on the argument.
@ -733,10 +766,10 @@ instance SqlSelect () () where
-- | You may return an 'Entity' from a 'select' query. -- | You may return an 'Entity' from a 'select' query.
instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where
sqlSelectCols escape expr@(EEntity ident) = ret sqlSelectCols conn expr@(EEntity ident) = ret
where where
process ed = uncommas $ process ed = uncommas $
map ((name <>) . escape) $ map ((name <>) . fromDBName conn) $
(entityID ed:) $ (entityID ed:) $
map fieldDB $ map fieldDB $
entityFields ed entityFields ed
@ -746,7 +779,7 @@ instance PersistEntity a => SqlSelect (SqlExpr (Entity a)) (Entity a) where
-- clause), while 'rawSql' assumes that it's just the -- clause), while 'rawSql' assumes that it's just the
-- name of the table (which doesn't allow self-joins, for -- name of the table (which doesn't allow self-joins, for
-- example). -- example).
name = useIdent escape ident <> "." name = useIdent conn ident <> "."
ret = let ed = entityDef $ getEntityVal expr ret = let ed = entityDef $ getEntityVal expr
in (process ed, mempty) in (process ed, mempty)
sqlSelectColCount = (+1) . length . entityFields . entityDef . getEntityVal sqlSelectColCount = (+1) . length . entityFields . entityDef . getEntityVal
@ -761,7 +794,7 @@ getEntityVal = error "Esqueleto/Sql/getEntityVal"
-- | You may return a possibly-@NULL@ 'Entity' from a 'select' query. -- | You may return a possibly-@NULL@ 'Entity' from a 'select' query.
instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where instance PersistEntity a => SqlSelect (SqlExpr (Maybe (Entity a))) (Maybe (Entity a)) where
sqlSelectCols escape (EMaybe ent) = sqlSelectCols escape ent sqlSelectCols conn (EMaybe ent) = sqlSelectCols conn ent
sqlSelectColCount = sqlSelectColCount . fromEMaybe sqlSelectColCount = sqlSelectColCount . fromEMaybe
where where
fromEMaybe :: SqlExpr (Maybe e) -> SqlExpr e fromEMaybe :: SqlExpr (Maybe e) -> SqlExpr e

View File

@ -117,6 +117,50 @@ main = do
, (Value (personName p2), Value (personName p1)) , (Value (personName p2), Value (personName p1))
, (Value (personName p2), Value (personName p2)) ] , (Value (personName p2), Value (personName p2)) ]
it "works with many kinds of LIMITs and OFFSETs" $
run $ do
[p1e, p2e, p3e, p4e] <- mapM insert' [p1, p2, p3, p4]
let people = from $ \p -> do
orderBy [asc (p ^. PersonName)]
return p
ret1 <- select $ do
p <- people
limit 2
limit 1
return p
liftIO $ ret1 `shouldBe` [ p1e ]
ret2 <- select $ do
p <- people
limit 1
limit 2
return p
liftIO $ ret2 `shouldBe` [ p1e, p4e ]
ret3 <- select $ do
p <- people
offset 3
offset 2
return p
liftIO $ ret3 `shouldBe` [ p3e, p2e ]
ret4 <- select $ do
p <- people
offset 3
limit 5
offset 2
limit 3
offset 1
limit 2
return p
liftIO $ ret4 `shouldBe` [ p4e, p3e ]
ret5 <- select $ do
p <- people
offset 1000
limit 1
limit 1000
offset 0
return p
liftIO $ ret5 `shouldBe` [ p1e, p4e, p3e, p2e ]
describe "select/JOIN" $ do describe "select/JOIN" $ do
it "works with a LEFT OUTER JOIN" $ it "works with a LEFT OUTER JOIN" $
run $ do run $ do