mirror of
https://github.com/launchbadge/sqlx.git
synced 2025-11-08 01:14:48 +00:00
fix(mysql): handle multiple waiting results correctly (#1439)
* test(mysql): add test case for pending rows and dropped transaction * fix(mysql): handle multiple waiting results correctly
This commit is contained in:
parent
24c0d5290b
commit
593364f801
@ -4,7 +4,7 @@ use crate::error::Error;
|
|||||||
use crate::executor::{Execute, Executor};
|
use crate::executor::{Execute, Executor};
|
||||||
use crate::ext::ustr::UStr;
|
use crate::ext::ustr::UStr;
|
||||||
use crate::logger::QueryLogger;
|
use crate::logger::QueryLogger;
|
||||||
use crate::mysql::connection::stream::Busy;
|
use crate::mysql::connection::stream::Waiting;
|
||||||
use crate::mysql::io::MySqlBufExt;
|
use crate::mysql::io::MySqlBufExt;
|
||||||
use crate::mysql::protocol::response::Status;
|
use crate::mysql::protocol::response::Status;
|
||||||
use crate::mysql::protocol::statement::{
|
use crate::mysql::protocol::statement::{
|
||||||
@ -93,7 +93,7 @@ impl MySqlConnection {
|
|||||||
let mut logger = QueryLogger::new(sql, self.log_settings.clone());
|
let mut logger = QueryLogger::new(sql, self.log_settings.clone());
|
||||||
|
|
||||||
self.stream.wait_until_ready().await?;
|
self.stream.wait_until_ready().await?;
|
||||||
self.stream.busy = Busy::Result;
|
self.stream.waiting.push_back(Waiting::Result);
|
||||||
|
|
||||||
Ok(Box::pin(try_stream! {
|
Ok(Box::pin(try_stream! {
|
||||||
// make a slot for the shared column data
|
// make a slot for the shared column data
|
||||||
@ -146,12 +146,12 @@ impl MySqlConnection {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stream.busy = Busy::NotBusy;
|
self.stream.waiting.pop_front();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherwise, this first packet is the start of the result-set metadata,
|
// otherwise, this first packet is the start of the result-set metadata,
|
||||||
self.stream.busy = Busy::Row;
|
*self.stream.waiting.front_mut().unwrap() = Waiting::Row;
|
||||||
|
|
||||||
let num_columns = packet.get_uint_lenenc() as usize; // column count
|
let num_columns = packet.get_uint_lenenc() as usize; // column count
|
||||||
|
|
||||||
@ -179,11 +179,11 @@ impl MySqlConnection {
|
|||||||
|
|
||||||
if eof.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) {
|
if eof.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) {
|
||||||
// more result sets exist, continue to the next one
|
// more result sets exist, continue to the next one
|
||||||
self.stream.busy = Busy::Result;
|
*self.stream.waiting.front_mut().unwrap() = Waiting::Result;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.stream.busy = Busy::NotBusy;
|
self.stream.waiting.pop_front();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,7 +16,7 @@ mod executor;
|
|||||||
mod stream;
|
mod stream;
|
||||||
mod tls;
|
mod tls;
|
||||||
|
|
||||||
pub(crate) use stream::{Busy, MySqlStream};
|
pub(crate) use stream::{MySqlStream, Waiting};
|
||||||
|
|
||||||
const MAX_PACKET_SIZE: u32 = 1024;
|
const MAX_PACKET_SIZE: u32 = 1024;
|
||||||
|
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::VecDeque;
|
||||||
use std::ops::{Deref, DerefMut};
|
use std::ops::{Deref, DerefMut};
|
||||||
|
|
||||||
use bytes::{Buf, Bytes};
|
use bytes::{Buf, Bytes};
|
||||||
@ -16,15 +17,13 @@ pub struct MySqlStream {
|
|||||||
pub(crate) server_version: (u16, u16, u16),
|
pub(crate) server_version: (u16, u16, u16),
|
||||||
pub(super) capabilities: Capabilities,
|
pub(super) capabilities: Capabilities,
|
||||||
pub(crate) sequence_id: u8,
|
pub(crate) sequence_id: u8,
|
||||||
pub(crate) busy: Busy,
|
pub(crate) waiting: VecDeque<Waiting>,
|
||||||
pub(crate) charset: CharSet,
|
pub(crate) charset: CharSet,
|
||||||
pub(crate) collation: Collation,
|
pub(crate) collation: Collation,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub(crate) enum Busy {
|
pub(crate) enum Waiting {
|
||||||
NotBusy,
|
|
||||||
|
|
||||||
// waiting for a result set
|
// waiting for a result set
|
||||||
Result,
|
Result,
|
||||||
|
|
||||||
@ -65,7 +64,7 @@ impl MySqlStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
busy: Busy::NotBusy,
|
waiting: VecDeque::new(),
|
||||||
capabilities,
|
capabilities,
|
||||||
server_version: (0, 0, 0),
|
server_version: (0, 0, 0),
|
||||||
sequence_id: 0,
|
sequence_id: 0,
|
||||||
@ -80,32 +79,32 @@ impl MySqlStream {
|
|||||||
self.stream.flush().await?;
|
self.stream.flush().await?;
|
||||||
}
|
}
|
||||||
|
|
||||||
while self.busy != Busy::NotBusy {
|
while !self.waiting.is_empty() {
|
||||||
while self.busy == Busy::Row {
|
while self.waiting.front() == Some(&Waiting::Row) {
|
||||||
let packet = self.recv_packet().await?;
|
let packet = self.recv_packet().await?;
|
||||||
|
|
||||||
if packet[0] == 0xfe && packet.len() < 9 {
|
if packet[0] == 0xfe && packet.len() < 9 {
|
||||||
let eof = packet.eof(self.capabilities)?;
|
let eof = packet.eof(self.capabilities)?;
|
||||||
|
|
||||||
self.busy = if eof.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) {
|
if eof.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) {
|
||||||
Busy::Result
|
*self.waiting.front_mut().unwrap() = Waiting::Result;
|
||||||
} else {
|
} else {
|
||||||
Busy::NotBusy
|
self.waiting.pop_front();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while self.busy == Busy::Result {
|
while self.waiting.front() == Some(&Waiting::Result) {
|
||||||
let packet = self.recv_packet().await?;
|
let packet = self.recv_packet().await?;
|
||||||
|
|
||||||
if packet[0] == 0x00 || packet[0] == 0xff {
|
if packet[0] == 0x00 || packet[0] == 0xff {
|
||||||
let ok = packet.ok()?;
|
let ok = packet.ok()?;
|
||||||
|
|
||||||
if !ok.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) {
|
if !ok.status.contains(Status::SERVER_MORE_RESULTS_EXISTS) {
|
||||||
self.busy = Busy::NotBusy;
|
self.waiting.pop_front();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
self.busy = Busy::Row;
|
*self.waiting.front_mut().unwrap() = Waiting::Row;
|
||||||
self.skip_result_metadata(packet).await?;
|
self.skip_result_metadata(packet).await?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -150,7 +149,7 @@ impl MySqlStream {
|
|||||||
// TODO: packet joining
|
// TODO: packet joining
|
||||||
|
|
||||||
if payload[0] == 0xff {
|
if payload[0] == 0xff {
|
||||||
self.busy = Busy::NotBusy;
|
self.waiting.pop_front();
|
||||||
|
|
||||||
// instead of letting this packet be looked at everywhere, we check here
|
// instead of letting this packet be looked at everywhere, we check here
|
||||||
// and emit a proper Error
|
// and emit a proper Error
|
||||||
|
|||||||
@ -2,7 +2,7 @@ use futures_core::future::BoxFuture;
|
|||||||
|
|
||||||
use crate::error::Error;
|
use crate::error::Error;
|
||||||
use crate::executor::Executor;
|
use crate::executor::Executor;
|
||||||
use crate::mysql::connection::Busy;
|
use crate::mysql::connection::Waiting;
|
||||||
use crate::mysql::protocol::text::Query;
|
use crate::mysql::protocol::text::Query;
|
||||||
use crate::mysql::{MySql, MySqlConnection};
|
use crate::mysql::{MySql, MySqlConnection};
|
||||||
use crate::transaction::{
|
use crate::transaction::{
|
||||||
@ -57,7 +57,7 @@ impl TransactionManager for MySqlTransactionManager {
|
|||||||
let depth = conn.transaction_depth;
|
let depth = conn.transaction_depth;
|
||||||
|
|
||||||
if depth > 0 {
|
if depth > 0 {
|
||||||
conn.stream.busy = Busy::Result;
|
conn.stream.waiting.push_back(Waiting::Result);
|
||||||
conn.stream.sequence_id = 0;
|
conn.stream.sequence_id = 0;
|
||||||
conn.stream
|
conn.stream
|
||||||
.write_packet(Query(&*rollback_ansi_transaction_sql(depth)));
|
.write_packet(Query(&*rollback_ansi_transaction_sql(depth)));
|
||||||
|
|||||||
@ -387,3 +387,62 @@ async fn test_issue_622() -> anyhow::Result<()> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[sqlx_macros::test]
|
||||||
|
async fn it_can_work_with_transactions() -> anyhow::Result<()> {
|
||||||
|
let mut conn = new::<MySql>().await?;
|
||||||
|
conn.execute("CREATE TEMPORARY TABLE users (id INTEGER PRIMARY KEY);")
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
// begin .. rollback
|
||||||
|
|
||||||
|
let mut tx = conn.begin().await?;
|
||||||
|
sqlx::query("INSERT INTO users (id) VALUES (?)")
|
||||||
|
.bind(1_i32)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users")
|
||||||
|
.fetch_one(&mut tx)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
tx.rollback().await?;
|
||||||
|
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users")
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(count, 0);
|
||||||
|
|
||||||
|
// begin .. commit
|
||||||
|
|
||||||
|
let mut tx = conn.begin().await?;
|
||||||
|
sqlx::query("INSERT INTO users (id) VALUES (?)")
|
||||||
|
.bind(1_i32)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
tx.commit().await?;
|
||||||
|
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users")
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
|
||||||
|
// begin .. (drop)
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut tx = conn.begin().await?;
|
||||||
|
|
||||||
|
sqlx::query("INSERT INTO users (id) VALUES (?)")
|
||||||
|
.bind(2)
|
||||||
|
.execute(&mut tx)
|
||||||
|
.await?;
|
||||||
|
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users")
|
||||||
|
.fetch_one(&mut tx)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(count, 2);
|
||||||
|
// tx is dropped
|
||||||
|
}
|
||||||
|
let count: i64 = sqlx::query_scalar("SELECT COUNT(*) FROM users")
|
||||||
|
.fetch_one(&mut conn)
|
||||||
|
.await?;
|
||||||
|
assert_eq!(count, 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user