Skip to content

Commit 12a08fd

Browse files
committed
Implement support for diesel::Instrumentation for all provided connection types
This commit implements the necessary methods to support the diesel Instrumentation interface for logging and other connection instrumentation functionality. It also adds tests for this new functionality.
1 parent d2fc97d commit 12a08fd

File tree

13 files changed

+584
-128
lines changed

13 files changed

+584
-128
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,10 @@ jobs:
4949
5050
- name: Set environment variables
5151
shell: bash
52-
if: matrix.rust == 'nightly'
52+
if: matrix.rust != 'nightly'
5353
run: |
54-
echo "RUSTFLAGS=--cap-lints=warn" >> $GITHUB_ENV
54+
echo "RUSTFLAGS=-D warnings" >> $GITHUB_ENV
55+
echo "RUSTDOCFLAGS=-D warnings" >> $GITHUB_ENV
5556
5657
- uses: ilammy/setup-nasm@v1
5758
if: matrix.backend == 'postgres' && matrix.os == 'windows-2019'
@@ -234,7 +235,7 @@ jobs:
234235
find ~/.cargo/registry -iname "*clippy.toml" -delete
235236
236237
- name: Run clippy
237-
run: cargo +stable clippy --all
238+
run: cargo +stable clippy --all --all-features
238239

239240
- name: Check formating
240241
run: cargo +stable fmt --all -- --check

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/
77
## [Unreleased]
88

99
* Added type `diesel_async::pooled_connection::mobc::PooledConnection`
10-
* MySQL/MariaDB now use `CLIENT_FOUND_ROWS` capability to allow consistent behavior with PostgreSQL regarding return value of UPDATe commands.
10+
* MySQL/MariaDB now use `CLIENT_FOUND_ROWS` capability to allow consistent behaviour with PostgreSQL regarding return value of UPDATe commands.
1111
* The minimal supported rust version is now 1.78.0
12+
* Add a `SyncConnectionWrapper` type that turns a sync connection into an async one. This enables SQLite support for diesel-async
13+
* Add support for `diesel::connection::Instrumentation` to support logging and other instrumentation for any of the provided connection impls.
1214

1315
## [0.4.1] - 2023-09-01
1416

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ cfg-if = "1"
4949
chrono = "0.4"
5050
diesel = { version = "2.2.0", default-features = false, features = ["chrono"] }
5151
diesel_migrations = "2.2.0"
52+
assert_matches = "1.0.1"
5253

5354
[features]
5455
default = []

src/async_connection_wrapper.rs

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,6 @@ mod implementation {
107107
pub struct AsyncConnectionWrapper<C, B> {
108108
inner: C,
109109
runtime: B,
110-
instrumentation: Option<Box<dyn Instrumentation>>,
111110
}
112111

113112
impl<C, B> From<C> for AsyncConnectionWrapper<C, B>
@@ -119,7 +118,6 @@ mod implementation {
119118
Self {
120119
inner,
121120
runtime: B::get_runtime(),
122-
instrumentation: None,
123121
}
124122
}
125123
}
@@ -150,11 +148,7 @@ mod implementation {
150148
let runtime = B::get_runtime();
151149
let f = C::establish(database_url);
152150
let inner = runtime.block_on(f)?;
153-
Ok(Self {
154-
inner,
155-
runtime,
156-
instrumentation: None,
157-
})
151+
Ok(Self { inner, runtime })
158152
}
159153

160154
fn execute_returning_count<T>(&mut self, source: &T) -> diesel::QueryResult<usize>
@@ -165,18 +159,18 @@ mod implementation {
165159
self.runtime.block_on(f)
166160
}
167161

168-
fn transaction_state(
169-
&mut self,
162+
fn transaction_state(
163+
&mut self,
170164
) -> &mut <Self::TransactionManager as diesel::connection::TransactionManager<Self>>::TransactionStateData{
171165
self.inner.transaction_state()
172166
}
173167

174168
fn instrumentation(&mut self) -> &mut dyn Instrumentation {
175-
&mut self.instrumentation
169+
self.inner.instrumentation()
176170
}
177171

178172
fn set_instrumentation(&mut self, instrumentation: impl Instrumentation) {
179-
self.instrumentation = Some(Box::new(instrumentation));
173+
self.inner.set_instrumentation(instrumentation);
180174
}
181175
}
182176

src/lib.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
#![warn(missing_docs)]
7070

7171
use diesel::backend::Backend;
72+
use diesel::connection::Instrumentation;
7273
use diesel::query_builder::{AsQuery, QueryFragment, QueryId};
7374
use diesel::result::Error;
7475
use diesel::row::Row;
@@ -347,4 +348,10 @@ pub trait AsyncConnection: SimpleAsyncConnection + Sized + Send {
347348
fn _silence_lint_on_execute_future(_: Self::ExecuteFuture<'_, '_>) {}
348349
#[doc(hidden)]
349350
fn _silence_lint_on_load_future(_: Self::LoadFuture<'_, '_>) {}
351+
352+
#[doc(hidden)]
353+
fn instrumentation(&mut self) -> &mut dyn Instrumentation;
354+
355+
/// Set a specific [`Instrumentation`] implementation for this connection
356+
fn set_instrumentation(&mut self, instrumentation: impl Instrumentation);
350357
}

