1#[cfg(feature = "server")]
2use crate::drive::votes::paths::vote_contested_resource_end_date_queries_at_time_tree_path_vec;
3use crate::drive::votes::paths::vote_end_date_queries_tree_path_vec;
4#[cfg(feature = "server")]
5use crate::drive::Drive;
6#[cfg(feature = "server")]
7use crate::error::drive::DriveError;
8#[cfg(feature = "server")]
9use crate::error::Error;
10#[cfg(feature = "server")]
11use crate::fees::op::LowLevelDriveOperation;
12#[cfg(feature = "server")]
13use crate::query::GroveError;
14use crate::query::Query;
15#[cfg(feature = "server")]
16use crate::util::common::encode::decode_u64;
17use crate::util::common::encode::encode_u64;
18use bincode::{Decode, DecodeUntrusted, Encode};
19#[cfg(feature = "server")]
20use dpp::block::block_info::BlockInfo;
21#[cfg(feature = "server")]
22use dpp::fee::Credits;
23use dpp::prelude::{TimestampIncluded, TimestampMillis};
24#[cfg(feature = "server")]
25use dpp::serialization::PlatformDeserializableTrusted;
26#[cfg(feature = "server")]
27use dpp::voting::vote_polls::VotePoll;
28#[cfg(feature = "server")]
29use grovedb::query_result_type::{QueryResultElements, QueryResultType};
30#[cfg(feature = "server")]
31use grovedb::{Element, TransactionArg};
32use grovedb::{PathQuery, SizedQuery};
33#[cfg(feature = "server")]
34use platform_version::version::PlatformVersion;
35#[cfg(feature = "server")]
36use std::collections::BTreeMap;
37
38#[derive(Debug, PartialEq, Clone, Encode, Decode, DecodeUntrusted)]
40pub struct VotePollsByEndDateDriveQuery {
41 pub start_time: Option<(TimestampMillis, TimestampIncluded)>,
43 pub end_time: Option<(TimestampMillis, TimestampIncluded)>,
45 pub limit: Option<u16>,
47 pub offset: Option<u16>,
49 pub order_ascending: bool,
51}
52
53impl VotePollsByEndDateDriveQuery {
54 pub fn path_query_for_end_time_included(end_time: TimestampMillis, limit: u16) -> PathQuery {
56 let path = vote_end_date_queries_tree_path_vec();
57
58 let mut query = Query::new_with_direction(true);
59
60 let encoded_time = encode_u64(end_time);
61
62 query.insert_range_to_inclusive(..=encoded_time);
63
64 let mut sub_query = Query::new();
65
66 sub_query.insert_all();
67
68 query.default_subquery_branch.subquery = Some(sub_query.into());
69
70 PathQuery {
71 path,
72 query: SizedQuery {
73 query,
74 limit: Some(limit),
75 offset: None,
76 },
77 }
78 }
79
80 pub fn path_query_for_single_end_time(end_time: TimestampMillis, limit: u16) -> PathQuery {
82 let path = vote_end_date_queries_tree_path_vec();
83
84 let mut query = Query::new_with_direction(true);
85
86 let encoded_time = encode_u64(end_time);
87
88 query.insert_key(encoded_time);
89
90 let mut sub_query = Query::new();
91
92 sub_query.insert_all();
93
94 query.default_subquery_branch.subquery = Some(sub_query.into());
95
96 PathQuery {
97 path,
98 query: SizedQuery {
99 query,
100 limit: Some(limit),
101 offset: None,
102 },
103 }
104 }
105
106 #[cfg(feature = "server")]
107 pub fn execute_no_proof_for_specialized_end_time_query(
110 end_time: TimestampMillis,
111 limit: u16,
112 drive: &Drive,
113 transaction: TransactionArg,
114 drive_operations: &mut Vec<LowLevelDriveOperation>,
115 platform_version: &PlatformVersion,
116 ) -> Result<BTreeMap<TimestampMillis, Vec<VotePoll>>, Error> {
117 let path_query = Self::path_query_for_end_time_included(end_time, limit);
118 let query_result = drive.grove_get_path_query(
119 &path_query,
120 transaction,
121 QueryResultType::QueryPathKeyElementTrioResultType,
122 drive_operations,
123 &platform_version.drive,
124 );
125 match query_result {
126 Err(Error::GroveDB(e))
127 if matches!(
128 e.as_ref(),
129 GroveError::PathKeyNotFound(_)
130 | GroveError::PathNotFound(_)
131 | GroveError::PathParentLayerNotFound(_)
132 ) =>
133 {
134 Ok(BTreeMap::new())
135 }
136 Err(e) => Err(e),
137 Ok((query_result_elements, _)) => {
138 let vote_polls_by_end_date = query_result_elements
139 .to_path_key_elements()
140 .into_iter()
141 .map(|(path, _, element)| {
142 let Some(last_path_component) = path.last() else {
143 return Err(Error::Drive(DriveError::CorruptedDriveState(
144 "we should always have a path not be null".to_string(),
145 )));
146 };
147 let timestamp = decode_u64(last_path_component)?;
148 let contested_document_resource_vote_poll_bytes =
149 element.into_item_bytes().map_err(Error::from)?;
150 let vote_poll = VotePoll::deserialize_from_bytes_trusted(
151 &contested_document_resource_vote_poll_bytes,
152 )?;
153 Ok((timestamp, vote_poll))
154 })
155 .collect::<Result<Vec<_>, Error>>()?
156 .into_iter()
157 .fold(
158 BTreeMap::new(),
159 |mut acc: BTreeMap<u64, Vec<VotePoll>>, (timestamp, vote_poll)| {
160 acc.entry(timestamp).or_default().push(vote_poll);
161 acc
162 },
163 );
164 Ok(vote_polls_by_end_date)
165 }
166 }
167 }
168
169 #[cfg(feature = "server")]
170 pub fn execute_no_proof_for_specialized_end_time_query_only_check_end_time(
173 end_time: TimestampMillis,
174 limit: u16,
175 drive: &Drive,
176 transaction: TransactionArg,
177 drive_operations: &mut Vec<LowLevelDriveOperation>,
178 platform_version: &PlatformVersion,
179 ) -> Result<Vec<VotePoll>, Error> {
180 let path_query = Self::path_query_for_single_end_time(end_time, limit);
181 let query_result = drive.grove_get_path_query(
182 &path_query,
183 transaction,
184 QueryResultType::QueryPathKeyElementTrioResultType,
185 drive_operations,
186 &platform_version.drive,
187 );
188 match query_result {
189 Err(Error::GroveDB(e))
190 if matches!(
191 e.as_ref(),
192 GroveError::PathKeyNotFound(_)
193 | GroveError::PathNotFound(_)
194 | GroveError::PathParentLayerNotFound(_)
195 ) =>
196 {
197 Ok(vec![])
198 }
199 Err(e) => Err(e),
200 Ok((query_result_elements, _)) => {
201 let vote_polls = query_result_elements
203 .to_path_key_elements()
204 .into_iter()
205 .map(|(_, _, element)| {
206 let vote_poll_bytes = element.into_item_bytes().map_err(Error::from)?;
208 let vote_poll = VotePoll::deserialize_from_bytes_trusted(&vote_poll_bytes)?;
210 Ok(vote_poll)
211 })
212 .collect::<Result<Vec<_>, Error>>()?;
213 Ok(vote_polls)
214 }
215 }
216 }
217
218 #[cfg(feature = "server")]
219 pub fn execute_no_proof_keys_for_single_end_time(
223 end_time: TimestampMillis,
224 limit: Option<u16>,
225 drive: &Drive,
226 transaction: TransactionArg,
227 drive_operations: &mut Vec<LowLevelDriveOperation>,
228 platform_version: &PlatformVersion,
229 ) -> Result<Vec<Vec<u8>>, Error> {
230 let mut query = Query::new();
231 query.insert_all();
232 let path_query = PathQuery::new(
233 vote_contested_resource_end_date_queries_at_time_tree_path_vec(end_time),
234 SizedQuery::new(query, limit, None),
235 );
236 let query_result = drive.grove_get_raw_path_query(
237 &path_query,
238 transaction,
239 QueryResultType::QueryKeyElementPairResultType,
240 drive_operations,
241 &platform_version.drive,
242 );
243 match query_result {
244 Err(Error::GroveDB(e))
245 if matches!(
246 e.as_ref(),
247 GroveError::PathKeyNotFound(_)
248 | GroveError::PathNotFound(_)
249 | GroveError::PathParentLayerNotFound(_)
250 ) =>
251 {
252 Ok(vec![])
253 }
254 Err(e) => Err(e),
255 Ok((query_result_elements, _)) => Ok(query_result_elements.to_keys()),
256 }
257 }
258
259 #[cfg(feature = "server")]
260 pub fn execute_no_proof_empty_end_times(
265 end_time: TimestampMillis,
266 limit: u16,
267 drive: &Drive,
268 transaction: TransactionArg,
269 drive_operations: &mut Vec<LowLevelDriveOperation>,
270 platform_version: &PlatformVersion,
271 ) -> Result<Vec<TimestampMillis>, Error> {
272 let mut query = Query::new_with_direction(true);
273 query.insert_range_to_inclusive(..=encode_u64(end_time));
274 let path_query = PathQuery::new(
275 vote_end_date_queries_tree_path_vec(),
276 SizedQuery::new(query, Some(limit), None),
277 );
278 let query_result = drive.grove_get_raw_path_query(
279 &path_query,
280 transaction,
281 QueryResultType::QueryKeyElementPairResultType,
282 drive_operations,
283 &platform_version.drive,
284 );
285 match query_result {
286 Err(Error::GroveDB(e))
287 if matches!(
288 e.as_ref(),
289 GroveError::PathKeyNotFound(_)
290 | GroveError::PathNotFound(_)
291 | GroveError::PathParentLayerNotFound(_)
292 ) =>
293 {
294 Ok(vec![])
295 }
296 Err(e) => Err(e),
297 Ok((query_result_elements, _)) => query_result_elements
298 .to_key_elements()
299 .into_iter()
300 .filter(|(_, element)| matches!(element, Element::Tree(None, _)))
301 .map(|(key, _)| decode_u64(&key))
302 .collect(),
303 }
304 }
305
306 pub fn construct_path_query(&self) -> PathQuery {
308 let path = vote_end_date_queries_tree_path_vec();
309
310 let mut query = Query::new_with_direction(self.order_ascending);
311
312 match &(self.start_time, self.end_time) {
314 (None, None) => {
315 query.insert_all();
316 }
317 (Some((starts_at_key_bytes, start_at_included)), None) => {
318 let starts_at_key = encode_u64(*starts_at_key_bytes);
319 match start_at_included {
320 true => query.insert_range_from(starts_at_key..),
321 false => query.insert_range_after(starts_at_key..),
322 }
323 }
324 (None, Some((ends_at_key_bytes, ends_at_included))) => {
325 let ends_at_key = encode_u64(*ends_at_key_bytes);
326 match ends_at_included {
327 true => query.insert_range_to_inclusive(..=ends_at_key),
328 false => query.insert_range_to(..ends_at_key),
329 }
330 }
331 (
332 Some((starts_at_key_bytes, start_at_included)),
333 Some((ends_at_key_bytes, ends_at_included)),
334 ) => {
335 let starts_at_key = encode_u64(*starts_at_key_bytes);
336 let ends_at_key = encode_u64(*ends_at_key_bytes);
337 match (start_at_included, ends_at_included) {
338 (true, true) => query.insert_range_inclusive(starts_at_key..=ends_at_key),
339 (true, false) => query.insert_range(starts_at_key..ends_at_key),
340 (false, true) => {
341 query.insert_range_after_to_inclusive(starts_at_key..=ends_at_key)
342 }
343 (false, false) => query.insert_range_after_to(starts_at_key..ends_at_key),
344 }
345 }
346 }
347
348 let mut sub_query = Query::new();
349
350 sub_query.insert_all();
351
352 query.default_subquery_branch.subquery = Some(sub_query.into());
353
354 PathQuery {
355 path,
356 query: SizedQuery {
357 query,
358 limit: self.limit,
359 offset: None,
360 },
361 }
362 }
363 #[cfg(feature = "server")]
364 pub fn execute_with_proof(
366 self,
367 drive: &Drive,
368 block_info: Option<BlockInfo>,
369 transaction: TransactionArg,
370 platform_version: &PlatformVersion,
371 ) -> Result<(Vec<u8>, u64), Error> {
372 let mut drive_operations = vec![];
373 let items = self.execute_with_proof_internal(
374 drive,
375 transaction,
376 &mut drive_operations,
377 platform_version,
378 )?;
379 let cost = if let Some(block_info) = block_info {
380 let fee_result = Drive::calculate_fee(
381 None,
382 Some(drive_operations),
383 &block_info.epoch,
384 drive.config.epochs_per_era,
385 platform_version,
386 None,
387 )?;
388 fee_result.processing_fee
389 } else {
390 0
391 };
392 Ok((items, cost))
393 }
394
395 #[cfg(feature = "server")]
396 pub(crate) fn execute_with_proof_internal(
398 self,
399 drive: &Drive,
400 transaction: TransactionArg,
401 drive_operations: &mut Vec<LowLevelDriveOperation>,
402 platform_version: &PlatformVersion,
403 ) -> Result<Vec<u8>, Error> {
404 let path_query = self.construct_path_query();
405 drive.grove_get_proved_path_query(
406 &path_query,
407 transaction,
408 drive_operations,
409 &platform_version.drive,
410 )
411 }
412 #[cfg(feature = "server")]
413 pub fn execute_no_proof_with_cost(
415 &self,
416 drive: &Drive,
417 block_info: Option<BlockInfo>,
418 transaction: TransactionArg,
419 platform_version: &PlatformVersion,
420 ) -> Result<(BTreeMap<TimestampMillis, Vec<VotePoll>>, Credits), Error> {
421 let mut drive_operations = vec![];
422 let result =
423 self.execute_no_proof(drive, transaction, &mut drive_operations, platform_version)?;
424 let cost = if let Some(block_info) = block_info {
425 let fee_result = Drive::calculate_fee(
426 None,
427 Some(drive_operations),
428 &block_info.epoch,
429 drive.config.epochs_per_era,
430 platform_version,
431 None,
432 )?;
433 fee_result.processing_fee
434 } else {
435 0
436 };
437 Ok((result, cost))
438 }
439
440 #[cfg(feature = "server")]
441 pub fn execute_no_proof(
443 &self,
444 drive: &Drive,
445 transaction: TransactionArg,
446 drive_operations: &mut Vec<LowLevelDriveOperation>,
447 platform_version: &PlatformVersion,
448 ) -> Result<BTreeMap<TimestampMillis, Vec<VotePoll>>, Error> {
449 let path_query = self.construct_path_query();
450 let query_result = drive.grove_get_path_query(
451 &path_query,
452 transaction,
453 QueryResultType::QueryPathKeyElementTrioResultType,
454 drive_operations,
455 &platform_version.drive,
456 );
457 match query_result {
458 Err(Error::GroveDB(e))
459 if matches!(
460 e.as_ref(),
461 GroveError::PathKeyNotFound(_)
462 | GroveError::PathNotFound(_)
463 | GroveError::PathParentLayerNotFound(_)
464 ) =>
465 {
466 Ok(BTreeMap::new())
467 }
468 Err(e) => Err(e),
469 Ok((query_result_elements, _)) => {
470 let vote_polls_by_end_date = query_result_elements
471 .to_path_key_elements()
472 .into_iter()
473 .map(|(path, _, element)| {
474 let Some(last_path_component) = path.last() else {
475 return Err(Error::Drive(DriveError::CorruptedDriveState(
476 "we should always have a path not be null".to_string(),
477 )));
478 };
479 let timestamp = decode_u64(last_path_component)?;
480 let contested_document_resource_vote_poll_bytes =
481 element.into_item_bytes().map_err(Error::from)?;
482 let vote_poll = VotePoll::deserialize_from_bytes_trusted(
483 &contested_document_resource_vote_poll_bytes,
484 )?;
485 Ok((timestamp, vote_poll))
486 })
487 .collect::<Result<Vec<_>, Error>>()?
488 .into_iter()
489 .fold(
490 BTreeMap::new(),
491 |mut acc: BTreeMap<u64, Vec<VotePoll>>, (timestamp, vote_poll)| {
492 acc.entry(timestamp).or_default().push(vote_poll);
493 acc
494 },
495 );
496 Ok(vote_polls_by_end_date)
497 }
498 }
499 }
500
501 #[cfg(feature = "server")]
502 pub fn execute_no_proof_keep_serialized(
504 &self,
505 drive: &Drive,
506 transaction: TransactionArg,
507 drive_operations: &mut Vec<LowLevelDriveOperation>,
508 platform_version: &PlatformVersion,
509 ) -> Result<BTreeMap<TimestampMillis, Vec<Vec<u8>>>, Error> {
510 let path_query = self.construct_path_query();
511 let query_result = drive.grove_get_path_query(
512 &path_query,
513 transaction,
514 QueryResultType::QueryPathKeyElementTrioResultType,
515 drive_operations,
516 &platform_version.drive,
517 );
518 match query_result {
519 Err(Error::GroveDB(e))
520 if matches!(
521 e.as_ref(),
522 GroveError::PathKeyNotFound(_)
523 | GroveError::PathNotFound(_)
524 | GroveError::PathParentLayerNotFound(_)
525 ) =>
526 {
527 Ok(BTreeMap::new())
528 }
529 Err(e) => Err(e),
530 Ok((query_result_elements, _)) => {
531 let vote_polls_by_end_date = query_result_elements
532 .to_path_key_elements()
533 .into_iter()
534 .map(|(path, _, element)| {
535 let Some(last_path_component) = path.last() else {
536 return Err(Error::Drive(DriveError::CorruptedDriveState(
537 "we should always have a path not be null".to_string(),
538 )));
539 };
540 let timestamp = decode_u64(last_path_component)?;
541 let contested_document_resource_vote_poll_bytes =
542 element.into_item_bytes().map_err(Error::from)?;
543 Ok((timestamp, contested_document_resource_vote_poll_bytes))
544 })
545 .collect::<Result<Vec<_>, Error>>()?
546 .into_iter()
547 .fold(
548 BTreeMap::new(),
549 |mut acc: BTreeMap<u64, Vec<Vec<u8>>>,
550 (timestamp, vote_poll_serialized)| {
551 acc.entry(timestamp).or_default().push(vote_poll_serialized);
552 acc
553 },
554 );
555 Ok(vote_polls_by_end_date)
556 }
557 }
558 }
559
560 #[cfg(feature = "server")]
561 #[allow(unused)]
562 pub(crate) fn execute_no_proof_internal(
564 &self,
565 drive: &Drive,
566 result_type: QueryResultType,
567 transaction: TransactionArg,
568 drive_operations: &mut Vec<LowLevelDriveOperation>,
569 platform_version: &PlatformVersion,
570 ) -> Result<QueryResultElements, Error> {
571 let path_query = self.construct_path_query();
572 let query_result = drive.grove_get_path_query(
573 &path_query,
574 transaction,
575 result_type,
576 drive_operations,
577 &platform_version.drive,
578 );
579 match query_result {
580 Err(Error::GroveDB(e))
581 if matches!(
582 e.as_ref(),
583 GroveError::PathKeyNotFound(_)
584 | GroveError::PathNotFound(_)
585 | GroveError::PathParentLayerNotFound(_)
586 ) =>
587 {
588 Ok(QueryResultElements::new())
589 }
590 _ => {
591 let (data, _) = query_result?;
592 {
593 Ok(data)
594 }
595 }
596 }
597 }
598}
599
600#[cfg(test)]
601mod tests {
602 use super::*;
603 use crate::drive::votes::paths::END_DATE_QUERIES_TREE_KEY;
604 use crate::drive::RootTree;
605 use grovedb::QueryItem;
606
607 fn expected_base_path() -> Vec<Vec<u8>> {
608 vec![
609 vec![RootTree::Votes as u8],
610 vec![END_DATE_QUERIES_TREE_KEY as u8],
611 ]
612 }
613
614 #[test]
619 fn construct_path_query_no_bounds_ascending() {
620 let query = VotePollsByEndDateDriveQuery {
621 start_time: None,
622 end_time: None,
623 limit: Some(10),
624 offset: None,
625 order_ascending: true,
626 };
627
628 let pq = query.construct_path_query();
629 assert_eq!(pq.path, expected_base_path());
630 assert_eq!(pq.query.limit, Some(10));
631 assert_eq!(pq.query.offset, None);
632
633 assert_eq!(pq.query.query.items.len(), 1);
635 assert!(matches!(&pq.query.query.items[0], QueryItem::RangeFull(..)));
636
637 assert!(pq.query.query.left_to_right);
639
640 assert!(pq.query.query.default_subquery_branch.subquery.is_some());
642 }
643
644 #[test]
645 fn construct_path_query_no_bounds_descending() {
646 let query = VotePollsByEndDateDriveQuery {
647 start_time: None,
648 end_time: None,
649 limit: None,
650 offset: None,
651 order_ascending: false,
652 };
653
654 let pq = query.construct_path_query();
655 assert!(!pq.query.query.left_to_right);
656 assert_eq!(pq.query.limit, None);
657 }
658
659 #[test]
660 fn construct_path_query_start_time_included() {
661 let query = VotePollsByEndDateDriveQuery {
662 start_time: Some((1000, true)),
663 end_time: None,
664 limit: Some(5),
665 offset: None,
666 order_ascending: true,
667 };
668
669 let pq = query.construct_path_query();
670 let items = &pq.query.query.items;
671 assert_eq!(items.len(), 1);
672 let encoded_1000 = encode_u64(1000);
673 assert!(
674 matches!(&items[0], QueryItem::RangeFrom(r) if r.start == encoded_1000),
675 "expected RangeFrom for included start time"
676 );
677 }
678
679 #[test]
680 fn construct_path_query_start_time_excluded() {
681 let query = VotePollsByEndDateDriveQuery {
682 start_time: Some((1000, false)),
683 end_time: None,
684 limit: Some(5),
685 offset: None,
686 order_ascending: true,
687 };
688
689 let pq = query.construct_path_query();
690 let items = &pq.query.query.items;
691 assert_eq!(items.len(), 1);
692 let encoded_1000 = encode_u64(1000);
693 assert!(
694 matches!(&items[0], QueryItem::RangeAfter(r) if r.start == encoded_1000),
695 "expected RangeAfter for excluded start time"
696 );
697 }
698
699 #[test]
700 fn construct_path_query_end_time_included() {
701 let query = VotePollsByEndDateDriveQuery {
702 start_time: None,
703 end_time: Some((2000, true)),
704 limit: Some(5),
705 offset: None,
706 order_ascending: true,
707 };
708
709 let pq = query.construct_path_query();
710 let items = &pq.query.query.items;
711 assert_eq!(items.len(), 1);
712 let encoded_2000 = encode_u64(2000);
713 assert!(
714 matches!(&items[0], QueryItem::RangeToInclusive(r) if r.end == encoded_2000),
715 "expected RangeToInclusive for included end time"
716 );
717 }
718
719 #[test]
720 fn construct_path_query_end_time_excluded() {
721 let query = VotePollsByEndDateDriveQuery {
722 start_time: None,
723 end_time: Some((2000, false)),
724 limit: Some(5),
725 offset: None,
726 order_ascending: true,
727 };
728
729 let pq = query.construct_path_query();
730 let items = &pq.query.query.items;
731 assert_eq!(items.len(), 1);
732 let encoded_2000 = encode_u64(2000);
733 assert!(
734 matches!(&items[0], QueryItem::RangeTo(r) if r.end == encoded_2000),
735 "expected RangeTo for excluded end time"
736 );
737 }
738
739 #[test]
740 fn construct_path_query_both_bounds_included() {
741 let query = VotePollsByEndDateDriveQuery {
742 start_time: Some((1000, true)),
743 end_time: Some((2000, true)),
744 limit: Some(20),
745 offset: None,
746 order_ascending: true,
747 };
748
749 let pq = query.construct_path_query();
750 let items = &pq.query.query.items;
751 assert_eq!(items.len(), 1);
752 let encoded_1000 = encode_u64(1000);
753 let encoded_2000 = encode_u64(2000);
754 assert!(
755 matches!(&items[0], QueryItem::RangeInclusive(r) if *r.start() == encoded_1000 && *r.end() == encoded_2000),
756 "expected RangeInclusive for both bounds included"
757 );
758 }
759
760 #[test]
761 fn construct_path_query_start_included_end_excluded() {
762 let query = VotePollsByEndDateDriveQuery {
763 start_time: Some((1000, true)),
764 end_time: Some((2000, false)),
765 limit: None,
766 offset: None,
767 order_ascending: true,
768 };
769
770 let pq = query.construct_path_query();
771 let items = &pq.query.query.items;
772 assert_eq!(items.len(), 1);
773 let encoded_1000 = encode_u64(1000);
774 let encoded_2000 = encode_u64(2000);
775 assert!(
776 matches!(&items[0], QueryItem::Range(r) if r.start == encoded_1000 && r.end == encoded_2000),
777 "expected Range (half-open) for start included, end excluded"
778 );
779 }
780
781 #[test]
782 fn construct_path_query_start_excluded_end_included() {
783 let query = VotePollsByEndDateDriveQuery {
784 start_time: Some((1000, false)),
785 end_time: Some((2000, true)),
786 limit: None,
787 offset: None,
788 order_ascending: true,
789 };
790
791 let pq = query.construct_path_query();
792 let items = &pq.query.query.items;
793 assert_eq!(items.len(), 1);
794 let encoded_1000 = encode_u64(1000);
795 let encoded_2000 = encode_u64(2000);
796 assert!(
797 matches!(&items[0], QueryItem::RangeAfterToInclusive(r) if *r.start() == encoded_1000 && *r.end() == encoded_2000),
798 "expected RangeAfterToInclusive"
799 );
800 }
801
802 #[test]
803 fn construct_path_query_both_bounds_excluded() {
804 let query = VotePollsByEndDateDriveQuery {
805 start_time: Some((1000, false)),
806 end_time: Some((2000, false)),
807 limit: None,
808 offset: None,
809 order_ascending: true,
810 };
811
812 let pq = query.construct_path_query();
813 let items = &pq.query.query.items;
814 assert_eq!(items.len(), 1);
815 let encoded_1000 = encode_u64(1000);
816 let encoded_2000 = encode_u64(2000);
817 assert!(
818 matches!(&items[0], QueryItem::RangeAfterTo(r) if r.start == encoded_1000 && r.end == encoded_2000),
819 "expected RangeAfterTo for both excluded"
820 );
821 }
822
823 #[test]
828 fn path_query_for_end_time_included_builds_correct_query() {
829 let end_time: u64 = 5000;
830 let limit: u16 = 50;
831
832 let pq = VotePollsByEndDateDriveQuery::path_query_for_end_time_included(end_time, limit);
833 assert_eq!(pq.path, expected_base_path());
834 assert_eq!(pq.query.limit, Some(limit));
835 assert!(pq.query.query.left_to_right);
836
837 let items = &pq.query.query.items;
838 assert_eq!(items.len(), 1);
839 let encoded_5000 = encode_u64(5000);
840 assert!(
841 matches!(&items[0], QueryItem::RangeToInclusive(r) if r.end == encoded_5000),
842 "expected RangeToInclusive up to end_time"
843 );
844
845 assert!(pq.query.query.default_subquery_branch.subquery.is_some());
847 }
848
849 #[test]
854 fn path_query_for_single_end_time_builds_key_query() {
855 let end_time: u64 = 7777;
856 let limit: u16 = 100;
857
858 let pq = VotePollsByEndDateDriveQuery::path_query_for_single_end_time(end_time, limit);
859 assert_eq!(pq.path, expected_base_path());
860 assert_eq!(pq.query.limit, Some(limit));
861
862 let items = &pq.query.query.items;
863 assert_eq!(items.len(), 1);
864 let encoded_7777 = encode_u64(7777);
865 assert!(
866 matches!(&items[0], QueryItem::Key(k) if *k == encoded_7777),
867 "expected Key query for single end time"
868 );
869 }
870}