on master help sign in
add new+old report expressions to notifications

resolves #157

commit6590d3e68881723cdfd9a31597b06fcb5e0375a5 authorAgentM <agentm@themactionfaction.com> dateTue 26 Sep 2017, 22:43 (9 years ago) parent5c038e4
8 files changed+48−40
docs/using_notifications.markdown+22−18 src/bin/TutorialD/Interpreter.hs+6−5 src/bin/TutorialD/Interpreter/DatabaseContextExpr.hs+3−2 src/lib/ProjectM36/Base.hs+3−2 src/lib/ProjectM36/Client.hs+6−3 src/lib/ProjectM36/Notifications.hs+2−2 src/lib/ProjectM36/RelationalExpression.hs+3−2 src/lib/ProjectM36/StaticOptimizer.hs+3−6
docs/using_notifications.markdown+22−18
@@ -9,8 +9,9 @@ As an example, consider an email application. Users of the applications will wan
99A Project:M36 notification is comprised of three user-selected parts:
1010
11111. an arbitrary notification name: used to uniquely identify the notification in the server and client
12−2. a trigger relational expression: when the outcome of this relational expression changes between two transactions, the notification is fired
13−3. a report relational expression: when the notification fires, the expression is evaluated and passed along
12+1. a trigger relational expression: when the outcome of this relational expression changes between two transactions, the notification is fired
13+1. a report relational expression for the pre-change context: when the notification fires, the expression is evaluated in the old context and passed along
14+1. a report relational expression for the post-change context: when the notification fires, the expression is evaluated in the new context and passed along
1415
1516By separating the "trigger" and "report" expressions, database users control what kind of reporting they receive when the outcome of a relational expression changes.
1617
@@ -21,7 +22,7 @@ Currently, notifications are sent to all connected clients and not only to the c
2122Notifications can be created in the ```tutd``` TutorialD interpreter with the following syntax:
2223
2324```
24−notify <notification name> <trigger relational expression> <report relational expression>
25+notify <notification name> <trigger relational expression> <report relational expression (pre-change)> <report relational expression (post-change)>
2526```
2627
2728## Deleting a Notification
@@ -39,21 +40,23 @@ Consider the following ```person``` relation variable where "name" is a key:
3940```
4041TutorialD (master/main): person:=relation{tuple{name "Steve",address "Main St."},tuple{name "John", address "Elm St."}}
4142TutorialD (master/main): :showexpr person
42−┌────────┬─────┐
43−│address │name │
44−├────────┼─────┤
45−│Elm St. │John │
46−│Main St.│Steve│
47−└────────┴─────┘
43+┌─────────────┬──────────┐
44+│address::Text│name::Text│
45+├─────────────┼──────────┤
46+│"Main St." │"Steve" │
47+│"Elm St." │"John" │
48+└─────────────┴──────────┘
4849```
4950
5051If a remote application is currently the address in the tuple for "Steve", then it would install a notification for that tuple's key:
5152
5253```
53−TutorialD (master/main): notify steve_change person where name="Steve" (person where name="Steve"){address}
54+TutorialD (master/main): notify steve_change person where name="Steve" true (person where name="Steve"){address}
5455TutorialD (master/main): :commit
5556```
5657
58+In this case, we pass `true` as the old context report expression because we plan to ignore the result.
59+
5760Then, when any client including the initiating client updates the "Steve" tuple, an asynchronous notification is fired:
5861
5962```
@@ -62,16 +65,17 @@ TutorialD (master/main): :commit
6265TutorialD (master/main): Notification received "steve_change": ...
6366```
6467
65−The asynchronously-received notification contains the result from the evaluated report relational expression. The expression, in this case, is the result of the report expression evaluated against the parent transactoin
68+The asynchronously-received notification contains the result from the evaluated report relational expression. The expression, in this case, is the result of the report expression evaluated against the new transaction:
6669
6770```
6871TutorialD (master/main): Notification received "steve_change":
6972Project (AttributeNames (fromList ["address"])) (Restrict (AttributeEqualityPredicate "name" (NakedAtomExpr Atom "Steve")) (RelationVariable "person"))
70−┌────────┐
71−│address │
72−├────────┤
73−│Main St.│
74−└────────┘
73+┌─────────────┐
74+│address::Text│
75+├─────────────┤
76+│"Grove St." │
77+└─────────────┘
78+
7579```
7680
7781## Notifications with ProjectM36.Client
@@ -80,10 +84,10 @@ First, implement a ```NotificationCallback``` function to perform an IO action w
8084
8185Each ProjectM36 Connection can only have one callback and it cannot be changed after Connection creation.
8286
83−Then, create a DatabaseExpr using the AddNotification constructor:
87+Then, create a DatabaseContextExpr using the AddNotification constructor:
8488
8589```
86−AddNotification <notification name::Text> <change expression::RelationalExpr> <report expression::RelationalExpr>
90+AddNotification <notification name::Text> <change expression::RelationalExpr> <report old context expression::RelationalExpr> <report new context expression::RelationalExpr>
8791```
8892
8993and execute it using ```executeDatabaseContextExpr```. In order for a notification to take effect, it must be committed as well.
src/bin/TutorialD/Interpreter.hs+6−5
@@ -123,7 +123,7 @@ evalTutorialDInteractive sessionId conn safe interactive expr = case expr of
123123 cancel <- runInputT settings $ do
124124 let promptDiscardChanges = do
125125 isatty <- haveTerminalUI
126− if isatty && interactive then do
126+ if isatty && interactive && execOp /= Commit && execOp /= Rollback then do
127127 mYesOrNo <- getInputLine "The current transaction has uncommitted changes. If you continue, the changes will be lost. Continue? (Y/n): "
128128 case mYesOrNo of
129129 Nothing -> promptDiscardChanges
@@ -222,12 +222,13 @@ data InterpreterConfig = LocalInterpreterConfig PersistenceStrategy HeadName (Ma
222222 RemoteInterpreterConfig C.NodeId C.DatabaseName HeadName (Maybe TutorialDExec)
223223
224224outputNotificationCallback :: C.NotificationCallback
225−outputNotificationCallback notName evaldNot = hPutStrLn stderr $ "Notification received " ++ show notName ++ ":\n" ++ show (reportExpr (C.notification evaldNot)) ++ "\n" ++ prettyEvaluatedNotification evaldNot
225+outputNotificationCallback notName evaldNot = hPutStrLn stderr $ "Notification received " ++ show notName ++ ":\n" ++ "\n" ++ prettyEvaluatedNotification evaldNot
226226
227227prettyEvaluatedNotification :: C.EvaluatedNotification -> String
228−prettyEvaluatedNotification eNotif = case C.reportRelation eNotif of
229− Left err -> show err
230− Right reportRel -> T.unpack (showRelation reportRel)
228+prettyEvaluatedNotification eNotif = let eRelShow eRel = case eRel of
229+ Left err -> show err
230+ Right reportRel -> T.unpack (showRelation reportRel) in
231+ eRelShow (C.reportOldRelation eNotif) <> "\n" <> eRelShow (C.reportNewRelation eNotif)
231232
232233reprLoop :: InterpreterConfig -> C.SessionId -> C.Connection -> IO ()
233234reprLoop config sessionId conn = do
src/bin/TutorialD/Interpreter/DatabaseContextExpr.hs+3−2
@@ -153,8 +153,9 @@ addNotificationP = do
153153 reserved "notify"
154154 notName <- identifier
155155 triggerExpr <- relExprP
156− resultExpr <- relExprP
157− pure $ AddNotification notName triggerExpr resultExpr
156+ resultOldExpr <- relExprP
157+ resultNewExpr <- relExprP
158+ pure $ AddNotification notName triggerExpr resultOldExpr resultNewExpr
158159
159160removeNotificationP :: Parser DatabaseContextExpr
160161removeNotificationP = do
src/lib/ProjectM36/Base.hs+3−2
@@ -227,7 +227,8 @@ type Notifications = M.Map NotificationName Notification
227227-- | When the changeExpr returns a different result in the database context, then the reportExpr is triggered and sent asynchronously to all clients.
228228data Notification = Notification {
229229 changeExpr :: RelationalExpr,
230− reportExpr :: RelationalExpr
230+ reportOldExpr :: RelationalExpr, --run the expression in the pre-change context
231+ reportNewExpr :: RelationalExpr --run the expression in the post-change context
231232 }
232233 deriving (Show, Eq, Binary, Generic, NFData)
233234
@@ -317,7 +318,7 @@ data DatabaseContextExpr =
317318 AddInclusionDependency IncDepName InclusionDependency |
318319 RemoveInclusionDependency IncDepName |
319320
320− AddNotification NotificationName RelationalExpr RelationalExpr |
321+ AddNotification NotificationName RelationalExpr RelationalExpr RelationalExpr |
321322 RemoveNotification NotificationName |
322323
323324 AddTypeConstructor TypeConstructorDef [DataConstructorDef] |
src/lib/ProjectM36/Client.hs+6−3
@@ -184,10 +184,11 @@ type EvaluatedNotifications = M.Map NotificationName EvaluatedNotification
184184newtype NotificationMessage = NotificationMessage EvaluatedNotifications
185185 deriving (Binary, Eq, Show, Generic)
186186
187−-- | When a notification is fired, the 'reportExpr' is evaluated in the commit's context, so that is returned along with the original notification.
187+-- | When a notification is fired, the 'reportOldExpr' is evaluated in the commit's pre-change context while the 'reportNewExpr' is evaluated in the post-change context and they are returned along with the original notification.
188188data EvaluatedNotification = EvaluatedNotification {
189189 notification :: Notification,
190− reportRelation :: Either RelationalError Relation
190+ reportOldRelation :: Either RelationalError Relation,
191+ reportNewRelation :: Either RelationalError Relation
191192 }
192193 deriving(Binary, Eq, Show, Generic)
193194
@@ -632,8 +633,10 @@ executeCommitExprSTM_ oldContext newContext nodes = do
632633 let nots = notifications oldContext
633634 fireNots = notificationChanges nots oldContext newContext
634635 evaldNots = M.map mkEvaldNot fireNots
636+ evalInContext expr ctx = runReader (RE.evalRelationalExpr expr) (RE.mkRelationalExprState ctx)
635637 mkEvaldNot notif = EvaluatedNotification { notification = notif,
636− reportRelation = runReader (RE.evalRelationalExpr (reportExpr notif)) (RE.mkRelationalExprState oldContext) }
638+ reportOldRelation = evalInContext (reportOldExpr notif) oldContext,
639+ reportNewRelation = evalInContext (reportNewExpr notif) newContext}
637640 pure (evaldNots, nodes)
638641
639642-- | Execute a transaction graph expression in the context of the session and connection. Transaction graph operators modify the transaction graph state.
src/lib/ProjectM36/Notifications.hs+2−2
@@ -9,8 +9,8 @@ import Data.Either (isRight)
99notificationChanges :: Notifications -> DatabaseContext -> DatabaseContext -> Notifications
1010notificationChanges nots context1 context2 = M.filter notificationFilter nots
1111 where
12− notificationFilter (Notification chExpr _) = let oldChangeEval = evalChangeExpr chExpr (RelationalExprStateElems context1)
13− newChangeEval = evalChangeExpr chExpr (RelationalExprStateElems context2) in
12+ notificationFilter (Notification chExpr _ _) = let oldChangeEval = evalChangeExpr chExpr (RelationalExprStateElems context1)
13+ newChangeEval = evalChangeExpr chExpr (RelationalExprStateElems context2) in
1414 oldChangeEval /= newChangeEval && isRight oldChangeEval
1515 evalChangeExpr chExpr = runReader (evalRelationalExpr chExpr)
1616
src/lib/ProjectM36/RelationalExpression.hs+3−2
@@ -366,7 +366,7 @@ evalDatabaseContextExpr (RemoveInclusionDependency depName) = do
366366 return Nothing
367367
368368-- | Add a notification which will send the resultExpr when triggerExpr changes between commits.
369−evalDatabaseContextExpr (AddNotification notName triggerExpr resultExpr) = do
369+evalDatabaseContextExpr (AddNotification notName triggerExpr resultOldExpr resultNewExpr) = do
370370 currentContext <- getStateContext
371371 let nots = notifications currentContext
372372 if M.member notName nots then
@@ -374,7 +374,8 @@ evalDatabaseContextExpr (AddNotification notName triggerExpr resultExpr) = do
374374 else do
375375 let newNotifications = M.insert notName newNotification nots
376376 newNotification = Notification { changeExpr = triggerExpr,
377− reportExpr = resultExpr }
377+ reportOldExpr = resultOldExpr,
378+ reportNewExpr = resultNewExpr}
378379 putStateContext $ currentContext { notifications = newNotifications }
379380 return Nothing
380381
src/lib/ProjectM36/StaticOptimizer.hs+3−6
@@ -148,16 +148,13 @@ applyStaticDatabaseOptimization dep@(AddInclusionDependency _ _) = return $ Righ
148148
149149applyStaticDatabaseOptimization (RemoveInclusionDependency name) = return $ Right (RemoveInclusionDependency name)
150150
151−applyStaticDatabaseOptimization (AddNotification name triggerExpr resultExpr) = do
151+applyStaticDatabaseOptimization (AddNotification name triggerExpr resultOldExpr resultNewExpr) = do
152152 context <- getStateContext
153153 let eTriggerExprOpt = runReader (applyStaticRelationalOptimization triggerExpr) (RelationalExprStateElems context)
154154 case eTriggerExprOpt of
155155 Left err -> pure $ Left err
156− Right triggerExprOpt -> do
157− let eResultExprOpt = runReader (applyStaticRelationalOptimization resultExpr) (RelationalExprStateElems context)
158− case eResultExprOpt of
159− Left err -> pure $ Left err
160− Right resultExprOpt -> pure (Right (AddNotification name triggerExprOpt resultExprOpt))
156+ Right triggerExprOpt -> --it doesn't make sense to optimize queries when we don't have their proper contexts
157+ pure (Right (AddNotification name triggerExprOpt resultOldExpr resultNewExpr))
161158
162159applyStaticDatabaseOptimization notif@(RemoveNotification _) = pure (Right notif)
163160