Skip to main content

drive/query/drive_document_count_query/executors/
total.rs

1//! Total-count executor for [`super::super::DocumentCountMode::Total`]
2//! dispatch — `prove = false` count queries without a range clause.
3
4use super::super::super::conditions::WhereClause;
5use super::super::{DriveDocumentCountQuery, SplitCountEntry};
6use crate::drive::Drive;
7use crate::error::query::QuerySyntaxError;
8use crate::error::Error;
9use crate::query::ResolvedTimeRange;
10use dpp::data_contract::document_type::DocumentTypeRef;
11use dpp::version::PlatformVersion;
12use grovedb::TransactionArg;
13
14impl Drive {
15    /// Total count for the given where clauses against an exactly-
16    /// covering countable index, OR — when the where clauses are
17    /// empty and the document type has `documents_countable: true` —
18    /// the type's primary-key CountTree (O(1) read at the doctype
19    /// tree's root).
20    ///
21    /// Single summed entry with empty key.
22    #[allow(clippy::too_many_arguments)]
23    pub fn execute_document_count_total_no_proof(
24        &self,
25        contract_id: [u8; 32],
26        document_type: DocumentTypeRef,
27        document_type_name: String,
28        where_clauses: Vec<WhereClause>,
29        resolved_time_ranges: &[ResolvedTimeRange],
30        transaction: TransactionArg,
31        platform_version: &PlatformVersion,
32    ) -> Result<Vec<SplitCountEntry>, Error> {
33        use dpp::data_contract::document_type::accessors::{
34            DocumentTypeV0Getters, DocumentTypeV2Getters,
35        };
36
37        // Fast path: unfiltered total count on a `documents_countable:
38        // true` document type reads the primary-key CountTree directly
39        // (O(1)). No index needed — the doctype tree itself carries
40        // the count.
41        if where_clauses.is_empty() && document_type.documents_countable() {
42            let count = self.read_primary_key_count_tree(
43                &contract_id,
44                &document_type_name,
45                transaction,
46                platform_version,
47            )?;
48            return Ok(vec![SplitCountEntry {
49                in_key: None,
50                key: vec![],
51                // `documents_countable` fast path: we read the
52                // CountTree directly and got an explicit count, so
53                // this is a verified `Some(_)` (possibly `Some(0)`
54                // for an empty doctype).
55                count: Some(count),
56            }]);
57        }
58
59        let index = DriveDocumentCountQuery::find_countable_index_for_where_clauses(
60            document_type.indexes(),
61            &where_clauses,
62            resolved_time_ranges,
63        )
64        .ok_or_else(|| {
65            Error::Query(QuerySyntaxError::WhereClauseOnNonIndexedProperty(
66                "count query requires a `countable: true` index whose properties \
67                     exactly match the where clause fields, or `documentsCountable: \
68                     true` on the document type for unfiltered total counts"
69                    .to_string(),
70            ))
71        })?;
72        let count_query = DriveDocumentCountQuery {
73            document_type,
74            contract_id,
75            document_type_name,
76            index,
77            where_clauses,
78        };
79        count_query.execute_no_proof(self, transaction, platform_version)
80    }
81
82    /// Reads the document-type primary-key tree's `CountTree` element
83    /// (`[contract_doc, contract_id, [1], doctype, 0]`) and returns
84    /// `count_value_or_default()`. Used by the `documents_countable:
85    /// true` fast path on the total-count flow.
86    ///
87    /// Returns 0 when the element doesn't exist (e.g. fresh contract
88    /// with no documents inserted). Caller is responsible for ensuring
89    /// `documents_countable` is set on the document type before
90    /// calling — without it the element at `[..., doctype, 0]` is a
91    /// regular `NormalTree` and `count_value_or_default()` returns 0
92    /// regardless of how many documents the type actually has.
93    pub(super) fn read_primary_key_count_tree(
94        &self,
95        contract_id: &[u8; 32],
96        document_type_name: &str,
97        transaction: TransactionArg,
98        platform_version: &PlatformVersion,
99    ) -> Result<u64, Error> {
100        let drive_version = &platform_version.drive;
101        let path = [
102            &[crate::drive::RootTree::DataContractDocuments as u8] as &[u8],
103            contract_id,
104            &[1u8],
105            document_type_name.as_bytes(),
106        ];
107        let mut drive_operations = vec![];
108        let element = self.grove_get_raw_optional(
109            grovedb_path::SubtreePath::from(path.as_slice()),
110            &[0],
111            crate::util::grove_operations::DirectQueryType::StatefulDirectQuery,
112            transaction,
113            &mut drive_operations,
114            drive_version,
115        )?;
116        Ok(element.map_or(0, |e| e.count_value_or_default()))
117    }
118}