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 key_array_element_clauses, DriveDocumentCountQuery, DriveDocumentQuery, InternalClauses,
113 OrderClause, SplitCountEntry, 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
311#[cfg(feature = "server")]
314fn check_composite_instance_limits(
315 path_query: &PathQuery,
316 platform_version: &PlatformVersion,
317) -> Result<(), Error> {
318 if platform_version
319 .drive
320 .grove_version
321 .grovedb_versions
322 .path_query_methods
323 .per_instance_query_limits
324 == 0
325 && path_query.has_instance_limits()
326 {
327 return Err(unsupported(
328 "composite query limits require a protocol version that supports per-instance limits"
329 .to_string(),
330 ));
331 }
332 Ok(())
333}
334
335fn document_bound_value(document: &Document, field: &str) -> Result<Option<Identifier>, Error> {
338 use dpp::document::property_names::{ID, OWNER_ID};
339 if field == ID {
340 return Ok(Some(document.id()));
341 }
342 if field == OWNER_ID {
343 return Ok(Some(document.owner_id()));
344 }
345 let Some(value) = document
346 .properties()
347 .get_optional_at_path(field)
348 .ok()
349 .flatten()
350 else {
351 return Ok(None);
352 };
353 value.to_identifier().map(Some).map_err(|_| {
354 Error::Drive(DriveError::CorruptedCodeExecution(
355 "a bound composite property must decode as an identifier: validate() only \
356 admits identifier-typed properties",
357 ))
358 })
359}
360
361fn sorted_values(values: &[Identifier]) -> Vec<Identifier> {
365 let mut sorted = values.to_vec();
366 sorted.sort();
367 sorted
368}
369
370fn document_reference_of(
375 property_type: &DocumentPropertyType,
376) -> Option<DocumentReferenceDeclaration<'_>> {
377 match property_type {
378 DocumentPropertyType::IdentifierWithReference(reference_target) => {
379 reference_target.as_document_reference()
380 }
381 _ => None,
382 }
383}
384
385impl<'a> DriveSubQuery<'a> {
386 fn bound_field(&self) -> Option<&str> {
387 self.binding.as_ref().map(|binding| binding.field.as_str())
388 }
389
390 fn is_by_id_join(&self) -> bool {
391 self.bound_field() == Some(dpp::document::property_names::ID)
392 }
393}
394
395impl<'a> DriveDocumentQuery<'a> {
396 pub fn validate_composite(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
405 if self.sub_queries.is_empty() {
406 return Err(unsupported(
407 "a composite query needs at least one sub-query; a page alone is a plain \
408 documents query"
409 .to_string(),
410 ));
411 }
412 if self.sub_queries.len() > MAX_SUB_QUERIES {
413 return Err(unsupported(format!(
414 "a composite query carries at most {} sub-queries, got {}",
415 MAX_SUB_QUERIES,
416 self.sub_queries.len(),
417 )));
418 }
419 let page_limit = match self.limit {
420 None => {
421 return Err(unsupported(
422 "composite queries require an explicit limit on the page: the page size \
423 bounds every derived sub-query"
424 .to_string(),
425 ));
426 }
427 Some(0) => {
428 return Err(unsupported(
429 "a composite page limit must be at least 1".to_string(),
430 ));
431 }
432 Some(limit) if limit as usize > MAX_BOUND_VALUES => {
433 return Err(unsupported(format!(
434 "a composite page limit of {} exceeds {}: a derived `IN` clause admits at \
435 most that many values",
436 limit, MAX_BOUND_VALUES,
437 )));
438 }
439 Some(limit) => limit,
440 };
441 if self.offset.is_some() {
442 return Err(unsupported(
443 "composite queries do not support a page offset; paginate with a range clause"
444 .to_string(),
445 ));
446 }
447 if self.start_at.is_some() {
448 return Err(unsupported(
449 "composite queries do not support a page cursor (startAt/startAfter); \
450 paginate with a range clause on the page's ordering property"
451 .to_string(),
452 ));
453 }
454 if self.page_is_by_ids() {
457 let ids = self.page_ids()?.len();
458 if (page_limit as usize) < ids {
459 return Err(unsupported(format!(
460 "a by-ids composite page addresses {} ids but its limit is {}: the ids \
461 bound the page, so the limit must cover them",
462 ids, page_limit,
463 )));
464 }
465 }
466 let direction = self.page_direction(platform_version)?;
469
470 for (index, sub_query) in self.sub_queries.iter().enumerate() {
471 self.validate_sub_query(index, sub_query, direction, platform_version)?;
472 }
473 self.validate_component_paths(platform_version)
474 }
475
476 fn validate_sub_query(
477 &self,
478 index: usize,
479 sub_query: &DriveSubQuery<'a>,
480 direction: bool,
481 platform_version: &PlatformVersion,
482 ) -> Result<(), Error> {
483 let label = |message: &str| unsupported(format!("sub-query {}: {}", index, message));
484
485 if sub_query
491 .where_clauses
492 .iter()
493 .any(|clause| clause.operator == WhereOperator::ContainsAny)
494 {
495 return Err(label(
496 "containsAny is not supported in a sub-query: a document holding several of \
497 the elements would be returned or counted once per element; use contains",
498 ));
499 }
500
501 let Some(binding) = &sub_query.binding else {
502 if sub_query.kind == SubQueryKind::Count {
504 return Err(label(
505 "a count sub-query must be bound (`COUNT ... WHERE <field> IN <derived \
506 values> GROUP BY <field>`); unbound counts stay on the regular count \
507 surface",
508 ));
509 }
510 match sub_query.limit {
511 None => {
512 return Err(label(
513 "a sibling documents sub-query requires an explicit limit",
514 ));
515 }
516 Some(0) => {
517 return Err(label("a sibling's limit must be at least 1"));
518 }
519 Some(limit) if limit as usize > MAX_BOUND_VALUES => {
520 return Err(label(&format!(
521 "limit {} exceeds {}",
522 limit, MAX_BOUND_VALUES
523 )));
524 }
525 Some(_) => {}
526 }
527 self.sub_query_document_query_with_direction(
529 sub_query,
530 &[],
531 direction,
532 platform_version,
533 )?
534 .construct_path_query(None, platform_version)?;
535 return Ok(());
536 };
537
538 let (source_contract, source_type, source_is_index_only_query) = match binding.source {
540 BindingSource::Page => (
541 self.contract,
542 self.document_type,
543 self.document_type.index_only(),
544 ),
545 BindingSource::SubQuery(source_index) => {
546 if source_index >= index {
547 return Err(label("a binding may only reference an earlier sub-query"));
548 }
549 let source = &self.sub_queries[source_index];
550 if source.kind != SubQueryKind::Documents {
551 return Err(label("a binding must reference a documents sub-query"));
552 }
553 (
554 source.contract,
555 source.document_type,
556 source.document_type.index_only(),
557 )
558 }
559 };
560
561 let source_property_type: Option<&DocumentPropertyType> = {
564 use dpp::document::property_names::{ID, OWNER_ID};
565 if binding.source_property == ID || binding.source_property == OWNER_ID {
566 None
567 } else {
568 let Some(property) = source_type
569 .flattened_properties()
570 .get(binding.source_property.as_str())
571 else {
572 return Err(label(&format!(
573 "source property \"{}\" does not name a property of \"{}\"",
574 binding.source_property,
575 source_type.name(),
576 )));
577 };
578 if !matches!(
579 property.property_type,
580 DocumentPropertyType::Identifier
581 | DocumentPropertyType::IdentifierWithReference(_)
582 ) {
583 return Err(label(&format!(
584 "source property \"{}\" is not identifier-typed; composite bindings \
585 derive identifiers only",
586 binding.source_property,
587 )));
588 }
589 Some(&property.property_type)
590 }
591 };
592
593 if source_is_index_only_query {
596 let carries = |index: &dpp::data_contract::document_type::Index| {
597 index.terminal_contains(&binding.source_property)
598 || index
599 .properties
600 .iter()
601 .any(|property| property.name == binding.source_property)
602 };
603 let (carried, index_name) = match binding.source {
604 BindingSource::Page => {
605 let index = self.index_only_query_index(platform_version)?;
606 (carries(index), index.name.clone())
607 }
608 BindingSource::SubQuery(source_index) => {
609 let source = &self.sub_queries[source_index];
610 let shape = self.sub_query_document_query_with_direction(
611 source,
612 &[Identifier::default()],
613 direction,
614 platform_version,
615 )?;
616 let index = shape.index_only_query_index(platform_version)?;
617 (carries(index), index.name.clone())
618 }
619 };
620 if !carried {
621 return Err(label(&format!(
622 "the indexOnly source resolves to index \"{}\", which does not carry the \
623 source property \"{}\"",
624 index_name, binding.source_property,
625 )));
626 }
627 }
628
629 if sub_query
630 .where_clauses
631 .iter()
632 .any(|clause| clause.field == binding.field)
633 {
634 return Err(label(&format!(
635 "the fixed clauses may not name the bound field \"{}\"; its `IN` clause is \
636 derived",
637 binding.field,
638 )));
639 }
640
641 if !sub_query.is_by_id_join() && binding.field != dpp::document::property_names::OWNER_ID {
647 let Some(property) = sub_query
648 .document_type
649 .flattened_properties()
650 .get(binding.field.as_str())
651 else {
652 return Err(label(&format!(
653 "bound field \"{}\" does not name a property of \"{}\"",
654 binding.field,
655 sub_query.document_type.name(),
656 )));
657 };
658 if !matches!(
659 property.property_type,
660 DocumentPropertyType::Identifier | DocumentPropertyType::IdentifierWithReference(_)
661 ) {
662 return Err(label(&format!(
663 "bound field \"{}\" is not identifier-typed; composite bindings derive \
664 identifiers only",
665 binding.field,
666 )));
667 }
668 }
669
670 match sub_query.kind {
671 SubQueryKind::Documents if sub_query.is_by_id_join() => {
672 if sub_query.document_type.index_only() {
673 return Err(label(
674 "a by-id join cannot target an indexOnly type: there is no \
675 primary-key tree to fetch from",
676 ));
677 }
678 if sub_query.limit.is_some() {
679 return Err(label(
680 "a by-id join takes no limit: every derived id must resolve, so \
681 completeness is set equality, not a page",
682 ));
683 }
684 if !sub_query.order_by.is_empty() {
685 return Err(label(
686 "a by-id join takes no ordering: results follow the derived ids' \
687 first appearance",
688 ));
689 }
690 if let Some(DocumentPropertyType::IdentifierWithReference(
703 DocumentPropertyReferenceTarget::PermanentDocumentLookup { lookup, .. }
704 | DocumentPropertyReferenceTarget::DeletableDocumentLookup { lookup, .. },
705 )) = source_property_type
706 {
707 return Err(label(&format!(
708 "the source property's refersTo finds its document by findBy ({}), so \
709 its values are not document ids: a by-id join needs a reference whose \
710 value is the referenced document's $id",
711 lookup.find_by_names(),
712 )));
713 }
714 if let Some(DocumentPropertyType::IdentifierWithReference(
717 DocumentPropertyReferenceTarget::AnyOf(_)
718 | DocumentPropertyReferenceTarget::AllOf(_),
719 )) = source_property_type
720 {
721 return Err(label(
722 "the source property declares a refersTo anyOf or allOf expression: a \
723 by-id join needs a reference to one document type",
724 ));
725 }
726 match source_property_type.and_then(document_reference_of) {
727 Some(DocumentReferenceDeclaration {
728 contract_id,
729 document_type_name,
730 ..
731 }) => {
732 let referenced_contract =
733 contract_id.unwrap_or_else(|| source_contract.id());
734 if referenced_contract != sub_query.contract.id()
735 || document_type_name != sub_query.document_type.name()
736 {
737 return Err(label(&format!(
738 "the source property's refersTo targets \"{}\", not this \
739 sub-query's type \"{}\"",
740 document_type_name,
741 sub_query.document_type.name(),
742 )));
743 }
744 }
745 None => {
746 return Err(label(&format!(
747 "a by-id join needs a source property declaring `refersTo: \
748 permanentDocument`, `refersTo: moderatedDocument` or `refersTo: \
749 deletableDocument` (\"{}\" does not): the declaration names the \
750 document type the derived ids resolve in",
751 binding.source_property,
752 )));
753 }
754 }
755 }
756 SubQueryKind::Documents => {
757 let shape = self.sub_query_document_query_with_direction(
759 sub_query,
760 &[Identifier::default()],
761 direction,
762 platform_version,
763 )?;
764 shape.construct_path_query(None, platform_version)?;
765 if sub_query.document_type.index_only() {
766 let index = shape.index_only_query_index(platform_version)?;
769 let carried = index.terminal_contains(&binding.field)
770 || index
771 .properties
772 .iter()
773 .any(|property| property.name == binding.field);
774 if !carried {
775 return Err(label(&format!(
776 "the indexOnly lookup resolves to index \"{}\", which does not \
777 carry the bound field \"{}\"",
778 index.name, binding.field,
779 )));
780 }
781 }
782 let value_bounded =
789 self.lookup_is_value_bounded(sub_query, binding, &shape, platform_version)?;
790 match (value_bounded, sub_query.limit) {
791 (true, Some(_)) => {
792 return Err(label(
793 "a value-bounded lookup (a unique index, or an indexOnly terminal \
794 with every prefix fixed, yields at most one row per derived \
795 value) takes no limit",
796 ));
797 }
798 (false, Some(0)) => {
799 return Err(label("a lookup's limit must be at least 1"));
800 }
801 (false, None) => {
802 return Err(label(
803 "a documents lookup on a non-unique index requires an explicit \
804 limit: it bounds the walk under each derived value",
805 ));
806 }
807 (false, Some(limit)) if limit as usize > MAX_BOUND_VALUES => {
808 return Err(label(&format!(
809 "limit {} exceeds {}",
810 limit, MAX_BOUND_VALUES
811 )));
812 }
813 _ => {}
814 }
815 }
816 SubQueryKind::Count => {
817 if sub_query.limit.is_some() {
818 return Err(label("a count sub-query takes no limit"));
819 }
820 if !sub_query.order_by.is_empty() {
821 return Err(label("a count sub-query takes no ordering"));
822 }
823 if sub_query.is_by_id_join() {
824 return Err(label(
825 "a count sub-query counts by an indexed property, not by `$id`",
826 ));
827 }
828 self.sub_query_count_query(sub_query, &[Identifier::default()], platform_version)?
830 .point_lookup_count_path_query(platform_version)?;
831 }
832 }
833 Ok(())
834 }
835
836 fn lookup_is_value_bounded(
843 &self,
844 sub_query: &DriveSubQuery<'a>,
845 binding: &SubQueryBinding,
846 shape: &DriveDocumentQuery<'a>,
847 platform_version: &PlatformVersion,
848 ) -> Result<bool, Error> {
849 let fixed_equalities: BTreeSet<&str> = sub_query
850 .where_clauses
851 .iter()
852 .filter(|clause| clause.operator == WhereOperator::Equal)
853 .map(|clause| clause.field.as_str())
854 .collect();
855 if sub_query.document_type.index_only() {
856 let index = shape.index_only_query_index(platform_version)?;
857 let terminal_is_bound = index.single_terminal() == Some(binding.field.as_str());
860 let prefix_fixed = index
861 .properties
862 .iter()
863 .all(|property| fixed_equalities.contains(property.name.as_str()));
864 return Ok(terminal_is_bound && prefix_fixed);
865 }
866 let mut wanted: BTreeSet<&str> = fixed_equalities.clone();
867 wanted.insert(binding.field.as_str());
868 Ok(sub_query.document_type.indexes().values().any(|index| {
869 index.unique
870 && index.properties.len() == wanted.len()
871 && index
872 .properties
873 .iter()
874 .all(|property| wanted.contains(property.name.as_str()))
875 }))
876 }
877
878 fn page_is_by_ids(&self) -> bool {
880 self.internal_clauses.primary_key_in_clause.is_some()
881 || self.internal_clauses.primary_key_equal_clause.is_some()
882 }
883
884 fn budget_as_instance_cap(mut path_query: PathQuery) -> PathQuery {
897 if let Some(limit) = path_query.query.limit.take() {
898 path_query.query.query.limit = Some(limit);
899 }
900 path_query
901 }
902
903 pub fn page_path_query(&self, platform_version: &PlatformVersion) -> Result<PathQuery, Error> {
910 if self.page_is_by_ids() {
911 let mut unlimited = self.clone();
912 unlimited.limit = None;
913 let mut path_query = unlimited.construct_path_query(None, platform_version)?;
914 path_query.query.limit = None;
918 return Ok(path_query);
919 }
920 Ok(Self::budget_as_instance_cap(
921 self.construct_path_query(None, platform_version)?,
922 ))
923 }
924
925 fn validate_component_paths(&self, platform_version: &PlatformVersion) -> Result<(), Error> {
944 let representative = [Identifier::default()];
945 let mut components: Vec<(Vec<Vec<u8>>, Component, bool)> = Vec::new();
946 let page = self.page_path_query(platform_version)?;
947 let direction = page.query.query.left_to_right;
948 components.push((page.path, Component::Page, page.query.query.limit.is_some()));
949 for (index, sub_query) in self.sub_queries.iter().enumerate() {
950 let path_query = self.sub_query_proof_path_query(
951 sub_query,
952 &representative,
953 direction,
954 platform_version,
955 )?;
956 components.push((
957 path_query.path,
958 Component::Sub(index),
959 path_query.query.query.limit.is_some(),
960 ));
961 }
962
963 let is_bound = |component: &Component| matches!(component, Component::Sub(index) if self.sub_queries[*index].binding.is_some());
964 for (path, component, limited) in &components {
965 if !*limited {
966 continue;
967 }
968 let lands_at_root = match component {
969 Component::Page => {
973 let (siblings, bound): (Vec<_>, Vec<_>) = components
974 .iter()
975 .skip(1)
976 .partition(|(_, other, _)| !is_bound(other));
977 bound.iter().any(|(other, _, _)| other.starts_with(path))
978 || (!siblings.is_empty()
979 && siblings.iter().all(|(other, _, _)| other.starts_with(path)))
980 }
981 Component::Sub(_) => components
985 .iter()
986 .filter(|(_, other, _)| other != component && !is_bound(other))
987 .all(|(other, _, _)| other.starts_with(path)),
988 };
989 if lands_at_root {
990 return Err(unsupported(format!(
991 "{} carries a limit and lands at the merged root of the composite proof \
992 (once the bound sub-queries that derive nothing drop out), where grovedb \
993 refuses a budget; give it a clause that narrows its path, or split it \
994 into a separate request",
995 match component {
996 Component::Page => "the page".to_string(),
997 Component::Sub(index) => format!("sub-query {}", index),
998 }
999 )));
1000 }
1001 }
1002
1003 let mut groups: BTreeMap<&Vec<Vec<u8>>, Vec<(Component, bool)>> = BTreeMap::new();
1004 for (path, component, limited) in &components {
1005 groups.entry(path).or_default().push((*component, *limited));
1006 }
1007 for members in groups.values() {
1008 let documents_members: Vec<Component> = members
1009 .iter()
1010 .map(|(component, _)| *component)
1011 .filter(|component| match component {
1012 Component::Page => true,
1013 Component::Sub(index) => {
1014 self.sub_queries[*index].kind == SubQueryKind::Documents
1015 }
1016 })
1017 .collect();
1018 let has_count_member = members.iter().any(|(component, _)| {
1019 matches!(component, Component::Sub(index) if self.sub_queries[*index].kind == SubQueryKind::Count)
1020 });
1021 if has_count_member && !documents_members.is_empty() {
1031 return Err(unsupported(
1032 "a count sub-query shares its index path with a documents component: \
1033 the count reads the index's value trees themselves while the documents \
1034 query descends past them, and one proof cannot serve both; count on \
1035 another index, or split them into separate requests"
1036 .to_string(),
1037 ));
1038 }
1039 if documents_members.len() < 2 {
1040 continue;
1041 }
1042 let has_sibling = documents_members.iter().any(|component| {
1043 matches!(component, Component::Sub(index) if self.sub_queries[*index].binding.is_none())
1044 });
1045 let has_page = documents_members.contains(&Component::Page);
1046 let all_subs_are_joins = documents_members.iter().all(|component| match component {
1047 Component::Page => true,
1048 Component::Sub(index) => self.sub_queries[*index].is_by_id_join(),
1049 });
1050 if has_sibling || (has_page && !(self.page_is_by_ids() && all_subs_are_joins)) {
1051 return Err(unsupported(
1052 "two documents components of the composite query address the same index \
1053 path and cannot be told apart by their derived values (a sibling, or a \
1054 page that is not a by-ids fetch, shares a path with another component); \
1055 split them into separate requests"
1056 .to_string(),
1057 ));
1058 }
1059 if members.iter().any(|(_, limited)| *limited) {
1063 return Err(unsupported(
1064 "two documents components of the composite query address the same index \
1065 path and one of them carries a limit, which cannot be merged with the \
1066 other's selection; split them into separate requests"
1067 .to_string(),
1068 ));
1069 }
1070 }
1071 Ok(())
1072 }
1073
1074 pub fn derive_values(
1079 &self,
1080 binding: &SubQueryBinding,
1081 source_documents: &[Document],
1082 ) -> Result<DerivedValues, Error> {
1083 let mut seen: BTreeSet<Identifier> = BTreeSet::new();
1084 let mut values = Vec::new();
1085 for document in source_documents {
1086 if let Some(value) = document_bound_value(document, &binding.source_property)? {
1087 if seen.insert(value) {
1088 values.push(value);
1089 }
1090 }
1091 }
1092 if values.len() > MAX_BOUND_VALUES {
1093 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1097 "a composite binding source yielded more documents than the shapes allow",
1098 )));
1099 }
1100 Ok(values)
1101 }
1102
1103 pub fn sub_query_document_query(
1107 &self,
1108 sub_query: &DriveSubQuery<'a>,
1109 values: &[Identifier],
1110 platform_version: &PlatformVersion,
1111 ) -> Result<DriveDocumentQuery<'a>, Error> {
1112 let direction = self.page_direction(platform_version)?;
1113 self.sub_query_document_query_with_direction(sub_query, values, direction, platform_version)
1114 }
1115
1116 pub(crate) fn sub_query_document_query_with_direction(
1120 &self,
1121 sub_query: &DriveSubQuery<'a>,
1122 values: &[Identifier],
1123 direction: bool,
1124 platform_version: &PlatformVersion,
1125 ) -> Result<DriveDocumentQuery<'a>, Error> {
1126 let ids = sorted_values(values);
1127 let in_value = || {
1128 Value::Array(
1129 ids.iter()
1130 .map(|id| Value::Identifier(id.to_buffer()))
1131 .collect(),
1132 )
1133 };
1134
1135 if sub_query.is_by_id_join() {
1136 if !sub_query.where_clauses.is_empty() {
1137 return Err(unsupported(
1138 "a by-id join takes no fixed clauses: every derived id must resolve"
1139 .to_string(),
1140 ));
1141 }
1142 return Ok(DriveDocumentQuery {
1143 contract: sub_query.contract,
1144 document_type: sub_query.document_type,
1145 internal_clauses: InternalClauses {
1146 primary_key_in_clause: Some(WhereClause {
1147 field: dpp::document::property_names::ID.to_string(),
1148 operator: WhereOperator::In,
1149 value: in_value(),
1150 }),
1151 primary_key_equal_clause: None,
1152 in_clauses: Vec::new(),
1153 range_clause: None,
1154 equal_clauses: Default::default(),
1155 },
1156 offset: None,
1157 limit: None,
1158 order_by: Default::default(),
1159 start_at: None,
1160 start_at_included: false,
1161 block_time_ms: None,
1162 resolved_time_ranges: Vec::new(),
1163 sub_queries: Vec::new(),
1164 });
1165 }
1166
1167 let mut clauses = sub_query.where_clauses.clone();
1168 let mut order_by: indexmap::IndexMap<String, OrderClause> = sub_query
1169 .order_by
1170 .iter()
1171 .map(|clause| (clause.field.clone(), clause.clone()))
1172 .collect();
1173 if let Some(binding) = &sub_query.binding {
1174 clauses.push(WhereClause {
1175 field: binding.field.clone(),
1176 operator: WhereOperator::In,
1177 value: in_value(),
1178 });
1179 if !order_by.contains_key(&binding.field) {
1188 order_by.insert(
1189 binding.field.clone(),
1190 OrderClause {
1191 field: binding.field.clone(),
1192 ascending: direction,
1193 },
1194 );
1195 }
1196 }
1197 Ok(DriveDocumentQuery {
1198 contract: sub_query.contract,
1199 document_type: sub_query.document_type,
1200 internal_clauses: InternalClauses::extract_from_clauses(clauses, platform_version)?,
1201 offset: None,
1202 limit: sub_query.limit,
1203 order_by,
1204 start_at: None,
1205 start_at_included: false,
1206 block_time_ms: None,
1207 resolved_time_ranges: Vec::new(),
1208 sub_queries: Vec::new(),
1209 })
1210 }
1211
1212 pub fn sub_query_count_query<'b>(
1216 &'b self,
1217 sub_query: &'b DriveSubQuery<'a>,
1218 values: &[Identifier],
1219 platform_version: &PlatformVersion,
1220 ) -> Result<DriveDocumentCountQuery<'b>, Error> {
1221 let Some(binding) = &sub_query.binding else {
1222 return Err(unsupported("a count sub-query must be bound".to_string()));
1223 };
1224 let mut where_clauses = key_array_element_clauses(
1228 sub_query.where_clauses.clone(),
1229 sub_query.document_type,
1230 false,
1231 platform_version,
1232 )?;
1233 where_clauses.push(WhereClause {
1234 field: binding.field.clone(),
1235 operator: WhereOperator::In,
1236 value: Value::Array(
1237 sorted_values(values)
1238 .into_iter()
1239 .map(|id| Value::Identifier(id.to_buffer()))
1240 .collect(),
1241 ),
1242 });
1243 let index = DriveDocumentCountQuery::find_countable_index_for_where_clauses(
1244 sub_query.document_type.indexes(),
1245 &where_clauses,
1246 &[],
1247 )
1248 .ok_or_else(|| {
1249 unsupported(format!(
1250 "count sub-query on \"{}\" needs a `countable: true` (or summableOffCountIndex) index covering its fixed \
1251 clauses and the bound field \"{}\"",
1252 sub_query.document_type.name(),
1253 binding.field,
1254 ))
1255 })?;
1256 Ok(DriveDocumentCountQuery {
1257 document_type: sub_query.document_type,
1258 contract_id: sub_query.contract.id().to_buffer(),
1259 document_type_name: sub_query.document_type.name().to_string(),
1260 index,
1261 where_clauses,
1262 })
1263 }
1264
1265 pub fn sub_query_path_query(
1267 &self,
1268 sub_query: &DriveSubQuery<'a>,
1269 values: &[Identifier],
1270 platform_version: &PlatformVersion,
1271 ) -> Result<PathQuery, Error> {
1272 let direction = self.page_direction(platform_version)?;
1273 self.sub_query_path_query_with_direction(sub_query, values, direction, platform_version)
1274 }
1275
1276 fn sub_query_path_query_with_direction(
1277 &self,
1278 sub_query: &DriveSubQuery<'a>,
1279 values: &[Identifier],
1280 direction: bool,
1281 platform_version: &PlatformVersion,
1282 ) -> Result<PathQuery, Error> {
1283 let path_query = match sub_query.kind {
1284 SubQueryKind::Documents => self
1285 .sub_query_document_query_with_direction(
1286 sub_query,
1287 values,
1288 direction,
1289 platform_version,
1290 )?
1291 .construct_path_query(None, platform_version)?,
1292 SubQueryKind::Count => self
1293 .sub_query_count_query(sub_query, values, platform_version)?
1294 .point_lookup_count_path_query(platform_version)?,
1295 };
1296 Ok(Self::budget_as_instance_cap(path_query))
1297 }
1298
1299 fn page_direction(&self, platform_version: &PlatformVersion) -> Result<bool, Error> {
1302 Ok(self
1303 .page_path_query(platform_version)?
1304 .query
1305 .query
1306 .left_to_right)
1307 }
1308
1309 pub(crate) fn sub_query_proof_path_query(
1314 &self,
1315 sub_query: &DriveSubQuery<'a>,
1316 values: &[Identifier],
1317 direction: bool,
1318 platform_version: &PlatformVersion,
1319 ) -> Result<PathQuery, Error> {
1320 let mut path_query = self.sub_query_path_query_with_direction(
1321 sub_query,
1322 values,
1323 direction,
1324 platform_version,
1325 )?;
1326 if sub_query.kind == SubQueryKind::Documents
1327 && !sub_query.is_by_id_join()
1328 && path_query.query.query.left_to_right != direction
1329 {
1330 return Err(unsupported(if sub_query.binding.is_none() {
1331 "a sibling sub-query's ordering must match the page's direction; order it \
1332 explicitly by its index property, in the page's direction"
1333 .to_string()
1334 } else {
1335 "a documents sub-query's outer ordering must match the page's direction; \
1336 changing it for the merged proof would change its result"
1337 .to_string()
1338 }));
1339 }
1340 path_query.query.query.left_to_right = direction;
1343 Ok(path_query)
1344 }
1345
1346 pub fn proof_path_queries(
1357 &self,
1358 derived: &[DerivedValues],
1359 platform_version: &PlatformVersion,
1360 ) -> Result<CompositeProofPathQueries, Error> {
1361 if derived.len() != self.sub_queries.len() {
1362 return Err(Error::Drive(DriveError::CorruptedCodeExecution(
1363 "one derived value list per sub-query",
1364 )));
1365 }
1366 let page = self.page_path_query(platform_version)?;
1367 let direction = page.query.query.left_to_right;
1368 let mut sub_path_queries = Vec::with_capacity(self.sub_queries.len());
1369 for (sub_query, values) in self.sub_queries.iter().zip(derived) {
1370 if sub_query.binding.is_some() && values.is_empty() {
1371 sub_path_queries.push(None);
1372 continue;
1373 }
1374 let path_query =
1375 self.sub_query_proof_path_query(sub_query, values, direction, platform_version)?;
1376 sub_path_queries.push(Some(path_query));
1377 }
1378 let mut count_terminal_paths = BTreeSet::new();
1383 for (sub_query, path_query) in self.sub_queries.iter().zip(&sub_path_queries) {
1384 if sub_query.kind != SubQueryKind::Count {
1385 continue;
1386 }
1387 if let Some(path_query) = path_query {
1388 for (mut path, key) in path_query
1389 .terminal_keys(MAX_BOUND_VALUES, &platform_version.drive.grove_version)?
1390 {
1391 path.push(key);
1392 count_terminal_paths.insert(path);
1393 }
1394 }
1395 }
1396 for terminal_path in count_terminal_paths {
1397 for component in std::iter::once(&page).chain(sub_path_queries.iter().flatten()) {
1398 if !terminal_path.starts_with(&component.path)
1402 && !component.path.starts_with(&terminal_path)
1403 {
1404 continue;
1405 }
1406 if Self::path_query_descends_through(component, &terminal_path, platform_version)? {
1407 return Err(unsupported(
1408 "a count sub-query selects a tree another component descends through; \
1409 split them into separate requests"
1410 .to_string(),
1411 ));
1412 }
1413 }
1414 }
1415 let documents: Vec<&PathQuery> = std::iter::once(&page)
1422 .chain(
1423 sub_path_queries
1424 .iter()
1425 .zip(&self.sub_queries)
1426 .filter(|(_, sub_query)| sub_query.kind == SubQueryKind::Documents)
1427 .filter_map(|(path_query, _)| path_query.as_ref()),
1428 )
1429 .collect();
1430 for deeper in &documents {
1431 for shallower in &documents {
1432 if deeper.path.len() <= shallower.path.len()
1433 || !deeper.path.starts_with(&shallower.path)
1434 {
1435 continue;
1436 }
1437 if Self::path_query_descends_through(shallower, &deeper.path, platform_version)? {
1438 return Err(unsupported(
1439 "a documents sub-query walks through another documents component's \
1440 subtree, so their rows could not be told apart; split them into \
1441 separate requests"
1442 .to_string(),
1443 ));
1444 }
1445 }
1446 }
1447 let removal_path_queries = self.removal_path_queries(derived, direction);
1451 Ok((page, sub_path_queries, removal_path_queries))
1452 }
1453
1454 fn path_query_descends_through(
1458 query: &PathQuery,
1459 terminal_path: &[Vec<u8>],
1460 platform_version: &PlatformVersion,
1461 ) -> Result<bool, Error> {
1462 let mut prefix = Vec::with_capacity(terminal_path.len());
1463 for key in terminal_path {
1464 let Some(selection) =
1465 query.query_items_at_path(&prefix, &platform_version.drive.grove_version)?
1466 else {
1467 return Ok(false);
1468 };
1469 if !selection.items.iter().any(|item| item.contains(key))
1470 || !selection.has_subquery_or_matching_in_path_on_key(key)
1471 {
1472 return Ok(false);
1473 }
1474 prefix.push(key.as_slice());
1475 }
1476 Ok(true)
1477 }
1478
1479 pub fn merged_path_query(
1485 page: &PathQuery,
1486 sub_path_queries: &[Option<PathQuery>],
1487 removal_path_queries: &[PathQuery],
1488 platform_version: &PlatformVersion,
1489 ) -> Result<PathQuery, Error> {
1490 let mut components: Vec<&PathQuery> = vec![page];
1491 components.extend(sub_path_queries.iter().flatten());
1492 components.extend(removal_path_queries);
1493 if components.len() == 1 {
1494 return Ok(page.clone());
1495 }
1496 PathQuery::merge(components, &platform_version.drive.grove_version)
1497 .map_err(merge_error_to_shape_error)
1498 }
1499
1500 pub(crate) fn decode_document_trios(
1504 query: &DriveDocumentQuery<'a>,
1505 trios: Vec<PresentTrio>,
1506 platform_version: &PlatformVersion,
1507 ) -> Result<Vec<Document>, Error> {
1508 if query.document_type.index_only() {
1509 let index = query.index_only_query_index(platform_version)?;
1510 return trios
1514 .into_iter()
1515 .map(|(path, key, element)| {
1516 synthesize_index_only_document(
1517 query.contract.id(),
1518 query.document_type,
1519 index,
1520 &path,
1521 &key,
1522 Some(&element),
1523 platform_version,
1524 )
1525 })
1526 .collect();
1527 }
1528 trios
1529 .into_iter()
1530 .map(|(_, _, element)| {
1531 let serialized = element.into_item_bytes().map_err(Error::from)?;
1532 Document::from_bytes(serialized.as_slice(), query.document_type, platform_version)
1533 .map_err(|e| Error::Protocol(Box::new(e)))
1534 })
1535 .collect()
1536 }
1537
1538 pub(crate) fn decode_sub_query_document_trios(
1542 &self,
1543 sub_query: &DriveSubQuery<'a>,
1544 values: &[Identifier],
1545 direction: bool,
1546 trios: Vec<PresentTrio>,
1547 platform_version: &PlatformVersion,
1548 ) -> Result<Vec<Document>, Error> {
1549 let query = self.sub_query_document_query_with_direction(
1550 sub_query,
1551 values,
1552 direction,
1553 platform_version,
1554 )?;
1555 let documents = Self::decode_document_trios(&query, trios, platform_version)?;
1556 self.assemble_documents(sub_query, values, &documents)
1557 }
1558
1559 fn decode_count_trios(
1565 index: &Index,
1566 base_path_len: usize,
1567 trios: Vec<PresentTrio>,
1568 ) -> Vec<SplitCountEntry> {
1569 let mut entries = point_lookup_count_entries(
1571 index,
1572 base_path_len,
1573 true,
1574 trios
1575 .into_iter()
1576 .map(|(path, key, element)| (path, key, Some(element))),
1577 );
1578 entries.sort_by(|a, b| a.key.cmp(&b.key));
1581 entries
1582 }
1583
1584 fn sub_result_absences(
1593 &self,
1594 derived: &[DerivedValues],
1595 sub_results: &[SubQueryResult],
1596 removals: &BTreeMap<Vec<Vec<u8>>, BTreeMap<Identifier, ContractDocumentRemovalEntry>>,
1597 ) -> Result<SubResultAbsences, Error> {
1598 let none = BTreeMap::new();
1599 let mut missing_ids = Vec::with_capacity(self.sub_queries.len());
1600 let mut sub_result_removals = Vec::with_capacity(self.sub_queries.len());
1601 for ((sub_query, values), result) in self.sub_queries.iter().zip(derived).zip(sub_results) {
1602 let kind = self.sub_query_join_kind(sub_query);
1603 let missing: Vec<Identifier> = match kind {
1604 Some(DocumentReferenceKind::Deletable | DocumentReferenceKind::Moderated) => {
1605 let present: BTreeSet<Identifier> = result
1606 .documents()
1607 .iter()
1608 .map(|document| document.id())
1609 .collect();
1610 values
1611 .iter()
1612 .filter(|value| !present.contains(*value))
1613 .copied()
1614 .collect()
1615 }
1616 _ => Vec::new(),
1617 };
1618 if kind == Some(DocumentReferenceKind::Moderated) {
1619 let path = contract_document_type_removals_path_vec(
1620 sub_query.contract.id().as_slice(),
1621 sub_query.document_type.name(),
1622 );
1623 sub_result_removals.push(pair_missing_with_removals(
1624 &missing,
1625 removals.get(&path).unwrap_or(&none),
1626 )?);
1627 missing_ids.push(Vec::new());
1628 } else {
1629 sub_result_removals.push(Vec::new());
1630 missing_ids.push(missing);
1631 }
1632 }
1633 Ok((missing_ids, sub_result_removals))
1634 }
1635
1636 fn by_id_join_kind(&self, binding: &SubQueryBinding) -> DocumentReferenceKind {
1642 let source_type = match binding.source {
1643 BindingSource::Page => Some(self.document_type),
1644 BindingSource::SubQuery(source_index) => self
1645 .sub_queries
1646 .get(source_index)
1647 .map(|source| source.document_type),
1648 };
1649 let Some(source_type) = source_type else {
1650 return DocumentReferenceKind::Permanent;
1651 };
1652 source_type
1653 .flattened_properties()
1654 .get(binding.source_property.as_str())
1655 .and_then(|property| document_reference_of(&property.property_type))
1656 .map_or(DocumentReferenceKind::Permanent, |declaration| {
1657 declaration.kind
1658 })
1659 }
1660
1661 fn sub_query_join_kind(&self, sub_query: &DriveSubQuery<'a>) -> Option<DocumentReferenceKind> {
1664 match &sub_query.binding {
1665 Some(binding) if sub_query.is_by_id_join() => Some(self.by_id_join_kind(binding)),
1666 _ => None,
1667 }
1668 }
1669
1670 fn removal_path_queries(&self, derived: &[DerivedValues], direction: bool) -> Vec<PathQuery> {
1678 let mut ids_by_type: BTreeMap<(Identifier, &str), BTreeSet<Identifier>> = BTreeMap::new();
1679 for (sub_query, values) in self.sub_queries.iter().zip(derived) {
1680 if values.is_empty()
1681 || self.sub_query_join_kind(sub_query) != Some(DocumentReferenceKind::Moderated)
1682 {
1683 continue;
1684 }
1685 ids_by_type
1686 .entry((
1687 sub_query.contract.id(),
1688 sub_query.document_type.name().as_str(),
1689 ))
1690 .or_default()
1691 .extend(values.iter().copied());
1692 }
1693 ids_by_type
1694 .into_iter()
1695 .map(|((contract_id, document_type_name), ids)| {
1696 let ids: Vec<Identifier> = ids.into_iter().collect();
1697 removals_path_query(contract_id, document_type_name, &ids, direction)
1698 })
1699 .collect()
1700 }
1701
1702 fn assemble_documents(
1714 &self,
1715 sub_query: &DriveSubQuery<'a>,
1716 values: &[Identifier],
1717 documents: &[Document],
1718 ) -> Result<Vec<Document>, Error> {
1719 let Some(binding) = &sub_query.binding else {
1720 return Ok(documents.to_vec());
1721 };
1722 let admitted: BTreeSet<Identifier> = values.iter().copied().collect();
1723 if sub_query.is_by_id_join() {
1724 let mut by_id: BTreeMap<Identifier, &Document> = BTreeMap::new();
1725 for document in documents {
1726 let id = document.id();
1727 if !admitted.contains(&id) {
1728 continue;
1730 }
1731 if by_id.insert(id, document).is_some() {
1732 return Err(corrupted_proof(format!(
1733 "composite join results carry document {} twice",
1734 id
1735 )));
1736 }
1737 }
1738 let target_is_permanent = self.by_id_join_kind(binding).is_permanent();
1739 let mut ordered = Vec::with_capacity(values.len());
1740 for value in values {
1741 match by_id.remove(value) {
1742 Some(document) => ordered.push(document.clone()),
1743 None if target_is_permanent => {
1744 return Err(corrupted_proof(format!(
1745 "composite join results are missing referenced document {}: a \
1746 permanentDocument reference cannot dangle, so the proof does \
1747 not cover the derived query",
1748 value
1749 )));
1750 }
1751 None => {}
1755 }
1756 }
1757 return Ok(ordered);
1758 }
1759 let mut mine = Vec::new();
1760 for document in documents {
1761 match document_bound_value(document, &binding.field)? {
1762 Some(value) if admitted.contains(&value) => mine.push(document.clone()),
1763 _ => {}
1764 }
1765 }
1766 Ok(mine)
1767 }
1768
1769 fn assemble_counts(
1772 values: &[Identifier],
1773 entries: Vec<SplitCountEntry>,
1774 ) -> Result<Vec<SplitCountEntry>, Error> {
1775 let admitted: BTreeSet<Identifier> = values.iter().copied().collect();
1776 let mut mine = Vec::with_capacity(entries.len());
1777 for entry in entries {
1778 let Ok(value) = Identifier::from_bytes(&entry.key) else {
1779 return Err(corrupted_proof(
1780 "a composite count entry is keyed by something other than an identifier"
1781 .to_string(),
1782 ));
1783 };
1784 if admitted.contains(&value) {
1785 mine.push(entry);
1786 }
1787 }
1788 Ok(mine)
1789 }
1790
1791 #[allow(clippy::too_many_arguments)]
1800 pub(crate) fn assemble_from_trios(
1801 &self,
1802 derived: &[DerivedValues],
1803 page_path_query: &PathQuery,
1804 sub_path_queries: &[Option<PathQuery>],
1805 removal_path_queries: &[PathQuery],
1806 trios: Vec<ProvedTrio>,
1807 sum_bearing_items_are_documents: bool,
1808 platform_version: &PlatformVersion,
1809 ) -> Result<CompositeDocumentsResult, Error> {
1810 let direction = page_path_query.query.query.left_to_right;
1815 let mut groups: Vec<(Vec<Vec<u8>>, Vec<Component>)> = Vec::new();
1816 let mut count_members_by_position: BTreeMap<_, Vec<usize>> = BTreeMap::new();
1817 let mut register = |path: &Vec<Vec<u8>>, component: Component| {
1818 if let Some((_, members)) = groups.iter_mut().find(|(p, _)| p == path) {
1819 members.push(component);
1820 } else {
1821 groups.push((path.clone(), vec![component]));
1822 }
1823 };
1824 register(&page_path_query.path, Component::Page);
1825 for (index, path_query) in sub_path_queries.iter().enumerate() {
1826 if let Some(path_query) = path_query {
1827 if self.sub_queries[index].kind == SubQueryKind::Count {
1828 for position in path_query
1829 .terminal_keys(MAX_BOUND_VALUES, &platform_version.drive.grove_version)?
1830 {
1831 count_members_by_position
1832 .entry(position)
1833 .or_default()
1834 .push(index);
1835 }
1836 } else {
1837 register(&path_query.path, Component::Sub(index));
1838 }
1839 }
1840 }
1841
1842 let mut trios_by_group: Vec<Vec<PresentTrio>> = vec![Vec::new(); groups.len()];
1845 let mut count_trios_by_sub: Vec<Vec<PresentTrio>> =
1846 vec![Vec::new(); self.sub_queries.len()];
1847 let mut removal_entries_by_path: RemovalEntriesByPath = removal_path_queries
1850 .iter()
1851 .map(|path_query| (path_query.path.clone(), Vec::new()))
1852 .collect();
1853 for (path, key, element) in trios {
1854 let Some(element) = element else {
1855 continue;
1856 };
1857 if let Some(entries) = removal_entries_by_path.get_mut(&path) {
1858 entries.push((key, element));
1859 continue;
1860 }
1861 let is_document = if sum_bearing_items_are_documents {
1867 element.has_basic_item()
1868 } else {
1869 matches!(element, Element::Item(..))
1870 };
1871 if !is_document {
1872 let position = (path, key);
1873 let members = count_members_by_position.get(&position).ok_or_else(|| {
1874 corrupted_proof(
1875 "the composite proof carries a count at a position no component \
1876 selected"
1877 .to_string(),
1878 )
1879 })?;
1880 let (last, others) = members.split_last().ok_or_else(|| {
1882 Error::Drive(DriveError::CorruptedCodeExecution(
1883 "a registered count position has at least one member",
1884 ))
1885 })?;
1886 for index in others {
1887 count_trios_by_sub[*index].push((
1888 position.0.clone(),
1889 position.1.clone(),
1890 element.clone(),
1891 ));
1892 }
1893 count_trios_by_sub[*last].push((position.0, position.1, element));
1894 continue;
1895 }
1896 let best = groups
1897 .iter()
1898 .enumerate()
1899 .filter(|(_, (base, _))| path.starts_with(base))
1900 .max_by_key(|(_, (base, _))| base.len())
1901 .map(|(index, _)| index)
1902 .ok_or_else(|| {
1903 corrupted_proof(
1904 "the composite proof proved an entry outside every component's \
1905 subtree"
1906 .to_string(),
1907 )
1908 })?;
1909 trios_by_group[best].push((path, key, element));
1910 }
1911
1912 let mut page_documents: Option<Vec<Document>> = None;
1915 let mut sub_results: Vec<Option<SubQueryResult>> = vec![None; self.sub_queries.len()];
1916 for ((_, documents_members), document_trios) in groups.iter().zip(trios_by_group) {
1917 let documents = match documents_members[0] {
1920 Component::Page => {
1921 Self::decode_document_trios(self, document_trios, platform_version)?
1922 }
1923 Component::Sub(index) => {
1924 let query = self.sub_query_document_query_with_direction(
1925 &self.sub_queries[index],
1926 &derived[index],
1927 direction,
1928 platform_version,
1929 )?;
1930 Self::decode_document_trios(&query, document_trios, platform_version)?
1931 }
1932 };
1933 let mut claimed: BTreeSet<usize> = BTreeSet::new();
1934 for member in documents_members {
1935 match member {
1936 Component::Page => {
1937 let page_ids: Option<BTreeSet<Identifier>> = if documents_members.len() > 1
1938 {
1939 Some(self.page_ids()?)
1940 } else {
1941 None
1942 };
1943 let mut mine = Vec::new();
1944 for (position, document) in documents.iter().enumerate() {
1945 let is_mine = page_ids
1946 .as_ref()
1947 .is_none_or(|ids| ids.contains(&document.id()));
1948 if is_mine {
1949 claimed.insert(position);
1950 mine.push(document.clone());
1951 }
1952 }
1953 page_documents = Some(mine);
1954 }
1955 Component::Sub(index) => {
1956 let sub_query = &self.sub_queries[*index];
1957 let mine =
1958 self.assemble_documents(sub_query, &derived[*index], &documents)?;
1959 let mine_ids: BTreeSet<Identifier> =
1960 mine.iter().map(|document| document.id()).collect();
1961 for (position, document) in documents.iter().enumerate() {
1962 if mine_ids.contains(&document.id()) {
1963 claimed.insert(position);
1964 }
1965 }
1966 sub_results[*index] = Some(SubQueryResult::Documents(mine));
1967 }
1968 }
1969 }
1970 if claimed.len() != documents.len() {
1971 return Err(corrupted_proof(
1972 "the composite proof carries a document that no component's \
1973 derivation asked for"
1974 .to_string(),
1975 ));
1976 }
1977 }
1978
1979 for (index, count_trios) in count_trios_by_sub.into_iter().enumerate() {
1980 if self.sub_queries[index].kind != SubQueryKind::Count {
1981 continue;
1982 }
1983 let Some(path_query) = &sub_path_queries[index] else {
1984 continue;
1985 };
1986 let values = &derived[index];
1990 let count_query = self.sub_query_count_query(
1991 &self.sub_queries[index],
1992 &values[..values.len().min(1)],
1993 platform_version,
1994 )?;
1995 let entries =
1996 Self::decode_count_trios(count_query.index, path_query.path.len(), count_trios);
1997 sub_results[index] = Some(SubQueryResult::Counts(Self::assemble_counts(
1998 &derived[index],
1999 entries,
2000 )?));
2001 }
2002
2003 let sub_results: Vec<SubQueryResult> = sub_results
2004 .into_iter()
2005 .zip(&self.sub_queries)
2006 .map(|(result, sub_query)| {
2007 result.unwrap_or_else(|| match sub_query.kind {
2008 SubQueryKind::Documents => SubQueryResult::Documents(Vec::new()),
2009 SubQueryKind::Count => SubQueryResult::Counts(Vec::new()),
2010 })
2011 })
2012 .collect();
2013 let removals = removal_entries_by_path
2014 .into_iter()
2015 .map(|(path, entries)| Ok((path, decode_removals(entries)?)))
2016 .collect::<Result<BTreeMap<_, _>, Error>>()?;
2017 let (sub_result_missing_ids, sub_result_removals) =
2018 self.sub_result_absences(derived, &sub_results, &removals)?;
2019 Ok(CompositeDocumentsResult {
2020 page_documents: page_documents.unwrap_or_default(),
2021 sub_results,
2022 sub_result_missing_ids,
2023 sub_result_removals,
2024 })
2025 }
2026
2027 fn page_ids(&self) -> Result<BTreeSet<Identifier>, Error> {
2031 let mut ids = BTreeSet::new();
2032 if let Some(clause) = &self.internal_clauses.primary_key_equal_clause {
2035 ids.insert(clause.value.to_identifier().map_err(|_| {
2036 Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
2037 "primary key equality must compare an identifier",
2038 ))
2039 })?);
2040 }
2041 if let Some(clause) = &self.internal_clauses.primary_key_in_clause {
2042 for value in clause.in_values().into_data_with_error()??.iter() {
2043 ids.insert(value.to_identifier().map_err(|_| {
2044 Error::Query(QuerySyntaxError::InvalidWhereClauseComponents(
2045 "primary key IN must contain identifiers",
2046 ))
2047 })?);
2048 }
2049 }
2050 Ok(ids)
2051 }
2052
2053 pub(crate) fn derive_for<'d>(
2062 &self,
2063 sub_query: &DriveSubQuery<'a>,
2064 page_documents: &[Document],
2065 sub_documents: impl Fn(usize) -> Option<&'d [Document]>,
2066 ) -> Result<DerivedValues, Error> {
2067 let Some(binding) = &sub_query.binding else {
2068 return Ok(Vec::new());
2069 };
2070 match binding.source {
2071 BindingSource::Page => self.derive_values(binding, page_documents),
2072 BindingSource::SubQuery(source_index) => {
2073 let documents = sub_documents(source_index).ok_or_else(|| {
2074 Error::Drive(DriveError::CorruptedCodeExecution(
2075 "a binding's source sub-query was not materialized before it",
2076 ))
2077 })?;
2078 self.derive_values(binding, documents)
2079 }
2080 }
2081 }
2082
2083 pub fn derive_all<'d>(
2086 &self,
2087 page_documents: &[Document],
2088 sub_documents: impl Fn(usize) -> Option<&'d [Document]>,
2089 ) -> Result<Vec<DerivedValues>, Error> {
2090 self.sub_queries
2091 .iter()
2092 .map(|sub_query| self.derive_for(sub_query, page_documents, &sub_documents))
2093 .collect()
2094 }
2095
2096 pub(crate) fn is_binding_source(&self, index: usize) -> bool {
2098 self.sub_queries.iter().any(|sub_query| {
2099 matches!(
2100 sub_query.binding,
2101 Some(SubQueryBinding {
2102 source: BindingSource::SubQuery(source),
2103 ..
2104 }) if source == index
2105 )
2106 })
2107 }
2108}
2109
2110#[cfg(feature = "server")]
2111impl<'a> DriveDocumentQuery<'a> {
2112 pub(crate) fn materialize_component(
2124 query: &DriveDocumentQuery<'a>,
2125 path_query: &PathQuery,
2126 drive: &crate::drive::Drive,
2127 transaction: grovedb::TransactionArg,
2128 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2129 platform_version: &PlatformVersion,
2130 ) -> Result<Vec<Document>, Error> {
2131 use grovedb::query_result_type::QueryResultType;
2132
2133 if query.document_type.index_only() {
2134 let results = match drive.grove_get_path_query(
2135 path_query,
2136 transaction,
2137 QueryResultType::QueryPathKeyElementTrioResultType,
2138 drive_operations,
2139 &platform_version.drive,
2140 ) {
2141 Err(error) if is_absent_path(&error) => return Ok(Vec::new()),
2142 other => other?.0,
2143 };
2144 return Self::decode_document_trios(
2145 query,
2146 results.to_path_key_elements(),
2147 platform_version,
2148 );
2149 }
2150 let serialized = match drive.grove_get_path_query_serialized_results(
2153 path_query,
2154 transaction,
2155 drive_operations,
2156 &platform_version.drive,
2157 ) {
2158 Err(error) if is_absent_path(&error) => return Ok(Vec::new()),
2159 other => other?.0,
2160 };
2161 serialized
2162 .into_iter()
2163 .map(|bytes| {
2164 Document::from_bytes(bytes.as_slice(), query.document_type, platform_version)
2165 .map_err(|e| Error::Protocol(Box::new(e)))
2166 })
2167 .collect()
2168 }
2169
2170 #[allow(clippy::too_many_arguments)]
2174 fn materialize_sub_result(
2175 &self,
2176 sub_query: &DriveSubQuery<'a>,
2177 values: &[Identifier],
2178 direction: bool,
2179 drive: &crate::drive::Drive,
2180 transaction: grovedb::TransactionArg,
2181 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2182 platform_version: &PlatformVersion,
2183 ) -> Result<SubQueryResult, Error> {
2184 use grovedb::query_result_type::{QueryResultElement, QueryResultType};
2185
2186 if sub_query.binding.is_some() && values.is_empty() {
2187 return Ok(match sub_query.kind {
2188 SubQueryKind::Documents => SubQueryResult::Documents(Vec::new()),
2189 SubQueryKind::Count => SubQueryResult::Counts(Vec::new()),
2190 });
2191 }
2192 match sub_query.kind {
2193 SubQueryKind::Documents => {
2194 let query = self.sub_query_document_query_with_direction(
2195 sub_query,
2196 values,
2197 direction,
2198 platform_version,
2199 )?;
2200 let path_query = self.sub_query_proof_path_query(
2201 sub_query,
2202 values,
2203 direction,
2204 platform_version,
2205 )?;
2206 check_composite_instance_limits(&path_query, platform_version)?;
2207 let documents = Self::materialize_component(
2208 &query,
2209 &path_query,
2210 drive,
2211 transaction,
2212 drive_operations,
2213 platform_version,
2214 )?;
2215 Ok(SubQueryResult::Documents(
2216 self.assemble_documents(sub_query, values, &documents)?,
2217 ))
2218 }
2219 SubQueryKind::Count => {
2220 let count_query =
2221 self.sub_query_count_query(sub_query, values, platform_version)?;
2222 let path_query = count_query.point_lookup_count_path_query(platform_version)?;
2223 let base_path_len = path_query.path.len();
2224 check_composite_instance_limits(&path_query, platform_version)?;
2225 let (results, _skipped) = match drive.grove_get_path_query(
2226 &path_query,
2227 transaction,
2228 QueryResultType::QueryPathKeyElementTrioResultType,
2229 drive_operations,
2230 &platform_version.drive,
2231 ) {
2232 Err(error) if is_absent_path(&error) => {
2234 return Ok(SubQueryResult::Counts(Vec::new()));
2235 }
2236 other => other?,
2237 };
2238 let trios = results
2239 .elements
2240 .into_iter()
2241 .filter_map(|element| match element {
2242 QueryResultElement::PathKeyElementTrioResultItem(trio) => Some(trio),
2243 _ => None,
2244 })
2245 .collect();
2246 let entries = Self::decode_count_trios(count_query.index, base_path_len, trios);
2247 Ok(SubQueryResult::Counts(Self::assemble_counts(
2248 values, entries,
2249 )?))
2250 }
2251 }
2252 }
2253
2254 pub(crate) fn execute_composite_no_proof_internal(
2256 &self,
2257 drive: &crate::drive::Drive,
2258 transaction: grovedb::TransactionArg,
2259 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2260 platform_version: &PlatformVersion,
2261 ) -> Result<CompositeDocumentsResult, Error> {
2262 self.validate_composite(platform_version)?;
2263
2264 let page_path_query = self.page_path_query(platform_version)?;
2265 let direction = page_path_query.query.query.left_to_right;
2266 self.refuse_an_uncovered_index_only_projection(platform_version)?;
2274 for sub_query in &self.sub_queries {
2275 if sub_query.kind == SubQueryKind::Documents && sub_query.document_type.index_only() {
2276 self.sub_query_document_query_with_direction(
2277 sub_query,
2278 &[Identifier::default()],
2279 direction,
2280 platform_version,
2281 )?
2282 .refuse_an_uncovered_index_only_projection(platform_version)?;
2283 }
2284 }
2285 check_composite_instance_limits(&page_path_query, platform_version)?;
2286 let page_documents = Self::materialize_component(
2287 self,
2288 &page_path_query,
2289 drive,
2290 transaction,
2291 drive_operations,
2292 platform_version,
2293 )?;
2294 let mut sub_results: Vec<SubQueryResult> = Vec::with_capacity(self.sub_queries.len());
2295 let mut derived = Vec::with_capacity(self.sub_queries.len());
2296 for sub_query in &self.sub_queries {
2297 let values = self.derive_for(sub_query, &page_documents, |source| {
2298 sub_results.get(source).map(|result| result.documents())
2299 })?;
2300 sub_results.push(self.materialize_sub_result(
2301 sub_query,
2302 &values,
2303 direction,
2304 drive,
2305 transaction,
2306 drive_operations,
2307 platform_version,
2308 )?);
2309 derived.push(values);
2310 }
2311 let (_, _, removal_path_queries) = self.proof_path_queries(&derived, platform_version)?;
2315 let mut removals = BTreeMap::new();
2317 for path_query in &removal_path_queries {
2318 removals.insert(
2319 path_query.path.clone(),
2320 fetch_removals(
2321 drive,
2322 path_query,
2323 transaction,
2324 drive_operations,
2325 platform_version,
2326 )?,
2327 );
2328 }
2329 let (sub_result_missing_ids, sub_result_removals) =
2330 self.sub_result_absences(&derived, &sub_results, &removals)?;
2331 Ok(CompositeDocumentsResult {
2332 page_documents,
2333 sub_results,
2334 sub_result_missing_ids,
2335 sub_result_removals,
2336 })
2337 }
2338
2339 pub(crate) fn execute_composite_with_proof_internal(
2356 &self,
2357 drive: &crate::drive::Drive,
2358 drive_operations: &mut Vec<crate::fees::op::LowLevelDriveOperation>,
2359 platform_version: &PlatformVersion,
2360 ) -> Result<(Vec<u8>, Vec<Document>), Error> {
2361 self.validate_composite(platform_version)?;
2362 let page_path_query = self.page_path_query(platform_version)?;
2363 let direction = page_path_query.query.query.left_to_right;
2364
2365 const MAX_ATTEMPTS: usize = 3;
2369 for _ in 0..MAX_ATTEMPTS {
2370 let operations_before = drive_operations.len();
2373 let root_before = drive
2374 .grove
2375 .root_hash(None, &platform_version.drive.grove_version)
2376 .unwrap()?;
2377
2378 check_composite_instance_limits(&page_path_query, platform_version)?;
2379 let page_documents = Self::materialize_component(
2380 self,
2381 &page_path_query,
2382 drive,
2383 None,
2384 drive_operations,
2385 platform_version,
2386 )?;
2387 let mut derived: Vec<DerivedValues> = Vec::with_capacity(self.sub_queries.len());
2390 let mut materialized: Vec<Option<Vec<Document>>> = vec![None; self.sub_queries.len()];
2391 for (index, sub_query) in self.sub_queries.iter().enumerate() {
2392 let values = self.derive_for(sub_query, &page_documents, |source| {
2393 materialized
2394 .get(source)
2395 .and_then(|documents| documents.as_deref())
2396 })?;
2397 if self.is_binding_source(index) {
2398 let result = self.materialize_sub_result(
2399 sub_query,
2400 &values,
2401 direction,
2402 drive,
2403 None,
2404 drive_operations,
2405 platform_version,
2406 )?;
2407 materialized[index] = Some(result.documents().to_vec());
2408 }
2409 derived.push(values);
2410 }
2411
2412 let (page_path_query, sub_path_queries, removal_path_queries) =
2413 self.proof_path_queries(&derived, platform_version)?;
2414 let merged_query = Self::merged_path_query(
2417 &page_path_query,
2418 &sub_path_queries,
2419 &removal_path_queries,
2420 platform_version,
2421 )?;
2422 check_composite_instance_limits(&merged_query, platform_version)?;
2423 let proof = drive
2424 .grove
2425 .prove_query(&merged_query, None, &platform_version.drive.grove_version)
2426 .unwrap()?;
2427
2428 let root_after = drive
2429 .grove
2430 .root_hash(None, &platform_version.drive.grove_version)
2431 .unwrap()?;
2432 if root_before != root_after {
2433 drive_operations.truncate(operations_before);
2434 continue;
2435 }
2436 return Ok((proof, page_documents));
2437 }
2438 Err(Error::Drive(DriveError::NotSupported(
2439 "composite proof generation raced a block commit on every attempt; transient — \
2440 retry the request",
2441 )))
2442 }
2443}
2444
2445#[cfg(test)]
2446mod tests {
2447 use super::*;
2448 use grovedb::{Query, SizedQuery, SubqueryBranch};
2449
2450 #[test]
2455 fn should_follow_a_walk_through_selected_keys_and_subqueries_only() {
2456 let pv = PlatformVersion::latest();
2457 let key = |name: &str| name.as_bytes().to_vec();
2458 let mut body = Query::new();
2459 body.insert_key(key("x"));
2460 body.default_subquery_branch = SubqueryBranch {
2461 subquery_path: Some(vec![key("c")]),
2462 subquery: Some(Box::new(Query::new_range_full())),
2463 };
2464 let shallower = PathQuery::new(vec![key("a"), key("b")], SizedQuery::new(body, None, None));
2465 let descends = |path: &[&str]| {
2466 DriveDocumentQuery::path_query_descends_through(
2467 &shallower,
2468 &path.iter().map(|segment| key(segment)).collect::<Vec<_>>(),
2469 pv,
2470 )
2471 .expect("the walk resolves")
2472 };
2473 assert!(
2474 descends(&["a", "b", "x", "c"]),
2475 "selected key, then its subquery path"
2476 );
2477 assert!(!descends(&["a", "b", "x", "d"]), "not the subquery path");
2478 assert!(!descends(&["a", "b", "y", "c"]), "an unselected key");
2479 assert!(!descends(&["a", "z"]), "off the base path");
2480 assert!(
2481 !descends(&["a", "b", "x", "c", "k"]),
2482 "past the walk's leaves"
2483 );
2484 }
2485}