holochain_zome_types/
query.rs

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