use diesel::connection::{set_default_instrumentation, Instrumentation, InstrumentationEvent};
use crate::instant::Instant;
use crate::lib_on::sql::{init_sql_state, send_sql_event, SqlEvent};
#[derive(Default)]
struct HotpathDieselInstrumentation {
pending: Option<(String, Instant)>,
}
impl Instrumentation for HotpathDieselInstrumentation {
fn on_connection_event(&mut self, event: InstrumentationEvent<'_>) {
match event {
InstrumentationEvent::StartQuery { query, .. } => {
let sql = clean_sql(&query.to_string());
self.pending = (!is_transaction_control(&sql)).then(|| (sql, Instant::now()));
}
InstrumentationEvent::FinishQuery { .. } => {
if let Some((sql, start)) = self.pending.take() {
let now = Instant::now();
send_sql_event(SqlEvent::Executed {
sql: sql.into(),
duration_nanos: now.duration_since(start).as_nanos() as u64,
elapsed_ns: crate::lib_on::elapsed_since_start_ns(now),
});
}
}
_ => {}
}
}
}
fn clean_sql(rendered: &str) -> String {
match rendered.rsplit_once(" -- binds:") {
Some((sql, _)) => sql.trim().to_string(),
None => rendered.trim().to_string(),
}
}
fn is_transaction_control(sql: &str) -> bool {
let head = sql.trim_start().as_bytes();
["BEGIN", "COMMIT", "ROLLBACK", "SAVEPOINT", "RELEASE"]
.iter()
.any(|kw| {
let kw = kw.as_bytes();
head.len() >= kw.len()
&& head[..kw.len()].eq_ignore_ascii_case(kw)
&& head.get(kw.len()).is_none_or(|b| b.is_ascii_whitespace())
})
}
fn diesel_instrumentation() -> Option<Box<dyn Instrumentation>> {
Some(Box::<HotpathDieselInstrumentation>::default())
}
pub fn instrument_diesel_sql() {
init_sql_state();
let _ = set_default_instrumentation(diesel_instrumentation);
}
#[cfg(test)]
mod tests {
use crate::lib_on::sql::diesel::{clean_sql, is_transaction_control};
#[test]
fn strips_binds_suffix() {
assert_eq!(
clean_sql("INSERT INTO t (a) VALUES (?) -- binds: [\"x\"]"),
"INSERT INTO t (a) VALUES (?)",
);
assert_eq!(
clean_sql("SELECT COUNT(*) FROM t"),
"SELECT COUNT(*) FROM t"
);
assert_eq!(
clean_sql("SELECT 1 -- binds: note -- binds: [42]"),
"SELECT 1 -- binds: note",
);
}
#[test]
fn detects_transaction_control() {
for sql in [
"BEGIN",
"COMMIT",
"ROLLBACK",
"SAVEPOINT diesel_savepoint_1",
] {
assert!(is_transaction_control(sql), "{sql} should be control");
}
for sql in [
"SELECT 1",
"INSERT INTO t (a) VALUES (?)",
"BEGINNER",
"-- 😀 SELECT 1",
"😀",
] {
assert!(!is_transaction_control(sql), "{sql} should be a query");
}
}
}