@@ -27,6 +27,7 @@ module ProjectM36.Client
2727 setCurrentSchemaName,
2828 transactionGraphAsRelation,
2929 relationVariablesAsRelation,
30+ disconnectedTransactionIsDirty,
3031 headName,
3132 remoteDBLookupName,
3233 defaultServerPort,
@@ -49,6 +50,7 @@ module ProjectM36.Client
4950 callTestTimeout_,
5051 RelationCardinality(..),
5152 TransactionGraphOperator(..),
53+ ProjectM36.Client.autoMergeToHead,
5254 transactionGraph_,
5355 disconnectedTransaction_,
5456 TransGraphRelationalExpr,
@@ -96,6 +98,7 @@ import Control.Monad.State
9698import Control.Monad.Trans.Reader
9799import qualified ProjectM36.RelationalExpression as RE
98100import ProjectM36.DatabaseContext (basicDatabaseContext)
101+import qualified ProjectM36.TransactionGraph as Graph
99102import ProjectM36.TransactionGraph
100103import qualified ProjectM36.Transaction as Trans
101104import ProjectM36.TransactionGraph.Persist
@@ -556,9 +559,30 @@ executeDatabaseContextExpr sessionId (InProcessConnection conf) expr = excMaybe
556559 newSession = Session newDiscon (Sess.schemaName session)
557560 STMMap.insert newSession sessionId sessions
558561 pure Nothing
559−
560562executeDatabaseContextExpr sessionId conn@(RemoteProcessConnection _) dbExpr = remoteCall conn (ExecuteDatabaseContextExpr sessionId dbExpr)
561563
564+-- | Similar to a git rebase, 'autoMergeToHead' atomically creates a temporary branch and merges it to the latest commit of the branch referred to by the 'HeadName' and commits the merge. This is useful to reduce incidents of 'TransactionIsNotAHeadError's but at the risk of merge errors (thus making it similar to rebasing).
565+autoMergeToHead :: SessionId -> Connection -> MergeStrategy -> HeadName -> IO (Either RelationalError ())
566+autoMergeToHead sessionId (InProcessConnection conf) strat headName' = do
567+ let sessions = ipSessions conf
568+ tvar = ipTransactionGraph conf
569+ id1 <- nextRandom
570+ id2 <- nextRandom
571+ atomically $ do
572+ eSession <- sessionForSessionId sessionId sessions
573+ case eSession of
574+ Left err -> pure (Left err)
575+ Right session -> do
576+ graph <- readTVar tvar
577+ case Graph.autoMergeToHead (id1, id2) (Sess.disconnectedTransaction session) headName' strat graph of
578+ Left err -> pure (Left err)
579+ Right (discon', graph') -> do
580+ writeTVar tvar graph'
581+ let newSession = Session discon' (Sess.schemaName session)
582+ STMMap.insert newSession sessionId sessions
583+ execute
584+autoMergeToHead sessionId conn@(RemoteProcessConnection _) strat headName' = remoteCall conn (ExecuteAutoMergeToHead sessionId strat headName')
585+
562586-- | Execute a database context IO-monad-based expression for the given session and connection. `DatabaseContextIOExpr` modify the DatabaseContext but cannot be purely implemented.
563587--this is almost completely identical to executeDatabaseContextExpr above
564588executeDatabaseContextIOExpr :: SessionId -> Connection -> DatabaseContextIOExpr -> IO (Maybe RelationalError)
@@ -865,6 +889,17 @@ atomTypesAsRelation sessionId (InProcessConnection conf) = do
865889 Left err -> pure (Left err)
866890 Right rel -> pure (Right rel)
867891atomTypesAsRelation sessionId conn@(RemoteProcessConnection _) = remoteCall conn (RetrieveAtomTypesAsRelation sessionId)
892+
893+disconnectedTransactionIsDirty :: SessionId -> Connection -> IO (Either RelationalError Bool)
894+disconnectedTransactionIsDirty sessionId (InProcessConnection conf) = do
895+ let sessions = ipSessions conf
896+ atomically $ do
897+ eSession <- sessionForSessionId sessionId sessions
898+ case eSession of
899+ Left err -> pure (Left err)
900+ Right session -> do
901+ pure (Right (isDirty session))
902+disconnectedTransactionIsDirty sessionId conn@(RemoteProcessConnection _) = remoteCall conn (RetrieveSessionIsDirty sessionId)
868903
869904--used only for testing- we expect this to throw an exception
870905callTestTimeout_ :: SessionId -> Connection -> IO Bool