1use crate::pool::SharedState;
2
3macro_rules! impl_with_annotations_mut {
9 () => {
10 #[must_use]
17 pub fn with_annotations(
18 &mut self,
19 annotations: crate::annotations::QueryAnnotations,
20 ) -> crate::annotations::AnnotatedMut<'_, Self> {
21 crate::annotations::AnnotatedMut {
22 state: self.state.clone(),
23 annotations,
24 inner: self,
25 }
26 }
27
28 #[must_use]
34 pub fn with_operation(
35 &mut self,
36 operation: impl Into<String>,
37 collection: impl Into<String>,
38 ) -> crate::annotations::AnnotatedMut<'_, Self> {
39 self.with_annotations(
40 crate::annotations::QueryAnnotations::new()
41 .operation(operation)
42 .collection(collection),
43 )
44 }
45 };
46}
47
48#[derive(Debug, Clone, Default, PartialEq, Eq)]
86pub struct QueryAnnotations {
87 pub(crate) operation: Option<String>,
89 pub(crate) collection: Option<String>,
91 pub(crate) query_summary: Option<String>,
93 pub(crate) stored_procedure: Option<String>,
95}
96
97impl QueryAnnotations {
98 #[must_use]
100 pub fn new() -> Self {
101 Self::default()
102 }
103
104 #[must_use]
113 pub fn operation(mut self, operation: impl Into<String>) -> Self {
114 self.operation = Some(operation.into());
115 self
116 }
117
118 #[must_use]
121 pub fn collection(mut self, collection: impl Into<String>) -> Self {
122 self.collection = Some(collection.into());
123 self
124 }
125
126 #[must_use]
134 pub fn query_summary(mut self, summary: impl Into<String>) -> Self {
135 self.query_summary = Some(summary.into());
136 self
137 }
138
139 #[must_use]
142 pub fn stored_procedure(mut self, name: impl Into<String>) -> Self {
143 self.stored_procedure = Some(name.into());
144 self
145 }
146}
147
148pub struct Annotated<'a, E> {
155 pub(crate) inner: &'a E,
156 pub(crate) annotations: QueryAnnotations,
157 pub(crate) state: SharedState,
158}
159
160impl<E: std::fmt::Debug> std::fmt::Debug for Annotated<'_, E> {
161 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
162 f.debug_struct("Annotated")
163 .field("annotations", &self.annotations)
164 .finish_non_exhaustive()
165 }
166}
167
168pub struct AnnotatedMut<'a, E> {
177 pub(crate) inner: &'a mut E,
178 pub(crate) annotations: QueryAnnotations,
179 pub(crate) state: SharedState,
180}
181
182impl<E: std::fmt::Debug> std::fmt::Debug for AnnotatedMut<'_, E> {
183 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184 f.debug_struct("AnnotatedMut")
185 .field("annotations", &self.annotations)
186 .finish_non_exhaustive()
187 }
188}
189
190#[cfg(test)]
191mod tests {
192 use super::*;
193
194 #[test]
197 fn setter_permutations() {
198 type Setter = fn(QueryAnnotations) -> QueryAnnotations;
199 type Getter = fn(&QueryAnnotations) -> Option<&str>;
200
201 let fields: &[(&str, Setter, Getter)] = &[
202 (
203 "operation",
204 |a| a.operation("OP"),
205 |a| a.operation.as_deref(),
206 ),
207 (
208 "collection",
209 |a| a.collection("COLL"),
210 |a| a.collection.as_deref(),
211 ),
212 (
213 "query_summary",
214 |a| a.query_summary("SUM"),
215 |a| a.query_summary.as_deref(),
216 ),
217 (
218 "stored_procedure",
219 |a| a.stored_procedure("SP"),
220 |a| a.stored_procedure.as_deref(),
221 ),
222 ];
223
224 for mask in 0u8..16 {
225 let mut ann = QueryAnnotations::new();
226 for (i, &(_, setter, _)) in fields.iter().enumerate() {
227 if mask & (1 << i) != 0 {
228 ann = setter(ann);
229 }
230 }
231 for (i, &(name, _, getter)) in fields.iter().enumerate() {
232 if mask & (1 << i) != 0 {
233 assert!(
234 getter(&ann).is_some(),
235 "{name} should be Some for mask {mask:#06b}"
236 );
237 } else {
238 assert!(
239 getter(&ann).is_none(),
240 "{name} should be None for mask {mask:#06b}"
241 );
242 }
243 }
244 }
245 }
246
247 #[test]
248 fn clone_produces_independent_copy() {
249 let original = QueryAnnotations::new()
250 .operation("SELECT")
251 .collection("users");
252 let cloned = original.clone();
253 let modified = original.query_summary("SELECT users");
254 assert_eq!(cloned.query_summary, None);
255 assert_eq!(modified.query_summary.as_deref(), Some("SELECT users"));
256 }
257
258 #[test]
259 fn debug_impl_is_non_empty() {
260 let ann = QueryAnnotations::new().operation("SELECT");
261 let debug = format!("{ann:?}");
262 assert!(debug.contains("SELECT"));
263 }
264
265 fn test_state() -> SharedState {
266 use std::sync::Arc;
267
268 use crate::attributes::{ConnectionAttributes, QueryTextMode};
269 use crate::metrics::Metrics;
270
271 SharedState {
272 attrs: Arc::new(ConnectionAttributes {
273 system: "sqlite",
274 host: None,
275 port: None,
276 namespace: None,
277 network_peer_address: None,
278 network_peer_port: None,
279 network_protocol_name: None,
280 network_transport: None,
281 pool_name: None,
282 query_text_mode: QueryTextMode::Off,
283 }),
284 metrics: Arc::new(Metrics::new()),
285 }
286 }
287
288 #[test]
289 fn annotated_debug() {
290 let inner = "pool";
291 let wrapper = Annotated {
292 inner: &inner,
293 annotations: QueryAnnotations::new().operation("SELECT"),
294 state: test_state(),
295 };
296 let debug = format!("{wrapper:?}");
297 assert!(debug.contains("Annotated"));
298 assert!(debug.contains("SELECT"));
299 }
300
301 #[test]
302 fn annotated_mut_debug() {
303 let mut inner = "conn";
304 let wrapper = AnnotatedMut {
305 inner: &mut inner,
306 annotations: QueryAnnotations::new().collection("users"),
307 state: test_state(),
308 };
309 let debug = format!("{wrapper:?}");
310 assert!(debug.contains("AnnotatedMut"));
311 assert!(debug.contains("users"));
312 }
313
314 use proptest::prelude::*;
315
316 proptest! {
317 #![proptest_config(ProptestConfig::with_cases(128))]
318
319 #[test]
322 fn operation_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
323 let ann = QueryAnnotations::new().operation(a).operation(b.clone());
324 prop_assert_eq!(ann.operation.as_deref(), Some(b.as_str()));
325 }
326
327 #[test]
328 fn collection_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
329 let ann = QueryAnnotations::new().collection(a).collection(b.clone());
330 prop_assert_eq!(ann.collection.as_deref(), Some(b.as_str()));
331 }
332
333 #[test]
334 fn query_summary_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
335 let ann = QueryAnnotations::new().query_summary(a).query_summary(b.clone());
336 prop_assert_eq!(ann.query_summary.as_deref(), Some(b.as_str()));
337 }
338
339 #[test]
340 fn stored_procedure_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
341 let ann = QueryAnnotations::new().stored_procedure(a).stored_procedure(b.clone());
342 prop_assert_eq!(ann.stored_procedure.as_deref(), Some(b.as_str()));
343 }
344
345 #[test]
348 fn operation_does_not_affect_other_fields(s in ".{0,64}") {
349 let ann = QueryAnnotations::new().operation(s);
350 prop_assert!(ann.collection.is_none());
351 prop_assert!(ann.query_summary.is_none());
352 prop_assert!(ann.stored_procedure.is_none());
353 }
354
355 #[test]
356 fn collection_does_not_affect_other_fields(s in ".{0,64}") {
357 let ann = QueryAnnotations::new().collection(s);
358 prop_assert!(ann.operation.is_none());
359 prop_assert!(ann.query_summary.is_none());
360 prop_assert!(ann.stored_procedure.is_none());
361 }
362
363 #[test]
364 fn query_summary_does_not_affect_other_fields(s in ".{0,64}") {
365 let ann = QueryAnnotations::new().query_summary(s);
366 prop_assert!(ann.operation.is_none());
367 prop_assert!(ann.collection.is_none());
368 prop_assert!(ann.stored_procedure.is_none());
369 }
370
371 #[test]
372 fn stored_procedure_does_not_affect_other_fields(s in ".{0,64}") {
373 let ann = QueryAnnotations::new().stored_procedure(s);
374 prop_assert!(ann.operation.is_none());
375 prop_assert!(ann.collection.is_none());
376 prop_assert!(ann.query_summary.is_none());
377 }
378
379 #[test]
382 fn no_panic_setting_all_fields(
383 op in any::<String>(),
384 coll in any::<String>(),
385 summary in any::<String>(),
386 sp in any::<String>(),
387 ) {
388 let _ann = QueryAnnotations::new()
389 .operation(op)
390 .collection(coll)
391 .query_summary(summary)
392 .stored_procedure(sp);
393 }
394
395 #[test]
398 fn clone_equals_original(
399 op in proptest::option::of(".{0,64}"),
400 coll in proptest::option::of(".{0,64}"),
401 summary in proptest::option::of(".{0,64}"),
402 sp in proptest::option::of(".{0,64}"),
403 ) {
404 let mut ann = QueryAnnotations::new();
405 if let Some(s) = op { ann = ann.operation(s); }
406 if let Some(s) = coll { ann = ann.collection(s); }
407 if let Some(s) = summary { ann = ann.query_summary(s); }
408 if let Some(s) = sp { ann = ann.stored_procedure(s); }
409 let cloned = ann.clone();
410 prop_assert_eq!(ann, cloned);
411 }
412 }
413}