@@ -1,76 +1,95 @@
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
1{-# LANGUAGE GeneralizedNewtypeDeriving #-}
2module ProjectM36.Client.Simple (
2module ProjectM36.Client.Simple (
3 simpleConnectProjectM36,
3 simpleConnectProjectM36,
4+ simpleConnectProjectM36At,
4 withTransaction,
5 withTransaction,
10 AtomType(..),
16 AtomType(..),
11 DbError(..),
19 DbError(..),
12 Attribute(..),
21 Attribute(..),
22+ C.Atomable(toAtom, fromAtom),
13 C.ConnectionInfo(..),
23 C.ConnectionInfo(..),
14 C.PersistenceStrategy(..),
24 C.PersistenceStrategy(..),
15 C.NotificationCallback,
25 C.NotificationCallback,
16 C.emptyNotificationCallback,
26 C.emptyNotificationCallback,
17 C.DatabaseContextExpr(..),
27 C.DatabaseContextExpr(..),
18− C.RelationalExprBase(..)
28+ C.RelationalExprBase(..)
20-- | A simplified client interface for Project:M36 database access.
30-- | A simplified client interface for Project:M36 database access.
21−import ProjectM36.Error
31+
32+import Control.Exception.Base
33+import Control.Monad ((<=<))
34+import Control.Monad.Reader
22import ProjectM36.Base
35import ProjectM36.Base
23import qualified ProjectM36.Client as C
36import qualified ProjectM36.Client as C
24−import Control.Monad.Reader
37+import ProjectM36.Error
25−import Control.Exception.Base
27type DbConn = (C.SessionId, C.Connection)
39type DbConn = (C.SessionId, C.Connection)
29−newtype Db a = Db {runDB :: ReaderT DbConn IO a}
41+newtype Db a = Db {runDb :: ReaderT DbConn IO a}
30 deriving (Functor, Applicative, Monad, MonadIO)
42 deriving (Functor, Applicative, Monad, MonadIO)
44+-- This exception type should never be observable by the API users.
45+-- It merely carries errors which end up as RelError at the end of a transaction.
32newtype TransactionCancelled = TransactionCancelled DbError deriving Show
46newtype TransactionCancelled = TransactionCancelled DbError deriving Show
33instance Exception TransactionCancelled
47instance Exception TransactionCancelled
35−-- | A simple alternative to 'connectProjectM36' which includes simple session management
49+-- | A simple alternative to 'connectProjectM36' which includes simple session management.
36−simpleConnectProjectM36 :: C.ConnectionInfo -> IO (Either DbError DbConn)
50+simpleConnectProjectM36At :: HeadName -> C.ConnectionInfo -> IO (Either DbError DbConn)
37−simpleConnectProjectM36 connInfo = do
51+simpleConnectProjectM36At headName connInfo = do
38 eConn <- C.connectProjectM36 connInfo
52 eConn <- C.connectProjectM36 connInfo
39 case eConn of
53 case eConn of
40 Left err -> pure (Left (ConnError err))
54 Left err -> pure (Left (ConnError err))
41 Right conn -> do
55 Right conn -> do
42− eSess <- C.createSessionAtHead conn "master"
56+ eSess <- C.createSessionAtHead conn headName
43 case eSess of
57 case eSess of
44 Left err -> do
58 Left err -> do
45 C.close conn
59 C.close conn
46 pure (Left (RelError err))
60 pure (Left (RelError err))
47 Right sess -> pure (Right (sess, conn))
61 Right sess -> pure (Right (sess, conn))
63+-- | Same as 'simpleConnectProjectM36At' but always connects to the @master@ branch.
64+simpleConnectProjectM36 :: C.ConnectionInfo -> IO (Either DbError DbConn)
65+simpleConnectProjectM36 = simpleConnectProjectM36At "master"
67+-- | Closes the database connection.
49−close :: DbConn -> IO ()
68+close :: DbConn -> IO ()
50close (_ , conn) = C.close conn
69close (_ , conn) = C.close conn
52-- | Runs a Db monad which may include some database updates. If an exception or error occurs, the transaction is rolled back. Otherwise, the transaction is committed to the head of the current branch.
71-- | Runs a Db monad which may include some database updates. If an exception or error occurs, the transaction is rolled back. Otherwise, the transaction is committed to the head of the current branch.
53withTransaction :: DbConn -> Db a -> IO (Either DbError a)
72withTransaction :: DbConn -> Db a -> IO (Either DbError a)
54−withTransaction sessconn = withTransactionUsing sessconn UnionMergeStrategy
73+withTransaction sessconn = withTransactionUsing sessconn UnionMergeStrategy
56-- | Same a 'withTransaction' except that the merge strategy can be specified.
75-- | Same a 'withTransaction' except that the merge strategy can be specified.
57withTransactionUsing :: DbConn -> MergeStrategy -> Db a -> IO (Either DbError a)
76withTransactionUsing :: DbConn -> MergeStrategy -> Db a -> IO (Either DbError a)
58withTransactionUsing (sess, conn) strat dbm = do
77withTransactionUsing (sess, conn) strat dbm = do
59 eHeadName <- C.headName sess conn
78 eHeadName <- C.headName sess conn
60− case eHeadName of
79+ case eHeadName of
61 Left err -> pure (Left (RelError err))
80 Left err -> pure (Left (RelError err))
62 Right headName -> do
81 Right headName -> do
63 let successFunc = C.autoMergeToHead sess conn strat headName
82 let successFunc = C.autoMergeToHead sess conn strat headName
64− block = runReaderT (runDB dbm) (sess, conn)
83+ block = runReaderT (runDb dbm) (sess, conn)
65 handler :: TransactionCancelled -> IO (Either DbError a)
84 handler :: TransactionCancelled -> IO (Either DbError a)
66 handler (TransactionCancelled err) = pure (Left err)
85 handler (TransactionCancelled err) = pure (Left err)
67 handle handler $ do
86 handle handler $ do
68 ret <- C.withTransaction sess conn (block >>= pure . Right) successFunc
87 ret <- C.withTransaction sess conn (block >>= pure . Right) successFunc
69− case ret of
88+ case ret of
70− Left err -> pure (Left (RelError err))
89+ Left err -> pure (Left (RelError err))
71 Right val -> pure (Right val)
90 Right val -> pure (Right val)
73−-- | A union of connection and other errors that can be returned from 'withDBConnection'.
92+-- | A union of connection and other errors that can be returned from 'withTransaction'.
74data DbError = ConnError C.ConnectionError |
93data DbError = ConnError C.ConnectionError |
75 RelError RelationalError |
94 RelError RelationalError |
76 TransactionRolledBack
95 TransactionRolledBack
@@ -78,26 +97,18 @@ data DbError = ConnError C.ConnectionError |
79-- | Execute a 'DatabaseContextExpr' in the 'DB' monad. Database context expressions manipulate the state of the database. In case of an error, the transaction is terminated and the connection's session is rolled back.
98-- | Execute a 'DatabaseContextExpr' in the 'DB' monad. Database context expressions manipulate the state of the database. In case of an error, the transaction is terminated and the connection's session is rolled back.
80execute :: C.DatabaseContextExpr -> Db ()
99execute :: C.DatabaseContextExpr -> Db ()
82− ret <- executeOrErr expr
100+execute = orCancelTransaction <=< executeOrErr
84− Left err -> liftIO $ cancelTransaction (RelError err)
87-- | Run a 'RelationalExpr' query in the 'DB' monad. Relational expressions perform read-only queries against the current database state.
102-- | Run a 'RelationalExpr' query in the 'DB' monad. Relational expressions perform read-only queries against the current database state.
88query :: C.RelationalExpr -> Db Relation
103query :: C.RelationalExpr -> Db Relation
89−query expr = do
104+query = orCancelTransaction <=< queryOrErr
90− ret <- queryOrErr expr
105+
92− Left err -> liftIO $ cancelTransaction (RelError err)
93− Right rel -> pure rel
95-- | Run a 'DatabaseContextExpr' update expression. If there is an error, just return it without cancelling the current transaction.
106-- | Run a 'DatabaseContextExpr' update expression. If there is an error, just return it without cancelling the current transaction.
96executeOrErr :: C.DatabaseContextExpr -> Db (Either RelationalError ())
107executeOrErr :: C.DatabaseContextExpr -> Db (Either RelationalError ())
97executeOrErr expr = Db $ do
108executeOrErr expr = Db $ do
98− (sess, conn) <- ask
109+ (sess, conn) <- ask
99 lift $ C.executeDatabaseContextExpr sess conn expr
110 lift $ C.executeDatabaseContextExpr sess conn expr
101-- | Run a 'RelationalExpr' query expression. If there is an error, just return it without cancelling the transaction.
112-- | Run a 'RelationalExpr' query expression. If there is an error, just return it without cancelling the transaction.
102queryOrErr :: C.RelationalExpr -> Db (Either RelationalError Relation)
113queryOrErr :: C.RelationalExpr -> Db (Either RelationalError Relation)
103queryOrErr expr = Db $ do
114queryOrErr expr = Db $ do
@@ -106,7 +117,12 @@ queryOrErr expr = Db $ do
107-- | Unconditionally roll back the current transaction and throw an exception to terminate the execution of the Db monad.
118-- | Unconditionally roll back the current transaction and throw an exception to terminate the execution of the Db monad.
108rollback :: Db ()
119rollback :: Db ()
109−rollback = Db $ lift $ cancelTransaction TransactionRolledBack
120+rollback = cancelTransaction TransactionRolledBack
122+-- | Cancel a transaction and carry some error information with it.
123+cancelTransaction :: DbError -> Db a
124+cancelTransaction err = liftIO $ throwIO (TransactionCancelled err)
111−cancelTransaction :: DbError -> IO a
126+-- | Converts the 'Either' result from a 'Db' action into an immediate cancel in the case of error.
112−cancelTransaction err = throwIO (TransactionCancelled err)
127+orCancelTransaction :: Either RelationalError a -> Db a
128+orCancelTransaction = either (cancelTransaction . RelError) pure