src/mysql/mod.rs

Lines changed: 111 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::stmt_cache::{PrepareCallback, StmtCache};
22
use crate::{AnsiTransactionManager, AsyncConnection, SimpleAsyncConnection};
33
use diesel::connection::statement_cache::{MaybeCached, StatementCacheKey};
4+
use diesel::connection::Instrumentation;
5+
use diesel::connection::InstrumentationEvent;
6+
use diesel::connection::StrQueryHelper;
47
use diesel::mysql::{Mysql, MysqlQueryBuilder, MysqlType};
58
use diesel::query_builder::QueryBuilder;
69
use diesel::query_builder::{bind_collector::RawBytesBindCollector, QueryFragment, QueryId};
@@ -26,12 +29,32 @@ pub struct AsyncMysqlConnection {
2629
conn: mysql_async::Conn,
2730
stmt_cache: StmtCache<Mysql, Statement>,
2831
transaction_manager: AnsiTransactionManager,
32+
instrumentation: std::sync::Mutex<Option<Box<dyn Instrumentation>>>,
2933
}
3034

3135
#[async_trait::async_trait]
3236
impl SimpleAsyncConnection for AsyncMysqlConnection {
3337
async fn batch_execute(&mut self, query: &str) -> diesel::QueryResult<()> {
34-
Ok(self.conn.query_drop(query).await.map_err(ErrorHelper)?)
38+
self.instrumentation
39+
.lock()
40+
.unwrap_or_else(|p| p.into_inner())
41+
.on_connection_event(InstrumentationEvent::start_query(&StrQueryHelper::new(
42+
query,
43+
)));
44+
let result = self
45+
.conn
46+
.query_drop(query)
47+
.await
48+
.map_err(ErrorHelper)
49+
.map_err(Into::into);
50+
self.instrumentation
51+
.lock()
52+
.unwrap_or_else(|p| p.into_inner())
53+
.on_connection_event(InstrumentationEvent::finish_query(
54+
&StrQueryHelper::new(query),
55+
result.as_ref().err(),
56+
));
57+
result
3558
}
3659
}
3760

