1use alloc::{
8 collections::{BTreeMap, btree_map},
9 string::String,
10 sync::Arc,
11 vec::Vec,
12};
13use core::{cmp::Ordering, iter::Peekable, marker::PhantomData, mem, ops::Bound};
14
15use buggy::{Bug, bug};
16use yoke::{Yoke, Yokeable};
17
18use crate::{
19 Address, Bytes, Checkpoint, ClientError, ClientState, CmdId, Command, Fact, FactPerspective,
20 GraphId, Keys, MaxCut, NullSink, Perspective, Policy, PolicyId, PolicyStore, Prior, Priority,
21 Query, QueryMut, Revertable, Segment as _, Sink, Storage, StorageError, StorageProvider,
22 policy::{ActionPlacement, CommandPlacement},
23};
24
25pub struct Session<SP: StorageProvider, PS> {
27 graph_id: GraphId,
29 policy_id: PolicyId,
31
32 base_facts: <SP::Storage as Storage>::FactIndex,
34 fact_log: Vec<(String, Keys, Option<Bytes>)>,
36 current_facts: Arc<BTreeMap<String, BTreeMap<Keys, Option<Bytes>>>>,
38
39 _policy_store: PhantomData<PS>,
41}
42
43struct SessionPerspective<'a, SP: StorageProvider, PS, MS> {
44 session: &'a mut Session<SP, PS>,
45 message_sink: &'a mut MS,
46}
47
48impl<SP: StorageProvider, PS> Session<SP, PS> {
49 pub(super) fn new(provider: &mut SP, graph_id: GraphId) -> Result<Self, ClientError> {
50 let storage = provider.get_storage(graph_id)?;
51 let head_loc = storage.get_head()?;
52 let seg = storage.get_segment(head_loc)?;
53
54 let base_facts = seg.facts()?;
55
56 let result = Self {
57 graph_id,
58 policy_id: seg.policy(),
59 base_facts,
60 fact_log: Vec::new(),
61 current_facts: Arc::default(),
62 _policy_store: PhantomData,
63 };
64
65 Ok(result)
66 }
67}
68
69impl<SP: StorageProvider, PS: PolicyStore> Session<SP, PS> {
70 pub fn action<ES, MS>(
73 &mut self,
74 client: &ClientState<PS, SP>,
75 effect_sink: &mut ES,
76 message_sink: &mut MS,
77 action: <PS::Policy as Policy>::Action<'_>,
78 ) -> Result<(), ClientError>
79 where
80 ES: Sink<PS::Effect>,
81 MS: for<'b> Sink<&'b [u8]>,
82 {
83 let policy = client.policy_store.get_policy(self.policy_id)?;
84
85 let mut perspective = SessionPerspective {
87 session: self,
88 message_sink,
89 };
90 let checkpoint = perspective.checkpoint();
91 effect_sink.begin();
92
93 match policy.call_action(
95 action,
96 &mut perspective,
97 effect_sink,
98 ActionPlacement::OffGraph,
99 ) {
100 Ok(()) => {
101 effect_sink.commit();
103 Ok(())
104 }
105 Err(e) => {
106 perspective.revert(checkpoint)?;
108 perspective.message_sink.rollback();
109 effect_sink.rollback();
110 Err(e.into())
111 }
112 }
113 }
114
115 pub fn receive(
120 &mut self,
121 client: &ClientState<PS, SP>,
122 sink: &mut impl Sink<PS::Effect>,
123 command_bytes: &[u8],
124 ) -> Result<(), ClientError> {
125 let command = SessionCommand::deserialize(self.graph_id, command_bytes)
126 .ok_or(ClientError::SessionDeserialize)?;
127
128 let policy = client.policy_store.get_policy(self.policy_id)?;
129
130 let mut perspective = SessionPerspective {
132 session: self,
133 message_sink: &mut NullSink,
134 };
135
136 sink.begin();
138 let checkpoint = perspective.checkpoint();
139 if let Err(e) =
140 policy.call_rule(&command, &mut perspective, sink, CommandPlacement::OffGraph)
141 {
142 perspective.revert(checkpoint)?;
143 sink.rollback();
144 return Err(e.into());
145 }
146 sink.commit();
147
148 Ok(())
149 }
150}
151
152fn session_parent(graph_id: GraphId) -> Prior<Address> {
160 Prior::Single(Address {
161 id: CmdId::transmute(graph_id),
162 max_cut: MaxCut::new(0),
163 })
164}
165
166struct SessionCommand<'a> {
168 graph_id: GraphId,
169 id: CmdId,
170 data: &'a [u8],
171}
172
173impl Command for SessionCommand<'_> {
174 fn priority(&self) -> Priority {
175 Priority::Basic(0)
176 }
177
178 fn id(&self) -> CmdId {
179 self.id
180 }
181
182 fn parent(&self) -> Prior<Address> {
183 session_parent(self.graph_id)
184 }
185
186 fn policy(&self) -> Option<&[u8]> {
187 None
189 }
190
191 fn bytes(&self) -> &[u8] {
192 self.data
193 }
194}
195
196impl<'sc> SessionCommand<'sc> {
197 fn from_cmd(graph_id: GraphId, command: &'sc impl Command) -> Result<Self, Bug> {
198 if command.policy().is_some() {
199 bug!("session command should have no policy");
200 }
201 if !matches!(command.priority(), Priority::Basic(_)) {
202 bug!("session command has bad priority");
203 }
204 if command.parent() != session_parent(graph_id) {
205 bug!("session command has bad parent");
206 }
207 Ok(SessionCommand {
208 graph_id,
209 id: command.id(),
210 data: command.bytes(),
211 })
212 }
213
214 fn serialize(&self) -> Vec<u8> {
215 [self.id.as_bytes(), self.data].concat()
216 }
217
218 fn deserialize(graph_id: GraphId, bytes: &'sc [u8]) -> Option<Self> {
219 let (id, data) = bytes.split_first_chunk()?;
220 Some(Self {
221 graph_id,
222 id: CmdId::from_bytes(*id),
223 data,
224 })
225 }
226}
227
228struct QueryIterator<I1: Iterator, I2: Iterator> {
230 prior: Peekable<I1>,
231 current: Peekable<I2>,
232}
233
234impl<I1, I2> QueryIterator<I1, I2>
235where
236 I1: Iterator<Item = Result<Fact, StorageError>>,
237 I2: Iterator<Item = (Keys, Option<Bytes>)>,
238{
239 fn new(prior: I1, current: I2) -> Self {
240 Self {
241 prior: prior.peekable(),
242 current: current.peekable(),
243 }
244 }
245}
246
247impl<I1, I2> Iterator for QueryIterator<I1, I2>
248where
249 I1: Iterator<Item = Result<Fact, StorageError>>,
250 I2: Iterator<Item = (Keys, Option<Bytes>)>,
251{
252 type Item = Result<Fact, StorageError>;
253
254 fn next(&mut self) -> Option<Self::Item> {
255 loop {
260 let Some(new) = self.current.peek() else {
261 return self.prior.next();
263 };
264 if let Some(old) = self.prior.peek() {
265 let Ok(old) = old else {
266 return self.prior.next();
268 };
269 match new.0.cmp(&old.key) {
270 Ordering::Equal => {
271 let _ = self.prior.next();
273 }
274 Ordering::Greater => {
275 return self.prior.next();
277 }
278 Ordering::Less => {
279 }
281 }
282 }
283 let Some(slot) = self.current.next() else {
284 bug!("expected Some after peek")
285 };
286 if let (k, Some(v)) = slot {
287 return Some(Ok(Fact {
288 key: k.iter().cloned().collect(),
289 value: v,
290 }));
291 }
292 }
293 }
294}
295
296impl<SP, PS, MS> FactPerspective for SessionPerspective<'_, SP, PS, MS> where SP: StorageProvider {}
297
298impl<SP, PS, MS> Query for SessionPerspective<'_, SP, PS, MS>
299where
300 SP: StorageProvider,
301{
302 fn query(&self, name: &str, keys: &[Bytes]) -> Result<Option<Bytes>, StorageError> {
303 if let Some(slot) = self
304 .session
305 .current_facts
306 .get(name)
307 .and_then(|m| m.get(keys))
308 {
309 return Ok(slot.clone());
310 }
311 self.session.base_facts.query(name, keys)
312 }
313
314 type QueryIterator = QueryIterator<
315 <<SP::Storage as Storage>::FactIndex as Query>::QueryIterator,
316 YokeIter<PrefixIter<'static>, Arc<BTreeMap<String, BTreeMap<Keys, Option<Bytes>>>>>,
317 >;
318 fn query_prefix(
319 &self,
320 name: &str,
321 prefix: &[Bytes],
322 ) -> Result<Self::QueryIterator, StorageError> {
323 let prior = self.session.base_facts.query_prefix(name, prefix)?;
324 let current = Yoke::<PrefixIter<'static>, _>::attach_to_cart(
325 Arc::clone(&self.session.current_facts),
326 |map| match map.get(name) {
327 Some(facts) => PrefixIter::new(facts, prefix.iter().cloned().collect()),
328 None => PrefixIter::default(),
329 },
330 );
331 Ok(QueryIterator::new(prior, YokeIter::new(current)))
332 }
333}
334
335#[derive(Default, Yokeable)]
340struct PrefixIter<'map> {
341 range: btree_map::Range<'map, Keys, Option<Bytes>>,
342 prefix: Keys,
343}
344
345impl<'map> PrefixIter<'map> {
346 fn new(map: &'map BTreeMap<Keys, Option<Bytes>>, prefix: Keys) -> Self {
347 let range = map.range::<[Bytes], _>((Bound::Included(prefix.as_ref()), Bound::Unbounded));
348 Self { range, prefix }
349 }
350}
351
352impl Iterator for PrefixIter<'_> {
353 type Item = (Keys, Option<Bytes>);
354
355 fn next(&mut self) -> Option<Self::Item> {
356 self.range
357 .next()
358 .filter(|(k, _)| k.starts_with(&self.prefix))
359 .map(|(k, v)| (k.clone(), v.clone()))
360 }
361}
362
363struct YokeIter<I: for<'a> Yokeable<'a>, C>(Option<Yoke<I, C>>);
365
366impl<I: for<'a> Yokeable<'a>, C> YokeIter<I, C> {
367 fn new(yoke: Yoke<I, C>) -> Self {
368 Self(Some(yoke))
369 }
370}
371
372impl<I, C> Iterator for YokeIter<I, C>
373where
374 I: Iterator + for<'a> Yokeable<'a>,
375 for<'a> <I as Yokeable<'a>>::Output: Iterator<Item = I::Item>,
376{
377 type Item = I::Item;
378
379 fn next(&mut self) -> Option<Self::Item> {
380 let mut item = None;
383 self.0 = Some(self.0.take()?.map_project::<I, _>(|mut it, _| {
384 item = it.next();
385 it
386 }));
387 item
388 }
389}
390
391impl<SP: StorageProvider, PS, MS> QueryMut for SessionPerspective<'_, SP, PS, MS> {
392 fn insert(&mut self, name: String, keys: Keys, value: Bytes) -> Result<(), StorageError> {
393 self.session
394 .fact_log
395 .push((name.clone(), keys.clone(), Some(value.clone())));
396 Arc::make_mut(&mut self.session.current_facts)
397 .entry(name)
398 .or_default()
399 .insert(keys, Some(value));
400 Ok(())
401 }
402
403 fn delete(&mut self, name: String, keys: Keys) -> Result<(), StorageError> {
404 self.session
405 .fact_log
406 .push((name.clone(), keys.clone(), None));
407 Arc::make_mut(&mut self.session.current_facts)
408 .entry(name)
409 .or_default()
410 .insert(keys, None);
411 Ok(())
412 }
413}
414
415impl<SP, PS, MS> Perspective for SessionPerspective<'_, SP, PS, MS>
416where
417 SP: StorageProvider,
418 MS: for<'b> Sink<&'b [u8]>,
419{
420 fn policy(&self) -> PolicyId {
421 self.session.policy_id
422 }
423
424 fn add_command(&mut self, command: &impl Command) -> Result<usize, StorageError> {
425 let command = SessionCommand::from_cmd(self.session.graph_id, command)?;
426 self.message_sink.consume(&command.serialize());
427
428 Ok(0)
429 }
430
431 fn includes(&self, _id: CmdId) -> bool {
432 debug_assert!(false, "only used in transactions");
433
434 false
435 }
436
437 fn head_address(&self) -> Result<Prior<Address>, Bug> {
438 Ok(session_parent(self.session.graph_id))
439 }
440}
441
442impl<SP, PS, MS> Revertable for SessionPerspective<'_, SP, PS, MS>
443where
444 SP: StorageProvider,
445{
446 fn checkpoint(&self) -> Checkpoint {
447 Checkpoint {
448 index: self.session.fact_log.len(),
449 }
450 }
451
452 fn revert(&mut self, checkpoint: Checkpoint) -> Result<(), StorageError> {
453 if checkpoint.index == self.session.fact_log.len() {
454 return Ok(());
455 }
456
457 if checkpoint.index > self.session.fact_log.len() {
458 bug!(
459 "A checkpoint's index should always be less than or equal to the length of a session's fact log!"
460 );
461 }
462
463 self.session.fact_log.truncate(checkpoint.index);
464 let mut facts =
466 Arc::get_mut(&mut self.session.current_facts).map_or_else(BTreeMap::new, mem::take);
467 facts.clear();
468 for (n, k, v) in self.session.fact_log.iter().cloned() {
469 facts.entry(n).or_default().insert(k, v);
470 }
471 self.session.current_facts = Arc::new(facts);
472
473 Ok(())
474 }
475}
476
477#[cfg(test)]
478mod test {
479 use super::*;
480
481 #[test]
482 fn test_query_iterator() {
483 #![allow(clippy::type_complexity)]
484
485 let prior: Vec<Result<(&[&[u8]], &[u8]), _>> = vec![
486 Ok((&[b"a"], b"a0")),
487 Ok((&[b"c"], b"c0")),
488 Ok((&[b"d"], b"d0")),
489 Ok((&[b"f"], b"f0")),
490 Err(StorageError::IoError),
491 ];
492 let current: Vec<([Bytes; 1], Option<&[u8]>)> = vec![
493 ([Bytes::from(*b"a")], None),
494 ([Bytes::from(*b"b")], Some(b"b1")),
495 ([Bytes::from(*b"e")], None),
496 ([Bytes::from(*b"j")], None),
497 ];
498 let merged: Vec<Result<(&[&[u8]], &[u8]), _>> = vec![
499 Ok((&[b"b"], b"b1")),
500 Ok((&[b"c"], b"c0")),
501 Ok((&[b"d"], b"d0")),
502 Ok((&[b"f"], b"f0")),
503 Err(StorageError::IoError),
504 ];
505
506 let got: Vec<_> = QueryIterator::new(
507 prior.into_iter().map(|r| {
508 r.map(|(k, v)| Fact {
509 key: k.into(),
510 value: v.into(),
511 })
512 }),
513 current
514 .into_iter()
515 .map(|(k, v)| (k.into_iter().collect(), v.map(Bytes::from))),
516 )
517 .collect();
518 let want: Vec<_> = merged
519 .into_iter()
520 .map(|r| {
521 r.map(|(k, v)| Fact {
522 key: k.into(),
523 value: v.into(),
524 })
525 })
526 .collect();
527
528 assert_eq!(got, want);
529 }
530}