use crate::pool::SharedState;
macro_rules! impl_with_annotations_mut {
() => {
#[must_use]
pub fn with_annotations(
&mut self,
annotations: crate::annotations::QueryAnnotations,
) -> crate::annotations::AnnotatedMut<'_, Self> {
crate::annotations::AnnotatedMut {
state: self.state.clone(),
annotations,
inner: self,
}
}
#[must_use]
pub fn with_operation(
&mut self,
operation: impl Into<String>,
collection: impl Into<String>,
) -> crate::annotations::AnnotatedMut<'_, Self> {
self.with_annotations(
crate::annotations::QueryAnnotations::new()
.operation(operation)
.collection(collection),
)
}
};
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct QueryAnnotations {
pub(crate) operation: Option<String>,
pub(crate) collection: Option<String>,
pub(crate) query_summary: Option<String>,
pub(crate) stored_procedure: Option<String>,
}
impl QueryAnnotations {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn operation(mut self, operation: impl Into<String>) -> Self {
self.operation = Some(operation.into());
self
}
#[must_use]
pub fn collection(mut self, collection: impl Into<String>) -> Self {
self.collection = Some(collection.into());
self
}
#[must_use]
pub fn query_summary(mut self, summary: impl Into<String>) -> Self {
self.query_summary = Some(summary.into());
self
}
#[must_use]
pub fn stored_procedure(mut self, name: impl Into<String>) -> Self {
self.stored_procedure = Some(name.into());
self
}
}
pub struct Annotated<'a, E> {
pub(crate) inner: &'a E,
pub(crate) annotations: QueryAnnotations,
pub(crate) state: SharedState,
}
impl<E: std::fmt::Debug> std::fmt::Debug for Annotated<'_, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Annotated")
.field("annotations", &self.annotations)
.finish_non_exhaustive()
}
}
pub struct AnnotatedMut<'a, E> {
pub(crate) inner: &'a mut E,
pub(crate) annotations: QueryAnnotations,
pub(crate) state: SharedState,
}
impl<E: std::fmt::Debug> std::fmt::Debug for AnnotatedMut<'_, E> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("AnnotatedMut")
.field("annotations", &self.annotations)
.finish_non_exhaustive()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn setter_permutations() {
type Setter = fn(QueryAnnotations) -> QueryAnnotations;
type Getter = fn(&QueryAnnotations) -> Option<&str>;
let fields: &[(&str, Setter, Getter)] = &[
(
"operation",
|a| a.operation("OP"),
|a| a.operation.as_deref(),
),
(
"collection",
|a| a.collection("COLL"),
|a| a.collection.as_deref(),
),
(
"query_summary",
|a| a.query_summary("SUM"),
|a| a.query_summary.as_deref(),
),
(
"stored_procedure",
|a| a.stored_procedure("SP"),
|a| a.stored_procedure.as_deref(),
),
];
for mask in 0u8..16 {
let mut ann = QueryAnnotations::new();
for (i, &(_, setter, _)) in fields.iter().enumerate() {
if mask & (1 << i) != 0 {
ann = setter(ann);
}
}
for (i, &(name, _, getter)) in fields.iter().enumerate() {
if mask & (1 << i) != 0 {
assert!(
getter(&ann).is_some(),
"{name} should be Some for mask {mask:#06b}"
);
} else {
assert!(
getter(&ann).is_none(),
"{name} should be None for mask {mask:#06b}"
);
}
}
}
}
#[test]
fn clone_produces_independent_copy() {
let original = QueryAnnotations::new()
.operation("SELECT")
.collection("users");
let cloned = original.clone();
let modified = original.query_summary("SELECT users");
assert_eq!(cloned.query_summary, None);
assert_eq!(modified.query_summary.as_deref(), Some("SELECT users"));
}
#[test]
fn debug_impl_is_non_empty() {
let ann = QueryAnnotations::new().operation("SELECT");
let debug = format!("{ann:?}");
assert!(debug.contains("SELECT"));
}
fn test_state() -> SharedState {
use std::sync::Arc;
use crate::attributes::{ConnectionAttributes, QueryTextMode};
use crate::metrics::Metrics;
SharedState {
attrs: Arc::new(ConnectionAttributes {
system: "sqlite",
host: None,
port: None,
namespace: None,
network_peer_address: None,
network_peer_port: None,
query_text_mode: QueryTextMode::Off,
}),
metrics: Arc::new(Metrics::new()),
}
}
#[test]
fn annotated_debug() {
let inner = "pool";
let wrapper = Annotated {
inner: &inner,
annotations: QueryAnnotations::new().operation("SELECT"),
state: test_state(),
};
let debug = format!("{wrapper:?}");
assert!(debug.contains("Annotated"));
assert!(debug.contains("SELECT"));
}
#[test]
fn annotated_mut_debug() {
let mut inner = "conn";
let wrapper = AnnotatedMut {
inner: &mut inner,
annotations: QueryAnnotations::new().collection("users"),
state: test_state(),
};
let debug = format!("{wrapper:?}");
assert!(debug.contains("AnnotatedMut"));
assert!(debug.contains("users"));
}
use proptest::prelude::*;
proptest! {
#![proptest_config(ProptestConfig::with_cases(128))]
#[test]
fn operation_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
let ann = QueryAnnotations::new().operation(a).operation(b.clone());
prop_assert_eq!(ann.operation.as_deref(), Some(b.as_str()));
}
#[test]
fn collection_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
let ann = QueryAnnotations::new().collection(a).collection(b.clone());
prop_assert_eq!(ann.collection.as_deref(), Some(b.as_str()));
}
#[test]
fn query_summary_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
let ann = QueryAnnotations::new().query_summary(a).query_summary(b.clone());
prop_assert_eq!(ann.query_summary.as_deref(), Some(b.as_str()));
}
#[test]
fn stored_procedure_last_write_wins(a in ".{0,64}", b in ".{0,64}") {
let ann = QueryAnnotations::new().stored_procedure(a).stored_procedure(b.clone());
prop_assert_eq!(ann.stored_procedure.as_deref(), Some(b.as_str()));
}
#[test]
fn operation_does_not_affect_other_fields(s in ".{0,64}") {
let ann = QueryAnnotations::new().operation(s);
prop_assert!(ann.collection.is_none());
prop_assert!(ann.query_summary.is_none());
prop_assert!(ann.stored_procedure.is_none());
}
#[test]
fn collection_does_not_affect_other_fields(s in ".{0,64}") {
let ann = QueryAnnotations::new().collection(s);
prop_assert!(ann.operation.is_none());
prop_assert!(ann.query_summary.is_none());
prop_assert!(ann.stored_procedure.is_none());
}
#[test]
fn query_summary_does_not_affect_other_fields(s in ".{0,64}") {
let ann = QueryAnnotations::new().query_summary(s);
prop_assert!(ann.operation.is_none());
prop_assert!(ann.collection.is_none());
prop_assert!(ann.stored_procedure.is_none());
}
#[test]
fn stored_procedure_does_not_affect_other_fields(s in ".{0,64}") {
let ann = QueryAnnotations::new().stored_procedure(s);
prop_assert!(ann.operation.is_none());
prop_assert!(ann.collection.is_none());
prop_assert!(ann.query_summary.is_none());
}
#[test]
fn no_panic_setting_all_fields(
op in any::<String>(),
coll in any::<String>(),
summary in any::<String>(),
sp in any::<String>(),
) {
let _ann = QueryAnnotations::new()
.operation(op)
.collection(coll)
.query_summary(summary)
.stored_procedure(sp);
}
#[test]
fn clone_equals_original(
op in proptest::option::of(".{0,64}"),
coll in proptest::option::of(".{0,64}"),
summary in proptest::option::of(".{0,64}"),
sp in proptest::option::of(".{0,64}"),
) {
let mut ann = QueryAnnotations::new();
if let Some(s) = op { ann = ann.operation(s); }
if let Some(s) = coll { ann = ann.collection(s); }
if let Some(s) = summary { ann = ann.query_summary(s); }
if let Some(s) = sp { ann = ann.stored_procedure(s); }
let cloned = ann.clone();
prop_assert_eq!(ann, cloned);
}
}
}