@@ -53,20 +76,18 @@ impl AsyncConnection for AsyncMysqlConnection {
5376
type TransactionManager = AnsiTransactionManager;
5477

5578
async fn establish(database_url: &str) -> diesel::ConnectionResult<Self> {
56-
let opts = Opts::from_url(database_url)
57-
.map_err(|e| diesel::result::ConnectionError::InvalidConnectionUrl(e.to_string()))?;
58-
let builder = OptsBuilder::from_opts(opts)
59-
.init(CONNECTION_SETUP_QUERIES.to_vec())
60-
.stmt_cache_size(0) // We have our own cache
61-
.client_found_rows(true); // This allows a consistent behavior between MariaDB/MySQL and PostgreSQL (and is already set in `diesel`)
62-
63-
let conn = mysql_async::Conn::new(builder).await.map_err(ErrorHelper)?;
64-
65-
Ok(AsyncMysqlConnection {
66-
conn,
67-
stmt_cache: StmtCache::new(),
68-
transaction_manager: AnsiTransactionManager::default(),
69-
})
79+
let mut instrumentation = diesel::connection::get_default_instrumentation();
80+
instrumentation.on_connection_event(InstrumentationEvent::start_establish_connection(
81+
database_url,
82+
));
83+
let r = Self::establish_connection_inner(database_url).await;
84+
instrumentation.on_connection_event(InstrumentationEvent::finish_establish_connection(
85+
database_url,
86+
r.as_ref().err(),
87+
));
88+
let mut conn = r?;
89+
conn.instrumentation = std::sync::Mutex::new(instrumentation);
90+
Ok(conn)
7091
}
7192

7293
fn load<'conn, 'query, T>(&'conn mut self, source: T) -> Self::LoadFuture<'conn, 'query>
@@ -80,7 +101,10 @@ impl AsyncConnection for AsyncMysqlConnection {
80101
let stmt_for_exec = match stmt {
81102
MaybeCached::Cached(ref s) => (*s).clone(),
82103
MaybeCached::CannotCache(ref s) => s.clone(),
83-
_ => todo!(),
104+
_ => unreachable!(
105+
"Diesel has only two variants here at the time of writing.\n\
106+
If you ever see this error message please open in issue in the diesel-async issue tracker"
107+
),
84108
};
85109

86110
let (tx, rx) = futures_channel::mpsc::channel(0);
@@ -152,6 +176,19 @@ impl AsyncConnection for AsyncMysqlConnection {
152176
fn transaction_state(&mut self) -> &mut AnsiTransactionManager {
153177
&mut self.transaction_manager
154178
}
179+
180+
fn instrumentation(&mut self) -> &mut dyn Instrumentation {
181+
self.instrumentation
182+
.get_mut()
183+
.unwrap_or_else(|p| p.into_inner())
184+
}
185+
186+
fn set_instrumentation(&mut self, instrumentation: impl Instrumentation) {
187+
*self
188+
.instrumentation
189+
.get_mut()
190+
.unwrap_or_else(|p| p.into_inner()) = Some(Box::new(instrumentation));
191+
}
155192
}
156193

157194
#[inline(always)]
@@ -195,6 +232,7 @@ impl AsyncMysqlConnection {
195232
conn,
196233
stmt_cache: StmtCache::new(),
197234
transaction_manager: AnsiTransactionManager::default(),
235+
instrumentation: std::sync::Mutex::new(None),
198236
};
199237

200238
for stmt in CONNECTION_SETUP_QUERIES {
@@ -219,6 +257,12 @@ impl AsyncMysqlConnection {
219257
T: QueryFragment<Mysql> + QueryId,
220258
F: Future<Output = QueryResult<R>> + Send,
221259
{
260+
self.instrumentation
261+
.lock()
262+
.unwrap_or_else(|p| p.into_inner())
263+
.on_connection_event(InstrumentationEvent::start_query(&diesel::debug_query(
264+
&query,
265+
)));
222266
let mut bind_collector = RawBytesBindCollector::<Mysql>::new();
223267
let bind_collector = query
224268
.collect_binds(&mut bind_collector, &mut (), &Mysql)
@@ -228,6 +272,7 @@ impl AsyncMysqlConnection {
228272
ref mut conn,
229273
ref mut stmt_cache,
230274
ref mut transaction_manager,
275+
ref instrumentation,
231276
..
232277
} = self;
233278

@@ -242,28 +287,37 @@ impl AsyncMysqlConnection {
242287
} = bind_collector?;
243288
let is_safe_to_cache_prepared = is_safe_to_cache_prepared?;
244289
let sql = sql?;
245-
let cache_key = if let Some(query_id) = query_id {
246-
StatementCacheKey::Type(query_id)
247-
} else {
248-
StatementCacheKey::Sql {
249-
sql: sql.clone(),
250-
bind_types: metadata.clone(),
251-
}
290+
let inner = async {
291+
let cache_key = if let Some(query_id) = query_id {
292+
StatementCacheKey::Type(query_id)
293+
} else {
294+
StatementCacheKey::Sql {
295+
sql: sql.clone(),
296+
bind_types: metadata.clone(),
297+
}
298+
};
299+
300+
let (stmt, conn) = stmt_cache
301+
.cached_prepared_statement(
302+
cache_key,
303+
sql.clone(),
304+
is_safe_to_cache_prepared,
305+
&metadata,
306+
conn,
307+
instrumentation,
308+
)
309+
.await?;
310+
callback(conn, stmt, ToSqlHelper { metadata, binds }).await
252311
};
253-
254-
let (stmt, conn) = stmt_cache
255-
.cached_prepared_statement(
256-
cache_key,
257-
sql,
258-
is_safe_to_cache_prepared,
259-
&metadata,
260-
conn,
261-
)
262-
.await?;
263-
update_transaction_manager_status(
264-
callback(conn, stmt, ToSqlHelper { metadata, binds }).await,
265-
transaction_manager,
266-
)
312+
let r = update_transaction_manager_status(inner.await, transaction_manager);
313+
instrumentation
314+
.lock()
315+
.unwrap_or_else(|p| p.into_inner())
316+
.on_connection_event(InstrumentationEvent::finish_query(
317+
&StrQueryHelper::new(&sql),
318+
r.as_ref().err(),
319+
));
320+
r
267321
}
268322
.boxed()
269323
}
@@ -300,6 +354,26 @@ impl AsyncMysqlConnection {
300354

301355
Ok(())
302356
}
357+
358+
async fn establish_connection_inner(
359+
database_url: &str,
360+
) -> Result<AsyncMysqlConnection, ConnectionError> {
361+
let opts = Opts::from_url(database_url)
362+
.map_err(|e| diesel::result::ConnectionError::InvalidConnectionUrl(e.to_string()))?;
363+
let builder = OptsBuilder::from_opts(opts)
364+
.init(CONNECTION_SETUP_QUERIES.to_vec())
365+
.stmt_cache_size(0) // We have our own cache
366+
.client_found_rows(true); // This allows a consistent behavior between MariaDB/MySQL and PostgreSQL (and is already set in `diesel`)
367+
368+
let conn = mysql_async::Conn::new(builder).await.map_err(ErrorHelper)?;
369+
370+
Ok(AsyncMysqlConnection {
371+
conn,
372+
stmt_cache: StmtCache::new(),
373+
transaction_manager: AnsiTransactionManager::default(),
374+
instrumentation: std::sync::Mutex::new(None),
375+
})
376+
}
303377
}
304378

305379
#[cfg(any(

0 commit comments

Comments
 (0)