selda-0.1.1.0: src/Database/Selda/Frontend.hs
{-# LANGUAGE ScopedTypeVariables, FlexibleContexts, OverloadedStrings #-}
-- | API for running Selda operations over databases.
module Database.Selda.Frontend
( Result, Res, MonadIO (..), MonadSelda (..), SeldaT
, query
, insert, insert_, insertWithPK
, update, update_
, deleteFrom, deleteFrom_
, createTable, tryCreateTable
, dropTable, tryDropTable
, transaction, setLocalCache
) where
import Database.Selda.Backend
import Database.Selda.Caching
import Database.Selda.Column
import Database.Selda.Compile
import Database.Selda.Query.Type
import Database.Selda.SQL
import Database.Selda.Table
import Database.Selda.Table.Compile
import Data.Proxy
import Data.Text (Text)
import Control.Monad
import Control.Monad.Catch
-- | Run a query within a Selda monad. In practice, this is often a 'SeldaT'
-- transformer on top of some other monad.
-- Selda transformers are entered using backend-specific @withX@ functions,
-- such as 'withSQLite' from the SQLite backend.
query :: (MonadSelda m, Result a) => Query s a -> m [Res a]
query q = do
backend <- seldaBackend
queryWith (runStmt backend) q
-- | Insert the given values into the given table. All columns of the table
-- must be present. If your table has an auto-incrementing primary key,
-- use the special value 'def' for that column to get the auto-incrementing
-- behavior.
-- Returns the number of rows that were inserted.
--
-- To insert a list of tuples into a table with auto-incrementing primary key:
--
-- > people :: Table (Auto Int :*: Text :*: Int :*: Maybe Text)
-- > people = table "ppl"
-- > $ autoPrimary "id"
-- > ¤ required "name"
-- > ¤ required "age"
-- > ¤ optional "pet"
-- >
-- > main = withSQLite "my_database.sqlite" $ do
-- > insert_ people
-- > [ def :*: "Link" :*: 125 :*: Just "horse"
-- > , def :*: "Zelda" :*: 119 :*: Nothing
-- > , ...
-- > ]
insert :: (MonadSelda m, Insert a) => Table a -> [a] -> m Int
insert _ [] = do
return 0
insert t cs = do
kw <- defaultKeyword <$> seldaBackend
res <- uncurry exec $ compileInsert kw t cs
liftIO $ invalidate (tableName t)
return res
-- | Like 'insert', but does not return anything.
-- Use this when you really don't care about how many rows were inserted.
insert_ :: (MonadSelda m, Insert a) => Table a -> [a] -> m ()
insert_ t cs = void $ insert t cs
-- | Like 'insert', but returns the primary key of the last inserted row.
-- Attempting to run this operation on a table without an auto-incrementing
-- primary key is a type error.
insertWithPK :: (MonadSelda m, Insert a) => Table a -> [a] -> m Int
insertWithPK t cs = do
backend <- seldaBackend
liftIO $ do
res <- uncurry (runStmtWithPK backend) $ compileInsert (defaultKeyword backend) t cs
invalidate (tableName t)
return res
-- | Update the given table using the given update function, for all rows
-- matching the given predicate. Returns the number of updated rows.
update :: (MonadSelda m, Columns (Cols s a), Result (Cols s a))
=> Table a -- ^ The table to update.
-> (Cols s a -> Col s Bool) -- ^ Predicate.
-> (Cols s a -> Cols s a) -- ^ Update function.
-> m Int
update tbl check upd = do
res <- uncurry exec $ compileUpdate tbl upd check
liftIO $ invalidate (tableName tbl)
return res
-- | Like 'update', but doesn't return the number of updated rows.
update_ :: (MonadSelda m, Columns (Cols s a), Result (Cols s a))
=> Table a
-> (Cols s a -> Col s Bool)
-> (Cols s a -> Cols s a)
-> m ()
update_ tbl check upd = void $ update tbl check upd
-- | From the given table, delete all rows matching the given predicate.
-- Returns the number of deleted rows.
deleteFrom :: (MonadSelda m, Columns (Cols s a))
=> Table a -> (Cols s a -> Col s Bool) -> m Int
deleteFrom tbl f = do
res <- uncurry exec $ compileDelete tbl f
liftIO $ invalidate (tableName tbl)
return res
-- | Like 'deleteFrom', but does not return the number of deleted rows.
deleteFrom_ :: (MonadSelda m, Columns (Cols s a))
=> Table a -> (Cols s a -> Col s Bool) -> m ()
deleteFrom_ tbl f = void $ deleteFrom tbl f
-- | Create a table from the given schema.
createTable :: MonadSelda m => Table a -> m ()
createTable tbl = do
cct <- customColType <$> seldaBackend
void . flip exec [] $ compileCreateTable cct Fail tbl
-- | Create a table from the given schema, unless it already exists.
tryCreateTable :: MonadSelda m => Table a -> m ()
tryCreateTable tbl = do
cct <- customColType <$> seldaBackend
void . flip exec [] $ compileCreateTable cct Ignore tbl
-- | Drop the given table.
dropTable :: MonadSelda m => Table a -> m ()
dropTable = withInval $ void . flip exec [] . compileDropTable Fail
-- | Drop the given table, if it exists.
tryDropTable :: MonadSelda m => Table a -> m ()
tryDropTable = withInval $ void . flip exec [] . compileDropTable Ignore
-- | Perform the given computation atomically.
-- If an exception is raised during its execution, the enture transaction
-- will be rolled back, and the exception re-thrown.
transaction :: (MonadSelda m, MonadThrow m, MonadCatch m) => m a -> m a
transaction m = do
void $ exec "BEGIN TRANSACTION" []
res <- try m
case res of
Left (SomeException e) -> do
void $ exec "ROLLBACK" []
throwM e
Right x -> do
void $ exec "COMMIT" []
return x
-- | Set the maximum local cache size to @n@. A cache size of zero disables
-- local cache altogether. Changing the cache size will also flush all
-- entries.
--
-- By default, local caching is turned off.
--
-- WARNING: local caching is guaranteed to be consistent with the underlying
-- database, ONLY under the assumption that no other process will modify it.
-- Also note that the cache is shared between ALL Selda computations running
-- within the same process.
setLocalCache :: MonadSelda m => Int -> m ()
setLocalCache = liftIO . setMaxItems
-- | Build the final result from a list of result columns.
queryWith :: forall s m a. (MonadSelda m, Result a)
=> QueryRunner (Int, [[SqlValue]]) -> Query s a -> m [Res a]
queryWith qr q = do
mres <- liftIO $ cached qry
case mres of
Just res -> do
return res
_ -> do
res <- fmap snd . liftIO $ uncurry qr qry
let res' = mkResults (Proxy :: Proxy a) res
liftIO $ cache tables qry res'
return res'
where
(tables, qry) = compileWithTables q
-- | Generate the final result of a query from a list of untyped result rows.
mkResults :: Result a => Proxy a -> [[SqlValue]] -> [Res a]
mkResults p = map (toRes p)
-- | Run the given computation over a table after invalidating all cached
-- results depending on that table.
withInval :: MonadSelda m => (Table a -> m b) -> Table a -> m b
withInval f t = do
res <- f t
liftIO $ invalidate $ tableName t
return res
-- | Execute a statement without a result.
exec :: MonadSelda m => Text -> [Param] -> m Int
exec q ps = do
backend <- seldaBackend
fmap fst . liftIO $ runStmt backend q ps