Skip to main content

scry_protocol/
event.rs

1use crate::ParamValue;
2use serde::{Deserialize, Serialize};
3use std::time::{Duration, SystemTime};
4
5/// Represents a captured SQL query event from the proxy
6#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct QueryEvent {
8    /// Unique identifier for this event
9    pub event_id: String,
10
11    /// Timestamp when the query was received
12    pub timestamp: SystemTime,
13
14    /// The SQL query text (raw if anonymization disabled, else same as normalized_query)
15    pub query: String,
16
17    /// Query parameters from Bind message
18    #[serde(default, skip_serializing_if = "Vec::is_empty")]
19    pub params: Vec<ParamValue>,
20
21    /// True if params couldn't be fully decoded (cache miss)
22    #[serde(default)]
23    pub params_incomplete: bool,
24
25    /// Normalized query with placeholders (if anonymization enabled)
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub normalized_query: Option<String>,
28
29    /// Fingerprints of literal values (if anonymization enabled)
30    /// Enables hot data detection while protecting PII
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub value_fingerprints: Option<Vec<String>>,
33
34    /// Query execution duration
35    pub duration: Duration,
36
37    /// Number of rows affected/returned (if available)
38    pub rows: Option<u64>,
39
40    /// Whether the query succeeded
41    pub success: bool,
42
43    /// Error message if query failed
44    pub error: Option<String>,
45
46    /// Database name
47    pub database: String,
48
49    /// Client connection ID
50    pub connection_id: String,
51}
52
53/// Builder for creating QueryEvent instances
54pub struct QueryEventBuilder {
55    event_id: String,
56    timestamp: SystemTime,
57    query: String,
58    params: Vec<ParamValue>,
59    params_incomplete: bool,
60    normalized_query: Option<String>,
61    value_fingerprints: Option<Vec<String>>,
62    duration: Option<Duration>,
63    rows: Option<u64>,
64    success: bool,
65    error: Option<String>,
66    database: String,
67    connection_id: String,
68}
69
70impl QueryEventBuilder {
71    pub fn new(query: impl Into<String>) -> Self {
72        Self {
73            event_id: uuid::Uuid::new_v4().to_string(),
74            timestamp: SystemTime::now(),
75            query: query.into(),
76            params: Vec::new(),
77            params_incomplete: false,
78            normalized_query: None,
79            value_fingerprints: None,
80            duration: None,
81            rows: None,
82            success: true,
83            error: None,
84            database: String::from("unknown"),
85            connection_id: String::from("unknown"),
86        }
87    }
88
89    pub fn params(mut self, params: Vec<ParamValue>) -> Self {
90        self.params = params;
91        self
92    }
93
94    pub fn params_incomplete(mut self, incomplete: bool) -> Self {
95        self.params_incomplete = incomplete;
96        self
97    }
98
99    pub fn normalized_query(mut self, normalized_query: impl Into<String>) -> Self {
100        self.normalized_query = Some(normalized_query.into());
101        self
102    }
103
104    pub fn value_fingerprints(mut self, fingerprints: Vec<String>) -> Self {
105        self.value_fingerprints = Some(fingerprints);
106        self
107    }
108
109    pub fn duration(mut self, duration: Duration) -> Self {
110        self.duration = Some(duration);
111        self
112    }
113
114    pub fn rows(mut self, rows: u64) -> Self {
115        self.rows = Some(rows);
116        self
117    }
118
119    pub fn success(mut self, success: bool) -> Self {
120        self.success = success;
121        self
122    }
123
124    pub fn error(mut self, error: impl Into<String>) -> Self {
125        self.error = Some(error.into());
126        self
127    }
128
129    pub fn database(mut self, database: impl Into<String>) -> Self {
130        self.database = database.into();
131        self
132    }
133
134    pub fn connection_id(mut self, connection_id: impl Into<String>) -> Self {
135        self.connection_id = connection_id.into();
136        self
137    }
138
139    pub fn build(self) -> QueryEvent {
140        QueryEvent {
141            event_id: self.event_id,
142            timestamp: self.timestamp,
143            query: self.query,
144            params: self.params,
145            params_incomplete: self.params_incomplete,
146            normalized_query: self.normalized_query,
147            value_fingerprints: self.value_fingerprints,
148            duration: self.duration.unwrap_or(Duration::from_millis(0)),
149            rows: self.rows,
150            success: self.success,
151            error: self.error,
152            database: self.database,
153            connection_id: self.connection_id,
154        }
155    }
156}
157
158#[cfg(test)]
159mod tests {
160    use super::*;
161    use crate::ParamValue;
162
163    #[test]
164    fn test_query_event_with_params() {
165        let event = QueryEventBuilder::new("SELECT * FROM users WHERE id = $1")
166            .params(vec![ParamValue::Int32(42)])
167            .build();
168
169        assert_eq!(event.params.len(), 1);
170        assert!(!event.params_incomplete);
171    }
172
173    #[test]
174    fn test_query_event_params_incomplete() {
175        let event = QueryEventBuilder::new("SELECT * FROM users WHERE id = $1")
176            .params(vec![ParamValue::Unknown { oid: 0, data: vec![0x01] }])
177            .params_incomplete(true)
178            .build();
179
180        assert!(event.params_incomplete);
181    }
182}