1use crate::{
7 db::{
8 DbSession, DynamicQuery, DynamicTypedEntityBinding, ExhaustiveQueryPageOutput,
9 ExhaustiveReadError, GroupedQueryOutput, LiveQueryPageOutput, MissingRowPolicy, QueryError,
10 ReadSetRevisionError, ReadSetRevisionProof, ScalarPageWork,
11 codec::{finalize_hash_sha256, new_hash_sha256_prefixed},
12 commit::{cursor_authentication_key, database_incarnation_id},
13 cursor::{
14 CursorBoundary, CursorBoundarySlot, CursorPlanError, ScalarOrderTermContract,
15 ScalarPageMode, ScalarPageToken, ScalarPageTokenAuthority, ScalarPageTokenProgress,
16 ScalarPageTokenWindow, decode_optional_cursor_token, encode_cursor,
17 },
18 data::{DecodedDataStoreKey, RawDataStoreKey},
19 executor::{
20 CoveringProjectionMetricsRecorder, PageWorkEnvelope,
21 ProjectionMaterializationMetricsRecorder, ScalarContinuationContext,
22 StructuralProjectionRequest, execute_structural_projection_page,
23 },
24 query::{
25 admission::{QueryAdmissionPolicy, QueryAdmissionSummary},
26 expr::{FilterExpr, OrderTerm as FluentOrderTerm},
27 intent::{IntentError, StructuralQuery},
28 },
29 session::AcceptedSchemaCatalogContext,
30 },
31 traits::CanisterKind,
32};
33use icydb_diagnostic_code::{
34 DiagnosticDecodeReason, DiagnosticExecutionBudgetResource, DiagnosticExecutionLane,
35 QueryReadAdmissionCode,
36};
37use sha2::Digest;
38#[cfg(test)]
39use std::cell::Cell;
40
41#[cfg(not(test))]
42const SCALAR_PAGE_OUTPUT_ROWS: usize = 1_024;
43#[cfg(test)]
44const SCALAR_PAGE_OUTPUT_ROWS: usize = 2;
45#[cfg(test)]
46const SCALAR_PAGE_KEY_ENTRIES: u64 = 4;
47
48#[cfg(test)]
49std::thread_local! {
50 static SCALAR_PAGE_RESULT_BYTES_LIMIT_OVERRIDE: Cell<Option<u64>> = const { Cell::new(None) };
51}
52
53#[cfg(test)]
54struct ScalarPageResultBytesLimitGuard(Option<u64>);
55
56#[cfg(test)]
57impl Drop for ScalarPageResultBytesLimitGuard {
58 fn drop(&mut self) {
59 SCALAR_PAGE_RESULT_BYTES_LIMIT_OVERRIDE.with(|limit| limit.set(self.0));
60 }
61}
62
63#[derive(Clone, Copy)]
64enum DynamicReadLane {
65 Public,
66 Trusted,
67}
68
69struct ScalarCursorContract {
70 signature: crate::db::cursor::ContinuationSignature,
71 authority: ScalarPageTokenAuthority,
72 window: ScalarPageTokenWindow,
73 order_terms: Vec<ScalarOrderTermContract>,
74}
75
76impl<C: CanisterKind> DbSession<C> {
77 fn may_select_exact_single_primary_key(
78 request: &DynamicQuery,
79 catalog: &AcceptedSchemaCatalogContext,
80 ) -> bool {
81 let [primary_key] = catalog.accepted_schema_info().primary_key_names() else {
82 return false;
83 };
84 matches!(
85 request.filter_expr(),
86 Some(FilterExpr::Eq { field, .. } | FilterExpr::In { field, .. })
87 if field.eq_ignore_ascii_case(primary_key)
88 )
89 }
90
91 fn exact_primary_key_candidate_bound(
92 prepared_plan: &crate::db::executor::SharedPreparedExecutionPlan,
93 ) -> Option<usize> {
94 let access = &prepared_plan.logical_plan().access;
95 if access.as_by_key_path().is_some() {
96 return Some(1);
97 }
98
99 access.as_by_keys_path().map(<[crate::value::Value]>::len)
100 }
101
102 fn structural_query_from_dynamic_request(
103 request: &DynamicQuery,
104 catalog: &AcceptedSchemaCatalogContext,
105 ) -> Result<StructuralQuery, QueryError> {
106 Self::structural_query_from_dynamic_request_with_page_limit(request, catalog, None, false)
107 }
108
109 fn structural_query_from_dynamic_request_with_page_limit(
110 request: &DynamicQuery,
111 catalog: &AcceptedSchemaCatalogContext,
112 page_limit: Option<u32>,
113 require_total_order: bool,
114 ) -> Result<StructuralQuery, QueryError> {
115 let schema = catalog.accepted_schema_info();
116 let mut query = StructuralQuery::new(MissingRowPolicy::Ignore);
117 if let Some(filter) = request.filter_expr() {
118 query = query.filter_for_schema(schema, filter.clone());
119 }
120 for order in request.order_terms() {
121 query = query.order_term(order.clone());
122 }
123 if require_total_order && request.order_terms().is_empty() {
124 for primary_key in schema.primary_key_names() {
125 query = query.order_term(FluentOrderTerm::asc(primary_key.clone()));
126 }
127 }
128 if !request.selected_fields().is_empty() {
129 query = query.select_fields(request.selected_fields().iter().cloned());
130 }
131 if let Some(limit) = page_limit.or_else(|| request.row_limit()) {
132 query = query.limit(limit);
133 }
134 for field in request.group_fields() {
135 query = query.group_by_with_schema(field, schema)?;
136 }
137 for aggregate in request.aggregates() {
138 query = query.aggregate(aggregate.clone());
139 }
140 if let Some((max_groups, max_group_bytes)) = request.grouped_execution_limits() {
141 if max_groups == 0 || max_group_bytes == 0 {
142 return Err(QueryReadAdmissionCode::GroupedQueryRequiresLimits.into());
143 }
144 query = query.grouped_limits(u64::from(max_groups), u64::from(max_group_bytes));
145 }
146
147 Ok(query)
148 }
149
150 fn scalar_page_cursor_error() -> QueryError {
151 QueryError::from_cursor_plan_error(CursorPlanError::invalid_continuation_cursor_payload(
152 DiagnosticDecodeReason::CursorTokenDecode,
153 ))
154 }
155
156 fn scalar_cursor_contract(
157 request: &DynamicQuery,
158 catalog: &AcceptedSchemaCatalogContext,
159 envelope: PageWorkEnvelope,
160 prepared_plan: &crate::db::executor::SharedPreparedExecutionPlan,
161 mode: ScalarPageMode,
162 proof: Option<&ReadSetRevisionProof>,
163 ) -> Result<ScalarCursorContract, QueryError> {
164 let mut signature = prepared_plan
165 .continuation_signature_for_runtime()
166 .map_err(QueryError::execute)?;
167 match (mode, proof) {
168 (ScalarPageMode::Live, None) => {}
169 (ScalarPageMode::Exhaustive, Some(proof)) => {
170 let mut hasher = new_hash_sha256_prefixed(b"icydb.exhaustive-cursor-proof.v1");
171 hasher.update(signature.into_bytes());
172 hasher.update(proof.signature_bytes());
173 signature = crate::db::cursor::ContinuationSignature::from_bytes(
174 finalize_hash_sha256(hasher),
175 );
176 }
177 _ => return Err(Self::scalar_page_cursor_error()),
178 }
179 let root_identity = catalog.runtime_root_identity();
180 let (root_fingerprint_method, root_fingerprint) = root_identity.fingerprint();
181 let authority = ScalarPageTokenAuthority::new(
182 database_incarnation_id()
183 .map_err(QueryError::execute)?
184 .to_bytes(),
185 root_identity.accepted_root_revision().get(),
186 root_fingerprint_method,
187 root_fingerprint,
188 catalog.fingerprint(),
189 prepared_plan.authority_ref().entity_tag(),
190 );
191 let window = ScalarPageTokenWindow::new(0, request.row_limit(), envelope.identity());
192 let canonical_order = prepared_plan
193 .logical_plan()
194 .scalar_plan()
195 .order
196 .as_ref()
197 .ok_or_else(Self::scalar_page_cursor_error)?;
198 let order_terms = canonical_order
199 .fields
200 .iter()
201 .map(|term| ScalarOrderTermContract::new(term.rendered_label(), term.direction()))
202 .collect::<Vec<_>>();
203
204 Ok(ScalarCursorContract {
205 signature,
206 authority,
207 window,
208 order_terms,
209 })
210 }
211
212 fn validate_scalar_page_token(
213 token: &ScalarPageToken,
214 mode: ScalarPageMode,
215 signature: crate::db::cursor::ContinuationSignature,
216 authority: ScalarPageTokenAuthority,
217 window: ScalarPageTokenWindow,
218 order_terms: &[ScalarOrderTermContract],
219 entity: &str,
220 ) -> Result<(), QueryError> {
221 if token.signature() != signature {
222 return Err(QueryError::from_cursor_plan_error(
223 CursorPlanError::continuation_cursor_signature_mismatch(
224 entity,
225 &signature,
226 &token.signature(),
227 ),
228 ));
229 }
230 if token.mode() != mode
231 || token.authority() != authority
232 || token.window() != window
233 || token.order_terms() != order_terms
234 {
235 return Err(Self::scalar_page_cursor_error());
236 }
237
238 Ok(())
239 }
240
241 fn physical_primary_key_boundary(
242 bytes: &[u8],
243 catalog: &AcceptedSchemaCatalogContext,
244 ) -> Result<CursorBoundary, QueryError> {
245 let raw = RawDataStoreKey::from_persisted_bytes(bytes.to_vec());
246 let key = DecodedDataStoreKey::try_from_raw(&raw)
247 .map_err(|_| Self::scalar_page_cursor_error())?;
248 if key.entity_tag() != catalog.accepted_entity_authority().entity_tag() {
249 return Err(Self::scalar_page_cursor_error());
250 }
251 let primary_key_arity = catalog.accepted_schema_info().primary_key_names().len();
252 let mut slots = Vec::with_capacity(primary_key_arity);
253 for component_index in 0..primary_key_arity {
254 slots.push(CursorBoundarySlot::Present(
255 key.primary_key_component_runtime_value(component_index)
256 .map_err(|_| Self::scalar_page_cursor_error())?,
257 ));
258 }
259
260 Ok(CursorBoundary { slots })
261 }
262
263 fn execute_dynamic_grouped_query_against_catalog(
264 &self,
265 request: &DynamicQuery,
266 lane: DynamicReadLane,
267 catalog: AcceptedSchemaCatalogContext,
268 ) -> Result<GroupedQueryOutput, QueryError> {
269 if !request.has_grouping() {
270 return Err(QueryError::intent(
271 IntentError::grouped_terminal_requires_grouped_query(),
272 ));
273 }
274 if request.grouped_execution_limits().is_none() {
275 return Err(QueryReadAdmissionCode::GroupedQueryRequiresLimits.into());
276 }
277 if !request.selected_fields().is_empty() {
278 return Err(QueryError::intent(
279 IntentError::grouped_output_defined_by_group_and_aggregates(),
280 ));
281 }
282 let query = Self::structural_query_from_dynamic_request(request, &catalog)?;
283 let public_admission = match lane {
284 DynamicReadLane::Public => Some(QueryAdmissionPolicy::default_bounded_read()),
285 DynamicReadLane::Trusted => None,
286 };
287
288 self.execute_structural_grouped_from_query(
289 &query,
290 &catalog,
291 public_admission.as_ref(),
292 request.continuation_cursor(),
293 )
294 }
295
296 #[expect(
297 clippy::too_many_lines,
298 reason = "live-page orchestration keeps planning, cursor validation, execution, and response proof in one auditable boundary"
299 )]
300 fn execute_scalar_page_against_catalog(
301 &self,
302 request: &DynamicQuery,
303 continuation: Option<&str>,
304 lane: DynamicReadLane,
305 catalog: AcceptedSchemaCatalogContext,
306 mode: ScalarPageMode,
307 supplied_proof: Option<&ReadSetRevisionProof>,
308 ) -> Result<(LiveQueryPageOutput, Option<ReadSetRevisionProof>), ExhaustiveReadError> {
309 if request.has_grouping()
310 || request.grouped_execution_limits().is_some()
311 || request.continuation_cursor().is_some()
312 {
313 return Err(
314 QueryError::intent(IntentError::scalar_terminal_requires_scalar_query()).into(),
315 );
316 }
317
318 let exhaustive_proof = match mode {
319 ScalarPageMode::Live => None,
320 ScalarPageMode::Exhaustive => {
321 if continuation.is_some() && supplied_proof.is_none() {
322 return Err(ReadSetRevisionError::ResumeProofRequired.into());
323 }
324 let proof = supplied_proof.cloned().map_or_else(
325 || self.capture_entity_read_set_revision_proof(catalog.identity().store_path()),
326 Ok,
327 )?;
328 Self::ensure_read_set_contains_store(&proof, catalog.identity().store_path())?;
329 self.verify_read_set_revision_proof(&proof)?;
330 Some(proof)
331 }
332 };
333
334 let envelope = match lane {
335 DynamicReadLane::Public => PageWorkEnvelope::public_scalar(),
336 DynamicReadLane::Trusted => PageWorkEnvelope::default_scalar(),
337 };
338 #[cfg(test)]
339 let envelope = SCALAR_PAGE_RESULT_BYTES_LIMIT_OVERRIDE.with(|limit| {
340 limit.get().map_or(envelope, |limit| {
341 envelope.with_limit_for_tests(DiagnosticExecutionBudgetResource::ResultBytes, limit)
342 })
343 });
344 #[cfg(test)]
345 let envelope = envelope.with_limit_for_tests(
346 DiagnosticExecutionBudgetResource::KeyIndexEntriesVisited,
347 SCALAR_PAGE_KEY_ENTRIES,
348 );
349 let page_row_limit = envelope
350 .limit(DiagnosticExecutionBudgetResource::ResultRows)
351 .and_then(|limit| usize::try_from(limit).ok())
352 .unwrap_or(SCALAR_PAGE_OUTPUT_ROWS)
353 .min(SCALAR_PAGE_OUTPUT_ROWS);
354 let decoded_token = decode_optional_cursor_token(continuation)
355 .map_err(QueryError::from_cursor_plan_error)?
356 .map(|bytes| {
357 ScalarPageToken::decode(
358 bytes.as_slice(),
359 &cursor_authentication_key().map_err(QueryError::execute)?,
360 )
361 .map_err(|error| {
362 QueryError::from_cursor_plan_error(CursorPlanError::from_token_wire_error(
363 error,
364 ))
365 })
366 })
367 .transpose()?;
368 let prior_rows_emitted = decoded_token
369 .as_ref()
370 .map_or(0, |token| token.progress().rows_emitted());
371 let remaining_limit = request
372 .row_limit()
373 .map(|limit| u64::from(limit).saturating_sub(prior_rows_emitted));
374 let page_output_limit = remaining_limit
375 .unwrap_or(page_row_limit as u64)
376 .min(page_row_limit as u64);
377 let page_output_limit = usize::try_from(page_output_limit).unwrap_or(page_row_limit);
378 let execution_limit = u32::try_from(page_row_limit).unwrap_or(u32::MAX);
379 let execution_lane = match lane {
380 DynamicReadLane::Public => DiagnosticExecutionLane::PublicRead,
381 DynamicReadLane::Trusted => DiagnosticExecutionLane::TrustedRead,
382 };
383 let exact_candidate =
384 decoded_token.is_none() && Self::may_select_exact_single_primary_key(request, &catalog);
385 let initial_plan = if exact_candidate {
386 let query = Self::structural_query_from_dynamic_request(request, &catalog)?;
387 Some(
388 self.structural_projection_prepared_plan_for_accepted_authority(
389 &query,
390 catalog.accepted_entity_authority(),
391 catalog.snapshot(),
392 execution_lane,
393 )?,
394 )
395 } else {
396 None
397 };
398 let initial_is_exact_exhaustion =
399 initial_plan.as_ref().is_some_and(|(prepared_plan, _, _)| {
400 Self::exact_primary_key_candidate_bound(prepared_plan)
401 .is_some_and(|bound| bound == 1 && bound <= page_output_limit)
402 });
403 let (prepared_plan, projection, _) = if initial_is_exact_exhaustion {
404 initial_plan.ok_or_else(Self::scalar_page_cursor_error)?
405 } else {
406 let query = Self::structural_query_from_dynamic_request_with_page_limit(
407 request,
408 &catalog,
409 Some(execution_limit),
410 true,
411 )?;
412 self.structural_projection_prepared_plan_for_accepted_authority(
413 &query,
414 catalog.accepted_entity_authority(),
415 catalog.snapshot(),
416 execution_lane,
417 )?
418 };
419 if matches!(lane, DynamicReadLane::Public) {
420 let policy = QueryAdmissionPolicy::default_bounded_read();
421 let summary = policy.evaluate(QueryAdmissionSummary::from_plan(
422 policy.lane(),
423 prepared_plan.logical_plan(),
424 ));
425 if let Some(rejection) = summary.rejection() {
426 return Err(QueryError::from(rejection.code()).into());
427 }
428 }
429
430 let exact_initial_exhaustion = initial_is_exact_exhaustion;
431 let cursor_contract = decoded_token
432 .as_ref()
433 .map(|token| {
434 let contract = Self::scalar_cursor_contract(
435 request,
436 &catalog,
437 envelope,
438 &prepared_plan,
439 mode,
440 exhaustive_proof.as_ref(),
441 )?;
442 Self::validate_scalar_page_token(
443 token,
444 mode,
445 contract.signature,
446 contract.authority,
447 contract.window,
448 contract.order_terms.as_slice(),
449 request.entity(),
450 )?;
451 Ok::<_, QueryError>(contract)
452 })
453 .transpose()?;
454 let deferred_cursor_plan =
455 (!exact_initial_exhaustion && decoded_token.is_none()).then(|| prepared_plan.clone());
456 let continuation_context = match decoded_token.as_ref() {
457 None => ScalarContinuationContext::initial(),
458 Some(token) if token.progress().unconsumed_lookahead().is_some() => {
459 return Err(Self::scalar_page_cursor_error().into());
460 }
461 Some(token) => {
462 let logical = token.progress().last_emitted_logical().cloned();
463 match token.progress().last_consumed_physical() {
464 Some(physical) => ScalarContinuationContext::resumed_with_primary_progress(
465 logical,
466 Self::physical_primary_key_boundary(physical, &catalog)?,
467 ),
468 None => logical.map_or_else(
469 ScalarContinuationContext::initial,
470 ScalarContinuationContext::resumed,
471 ),
472 }
473 }
474 };
475 if decoded_token.is_some() && !continuation_context.has_progress() {
476 return Err(Self::scalar_page_cursor_error().into());
477 }
478
479 let value_catalog = prepared_plan
480 .authority_ref()
481 .accepted_schema_info()
482 .map(crate::db::schema::SchemaInfo::value_catalog_handle)
483 .cloned()
484 .ok_or_else(QueryError::invariant)?;
485 let (columns, _fixed_scales) = projection.into_components();
486 let projection_request = StructuralProjectionRequest::new(
487 self.debug,
488 prepared_plan,
489 CoveringProjectionMetricsRecorder::none(),
490 ProjectionMaterializationMetricsRecorder::none(),
491 execution_lane,
492 )
493 .with_distinct_output_offset(usize::try_from(prior_rows_emitted).unwrap_or(usize::MAX))
494 .with_page_work_envelope(envelope);
495 let projection_request = if exact_initial_exhaustion {
496 projection_request
497 } else {
498 projection_request
499 .with_continuation(continuation_context)
500 .with_cursor_emission(page_output_limit)
501 };
502 let page = execute_structural_projection_page(&self.db, projection_request)
503 .map_err(QueryError::execute)?;
504 let row_count = page.rows.row_count();
505 let rows = page
506 .rows
507 .into_value_rows()
508 .into_iter()
509 .map(|row| {
510 row.iter()
511 .map(|value| {
512 crate::db::schema::output_value_from_runtime(
513 value_catalog.enum_catalog(),
514 value,
515 )
516 .map_err(|_| QueryError::invariant())
517 })
518 .collect::<Result<Vec<_>, _>>()
519 })
520 .collect::<Result<Vec<_>, _>>()?;
521 let rows_emitted = prior_rows_emitted.saturating_add(u64::from(row_count));
522 let total_limit_reached = request
523 .row_limit()
524 .is_some_and(|limit| rows_emitted >= u64::from(limit));
525 let continuation = if page.has_more && !total_limit_reached {
526 if page.last_emitted_logical.is_none() && page.last_consumed_physical.is_none() {
527 return Err(Self::scalar_page_cursor_error().into());
528 }
529 let cursor_contract = if let Some(contract) = cursor_contract {
530 contract
531 } else {
532 let prepared_plan = deferred_cursor_plan
533 .as_ref()
534 .ok_or_else(Self::scalar_page_cursor_error)?;
535 Self::scalar_cursor_contract(
536 request,
537 &catalog,
538 envelope,
539 prepared_plan,
540 mode,
541 exhaustive_proof.as_ref(),
542 )?
543 };
544 let token = ScalarPageToken::new(
545 mode,
546 cursor_contract.signature,
547 cursor_contract.authority,
548 cursor_contract.window,
549 cursor_contract.order_terms,
550 ScalarPageTokenProgress::new(
551 page.last_emitted_logical,
552 page.last_consumed_physical,
553 None,
554 decoded_token
555 .as_ref()
556 .map_or(0, |token| token.progress().matching_rows_skipped()),
557 rows_emitted,
558 ),
559 );
560 Some(encode_cursor(
561 token
562 .encode(&cursor_authentication_key().map_err(QueryError::execute)?)
563 .map_err(|error| {
564 QueryError::from_cursor_plan_error(CursorPlanError::from_token_wire_error(
565 error,
566 ))
567 })?
568 .as_slice(),
569 ))
570 } else {
571 None
572 };
573
574 if let Some(proof) = exhaustive_proof.as_ref() {
575 self.verify_read_set_revision_proof(proof)?;
576 }
577
578 Ok((
579 LiveQueryPageOutput {
580 entity: catalog.snapshot().entity_name().to_string(),
581 columns,
582 rows,
583 row_count,
584 continuation,
585 work: ScalarPageWork {
586 envelope_identity: envelope.identity(),
587 entries_visited: page.scanned_keys as u64,
588 result_rows: row_count,
589 },
590 },
591 exhaustive_proof,
592 ))
593 }
594
595 pub fn execute_public_live_page(
597 &self,
598 request: &DynamicQuery,
599 continuation: Option<&str>,
600 ) -> Result<LiveQueryPageOutput, QueryError> {
601 let catalog = self
602 .accepted_schema_catalog_context_for_entity_name(Some(request.entity()))
603 .map_err(QueryError::execute)?;
604 self.execute_scalar_page_against_catalog(
605 request,
606 continuation,
607 DynamicReadLane::Public,
608 catalog,
609 ScalarPageMode::Live,
610 None,
611 )
612 .map(|(page, _)| page)
613 .map_err(Self::live_page_error)
614 }
615
616 #[doc(hidden)]
619 pub fn execute_public_live_page_for_typed_binding(
620 &self,
621 binding: &DynamicTypedEntityBinding,
622 request: &DynamicQuery,
623 continuation: Option<&str>,
624 ) -> Result<Option<LiveQueryPageOutput>, QueryError> {
625 let Some(catalog) = self
626 .current_typed_entity_binding_catalog(binding)
627 .map_err(QueryError::execute)?
628 else {
629 return Ok(None);
630 };
631 self.execute_scalar_page_against_catalog(
632 request,
633 continuation,
634 DynamicReadLane::Public,
635 catalog,
636 ScalarPageMode::Live,
637 None,
638 )
639 .map(|(page, _)| Some(page))
640 .map_err(Self::live_page_error)
641 }
642
643 pub fn execute_public_dynamic_grouped_query(
645 &self,
646 request: &DynamicQuery,
647 ) -> Result<GroupedQueryOutput, QueryError> {
648 let catalog = self
649 .accepted_schema_catalog_context_for_entity_name(Some(request.entity()))
650 .map_err(QueryError::execute)?;
651 self.execute_dynamic_grouped_query_against_catalog(
652 request,
653 DynamicReadLane::Public,
654 catalog,
655 )
656 }
657
658 #[doc(hidden)]
661 pub fn execute_public_dynamic_grouped_query_for_typed_binding(
662 &self,
663 binding: &DynamicTypedEntityBinding,
664 request: &DynamicQuery,
665 ) -> Result<Option<GroupedQueryOutput>, QueryError> {
666 let Some(catalog) = self
667 .current_typed_entity_binding_catalog(binding)
668 .map_err(QueryError::execute)?
669 else {
670 return Ok(None);
671 };
672 self.execute_dynamic_grouped_query_against_catalog(
673 request,
674 DynamicReadLane::Public,
675 catalog,
676 )
677 .map(Some)
678 }
679
680 pub fn execute_trusted_dynamic_grouped_query(
685 &self,
686 request: &DynamicQuery,
687 ) -> Result<GroupedQueryOutput, QueryError> {
688 let catalog = self
689 .accepted_schema_catalog_context_for_entity_name(Some(request.entity()))
690 .map_err(QueryError::execute)?;
691 self.execute_dynamic_grouped_query_against_catalog(
692 request,
693 DynamicReadLane::Trusted,
694 catalog,
695 )
696 }
697
698 pub fn execute_trusted_live_page(
703 &self,
704 request: &DynamicQuery,
705 continuation: Option<&str>,
706 ) -> Result<LiveQueryPageOutput, QueryError> {
707 let catalog = self
708 .accepted_schema_catalog_context_for_entity_name(Some(request.entity()))
709 .map_err(QueryError::execute)?;
710 self.execute_scalar_page_against_catalog(
711 request,
712 continuation,
713 DynamicReadLane::Trusted,
714 catalog,
715 ScalarPageMode::Live,
716 None,
717 )
718 .map(|(page, _)| page)
719 .map_err(Self::live_page_error)
720 }
721
722 #[cfg(test)]
723 pub(in crate::db) fn execute_trusted_live_page_with_result_bytes_limit_for_tests(
724 &self,
725 request: &DynamicQuery,
726 continuation: Option<&str>,
727 result_bytes_limit: u64,
728 ) -> Result<LiveQueryPageOutput, QueryError> {
729 let previous = SCALAR_PAGE_RESULT_BYTES_LIMIT_OVERRIDE
730 .with(|limit| limit.replace(Some(result_bytes_limit)));
731 let _guard = ScalarPageResultBytesLimitGuard(previous);
732 self.execute_trusted_live_page(request, continuation)
733 }
734
735 pub fn execute_public_exhaustive_page(
737 &self,
738 request: &DynamicQuery,
739 continuation: Option<&str>,
740 proof: Option<&ReadSetRevisionProof>,
741 ) -> Result<ExhaustiveQueryPageOutput, ExhaustiveReadError> {
742 let catalog =
743 self.accepted_schema_catalog_context_for_entity_name(Some(request.entity()))?;
744 let (page, proof) = self.execute_scalar_page_against_catalog(
745 request,
746 continuation,
747 DynamicReadLane::Public,
748 catalog,
749 ScalarPageMode::Exhaustive,
750 proof,
751 )?;
752 let proof = proof.ok_or(ReadSetRevisionError::NonCanonical)?;
753 Ok(ExhaustiveQueryPageOutput::from_live_page(page, proof))
754 }
755
756 #[doc(hidden)]
758 pub fn execute_public_exhaustive_page_for_typed_binding(
759 &self,
760 binding: &DynamicTypedEntityBinding,
761 request: &DynamicQuery,
762 continuation: Option<&str>,
763 proof: Option<&ReadSetRevisionProof>,
764 ) -> Result<Option<ExhaustiveQueryPageOutput>, ExhaustiveReadError> {
765 let Some(catalog) = self.current_typed_entity_binding_catalog(binding)? else {
766 return Ok(None);
767 };
768 let (page, proof) = self.execute_scalar_page_against_catalog(
769 request,
770 continuation,
771 DynamicReadLane::Public,
772 catalog,
773 ScalarPageMode::Exhaustive,
774 proof,
775 )?;
776 let proof = proof.ok_or(ReadSetRevisionError::NonCanonical)?;
777 Ok(Some(ExhaustiveQueryPageOutput::from_live_page(page, proof)))
778 }
779
780 pub fn execute_trusted_exhaustive_page(
782 &self,
783 request: &DynamicQuery,
784 continuation: Option<&str>,
785 proof: Option<&ReadSetRevisionProof>,
786 ) -> Result<ExhaustiveQueryPageOutput, ExhaustiveReadError> {
787 let catalog =
788 self.accepted_schema_catalog_context_for_entity_name(Some(request.entity()))?;
789 let (page, proof) = self.execute_scalar_page_against_catalog(
790 request,
791 continuation,
792 DynamicReadLane::Trusted,
793 catalog,
794 ScalarPageMode::Exhaustive,
795 proof,
796 )?;
797 let proof = proof.ok_or(ReadSetRevisionError::NonCanonical)?;
798 Ok(ExhaustiveQueryPageOutput::from_live_page(page, proof))
799 }
800
801 fn live_page_error(error: ExhaustiveReadError) -> QueryError {
802 match error {
803 ExhaustiveReadError::Query(error) => error,
804 ExhaustiveReadError::Revision(_) => QueryError::invariant(),
805 }
806 }
807}