1use std::collections::BTreeMap;
2use std::ops::ControlFlow;
3
4use super::super::error::Error;
5use super::super::manifest::AsyncManifestStore;
6use super::super::read::{EntryRef, ScanOutcome};
7use super::super::store::AsyncStore;
8use super::super::tree::Tree;
9use super::super::versioned_map::MapVersionId;
10use super::super::AsyncProlly;
11use super::async_coordinator::{find_snapshot, AsyncIndexedMap};
12use super::budget::{BudgetCounter, Deadline, QueryBudget};
13use super::definition::IndexProjection;
14use super::publication::AsyncIndexedStore;
15use super::snapshot::{
16 physical_bounds, IndexedSourceRecord, LogicalBounds, ProjectedIndexEntry, SecondaryIndexCursor,
17 SecondaryIndexDirection, SecondaryIndexMatch, SecondaryIndexMatchRef, SecondaryIndexPage,
18 SnapshotContext,
19};
20use super::state::{
21 CollectionIndexPolicy, IndexDescriptor, IndexedCollectionState, IndexedSnapshotId,
22 IndexedSnapshotManifest, IndexedSnapshotRecord,
23};
24use super::storage::{decode_physical_index_key, physical_index_key, IndexValue};
25
26pub struct AsyncIndexedSnapshot<'a, S: AsyncIndexedStore> {
28 id: IndexedSnapshotId,
29 state_tree: Tree,
30 state_version: MapVersionId,
31 source_tree: Tree,
32 source_version: MapVersionId,
33 indexes: BTreeMap<Vec<u8>, AsyncSecondaryIndexSnapshot<'a, S>>,
34}
35
36impl<'a, S: AsyncIndexedStore> AsyncIndexedSnapshot<'a, S> {
37 pub fn id(&self) -> &IndexedSnapshotId {
39 &self.id
40 }
41
42 pub fn source_version(&self) -> &MapVersionId {
44 &self.source_version
45 }
46
47 pub fn state_version(&self) -> &MapVersionId {
49 &self.state_version
50 }
51
52 pub fn source_tree(&self) -> &Tree {
54 &self.source_tree
55 }
56
57 pub fn state_tree(&self) -> &Tree {
59 &self.state_tree
60 }
61
62 pub fn index(
64 &self,
65 name: impl AsRef<[u8]>,
66 ) -> Result<&AsyncSecondaryIndexSnapshot<'a, S>, Error> {
67 self.indexes
68 .get(name.as_ref())
69 .ok_or_else(|| Error::IndexUnavailableAtVersion {
70 name: name.as_ref().to_vec(),
71 source_version: self.source_version.clone(),
72 })
73 }
74
75 pub fn indexes(&self) -> impl ExactSizeIterator<Item = &AsyncSecondaryIndexSnapshot<'a, S>> {
77 self.indexes.values()
78 }
79}
80
81pub struct AsyncSecondaryIndexSnapshot<'a, S: AsyncIndexedStore> {
83 prolly: &'a AsyncProlly<S>,
84 snapshot_id: SnapshotContext,
85 descriptor: IndexDescriptor,
86 selected: super::state::IndexSnapshotRef,
87 source_tree: Tree,
88 index_tree: Tree,
89 max_projection_bytes: usize,
90}
91
92pub struct AsyncSecondaryIndexQuery<'query, 'engine, S: AsyncIndexedStore> {
94 index: &'query AsyncSecondaryIndexSnapshot<'engine, S>,
95 budget: QueryBudget,
96}
97
98impl<'query, 'engine, S> AsyncSecondaryIndexQuery<'query, 'engine, S>
99where
100 S: AsyncIndexedStore + Clone,
101 <S as AsyncStore>::Error: Send + Sync,
102 <S as AsyncManifestStore>::Error: Send + Sync,
103{
104 pub async fn exact_page(
105 &self,
106 term: &[u8],
107 cursor: Option<&SecondaryIndexCursor>,
108 limit: usize,
109 ) -> Result<SecondaryIndexPage, Error> {
110 self.index
111 .page(
112 LogicalBounds::Exact(term.to_vec()),
113 SecondaryIndexDirection::Forward,
114 cursor,
115 limit,
116 &self.budget,
117 )
118 .await
119 }
120
121 pub async fn prefix_page(
122 &self,
123 prefix: &[u8],
124 cursor: Option<&SecondaryIndexCursor>,
125 limit: usize,
126 ) -> Result<SecondaryIndexPage, Error> {
127 self.index
128 .page(
129 LogicalBounds::Prefix(prefix.to_vec()),
130 SecondaryIndexDirection::Forward,
131 cursor,
132 limit,
133 &self.budget,
134 )
135 .await
136 }
137
138 pub async fn range_page(
139 &self,
140 start: &[u8],
141 end: Option<&[u8]>,
142 cursor: Option<&SecondaryIndexCursor>,
143 limit: usize,
144 ) -> Result<SecondaryIndexPage, Error> {
145 self.index
146 .page(
147 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
148 SecondaryIndexDirection::Forward,
149 cursor,
150 limit,
151 &self.budget,
152 )
153 .await
154 }
155
156 pub async fn exact_reverse_page(
157 &self,
158 term: &[u8],
159 cursor: Option<&SecondaryIndexCursor>,
160 limit: usize,
161 ) -> Result<SecondaryIndexPage, Error> {
162 self.index
163 .page(
164 LogicalBounds::Exact(term.to_vec()),
165 SecondaryIndexDirection::Reverse,
166 cursor,
167 limit,
168 &self.budget,
169 )
170 .await
171 }
172
173 pub async fn prefix_reverse_page(
174 &self,
175 prefix: &[u8],
176 cursor: Option<&SecondaryIndexCursor>,
177 limit: usize,
178 ) -> Result<SecondaryIndexPage, Error> {
179 self.index
180 .page(
181 LogicalBounds::Prefix(prefix.to_vec()),
182 SecondaryIndexDirection::Reverse,
183 cursor,
184 limit,
185 &self.budget,
186 )
187 .await
188 }
189
190 pub async fn range_reverse_page(
191 &self,
192 start: &[u8],
193 end: Option<&[u8]>,
194 cursor: Option<&SecondaryIndexCursor>,
195 limit: usize,
196 ) -> Result<SecondaryIndexPage, Error> {
197 self.index
198 .page(
199 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
200 SecondaryIndexDirection::Reverse,
201 cursor,
202 limit,
203 &self.budget,
204 )
205 .await
206 }
207
208 pub async fn records(&self, term: &[u8]) -> Result<Vec<IndexedSourceRecord>, Error> {
209 self.index.records_with_budget(term, &self.budget).await
210 }
211}
212
213impl<'a, S> AsyncSecondaryIndexSnapshot<'a, S>
214where
215 S: AsyncIndexedStore + Clone,
216 <S as AsyncStore>::Error: Send + Sync,
217 <S as AsyncManifestStore>::Error: Send + Sync,
218{
219 pub fn query(&self, budget: QueryBudget) -> Result<AsyncSecondaryIndexQuery<'_, 'a, S>, Error> {
221 budget.validate()?;
222 Ok(AsyncSecondaryIndexQuery {
223 index: self,
224 budget,
225 })
226 }
227
228 pub fn name(&self) -> &[u8] {
229 &self.descriptor.name
230 }
231
232 pub fn descriptor(&self) -> &IndexDescriptor {
233 &self.descriptor
234 }
235
236 pub fn snapshot_ref(&self) -> &super::state::IndexSnapshotRef {
237 &self.selected
238 }
239
240 pub fn tree(&self) -> &Tree {
241 &self.index_tree
242 }
243
244 pub async fn exact(&self, term: &[u8]) -> Result<Vec<SecondaryIndexMatch>, Error> {
245 self.collect_page(
246 self.exact_page(term, None, QueryBudget::default().max_returned_entries)
247 .await?,
248 )
249 }
250
251 pub async fn prefix(&self, prefix: &[u8]) -> Result<Vec<SecondaryIndexMatch>, Error> {
252 self.collect_page(
253 self.prefix_page(prefix, None, QueryBudget::default().max_returned_entries)
254 .await?,
255 )
256 }
257
258 pub async fn range(
259 &self,
260 start_term: &[u8],
261 end_term: Option<&[u8]>,
262 ) -> Result<Vec<SecondaryIndexMatch>, Error> {
263 self.collect_page(
264 self.range_page(
265 start_term,
266 end_term,
267 None,
268 QueryBudget::default().max_returned_entries,
269 )
270 .await?,
271 )
272 }
273
274 pub async fn primary_keys(&self, term: &[u8]) -> Result<Vec<Vec<u8>>, Error> {
275 Ok(self
276 .exact(term)
277 .await?
278 .into_iter()
279 .map(|matched| matched.primary_key)
280 .collect())
281 }
282
283 pub async fn projected(&self, term: &[u8]) -> Result<Vec<ProjectedIndexEntry>, Error> {
284 Ok(self
285 .exact(term)
286 .await?
287 .into_iter()
288 .map(|matched| (matched.primary_key, matched.projection))
289 .collect())
290 }
291
292 pub async fn records(&self, term: &[u8]) -> Result<Vec<IndexedSourceRecord>, Error> {
294 self.records_with_budget(term, &QueryBudget::default())
295 .await
296 }
297
298 async fn records_with_budget(
299 &self,
300 term: &[u8],
301 budget: &QueryBudget,
302 ) -> Result<Vec<IndexedSourceRecord>, Error> {
303 budget.validate()?;
304 let matches = self
305 .page(
306 LogicalBounds::Exact(term.to_vec()),
307 SecondaryIndexDirection::Forward,
308 None,
309 budget.max_returned_entries,
310 budget,
311 )
312 .await?;
313 let matches = self.collect_page(matches)?;
314 if matches.len() > budget.max_source_fetches {
315 return Err(Error::IndexResourceLimitExceeded {
316 resource: "query_source_fetches",
317 limit: budget.max_source_fetches,
318 actual: matches.len(),
319 });
320 }
321 let keys = matches
322 .iter()
323 .map(|matched| matched.primary_key.as_slice())
324 .collect::<Vec<_>>();
325 let values = self.prolly.get_many(&self.source_tree, &keys).await?;
326 let counter = BudgetCounter::new();
327 let mut returned_bytes = 0usize;
328 let mut accounted_memory = 0usize;
329 let mut records = Vec::with_capacity(matches.len());
330 for (matched, value) in matches.into_iter().zip(values) {
331 let value = value.ok_or_else(|| Error::IndexSnapshotMismatch {
332 name: self.descriptor.name.clone(),
333 source_version: self.snapshot_id.source_version.clone(),
334 reason: format!(
335 "index references missing source primary key {:?}",
336 matched.primary_key
337 ),
338 })?;
339 let retained = matched.primary_key.len().checked_add(value.len()).ok_or(
340 Error::IndexResourceLimitExceeded {
341 resource: "query_returned_bytes",
342 limit: budget.max_returned_bytes,
343 actual: usize::MAX,
344 },
345 )?;
346 counter.charge(
347 "query_returned_bytes",
348 &mut returned_bytes,
349 retained,
350 budget.max_returned_bytes,
351 )?;
352 counter.charge(
353 "query_accounted_memory_bytes",
354 &mut accounted_memory,
355 retained,
356 budget.max_accounted_memory_bytes,
357 )?;
358 counter.check_elapsed("query_elapsed_millis", budget.max_elapsed)?;
359 records.push((matched.primary_key, value));
360 }
361 Ok(records)
362 }
363
364 pub async fn scan_exact(
365 &self,
366 term: &[u8],
367 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>),
368 ) -> Result<u64, Error> {
369 Ok(self
370 .scan_exact_until(term, |row| {
371 visit(row);
372 ControlFlow::<()>::Continue(())
373 })
374 .await?
375 .visited)
376 }
377
378 pub async fn scan_exact_until<B>(
379 &self,
380 term: &[u8],
381 visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
382 ) -> Result<ScanOutcome<B>, Error> {
383 self.scan_matches_until(
384 LogicalBounds::Exact(term.to_vec()),
385 SecondaryIndexDirection::Forward,
386 visit,
387 )
388 .await
389 }
390
391 pub async fn scan_prefix(
392 &self,
393 prefix: &[u8],
394 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>),
395 ) -> Result<u64, Error> {
396 Ok(self
397 .scan_prefix_until(prefix, |row| {
398 visit(row);
399 ControlFlow::<()>::Continue(())
400 })
401 .await?
402 .visited)
403 }
404
405 pub async fn scan_prefix_until<B>(
406 &self,
407 prefix: &[u8],
408 visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
409 ) -> Result<ScanOutcome<B>, Error> {
410 self.scan_matches_until(
411 LogicalBounds::Prefix(prefix.to_vec()),
412 SecondaryIndexDirection::Forward,
413 visit,
414 )
415 .await
416 }
417
418 pub async fn scan_range(
419 &self,
420 start: &[u8],
421 end: Option<&[u8]>,
422 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>),
423 ) -> Result<u64, Error> {
424 Ok(self
425 .scan_range_until(start, end, |row| {
426 visit(row);
427 ControlFlow::<()>::Continue(())
428 })
429 .await?
430 .visited)
431 }
432
433 pub async fn scan_range_until<B>(
434 &self,
435 start: &[u8],
436 end: Option<&[u8]>,
437 visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
438 ) -> Result<ScanOutcome<B>, Error> {
439 self.scan_matches_until(
440 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
441 SecondaryIndexDirection::Forward,
442 visit,
443 )
444 .await
445 }
446
447 pub async fn scan_exact_reverse(
448 &self,
449 term: &[u8],
450 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>),
451 ) -> Result<u64, Error> {
452 Ok(self
453 .scan_exact_reverse_until(term, |row| {
454 visit(row);
455 ControlFlow::<()>::Continue(())
456 })
457 .await?
458 .visited)
459 }
460
461 pub async fn scan_exact_reverse_until<B>(
462 &self,
463 term: &[u8],
464 visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
465 ) -> Result<ScanOutcome<B>, Error> {
466 self.scan_matches_until(
467 LogicalBounds::Exact(term.to_vec()),
468 SecondaryIndexDirection::Reverse,
469 visit,
470 )
471 .await
472 }
473
474 pub async fn scan_prefix_reverse(
475 &self,
476 prefix: &[u8],
477 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>),
478 ) -> Result<u64, Error> {
479 Ok(self
480 .scan_prefix_reverse_until(prefix, |row| {
481 visit(row);
482 ControlFlow::<()>::Continue(())
483 })
484 .await?
485 .visited)
486 }
487
488 pub async fn scan_prefix_reverse_until<B>(
489 &self,
490 prefix: &[u8],
491 visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
492 ) -> Result<ScanOutcome<B>, Error> {
493 self.scan_matches_until(
494 LogicalBounds::Prefix(prefix.to_vec()),
495 SecondaryIndexDirection::Reverse,
496 visit,
497 )
498 .await
499 }
500
501 pub async fn scan_range_reverse(
502 &self,
503 start: &[u8],
504 end: Option<&[u8]>,
505 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>),
506 ) -> Result<u64, Error> {
507 Ok(self
508 .scan_range_reverse_until(start, end, |row| {
509 visit(row);
510 ControlFlow::<()>::Continue(())
511 })
512 .await?
513 .visited)
514 }
515
516 pub async fn scan_range_reverse_until<B>(
517 &self,
518 start: &[u8],
519 end: Option<&[u8]>,
520 visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
521 ) -> Result<ScanOutcome<B>, Error> {
522 self.scan_matches_until(
523 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
524 SecondaryIndexDirection::Reverse,
525 visit,
526 )
527 .await
528 }
529
530 async fn scan_matches_until<B>(
531 &self,
532 logical: LogicalBounds,
533 direction: SecondaryIndexDirection,
534 mut visit: impl for<'row> FnMut(SecondaryIndexMatchRef<'row>) -> ControlFlow<B>,
535 ) -> Result<ScanOutcome<B>, Error> {
536 let budget = QueryBudget::default();
537 budget.validate()?;
538 let started = Deadline::new();
539 let mut scanned = 0usize;
540 let mut returned = 0usize;
541 let mut returned_bytes = 0usize;
542 let bounds = physical_bounds(&logical)?;
543 let mut handle = |entry: EntryRef<'_>| {
544 scanned = scanned.saturating_add(1);
545 returned = returned.saturating_add(1);
546 returned_bytes = returned_bytes
547 .saturating_add(entry.key().len())
548 .saturating_add(entry.value().len());
549 if scanned > budget.max_scanned_entries
550 || returned > budget.max_returned_entries
551 || returned_bytes > budget.max_returned_bytes
552 || returned_bytes > budget.max_accounted_memory_bytes
553 || started.exceeded(budget.max_elapsed)
554 {
555 return ControlFlow::Break(Err(Error::IndexResourceLimitExceeded {
556 resource: "query_scan_budget",
557 limit: budget.max_scanned_entries.min(budget.max_returned_entries),
558 actual: scanned.max(returned),
559 }));
560 }
561 match self.decode_match(entry.key(), entry.value()) {
562 Ok(matched) => match visit(SecondaryIndexMatchRef {
563 term: &matched.term,
564 primary_key: &matched.primary_key,
565 projection: matched.projection.as_deref(),
566 }) {
567 ControlFlow::Continue(()) => ControlFlow::Continue(()),
568 ControlFlow::Break(value) => ControlFlow::Break(Ok(value)),
569 },
570 Err(error) => ControlFlow::Break(Err(error)),
571 }
572 };
573 let outcome = match direction {
574 SecondaryIndexDirection::Reverse => {
575 self.prolly
576 .scan_range_reverse_until(
577 &self.index_tree,
578 &bounds.start,
579 bounds.end.as_deref(),
580 &mut handle,
581 )
582 .await?
583 }
584 SecondaryIndexDirection::Forward => {
585 self.prolly
586 .scan_range_until(
587 &self.index_tree,
588 &bounds.start,
589 bounds.end.as_deref(),
590 &mut handle,
591 )
592 .await?
593 }
594 };
595 match outcome.break_value {
596 Some(Ok(value)) => Ok(ScanOutcome::stopped(outcome.visited, value)),
597 Some(Err(error)) => Err(error),
598 None => Ok(ScanOutcome::complete(outcome.visited)),
599 }
600 }
601
602 pub async fn exact_page(
603 &self,
604 term: &[u8],
605 cursor: Option<&SecondaryIndexCursor>,
606 limit: usize,
607 ) -> Result<SecondaryIndexPage, Error> {
608 self.page(
609 LogicalBounds::Exact(term.to_vec()),
610 SecondaryIndexDirection::Forward,
611 cursor,
612 limit,
613 &QueryBudget::default(),
614 )
615 .await
616 }
617
618 pub async fn exact_reverse_page(
619 &self,
620 term: &[u8],
621 cursor: Option<&SecondaryIndexCursor>,
622 limit: usize,
623 ) -> Result<SecondaryIndexPage, Error> {
624 self.page(
625 LogicalBounds::Exact(term.to_vec()),
626 SecondaryIndexDirection::Reverse,
627 cursor,
628 limit,
629 &QueryBudget::default(),
630 )
631 .await
632 }
633
634 pub async fn prefix_page(
635 &self,
636 prefix: &[u8],
637 cursor: Option<&SecondaryIndexCursor>,
638 limit: usize,
639 ) -> Result<SecondaryIndexPage, Error> {
640 self.page(
641 LogicalBounds::Prefix(prefix.to_vec()),
642 SecondaryIndexDirection::Forward,
643 cursor,
644 limit,
645 &QueryBudget::default(),
646 )
647 .await
648 }
649
650 pub async fn prefix_reverse_page(
651 &self,
652 prefix: &[u8],
653 cursor: Option<&SecondaryIndexCursor>,
654 limit: usize,
655 ) -> Result<SecondaryIndexPage, Error> {
656 self.page(
657 LogicalBounds::Prefix(prefix.to_vec()),
658 SecondaryIndexDirection::Reverse,
659 cursor,
660 limit,
661 &QueryBudget::default(),
662 )
663 .await
664 }
665
666 pub async fn range_page(
667 &self,
668 start: &[u8],
669 end: Option<&[u8]>,
670 cursor: Option<&SecondaryIndexCursor>,
671 limit: usize,
672 ) -> Result<SecondaryIndexPage, Error> {
673 self.page(
674 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
675 SecondaryIndexDirection::Forward,
676 cursor,
677 limit,
678 &QueryBudget::default(),
679 )
680 .await
681 }
682
683 pub async fn range_reverse_page(
684 &self,
685 start: &[u8],
686 end: Option<&[u8]>,
687 cursor: Option<&SecondaryIndexCursor>,
688 limit: usize,
689 ) -> Result<SecondaryIndexPage, Error> {
690 self.page(
691 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
692 SecondaryIndexDirection::Reverse,
693 cursor,
694 limit,
695 &QueryBudget::default(),
696 )
697 .await
698 }
699
700 pub fn exact_cursor_after(
703 &self,
704 query_term: &[u8],
705 term: &[u8],
706 primary_key: &[u8],
707 direction: SecondaryIndexDirection,
708 ) -> Result<SecondaryIndexCursor, Error> {
709 self.cursor_after_logical(
710 LogicalBounds::Exact(query_term.to_vec()),
711 term,
712 primary_key,
713 direction,
714 )
715 }
716
717 pub fn prefix_cursor_after(
718 &self,
719 query_prefix: &[u8],
720 term: &[u8],
721 primary_key: &[u8],
722 direction: SecondaryIndexDirection,
723 ) -> Result<SecondaryIndexCursor, Error> {
724 self.cursor_after_logical(
725 LogicalBounds::Prefix(query_prefix.to_vec()),
726 term,
727 primary_key,
728 direction,
729 )
730 }
731
732 pub fn range_cursor_after(
733 &self,
734 start: &[u8],
735 end: Option<&[u8]>,
736 term: &[u8],
737 primary_key: &[u8],
738 direction: SecondaryIndexDirection,
739 ) -> Result<SecondaryIndexCursor, Error> {
740 self.cursor_after_logical(
741 LogicalBounds::Range(start.to_vec(), end.map(ToOwned::to_owned)),
742 term,
743 primary_key,
744 direction,
745 )
746 }
747
748 fn cursor_after_logical(
749 &self,
750 logical: LogicalBounds,
751 term: &[u8],
752 primary_key: &[u8],
753 direction: SecondaryIndexDirection,
754 ) -> Result<SecondaryIndexCursor, Error> {
755 let cursor = SecondaryIndexCursor {
756 snapshot: self.snapshot_id.snapshot.clone(),
757 source_version: self.snapshot_id.source_version.clone(),
758 state_version: self.snapshot_id.state_version.clone(),
759 index_name: self.descriptor.name.clone(),
760 index_version: MapVersionId::for_tree(&self.selected.tree)?,
761 definition_fingerprint: self.descriptor.fingerprint.clone(),
762 direction,
763 bounds: logical.clone(),
764 raw_key: Some(physical_index_key(term, primary_key)?),
765 };
766 self.validate_cursor(&cursor, &logical, direction)?;
767 Ok(cursor)
768 }
769
770 async fn page(
771 &self,
772 logical: LogicalBounds,
773 direction: SecondaryIndexDirection,
774 cursor: Option<&SecondaryIndexCursor>,
775 limit: usize,
776 budget: &QueryBudget,
777 ) -> Result<SecondaryIndexPage, Error> {
778 budget.validate()?;
779 let counter = BudgetCounter::new();
780 let max_page_entries = budget.max_page_entries.min(budget.max_returned_entries);
781 if limit > max_page_entries {
782 return Err(Error::IndexResourceLimitExceeded {
783 resource: "query_page_entries",
784 limit: max_page_entries,
785 actual: limit,
786 });
787 }
788 if let Some(cursor) = cursor {
789 self.validate_cursor(cursor, &logical, direction)?;
790 }
791 if limit == 0 {
792 let next_cursor = cursor.cloned().or_else(|| {
793 Some(SecondaryIndexCursor {
794 snapshot: self.snapshot_id.snapshot.clone(),
795 source_version: self.snapshot_id.source_version.clone(),
796 state_version: self.snapshot_id.state_version.clone(),
797 index_name: self.descriptor.name.clone(),
798 index_version: MapVersionId::for_tree(&self.selected.tree)
799 .expect("validated index tree"),
800 definition_fingerprint: self.descriptor.fingerprint.clone(),
801 direction,
802 bounds: logical,
803 raw_key: None,
804 })
805 });
806 return Ok(SecondaryIndexPage {
807 matches: Vec::new(),
808 next_cursor,
809 });
810 }
811 let bounds = physical_bounds(&logical)?;
812 let mut matches = Vec::with_capacity(limit);
813 let mut returned_bytes = 0usize;
814 let mut accounted_memory = 0usize;
815 let mut scanned = 0usize;
816 let mut has_more = false;
817 let mut raw_key = None;
818 let after = cursor.and_then(|cursor| cursor.raw_key.as_deref());
819 let mut handle = |entry: EntryRef<'_>| {
820 if direction == SecondaryIndexDirection::Forward
821 && after.is_some_and(|after| entry.key() <= after)
822 {
823 return ControlFlow::Continue(());
824 }
825 scanned = scanned.saturating_add(1);
826 if scanned > budget.max_scanned_entries {
827 return ControlFlow::Break(Err(Error::IndexResourceLimitExceeded {
828 resource: "query_scanned_entries",
829 limit: budget.max_scanned_entries,
830 actual: scanned,
831 }));
832 }
833 if matches.len() == limit {
834 has_more = true;
835 return ControlFlow::Break(Ok(()));
836 }
837 let matched = match self.decode_match(entry.key(), entry.value()) {
838 Ok(matched) => matched,
839 Err(error) => return ControlFlow::Break(Err(error)),
840 };
841 let retained = matched
842 .term
843 .len()
844 .checked_add(matched.primary_key.len())
845 .and_then(|bytes| {
846 bytes.checked_add(matched.projection.as_ref().map_or(0, Vec::len))
847 })
848 .ok_or(Error::IndexResourceLimitExceeded {
849 resource: "query_returned_bytes",
850 limit: budget.max_returned_bytes,
851 actual: usize::MAX,
852 });
853 let retained = match retained {
854 Ok(retained) => retained,
855 Err(error) => return ControlFlow::Break(Err(error)),
856 };
857 if let Err(error) = counter
858 .charge(
859 "query_returned_bytes",
860 &mut returned_bytes,
861 retained,
862 budget.max_returned_bytes,
863 )
864 .and_then(|_| {
865 counter.charge(
866 "query_accounted_memory_bytes",
867 &mut accounted_memory,
868 retained,
869 budget.max_accounted_memory_bytes,
870 )
871 })
872 .and_then(|_| counter.check_elapsed("query_elapsed_millis", budget.max_elapsed))
873 {
874 return ControlFlow::Break(Err(error));
875 }
876 raw_key = Some(entry.key().to_vec());
877 matches.push(matched);
878 ControlFlow::Continue(())
879 };
880 let outcome: ScanOutcome<Result<(), Error>> = match direction {
881 SecondaryIndexDirection::Forward => {
882 let start = after.unwrap_or(&bounds.start);
883 self.prolly
884 .scan_range_until(&self.index_tree, start, bounds.end.as_deref(), &mut handle)
885 .await?
886 }
887 SecondaryIndexDirection::Reverse => {
888 let end = after.or(bounds.end.as_deref());
889 self.prolly
890 .scan_range_reverse_until(&self.index_tree, &bounds.start, end, &mut handle)
891 .await?
892 }
893 };
894 if let Some(Err(error)) = outcome.break_value {
895 return Err(error);
896 }
897 let next_cursor = has_more.then(|| SecondaryIndexCursor {
898 snapshot: self.snapshot_id.snapshot.clone(),
899 source_version: self.snapshot_id.source_version.clone(),
900 state_version: self.snapshot_id.state_version.clone(),
901 index_name: self.descriptor.name.clone(),
902 index_version: MapVersionId::for_tree(&self.selected.tree)
903 .expect("validated index tree"),
904 definition_fingerprint: self.descriptor.fingerprint.clone(),
905 direction,
906 bounds: logical,
907 raw_key,
908 });
909 Ok(SecondaryIndexPage {
910 matches,
911 next_cursor,
912 })
913 }
914
915 fn validate_cursor(
916 &self,
917 cursor: &SecondaryIndexCursor,
918 bounds: &LogicalBounds,
919 direction: SecondaryIndexDirection,
920 ) -> Result<(), Error> {
921 let index_version =
922 MapVersionId::for_tree(&self.selected.tree).expect("validated index tree");
923 let valid = cursor.snapshot == self.snapshot_id.snapshot
924 && cursor.source_version == self.snapshot_id.source_version
925 && cursor.state_version == self.snapshot_id.state_version
926 && cursor.index_name == self.descriptor.name
927 && cursor.index_version == index_version
928 && cursor.definition_fingerprint == self.descriptor.fingerprint
929 && cursor.direction == direction
930 && &cursor.bounds == bounds;
931 let physical_key_valid = match cursor.raw_key.as_deref() {
932 None => true,
933 Some(raw_key) => {
934 let physical = physical_bounds(bounds)?;
935 raw_key >= physical.start.as_slice()
936 && physical.end.as_deref().is_none_or(|end| raw_key < end)
937 && decode_physical_index_key(raw_key).is_ok()
938 }
939 };
940 if valid && physical_key_valid {
941 return Ok(());
942 }
943 Err(Error::IndexCursorVersionMismatch {
944 expected: format!(
945 "source={}, state={}, index={}, direction={direction:?}, bounds={bounds:?}",
946 self.snapshot_id.source_version, self.snapshot_id.state_version, index_version
947 ),
948 actual: format!(
949 "source={}, state={}, index={}, direction={:?}, bounds={:?}",
950 cursor.source_version,
951 cursor.state_version,
952 cursor.index_version,
953 cursor.direction,
954 cursor.bounds
955 ),
956 })
957 }
958
959 fn collect_page(&self, page: SecondaryIndexPage) -> Result<Vec<SecondaryIndexMatch>, Error> {
960 if page.next_cursor.is_some() {
961 let limit = QueryBudget::default().max_returned_entries;
962 return Err(Error::IndexResourceLimitExceeded {
963 resource: "query_returned_entries",
964 limit,
965 actual: limit.saturating_add(1),
966 });
967 }
968 Ok(page.matches)
969 }
970
971 fn decode_match(&self, key: &[u8], value: &[u8]) -> Result<SecondaryIndexMatch, Error> {
972 let decoded = decode_physical_index_key(key)?;
973 let stored = IndexValue::from_bytes(value, self.max_projection_bytes)?;
974 let projection = match (self.descriptor.projection, stored) {
975 (IndexProjection::KeysOnly, IndexValue::KeysOnly) => None,
976 (IndexProjection::Include, IndexValue::Included(bytes))
977 | (IndexProjection::All, IndexValue::FullSource(bytes)) => Some(bytes),
978 _ => {
979 return Err(Error::IndexSnapshotMismatch {
980 name: self.descriptor.name.clone(),
981 source_version: self.snapshot_id.source_version.clone(),
982 reason: "stored projection value does not match its descriptor".to_string(),
983 })
984 }
985 };
986 Ok(SecondaryIndexMatch {
987 term: decoded.term,
988 primary_key: decoded.primary_key,
989 projection,
990 })
991 }
992}
993
994impl<'a, S> AsyncIndexedMap<'a, S>
995where
996 S: AsyncIndexedStore + Clone,
997 <S as AsyncStore>::Error: Send + Sync,
998 <S as AsyncManifestStore>::Error: Send + Sync,
999{
1000 pub async fn snapshot(&self) -> Result<AsyncIndexedSnapshot<'a, S>, Error> {
1002 let loaded = self.load_state().await?;
1003 let record = loaded.state.head_snapshot()?.clone();
1004 let record_id = loaded.state.head.clone();
1005 self.resolve_snapshot(loaded.tree, loaded.state, record_id, record)
1006 }
1007
1008 pub async fn snapshot_at(
1010 &self,
1011 source_version: &MapVersionId,
1012 ) -> Result<AsyncIndexedSnapshot<'a, S>, Error> {
1013 let loaded = self.load_state().await?;
1014 let record = find_snapshot(&loaded.state, source_version)?.clone();
1015 let record_id = record.id()?;
1016 self.resolve_snapshot(loaded.tree, loaded.state, record_id, record)
1017 }
1018
1019 pub async fn snapshot_by_id(
1021 &self,
1022 id: &IndexedSnapshotId,
1023 ) -> Result<AsyncIndexedSnapshot<'a, S>, Error> {
1024 let loaded = self.load_state().await?;
1025 let record = loaded.state.snapshots.get(id).cloned().ok_or_else(|| {
1026 Error::InvalidVersionedMap(format!(
1027 "indexed snapshot {:?} is not retained",
1028 id.as_cid()
1029 ))
1030 })?;
1031 self.resolve_snapshot(loaded.tree, loaded.state, id.clone(), record)
1032 }
1033
1034 pub fn snapshot_from_manifest(
1041 &self,
1042 manifest_tree: Tree,
1043 manifest: IndexedSnapshotManifest,
1044 ) -> Result<AsyncIndexedSnapshot<'a, S>, Error> {
1045 manifest.validate()?;
1046 if manifest.record.source_map_id != self.source_map_id {
1047 return Err(Error::InvalidVersionedMap(
1048 "indexed snapshot manifest belongs to another source".to_string(),
1049 ));
1050 }
1051 let active = manifest
1052 .record
1053 .indexes
1054 .iter()
1055 .map(|index| (index.name.clone(), index.descriptor_fingerprint.clone()))
1056 .collect();
1057 let descriptors = manifest
1058 .descriptors
1059 .into_iter()
1060 .map(|descriptor| {
1061 (
1062 (descriptor.name.clone(), descriptor.fingerprint.clone()),
1063 descriptor,
1064 )
1065 })
1066 .collect();
1067 let record_id = manifest.snapshot_id;
1068 let record = manifest.record;
1069 let state = IndexedCollectionState {
1070 source_map_id: self.source_map_id.clone(),
1071 policy: CollectionIndexPolicy::default(),
1072 head: record_id.clone(),
1073 snapshots: BTreeMap::from([(record_id.clone(), record.clone())]),
1074 descriptors,
1075 active,
1076 retired: Default::default(),
1077 pins: BTreeMap::new(),
1078 };
1079 state.validate_closure()?;
1080 self.resolve_snapshot(manifest_tree, state, record_id, record)
1081 }
1082
1083 fn resolve_snapshot(
1084 &self,
1085 state_tree: Tree,
1086 state: super::state::IndexedCollectionState,
1087 record_id: IndexedSnapshotId,
1088 record: IndexedSnapshotRecord,
1089 ) -> Result<AsyncIndexedSnapshot<'a, S>, Error> {
1090 let source_version = MapVersionId::for_tree(&record.source.tree)?;
1091 let state_version = MapVersionId::for_tree(&state_tree)?;
1092 let snapshot_id = SnapshotContext {
1093 snapshot: record_id,
1094 source_version: source_version.clone(),
1095 state_version: state_version.clone(),
1096 };
1097 let mut indexes = BTreeMap::new();
1098 for selected in record.indexes {
1099 let descriptor = state
1100 .descriptors
1101 .get(&(
1102 selected.name.clone(),
1103 selected.descriptor_fingerprint.clone(),
1104 ))
1105 .cloned()
1106 .ok_or_else(|| Error::IndexSnapshotMismatch {
1107 name: selected.name.clone(),
1108 source_version: source_version.clone(),
1109 reason: "canonical descriptor is missing".to_string(),
1110 })?;
1111 let max_projection_bytes = match descriptor.projection {
1112 IndexProjection::KeysOnly => 0,
1113 IndexProjection::Include => descriptor.limits.max_projection_bytes,
1114 IndexProjection::All => descriptor.limits.max_all_value_bytes,
1115 };
1116 indexes.insert(
1117 selected.name.clone(),
1118 AsyncSecondaryIndexSnapshot {
1119 prolly: self.prolly,
1120 snapshot_id: snapshot_id.clone(),
1121 descriptor,
1122 source_tree: record.source.tree.clone(),
1123 index_tree: selected.tree.clone(),
1124 max_projection_bytes,
1125 selected,
1126 },
1127 );
1128 }
1129 Ok(AsyncIndexedSnapshot {
1130 id: snapshot_id.snapshot,
1131 state_tree,
1132 state_version,
1133 source_tree: record.source.tree,
1134 source_version,
1135 indexes,
1136 })
1137 }
1138}