module Database.Seakale.Store
( Entity(..)
, MonadSelect
, MonadStore
, select
, select_
, count
, getMany
, getMaybe
, get
, insertMany
, insert
, updateMany
, update
, UpdateSetter
, (=.)
, deleteMany
, delete
, Storable(..)
, Relation(..)
, RelationName(..)
, Column(..)
, Property(..)
, EntityIDProperty(..)
, SelectClauses
, groupBy
, asc
, desc
, limit
, offset
, Condition
, (==.)
, (/=.)
, (<=.)
, (<.)
, (>=.)
, (>.)
, (==#)
, (/=#)
, (<=#)
, (<#)
, (>=#)
, (>#)
, (==~)
, (/=~)
, (<=~)
, (<~)
, (>=~)
, (>~)
, (&&.)
, (||.)
, isNull
, isNotNull
, inList
, notInList
) where
import Control.Monad
import Data.List hiding (groupBy, insert, delete)
import Data.Maybe
import Data.Monoid
import qualified Data.ByteString.Char8 as BS
import Database.Seakale.FromRow
import Database.Seakale.ToRow
import Database.Seakale.Types
import Database.Seakale.Store.Internal
hiding (select, count, insert, update, delete)
import qualified Database.Seakale.Store.Internal as I
select :: ( MonadSelect backend m, Storable backend k l a
, FromRow backend (k :+ l) (Entity a) ) => Condition backend a
-> SelectClauses backend a -> m [Entity a]
select cond clauses = do
backend <- getBackend
I.select (relation backend) cond clauses
select_ :: ( MonadSelect backend m, Storable backend k l a
, FromRow backend (k :+ l) (Entity a) ) => Condition backend a
-> m [Entity a]
select_ cond = select cond mempty
count :: (MonadSelect backend m, Storable backend k l a) => Condition backend a
-> m Integer
count cond = do
backend <- getBackend
I.count (relation backend) cond
getMany :: ( MonadSelect backend m, Storable backend k l a
, FromRow backend (k :+ l) (Entity a), ToRow backend k (EntityID a) )
=> [EntityID a] -> m [Entity a]
getMany ids = select_ $ EntityID `inList` ids
getMaybe :: ( MonadSelect backend m, Storable backend k l a
, FromRow backend (k :+ l) (Entity a), ToRow backend k (EntityID a)
) => EntityID a -> m (Maybe a)
getMaybe i =
(fmap entityVal . listToMaybe) <$> select (EntityID ==. i) (limit 1)
get :: ( MonadSelect backend m, Storable backend k l a
, FromRow backend (k :+ l) (Entity a), ToRow backend k (EntityID a) )
=> EntityID a -> m a
get i = maybe (throwSeakaleError EntityNotFoundError) return =<< getMaybe i
insertMany :: forall backend m k l a.
( MonadStore backend m, Storable backend k l a, ToRow backend l a
, FromRow backend k (EntityID a) ) => [a] -> m [EntityID a]
insertMany = I.insert
insert :: ( MonadStore backend m, Storable backend k l a, ToRow backend l a
, FromRow backend k (EntityID a) ) => a -> m (EntityID a)
insert = fmap head . insertMany . pure
updateMany :: forall backend m k l a.
(MonadStore backend m, Storable backend k l a)
=> UpdateSetter backend a -> Condition backend a -> m Integer
updateMany setter cond = I.update setter cond
update :: ( MonadStore backend m, Storable backend k l a
, ToRow backend k (EntityID a) )
=> EntityID a -> UpdateSetter backend a -> m ()
update i setter = do
n <- updateMany setter $ EntityID ==. i
unless (n == 1) $ throwSeakaleError EntityNotFoundError
deleteMany :: forall backend m k l a.
(MonadStore backend m, Storable backend k l a)
=> Condition backend a -> m Integer
deleteMany = I.delete
delete :: ( MonadStore backend m, Storable backend k l a
, ToRow backend k (EntityID a) ) => EntityID a -> m ()
delete i = do
n <- deleteMany $ EntityID ==. i
unless (n == 1) $ throwSeakaleError EntityNotFoundError
class Property backend a f | f -> a where
toColumns :: backend -> f backend n b -> Vector n Column
data EntityIDProperty a backend :: Nat -> * -> * where
EntityID :: forall backend k l a. Storable backend k l a
=> EntityIDProperty a backend k (EntityID a)
instance Property backend a (EntityIDProperty a) where
toColumns backend x@EntityID = go (relation backend) x
where
go :: Relation backend k l a -> EntityIDProperty a backend k (EntityID a)
-> Vector k Column
go Relation{..} _ = relationIDColumns
(==.) :: (Property backend a f, ToRow backend n b) => f backend n b -> b
-> Condition backend a
(==.) prop vals =
buildCondition "=" (flip toColumns prop) (flip toRow vals)
(/=.) :: (Property backend a f, ToRow backend n b) => f backend n b -> b
-> Condition backend a
(/=.) prop vals =
buildCondition "<>" (flip toColumns prop) (flip toRow vals)
(<=.) :: (Property backend a f, ToRow backend n b) => f backend n b -> b
-> Condition backend a
(<=.) prop vals =
buildCondition "<=" (flip toColumns prop) (flip toRow vals)
(<.) :: (Property backend a f, ToRow backend n b) => f backend n b -> b
-> Condition backend a
(<.) prop vals =
buildCondition "<" (flip toColumns prop) (flip toRow vals)
(>=.) :: (Property backend a f, ToRow backend n b) => f backend n b -> b
-> Condition backend a
(>=.) prop vals =
buildCondition ">=" (flip toColumns prop) (flip toRow vals)
(>.) :: (Property backend a f, ToRow backend n b) => f backend n b -> b
-> Condition backend a
(>.) cols vals =
buildCondition ">" (flip toColumns cols) (flip toRow vals)
(==#) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n b -> Condition backend a
(==#) prop1 prop2 =
buildCondition' "=" (flip toColumns prop1) (flip toColumns prop2)
(/=#) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n b -> Condition backend a
(/=#) prop1 prop2 =
buildCondition' "<>" (flip toColumns prop1) (flip toColumns prop2)
(<#) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n b -> Condition backend a
(<#) prop1 prop2 =
buildCondition' "<" (flip toColumns prop1) (flip toColumns prop2)
(<=#) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n b -> Condition backend a
(<=#) prop1 prop2 =
buildCondition' "<=" (flip toColumns prop1) (flip toColumns prop2)
(>#) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n b -> Condition backend a
(>#) prop1 prop2 =
buildCondition' ">" (flip toColumns prop1) (flip toColumns prop2)
(>=#) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n b -> Condition backend a
(>=#) prop1 prop2 =
buildCondition' ">=" (flip toColumns prop1) (flip toColumns prop2)
(==~) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n c -> Condition backend a
(==~) prop1 prop2 =
buildCondition' "=" (flip toColumns prop1) (flip toColumns prop2)
(/=~) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n c -> Condition backend a
(/=~) prop1 prop2 =
buildCondition' "<>" (flip toColumns prop1) (flip toColumns prop2)
(<~) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n c -> Condition backend a
(<~) prop1 prop2 =
buildCondition' "<" (flip toColumns prop1) (flip toColumns prop2)
(<=~) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n c -> Condition backend a
(<=~) prop1 prop2 =
buildCondition' "<=" (flip toColumns prop1) (flip toColumns prop2)
(>~) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n c -> Condition backend a
(>~) prop1 prop2 =
buildCondition' ">" (flip toColumns prop1) (flip toColumns prop2)
(>=~) :: (Property backend a f, Property backend a g)
=> f backend n b -> g backend n c -> Condition backend a
(>=~) prop1 prop2 =
buildCondition' ">=" (flip toColumns prop1) (flip toColumns prop2)
(&&.) :: Condition backend a -> Condition backend a -> Condition backend a
(&&.) = mappend
(||.) :: Condition backend a -> Condition backend a -> Condition backend a
(||.) = combineConditions "OR"
infix 4 ==., /=., <=., <., >=., >.
infix 4 ==#, /=#, <=#, <#, >=#, >#
infix 4 ==~, /=~, <=~, <~, >=~, >~
infixr 2 &&., ||.
isNull :: Property backend a f => f backend n b -> Condition backend a
isNull prop = Condition $ \prefix backend ->
let bs = mconcat . intersperse " AND "
. map (\col -> unColumn col prefix <> " IS NULL")
. vectorToList $ toColumns backend prop
in (Plain bs EmptyQuery, Nil)
isNotNull :: Property backend a f => f backend n b -> Condition backend a
isNotNull prop = Condition $ \prefix backend ->
let bs = mconcat . intersperse " AND "
. map (\col -> unColumn col prefix <> " IS NOT NULL")
. vectorToList $ toColumns backend prop
in (Plain bs EmptyQuery, Nil)
listHelper :: (Property backend a f, ToRow backend n b) => BS.ByteString
-> f backend n b -> [b] -> Condition backend a
listHelper op prop values =
let step value (suffix, (Condition f)) =
(", ",) $ Condition $ \prefix backend ->
let (req, dat) = f prefix backend
valueList = mconcat $ intersperse ", " $ vectorToList $
toRow backend value
in (Hole (Plain suffix req), Cons ("(" <> valueList <> ")") dat)
(_, cond) = foldr step ("", mempty) values
in case cond of
Condition f -> Condition $ \prefix backend ->
let (req, dat) = f prefix backend
colList = mconcat $ intersperse ", " $ map (flip unColumn prefix) $
vectorToList $ toColumns backend prop
req' = Plain ("(" <> colList <> ") " <> op <> " (") req
`qappendZero` Plain ")" EmptyQuery
in (req', dat)
inList :: (Property backend a f, ToRow backend n b) => f backend n b -> [b]
-> Condition backend a
inList _ [] = Condition $ \_ _ -> (Plain "1=0" EmptyQuery, Nil)
inList prop values = listHelper "IN" prop values
notInList :: (Property backend a f, ToRow backend n b) => f backend n b -> [b]
-> Condition backend a
notInList _ [] = Condition $ \_ _ -> (Plain "1=1" EmptyQuery, Nil)
notInList prop values = listHelper "NOT IN" prop values
groupBy :: Property backend a f => f backend n b -> SelectClauses backend a
groupBy prop = mempty { selectGroupBy = vectorToList . flip toColumns prop }
asc :: Property backend a f => f backend n b -> SelectClauses backend a
asc prop =
let f backend = map (,Asc) $ vectorToList (toColumns backend prop)
in mempty { selectOrderBy = f }
desc :: Property backend a f => f backend n b -> SelectClauses backend a
desc prop =
let f backend = map (,Desc) $ vectorToList (toColumns backend prop)
in mempty { selectOrderBy = f }
limit :: Int -> SelectClauses backend a
limit n = mempty { selectLimit = Just n }
offset :: Int -> SelectClauses backend a
offset n = mempty { selectOffset = Just n }
(=.) :: (Property backend a f, ToRow backend n b)
=> f backend n b -> b -> UpdateSetter backend a
(=.) col value = UpdateSetter $ \backend ->
vzip (toColumns backend col) (toRow backend value)