{-# LANGUAGE ScopedTypeVariables, FlexibleContexts, OverloadedStrings #-}
module Database.Selda.Frontend
( Result, Res, MonadIO (..), MonadSelda (..), SeldaT, OnError (..)
, query, queryInto
, insert, insert_, insertWithPK, tryInsert, insertWhen, insertUnless
, update, update_, upsert
, deleteFrom, deleteFrom_
, createTable, tryCreateTable, createTableWithoutIndexes, createTableIndexes
, dropTable, tryDropTable
, transaction, withoutForeignKeyEnforcement
) where
import Database.Selda.Backend.Internal
import Database.Selda.Column
import Database.Selda.Compile
import Database.Selda.Generic
import Database.Selda.Query.Type
import Database.Selda.SqlType (ID, invalidId, toId)
import Database.Selda.Table
import Database.Selda.Table.Compile
import Database.Selda.Types (fromTableName)
import Data.Proxy
import Data.Text (Text)
import Control.Monad
import Control.Monad.Catch
import Control.Monad.IO.Class
query :: (MonadSelda m, Result a) => Query (Backend m) a -> m [Res a]
query q = withBackend (flip queryWith q . runStmt)
queryInto :: (MonadSelda m, Relational a)
=> Table a
-> Query (Backend m) (Row (Backend m) a)
-> m Int
queryInto tbl q = withBackend $ \b -> do
let (qry, ps) = compileWith (ppConfig b) q
qry' = mconcat ["INSERT INTO ", tblName, " ", qry]
fmap fst . liftIO $ runStmt b qry' ps
where
tblName = fromTableName (tableName tbl)
insert :: (MonadSelda m, Relational a) => Table a -> [a] -> m Int
insert _ [] = do
return 0
insert t cs = withBackend $ \b -> do
sum <$> mapM (uncurry exec) (compileInsert (ppConfig b) t cs)
tryInsert :: (MonadSelda m, MonadCatch m, Relational a) => Table a -> [a] -> m Bool
tryInsert tbl row = do
mres <- try $ insert tbl row
case mres of
Right _ -> return True
Left (SqlError _) -> return False
Left e -> throwM e
upsert :: (MonadSelda m, MonadMask m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> (Row (Backend m) a -> Row (Backend m) a)
-> [a]
-> m (Maybe (ID a))
upsert tbl check upd rows = transaction $ do
updated <- update tbl check upd
if updated == 0
then Just <$> insertWithPK tbl rows
else pure Nothing
insertUnless :: (MonadSelda m, MonadMask m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> [a]
-> m (Maybe (ID a))
insertUnless tbl check rows = upsert tbl check id rows
insertWhen :: (MonadSelda m, MonadMask m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> [a]
-> m (Maybe (ID a))
insertWhen tbl check rows = transaction $ do
matches <- update tbl check id
if matches > 0
then Just <$> insertWithPK tbl rows
else pure Nothing
insert_ :: (MonadSelda m, Relational a) => Table a -> [a] -> m ()
insert_ t cs = void $ insert t cs
insertWithPK :: (MonadSelda m, Relational a) => Table a -> [a] -> m (ID a)
insertWithPK t cs = withBackend $ \b -> do
if tableHasAutoPK t
then do
res <- liftIO $ do
mapM (uncurry (runStmtWithPK b)) $ compileInsert (ppConfig b) t cs
return $ toId (last res)
else do
insert_ t cs
return invalidId
update :: (MonadSelda m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> (Row (Backend m) a -> Row (Backend m) a)
-> m Int
update tbl check upd = withBackend $ \b -> do
res <- uncurry exec $ compileUpdate (ppConfig b) tbl upd check
return res
update_ :: (MonadSelda m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> (Row (Backend m) a -> Row (Backend m) a)
-> m ()
update_ tbl check upd = void $ update tbl check upd
deleteFrom :: (MonadSelda m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> m Int
deleteFrom tbl f = withBackend $ \b -> do
res <- uncurry exec $ compileDelete (ppConfig b) tbl f
return res
deleteFrom_ :: (MonadSelda m, Relational a)
=> Table a
-> (Row (Backend m) a -> Col (Backend m) Bool)
-> m ()
deleteFrom_ tbl f = void $ deleteFrom tbl f
createTable :: MonadSelda m => Table a -> m ()
createTable tbl = do
createTableWithoutIndexes Fail tbl
createTableIndexes Fail tbl
createTableWithoutIndexes :: MonadSelda m => OnError -> Table a -> m ()
createTableWithoutIndexes onerror tbl = withBackend $ \b -> do
void $ exec (compileCreateTable (ppConfig b) onerror tbl) []
createTableIndexes :: MonadSelda m => OnError -> Table a -> m ()
createTableIndexes ifex tbl = withBackend $ \b -> do
mapM_ (flip exec []) $ compileCreateIndexes (ppConfig b) ifex tbl
tryCreateTable :: MonadSelda m => Table a -> m ()
tryCreateTable tbl = do
createTableWithoutIndexes Ignore tbl
createTableIndexes Ignore tbl
dropTable :: MonadSelda m => Table a -> m ()
dropTable = void . flip exec [] . compileDropTable Fail
tryDropTable :: MonadSelda m => Table a -> m ()
tryDropTable = void . flip exec [] . compileDropTable Ignore
transaction :: (MonadSelda m, MonadMask m) => m a -> m a
transaction m = mask $ \restore -> transact $ do
void $ exec "BEGIN TRANSACTION" []
x <- restore m `onException` void (exec "ROLLBACK" [])
void $ exec "COMMIT" []
return x
withoutForeignKeyEnforcement :: (MonadSelda m, MonadMask m) => m a -> m a
withoutForeignKeyEnforcement m = withBackend $ \b -> do
bracket_ (liftIO $ disableForeignKeys b True)
(liftIO $ disableForeignKeys b False)
m
queryWith :: forall m a. (MonadSelda m, Result a)
=> QueryRunner (Int, [[SqlValue]]) -> Query (Backend m) a -> m [Res a]
queryWith run q = withBackend $ \b -> do
res <- fmap snd . liftIO . uncurry run $ compileWith (ppConfig b) q
return $ mkResults (Proxy :: Proxy a) res
mkResults :: Result a => Proxy a -> [[SqlValue]] -> [Res a]
mkResults p = map (buildResult p)
{-# INLINE exec #-}
exec :: MonadSelda m => Text -> [Param] -> m Int
exec q ps = withBackend $ \b -> liftIO $ execIO b q ps
{-# INLINE execIO #-}
execIO :: SeldaBackend b -> Text -> [Param] -> IO Int
execIO backend q ps = fmap fst $ runStmt backend q ps