1use opentelemetry::KeyValue;
2use opentelemetry_semantic_conventions::attribute;
3
4#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
16pub enum QueryTextMode {
17 #[default]
21 Full,
22 Obfuscated,
25 Off,
27}
28
29#[derive(Debug, Clone)]
36pub(crate) struct ConnectionAttributes {
37 pub system: &'static str,
39 pub host: Option<String>,
41 pub port: Option<u16>,
43 pub namespace: Option<String>,
45 pub network_peer_address: Option<String>,
47 pub network_peer_port: Option<u16>,
49 pub query_text_mode: QueryTextMode,
51}
52
53impl ConnectionAttributes {
54 pub fn base_key_values(&self) -> Vec<KeyValue> {
57 let mut attrs = Vec::with_capacity(6);
58 attrs.push(KeyValue::new(attribute::DB_SYSTEM_NAME, self.system));
59 if let Some(ref host) = self.host {
60 attrs.push(KeyValue::new(attribute::SERVER_ADDRESS, host.clone()));
61 }
62 if let Some(port) = self.port {
63 attrs.push(KeyValue::new(attribute::SERVER_PORT, i64::from(port)));
64 }
65 if let Some(ref ns) = self.namespace {
66 attrs.push(KeyValue::new(attribute::DB_NAMESPACE, ns.clone()));
67 }
68 if let Some(ref addr) = self.network_peer_address {
69 attrs.push(KeyValue::new(attribute::NETWORK_PEER_ADDRESS, addr.clone()));
70 }
71 if let Some(port) = self.network_peer_port {
72 attrs.push(KeyValue::new(attribute::NETWORK_PEER_PORT, i64::from(port)));
73 }
74 attrs
75 }
76}
77
78pub(crate) fn span_name(
87 system: &str,
88 operation: Option<&str>,
89 collection: Option<&str>,
90 summary: Option<&str>,
91) -> String {
92 if let Some(s) = summary {
93 return s.to_owned();
94 }
95 match (operation, collection) {
96 (Some(op), Some(coll)) => format!("{op} {coll}"),
97 (Some(op), None) => op.to_owned(),
98 _ => system.to_owned(),
99 }
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn span_name_with_operation_and_collection() {
108 assert_eq!(
109 span_name("postgresql", Some("SELECT"), Some("users"), None),
110 "SELECT users"
111 );
112 }
113
114 #[test]
115 fn span_name_with_operation_only() {
116 assert_eq!(
117 span_name("postgresql", Some("SELECT"), None, None),
118 "SELECT"
119 );
120 }
121
122 #[test]
123 fn span_name_fallback_to_system() {
124 assert_eq!(span_name("sqlite", None, None, None), "sqlite");
125 }
126
127 #[test]
128 fn span_name_collection_without_operation_falls_back() {
129 assert_eq!(span_name("mysql", None, Some("orders"), None), "mysql");
130 }
131
132 #[test]
133 fn span_name_summary_wins_over_operation_and_collection() {
134 assert_eq!(
135 span_name(
136 "postgresql",
137 Some("SELECT"),
138 Some("users"),
139 Some("daily report")
140 ),
141 "daily report"
142 );
143 }
144
145 #[test]
146 fn span_name_summary_alone() {
147 assert_eq!(
148 span_name("sqlite", None, None, Some("custom name")),
149 "custom name"
150 );
151 }
152
153 #[test]
154 fn base_key_values_all_fields() {
155 let attrs = ConnectionAttributes {
156 system: "postgresql",
157 host: Some("localhost".into()),
158 port: Some(5432),
159 namespace: Some("mydb".into()),
160 network_peer_address: Some("127.0.0.1".into()),
161 network_peer_port: Some(5432),
162 query_text_mode: QueryTextMode::Full,
163 };
164 let kvs = attrs.base_key_values();
165 assert_eq!(kvs.len(), 6);
166 assert_eq!(kvs[0].key.as_str(), "db.system.name");
167 assert_eq!(kvs[1].key.as_str(), "server.address");
168 assert_eq!(kvs[2].key.as_str(), "server.port");
169 assert_eq!(kvs[3].key.as_str(), "db.namespace");
170 assert_eq!(kvs[4].key.as_str(), "network.peer.address");
171 assert_eq!(kvs[5].key.as_str(), "network.peer.port");
172 }
173
174 #[test]
175 fn base_key_values_minimal() {
176 let attrs = ConnectionAttributes {
177 system: "sqlite",
178 host: None,
179 port: None,
180 namespace: None,
181 network_peer_address: None,
182 network_peer_port: None,
183 query_text_mode: QueryTextMode::Off,
184 };
185 let kvs = attrs.base_key_values();
186 assert_eq!(kvs.len(), 1);
187 assert_eq!(kvs[0].key.as_str(), "db.system.name");
188 }
189}