Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions sqlx-core/src/common/statement_cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,8 @@ impl<T> StatementCache<T> {
pub fn is_enabled(&self) -> bool {
self.capacity() > 0
}

pub fn iter(&self) -> impl Iterator<Item=&T> {
self.inner.iter().map(|(_, v)| v)
}
}
6 changes: 5 additions & 1 deletion sqlx-mysql/src/connection/executor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,11 @@ impl MySqlConnection {
loop {
// query response is a meta-packet which may be one of:
// Ok, Err, ResultSet, or (unhandled) LocalInfileRequest
let mut packet = self.inner.stream.recv_packet().await?;
let mut packet = self.inner.stream.recv_packet().await.inspect_err(|_| {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, if you could find the exact error code that's returned, that would probably be better. This is a huge pessimization.

Keep in mind we also get errors here for "normal" errors like constraint violations.

This would leak any valid prepared statements on the server side.

// if a prepared statement vanished on the server side, we get an error here
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be good to link to the specific issue for context, if you created one (I didn't look).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is also fine.

// clear the statement cache in case the connection got reset to cause re-preparing
self.inner.cache_statement.clear();
})?;

if packet[0] == 0x00 || packet[0] == 0xff {
// first packet in a query response is OK or ERR
Expand Down
13 changes: 13 additions & 0 deletions sqlx-mysql/src/connection/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,19 @@ impl MySqlConnection {
.status_flags
.intersects(Status::SERVER_STATUS_IN_TRANS)
}

pub async fn nuke_cached_statements(&mut self) -> Result<(), Error> {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, this shouldn't just be a public method on the connection. Users might confuse it for the method that they're supposed to use to clear the prepared statement cache.

The easiest route would be to just mark this #[doc(hidden)].

for (statement_id, _) in self.inner.cache_statement.iter() {
self.inner
.stream
.send_packet(StmtClose {
statement: *statement_id,
})
.await?;
}

Ok(())
}
}

impl Debug for MySqlConnection {
Expand Down
24 changes: 24 additions & 0 deletions tests/mysql/mysql.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,30 @@ async fn it_maths() -> anyhow::Result<()> {
Ok(())
}

#[sqlx_macros::test]
async fn it_clears_statement_cache_on_error() -> anyhow::Result<()> {
setup_if_needed();

let query = "SELECT 1";

let mut conn = new::<MySql>().await?;
let _ = sqlx::query(query).fetch_one(&mut conn).await?;
assert_eq!(1, conn.cached_statements_size());

// clear cached statements only on the server side
conn.nuke_cached_statements().await?;
assert_eq!(1, conn.cached_statements_size());

// one query fails as the statement is not cached server-side any more, client-side cache is cleared
assert!(sqlx::query(query).fetch_one(&mut conn).await.is_err());
assert_eq!(0, conn.cached_statements_size());

// next query succeeds again
let _ = sqlx::query(query).fetch_one(&mut conn).await?;

Ok(())
}

#[sqlx_macros::test]
async fn it_can_fail_at_querying() -> anyhow::Result<()> {
let mut conn = new::<MySql>().await?;
Expand Down
Loading