holochain_zome_types/
query.rs

1//! Types for source chain queries
2
3use std::collections::HashMap;
4use std::collections::HashSet;
5
6use crate::prelude::*;
7use crate::warrant::Warrant;
8use holo_hash::EntryHash;
9use holo_hash::HasHash;
10use holo_hash::{ActionHash, AgentPubKey, AnyLinkableHash};
11use holochain_integrity_types::{LinkTag, LinkTypeFilter};
12pub use holochain_serialized_bytes::prelude::*;
13
14/// Defines several ways that queries can be restricted to a range.
15/// Notably hash bounded ranges disambiguate forks whereas sequence indexes do
16/// not as the same position can be found in many forks.
17/// The reason that this does NOT use native rust range traits is that the hash
18/// bounded queries MUST be inclusive otherwise the integrity and fork
19/// disambiguation logic is impossible. An exclusive range bound that does not
20/// include the final action tells us nothing about which fork to select
21/// between N forks of equal length that proceed it. With an inclusive hash
22/// bounded range the final action always points unambiguously at the "correct"
23/// fork that the range is over. Start hashes are not needed to provide this
24/// property so ranges can be hash terminated with a length of preceeding
25/// records to return only. Technically the seq bounded ranges do not imply
26/// any fork disambiguation and so could be a range but for simplicity we left
27/// the API symmetrical in boundedness across all enum variants.
28// TODO It may be possible to provide/implement RangeBounds in the case that
29// a full sequence of records/actions is provided but it would need to be
30// handled as inclusive first, to enforce the integrity of the query, then the
31// exclusiveness achieved by simply removing the final record after the fact.
32#[derive(serde::Serialize, serde::Deserialize, PartialEq, Clone, Debug)]
33pub enum ChainQueryFilterRange {
34    /// Do NOT apply any range filtering for this query.
35    Unbounded,
36    /// A range over source chain sequence numbers.
37    /// This is ambiguous over forking histories and so should NOT be used in
38    /// validation logic.
39    /// Inclusive start, inclusive end.
40    ActionSeqRange(u32, u32),
41    /// A range over source chain action hashes.
42    /// This CAN be used in validation logic as forks are disambiguated.
43    /// Inclusive start and end (unlike std::ops::Range).
44    ActionHashRange(ActionHash, ActionHash),
45    /// The terminating action hash and N preceeding records.
46    /// N = 0 returns only the record with this `ActionHash`.
47    /// This CAN be used in validation logic as forks are not possible when
48    /// "looking up" towards genesis from some `ActionHash`.
49    ActionHashTerminated(ActionHash, u32),
50}
51
52impl Default for ChainQueryFilterRange {
53    fn default() -> Self {
54        Self::Unbounded
55    }
56}
57
58/// Specifies arguments to a query of the source chain, including ordering and filtering.
59///
60/// This struct is used to construct an actual SQL query on the database, and also has methods
61/// to allow filtering in-memory.
62#[derive(
63    serde::Serialize, serde::Deserialize, SerializedBytes, Default, PartialEq, Clone, Debug,
64)]
65// TODO: get feedback on whether it's OK to remove non_exhaustive
66// #[non_exhaustive]
67pub struct ChainQueryFilter {
68    /// Limit the results to a range of records according to their actions.
69    pub sequence_range: ChainQueryFilterRange,
70    /// Filter by EntryType
71    // NB: if this filter is set, you can't verify the results, so don't
72    //     use this in validation
73    pub entry_type: Option<Vec<EntryType>>,
74    /// Filter by a list of `EntryHash`.
75    pub entry_hashes: Option<HashSet<EntryHash>>,
76    /// Filter by ActionType
77    // NB: if this filter is set, you can't verify the results, so don't
78    //     use this in validation
79    pub action_type: Option<Vec<ActionType>>,
80    /// Include the entries in the records
81    pub include_entries: bool,
82    /// The query should be ordered in descending order (default is ascending),
83    /// when run as a database query. There is no provisioning for in-memory ordering.
84    pub order_descending: bool,
85}
86
87/// A query for links to be used with host functions that support filtering links
88#[derive(serde::Serialize, serde::Deserialize, SerializedBytes, PartialEq, Clone, Debug)]
89pub struct LinkQuery {
90    /// The base to find links from.
91    pub base: AnyLinkableHash,
92
93    /// Filter by the link type.
94    pub link_type: LinkTypeFilter,
95
96    /// Filter by tag prefix.
97    pub tag_prefix: Option<LinkTag>,
98
99    /// Only include links created before this time.
100    pub before: Option<Timestamp>,
101
102    /// Only include links created after this time.
103    pub after: Option<Timestamp>,
104
105    /// Only include links created by this author.
106    pub author: Option<AgentPubKey>,
107}
108
109#[derive(Clone, Debug, PartialEq, serde::Serialize, serde::Deserialize, SerializedBytes)]
110/// An agents chain records returned from a agent_activity_query
111pub struct AgentActivity {
112    /// Valid actions on this chain. `(sequence, action_hash)`.
113    pub valid_activity: Vec<(u32, ActionHash)>,
114    /// Rejected actions on this chain. `(sequence, action_hash)`.
115    pub rejected_activity: Vec<(u32, ActionHash)>,
116    /// The status of this chain.
117    pub status: ChainStatus,
118    /// The highest chain action that has
119    /// been observed by this authority.
120    pub highest_observed: Option<HighestObserved>,
121    /// Warrants about this AgentActivity.
122    /// Placeholder for future.
123    pub warrants: Vec<Warrant>,
124}
125
126#[derive(Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, SerializedBytes)]
127/// Get either the full activity or just the status of the chain
128pub enum ActivityRequest {
129    /// Just request the status of the chain
130    Status,
131    /// Request all the activity
132    Full,
133}
134
135#[derive(Clone, Debug, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]
136/// The highest action sequence observed by this authority.
137/// This also includes the actions at this sequence.
138/// If there is more then one then there is a fork.
139///
140/// This type is to prevent actions being hidden by
141/// withholding the previous action.
142///
143/// The information is tracked at the edge of holochain before
144/// validation (but after drop checks).
145pub struct HighestObserved {
146    /// The highest sequence number observed.
147    pub action_seq: u32,
148    /// Hashes of any actions claiming to be at this
149    /// action sequence.
150    pub hash: Vec<ActionHash>,
151}
152
153#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
154/// Status of the agent activity chain
155// TODO: In the future we will most likely be replaced
156// by warrants instead of Forked / Invalid so we can provide
157// evidence of why the chain has a status.
158#[derive(Default)]
159pub enum ChainStatus {
160    /// This authority has no information on the chain.
161    #[default]
162    Empty,
163    /// The chain is valid as at this action sequence and action hash.
164    Valid(ChainHead),
165    /// Chain is forked.
166    Forked(ChainFork),
167    /// Chain is invalid because of this action.
168    Invalid(ChainHead),
169}
170
171#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
172/// The action at the head of the complete chain.
173/// This is as far as this authority can see a
174/// chain with no gaps.
175pub struct ChainHead {
176    /// Sequence number of this chain head.
177    pub action_seq: u32,
178    /// Hash of this chain head
179    pub hash: ActionHash,
180}
181
182#[derive(Clone, Debug, Hash, Eq, PartialEq, serde::Serialize, serde::Deserialize)]
183/// The chain has been forked by these two actions
184pub struct ChainFork {
185    /// The point where the chain has forked.
186    pub fork_seq: u32,
187    /// The first action at this sequence position.
188    pub first_action: ActionHash,
189    /// The second action at this sequence position.
190    pub second_action: ActionHash,
191}
192
193impl ChainQueryFilter {
194    /// Create a no-op ChainQueryFilter which returns everything.
195    pub fn new() -> Self {
196        Self {
197            include_entries: false,
198            ..Self::default()
199        }
200    }
201
202    /// Filter on sequence range.
203    pub fn sequence_range(mut self, sequence_range: ChainQueryFilterRange) -> Self {
204        self.sequence_range = sequence_range;
205        self
206    }
207
208    /// Filter on entry type. This function can be called multiple times
209    /// to create an OR query on all provided entry types.
210    pub fn entry_type(mut self, entry_type: EntryType) -> Self {
211        match self.entry_type {
212            Some(ref mut types) => {
213                types.push(entry_type);
214            }
215            None => {
216                self.entry_type = Some(vec![entry_type]);
217            }
218        }
219
220        self
221    }
222
223    /// Filter on entry hashes.
224    pub fn entry_hashes(mut self, entry_hashes: HashSet<EntryHash>) -> Self {
225        self.entry_hashes = Some(entry_hashes);
226        self
227    }
228
229    /// Filter on action type. This function can be called multiple times
230    /// to create an OR query on all provided action types.
231    pub fn action_type(mut self, action_type: ActionType) -> Self {
232        match self.action_type {
233            Some(ref mut types) => {
234                types.push(action_type);
235            }
236            None => {
237                self.action_type = Some(vec![action_type]);
238            }
239        }
240
241        self
242    }
243
244    /// Include the entries in the RecordsVec that is returned.
245    pub fn include_entries(mut self, include_entries: bool) -> Self {
246        self.include_entries = include_entries;
247        self
248    }
249
250    /// Set the order to ascending.
251    pub fn ascending(mut self) -> Self {
252        self.order_descending = false;
253        self
254    }
255
256    /// Set the order to ascending.
257    pub fn descending(mut self) -> Self {
258        self.order_descending = true;
259        self
260    }
261
262    /// If the sequence range supports fork disambiguation, apply it to remove
263    /// actions that are not in the correct branch.
264    /// Numerical range bounds do NOT support fork disambiguation, and neither
265    /// does unbounded, but everything hash bounded does.
266    pub fn disambiguate_forks<T: ActionHashedContainer + Clone>(&self, actions: Vec<T>) -> Vec<T> {
267        match &self.sequence_range {
268            ChainQueryFilterRange::Unbounded => actions,
269            ChainQueryFilterRange::ActionSeqRange(start, end) => actions
270                .into_iter()
271                .filter(|action| {
272                    *start <= action.action().action_seq() && action.action().action_seq() <= *end
273                })
274                .collect(),
275            ChainQueryFilterRange::ActionHashRange(start, end) => {
276                let mut action_hashmap = actions
277                    .into_iter()
278                    .map(|action| (action.action_hash().clone(), action))
279                    .collect::<HashMap<ActionHash, T>>();
280                let mut filtered_actions = Vec::new();
281                let mut maybe_next_action = action_hashmap.remove(end);
282                while let Some(next_action) = maybe_next_action {
283                    maybe_next_action = next_action
284                        .action()
285                        .prev_action()
286                        .and_then(|prev_action| action_hashmap.remove(prev_action));
287                    let is_start = next_action.action_hash() == start;
288                    filtered_actions.push(next_action);
289                    // This comes after the push to make the range inclusive.
290                    if is_start {
291                        break;
292                    }
293                }
294                filtered_actions
295            }
296            ChainQueryFilterRange::ActionHashTerminated(end, n) => {
297                let mut action_hashmap = actions
298                    .iter()
299                    .map(|action| (action.action_hash().clone(), action.clone()))
300                    .collect::<HashMap<ActionHash, T>>();
301                let mut filtered_actions = Vec::new();
302                let mut maybe_next_action = action_hashmap.remove(end);
303                let mut i = 0;
304                while let Some(next_action) = maybe_next_action {
305                    maybe_next_action = next_action
306                        .action()
307                        .prev_action()
308                        .and_then(|prev_action| action_hashmap.remove(prev_action));
309                    filtered_actions.push(next_action.clone());
310                    // This comes after the push to make the range inclusive.
311                    if i == *n {
312                        break;
313                    }
314                    i += 1;
315                }
316                filtered_actions
317            }
318        }
319    }
320
321    /// Filter a vector of hashed actions according to the query.
322    pub fn filter_actions<T: ActionHashedContainer + Clone>(&self, actions: Vec<T>) -> Vec<T> {
323        self.disambiguate_forks(actions)
324            .into_iter()
325            .filter(|action| {
326                self.action_type
327                    .as_ref()
328                    .map(|action_types| action_types.contains(&action.action().action_type()))
329                    .unwrap_or(true)
330                    && self
331                        .entry_type
332                        .as_ref()
333                        .map(|entry_types| {
334                            action
335                                .action()
336                                .entry_type()
337                                .map(|entry_type| entry_types.contains(entry_type))
338                                .unwrap_or(false)
339                        })
340                        .unwrap_or(true)
341                    && self
342                        .entry_hashes
343                        .as_ref()
344                        .map(|entry_hashes| match action.action().entry_hash() {
345                            Some(entry_hash) => entry_hashes.contains(entry_hash),
346                            None => false,
347                        })
348                        .unwrap_or(true)
349            })
350            .collect()
351    }
352
353    /// Filter a vector of records according to the query.
354    pub fn filter_records(&self, records: Vec<Record>) -> Vec<Record> {
355        let actions = self.filter_actions(
356            records
357                .iter()
358                .map(|record| record.action_hashed().clone())
359                .collect(),
360        );
361        let action_hashset = actions
362            .iter()
363            .map(|action| action.as_hash().clone())
364            .collect::<HashSet<ActionHash>>();
365        records
366            .into_iter()
367            .filter(|record| action_hashset.contains(record.action_address()))
368            .collect()
369    }
370}
371
372impl LinkQuery {
373    /// Create a new link query for a base and link type
374    pub fn new(base: impl Into<AnyLinkableHash>, link_type: LinkTypeFilter) -> Self {
375        LinkQuery {
376            base: base.into(),
377            link_type,
378            tag_prefix: None,
379            before: None,
380            after: None,
381            author: None,
382        }
383    }
384
385    /// Filter by tag prefix.
386    pub fn tag_prefix(mut self, tag_prefix: LinkTag) -> Self {
387        self.tag_prefix = Some(tag_prefix);
388        self
389    }
390
391    /// Filter for links created before `before`.
392    pub fn before(mut self, before: Timestamp) -> Self {
393        self.before = Some(before);
394        self
395    }
396
397    /// Filter for links create after `after`.
398    pub fn after(mut self, after: Timestamp) -> Self {
399        self.after = Some(after);
400        self
401    }
402
403    /// Filter for links created by this author.
404    pub fn author(mut self, author: AgentPubKey) -> Self {
405        self.author = Some(author);
406        self
407    }
408}
409
410#[cfg(test)]
411#[cfg(feature = "fixturators")]
412mod tests {
413    use super::ChainQueryFilter;
414    use crate::action::EntryType;
415    use crate::fixt::AppEntryDefFixturator;
416    use crate::prelude::*;
417    use ::fixt::prelude::*;
418    use holo_hash::fixt::EntryHashFixturator;
419    use holo_hash::HasHash;
420
421    /// Create three Actions with various properties.
422    /// Also return the EntryTypes used to construct the first two actions.
423    fn fixtures() -> [ActionHashed; 7] {
424        let entry_type_1 = EntryType::App(fixt!(AppEntryDef));
425        let entry_type_2 = EntryType::AgentPubKey;
426
427        let entry_hash_0 = fixt!(EntryHash);
428
429        let mut h0 = fixt!(Create);
430        h0.entry_type = entry_type_1.clone();
431        h0.action_seq = 0;
432        h0.entry_hash = entry_hash_0.clone();
433        let hh0 = ActionHashed::from_content_sync(h0);
434
435        let mut h1 = fixt!(Update);
436        h1.entry_type = entry_type_2.clone();
437        h1.action_seq = 1;
438        h1.prev_action = hh0.as_hash().clone();
439        let hh1 = ActionHashed::from_content_sync(h1);
440
441        let mut h2 = fixt!(CreateLink);
442        h2.action_seq = 2;
443        h2.prev_action = hh1.as_hash().clone();
444        let hh2 = ActionHashed::from_content_sync(h2);
445
446        let mut h3 = fixt!(Create);
447        h3.entry_type = entry_type_2.clone();
448        h3.action_seq = 3;
449        h3.prev_action = hh2.as_hash().clone();
450        let hh3 = ActionHashed::from_content_sync(h3);
451
452        // Cheeky forker!
453        let mut h3a = fixt!(Create);
454        h3a.entry_type = entry_type_1.clone();
455        h3a.action_seq = 3;
456        h3a.prev_action = hh2.as_hash().clone();
457        let hh3a = ActionHashed::from_content_sync(h3a);
458
459        let mut h4 = fixt!(Update);
460        h4.entry_type = entry_type_1.clone();
461        // same entry content as h0
462        h4.entry_hash = entry_hash_0;
463        h4.action_seq = 4;
464        h4.prev_action = hh3.as_hash().clone();
465        let hh4 = ActionHashed::from_content_sync(h4);
466
467        let mut h5 = fixt!(CreateLink);
468        h5.action_seq = 5;
469        h5.prev_action = hh4.as_hash().clone();
470        let hh5 = ActionHashed::from_content_sync(h5);
471
472        [hh0, hh1, hh2, hh3, hh3a, hh4, hh5]
473    }
474
475    fn map_query(query: &ChainQueryFilter, actions: &[ActionHashed]) -> Vec<bool> {
476        let filtered = query.filter_actions(actions.to_vec());
477        actions
478            .iter()
479            .map(|h| filtered.contains(h))
480            .collect::<Vec<_>>()
481    }
482
483    #[test]
484    fn filter_by_entry_type() {
485        let actions = fixtures();
486
487        let query_1 =
488            ChainQueryFilter::new().entry_type(actions[0].entry_type().unwrap().to_owned());
489        let query_2 =
490            ChainQueryFilter::new().entry_type(actions[1].entry_type().unwrap().to_owned());
491
492        assert_eq!(
493            map_query(&query_1, &actions),
494            [true, false, false, false, true, true, false].to_vec()
495        );
496        assert_eq!(
497            map_query(&query_2, &actions),
498            [false, true, false, true, false, false, false].to_vec()
499        );
500    }
501
502    #[test]
503    fn filter_by_entry_hash() {
504        let actions = fixtures();
505
506        let query = ChainQueryFilter::new().entry_hashes(
507            vec![
508                actions[3].entry_hash().unwrap().clone(),
509                // actions[5] has same entry hash as actions[0]
510                actions[5].entry_hash().unwrap().clone(),
511            ]
512            .into_iter()
513            .collect(),
514        );
515
516        assert_eq!(
517            map_query(&query, &actions),
518            vec![true, false, false, true, false, true, false]
519        );
520    }
521
522    #[test]
523    fn filter_by_action_type() {
524        let actions = fixtures();
525
526        let query_1 = ChainQueryFilter::new().action_type(actions[0].action_type());
527        let query_2 = ChainQueryFilter::new().action_type(actions[1].action_type());
528        let query_3 = ChainQueryFilter::new().action_type(actions[2].action_type());
529
530        assert_eq!(
531            map_query(&query_1, &actions),
532            [true, false, false, true, true, false, false].to_vec()
533        );
534        assert_eq!(
535            map_query(&query_2, &actions),
536            [false, true, false, false, false, true, false].to_vec()
537        );
538        assert_eq!(
539            map_query(&query_3, &actions),
540            [false, false, true, false, false, false, true].to_vec()
541        );
542    }
543
544    #[test]
545    fn filter_by_chain_sequence() {
546        let actions = fixtures();
547
548        for (sequence_range, expected, name) in vec![
549            (
550                ChainQueryFilterRange::Unbounded,
551                vec![true, true, true, true, true, true, true],
552                "unbounded",
553            ),
554            (
555                ChainQueryFilterRange::ActionSeqRange(0, 0),
556                vec![true, false, false, false, false, false, false],
557                "first only",
558            ),
559            (
560                ChainQueryFilterRange::ActionSeqRange(0, 1),
561                vec![true, true, false, false, false, false, false],
562                "several from start",
563            ),
564            (
565                ChainQueryFilterRange::ActionSeqRange(1, 2),
566                vec![false, true, true, false, false, false, false],
567                "several not start",
568            ),
569            (
570                ChainQueryFilterRange::ActionSeqRange(2, 999),
571                vec![false, false, true, true, true, true, true],
572                "exceeds chain length, not start",
573            ),
574            (
575                ChainQueryFilterRange::ActionHashRange(
576                    actions[2].as_hash().clone(),
577                    actions[6].as_hash().clone(),
578                ),
579                vec![false, false, true, true, false, true, true],
580                "hash bounded not 3a",
581            ),
582            (
583                ChainQueryFilterRange::ActionHashRange(
584                    actions[2].as_hash().clone(),
585                    actions[4].as_hash().clone(),
586                ),
587                vec![false, false, true, false, true, false, false],
588                "hash bounded 3a",
589            ),
590            (
591                ChainQueryFilterRange::ActionHashTerminated(actions[2].as_hash().clone(), 1),
592                vec![false, true, true, false, false, false, false],
593                "hash terminated not start",
594            ),
595            (
596                ChainQueryFilterRange::ActionHashTerminated(actions[2].as_hash().clone(), 0),
597                vec![false, false, true, false, false, false, false],
598                "hash terminated not start 0 prior",
599            ),
600            (
601                ChainQueryFilterRange::ActionHashTerminated(actions[5].as_hash().clone(), 7),
602                vec![true, true, true, true, false, true, false],
603                "hash terminated main chain before chain start",
604            ),
605            (
606                ChainQueryFilterRange::ActionHashTerminated(actions[4].as_hash().clone(), 7),
607                vec![true, true, true, false, true, false, false],
608                "hash terminated 3a chain before chain start",
609            ),
610        ] {
611            assert_eq!(
612                (
613                    map_query(
614                        &ChainQueryFilter::new().sequence_range(sequence_range),
615                        &actions,
616                    ),
617                    name
618                ),
619                (expected, name),
620            );
621        }
622    }
623
624    #[test]
625    fn filter_by_multi() {
626        let actions = fixtures();
627
628        assert_eq!(
629            map_query(
630                &ChainQueryFilter::new()
631                    .action_type(actions[0].action_type())
632                    .entry_type(actions[0].entry_type().unwrap().clone())
633                    .sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 0)),
634                &actions
635            ),
636            [true, false, false, false, false, false, false].to_vec()
637        );
638
639        assert_eq!(
640            map_query(
641                &ChainQueryFilter::new()
642                    .action_type(actions[1].action_type())
643                    .entry_type(actions[0].entry_type().unwrap().clone())
644                    .sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 999)),
645                &actions
646            ),
647            [false, false, false, false, false, true, false].to_vec()
648        );
649
650        assert_eq!(
651            map_query(
652                &ChainQueryFilter::new()
653                    .entry_type(actions[0].entry_type().unwrap().clone())
654                    .sequence_range(ChainQueryFilterRange::ActionSeqRange(0, 999)),
655                &actions
656            ),
657            [true, false, false, false, true, true, false].to_vec()
658        );
659    }
660
661    #[test]
662    fn filter_by_multiple_action_types() {
663        let actions = fixtures();
664
665        // Filter for create and update actions
666        assert_eq!(
667            map_query(
668                &ChainQueryFilter::new()
669                    .action_type(actions[0].action_type())
670                    .action_type(actions[1].action_type()),
671                &actions
672            ),
673            [true, true, false, true, true, true, false].to_vec()
674        );
675
676        // Filter for create actions only
677        assert_eq!(
678            map_query(
679                &ChainQueryFilter::new().action_type(actions[0].action_type()),
680                &actions
681            ),
682            [true, false, false, true, true, false, false].to_vec()
683        );
684    }
685
686    #[test]
687    fn filter_by_multiple_entry_types() {
688        let actions = fixtures();
689
690        // Filter for app entries and agent public keys
691        assert_eq!(
692            map_query(
693                &ChainQueryFilter::new()
694                    .entry_type(actions[0].entry_type().unwrap().clone())
695                    .entry_type(actions[1].entry_type().unwrap().clone()),
696                &actions
697            ),
698            [true, true, false, true, true, true, false].to_vec()
699        );
700
701        // Filter for app entries only
702        assert_eq!(
703            map_query(
704                &ChainQueryFilter::new().entry_type(actions[0].entry_type().unwrap().clone()),
705                &actions
706            ),
707            [true, false, false, false, true, true, false].to_vec()
708        );
709    }
710}