@@ -10,6 +10,7 @@ module ProjectM36.Client
1010 close,
1111 executeRelationalExpr,
1212 executeDatabaseContextExpr,
13+ executeDatabaseContextIOExpr,
1314 executeGraphExpr,
1415 commit,
1516 rollback,
@@ -64,6 +65,7 @@ import ProjectM36.Persist (DiskSync(..))
6465import ProjectM36.Notifications
6566import ProjectM36.Server.RemoteCallTypes
6667import ProjectM36.Relation (typesAsRelation)
68+import ProjectM36.AtomFunctionBody (initScriptSession, ScriptSession)
6769import qualified ProjectM36.Relation as R
6870import Network.Transport.TCP (createTransport, defaultTCPParameters, encodeEndPointAddress)
6971import Control.Distributed.Process.Node (newLocalNode, initRemoteTable, runProcess, LocalNode, forkProcess)
@@ -137,7 +139,7 @@ defaultRemoteConnectionInfo = RemoteProcessConnectionInfo defaultDatabaseName (c
137139-- | The 'Connection' represents either local or remote access to a database. All operations flow through the connection.
138140type ClientNodes = STMSet.Set ProcessId
139141
140−data Connection = InProcessConnection PersistenceStrategy ClientNodes Sessions (TVar TransactionGraph) |
142+data Connection = InProcessConnection PersistenceStrategy ClientNodes Sessions (TVar TransactionGraph) ScriptSession |
141143 RemoteProcessConnection LocalNode ProcessId
142144
143145-- | There are several reasons why a connection can fail.
@@ -193,7 +195,8 @@ connectProjectM36 (InProcessConnectionInfo strat notificationCallback) = do
193195 clientNodes <- STMSet.newIO
194196 sessions <- STMMap.newIO
195197 notificationPid <- startNotificationListener notificationCallback
196− let conn = InProcessConnection strat clientNodes sessions graphTvar
198+ interpreterSession <- initScriptSession
199+ let conn = InProcessConnection strat clientNodes sessions graphTvar interpreterSession
197200 addClientNode conn notificationPid
198201 pure (Right conn)
199202 MinimalPersistence dbdir -> connectPersistentProjectM36 strat NoDiskSync dbdir freshGraph notificationCallback
@@ -239,21 +242,22 @@ connectPersistentProjectM36 strat sync dbdir freshGraph notificationCallback = d
239242 tvarGraph <- newTVarIO graph'
240243 sessions <- STMMap.newIO
241244 clientNodes <- STMSet.newIO
242− let conn = InProcessConnection strat clientNodes sessions tvarGraph
245+ scriptSession <- initScriptSession
246+ let conn = InProcessConnection strat clientNodes sessions tvarGraph scriptSession
243247 notificationPid <- startNotificationListener notificationCallback
244248 addClientNode conn notificationPid
245249 pure (Right conn)
246250
247251-- | Create a new session at the transaction id and return the session's Id.
248252createSessionAtCommit :: TransactionId -> Connection -> IO (Either RelationalError SessionId)
249−createSessionAtCommit commitId conn@(InProcessConnection _ _ _ _) = do
253+createSessionAtCommit commitId conn@(InProcessConnection _ _ _ _ _) = do
250254 newSessionId <- nextRandom
251255 atomically $ do
252256 createSessionAtCommit_ commitId newSessionId conn
253257createSessionAtCommit uuid conn@(RemoteProcessConnection _ _) = remoteCall conn (CreateSessionAtCommit uuid)
254258
255259createSessionAtCommit_ :: TransactionId -> SessionId -> Connection -> STM (Either RelationalError SessionId)
256−createSessionAtCommit_ commitId newSessionId (InProcessConnection _ _ sessions graphTvar) = do
260+createSessionAtCommit_ commitId newSessionId (InProcessConnection _ _ sessions graphTvar _) = do
257261 graph <- readTVar graphTvar
258262 case transactionForId commitId graph of
259263 Left err -> pure (Left err)
@@ -269,7 +273,7 @@ createSessionAtCommit_ _ _ (RemoteProcessConnection _ _) = error "createSessionA
269273
270274-- | Call 'createSessionAtHead' with a transaction graph's head's name to create a new session pinned to that head. This function returns a 'SessionId' which can be used in other function calls to reference the point in the transaction graph.
271275createSessionAtHead :: HeadName -> Connection -> IO (Either RelationalError SessionId)
272−createSessionAtHead headn conn@(InProcessConnection _ _ _ graphTvar) = do
276+createSessionAtHead headn conn@(InProcessConnection _ _ _ graphTvar _) = do
273277 newSessionId <- nextRandom
274278 atomically $ do
275279 graph <- readTVar graphTvar
@@ -281,16 +285,16 @@ createSessionAtHead headn conn@(RemoteProcessConnection _ _) = remoteCall conn (
281285-- | Used internally for server connections to keep track of remote nodes for the purpose of sending notifications later.
282286addClientNode :: Connection -> ProcessId -> IO ()
283287addClientNode (RemoteProcessConnection _ _) _ = error "addClientNode called on remote connection"
284−addClientNode (InProcessConnection _ clientNodes _ _) newProcessId = atomically (STMSet.insert newProcessId clientNodes)
288+addClientNode (InProcessConnection _ clientNodes _ _ _) newProcessId = atomically (STMSet.insert newProcessId clientNodes)
285289
286290-- | Discards a session, eliminating any uncommitted changes present in the session.
287291closeSession :: SessionId -> Connection -> IO ()
288−closeSession sessionId (InProcessConnection _ _ sessions _) = do
292+closeSession sessionId (InProcessConnection _ _ sessions _ _) = do
289293 atomically $ STMMap.delete sessionId sessions
290294closeSession sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (CloseSession sessionId)
291295-- | 'close' cleans up the database access connection. Note that sessions persist even after the connection is closed.
292296close :: Connection -> IO ()
293−close (InProcessConnection _ _ sessions _) = atomically $ do
297+close (InProcessConnection _ _ sessions _ _) = atomically $ do
294298 traverse_ (\(k,_) -> STMMap.delete k sessions) (STMMap.stream sessions)
295299 pure ()
296300
@@ -307,7 +311,7 @@ runProcessResult localNode proc = do
307311 takeMVar ret
308312
309313remoteCall :: (Serializable a, Serializable b) => Connection -> a -> IO b
310−remoteCall (InProcessConnection _ _ _ _) _ = error "remoteCall called on local connection"
314+remoteCall (InProcessConnection _ _ _ _ _) _ = error "remoteCall called on local connection"
311315remoteCall (RemoteProcessConnection localNode serverProcessId) arg = runProcessResult localNode $ do
312316 ret <- safeCall serverProcessId arg
313317 case ret of
@@ -321,7 +325,7 @@ sessionForSessionId sessionId sessions = do
321325
322326-- | Execute a relational expression in the context of the session and connection. Relational expressions are queries and therefore cannot alter the database.
323327executeRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Relation)
324−executeRelationalExpr sessionId (InProcessConnection _ _ sessions _) expr = atomically $ do
328+executeRelationalExpr sessionId (InProcessConnection _ _ sessions _ _) expr = atomically $ do
325329 eSession <- sessionForSessionId sessionId sessions
326330 case eSession of
327331 Left err -> pure $ Left err
@@ -330,7 +334,7 @@ executeRelationalExpr sessionId conn@(RemoteProcessConnection _ _) relExpr = rem
330334
331335-- | Execute a database context expression in the context of the session and connection. Database expressions modify the current session's disconnected transaction but cannot modify the transaction graph.
332336executeDatabaseContextExpr :: SessionId -> Connection -> DatabaseContextExpr -> IO (Maybe RelationalError)
333−executeDatabaseContextExpr sessionId (InProcessConnection _ _ sessions _) expr = atomically $ do
337+executeDatabaseContextExpr sessionId (InProcessConnection _ _ sessions _ _) expr = atomically $ do
334338 eSession <- sessionForSessionId sessionId sessions
335339 case eSession of
336340 Left err -> pure $ Just err
@@ -342,6 +346,24 @@ executeDatabaseContextExpr sessionId (InProcessConnection _ _ sessions _) expr =
342346 STMMap.insert newSession sessionId sessions
343347 return Nothing
344348executeDatabaseContextExpr sessionId conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (ExecuteDatabaseContextExpr sessionId dbExpr)
349+
350+-- | Execute a database context IO-monad-based expression for the given session and connection. `DatabaseContextIOExpr` modify the DatabaseContext but cannot be purely implemented.
351+--this is almost completely identical to executeDatabaseContextExpr above
352+executeDatabaseContextIOExpr :: SessionId -> Connection -> DatabaseContextIOExpr -> IO (Maybe RelationalError)
353+executeDatabaseContextIOExpr sessionId (InProcessConnection _ _ sessions _ scriptSession) expr = do
354+ eSession <- atomically $ sessionForSessionId sessionId sessions --potentially race condition due to interleaved IO?
355+ case eSession of
356+ Left err -> pure $ Just err
357+ Right session -> do
358+ res <- RE.evalDatabaseContextIOExpr scriptSession (sessionContext session) expr
359+ case res of
360+ Left err -> pure (Just err)
361+ Right context' -> do
362+ let newDiscon = DisconnectedTransaction (sessionParentId session) context'
363+ newSession = Session newDiscon
364+ atomically $ STMMap.insert newSession sessionId sessions
365+ pure Nothing
366+executeDatabaseContextIOExpr sessionId conn@(RemoteProcessConnection _ _) dbExpr = remoteCall conn (ExecuteDatabaseContextIOExpr sessionId dbExpr)
345367
346368executeGraphExprSTM_ :: TransactionId -> SessionId -> Sessions -> TransactionGraphOperator -> TVar TransactionGraph -> STM (Either RelationalError TransactionGraph)
347369executeGraphExprSTM_ freshId sessionId sessions graphExpr graphTvar = do
@@ -369,7 +391,7 @@ executeCommitExprSTM_ oldContext newContext nodes = do
369391
370392-- | Execute a transaction graph expression in the context of the session and connection. Transaction graph operators modify the transaction graph state.
371393executeGraphExpr :: SessionId -> Connection -> TransactionGraphOperator -> IO (Maybe RelationalError)
372−executeGraphExpr sessionId (InProcessConnection strat clientNodes sessions graphTvar) graphExpr = do
394+executeGraphExpr sessionId (InProcessConnection strat clientNodes sessions graphTvar _) graphExpr = do
373395 freshId <- nextRandom
374396 manip <- atomically $ do
375397 eSession <- sessionForSessionId sessionId sessions
@@ -411,7 +433,7 @@ commitSTM_ freshUUID sessionId sessions graph = do
411433
412434-- | After modifying a session, 'commit' the transaction to the transaction graph at the head which the session is referencing. This will also trigger checks for any notifications which need to be propagated.
413435commit :: SessionId -> Connection -> IO (Maybe RelationalError)
414−commit sessionId conn@(InProcessConnection _ _ _ _) = executeGraphExpr sessionId conn Commit
436+commit sessionId conn@(InProcessConnection _ _ _ _ _) = executeGraphExpr sessionId conn Commit
415437commit sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr sessionId Commit)
416438
417439sendNotifications :: [ProcessId] -> EvaluatedNotifications -> IO ()
@@ -425,7 +447,7 @@ sendNotifications pids nots = mapM_ sendNots pids
425447
426448-- | Discard any changes made in the current session. This resets the disconnected transaction to reference the original database context of the parent transaction and is a very cheap operation.
427449rollback :: SessionId -> Connection -> IO (Maybe RelationalError)
428−rollback sessionId conn@(InProcessConnection _ _ _ _) = executeGraphExpr sessionId conn Rollback
450+rollback sessionId conn@(InProcessConnection _ _ _ _ _) = executeGraphExpr sessionId conn Rollback
429451rollback sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteGraphExpr sessionId Rollback)
430452
431453processPersistence :: PersistenceStrategy -> TransactionGraph -> IO ()
@@ -435,11 +457,11 @@ processPersistence (CrashSafePersistence dbdir) graph = transactionGraphPersist
435457
436458-- | Return a relation whose type would match that of the relational expression if it were executed. This is useful for checking types and validating a relational expression's types.
437459typeForRelationalExpr :: SessionId -> Connection -> RelationalExpr -> IO (Either RelationalError Relation)
438−typeForRelationalExpr sessionId conn@(InProcessConnection _ _ _ _) relExpr = atomically $ typeForRelationalExprSTM sessionId conn relExpr
460+typeForRelationalExpr sessionId conn@(InProcessConnection _ _ _ _ _) relExpr = atomically $ typeForRelationalExprSTM sessionId conn relExpr
439461typeForRelationalExpr sessionId conn@(RemoteProcessConnection _ _) relExpr = remoteCall conn (ExecuteTypeForRelationalExpr sessionId relExpr)
440462
441463typeForRelationalExprSTM :: SessionId -> Connection -> RelationalExpr -> STM (Either RelationalError Relation)
442−typeForRelationalExprSTM sessionId (InProcessConnection _ _ sessions _) relExpr = do
464+typeForRelationalExprSTM sessionId (InProcessConnection _ _ sessions _ _) relExpr = do
443465 eSession <- sessionForSessionId sessionId sessions
444466 case eSession of
445467 Left err -> pure $ Left err
@@ -449,7 +471,7 @@ typeForRelationalExprSTM _ _ _ = error "typeForRelationalExprSTM called on non-l
449471
450472-- | Return a 'Map' of the database's constraints at the context of the session and connection.
451473inclusionDependencies :: SessionId -> Connection -> IO (Either RelationalError (M.Map IncDepName InclusionDependency))
452−inclusionDependencies sessionId (InProcessConnection _ _ sessions _) = do
474+inclusionDependencies sessionId (InProcessConnection _ _ sessions _ _) = do
453475 atomically $ do
454476 eSession <- sessionForSessionId sessionId sessions
455477 case eSession of
@@ -460,7 +482,7 @@ inclusionDependencies sessionId conn@(RemoteProcessConnection _ _) = remoteCall
460482
461483-- | Return an optimized database expression which is logically equivalent to the input database expression. This function can be used to determine which expression will actually be evaluated.
462484planForDatabaseContextExpr :: SessionId -> Connection -> DatabaseContextExpr -> IO (Either RelationalError DatabaseContextExpr)
463−planForDatabaseContextExpr sessionId (InProcessConnection _ _ sessions _) dbExpr = do
485+planForDatabaseContextExpr sessionId (InProcessConnection _ _ sessions _ _) dbExpr = do
464486 atomically $ do
465487 eSession <- sessionForSessionId sessionId sessions
466488 case eSession of
@@ -474,7 +496,7 @@ planForDatabaseContextExpr sessionId conn@(RemoteProcessConnection _ _) dbExpr =
474496-- * id- id attribute of the transaction
475497-- * parents- a relation-valued attribute which contains a relation of transaction ids which are parent transaction to the transaction
476498transactionGraphAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation)
477−transactionGraphAsRelation sessionId (InProcessConnection _ _ sessions tvar) = do
499+transactionGraphAsRelation sessionId (InProcessConnection _ _ sessions tvar _) = do
478500 atomically $ do
479501 eSession <- sessionForSessionId sessionId sessions
480502 case eSession of
@@ -486,7 +508,7 @@ transactionGraphAsRelation sessionId (InProcessConnection _ _ sessions tvar) = d
486508transactionGraphAsRelation sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (RetrieveTransactionGraph sessionId)
487509
488510relationVariablesAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation)
489−relationVariablesAsRelation sessionId (InProcessConnection _ _ sessions _) = do
511+relationVariablesAsRelation sessionId (InProcessConnection _ _ sessions _ _) = do
490512 atomically $ do
491513 eSession <- sessionForSessionId sessionId sessions
492514 case eSession of
@@ -497,7 +519,7 @@ relationVariablesAsRelation sessionId conn@(RemoteProcessConnection _ _) = remot
497519
498520-- | Returns the transaction id for the connection's disconnected transaction committed parent transaction.
499521headTransactionId :: SessionId -> Connection -> IO (Maybe TransactionId)
500−headTransactionId sessionId (InProcessConnection _ _ sessions _) = do
522+headTransactionId sessionId (InProcessConnection _ _ sessions _ _) = do
501523 atomically $ do
502524 eSession <- sessionForSessionId sessionId sessions
503525 case eSession of
@@ -517,12 +539,12 @@ headNameSTM_ sessionId sessions graphTvar = do
517539
518540-- | Returns Just the name of the head of the current disconnected transaction or Nothing.
519541headName :: SessionId -> Connection -> IO (Maybe HeadName)
520−headName sessionId (InProcessConnection _ _ sessions graphTvar) = do
542+headName sessionId (InProcessConnection _ _ sessions graphTvar _) = do
521543 atomically (headNameSTM_ sessionId sessions graphTvar)
522544headName sessionId conn@(RemoteProcessConnection _ _) = remoteCall conn (ExecuteHeadName sessionId)
523545
524546atomTypesAsRelation :: SessionId -> Connection -> IO (Either RelationalError Relation)
525−atomTypesAsRelation sessionId (InProcessConnection _ _ sessions _) = do
547+atomTypesAsRelation sessionId (InProcessConnection _ _ sessions _ _) = do
526548 atomically $ do
527549 eSession <- sessionForSessionId sessionId sessions
528550 case eSession of