1use crate::drive::contract::moderation::types::ContractDocumentRemovalEntry;
97use crate::drive::contract::paths::contract_document_type_removals_path_vec;
98use crate::error::drive::DriveError;
99use crate::error::proof::ProofError;
100use crate::error::query::QuerySyntaxError;
101use crate::error::Error;
102use crate::query::drive_document_count_query::point_lookup_count_entries;
103use crate::query::index_only_synthesis::synthesize_index_only_document;
104#[cfg(feature = "server")]
105use crate::query::is_absent_path;
106#[cfg(feature = "server")]
107use crate::query::moderated_join::fetch_removals;
108use crate::query::moderated_join::{
109 decode_removals, pair_missing_with_removals, removals_path_query,
110};
111use crate::query::{
112 DriveDocumentCountQuery, DriveDocumentQuery, InternalClauses, OrderClause, SplitCountEntry,
113 WhereClause, WhereOperator,
114};
115use dpp::data_contract::accessors::v0::DataContractV0Getters;
116use dpp::data_contract::document_type::accessors::{DocumentTypeV0Getters, DocumentTypeV2Getters};
117use dpp::data_contract::document_type::{
118 DocumentPropertyReferenceTarget, DocumentPropertyType, DocumentReferenceDeclaration,
119 DocumentReferenceKind, DocumentTypeRef, Index,
120};
121use dpp::data_contract::DataContract;
122use dpp::document::serialization_traits::DocumentPlatformConversionMethodsV0;
123use dpp::document::{Document, DocumentV0Getters};
124use dpp::identifier::Identifier;
125use dpp::platform_value::btreemap_extensions::BTreeValueMapPathHelper;
126use dpp::platform_value::Value;
127use dpp::version::PlatformVersion;
128use grovedb::{Element, PathQuery};
129use std::collections::{BTreeMap, BTreeSet};
130
131pub const MAX_SUB_QUERIES: usize = 10;
136
137pub const MAX_BOUND_VALUES: usize = 100;
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144pub enum BindingSource {
145 Page,
147 SubQuery(usize),
150}
151
152#[derive(Debug, Clone, PartialEq, Eq)]
155pub struct SubQueryBinding {
156 pub source: BindingSource,
158 pub source_property: String,
162 pub field: String,
165}
166
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
169pub enum SubQueryKind {
170 Documents,
172 Count,
175}
176
177#[derive(Debug, Clone, PartialEq)]
179pub struct DriveSubQuery<'a> {
180 pub contract: &'a DataContract,
182 pub document_type: DocumentTypeRef<'a>,
184 pub kind: SubQueryKind,
186 pub where_clauses: Vec<WhereClause>,
189 pub order_by: Vec<OrderClause>,
196 pub limit: Option<u16>,
202 pub binding: Option<SubQueryBinding>,
204}
205
206#[derive(Debug, Clone, PartialEq)]
208pub enum SubQueryResult {
209 Documents(Vec<Document>),
212 Counts(Vec<SplitCountEntry>),
215}
216
217impl SubQueryResult {
218 pub fn documents(&self) -> &[Document] {
220 match self {
221 Self::Documents(documents) => documents,
222 Self::Counts(_) => &[],
223 }
224 }
225
226 pub fn counts(&self) -> &[SplitCountEntry] {
228 match self {
229 Self::Counts(entries) => entries,
230 Self::Documents(_) => &[],
231 }
232 }
233}
234
235#[derive(Debug, Default)]
237pub struct CompositeDocumentsResult {
238 pub page_documents: Vec<Document>,
240 pub sub_results: Vec<SubQueryResult>,
242 pub sub_result_missing_ids: Vec<Vec<Identifier>>,
250 pub sub_result_removals: Vec<Vec<ContractDocumentRemovalEntry>>,
257}
258
259type DerivedValues = Vec<Identifier>;
261
262pub(crate) type ProvedTrio = (Vec<Vec<u8>>, Vec<u8>, Option<Element>);
265
266pub(crate) type PresentTrio = (Vec<Vec<u8>>, Vec<u8>, Element);
268
269pub type CompositeProofPathQueries = (PathQuery, Vec<Option<PathQuery>>, Vec<PathQuery>);
274
275type RemovalEntriesByPath = BTreeMap<Vec<Vec<u8>>, Vec<(Vec<u8>, Element)>>;
278
279type SubResultAbsences = (Vec<Vec<Identifier>>, Vec<Vec<ContractDocumentRemovalEntry>>);
282
283#[derive(Debug, Clone, Copy, PartialEq, Eq)]
285enum Component {
286 Page,
287 Sub(usize),
288}
289
290fn unsupported(message: String) -> Error {
291 Error::Query(QuerySyntaxError::Unsupported(message))
292}
293
294fn corrupted_proof(message: String) -> Error {
295 Error::Proof(ProofError::CorruptedProof(message))
296}
297
298fn merge_error_to_shape_error(error: grovedb::Error) -> Error {
302 match error {
303 grovedb::Error::NotSupported(message) => unsupported(format!(
304 "the composite query's components cannot be merged into one proof: {}",
305 message
306 )),
307 other => Error::from(other),
308 }
309}
310
311fn document_bound_value(document: &Document, field: &str) -> Result<Option<Identifier>, Error> {
314 use dpp::document::property_names::{ID, OWNER_ID};
315 if field == ID {
316 return Ok(Some(document.id()));
317 }
318 if field == OWNER_ID {
319 return Ok(Some(document.owner_id()));
320 }
321 let Some(value) = document
322 .properties()
323 .get_optional_at_path(field)
324 .ok()
325 .flatten()
326 else {
327 return Ok(None);
328 };
329 value.to_identifier().map(Some).map_err(|_| {
330 Error::Drive(DriveError::CorruptedCodeExecution(
331 "a bound composite property must decode as an identifier: validate() only \
332 admits identifier-typed properties",
333 ))
334 })
335}
336
337fn sorted_values(values: &[Identifier]) -> Vec<Identifier> {
341 let mut sorted = values.to_vec();
342 sorted.sort();
343 sorted
344}
345
346fn document_reference_of(
351 property_type: &DocumentPropertyType,
352) -> Option<DocumentReferenceDeclaration<'_>> {
353 match property_type {
354 DocumentPropertyType::IdentifierWithReference(reference_target) => {
355 reference_target.as_document_reference()
356 }
357 _ => None,
358 }
359}
360
361impl<'a> DriveSubQuery<'a> {
362 fn bound_field(&self) -> Option<&str> {
363 self.binding.as_ref().map(|binding| binding.field.as_str())
364 }
365
366 fn is_by_id_join(&self) -> bool {
367 self.bound_field() == Some(dpp::document::property_names::ID)
368 }
369}
370
371impl<'a> DriveDocumentQuery<'a> {
372 pub fn validate_composite(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
381 if self.sub_queries.is_empty() {
382 return Err(unsupported(
383 "a composite query needs at least one sub-query; a page alone is a plain \
384 documents query"
385 .to_string(),
386 ));
387 }
388 if self.sub_queries.len() > MAX_SUB_QUERIES {
389 return Err(unsupported(format!(
390 "a composite query carries at most {} sub-queries, got {}",
391 MAX_SUB_QUERIES,
392 self.sub_queries.len(),
393 )));
394 }
395 let page_limit = match self.limit {
396 None => {
397 return Err(unsupported(
398 "composite queries require an explicit limit on the page: the page size \
399 bounds every derived sub-query"
400 .to_string(),
401 ));
402 }
403 Some(0) => {
404 return Err(unsupported(
405 "a composite page limit must be at least 1".to_string(),
406 ));
407 }
408 Some(limit) if limit as usize > MAX_BOUND_VALUES => {
409 return Err(unsupported(format!(
410 "a composite page limit of {} exceeds {}: a derived `IN` clause admits at \
411 most that many values",
412 limit, MAX_BOUND_VALUES,
413 )));
414 }
415 Some(limit) => limit,
416 };
417 if self.offset.is_some() {
418 return Err(unsupported(
419 "composite queries do not support a page offset; paginate with a range clause"
420 .to_string(),
421 ));
422 }
423 if self.start_at.is_some() {
424 return Err(unsupported(
425 "composite queries do not support a page cursor (startAt/startAfter); \
426 paginate with a range clause on the page's ordering property"
427 .to_string(),
428 ));
429 }
430 if self.page_is_by_ids() {
433 let ids = self.page_ids()?.len();
434 if (page_limit as usize) < ids {
435 return Err(unsupported(format!(
436 "a by-ids composite page addresses {} ids but its limit is {}: the ids \
437 bound the page, so the limit must cover them",
438 ids, page_limit,
439 )));
440 }
441 }
442 let direction = self.page_direction(platform_version)?;
445
446 for (index, sub_query) in self.sub_queries.iter().enumerate() {
447 self.validate_sub_query(index, sub_query, direction, platform_version)?;
448 }
449 self.validate_component_paths(platform_version)
450 }
451
452 fn validate_sub_query(
453 &self,
454 index: usize,
455 sub_query: &DriveSubQuery<'a>,
456 direction: bool,
457 platform_version: &PlatformVersion,
458 ) -> Result<(), Error> {
459 let label = |message: &str| unsupported(format!("sub-query {}: {}", index, message));
460
461 let Some(binding) = &sub_query.binding else {
462 if sub_query.kind == SubQueryKind::Count {
464 return Err(label(
465 "a count sub-query must be bound (`COUNT ... WHERE <field> IN <derived \
466 values> GROUP BY <field>`); unbound counts stay on the regular count \
467 surface",
468 ));
469 }
470 match sub_query.limit {
471 None => {
472 return Err(label(
473 "a sibling documents sub-query requires an explicit limit",
474 ));
475 }
476 Some(0) => {
477 return Err(label("a sibling's limit must be at least 1"));
478 }
479 Some(limit) if limit as usize > MAX_BOUND_VALUES => {
480 return Err(label(&format!(
481 "limit {} exceeds {}",
482 limit, MAX_BOUND_VALUES
483 )));
484 }
485 Some(_) => {}
486 }
487 self.sub_query_document_query_with_direction(
489 sub_query,
490 &[],
491 direction,
492 platform_version,
493 )?
494 .construct_path_query(None, platform_version)?;
495 return Ok(());
496 };
497
498 let (source_contract, source_type, source_is_index_only_query) = match binding.source {
500 BindingSource::Page => (
501 self.contract,
502 self.document_type,
503 self.document_type.index_only(),
504 ),
505 BindingSource::SubQuery(source_index) => {
506 if source_index >= index {
507 return Err(label("a binding may only reference an earlier sub-query"));
508 }
509 let source = &self.sub_queries[source_index];
510 if source.kind != SubQueryKind::Documents {
511 return Err(label("a binding must reference a documents sub-query"));
512 }
513 (
514 source.contract,
515 source.document_type,
516 source.document_type.index_only(),
517 )
518 }
519 };
520
521 let source_property_type: Option<&DocumentPropertyType> = {
524 use dpp::document::property_names::{ID, OWNER_ID};
525 if binding.source_property == ID || binding.source_property == OWNER_ID {
526 None
527 } else {
528 let Some(property) = source_type
529 .flattened_properties()
530 .get(binding.source_property.as_str())
531 else {
532 return Err(label(&format!(
533 "source property \"{}\" does not name a property of \"{}\"",
534 binding.source_property,
535 source_type.name(),
536 )));
537 };
538 if !matches!(
539 property.property_type,
540 DocumentPropertyType::Identifier
541 | DocumentPropertyType::IdentifierWithReference(_)
542 ) {
543 return Err(label(&format!(
544 "source property \"{}\" is not identifier-typed; composite bindings \
545 derive identifiers only",
546 binding.source_property,
547 )));
548 }
549 Some(&property.property_type)
550 }
551 };
552
553 if source_is_index_only_query {
556 let carries = |index: &dpp::data_contract::document_type::Index| {
557 index.terminal_contains(&binding.source_property)
558 || index
559 .properties
560 .iter()
561 .any(|property| property.name == binding.source_property)
562 };
563 let (carried, index_name) = match binding.source {
564 BindingSource::Page => {
565 let index = self.index_only_query_index(platform_version)?;
566 (carries(index), index.name.clone())
567 }
568 BindingSource::SubQuery(source_index) => {
569 let source = &self.sub_queries[source_index];
570 let shape = self.sub_query_document_query_with_direction(
571 source,
572 &[Identifier::default()],
573 direction,
574 platform_version,
575 )?;
576 let index = shape.index_only_query_index(platform_version)?;
577 (carries(index), index.name.clone())
578 }
579 };
580 if !carried {
581 return Err(label(&format!(
582 "the indexOnly source resolves to index \"{}\", which does not carry the \
583 source property \"{}\"",
584 index_name, binding.source_property,
585 )));
586 }
587 }
588
589 if sub_query
590 .where_clauses
591 .iter()
592 .any(|clause| clause.field == binding.field)
593 {
594 return Err(label(&format!(
595 "the fixed clauses may not name the bound field \"{}\"; its `IN` clause is \
596 derived",
597 binding.field,
598 )));
599 }
600
601 if !sub_query.is_by_id_join() && binding.field != dpp::document::property_names::OWNER_ID {
607 let Some(property) = sub_query
608 .document_type
609 .flattened_properties()
610 .get(binding.field.as_str())
611 else {
612 return Err(label(&format!(
613 "bound field \"{}\" does not name a property of \"{}\"",
614 binding.field,
615 sub_query.document_type.name(),
616 )));
617 };
618 if !matches!(
619 property.property_type,
620 DocumentPropertyType::Identifier | DocumentPropertyType::IdentifierWithReference(_)
621 ) {
622 return Err(label(&format!(
623 "bound field \"{}\" is not identifier-typed; composite bindings derive \
624 identifiers only",
625 binding.field,
626 )));
627 }
628 }
629
630 match sub_query.kind {
631 SubQueryKind::Documents if sub_query.is_by_id_join() => {
632 if sub_query.document_type.index_only() {
633 return Err(label(
634 "a by-id join cannot target an indexOnly type: there is no \
635 primary-key tree to fetch from",
636 ));
637 }
638 if sub_query.limit.is_some() {
639 return Err(label(
640 "a by-id join takes no limit: every derived id must resolve, so \
641 completeness is set equality, not a page",
642 ));
643 }
644 if !sub_query.order_by.is_empty() {
645 return Err(label(
646 "a by-id join takes no ordering: results follow the derived ids' \
647 first appearance",
648 ));
649 }
650 if let Some(DocumentPropertyType::IdentifierWithReference(
663 DocumentPropertyReferenceTarget::PermanentDocumentLookup { lookup, .. }
664 | DocumentPropertyReferenceTarget::DeletableDocumentLookup { lookup, .. },
665 )) = source_property_type
666 {
667 return Err(label(&format!(
668 "the source property's refersTo finds its document by findBy ({}), so \
669 its values are not document ids: a by-id join needs a reference whose \
670 value is the referenced document's $id",
671 lookup.find_by_names(),
672 )));
673 }
674 if let Some(DocumentPropertyType::IdentifierWithReference(
677 DocumentPropertyReferenceTarget::AnyOf(_)
678 | DocumentPropertyReferenceTarget::AllOf(_),
679 )) = source_property_type
680 {
681 return Err(label(
682 "the source property declares a refersTo anyOf or allOf expression: a \
683 by-id join needs a reference to one document type",
684 ));
685 }
686 match source_property_type.and_then(document_reference_of) {
687 Some(DocumentReferenceDeclaration {
688 contract_id,
689 document_type_name,
690 ..
691 }) => {
692 let referenced_contract =
693 contract_id.unwrap_or_else(|| source_contract.id());
694 if referenced_contract != sub_query.contract.id()
695 || document_type_name != sub_query.document_type.name()
696 {
697 return Err(label(&format!(
698 "the source property's refersTo targets \"{}\", not this \
699 sub-query's type \"{}\"",
700 document_type_name,
701 sub_query.document_type.name(),
702 )));
703 }
704 }
705 None => {
706 return Err(label(&format!(
707 "a by-id join needs a source property declaring `refersTo: \
708 permanentDocument`, `refersTo: moderatedDocument` or `refersTo: \
709 deletableDocument` (\"{}\" does not): the declaration names the \
710 document type the derived ids resolve in",
711 binding.source_property,
712 )));
713 }
714 }
715 }
716 SubQueryKind::Documents => {
717 let shape = self.sub_query_document_query_with_direction(
719 sub_query,
720 &[Identifier::default()],
721 direction,
722 platform_version,
723 )?;
724 shape.construct_path_query(None, platform_version)?;
725 if sub_query.document_type.index_only() {
726 let index = shape.index_only_query_index(platform_version)?;
729 let carried = index.terminal_contains(&binding.field)
730 || index
731 .properties
732 .iter()
733 .any(|property| property.name == binding.field);
734 if !carried {
735 return Err(label(&format!(
736 "the indexOnly lookup resolves to index \"{}\", which does not \
737 carry the bound field \"{}\"",
738 index.name, binding.field,
739 )));
740 }
741 }
742 let value_bounded =
749 self.lookup_is_value_bounded(sub_query, binding, &shape, platform_version)?;
750 match (value_bounded, sub_query.limit) {
751 (true, Some(_)) => {
752 return Err(label(
753 "a value-bounded lookup (a unique index, or an indexOnly terminal \
754 with every prefix fixed, yields at most one row per derived \
755 value) takes no limit",
756 ));
757 }
758 (false, Some(0)) => {
759 return Err(label("a lookup's limit must be at least 1"));
760 }
761 (false, None) => {
762 return Err(label(
763 "a documents lookup on a non-unique index requires an explicit \
764 limit: it bounds the walk under each derived value",
765 ));
766 }
767 (false, Some(limit)) if limit as usize > MAX_BOUND_VALUES => {
768 return Err(label(&format!(
769 "limit {} exceeds {}",
770 limit, MAX_BOUND_VALUES
771 )));
772 }
773 _ => {}
774 }
775 }
776 SubQueryKind::Count => {
777 if sub_query.limit.is_some() {
778 return Err(label("a count sub-query takes no limit"));
779 }
780 if !sub_query.order_by.is_empty() {
781 return Err(label("a count sub-query takes no ordering"));
782 }
783 if sub_query.is_by_id_join() {
784 return Err(label(
785 "a count sub-query counts by an indexed property, not by `$id`",
786 ));
787 }
788 self.sub_query_count_query(sub_query, &[Identifier::default()], platform_version)?
790 .point_lookup_count_path_query(platform_version)?;
791 }
792 }
793 Ok(())
794 }
795
796 fn lookup_is_value_bounded(
803 &self,
804 sub_query: &DriveSubQuery<'a>,
805 binding: &SubQueryBinding,
806 shape: &DriveDocumentQuery<'a>,
807 platform_version: &PlatformVersion,
808 ) -> Result<bool, Error> {
809 let fixed_equalities: BTreeSet<&str> = sub_query
810 .where_clauses
811 .iter()
812 .filter(|clause| clause.operator == WhereOperator::Equal)
813 .map(|clause| clause.field.as_str())
814 .collect();
815 if sub_query.document_type.index_only() {
816 let index = shape.index_only_query_index(platform_version)?;
817 let terminal_is_bound = index.single_terminal() == Some(binding.field.as_str());
820 let prefix_fixed = index
821 .properties
822 .iter()
823 .all(|property| fixed_equalities.contains(property.name.as_str()));
824 return Ok(terminal_is_bound && prefix_fixed);
825 }
826 let mut wanted: BTreeSet<&str> = fixed_equalities.clone();
827 wanted.insert(binding.field.as_str());
828 Ok(sub_query.document_type.indexes().values().any(|index| {
829 index.unique
830 && index.properties.len() == wanted.len()
831 && index
832 .properties
833 .iter()
834 .all(|property| wanted.contains(property.name.as_str()))
835 }))
836 }
837
838 fn page_is_by_ids(&self) -> bool {
840 self.internal_clauses.primary_key_in_clause.is_some()
841 || self.internal_clauses.primary_key_equal_clause.is_some()
842 }
843
844 fn budget_as_instance_cap(mut path_query: PathQuery) -> PathQuery {
857 if let Some(limit) = path_query.query.limit.take() {
858 path_query.query.query.limit = Some(limit);
859 }
860 path_query
861 }
862
863 pub fn page_path_query(&self, platform_version: &PlatformVersion) -> Result<PathQuery, Error> {
870 if self.page_is_by_ids() {
871 let mut unlimited = self.clone();
872 unlimited.limit = None;
873 let mut path_query = unlimited.construct_path_query(None, platform_version)?;
874 path_query.query.limit = None;
878 return Ok(path_query);
879 }
880 Ok(Self::budget_as_instance_cap(
881 self.construct_path_query(None, platform_version)?,
882 ))
883 }
884
885 fn validate_component_paths(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
904 let representative = [Identifier::default()];
905 let mut components: Vec<(Vec<Vec<u8>>, Component, bool)> = Vec::new();
906 let page = self.page_path_query(platform_version)?;
907 let direction = page.query.query.left_to_right;
908 components.push((page.path, Component::Page, page.query.query.limit.is_some()));
909 for (index, sub_query) in self.sub_queries.iter().enumerate() {
910 let path_query = self.sub_query_proof_path_query(
911 sub_query,
912 &representative,
913 direction,
914 platform_version,
915 )?;
916 components.push((
917 path_query.path,
918 Component::Sub(index),
919 path_query.query.query.limit.is_some(),
920 ));
921 }
922
923 let is_bound = |component: &Component| matches!(component, Component::Sub(index) if self.sub_queries[*index].binding.is_some());
924 for (path, component, limited) in &components {
925 if !*limited {
926 continue;
927 }
928 let lands_at_root = match component {
929 Component::Page => {
933 let (siblings, bound): (Vec<_>, Vec<_>) = components
934 .iter()
935 .skip(1)
936 .partition(|(_, other, _)| !is_bound(other));
937 bound.iter().any(|(other, _, _)| other.starts_with(path))
938 || (!siblings.is_empty()
939 && siblings.iter().all(|(other, _, _)| other.starts_with(path)))
940 }
941 Component::Sub(_) => components
945 .iter()
946 .filter(|(_, other, _)| other != component && !is_bound(other))
947 .all(|(other, _, _)| other.starts_with(path)),
948 };
949 if lands_at_root {
950 return Err(unsupported(format!(
951 "{} carries a limit and lands at the merged root of the composite proof \
952 (once the bound sub-queries that derive nothing drop out), where grovedb \
953 refuses a budget; give it a clause that narrows its path, or split it \
954 into a separate request",
955 match component {
956 Component::Page => "the page".to_string(),
957 Component::Sub(index) => format!("sub-query {}", index),
958 }
959 )));
960 }
961 }
962
963 let mut groups: BTreeMap<&Vec<Vec<u8>>, Vec<(Component, bool)>> = BTreeMap::new();
964 for (path, component, limited) in &components {
965 groups.entry(path).or_default().push((*component, *limited));
966 }
967 for members in groups.values() {
968 let documents_members: Vec<Component> = members
969 .iter()
970 .map(|(component, _)| *component)
971 .filter(|component| match component {
972 Component::Page => true,
973 Component::Sub(index) => {
974 self.sub_queries[*index].kind == SubQueryKind::Documents
975 }
976 })
977 .collect();
978 let has_count_member = members.iter().any(|(component, _)| {
979 matches!(component, Component::Sub(index) if self.sub_queries[*index].kind == SubQueryKind::Count)
980 });
981 if has_count_member && !documents_members.is_empty() {
991 return Err(unsupported(
992 "a count sub-query shares its index path with a documents component: \
993 the count reads the index's value trees themselves while the documents \
994 query descends past them, and one proof cannot serve both; count on \
995 another index, or split them into separate requests"
996 .to_string(),
997 ));
998 }
999 if documents_members.len() < 2 {
1000 continue;
1001 }
1002 let has_sibling = documents_members.iter().any(|component| {
1003 matches!(component, Component::Sub(index) if self.sub_queries[*index].binding.is_none())
1004 });
1005 let has_page = documents_members.contains(&Component::Page);
1006 let all_subs_are_joins = documents_members.iter().all(|component| match component {
1007 Component::Page => true,
1008 Component::Sub(index) => self.sub_queries[*index].is_by_id_join(),
1009 });
1010 if has_sibling || (has_page && !(self.page_is_by_ids() && all_subs_are_joins)) {
1011 return Err(unsupported(
1012 "two documents components of the composite query address the same index \
1013 path and cannot be told apart by their derived values (a sibling, or a \
1014 page that is not a by-ids fetch, shares a path with another component); \
1015 split them into separate requests"
1016 .to_string(),
1017 ));
1018 }
1019 if members.iter().any(|(_, limited)| *limited) {
1023 return Err(unsupported(
1024 "two documents components of the composite query address the same index \
1025 path and one of them carries a limit, which cannot be merged with the \
1026 other's selection; split them into separate requests"
1027 .to_string(),
1028 ));
1029 }
1030 }
1031 Ok(())
1032 }
1033
1034 pub fn derive_values(
1039 &self,
1040 binding: &SubQueryBinding,
1041 source_documents: &[Document],
1042 ) -> Result<DerivedValues, Error> {
1043 let mut seen: BTreeSet<Identifier> = BTreeSet::new();
1044 let mut values = Vec::new();
1045 for document in source_documents {
1046 if let Some(value) = document_bound_value(document, &binding.source_property)? {
1047 if seen.insert(value) {
1048 values.push(value);
1049 }
1050 }
1051 }
1052 if values.len() > MAX_BOUND_VALUES {
1053 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1057 "a composite binding source yielded more documents than the shapes allow",
1058 )));
1059 }
1060 Ok(values)
1061 }
1062
1063 pub fn sub_query_document_query(
1067 &self,
1068 sub_query: &DriveSubQuery<'a>,
1069 values: &[Identifier],
1070 platform_version: &PlatformVersion,
1071 ) -> Result<DriveDocumentQuery<'a>, Error> {
1072 let direction = self.page_direction(platform_version)?;
1073 self.sub_query_document_query_with_direction(sub_query, values, direction, platform_version)
1074 }
1075
1076 pub(crate) fn sub_query_document_query_with_direction(
1080 &self,
1081 sub_query: &DriveSubQuery<'a>,
1082 values: &[Identifier],
1083 direction: bool,
1084 platform_version: &PlatformVersion,
1085 ) -> Result<DriveDocumentQuery<'a>, Error> {
1086 let ids = sorted_values(values);
1087 let in_value = || {
1088 Value::Array(
1089 ids.iter()
1090 .map(|id| Value::Identifier(id.to_buffer()))
1091 .collect(),
1092 )
1093 };
1094
1095 if sub_query.is_by_id_join() {
1096 if !sub_query.where_clauses.is_empty() {
1097 return Err(unsupported(
1098 "a by-id join takes no fixed clauses: every derived id must resolve"
1099 .to_string(),
1100 ));
1101 }
1102 return Ok(DriveDocumentQuery {
1103 contract: sub_query.contract,
1104 document_type: sub_query.document_type,
1105 internal_clauses: InternalClauses {
1106 primary_key_in_clause: Some(WhereClause {
1107 field: dpp::document::property_names::ID.to_string(),
1108 operator: WhereOperator::In,
1109 value: in_value(),
1110 }),
1111 primary_key_equal_clause: None,
1112 in_clauses: Vec::new(),
1113 range_clause: None,
1114 equal_clauses: Default::default(),
1115 },
1116 offset: None,
1117 limit: None,
1118 order_by: Default::default(),
1119 start_at: None,
1120 start_at_included: false,
1121 block_time_ms: None,
1122 resolved_time_ranges: Vec::new(),
1123 sub_queries: Vec::new(),
1124 });
1125 }
1126
1127 let mut clauses = sub_query.where_clauses.clone();
1128 let mut order_by: indexmap::IndexMap<String, OrderClause> = sub_query
1129 .order_by
1130 .iter()
1131 .map(|clause| (clause.field.clone(), clause.clone()))
1132 .collect();
1133 if let Some(binding) = &sub_query.binding {
1134 clauses.push(WhereClause {
1135 field: binding.field.clone(),
1136 operator: WhereOperator::In,
1137 value: in_value(),
1138 });
1139 if !order_by.contains_key(&binding.field) {
1148 order_by.insert(
1149 binding.field.clone(),
1150 OrderClause {
1151 field: binding.field.clone(),
1152 ascending: direction,
1153 },
1154 );
1155 }
1156 }
1157 Ok(DriveDocumentQuery {
1158 contract: sub_query.contract,
1159 document_type: sub_query.document_type,
1160 internal_clauses: InternalClauses::extract_from_clauses(clauses, platform_version)?,
1161 offset: None,
1162 limit: sub_query.limit,
1163 order_by,
1164 start_at: None,
1165 start_at_included: false,
1166 block_time_ms: None,
1167 resolved_time_ranges: Vec::new(),
1168 sub_queries: Vec::new(),
1169 })
1170 }
1171
1172 pub fn sub_query_count_query<'b>(
1176 &'b self,
1177 sub_query: &'b DriveSubQuery<'a>,
1178 values: &[Identifier],
1179 _platform_version: &PlatformVersion,
1180 ) -> Result<DriveDocumentCountQuery<'b>, Error> {
1181 let Some(binding) = &sub_query.binding else {
1182 return Err(unsupported("a count sub-query must be bound".to_string()));
1183 };
1184 let mut where_clauses = sub_query.where_clauses.clone();
1185 where_clauses.push(WhereClause {
1186 field: binding.field.clone(),
1187 operator: WhereOperator::In,
1188 value: Value::Array(
1189 sorted_values(values)
1190 .into_iter()
1191 .map(|id| Value::Identifier(id.to_buffer()))
1192 .collect(),
1193 ),
1194 });
1195 let index = DriveDocumentCountQuery::find_countable_index_for_where_clauses(
1196 sub_query.document_type.indexes(),
1197 &where_clauses,
1198 &[],
1199 )
1200 .ok_or_else(|| {
1201 unsupported(format!(
1202 "count sub-query on \"{}\" needs a `countable: true` (or summableOffCountIndex) index covering its fixed \
1203 clauses and the bound field \"{}\"",
1204 sub_query.document_type.name(),
1205 binding.field,
1206 ))
1207 })?;
1208 Ok(DriveDocumentCountQuery {
1209 document_type: sub_query.document_type,
1210 contract_id: sub_query.contract.id().to_buffer(),
1211 document_type_name: sub_query.document_type.name().to_string(),
1212 index,
1213 where_clauses,
1214 })
1215 }
1216
1217 pub fn sub_query_path_query(
1219 &self,
1220 sub_query: &DriveSubQuery<'a>,
1221 values: &[Identifier],
1222 platform_version: &PlatformVersion,
1223 ) -> Result<PathQuery, Error> {
1224 let direction = self.page_direction(platform_version)?;
1225 self.sub_query_path_query_with_direction(sub_query, values, direction, platform_version)
1226 }
1227
1228 fn sub_query_path_query_with_direction(
1229 &self,
1230 sub_query: &DriveSubQuery<'a>,
1231 values: &[Identifier],
1232 direction: bool,
1233 platform_version: &PlatformVersion,
1234 ) -> Result<PathQuery, Error> {
1235 let path_query = match sub_query.kind {
1236 SubQueryKind::Documents => self
1237 .sub_query_document_query_with_direction(
1238 sub_query,
1239 values,
1240 direction,
1241 platform_version,
1242 )?
1243 .construct_path_query(None, platform_version)?,
1244 SubQueryKind::Count => self
1245 .sub_query_count_query(sub_query, values, platform_version)?
1246 .point_lookup_count_path_query(platform_version)?,
1247 };
1248 Ok(Self::budget_as_instance_cap(path_query))
1249 }
1250
1251 fn page_direction(&self, platform_version: &PlatformVersion) -> Result<bool, Error> {
1254 Ok(self
1255 .page_path_query(platform_version)?
1256 .query
1257 .query
1258 .left_to_right)
1259 }
1260
1261 pub(crate) fn sub_query_proof_path_query(
1266 &self,
1267 sub_query: &DriveSubQuery<'a>,
1268 values: &[Identifier],
1269 direction: bool,
1270 platform_version: &PlatformVersion,
1271 ) -> Result<PathQuery, Error> {
1272 let mut path_query = self.sub_query_path_query_with_direction(
1273 sub_query,
1274 values,
1275 direction,
1276 platform_version,
1277 )?;
1278 if sub_query.kind == SubQueryKind::Documents
1279 && !sub_query.is_by_id_join()
1280 && path_query.query.query.left_to_right != direction
1281 {
1282 return Err(unsupported(if sub_query.binding.is_none() {
1283 "a sibling sub-query's ordering must match the page's direction; order it \
1284 explicitly by its index property, in the page's direction"
1285 .to_string()
1286 } else {
1287 "a documents sub-query's outer ordering must match the page's direction; \
1288 changing it for the merged proof would change its result"
1289 .to_string()
1290 }));
1291 }
1292 path_query.query.query.left_to_right = direction;
1295 Ok(path_query)
1296 }
1297
1298 pub fn proof_path_queries(
1309 &self,
1310 derived: &[DerivedValues],
1311 platform_version: &PlatformVersion,
1312 ) -> Result<CompositeProofPathQueries, Error> {
1313 if derived.len() != self.sub_queries.len() {
1314 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1315 "one derived value list per sub-query",
1316 )));
1317 }
1318 let page = self.page_path_query(platform_version)?;
1319 let direction = page.query.query.left_to_right;
1320 let mut sub_path_queries = Vec::with_capacity(self.sub_queries.len());
1321 for (sub_query, values) in self.sub_queries.iter().zip(derived) {
1322 if sub_query.binding.is_some() && values.is_empty() {
1323 sub_path_queries.push(None);
1324 continue;
1325 }
1326 let path_query =
1327 self.sub_query_proof_path_query(sub_query, values, direction, platform_version)?;
1328 sub_path_queries.push(Some(path_query));
1329 }
1330 let mut count_terminal_paths = BTreeSet::new();
1335 for (sub_query, path_query) in self.sub_queries.iter().zip(&sub_path_queries) {
1336 if sub_query.kind != SubQueryKind::Count {
1337 continue;
1338 }
1339 if let Some(path_query) = path_query {
1340 for (mut path, key) in path_query
1341 .terminal_keys(MAX_BOUND_VALUES, &platform_version.drive.grove_version)?
1342 {
1343 path.push(key);
1344 count_terminal_paths.insert(path);
1345 }
1346 }
1347 }
1348 for terminal_path in count_terminal_paths {
1349 for component in std::iter::once(&page).chain(sub_path_queries.iter().flatten()) {
1350 if !terminal_path.starts_with(&component.path)
1354 && !component.path.starts_with(&terminal_path)
1355 {
1356 continue;
1357 }
1358 if Self::path_query_descends_through(component, &terminal_path, platform_version)? {
1359 return Err(unsupported(
1360 "a count sub-query selects a tree another component descends through; \
1361 split them into separate requests"
1362 .to_string(),
1363 ));
1364 }
1365 }
1366 }
1367 let documents: Vec<&PathQuery> = std::iter::once(&page)
1374 .chain(
1375 sub_path_queries
1376 .iter()
1377 .zip(&self.sub_queries)
1378 .filter(|(_, sub_query)| sub_query.kind == SubQueryKind::Documents)
1379 .filter_map(|(path_query, _)| path_query.as_ref()),
1380 )
1381 .collect();
1382 for deeper in &documents {
1383 for shallower in &documents {
1384 if deeper.path.len() <= shallower.path.len()
1385 || !deeper.path.starts_with(&shallower.path)
1386 {
1387 continue;
1388 }
1389 if Self::path_query_descends_through(shallower, &deeper.path, platform_version)? {
1390 return Err(unsupported(
1391 "a documents sub-query walks through another documents component's \
1392 subtree, so their rows could not be told apart; split them into \
1393 separate requests"
1394 .to_string(),
1395 ));
1396 }
1397 }
1398 }
1399 let removal_path_queries = self.removal_path_queries(derived, direction);
1403 Ok((page, sub_path_queries, removal_path_queries))
1404 }
1405
1406 fn path_query_descends_through(
1410 query: &PathQuery,
1411 terminal_path: &[Vec<u8>],
1412 platform_version: &PlatformVersion,
1413 ) -> Result<bool, Error> {
1414 let mut prefix = Vec::with_capacity(terminal_path.len());
1415 for key in terminal_path {
1416 let Some(selection) =
1417 query.query_items_at_path(&prefix, &platform_version.drive.grove_version)?
1418 else {
1419 return Ok(false);
1420 };
1421 if !selection.items.iter().any(|item| item.contains(key))
1422 || !selection.has_subquery_or_matching_in_path_on_key(key)
1423 {
1424 return Ok(false);
1425 }
1426 prefix.push(key.as_slice());
1427 }
1428 Ok(true)
1429 }
1430
1431 pub fn merged_path_query(
1437 page: &PathQuery,
1438 sub_path_queries: &[Option<PathQuery>],
1439 removal_path_queries: &[PathQuery],
1440 platform_version: &PlatformVersion,
1441 ) -> Result<PathQuery, Error> {
1442 let mut components: Vec<&PathQuery> = vec![page];
1443 components.extend(sub_path_queries.iter().flatten());
1444 components.extend(removal_path_queries);
1445 if components.len() == 1 {
1446 return Ok(page.clone());
1447 }
1448 PathQuery::merge(components, &platform_version.drive.grove_version)
1449 .map_err(merge_error_to_shape_error)
1450 }
1451
1452 pub(crate) fn decode_document_trios(
1456 query: &DriveDocumentQuery<'a>,
1457 trios: Vec<PresentTrio>,
1458 platform_version: &PlatformVersion,
1459 ) -> Result<Vec<Document>, Error> {
1460 if query.document_type.index_only() {
1461 let index = query.index_only_query_index(platform_version)?;
1462 return trios
1463 .into_iter()
1464 .map(|(path, key, element)| {
1465 synthesize_index_only_document(
1466 query.contract.id(),
1467 query.document_type,
1468 index,
1469 &path,
1470 &key,
1471 Some(&element),
1472 )
1473 })
1474 .collect();
1475 }
1476 trios
1477 .into_iter()
1478 .map(|(_, _, element)| {
1479 let serialized = element.into_item_bytes().map_err(Error::from)?;
1480 Document::from_bytes(serialized.as_slice(), query.document_type, platform_version)
1481 .map_err(|e| Error::Protocol(Box::new(e)))
1482 })
1483 .collect()
1484 }
1485
1486 pub(crate) fn decode_sub_query_document_trios(
1490 &self,
1491 sub_query: &DriveSubQuery<'a>,
1492 values: &[Identifier],
1493 direction: bool,
1494 trios: Vec<PresentTrio>,
1495 platform_version: &PlatformVersion,
1496 ) -> Result<Vec<Document>, Error> {
1497 let query = self.sub_query_document_query_with_direction(
1498 sub_query,
1499 values,
1500 direction,
1501 platform_version,
1502 )?;
1503 let documents = Self::decode_document_trios(&query, trios, platform_version)?;
1504 self.assemble_documents(sub_query, values, &documents)
1505 }
1506
1507 fn decode_count_trios(
1513 index: &Index,
1514 base_path_len: usize,
1515 trios: Vec<PresentTrio>,
1516 ) -> Vec<SplitCountEntry> {
1517 let mut entries = point_lookup_count_entries(
1519 index,
1520 base_path_len,
1521 true,
1522 trios
1523 .into_iter()
1524 .map(|(path, key, element)| (path, key, Some(element))),
1525 );
1526 entries.sort_by(|a, b| a.key.cmp(&b.key));
1529 entries
1530 }
1531
1532 fn sub_result_absences(
1541 &self,
1542 derived: &[DerivedValues],
1543 sub_results: &[SubQueryResult],
1544 removals: &BTreeMap<Vec<Vec<u8>>, BTreeMap<Identifier, ContractDocumentRemovalEntry>>,
1545 ) -> Result<SubResultAbsences, Error> {
1546 let none = BTreeMap::new();
1547 let mut missing_ids = Vec::with_capacity(self.sub_queries.len());
1548 let mut sub_result_removals = Vec::with_capacity(self.sub_queries.len());
1549 for ((sub_query, values), result) in self.sub_queries.iter().zip(derived).zip(sub_results) {
1550 let kind = self.sub_query_join_kind(sub_query);
1551 let missing: Vec<Identifier> = match kind {
1552 Some(DocumentReferenceKind::Deletable | DocumentReferenceKind::Moderated) => {
1553 let present: BTreeSet<Identifier> = result
1554 .documents()
1555 .iter()
1556 .map(|document| document.id())
1557 .collect();
1558 values
1559 .iter()
1560 .filter(|value| !present.contains(*value))
1561 .copied()
1562 .collect()
1563 }
1564 _ => Vec::new(),
1565 };
1566 if kind == Some(DocumentReferenceKind::Moderated) {
1567 let path = contract_document_type_removals_path_vec(
1568 sub_query.contract.id().as_slice(),
1569 sub_query.document_type.name(),
1570 );
1571 sub_result_removals.push(pair_missing_with_removals(
1572 &missing,
1573 removals.get(&path).unwrap_or(&none),
1574 )?);
1575 missing_ids.push(Vec::new());
1576 } else {
1577 sub_result_removals.push(Vec::new());
1578 missing_ids.push(missing);
1579 }
1580 }
1581 Ok((missing_ids, sub_result_removals))
1582 }
1583
1584 fn by_id_join_kind(&self, binding: &SubQueryBinding) -> DocumentReferenceKind {
1590 let source_type = match binding.source {
1591 BindingSource::Page => Some(self.document_type),
1592 BindingSource::SubQuery(source_index) => self
1593 .sub_queries
1594 .get(source_index)
1595 .map(|source| source.document_type),
1596 };
1597 let Some(source_type) = source_type else {
1598 return DocumentReferenceKind::Permanent;
1599 };
1600 source_type
1601 .flattened_properties()
1602 .get(binding.source_property.as_str())
1603 .and_then(|property| document_reference_of(&property.property_type))
1604 .map_or(DocumentReferenceKind::Permanent, |declaration| {
1605 declaration.kind
1606 })
1607 }
1608
1609 fn sub_query_join_kind(&self, sub_query: &DriveSubQuery<'a>) -> Option<DocumentReferenceKind> {
1612 match &sub_query.binding {
1613 Some(binding) if sub_query.is_by_id_join() => Some(self.by_id_join_kind(binding)),
1614 _ => None,
1615 }
1616 }
1617
1618 fn removal_path_queries(&self, derived: &[DerivedValues], direction: bool) -> Vec<PathQuery> {
1626 let mut ids_by_type: BTreeMap<(Identifier, &str), BTreeSet<Identifier>> = BTreeMap::new();
1627 for (sub_query, values) in self.sub_queries.iter().zip(derived) {
1628 if values.is_empty()
1629 || self.sub_query_join_kind(sub_query) != Some(DocumentReferenceKind::Moderated)
1630 {
1631 continue;
1632 }
1633 ids_by_type
1634 .entry((
1635 sub_query.contract.id(),
1636 sub_query.document_type.name().as_str(),
1637 ))
1638 .or_default()
1639 .extend(values.iter().copied());
1640 }
1641 ids_by_type
1642 .into_iter()
1643 .map(|((contract_id, document_type_name), ids)| {
1644 let ids: Vec<Identifier> = ids.into_iter().collect();
1645 removals_path_query(contract_id, document_type_name, &ids, direction)
1646 })
1647 .collect()
1648 }
1649
1650 fn assemble_documents(
1662 &self,
1663 sub_query: &DriveSubQuery<'a>,
1664 values: &[Identifier],
1665 documents: &[Document],
1666 ) -> Result<Vec<Document>, Error> {
1667 let Some(binding) = &sub_query.binding else {
1668 return Ok(documents.to_vec());
1669 };
1670 let admitted: BTreeSet<Identifier> = values.iter().copied().collect();
1671 if sub_query.is_by_id_join() {
1672 let mut by_id: BTreeMap<Identifier, &Document> = BTreeMap::new();
1673 for document in documents {
1674 let id = document.id();
1675 if !admitted.contains(&id) {
1676 continue;
1678 }
1679 if by_id.insert(id, document).is_some() {
1680 return Err(corrupted_proof(format!(
1681 "composite join results carry document {} twice",
1682 id
1683 )));
1684 }
1685 }
1686 let target_is_permanent = self.by_id_join_kind(binding).is_permanent();
1687 let mut ordered = Vec::with_capacity(values.len());
1688 for value in values {
1689 match by_id.remove(value) {
1690 Some(document) => ordered.push(document.clone()),
1691 None if target_is_permanent => {
1692 return Err(corrupted_proof(format!(
1693 "composite join results are missing referenced document {}: a \
1694 permanentDocument reference cannot dangle, so the proof does \
1695 not cover the derived query",
1696 value
1697 )));
1698 }
1699 None => {}
1703 }
1704 }
1705 return Ok(ordered);
1706 }
1707 let mut mine = Vec::new();
1708 for document in documents {
1709 match document_bound_value(document, &binding.field)? {
1710 Some(value) if admitted.contains(&value) => mine.push(document.clone()),
1711 _ => {}
1712 }
1713 }
1714 Ok(mine)
1715 }
1716
1717 fn assemble_counts(
1720 values: &[Identifier],
1721 entries: Vec<SplitCountEntry>,
1722 ) -> Result<Vec<SplitCountEntry>, Error> {
1723 let admitted: BTreeSet<Identifier> = values.iter().copied().collect();
1724 let mut mine = Vec::with_capacity(entries.len());
1725 for entry in entries {
1726 let Ok(value) = Identifier::from_bytes(&entry.key) else {
1727 return Err(corrupted_proof(
1728 "a composite count entry is keyed by something other than an identifier"
1729 .to_string(),
1730 ));
1731 };
1732 if admitted.contains(&value) {
1733 mine.push(entry);
1734 }
1735 }
1736 Ok(mine)
1737 }
1738
1739 #[allow(clippy::too_many_arguments)]
1748 pub(crate) fn assemble_from_trios(
1749 &self,
1750 derived: &[DerivedValues],
1751 page_path_query: &PathQuery,
1752 sub_path_queries: &[Option<PathQuery>],
1753 removal_path_queries: &[PathQuery],
1754 trios: Vec<ProvedTrio>,
1755 sum_bearing_items_are_documents: bool,
1756 platform_version: &PlatformVersion,
1757 ) -> Result<CompositeDocumentsResult, Error> {
1758 let direction = page_path_query.query.query.left_to_right;
1763 let mut groups: Vec<(Vec<Vec<u8>>, Vec<Component>)> = Vec::new();
1764 let mut count_members_by_position: BTreeMap<_, Vec<usize>> = BTreeMap::new();
1765 let mut register = |path: &Vec<Vec<u8>>, component: Component| {
1766 if let Some((_, members)) = groups.iter_mut().find(|(p, _)| p == path) {
1767 members.push(component);
1768 } else {
1769 groups.push((path.clone(), vec![component]));
1770 }
1771 };
1772 register(&page_path_query.path, Component::Page);
1773 for (index, path_query) in sub_path_queries.iter().enumerate() {
1774 if let Some(path_query) = path_query {
1775 if self.sub_queries[index].kind == SubQueryKind::Count {
1776 for position in path_query
1777 .terminal_keys(MAX_BOUND_VALUES, &platform_version.drive.grove_version)?
1778 {
1779 count_members_by_position
1780 .entry(position)
1781 .or_default()
1782 .push(index);
1783 }
1784 } else {
1785 register(&path_query.path, Component::Sub(index));
1786 }
1787 }
1788 }
1789
1790 let mut trios_by_group: Vec<Vec<PresentTrio>> = vec![Vec::new(); groups.len()];
1793 let mut count_trios_by_sub: Vec<Vec<PresentTrio>> =
1794 vec![Vec::new(); self.sub_queries.len()];
1795 let mut removal_entries_by_path: RemovalEntriesByPath = removal_path_queries
1798 .iter()
1799 .map(|path_query| (path_query.path.clone(), Vec::new()))
1800 .collect();
1801 for (path, key, element) in trios {
1802 let Some(element) = element else {
1803 continue;
1804 };
1805 if let Some(entries) = removal_entries_by_path.get_mut(&path) {
1806 entries.push((key, element));
1807 continue;
1808 }
1809 let is_document = if sum_bearing_items_are_documents {
1815 element.has_basic_item()
1816 } else {
1817 matches!(element, Element::Item(..))
1818 };
1819 if !is_document {
1820 let position = (path, key);
1821 let members = count_members_by_position.get(&position).ok_or_else(|| {
1822 corrupted_proof(
1823 "the composite proof carries a count at a position no component \
1824 selected"
1825 .to_string(),
1826 )
1827 })?;
1828 let (last, others) = members.split_last().ok_or_else(|| {
1830 Error::Drive(DriveError::CorruptedCodeExecution(
1831 "a registered count position has at least one member",
1832 ))
1833 })?;
1834 for index in others {
1835 count_trios_by_sub[*index].push((
1836 position.0.clone(),
1837 position.1.clone(),
1838 element.clone(),
1839 ));
1840 }
1841 count_trios_by_sub[*last].push((position.0, position.1, element));
1842 continue;
1843 }
1844 let best = groups
1845 .iter()
1846 .enumerate()
1847 .filter(|(_, (base, _))| path.starts_with(base))
1848 .max_by_key(|(_, (base, _))| base.len())
1849 .map(|(index, _)| index)
1850 .ok_or_else(|| {
1851 corrupted_proof(
1852 "the composite proof proved an entry outside every component's \
1853 subtree"
1854 .to_string(),
1855 )
1856 })?;
1857 trios_by_group[best].push((path, key, element));
1858 }
1859
1860 let mut page_documents: Option<Vec<Document>> = None;
1863 let mut sub_results: Vec<Option<SubQueryResult>> = vec![None; self.sub_queries.len()];
1864 for ((_, documents_members), document_trios) in groups.iter().zip(trios_by_group) {
1865 let documents = match documents_members[0] {
1868 Component::Page => {
1869 Self::decode_document_trios(self, document_trios, platform_version)?
1870 }
1871 Component::Sub(index) => {
1872 let query = self.sub_query_document_query_with_direction(
1873 &self.sub_queries[index],
1874 &derived[index],
1875 direction,
1876 platform_version,
1877 )?;
1878 Self::decode_document_trios(&query, document_trios, platform_version)?
1879 }
1880 };
1881 let mut claimed: BTreeSet<usize> = BTreeSet::new();
1882 for member in documents_members {
1883 match member {
1884 Component::Page => {
1885 let page_ids: Option<BTreeSet<Identifier>> = if documents_members.len() > 1
1886 {
1887 Some(self.page_ids()?)
1888 } else {
1889 None
1890 };
1891 let mut mine = Vec::new();
1892 for (position, document) in documents.iter().enumerate() {
1893 let is_mine = page_ids
1894 .as_ref()
1895 .is_none_or(|ids| ids.contains(&document.id()));
1896 if is_mine {
1897 claimed.insert(position);
1898 mine.push(document.clone());
1899 }
1900 }
1901 page_documents = Some(mine);
1902 }
1903 Component::Sub(index) => {
1904 let sub_query = &self.sub_queries[*index];
1905 let mine =
1906 self.assemble_documents(sub_query, &derived[*index], &documents)?;
1907 let mine_ids: BTreeSet<Identifier> =
1908 mine.iter().map(|document| document.id()).collect();
1909 for (position, document) in documents.iter().enumerate() {
1910 if mine_ids.contains(&document.id()) {
1911 claimed.insert(position);
1912 }
1913 }
1914 sub_results[*index] = Some(SubQueryResult::Documents(mine));
1915 }
1916 }
1917 }
1918 if claimed.len() != documents.len() {
1919 return Err(corrupted_proof(
1920 "the composite proof carries a document that no component's \
1921 derivation asked for"
1922 .to_string(),
1923 ));
1924 }
1925 }
1926
1927 for (index, count_trios) in count_trios_by_sub.into_iter().enumerate() {
1928 if self.sub_queries[index].kind != SubQueryKind::Count {
1929 continue;
1930 }
1931 let Some(path_query) = &sub_path_queries[index] else {
1932 continue;
1933 };
1934 let values = &derived[index];
1938 let count_query = self.sub_query_count_query(
1939 &self.sub_queries[index],
1940 &values[..values.len().min(1)],
1941 platform_version,
1942 )?;
1943 let entries =
1944 Self::decode_count_trios(count_query.index, path_query.path.len(), count_trios);
1945 sub_results[index] = Some(SubQueryResult::Counts(Self::assemble_counts(
1946 &derived[index],
1947 entries,
1948 )?));
1949 }
1950
1951 let sub_results: Vec<SubQueryResult> = sub_results
1952 .into_iter()
1953 .zip(&self.sub_queries)
1954 .map(|(result, sub_query)| {
1955 result.unwrap_or_else(|| match sub_query.kind {
1956 SubQueryKind::Documents => SubQueryResult::Documents(Vec::new()),
1957 SubQueryKind::Count => SubQueryResult::Counts(Vec::new()),
1958 })
1959 })
1960 .collect();
1961 let removals = removal_entries_by_path
1962 .into_iter()
1963 .map(|(path, entries)| Ok((path, decode_removals(entries)?)))
1964 .collect::<Result<BTreeMap<_, _>, Error>>()?;
1965 let (sub_result_missing_ids, sub_result_removals) =
1966 self.sub_result_absences(derived, &sub_results, &removals)?;
1967 Ok(CompositeDocumentsResult {
1968 page_documents: page_documents.unwrap_or_default(),
1969 sub_results,
1970 sub_result_missing_ids,
1971 sub_result_removals,
1972 })
1973 }
1974
1975 fn page_ids(&self) -> Result<BTreeSet<Identifier>, Error> {
1979 let mut ids = BTreeSet::new();
1980 if let Some(clause) = &self.internal_clauses.primary_key_equal_clause {
1983 ids.insert(clause.value.to_identifier().map_err(|_| {
1984 Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
1985 "primary key equality must compare an identifier",
1986 ))
1987 })?);
1988 }
1989 if let Some(clause) = &self.internal_clauses.primary_key_in_clause {
1990 for value in clause.in_values().into_data_with_error()??.iter() {
1991 ids.insert(value.to_identifier().map_err(|_| {
1992 Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
1993 "primary key IN must contain identifiers",
1994 ))
1995 })?);
1996 }
1997 }
1998 Ok(ids)
1999 }
2000
2001 pub(crate) fn derive_for<'d>(
2010 &self,
2011 sub_query: &DriveSubQuery<'a>,
2012 page_documents: &[Document],
2013 sub_documents: impl Fn(usize) -> Option<&'d [Document]>,
2014 ) -> Result<DerivedValues, Error> {
2015 let Some(binding) = &sub_query.binding else {
2016 return Ok(Vec::new());
2017 };
2018 match binding.source {
2019 BindingSource::Page => self.derive_values(binding, page_documents),
2020 BindingSource::SubQuery(source_index) => {
2021 let documents = sub_documents(source_index).ok_or_else(|| {
2022 Error::Drive(DriveError::CorruptedCodeExecution(
2023 "a binding's source sub-query was not materialized before it",
2024 ))
2025 })?;
2026 self.derive_values(binding, documents)
2027 }
2028 }
2029 }
2030
2031 pub fn derive_all<'d>(
2034 &self,
2035 page_documents: &[Document],
2036 sub_documents: impl Fn(usize) -> Option<&'d [Document]>,
2037 ) -> Result<Vec<DerivedValues>, Error> {
2038 self.sub_queries
2039 .iter()
2040 .map(|sub_query| self.derive_for(sub_query, page_documents, &sub_documents))
2041 .collect()
2042 }
2043
2044 pub(crate) fn is_binding_source(&self, index: usize) -> bool {
2046 self.sub_queries.iter().any(|sub_query| {
2047 matches!(
2048 sub_query.binding,
2049 Some(SubQueryBinding {
2050 source: BindingSource::SubQuery(source),
2051 ..
2052 }) if source == index
2053 )
2054 })
2055 }
2056}
2057
2058#[cfg(feature = "server")]
2059impl<'a> DriveDocumentQuery<'a> {
2060 pub(crate) fn materialize_component(
2072 query: &DriveDocumentQuery<'a>,
2073 path_query: &PathQuery,
2074 drive: &crate::drive::Drive,
2075 transaction: grovedb::TransactionArg,
2076 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2077 platform_version: &PlatformVersion,
2078 ) -> Result<Vec<Document>, Error> {
2079 use grovedb::query_result_type::QueryResultType;
2080
2081 if query.document_type.index_only() {
2082 let results = match drive.grove_get_path_query(
2083 path_query,
2084 transaction,
2085 QueryResultType::QueryPathKeyElementTrioResultType,
2086 drive_operations,
2087 &platform_version.drive,
2088 ) {
2089 Err(error) if is_absent_path(&error) => return Ok(Vec::new()),
2090 other => other?.0,
2091 };
2092 return Self::decode_document_trios(
2093 query,
2094 results.to_path_key_elements(),
2095 platform_version,
2096 );
2097 }
2098 let serialized = match drive.grove_get_path_query_serialized_results(
2101 path_query,
2102 transaction,
2103 drive_operations,
2104 &platform_version.drive,
2105 ) {
2106 Err(error) if is_absent_path(&error) => return Ok(Vec::new()),
2107 other => other?.0,
2108 };
2109 serialized
2110 .into_iter()
2111 .map(|bytes| {
2112 Document::from_bytes(bytes.as_slice(), query.document_type, platform_version)
2113 .map_err(|e| Error::Protocol(Box::new(e)))
2114 })
2115 .collect()
2116 }
2117
2118 #[allow(clippy::too_many_arguments)]
2122 fn materialize_sub_result(
2123 &self,
2124 sub_query: &DriveSubQuery<'a>,
2125 values: &[Identifier],
2126 direction: bool,
2127 drive: &crate::drive::Drive,
2128 transaction: grovedb::TransactionArg,
2129 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2130 platform_version: &PlatformVersion,
2131 ) -> Result<SubQueryResult, Error> {
2132 use grovedb::query_result_type::{QueryResultElement, QueryResultType};
2133
2134 if sub_query.binding.is_some() && values.is_empty() {
2135 return Ok(match sub_query.kind {
2136 SubQueryKind::Documents => SubQueryResult::Documents(Vec::new()),
2137 SubQueryKind::Count => SubQueryResult::Counts(Vec::new()),
2138 });
2139 }
2140 match sub_query.kind {
2141 SubQueryKind::Documents => {
2142 let query = self.sub_query_document_query_with_direction(
2143 sub_query,
2144 values,
2145 direction,
2146 platform_version,
2147 )?;
2148 let path_query = self.sub_query_proof_path_query(
2149 sub_query,
2150 values,
2151 direction,
2152 platform_version,
2153 )?;
2154 let documents = Self::materialize_component(
2155 &query,
2156 &path_query,
2157 drive,
2158 transaction,
2159 drive_operations,
2160 platform_version,
2161 )?;
2162 Ok(SubQueryResult::Documents(
2163 self.assemble_documents(sub_query, values, &documents)?,
2164 ))
2165 }
2166 SubQueryKind::Count => {
2167 let count_query =
2168 self.sub_query_count_query(sub_query, values, platform_version)?;
2169 let path_query = count_query.point_lookup_count_path_query(platform_version)?;
2170 let base_path_len = path_query.path.len();
2171 let (results, _skipped) = match drive.grove_get_path_query(
2172 &path_query,
2173 transaction,
2174 QueryResultType::QueryPathKeyElementTrioResultType,
2175 drive_operations,
2176 &platform_version.drive,
2177 ) {
2178 Err(error) if is_absent_path(&error) => {
2180 return Ok(SubQueryResult::Counts(Vec::new()));
2181 }
2182 other => other?,
2183 };
2184 let trios = results
2185 .elements
2186 .into_iter()
2187 .filter_map(|element| match element {
2188 QueryResultElement::PathKeyElementTrioResultItem(trio) => Some(trio),
2189 _ => None,
2190 })
2191 .collect();
2192 let entries = Self::decode_count_trios(count_query.index, base_path_len, trios);
2193 Ok(SubQueryResult::Counts(Self::assemble_counts(
2194 values, entries,
2195 )?))
2196 }
2197 }
2198 }
2199
2200 pub(crate) fn execute_composite_no_proof_internal(
2202 &self,
2203 drive: &crate::drive::Drive,
2204 transaction: grovedb::TransactionArg,
2205 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2206 platform_version: &PlatformVersion,
2207 ) -> Result<CompositeDocumentsResult, Error> {
2208 self.validate_composite(platform_version)?;
2209
2210 let page_path_query = self.page_path_query(platform_version)?;
2211 let direction = page_path_query.query.query.left_to_right;
2212 self.refuse_an_uncovered_index_only_projection(platform_version)?;
2220 for sub_query in &self.sub_queries {
2221 if sub_query.kind == SubQueryKind::Documents && sub_query.document_type.index_only() {
2222 self.sub_query_document_query_with_direction(
2223 sub_query,
2224 &[Identifier::default()],
2225 direction,
2226 platform_version,
2227 )?
2228 .refuse_an_uncovered_index_only_projection(platform_version)?;
2229 }
2230 }
2231 let page_documents = Self::materialize_component(
2232 self,
2233 &page_path_query,
2234 drive,
2235 transaction,
2236 drive_operations,
2237 platform_version,
2238 )?;
2239 let mut sub_results: Vec<SubQueryResult> = Vec::with_capacity(self.sub_queries.len());
2240 let mut derived = Vec::with_capacity(self.sub_queries.len());
2241 for sub_query in &self.sub_queries {
2242 let values = self.derive_for(sub_query, &page_documents, |source| {
2243 sub_results.get(source).map(|result| result.documents())
2244 })?;
2245 sub_results.push(self.materialize_sub_result(
2246 sub_query,
2247 &values,
2248 direction,
2249 drive,
2250 transaction,
2251 drive_operations,
2252 platform_version,
2253 )?);
2254 derived.push(values);
2255 }
2256 let (_, _, removal_path_queries) = self.proof_path_queries(&derived, platform_version)?;
2260 let mut removals = BTreeMap::new();
2262 for path_query in &removal_path_queries {
2263 removals.insert(
2264 path_query.path.clone(),
2265 fetch_removals(
2266 drive,
2267 path_query,
2268 transaction,
2269 drive_operations,
2270 platform_version,
2271 )?,
2272 );
2273 }
2274 let (sub_result_missing_ids, sub_result_removals) =
2275 self.sub_result_absences(&derived, &sub_results, &removals)?;
2276 Ok(CompositeDocumentsResult {
2277 page_documents,
2278 sub_results,
2279 sub_result_missing_ids,
2280 sub_result_removals,
2281 })
2282 }
2283
2284 pub(crate) fn execute_composite_with_proof_internal(
2301 &self,
2302 drive: &crate::drive::Drive,
2303 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2304 platform_version: &PlatformVersion,
2305 ) -> Result<(Vec<u8>, Vec<Document>), Error> {
2306 self.validate_composite(platform_version)?;
2307 let page_path_query = self.page_path_query(platform_version)?;
2308 let direction = page_path_query.query.query.left_to_right;
2309
2310 const MAX_ATTEMPTS: usize = 3;
2314 for _ in 0..MAX_ATTEMPTS {
2315 let operations_before = drive_operations.len();
2318 let root_before = drive
2319 .grove
2320 .root_hash(None, &platform_version.drive.grove_version)
2321 .unwrap()?;
2322
2323 let page_documents = Self::materialize_component(
2324 self,
2325 &page_path_query,
2326 drive,
2327 None,
2328 drive_operations,
2329 platform_version,
2330 )?;
2331 let mut derived: Vec<DerivedValues> = Vec::with_capacity(self.sub_queries.len());
2334 let mut materialized: Vec<Option<Vec<Document>>> = vec![None; self.sub_queries.len()];
2335 for (index, sub_query) in self.sub_queries.iter().enumerate() {
2336 let values = self.derive_for(sub_query, &page_documents, |source| {
2337 materialized
2338 .get(source)
2339 .and_then(|documents| documents.as_deref())
2340 })?;
2341 if self.is_binding_source(index) {
2342 let result = self.materialize_sub_result(
2343 sub_query,
2344 &values,
2345 direction,
2346 drive,
2347 None,
2348 drive_operations,
2349 platform_version,
2350 )?;
2351 materialized[index] = Some(result.documents().to_vec());
2352 }
2353 derived.push(values);
2354 }
2355
2356 let (page_path_query, sub_path_queries, removal_path_queries) =
2357 self.proof_path_queries(&derived, platform_version)?;
2358 let merged_query = Self::merged_path_query(
2361 &page_path_query,
2362 &sub_path_queries,
2363 &removal_path_queries,
2364 platform_version,
2365 )?;
2366 let proof = drive
2367 .grove
2368 .prove_query(&merged_query, None, &platform_version.drive.grove_version)
2369 .unwrap()?;
2370
2371 let root_after = drive
2372 .grove
2373 .root_hash(None, &platform_version.drive.grove_version)
2374 .unwrap()?;
2375 if root_before != root_after {
2376 drive_operations.truncate(operations_before);
2377 continue;
2378 }
2379 return Ok((proof, page_documents));
2380 }
2381 Err(Error::Drive(DriveError::NotSupported(
2382 "composite proof generation raced a block commit on every attempt; transient — \
2383 retry the request",
2384 )))
2385 }
2386}
2387
2388#[cfg(test)]
2389mod tests {
2390 use super::*;
2391 use grovedb::{Query, SizedQuery, SubqueryBranch};
2392
2393 #[test]
2398 fn should_follow_a_walk_through_selected_keys_and_subqueries_only() {
2399 let pv = PlatformVersion::latest();
2400 let key = |name: &str| name.as_bytes().to_vec();
2401 let mut body = Query::new();
2402 body.insert_key(key("x"));
2403 body.default_subquery_branch = SubqueryBranch {
2404 subquery_path: Some(vec![key("c")]),
2405 subquery: Some(Box::new(Query::new_range_full())),
2406 };
2407 let shallower = PathQuery::new(vec![key("a"), key("b")], SizedQuery::new(body, None, None));
2408 let descends = |path: &[&str]| {
2409 DriveDocumentQuery::path_query_descends_through(
2410 &shallower,
2411 &path.iter().map(|segment| key(segment)).collect::<Vec<_>>(),
2412 pv,
2413 )
2414 .expect("the walk resolves")
2415 };
2416 assert!(
2417 descends(&["a", "b", "x", "c"]),
2418 "selected key, then its subquery path"
2419 );
2420 assert!(!descends(&["a", "b", "x", "d"]), "not the subquery path");
2421 assert!(!descends(&["a", "b", "y", "c"]), "an unselected key");
2422 assert!(!descends(&["a", "z"]), "off the base path");
2423 assert!(
2424 !descends(&["a", "b", "x", "c", "k"]),
2425 "past the walk's leaves"
2426 );
2427 }
2428}