1use crate::connection::LogSettings;
2#[cfg(feature = "sqlite")]
3use std::collections::HashSet;
4#[cfg(feature = "sqlite")]
5use std::fmt::Debug;
6#[cfg(feature = "sqlite")]
7use std::hash::Hash;
8use std::time::Instant;
9
10pub(crate) struct QueryLogger<'q> {
11 sql: &'q str,
12 rows_returned: u64,
13 rows_affected: u64,
14 start: Instant,
15 settings: LogSettings,
16}
17
18impl<'q> QueryLogger<'q> {
19 pub(crate) fn new(sql: &'q str, settings: LogSettings) -> Self {
20 Self {
21 sql,
22 rows_returned: 0,
23 rows_affected: 0,
24 start: Instant::now(),
25 settings,
26 }
27 }
28
29 pub(crate) fn increment_rows_returned(&mut self) {
30 self.rows_returned += 1;
31 }
32
33 pub(crate) fn increase_rows_affected(&mut self, n: u64) {
34 self.rows_affected += n;
35 }
36
37 pub(crate) fn finish(&self) {
38 let elapsed = self.start.elapsed();
39
40 let lvl = if elapsed >= self.settings.slow_statements_duration {
41 self.settings.slow_statements_level
42 } else {
43 self.settings.statements_level
44 };
45
46 if let Some(lvl) = lvl
47 .to_level()
48 .filter(|lvl| log::log_enabled!(target: "sqlx::query", *lvl))
49 {
50 let mut summary = parse_query_summary(&self.sql);
51
52 let sql = if summary != self.sql {
53 summary.push_str(" …");
54 format!(
55 "\n\n{}\n",
56 sqlformat::format(
57 &self.sql,
58 &sqlformat::QueryParams::None,
59 sqlformat::FormatOptions::default()
60 )
61 )
62 } else {
63 String::new()
64 };
65
66 log::logger().log(
67 &log::Record::builder()
68 .args(format_args!(
69 "{}; rows affected: {}, rows returned: {}, elapsed: {:.3?}{}",
70 summary, self.rows_affected, self.rows_returned, elapsed, sql
71 ))
72 .level(lvl)
73 .module_path_static(Some("sqlx::query"))
74 .target("sqlx::query")
75 .build(),
76 );
77 }
78 }
79}
80
81impl<'q> Drop for QueryLogger<'q> {
82 fn drop(&mut self) {
83 self.finish();
84 }
85}
86
87#[cfg(feature = "sqlite")]
88pub(crate) struct QueryPlanLogger<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> {
89 sql: &'q str,
90 unknown_operations: HashSet<O>,
91 results: Vec<R>,
92 program: &'q [P],
93 settings: LogSettings,
94}
95
96#[cfg(feature = "sqlite")]
97impl<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> QueryPlanLogger<'q, O, R, P> {
98 pub(crate) fn new(sql: &'q str, program: &'q [P], settings: LogSettings) -> Self {
99 Self {
100 sql,
101 unknown_operations: HashSet::new(),
102 results: Vec::new(),
103 program,
104 settings,
105 }
106 }
107
108 pub(crate) fn log_enabled(&self) -> bool {
109 if let Some(_lvl) = self
110 .settings
111 .statements_level
112 .to_level()
113 .filter(|lvl| log::log_enabled!(target: "sqlx::explain", *lvl))
114 {
115 return true;
116 } else {
117 return false;
118 }
119 }
120
121 pub(crate) fn add_result(&mut self, result: R) {
122 self.results.push(result);
123 }
124
125 pub(crate) fn add_unknown_operation(&mut self, operation: O) {
126 self.unknown_operations.insert(operation);
127 }
128
129 pub(crate) fn finish(&self) {
130 let lvl = self.settings.statements_level;
131
132 if let Some(lvl) = lvl
133 .to_level()
134 .filter(|lvl| log::log_enabled!(target: "sqlx::explain", *lvl))
135 {
136 let mut summary = parse_query_summary(&self.sql);
137
138 let sql = if summary != self.sql {
139 summary.push_str(" …");
140 format!(
141 "\n\n{}\n",
142 sqlformat::format(
143 &self.sql,
144 &sqlformat::QueryParams::None,
145 sqlformat::FormatOptions::default()
146 )
147 )
148 } else {
149 String::new()
150 };
151
152 log::logger().log(
153 &log::Record::builder()
154 .args(format_args!(
155 "{}; program:{:?}, unknown_operations:{:?}, results: {:?}{}",
156 summary, self.program, self.unknown_operations, self.results, sql
157 ))
158 .level(lvl)
159 .module_path_static(Some("sqlx::explain"))
160 .target("sqlx::explain")
161 .build(),
162 );
163 }
164 }
165}
166
167#[cfg(feature = "sqlite")]
168impl<'q, O: Debug + Hash + Eq, R: Debug, P: Debug> Drop for QueryPlanLogger<'q, O, R, P> {
169 fn drop(&mut self) {
170 self.finish();
171 }
172}
173
174fn parse_query_summary(sql: &str) -> String {
175 sql.split_whitespace()
177 .take(4)
178 .collect::<Vec<&str>>()
179 .join(" ")
180}