@@ -30,6 +30,9 @@ module ProjectM36.Client
30 Attribute(..),
30 Attribute(..),
31 attributesFromList,
31 attributesFromList,
32 createNodeId,
32 createNodeId,
33+ createSessionAtCommit,
33 TransactionGraphOperator(..),
36 TransactionGraphOperator(..),
35 NodeId(..),
38 NodeId(..),
@@ -40,7 +43,6 @@ import qualified ProjectM36.Base as B
40import ProjectM36.Error
43import ProjectM36.Error
41import ProjectM36.StaticOptimizer
44import ProjectM36.StaticOptimizer
42import Control.Monad.State
45import Control.Monad.State
43−import ProjectM36.Transaction
44import qualified ProjectM36.RelationalExpression as RE
46import qualified ProjectM36.RelationalExpression as RE
45import ProjectM36.TransactionGraph
47import ProjectM36.TransactionGraph
46import ProjectM36.TransactionGraph.Persist
48import ProjectM36.TransactionGraph.Persist
@@ -63,6 +65,9 @@ import Control.Concurrent.MVar
63import qualified Data.Map as M
65import qualified Data.Map as M
64import Control.Distributed.Process.Serializable (Serializable)
66import Control.Distributed.Process.Serializable (Serializable)
65--import Control.Distributed.Process.Debug
67--import Control.Distributed.Process.Debug
68+import qualified STMContainers.Map as STMMap
69+import ProjectM36.Session
67type Hostname = String
72type Hostname = String
@@ -84,9 +89,12 @@ defaultDatabaseName = "base"
85defaultRemoteConnectionInfo :: ConnectionInfo
90defaultRemoteConnectionInfo :: ConnectionInfo
86defaultRemoteConnectionInfo = RemoteProcessConnectionInfo defaultDatabaseName (createNodeId "127.0.0.1" defaultServerPort)
91defaultRemoteConnectionInfo = RemoteProcessConnectionInfo defaultDatabaseName (createNodeId "127.0.0.1" defaultServerPort)
93+type Sessions = STMMap.Map SessionId Session
88−data Connection = InProcessConnection PersistenceStrategy (TVar (DisconnectedTransaction, TransactionGraph)) |
96+data Connection = InProcessConnection PersistenceStrategy Sessions (TVar TransactionGraph) |
89− RemoteProcessConnection LocalNode ProcessId -- perhaps should reference the local node as well
97+ RemoteProcessConnection LocalNode ProcessId
91data ConnectionError = SetupDatabaseDirectoryError PersistenceError |
99data ConnectionError = SetupDatabaseDirectoryError PersistenceError |
92 IOExceptionError IOException |
100 IOExceptionError IOException |
@@ -109,16 +117,16 @@ connectProjectM36 :: ConnectionInfo -> IO (Either ConnectionError Connection)
109--create a new in-memory database/transaction graph
117--create a new in-memory database/transaction graph
110connectProjectM36 (InProcessConnectionInfo strat) = do
118connectProjectM36 (InProcessConnectionInfo strat) = do
111 freshUUID <- nextRandom
119 freshUUID <- nextRandom
112− let bootstrapContext = RE.basicDatabaseContext
120+ let bootstrapContext = RE.basicDatabaseContext
113− freshDiscon = newDisconnectedTransaction freshUUID bootstrapContext
114 freshGraph = bootstrapTransactionGraph freshUUID bootstrapContext
121 freshGraph = bootstrapTransactionGraph freshUUID bootstrapContext
115 case strat of
122 case strat of
116 --create date examples graph for now- probably should be empty context in the future
123 --create date examples graph for now- probably should be empty context in the future
117 NoPersistence -> do
124 NoPersistence -> do
118− tvar <- newTVarIO (freshDiscon, freshGraph)
125+ graphTvar <- newTVarIO freshGraph
126+ sessions <- STMMap.newIO
119− return $ Right $ InProcessConnection strat tvar
127+ return $ Right $ InProcessConnection strat sessions graphTvar
120− MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshDiscon freshGraph
128+ MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph
121− CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshDiscon freshGraph
129+ CrashSafePersistence dbdir -> connectPersistentProjectM36 strat FsyncDiskSync dbdir freshGraph
123connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
131connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
124 connStatus <- newEmptyMVar
132 connStatus <- newEmptyMVar
@@ -140,14 +148,13 @@ connectProjectM36 (RemoteProcessConnectionInfo databaseName serverNodeId) = do
140 liftIO $ putMVar connStatus (Right $ RemoteProcessConnection localNode serverProcessId)
148 liftIO $ putMVar connStatus (Right $ RemoteProcessConnection localNode serverProcessId)
141 status <- takeMVar connStatus
149 status <- takeMVar connStatus
142 pure status
150 pure status
144connectPersistentProjectM36 :: PersistenceStrategy ->
152connectPersistentProjectM36 :: PersistenceStrategy ->
145 DiskSync ->
153 DiskSync ->
146 FilePath ->
154 FilePath ->
147− DisconnectedTransaction ->
148 TransactionGraph ->
155 TransactionGraph ->
149 IO (Either ConnectionError Connection)
156 IO (Either ConnectionError Connection)
150−connectPersistentProjectM36 strat sync dbdir freshDiscon freshGraph = do
157+connectPersistentProjectM36 strat sync dbdir freshGraph = do
151 err <- setupDatabaseDir sync dbdir freshGraph
158 err <- setupDatabaseDir sync dbdir freshGraph
152 case err of
159 case err of
153 Just err' -> return $ Left (SetupDatabaseDirectoryError err')
160 Just err' -> return $ Left (SetupDatabaseDirectoryError err')
@@ -156,11 +163,54 @@ connectPersistentProjectM36 strat sync dbdir freshDiscon freshGraph = do
156 case graph of
163 case graph of
157 Left err' -> return $ Left (SetupDatabaseDirectoryError err')
164 Left err' -> return $ Left (SetupDatabaseDirectoryError err')
158 Right graph' -> do
165 Right graph' -> do
159− tvar <- newTVarIO (freshDiscon, graph')
166+ tvarGraph <- newTVarIO graph'
167+ sessions <- STMMap.newIO
160− return $ Right $ InProcessConnection strat tvar
168+ return $ Right $ InProcessConnection strat sessions tvarGraph
170+-- | Create a new session at the transaction UUID and return the session's Id.
171+createSessionAtCommit :: UUID -> Connection -> IO (Either RelationalError SessionId)
172+createSessionAtCommit commitUUID conn@(InProcessConnection _ _ _) = do
173+ newSessionId <- nextRandom
175+ createSessionAtCommit_ commitUUID newSessionId conn
176+createSessionAtCommit uuid conn@(RemoteProcessConnection _ _) = remoteCall conn (CreateSessionAtCommit uuid)
178+createSessionAtCommit_ :: UUID -> SessionId -> Connection -> STM (Either RelationalError SessionId)
179+createSessionAtCommit_ commitUUID newSessionId (InProcessConnection _ sessions graphTvar) = do
180+ graph <- readTVar graphTvar
181+ case transactionForUUID commitUUID graph of
182+ Left err -> pure (Left err)
184+ let freshDiscon = DisconnectedTransaction commitUUID RE.basicDatabaseContext
185+ keyDuplication <- STMMap.lookup newSessionId sessions
186+ case keyDuplication of
187+ Just _ -> pure $ Left (SessionIdInUse newSessionId)
189+ STMMap.insert (Session freshDiscon) newSessionId sessions
190+ pure $ Right newSessionId
191+createSessionAtCommit_ _ _ (RemoteProcessConnection _ _) = error "createSessionAtCommit_ called on remote connection"
193+createSessionAtHead :: HeadName -> Connection -> IO (Either RelationalError SessionId)
194+createSessionAtHead headn conn@(InProcessConnection _ _ tvar) = do
195+ newSessionId <- nextRandom
197+ graph <- readTVar tvar
198+ case transactionForHead headn graph of
199+ Nothing -> pure $ Left (NoSuchHeadNameError headn)
200+ Just trans -> createSessionAtCommit_ (transactionUUID trans) newSessionId conn
201+createSessionAtHead headn conn@(RemoteProcessConnection _ _) = remoteCall conn (CreateSessionAtHead headn)
203+-- | Discards a session, eliminating any uncommitted changes present in the session.
204+closeSession :: SessionId -> Connection -> IO ()
205+closeSession sessionId (InProcessConnection _ sessions _) = do
207+ STMMap.delete sessionId sessions
208+closeSession sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (CloseSession sessionId)
162close :: Connection -> IO ()
210close :: Connection -> IO ()
163−close (InProcessConnection _ _) = pure ()
211+close (InProcessConnection _ sessions _) = atomically $ do
212+ traverse_ (\(k,_) -> STMMap.delete k sessions) (STMMap.stream sessions)
164close (RemoteProcessConnection localNode serverProcessId) = do
214close (RemoteProcessConnection localNode serverProcessId) = do
165 runProcessResult localNode $ do
215 runProcessResult localNode $ do
166 call serverProcessId Logout
216 call serverProcessId Logout
@@ -174,114 +224,145 @@ runProcessResult localNode proc = do
174 takeMVar ret
224 takeMVar ret
176remoteCall :: (Serializable a, Serializable b) => Connection -> a -> IO b
226remoteCall :: (Serializable a, Serializable b) => Connection -> a -> IO b
177−remoteCall (InProcessConnection _ _) _ = error "remoteCall called on local connection"
227+remoteCall (InProcessConnection _ _ _) _ = error "remoteCall called on local connection"
178remoteCall (RemoteProcessConnection localNode serverProcessId) arg = runProcessResult localNode $ do
228remoteCall (RemoteProcessConnection localNode serverProcessId) arg = runProcessResult localNode $ do
179 ret <- safeCall serverProcessId arg
229 ret <- safeCall serverProcessId arg
180 case ret of
230 case ret of
181 Left err -> error (show err)
231 Left err -> error (show err)
182 Right ret' -> pure ret'
232 Right ret' -> pure ret'
234+sessionForSessionId :: SessionId -> Sessions -> STM (Either RelationalError Session)
235+sessionForSessionId sessionId sessions = do
236+ maybeSession <- STMMap.lookup sessionId sessions
237+ pure $ maybe (Left $ NoSuchSession sessionId) Right maybeSession
184−executeRelationalExpr :: Connection -> RelationalExpr -> IO (Either RelationalError Relation)
239+executeRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Relation)
185−executeRelationalExpr (InProcessConnection _ tvar) expr = atomically $ do
240+executeRelationalExpr sessionId (InProcessConnection _ sessions _) expr = atomically $ do
186− ((DisconnectedTransaction _ context), _) <- readTVar tvar
241+ eSession <- sessionForSessionId sessionId sessions
243+ Left err -> pure $ Left err
187− return $ evalState (RE.evalRelationalExpr expr) context
244+ Right (Session (DisconnectedTransaction _ context)) -> pure $ evalState (RE.evalRelationalExpr expr) context
188−executeRelationalExpr conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteRelationalExpr relExpr)
245+executeRelationalExpr sessionId conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteRelationalExpr sessionId relExpr)
190−executeDatabaseContextExpr :: Connection -> DatabaseExpr -> IO (Maybe RelationalError)
247+executeDatabaseContextExpr :: SessionId -> Connection -> DatabaseExpr -> IO (Maybe RelationalError)
191−executeDatabaseContextExpr (InProcessConnection _ tvar) expr = atomically $ do
248+executeDatabaseContextExpr sessionId (InProcessConnection _ sessions _) expr = atomically $ do
192− ((DisconnectedTransaction parentUUID context), graph) <- readTVar tvar
249+ eSession <- sessionForSessionId sessionId sessions
251+ Left err -> pure $ Just err
193− case runState (RE.evalContextExpr expr) context of
252+ Right session -> case runState (RE.evalContextExpr expr) (sessionContext session) of
194− (Just err,_) -> return $ Just err
253+ (Just err,_) -> return $ Just err
195− (Nothing, context') -> do
254+ (Nothing, context') -> do
196− let newDiscon = DisconnectedTransaction parentUUID context'
255+ let newDiscon = DisconnectedTransaction (sessionParentUUID session) context'
197− writeTVar tvar (newDiscon, graph)
256+ newSession = Session newDiscon
257+ STMMap.insert newSession sessionId sessions
198 return Nothing
258 return Nothing
199−executeDatabaseContextExpr conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (ExecuteDatabaseContextExpr dbExpr)
259+executeDatabaseContextExpr sessionId conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (ExecuteDatabaseContextExpr sessionId dbExpr)
201−executeGraphExpr :: Connection -> TransactionGraphOperator -> IO (Maybe RelationalError)
261+executeGraphExpr :: SessionId -> Connection -> TransactionGraphOperator -> IO (Maybe RelationalError)
202−executeGraphExpr (InProcessConnection strat tvar) graphExpr = do
262+executeGraphExpr sessionId (InProcessConnection strat sessions graphTvar) graphExpr = do
203 freshUUID <- nextRandom
263 freshUUID <- nextRandom
204 manip <- atomically $ do
264 manip <- atomically $ do
205− (discon, graph) <- readTVar tvar
265+ graph <- readTVar graphTvar
266+ eSession <- sessionForSessionId sessionId sessions
268+ Left err -> pure $ Left err
206− case evalGraphOp freshUUID discon graph graphExpr of
269+ Right (Session discon) -> case evalGraphOp freshUUID discon graph graphExpr of
207− Left err -> return $ Left err
270+ Left err -> pure $ Left err
208− Right (discon', graph') -> do
271+ Right (discon', graph') -> do
209− writeTVar tvar (discon', graph')
272+ writeTVar graphTvar graph'
210− return $ Right (discon', graph')
273+ let newSession = Session discon'
274+ STMMap.insert newSession sessionId sessions
211 case manip of
276 case manip of
212 Left err -> return $ Just err
277 Left err -> return $ Just err
213− Right (_, newGraph) -> do
278+ Right newGraph -> do
214 --update filesystem database, if necessary
279 --update filesystem database, if necessary
215 --this should really grab a lock at the beginning of the method to be threadsafe
280 --this should really grab a lock at the beginning of the method to be threadsafe
216 processPersistence strat newGraph
281 processPersistence strat newGraph
217 return Nothing
282 return Nothing
218−executeGraphExpr conn@(RemoteProcessConnection _ _) graphExpr = remoteCall conn (ExecuteGraphExpr graphExpr)
283+executeGraphExpr sessionId conn@(RemoteProcessConnection _ _) graphExpr = remoteCall conn (ExecuteGraphExpr sessionId graphExpr)
220−commit :: Connection -> IO (Maybe RelationalError)
285+commit :: SessionId -> Connection -> IO (Maybe RelationalError)
221−commit conn@(InProcessConnection _ _) = executeGraphExpr conn Commit
286+commit sessionId conn@(InProcessConnection _ _ _) = executeGraphExpr sessionId conn Commit
222−commit conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr Commit)
287+commit sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr sessionId Commit)
224−rollback :: Connection -> IO (Maybe RelationalError)
289+rollback :: SessionId -> Connection -> IO (Maybe RelationalError)
225−rollback conn@(InProcessConnection _ _) = executeGraphExpr conn Rollback
290+rollback sessionId conn@(InProcessConnection _ _ _) = executeGraphExpr sessionId conn Rollback
226−rollback conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr Rollback)
291+rollback sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr sessionId Rollback)
228processPersistence :: PersistenceStrategy -> TransactionGraph -> IO ()
293processPersistence :: PersistenceStrategy -> TransactionGraph -> IO ()
229processPersistence NoPersistence _ = return ()
294processPersistence NoPersistence _ = return ()
230processPersistence (MinimalPersistence dbdir) graph = transactionGraphPersist NoDiskSync dbdir graph
295processPersistence (MinimalPersistence dbdir) graph = transactionGraphPersist NoDiskSync dbdir graph
231processPersistence (CrashSafePersistence dbdir) graph = transactionGraphPersist FsyncDiskSync dbdir graph
296processPersistence (CrashSafePersistence dbdir) graph = transactionGraphPersist FsyncDiskSync dbdir graph
233−typeForRelationalExpr :: Connection -> RelationalExpr -> IO (Either RelationalError Relation)
298+typeForRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Relation)
234−typeForRelationalExpr conn@(InProcessConnection _ _) relExpr = atomically $ typeForRelationalExprSTM conn relExpr
299+typeForRelationalExpr sessionId conn@(InProcessConnection _ _ _) relExpr = atomically $ typeForRelationalExprSTM sessionId conn relExpr
235−typeForRelationalExpr conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteTypeForRelationalExpr relExpr)
300+typeForRelationalExpr sessionId conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteTypeForRelationalExpr sessionId relExpr)
237−typeForRelationalExprSTM :: Connection -> RelationalExpr -> STM (Either RelationalError Relation)
302+typeForRelationalExprSTM :: SessionId -> Connection -> RelationalExpr -> STM (Either RelationalError Relation)
238−typeForRelationalExprSTM (InProcessConnection _ tvar) relExpr = do
303+typeForRelationalExprSTM sessionId (InProcessConnection _ sessions _) relExpr = do
239− (DisconnectedTransaction _ context, _) <- readTVar tvar
304+ eSession <- sessionForSessionId sessionId sessions
306+ Left err -> pure $ Left err
240− pure $ evalState (RE.typeForRelationalExpr relExpr) context
307+ Right session -> pure $ evalState (RE.typeForRelationalExpr relExpr) (sessionContext session)
241−typeForRelationalExprSTM _ _ = error "typeForRelationalExprSTM called on non-local connection"
309+typeForRelationalExprSTM _ _ _ = error "typeForRelationalExprSTM called on non-local connection"
243−inclusionDependencies :: Connection -> IO (M.Map IncDepName InclusionDependency)
311+inclusionDependencies :: SessionId -> Connection -> IO (Either RelationalError (M.Map IncDepName InclusionDependency))
244−inclusionDependencies (InProcessConnection _ tvar) = do
312+inclusionDependencies sessionId (InProcessConnection _ sessions _) = do
245 atomically $ do
313 atomically $ do
246− ((DisconnectedTransaction _ context), _) <- readTVar tvar
314+ eSession <- sessionForSessionId sessionId sessions
316+ Left err -> pure $ Left err
247− pure (B.inclusionDependencies context)
317+ Right session -> pure $ Right (B.inclusionDependencies (sessionContext session))
248−inclusionDependencies conn@(RemoteProcessConnection _ _) = remoteCall conn RetrieveInclusionDependencies
319+inclusionDependencies sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (RetrieveInclusionDependencies sessionId)
251−planForDatabaseContextExpr :: Connection -> DatabaseExpr -> IO (Either RelationalError DatabaseExpr)
322+planForDatabaseContextExpr :: SessionId -> Connection -> DatabaseExpr -> IO (Either RelationalError DatabaseExpr)
252−planForDatabaseContextExpr (InProcessConnection _ tvar) dbExpr = do
323+planForDatabaseContextExpr sessionId (InProcessConnection _ sessions _) dbExpr = do
253 atomically $ do
324 atomically $ do
254− ((DisconnectedTransaction _ context), _) <- readTVar tvar
325+ eSession <- sessionForSessionId sessionId sessions
327+ Left err -> pure $ Left err
255− pure $ evalState (applyStaticDatabaseOptimization dbExpr) context
328+ Right session -> pure $ evalState (applyStaticDatabaseOptimization dbExpr) (sessionContext session)
256−planForDatabaseContextExpr conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (RetrievePlanForDatabaseContextExpr dbExpr)
329+planForDatabaseContextExpr sessionId conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (RetrievePlanForDatabaseContextExpr sessionId dbExpr)
258−transactionGraphAsRelation :: Connection -> IO (Either RelationalError Relation)
331+transactionGraphAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation)
259−transactionGraphAsRelation (InProcessConnection _ tvar) = do
332+transactionGraphAsRelation sessionId (InProcessConnection _ sessions tvar) = do
260 atomically $ do
333 atomically $ do
334+ eSession <- sessionForSessionId sessionId sessions
336+ Left err -> pure $ Left err
337+ Right (Session discon) -> do
261− (discon, graph) <- readTVar tvar
338+ graph <- readTVar tvar
262− pure $ graphAsRelation discon graph
339+ pure $ graphAsRelation discon graph
263−transactionGraphAsRelation conn@(RemoteProcessConnection _ _) = remoteCall conn RetrieveTransactionGraph
341+transactionGraphAsRelation sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (RetrieveTransactionGraph sessionId)
265-- | Returns the UUID for the connection's disconnected transaction committed parent transaction.
343-- | Returns the UUID for the connection's disconnected transaction committed parent transaction.
266−headTransactionUUID :: Connection -> IO (UUID)
344+headTransactionUUID :: SessionId -> Connection -> IO (Maybe UUID)
267−headTransactionUUID (InProcessConnection _ tvar) = do
345+headTransactionUUID sessionId (InProcessConnection _ sessions _) = do
268 atomically $ do
346 atomically $ do
269− (DisconnectedTransaction parentUUID _, _) <- readTVar tvar
347+ eSession <- sessionForSessionId sessionId sessions
270− pure parentUUID
348+ case eSession of
349+ Left _ -> pure Nothing
350+ Right session -> pure $ Just (sessionParentUUID session)
271−headTransactionUUID conn@(RemoteProcessConnection _ _) = remoteCall conn RetrieveHeadTransactionUUID
351+headTransactionUUID sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (RetrieveHeadTransactionUUID sessionId)
273-- | Returns Just the name of the head of the current disconnected transaction or Nothing.
353-- | Returns Just the name of the head of the current disconnected transaction or Nothing.
274−headName :: Connection -> IO (Maybe HeadName)
354+headName :: SessionId -> Connection -> IO (Maybe HeadName)
275−headName (InProcessConnection _ tvar) = do
355+headName sessionId (InProcessConnection _ sessions graphTvar) = do
276 atomically $ do
356 atomically $ do
277− (DisconnectedTransaction parentUUID _, graph) <- readTVar tvar
357+ graph <- readTVar graphTvar
358+ eSession <- sessionForSessionId sessionId sessions
360+ Left _ -> pure $ Nothing
278− pure $ case transactionForUUID parentUUID graph of
361+ Right session -> pure $ case transactionForUUID (sessionParentUUID session) graph of
279− Left _ -> Nothing
362+ Left _ -> Nothing
280− Right parentTrans -> headNameForTransaction parentTrans graph
363+ Right parentTrans -> headNameForTransaction parentTrans graph
281−headName (RemoteProcessConnection localNode serverProcessId) = do
364+headName sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteHeadName sessionId)
282− runProcessResult localNode $ do
365+
283− ret <- call serverProcessId ExecuteHeadName