1use rushdb_lmpg::reconstruct;
6use rushdb_lmpg::Lmpg;
7use serde_json::{Map, Value as Json};
8
9use crate::budget::WorkingSet;
10use crate::planner::index_candidates_with_budget;
11use crate::point_fields::{self, PointFields};
12use crate::{
13 check_walk_budget, eval_expr_ctx, normalize, order, planner, related_exists, scan,
14 sort_ordered_rows, working_set_budget, EvalCtx, Expr, QueryError, QuerySnapshot, SearchRequest,
15};
16
17pub(crate) struct Matches {
19 pub(crate) retained: Vec<u64>,
22 pub(crate) offset: usize,
24 pub(crate) total: u64,
26}
27
28pub(crate) fn run_matches(
43 lmpg: &Lmpg,
44 req: &SearchRequest,
45 is_cancelled: &dyn Fn() -> bool,
46 snap: &QuerySnapshot,
47 skip: usize,
48 limit: usize,
49 known_total: Option<u64>,
50) -> Result<Matches, QueryError> {
51 check_walk_budget(lmpg, snap, req)?;
52 let page = (skip, limit);
53 if let Some(matches) = run_pass(lmpg, req, is_cancelled, snap, page, known_total, true)? {
54 return Ok(matches);
55 }
56 Ok(
57 run_pass(lmpg, req, is_cancelled, snap, page, known_total, false)?
58 .expect("a predicate-first pass always completes"),
59 )
60}
61
62pub(crate) fn run_pass(
65 lmpg: &Lmpg,
66 req: &SearchRequest,
67 is_cancelled: &dyn Fn() -> bool,
68 snap: &QuerySnapshot,
69 (skip, limit): (usize, usize),
70 known_total: Option<u64>,
71 order_first: bool,
72) -> Result<Option<Matches>, QueryError> {
73 let mut working = WorkingSet {
74 limit: working_set_budget(lmpg).map(|bytes| u64::try_from(bytes).unwrap_or(u64::MAX)),
75 charged: 0,
76 };
77 let CandidateSource {
78 phases,
79 exact,
80 known,
81 predicate,
82 member_of,
83 plan,
84 } = candidate_source(
85 lmpg,
86 req,
87 is_cancelled,
88 snap,
89 (skip, limit),
90 known_total,
91 order_first,
92 &mut working,
93 )?;
94 let mirror = plan.as_ref().is_some_and(order::OrderPlan::mirror);
95 let budget = plan.as_ref().map(|plan| plan.predicate_first_reads);
96 let ctx = EvalCtx::at(lmpg, snap.clone(), Some(is_cancelled));
97 let mut session = reconstruct::ReconstructSession::new(snap, lmpg.tenant);
98 let (order_fields, predicate_fields) =
99 point_reads(snap, lmpg.tenant, req, &phases, predicate.as_ref())?;
100 let mut kept = Kept::new(mirror, known, skip, limit);
101 let mut total: u64 = 0;
102 let mut examined: u64 = 0;
103 for Phase {
104 mut candidates,
105 sorted,
106 } in phases
107 {
108 if known.is_some() && kept.full() {
109 break;
110 }
111 let mut ordered = Sorted::new(kept.keep - kept.count.min(kept.keep));
112 while let Some(ord) = candidates.next(is_cancelled)? {
113 if is_cancelled() {
114 return Err(QueryError::Cancelled);
115 }
116 examined += 1;
117 if budget.is_some_and(|budget| examined + candidates.passed() > budget) {
118 return Ok(None);
119 }
120 if member_of.as_ref().is_some_and(|set| !set.contains(ord)) {
121 continue;
122 }
123 if !exact
124 && !is_match(
125 &ctx,
126 req,
127 predicate.as_ref(),
128 predicate_fields.as_ref(),
129 &mut session,
130 &mut working,
131 ord,
132 )?
133 {
134 continue;
135 }
136 total += 1;
137 if sorted {
138 let row = read_row(order_fields.as_ref(), &mut session, snap, lmpg.tenant, ord)?;
139 ordered.push(ord, row, req, &mut working)?;
140 } else if !kept.push(ord, &mut working)? && known.is_some() {
141 break;
142 }
143 }
144 if candidates.gave_up() {
145 return Ok(None);
146 }
147 examined += candidates.passed();
148 if sorted {
149 for ord in ordered.finish(req) {
150 kept.count += 1;
151 kept.retained.push_back(ord);
152 }
153 }
154 }
155 let (retained, offset) = if mirror {
156 (kept.retained.into_iter().rev().collect(), skip)
157 } else {
158 (kept.retained.into(), 0)
159 };
160 Ok(Some(Matches {
161 retained,
162 offset,
163 total: known.unwrap_or(total),
164 }))
165}
166
167fn point_reads(
171 snap: &QuerySnapshot,
172 tenant: rushdb_types::ids::TenantId,
173 req: &SearchRequest,
174 phases: &[Phase<'_>],
175 predicate: Option<&Expr>,
176) -> Result<(Option<PointFields>, Option<PointFields>), QueryError> {
177 let order_fields = if phases.iter().any(|phase| phase.sorted) {
178 PointFields::resolve_in(
179 snap,
180 tenant,
181 &req.labels,
182 req.order_by.iter().map(|(f, _)| f.as_str()),
183 )?
184 } else {
185 None
186 };
187 let predicate_fields = match predicate {
188 Some(expr) => PointFields::for_predicate(snap, tenant, &req.labels, expr)?,
189 None => None,
190 };
191 Ok((order_fields, predicate_fields))
192}
193
194struct Kept {
196 retained: std::collections::VecDeque<u64>,
197 count: usize,
198 keep: usize,
200 page: Option<usize>,
202}
203
204impl Kept {
205 fn new(mirror: bool, known: Option<u64>, skip: usize, limit: usize) -> Self {
209 let keep = if mirror {
210 usize::try_from(known.unwrap_or(0))
211 .unwrap_or(usize::MAX)
212 .saturating_sub(skip)
213 } else {
214 skip.saturating_add(limit)
215 };
216 Self {
217 retained: std::collections::VecDeque::new(),
218 count: 0,
219 keep,
220 page: mirror.then_some(limit),
221 }
222 }
223
224 fn full(&self) -> bool {
225 self.count >= self.keep
226 }
227
228 fn push(&mut self, ord: u64, working: &mut WorkingSet) -> Result<bool, QueryError> {
230 if self.full() {
231 return Ok(false);
232 }
233 self.count += 1;
234 self.retained.push_back(ord);
235 if self.page.is_some_and(|page| self.retained.len() > page) {
236 self.retained.pop_front();
237 } else {
238 working.charge(8, true)?;
239 }
240 Ok(true)
241 }
242}
243
244struct Sorted {
248 rows: Vec<OrderRow>,
249 bytes: Vec<u64>,
250 wanted: usize,
251}
252
253impl Sorted {
254 fn new(wanted: usize) -> Self {
255 Self {
256 rows: Vec::new(),
257 bytes: Vec::new(),
258 wanted,
259 }
260 }
261
262 fn push(
263 &mut self,
264 ord: u64,
265 row: Map<String, Json>,
266 req: &SearchRequest,
267 working: &mut WorkingSet,
268 ) -> Result<(), QueryError> {
269 let bytes =
270 serde_json::to_vec(&row).map_or(0, |v| u64::try_from(v.len()).unwrap_or(u64::MAX));
271 working.charge(bytes, true)?;
272 self.rows.push((ord, row));
273 self.bytes.push(bytes);
274 if self.rows.len() >= self.wanted.saturating_mul(2).max(64) {
275 working.charged = working.charged.saturating_sub(cut_ordered(
276 &mut self.rows,
277 &mut self.bytes,
278 req,
279 self.wanted,
280 ));
281 }
282 Ok(())
283 }
284
285 fn finish(mut self, req: &SearchRequest) -> impl Iterator<Item = u64> {
287 cut_ordered(&mut self.rows, &mut self.bytes, req, self.wanted);
288 self.rows.into_iter().map(|(ord, _)| ord)
289 }
290}
291
292pub(crate) fn is_match(
298 ctx: &EvalCtx<'_>,
299 req: &SearchRequest,
300 predicate: Option<&Expr>,
301 fields: Option<&PointFields>,
302 session: &mut reconstruct::ReconstructSession<'_, QuerySnapshot>,
303 working: &mut WorkingSet,
304 ord: u64,
305) -> Result<bool, QueryError> {
306 let (snap, tenant) = (&ctx.snap, ctx.lmpg.tenant);
307 if let Some(expr) = predicate {
308 let obj = read_row(fields, session, snap, tenant, ord)?;
309 let bytes =
310 serde_json::to_vec(&obj).map_or(0, |v| u64::try_from(v.len()).unwrap_or(u64::MAX));
311 working.charge(bytes, false)?;
312 if !eval_expr_ctx(ctx, expr, &obj, ord)? {
313 return Ok(false);
314 }
315 }
316 for spec in &req.related {
317 if !related_exists(snap, tenant, ord, spec)? {
318 return Ok(false);
319 }
320 }
321 Ok(true)
322}
323
324pub(crate) fn filtered_row<R: rushdb_lmpg::reader::TryRead>(
329 reader: &R,
330 snap: &QuerySnapshot,
331 tenant: rushdb_types::ids::TenantId,
332 predicate: &Expr,
333 ord: u64,
334) -> Result<Map<String, Json>, QueryError> {
335 let mut session = reconstruct::ReconstructSession::new(reader, tenant);
336 let mut names = std::collections::BTreeSet::new();
337 let record = if point_fields::predicate_fields(predicate, &mut names) {
338 let selected: std::collections::HashSet<String> = names
339 .iter()
340 .flat_map(|name| [name.clone(), normalize(name)])
341 .collect();
342 session.reconstruct_record_selected(ord, &selected)?
343 } else {
344 session.reconstruct_record(ord)?
345 };
346 let _ = snap;
347 Ok(match record {
348 Some(Json::Object(map)) => map,
349 _ => Map::new(),
350 })
351}
352
353pub(crate) fn read_row(
355 fields: Option<&PointFields>,
356 session: &mut reconstruct::ReconstructSession<'_, QuerySnapshot>,
357 snap: &QuerySnapshot,
358 tenant: rushdb_types::ids::TenantId,
359 ord: u64,
360) -> Result<Map<String, Json>, QueryError> {
361 match fields {
362 Some(fields) => fields.row(snap, tenant, ord),
363 None => Ok(session
364 .reconstruct_record(ord)?
365 .and_then(|record| match record {
366 Json::Object(map) => Some(map),
367 _ => None,
368 })
369 .unwrap_or_default()),
370 }
371}
372
373pub(crate) struct PagePlan {
376 index_plan: Option<planner::IndexPlan>,
377 exact: bool,
378 known: Option<u64>,
379 predicate: Option<Expr>,
380 order: Option<order::OrderPlan>,
381}
382
383pub(crate) fn plan_page(
386 lmpg: &Lmpg,
387 req: &SearchRequest,
388 is_cancelled: &dyn Fn() -> bool,
389 snap: &QuerySnapshot,
390 (skip, limit): (usize, usize),
391 known_total: Option<u64>,
392 order_first: bool,
393) -> Result<PagePlan, QueryError> {
394 let index_plan =
395 index_candidates_with_budget(lmpg, snap, req, working_set_budget(lmpg), is_cancelled)?;
396 let (exact, known, predicate, candidates) = if let Some(plan) = &index_plan {
397 let known = known_total
398 .or(plan.known_total)
399 .or_else(|| plan.exact.then(|| plan.ords.len()));
400 (plan.exact, known, plan.residual.clone(), plan.ords.len())
401 } else {
402 let exact = req.where_.is_none() && req.related.is_empty();
403 let records = walk_total(lmpg, snap, &req.labels)?;
404 let known = known_total.or_else(|| exact.then_some(records));
405 (exact, known, req.where_.clone(), records)
406 };
407 let whole_set = index_plan
410 .as_ref()
411 .is_none_or(|plan| plan.known_total.is_none());
412 let order = if order_first && whole_set {
413 order::plan_order(
414 snap.as_ref(),
415 lmpg.tenant,
416 &order::PageShape {
417 labels: &req.labels,
418 order_by: &req.order_by,
419 skip,
420 limit,
421 matches: known,
422 candidates,
423 },
424 )?
425 } else {
426 None
427 };
428 Ok(PagePlan {
429 index_plan,
430 exact,
431 known,
432 predicate,
433 order,
434 })
435}
436
437#[derive(Clone, Debug, PartialEq, Eq)]
440pub struct OrderExplain {
441 pub source: &'static str,
443 pub reverse_walk: bool,
445 pub estimated_reads: u64,
447 pub predicate_first_reads: u64,
449}
450
451pub fn explain_order(
457 lmpg: &Lmpg,
458 req: &SearchRequest,
459 snap: &QuerySnapshot,
460 skip: usize,
461 limit: usize,
462) -> Result<Option<OrderExplain>, QueryError> {
463 let plan = plan_page(lmpg, req, &|| false, snap, (skip, limit), None, true)?;
464 Ok(plan.order.map(|order| OrderExplain {
465 source: order.source(),
466 reverse_walk: order.mirror(),
467 estimated_reads: order.order_first_reads,
468 predicate_first_reads: order.predicate_first_reads,
469 }))
470}
471
472pub(crate) struct Phase<'v> {
474 candidates: scan::Candidates<'v>,
475 sorted: bool,
479}
480
481pub(crate) struct CandidateSource<'v> {
483 phases: Vec<Phase<'v>>,
485 exact: bool,
488 known: Option<u64>,
490 predicate: Option<Expr>,
493 member_of: Option<planner::OwnerSet>,
496 plan: Option<order::OrderPlan>,
498}
499
500#[expect(
505 clippy::too_many_arguments,
506 reason = "the pass's inputs, each used once to choose its source"
507)]
508pub(crate) fn candidate_source<'v>(
509 lmpg: &Lmpg,
510 req: &SearchRequest,
511 is_cancelled: &dyn Fn() -> bool,
512 snap: &'v QuerySnapshot,
513 (skip, limit): (usize, usize),
514 known_total: Option<u64>,
515 order_first: bool,
516 working: &mut WorkingSet,
517) -> Result<CandidateSource<'v>, QueryError> {
518 let PagePlan {
519 index_plan,
520 exact,
521 known,
522 predicate,
523 order: plan,
524 } = plan_page(
525 lmpg,
526 req,
527 is_cancelled,
528 snap,
529 (skip, limit),
530 known_total,
531 order_first,
532 )?;
533 if let Some(plan) = &index_plan {
534 working.charge(
535 u64::try_from(plan.ords.serialized_size()).unwrap_or(u64::MAX),
536 true,
537 )?;
538 }
539 let in_order = |candidates| Phase {
540 candidates,
541 sorted: false,
542 };
543 let Some(plan) = plan else {
544 let candidates = match index_plan {
545 Some(plan) => scan::Candidates::from_set(plan.ords),
546 None => scan::Candidates::walk(snap, lmpg.tenant, &req.labels)?,
547 };
548 return Ok(CandidateSource {
549 phases: vec![Phase {
550 candidates,
551 sorted: !req.order_by.is_empty(),
552 }],
553 exact,
554 known,
555 predicate,
556 member_of: None,
557 plan: None,
558 });
559 };
560 let (phases, member_of) = match &plan.path {
561 order::OrderPath::StorageMirror => {
562 let candidates = match index_plan {
563 Some(plan) => scan::Candidates::from_set_descending(plan.ords),
564 None => scan::Candidates::walk_ordered(snap, lmpg.tenant, &req.labels, true)?,
565 };
566 (vec![in_order(candidates)], None)
567 }
568 order::OrderPath::Id {
569 walk_descending, ..
570 } => (
571 vec![in_order(scan::Candidates::by_id(
572 snap,
573 lmpg.tenant,
574 &req.labels,
575 *walk_descending,
576 )?)],
577 index_plan.map(|plan| plan.ords),
578 ),
579 order::OrderPath::Property {
580 descending,
581 pids,
582 holding,
583 scope,
584 missing,
585 index,
586 } => {
587 let phases = property_phases(
588 snap,
589 lmpg.tenant,
590 *descending,
591 (pids, holding, scope),
592 *missing,
593 index.as_ref(),
594 )?;
595 (phases, index_plan.map(|plan| plan.ords))
596 }
597 };
598 let mut phases = phases;
599 for phase in &mut phases {
600 phase.candidates.give_up_after(plan.predicate_first_reads);
602 }
603 Ok(CandidateSource {
604 phases,
605 exact,
606 known,
607 predicate,
608 member_of,
609 plan: Some(plan),
610 })
611}
612
613pub(crate) type OrderRow = (u64, Map<String, Json>);
615
616pub(crate) fn cut_ordered(
619 ordered: &mut Vec<OrderRow>,
620 bytes: &mut Vec<u64>,
621 req: &SearchRequest,
622 keep: usize,
623) -> u64 {
624 let mut rows: Vec<(OrderRow, u64)> = ordered.drain(..).zip(bytes.drain(..)).collect();
625 sort_ordered_rows(&mut rows, req);
629 let freed = rows.iter().skip(keep).map(|(_, b)| *b).sum();
630 rows.truncate(keep);
631 for (row, b) in rows {
632 ordered.push(row);
633 bytes.push(b);
634 }
635 freed
636}
637
638pub(crate) fn walk_total(
640 lmpg: &Lmpg,
641 snap: &QuerySnapshot,
642 labels: &[String],
643) -> Result<u64, QueryError> {
644 if labels.is_empty() {
645 return Ok(rushdb_lmpg::derived::tenant_usage(snap.as_ref(), lmpg.tenant)?.records);
646 }
647 label_total(snap.as_ref(), lmpg.tenant, labels)
648}
649
650pub(crate) fn label_total<R: rushdb_lmpg::reader::TryRead + ?Sized>(
652 snap: &R,
653 tenant: rushdb_types::ids::TenantId,
654 labels: &[String],
655) -> Result<u64, QueryError> {
656 let distinct: std::collections::BTreeSet<&String> = labels.iter().collect();
657 let mut total = 0u64;
658 for label in distinct {
659 total = total.saturating_add(rushdb_lmpg::derived::label_count(snap, tenant, label)?);
660 }
661 Ok(total)
662}
663
664fn property_phases<'v>(
668 snap: &'v QuerySnapshot,
669 tenant: rushdb_types::ids::TenantId,
670 descending: bool,
671 (pids, holding, scope): (&[u64], &std::collections::BTreeSet<String>, &[String]),
672 missing: u64,
673 index: Option<&order::IndexOrder>,
674) -> Result<Vec<Phase<'v>>, QueryError> {
675 let in_order = |candidates| Phase {
676 candidates,
677 sorted: false,
678 };
679 let missing = in_order(scan::Candidates::missing(
680 snap,
681 tenant,
682 scope,
683 holding.clone(),
684 pids.to_vec(),
685 missing,
686 )?);
687 let presence = |nullness: &[u8]| {
688 let mut prefixes = Vec::new();
689 for label in holding {
690 for &pid in pids {
691 for &nullness in nullness {
692 prefixes.push(rushdb_types::keys::presence_nullness_prefix(
693 tenant,
694 rushdb_types::ids::OwnerKind::Record,
695 label,
696 pid,
697 nullness,
698 ));
699 }
700 }
701 }
702 scan::Candidates::presence(snap, &prefixes)
703 };
704 let phases = match index {
706 None => {
707 let holders = Phase {
708 candidates: presence(&[
709 rushdb_types::keys::NULLNESS_VALUE,
710 rushdb_types::keys::NULLNESS_NULL,
711 ])?,
712 sorted: true,
713 };
714 if descending {
715 vec![holders, missing]
716 } else {
717 vec![missing, holders]
718 }
719 }
720 Some(index) => {
721 let nulls = in_order(presence(&[rushdb_types::keys::NULLNESS_NULL])?);
724 let values = in_order(scan::Candidates::index_order(
725 snap, tenant, &index.ids, descending,
726 )?);
727 let mut phases = if index.numeric {
728 vec![missing, nulls, values]
729 } else {
730 vec![missing, values, nulls]
731 };
732 if descending {
733 phases.reverse();
734 }
735 phases
736 }
737 };
738 Ok(phases)
739}