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}