use std::collections::VecDeque;
use std::sync::Mutex;
use std::time::{Duration, SystemTime};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum QuerySource {
User,
Internal,
}
#[derive(Debug, Clone)]
pub enum QueryOutcome {
Rows(usize),
Affected(u64),
Ran,
Error(String),
}
#[derive(Debug, Clone)]
pub struct LoggedQuery {
pub sql: String,
pub source: QuerySource,
pub outcome: QueryOutcome,
pub elapsed: Duration,
pub at: SystemTime,
}
const CAPACITY: usize = 500;
#[derive(Debug, Default)]
pub struct QueryLog(Mutex<VecDeque<LoggedQuery>>);
impl QueryLog {
pub(crate) fn record(&self, entry: LoggedQuery) {
let mut log = self.lock();
if log.len() >= CAPACITY {
log.pop_front();
}
log.push_back(entry);
}
pub fn snapshot(&self) -> Vec<LoggedQuery> {
self.lock().iter().cloned().collect()
}
pub fn clear(&self) {
self.lock().clear();
}
fn lock(&self) -> std::sync::MutexGuard<'_, VecDeque<LoggedQuery>> {
self.0
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
}
#[cfg(test)]
mod tests {
use super::*;
fn entry(sql: &str) -> LoggedQuery {
LoggedQuery {
sql: sql.to_string(),
source: QuerySource::Internal,
outcome: QueryOutcome::Rows(0),
elapsed: Duration::ZERO,
at: SystemTime::now(),
}
}
#[test]
fn keeps_entries_in_order() {
let log = QueryLog::default();
log.record(entry("select 1"));
log.record(entry("select 2"));
let snapshot = log.snapshot();
assert_eq!(snapshot.len(), 2);
assert_eq!(snapshot[0].sql, "select 1");
assert_eq!(snapshot[1].sql, "select 2");
}
#[test]
fn drops_the_oldest_past_capacity() {
let log = QueryLog::default();
for index in 0..CAPACITY + 10 {
log.record(entry(&index.to_string()));
}
let snapshot = log.snapshot();
assert_eq!(snapshot.len(), CAPACITY);
assert_eq!(snapshot[0].sql, "10", "the first 10 should have aged out");
assert_eq!(snapshot[snapshot.len() - 1].sql, (CAPACITY + 9).to_string());
}
#[test]
fn clear_empties_the_log() {
let log = QueryLog::default();
log.record(entry("select 1"));
log.clear();
assert!(log.snapshot().is_empty());
}
}