{-# LANGUAGE FlexibleContexts, OverloadedStrings, ScopedTypeVariables #-}
module Database.Selda.Query
( select, selectValues, Database.Selda.Query.distinct
, restrict, groupBy, limit, order, orderRandom
, aggregate, leftJoin, innerJoin
) where
import Data.Maybe (isNothing)
import Database.Selda.Column
import Database.Selda.Generic
import Database.Selda.Inner
import Database.Selda.Query.Type
import Database.Selda.SQL as SQL
import Database.Selda.SqlType (SqlType)
import Database.Selda.Table
import Database.Selda.Transform
import Control.Monad.State.Strict
import Data.Proxy
import GHC.Generics (Rep)
import Unsafe.Coerce
select :: Relational a => Table a -> Query s (Row s a)
select (Table name cs _ _) = Query $ do
rns <- renameAll $ map colExpr cs
st <- get
put $ st {sources = sqlFrom rns (TableName name) : sources st}
return $ Many (map hideRenaming rns)
selectValues :: forall s a. Relational a => [a] -> Query s (Row s a)
selectValues [] = Query $ do
st <- get
put $ st {sources = sqlFrom [] EmptyTable : sources st}
return $ Many (gNew (Proxy :: Proxy (Rep a)))
selectValues (row:rows) = Query $ do
names <- mapM (const freshName) firstrow
let rns = [Named n (Col n) | n <- names]
row' = mkFirstRow names
s <- get
put $ s {sources = sqlFrom rns (Values row' rows') : sources s}
return $ Many (map hideRenaming rns)
where
firstrow = map defToVal $ params row
mkFirstRow ns =
[ Named n (Lit l)
| (Param l, n) <- zip firstrow ns
]
rows' = map (map defToVal . params) rows
defToVal (Left x) = x
defToVal (Right x) = x
restrict :: Same s t => Col s Bool -> Query t ()
restrict (One p) = Query $ do
st <- get
put $ case sources st of
[] ->
st {staticRestricts = p : staticRestricts st}
[sql] | not $ p `wasRenamedIn` cols sql ->
st {sources = [sql {restricts = p : restricts sql}]}
ss ->
st {sources = [(sqlFrom (allCols ss) (Product ss)) {restricts = [p]}]}
where
wasRenamedIn predicate cs =
let cs' = [n | Named n _ <- cs]
in any (`elem` cs') (colNames [Some predicate])
aggregate :: (Columns (AggrCols a), Aggregates a)
=> Query (Inner s) a
-> Query s (AggrCols a)
aggregate q = Query $ do
(gst, aggrs) <- isolate q
cs <- renameAll $ unAggrs aggrs
let sql = (sqlFrom cs (Product [state2sql gst])) {groups = groupCols gst}
modify $ \st -> st {sources = sql : sources st}
pure $ toTup [n | Named n _ <- cs]
leftJoin :: (Columns a, Columns (OuterCols a), Columns (LeftCols a))
=> (OuterCols a -> Col s Bool)
-> Query (Inner s) a
-> Query s (LeftCols a)
leftJoin = someJoin LeftJoin
innerJoin :: (Columns a, Columns (OuterCols a))
=> (OuterCols a -> Col s Bool)
-> Query (Inner s) a
-> Query s (OuterCols a)
innerJoin = someJoin InnerJoin
someJoin :: (Columns a, Columns (OuterCols a), Columns a')
=> JoinType
-> (OuterCols a -> Col s Bool)
-> Query (Inner s) a
-> Query s a'
someJoin jointype check q = Query $ do
(join_st, res) <- isolate q
cs <- renameAll $ fromTup res
st <- get
let nameds = [n | Named n _ <- cs]
left = state2sql st
right = sqlFrom cs (Product [state2sql join_st])
One on = check $ toTup nameds
outCols = [Some $ Col n | Named n _ <- cs] ++ allCols [left]
put $ st {sources = [sqlFrom outCols (Join jointype on left right)]}
pure $ toTup nameds
groupBy :: (Same s t, SqlType a) => Col (Inner s) a -> Query (Inner t) (Aggr (Inner t) a)
groupBy (One c) = Query $ do
st <- get
put $ st {groupCols = Some c : groupCols st}
return (Aggr c)
limit :: Same s t => Int -> Int -> Query (Inner s) a -> Query t (OuterCols a)
limit from to q = Query $ do
(lim_st, res) <- isolate q
st <- get
let sql' = case sources lim_st of
[sql] | isNothing (limits sql) -> sql
ss -> sqlFrom (allCols ss) (Product ss)
put $ st {sources = sql' {limits = Just (from, to)} : sources st}
return $ unsafeCoerce res
order :: (Same s t, SqlType a) => Col s a -> Order -> Query t ()
order (One c) o = Query $ do
st <- get
case sources st of
[sql] -> put st {sources = [sql {ordering = (o, Some c) : ordering sql}]}
ss -> put st {sources = [sql {ordering = [(o, Some c)]}]}
where sql = sqlFrom (allCols ss) (Product ss)
orderRandom :: Query s ()
orderRandom = order (One (NulOp (Fun0 "RANDOM") :: Exp SQL Int)) Asc
distinct :: (Columns a, Columns (OuterCols a))
=> Query (Inner s) a
-> Query s (OuterCols a)
distinct q = Query $ do
(inner_st, res) <- isolate q
st <- get
let ss = sources inner_st
put st {sources = [(sqlFrom (allCols ss) (Product ss)) {SQL.distinct = True}]}
return (unsafeCoerce res)