dash_platform_queries/documents/
array_element_clauses.rs1use crate::documents::document_query::DocumentQuery;
7use dpp::data_contract::accessors::v0::DataContractV0Getters;
8use dpp::data_contract::document_type::DocumentTypeRef;
9use dpp::data_contract::DataContract;
10use dpp::version::PlatformVersion;
11use drive::query::{
12 key_array_element_clauses, validate_and_canonicalize_where_clauses, WhereClause, WhereOperator,
13};
14
15pub fn key_element_clauses(
28 data_contract: &DataContract,
29 document_type_name: &str,
30 where_clauses: Vec<WhereClause>,
31 grouped_by_in: bool,
32 platform_version: &PlatformVersion,
33) -> Result<Vec<WhereClause>, drive_proof_verifier::Error> {
34 key_clauses_of(
35 document_type_named(data_contract, document_type_name)?,
36 where_clauses,
37 grouped_by_in,
38 platform_version,
39 )
40}
41
42pub(super) fn aggregate_request_where_clauses(
48 request: &DocumentQuery,
49 folds_entries: bool,
50 platform_version: &PlatformVersion,
51) -> Result<Vec<WhereClause>, drive_proof_verifier::Error> {
52 aggregate_where_clauses(
53 document_type_named(&request.data_contract, &request.document_type_name)?,
54 &request.group_by,
55 request.where_clauses.clone(),
56 folds_entries,
57 platform_version,
58 )
59}
60
61fn document_type_named<'a>(
62 data_contract: &'a DataContract,
63 document_type_name: &str,
64) -> Result<DocumentTypeRef<'a>, drive_proof_verifier::Error> {
65 data_contract
66 .document_type_for_name(document_type_name)
67 .map_err(|e| drive_proof_verifier::Error::RequestError {
68 error: format!("document type {document_type_name} not found in contract: {e}"),
69 })
70}
71
72pub fn grouped_by_in_field(group_by: &[String], where_clauses: &[WhereClause]) -> bool {
76 match group_by {
77 [field] => where_clauses.iter().any(|clause| {
78 matches!(
79 clause.operator,
80 WhereOperator::In | WhereOperator::ContainsAny
81 ) && clause.field == *field
82 }),
83 _ => false,
84 }
85}
86
87pub(super) fn aggregate_where_clauses(
100 document_type: DocumentTypeRef,
101 group_by: &[String],
102 where_clauses: Vec<WhereClause>,
103 folds_entries: bool,
104 platform_version: &PlatformVersion,
105) -> Result<Vec<WhereClause>, drive_proof_verifier::Error> {
106 let grouped_by_in = !folds_entries && grouped_by_in_field(group_by, &where_clauses);
107 let keyed = key_clauses_of(
108 document_type,
109 where_clauses,
110 grouped_by_in,
111 platform_version,
112 )?;
113 validate_and_canonicalize_where_clauses(keyed, platform_version).map_err(|e| {
114 drive_proof_verifier::Error::RequestError {
115 error: format!("where-clause canonicalization failed: {e}"),
116 }
117 })
118}
119
120fn key_clauses_of(
121 document_type: DocumentTypeRef,
122 where_clauses: Vec<WhereClause>,
123 grouped_by_in: bool,
124 platform_version: &PlatformVersion,
125) -> Result<Vec<WhereClause>, drive_proof_verifier::Error> {
126 key_array_element_clauses(
127 where_clauses,
128 document_type,
129 grouped_by_in,
130 platform_version,
131 )
132 .map_err(|e| drive_proof_verifier::Error::RequestError {
133 error: format!("where-clause keying failed: {e}"),
134 })
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140 use dpp::data_contract::DataContractFactory;
141 use dpp::platform_value::{platform_value, Identifier, Value};
142
143 fn post_contract() -> DataContract {
144 DataContractFactory::new(PlatformVersion::latest().protocol_version)
145 .expect("factory")
146 .create_with_value_config(
147 Identifier::new([7u8; 32]),
148 0,
149 platform_value!({"post": {
150 "type": "object",
151 "properties": {
152 "hashtags": {
153 "type": "array",
154 "maxItems": 10,
155 "items": {"type": "string", "minLength": 1, "maxLength": 61},
156 "position": 0
157 }
158 },
159 "required": ["$createdAt"],
160 "indices": [
161 {"name": "byHashtagTime",
162 "properties": [{"hashtags": "asc"}, {"$createdAt": "asc"}],
163 "countable": "countable"}
164 ],
165 "additionalProperties": false
166 }}),
167 None,
168 None,
169 )
170 .expect("the post contract is well-formed")
171 .data_contract_owned()
172 }
173
174 fn several_hashtags() -> Vec<WhereClause> {
175 vec![WhereClause {
176 field: "hashtags".to_string(),
177 operator: WhereOperator::ContainsAny,
178 value: platform_value!(["dash", "news"]),
179 }]
180 }
181
182 fn created_after_zero() -> WhereClause {
183 WhereClause {
184 field: "$createdAt".to_string(),
185 operator: WhereOperator::GreaterThan,
186 value: Value::U64(0),
187 }
188 }
189
190 #[test]
191 fn should_answer_several_elements_only_grouped_by_the_array_alone() {
192 let contract = post_contract();
193 let platform_version = PlatformVersion::latest();
194 let by_hashtags = ["hashtags".to_string()];
195 let several = several_hashtags();
196 assert!(grouped_by_in_field(&by_hashtags, &several));
197 assert!(!grouped_by_in_field(&[], &several));
198 assert!(!grouped_by_in_field(
199 &["hashtags".to_string(), "$createdAt".to_string()],
200 &several
201 ));
202 let mut in_a_range = several;
204 in_a_range.push(created_after_zero());
205 key_element_clauses(&contract, "post", in_a_range, true, platform_version)
206 .expect_err("several elements under a range are refused");
207 }
208
209 #[test]
213 fn should_refuse_to_verify_a_total_over_several_elements() {
214 let contract = post_contract();
215 let platform_version = PlatformVersion::latest();
216 let several = several_hashtags();
217
218 let error = key_element_clauses(
219 &contract,
220 "post",
221 several.clone(),
222 grouped_by_in_field(&[], &several),
223 platform_version,
224 )
225 .expect_err("an ungrouped total over several elements is refused");
226 assert!(
227 format!("{error:?}").contains("only answered grouped by"),
228 "{error:?}"
229 );
230
231 let keyed = key_element_clauses(
232 &contract,
233 "post",
234 several.clone(),
235 grouped_by_in_field(&["hashtags".to_string()], &several),
236 platform_version,
237 )
238 .expect("grouped by the array, one total per element");
239 assert_eq!(keyed[0].operator, WhereOperator::In);
240
241 let one = vec![WhereClause {
242 field: "hashtags".to_string(),
243 operator: WhereOperator::Contains,
244 value: Value::Text("dash".to_string()),
245 }];
246 let keyed = key_element_clauses(&contract, "post", one, false, platform_version)
247 .expect("one element is a total of its own");
248 assert_eq!(keyed[0].operator, WhereOperator::Equal);
249 }
250}