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, a `rangeCountable: true` \
68                     index they cover up to its last property, or \
69                     `documentsCountable: true` on the document type for unfiltered \
70                     total counts"
71                    .to_string(),
72            ))
73        })?;
74        let count_query = DriveDocumentCountQuery {
75            document_type,
76            contract_id,
77            document_type_name,
78            index,
79            where_clauses,
80        };
81        count_query.execute_no_proof(self, transaction, platform_version)
82    }
83
84    /// Reads the document-type primary-key tree's `CountTree` element
85    /// (`[contract_doc, contract_id, [1], doctype, 0]`) and returns
86    /// `count_value_or_default()`. Used by the `documents_countable:
87    /// true` fast path on the total-count flow.
88    ///
89    /// Returns 0 when the element doesn't exist (e.g. fresh contract
90    /// with no documents inserted). Caller is responsible for ensuring
91    /// `documents_countable` is set on the document type before
92    /// calling — without it the element at `[..., doctype, 0]` is a
93    /// regular `NormalTree` and `count_value_or_default()` returns 0
94    /// regardless of how many documents the type actually has.
95    pub(super) fn read_primary_key_count_tree(
96        &self,
97        contract_id: &[u8; 32],
98        document_type_name: &str,
99        transaction: TransactionArg,
100        platform_version: &PlatformVersion,
101    ) -> Result<u64, Error> {
102        let drive_version = &platform_version.drive;
103        let path = [
104            &[crate::drive::RootTree::DataContractDocuments as u8] as &[u8],
105            contract_id,
106            &[1u8],
107            document_type_name.as_bytes(),
108        ];
109        let mut drive_operations = vec![];
110        let element = self.grove_get_raw_optional(
111            grovedb_path::SubtreePath::from(path.as_slice()),
112            &[0],
113            crate::util::grove_operations::DirectQueryType::StatefulDirectQuery,
114            transaction,
115            &mut drive_operations,
116            drive_version,
117        )?;
118        Ok(element.map_or(0, |e| e.count_value_or_default()))
119    }
120}