{-# LANGUAGE GADTs, TypeOperators, TypeFamilies, ScopedTypeVariables #-}
{-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
{-# LANGUAGE ConstraintKinds #-}
module Database.Selda.Compile
( Result, Res
, buildResult, compQuery, compQueryWithFreshScope
, compile, compileWith
, compileInsert, compileUpdate, compileDelete
)
where
import Control.Monad (liftM2)
import Database.Selda.Column
import Database.Selda.Generic
import Database.Selda.Query.Type
import Database.Selda.SQL
import Database.Selda.SQL.Print
import Database.Selda.SQL.Print.Config
import Database.Selda.SqlRow
import Database.Selda.SqlType
import Database.Selda.Table
import Database.Selda.Table.Compile
import Database.Selda.Transform
import Database.Selda.Types
import Data.Proxy
import Data.Text (Text, empty)
import Data.Typeable (Typeable)
import Data.IORef
import System.IO.Unsafe
compile :: Result a => Query s a -> (Text, [Param])
compile = compileWith defPPConfig
compileWith :: Result a => PPConfig -> Query s a -> (Text, [Param])
compileWith cfg = compSql cfg . snd . compQuery 0
compileInsert :: Relational a => PPConfig -> Table a -> [a] -> [(Text, [Param])]
compileInsert _ _ [] =
[(empty, [])]
compileInsert cfg tbl rows =
case ppMaxInsertParams cfg of
Nothing -> [compInsert cfg tbl rows']
Just n -> map (compInsert cfg tbl) (chunk (n `div` rowlen) rows')
where
rows' = map params rows
rowlen = length (head rows')
chunk chunksize xs =
case splitAt chunksize xs of
([], []) -> []
(x, []) -> [x]
(x, xs') -> x : chunk chunksize xs'
compileUpdate :: forall s a. (Relational a, SqlRow a)
=> PPConfig
-> Table a
-> (Row s a -> Row s a)
-> (Row s a -> Col s Bool)
-> (Text, [Param])
compileUpdate cfg tbl upd check =
compUpdate cfg (tableName tbl) predicate updated
where
names = map colName (tableCols tbl)
cs = tableExpr tbl
updated = zip names (finalCols (upd cs))
One predicate = check cs
compileDelete :: Relational a
=> PPConfig
-> Table a
-> (Row s a -> Col s Bool)
-> (Text, [Param])
compileDelete cfg tbl check = compDelete cfg (tableName tbl) predicate
where One predicate = check $ toTup $ map colName $ tableCols tbl
compQuery :: Result a => Scope -> Query s a -> (Int, SQL)
compQuery ns q =
(nameSupply st, SQL final (Product [srcs]) [] [] [] Nothing False)
where
(cs, st) = runQueryM ns q
final = finalCols cs
sql = state2sql st
live = colNames final ++ allNonOutputColNames sql
srcs = removeDeadCols live sql
{-# NOINLINE scopeSupply #-}
scopeSupply :: IORef Scope
scopeSupply = unsafePerformIO $ newIORef 1
compQueryWithFreshScope :: Result a => Query s a -> (Int, SQL)
compQueryWithFreshScope q = unsafePerformIO $ do
s <- atomicModifyIORef' scopeSupply (\s -> (s+1, s))
return $ compQuery s q
buildResult :: Result r => Proxy r -> [SqlValue] -> Res r
buildResult p = runResultReader (toRes p)
type family Res r where
Res (Col s a :*: b) = a :*: Res b
Res (Row s a :*: b) = a :*: Res b
Res (Col s a) = a
Res (Row s a) = a
class Typeable (Res r) => Result r where
toRes :: Proxy r -> ResultReader (Res r)
finalCols :: r -> [SomeCol SQL]
instance (SqlType a, Result b) => Result (Col s a :*: b) where
toRes _ = liftM2 (:*:) (fromSql <$> next) (toRes (Proxy :: Proxy b))
finalCols (a :*: b) = finalCols a ++ finalCols b
instance (SqlRow a, Result b) => Result (Row s a :*: b) where
toRes _ = liftM2 (:*:) nextResult (toRes (Proxy :: Proxy b))
finalCols (a :*: b) = finalCols a ++ finalCols b
instance SqlType a => Result (Col s a) where
toRes _ = fromSql <$> next
finalCols (One c) = [Some c]
instance SqlRow a => Result (Row s a) where
toRes _ = nextResult
finalCols (Many cs) = [Some c | Untyped c <- cs]