1{-# LANGUAGE OverloadedStrings #-}
2module ProjectM36.Client
11 executeRelationalExpr,
12 executeDatabaseContextExpr,
16 typeForRelationalExpr,
17 inclusionDependencies,
18 planForDatabaseContextExpr,
20 transactionGraphAsRelation,
25 PersistenceStrategy(..),
31 TransactionGraphOperator(..),
36import ProjectM36.Base hiding (inclusionDependencies) --defined in this module as well
37import qualified ProjectM36.Base as B
38import ProjectM36.Error
39import ProjectM36.StaticOptimizer
40import Control.Monad.State
41import ProjectM36.Transaction
42import qualified ProjectM36.RelationalExpression as RE
43import ProjectM36.TransactionGraph
44import ProjectM36.TransactionGraph.Persist
45import ProjectM36.Attribute
46import ProjectM36.Persist (DiskSync(..))
47import ProjectM36.Daemon.RemoteCallTypes
48import Network.Transport.TCP (createTransport, defaultTCPParameters, encodeEndPointAddress)
49import Control.Distributed.Process.Node (newLocalNode, initRemoteTable, runProcess, LocalNode)
50import Control.Distributed.Process.Extras.Internal.Types (whereisRemote)
51import Control.Distributed.Process.ManagedProcess.Client (call, safeCall)
52import Control.Distributed.Process (NodeId(..))
54import Data.UUID (UUID)
55import Data.UUID.V4 (nextRandom)
56import Control.Concurrent.STM
58import Control.Distributed.Process (ProcessId, Process)
59import Control.Exception (IOException)
60import Control.Concurrent.MVar
61import qualified Data.Map as M
62import Control.Distributed.Process.Serializable (Serializable)
63--import Control.Distributed.Process.Debug
69type DatabaseName = String
71data ConnectionInfo = InProcessConnectionInfo PersistenceStrategy |
72 RemoteProcessConnectionInfo DatabaseName NodeId
74createNodeId :: Hostname -> Port -> NodeId
75createNodeId host port = NodeId $ encodeEndPointAddress host (show port) 0
77defaultServerPort :: Port
78defaultServerPort = 6543
80data Connection = InProcessConnection PersistenceStrategy (TVar (DisconnectedTransaction, TransactionGraph)) |
81 RemoteProcessConnection LocalNode ProcessId -- perhaps should reference the local node as well
83data ConnectionError = SetupDatabaseDirectoryError PersistenceError |
84 IOExceptionError IOException |
85 NoSuchDatabaseByNameError DatabaseName |
89remoteDBLookupName :: DatabaseName -> String
90remoteDBLookupName = (++) "db-"
92commonLocalNode :: IO (Either ConnectionError LocalNode)
94 eLocalTransport <- createTransport "127.0.0.1" "0" defaultTCPParameters
95 case eLocalTransport of
96 Left err -> pure (Left $ IOExceptionError err)
97 Right localTransport -> newLocalNode localTransport initRemoteTable >>= pure . Right
99-- connect to an in-process database
100connectProjectM36 :: ConnectionInfo -> IO (Either ConnectionError Connection)
101--create a new in-memory database/transaction graph
102connectProjectM36 (InProcessConnectionInfo strat) = do
103 freshUUID <- nextRandom
104 let bootstrapContext = RE.basicDatabaseContext
105 freshDiscon = newDisconnectedTransaction freshUUID bootstrapContext
106 freshGraph = bootstrapTransactionGraph freshUUID bootstrapContext
108 --create date examples graph for now- probably should be empty context in the future
110 tvar <- newTVarIO (freshDiscon, freshGraph)
111 return $ Right $ InProcessConnection strat tvar
112 MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshDiscon freshGraph
113 CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshDiscon freshGraph
115connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
116 connStatus <- newEmptyMVar
117 eLocalNode <- commonLocalNode
118 let dbName = remoteDBLookupName databaseName
119 putStrLn $ show serverNodeId ++ " " ++ dbName
121 Left err -> pure (Left err)
122 Right localNode -> do
123 runProcess localNode $ do
124 mServerProcessId <- whereisRemote serverNodeId dbName
125 case mServerProcessId of
126 Nothing -> liftIO $ putMVar connStatus $ Left (NoSuchDatabaseByNameError databaseName)
127 Just serverProcessId -> do
128 loginConfirmation <- call serverProcessId Login
129 if not loginConfirmation then
130 liftIO $ putMVar connStatus (Left LoginError)
132 liftIO $ putMVar connStatus (Right $ RemoteProcessConnection localNode serverProcessId)
133 status <- takeMVar connStatus
136connectPersistentProjectM36 :: PersistenceStrategy ->
139 DisconnectedTransaction ->
141 IO (Either ConnectionError Connection)
142connectPersistentProjectM36 strat sync dbdir freshDiscon freshGraph = do
143 err <- setupDatabaseDir sync dbdir freshGraph
145 Just err' -> return $ Left (SetupDatabaseDirectoryError err')
147 graph <- transactionGraphLoad dbdir emptyTransactionGraph
149 Left err' -> return $ Left (SetupDatabaseDirectoryError err')
151 tvar <- newTVarIO (freshDiscon, graph')
152 return $ Right $ InProcessConnection strat tvar
154close :: Connection -> IO ()
155close (InProcessConnection _ _) = pure ()
156close (RemoteProcessConnection localNode serverProcessId) = do
157 runProcessResult localNode $ do
158 call serverProcessId Logout
160runProcessResult :: LocalNode -> Process a -> IO a
161runProcessResult localNode proc = do
163 runProcess localNode $ do
165 liftIO $ putMVar ret val
168remoteCall :: (Serializable a, Serializable b) => Connection -> a -> IO b
169remoteCall (InProcessConnection _ _) _ = error "remoteCall called on local connection"
170remoteCall (RemoteProcessConnection localNode serverProcessId) arg = runProcessResult localNode $ do
171 ret <- safeCall serverProcessId arg
173 Left err -> error (show err)
174 Right ret' -> pure ret'
176executeRelationalExpr :: Connection -> RelationalExpr -> IO (Either RelationalError Relation)
177executeRelationalExpr (InProcessConnection _ tvar) expr = atomically $ do
178 ((DisconnectedTransaction _ context), _) <- readTVar tvar
179 return $ evalState (RE.evalRelationalExpr expr) context
180executeRelationalExpr conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteRelationalExpr relExpr)
182executeDatabaseContextExpr :: Connection -> DatabaseExpr -> IO (Maybe RelationalError)
183executeDatabaseContextExpr (InProcessConnection _ tvar) expr = atomically $ do
184 ((DisconnectedTransaction parentUUID context), graph) <- readTVar tvar
185 case runState (RE.evalContextExpr expr) context of
186 (Just err,_) -> return $ Just err
187 (Nothing, context') -> do
188 let newDiscon = DisconnectedTransaction parentUUID context'
189 writeTVar tvar (newDiscon, graph)
191executeDatabaseContextExpr conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (ExecuteDatabaseContextExpr dbExpr)
193executeGraphExpr :: Connection -> TransactionGraphOperator -> IO (Maybe RelationalError)
194executeGraphExpr (InProcessConnection strat tvar) graphExpr = do
195 freshUUID <- nextRandom
196 manip <- atomically $ do
197 (discon, graph) <- readTVar tvar
198 case evalGraphOp freshUUID discon graph graphExpr of
199 Left err -> return $ Left err
200 Right (discon', graph') -> do
201 writeTVar tvar (discon', graph')
202 return $ Right (discon', graph')
204 Left err -> return $ Just err
205 Right (_, newGraph) -> do
206 --update filesystem database, if necessary
207 --this should really grab a lock at the beginning of the method to be threadsafe
208 processPersistence strat newGraph
210executeGraphExpr conn@(RemoteProcessConnection _ _) graphExpr = remoteCall conn (ExecuteGraphExpr graphExpr)
212commit :: Connection -> IO (Maybe RelationalError)
213commit conn@(InProcessConnection _ _) = executeGraphExpr conn Commit
214commit conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr Commit)
216rollback :: Connection -> IO (Maybe RelationalError)
217rollback conn@(InProcessConnection _ _) = executeGraphExpr conn Rollback
218rollback conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr Rollback)
220processPersistence :: PersistenceStrategy -> TransactionGraph -> IO ()
221processPersistence NoPersistence _ = return ()
222processPersistence (MinimalPersistence dbdir) graph = transactionGraphPersist NoDiskSync dbdir graph
223processPersistence (CrashSafePersistence dbdir) graph = transactionGraphPersist FsyncDiskSync dbdir graph
225typeForRelationalExpr :: Connection -> RelationalExpr -> IO (Either RelationalError Relation)
226typeForRelationalExpr conn@(InProcessConnection _ _) relExpr = atomically $ typeForRelationalExprSTM conn relExpr
227typeForRelationalExpr conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteTypeForRelationalExpr relExpr)
229typeForRelationalExprSTM :: Connection -> RelationalExpr -> STM (Either RelationalError Relation)
230typeForRelationalExprSTM (InProcessConnection _ tvar) relExpr = do
231 (DisconnectedTransaction _ context, _) <- readTVar tvar
232 pure $ evalState (RE.typeForRelationalExpr relExpr) context
233typeForRelationalExprSTM _ _ = error "typeForRelationalExprSTM called on non-local connection"
235inclusionDependencies :: Connection -> IO (M.Map IncDepName InclusionDependency)
236inclusionDependencies (InProcessConnection _ tvar) = do
238 ((DisconnectedTransaction _ context), _) <- readTVar tvar
239 pure (B.inclusionDependencies context)
240inclusionDependencies conn@(RemoteProcessConnection _ _) = remoteCall conn RetrieveInclusionDependencies
243planForDatabaseContextExpr :: Connection -> DatabaseExpr -> IO (Either RelationalError DatabaseExpr)
244planForDatabaseContextExpr (InProcessConnection _ tvar) dbExpr = do
246 ((DisconnectedTransaction _ context), _) <- readTVar tvar
247 pure $ evalState (applyStaticDatabaseOptimization dbExpr) context
248planForDatabaseContextExpr conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (RetrievePlanForDatabaseContextExpr dbExpr)
250transactionGraphAsRelation :: Connection -> IO (Either RelationalError Relation)
251transactionGraphAsRelation (InProcessConnection _ tvar) = do
253 (discon, graph) <- readTVar tvar
254 pure $ graphAsRelation discon graph
255transactionGraphAsRelation conn@(RemoteProcessConnection _ _) = remoteCall conn RetrieveTransactionGraph
257-- | Returns the UUID for the connection's disconnected transaction committed parent transaction.
258headTransactionUUID :: Connection -> IO (UUID)
259headTransactionUUID (InProcessConnection _ tvar) = do
261 (DisconnectedTransaction parentUUID _, _) <- readTVar tvar
263headTransactionUUID conn@(RemoteProcessConnection _ _) = remoteCall conn RetrieveHeadTransactionUUID
265-- | Returns Just the name of the head of the current disconnected transaction or Nothing.
266headName :: Connection -> IO (Maybe HeadName)
267headName (InProcessConnection _ tvar) = do
269 (DisconnectedTransaction parentUUID _, graph) <- readTVar tvar
270 pure $ case transactionForUUID parentUUID graph of
272 Right parentTrans -> headNameForTransaction parentTrans graph
273headName (RemoteProcessConnection localNode serverProcessId) = do
274 runProcessResult localNode $ do
275 ret <- call serverProcessId ExecuteHeadName
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list