Skip to main content

sqlmodel_mysql/
async_connection.rs

1//! Async MySQL connection implementation.
2//!
3//! This module implements the async MySQL connection using asupersync's TCP primitives.
4//! It provides the `Connection` trait implementation for integration with sqlmodel-core.
5
6// Allow `impl Future` return types in trait methods - intentional design for async trait compat
7#![allow(clippy::manual_async_fn)]
8// The Error type is intentionally large to carry full context
9#![allow(clippy::result_large_err)]
10
11use std::collections::HashMap;
12use std::future::Future;
13use std::io::{self, Read as StdRead, Write as StdWrite};
14use std::net::TcpStream as StdTcpStream;
15use std::sync::Arc;
16
17use asupersync::io::{AsyncRead, AsyncWrite, ReadBuf};
18use asupersync::net::TcpStream;
19use asupersync::sync::{Mutex, OwnedMutexGuard};
20use asupersync::{Cx, Outcome};
21
22use sqlmodel_core::connection::{Connection, IsolationLevel, PreparedStatement, TransactionOps};
23use sqlmodel_core::error::{
24    ConnectionError, ConnectionErrorKind, ProtocolError, QueryError, QueryErrorKind,
25};
26use sqlmodel_core::{Error, Row, Value};
27
28#[cfg(feature = "console")]
29use sqlmodel_console::{ConsoleAware, SqlModelConsole};
30
31use crate::auth;
32use crate::config::MySqlConfig;
33use crate::connection::{ConnectionState, ServerCapabilities};
34use crate::protocol::{
35    Command, ErrPacket, MAX_PACKET_SIZE, PacketHeader, PacketReader, PacketType, PacketWriter,
36    capabilities, charset, prepared,
37};
38use crate::types::{
39    ColumnDef, FieldType, decode_binary_value_with_len, decode_text_value, interpolate_params,
40};
41
42/// Async MySQL connection.
43///
44/// This connection uses asupersync's TCP stream for non-blocking I/O
45/// and implements the `Connection` trait from sqlmodel-core.
46pub struct MySqlAsyncConnection {
47    /// TCP stream (either sync for compatibility or async wrapper)
48    stream: Option<ConnectionStream>,
49    /// Current connection state
50    state: ConnectionState,
51    /// Server capabilities from handshake
52    server_caps: Option<ServerCapabilities>,
53    /// Connection ID
54    connection_id: u32,
55    /// Server status flags
56    status_flags: u16,
57    /// Affected rows from last statement
58    affected_rows: u64,
59    /// Last insert ID
60    last_insert_id: u64,
61    /// Number of warnings
62    warnings: u16,
63    /// Connection configuration
64    config: MySqlConfig,
65    /// Current sequence ID for packet framing
66    sequence_id: u8,
67    /// Prepared statement metadata (keyed by statement ID)
68    prepared_stmts: HashMap<u32, PreparedStmtMeta>,
69    /// Optional console for rich output
70    #[cfg(feature = "console")]
71    console: Option<Arc<SqlModelConsole>>,
72}
73
74/// Metadata for a prepared statement.
75///
76/// Stores the MySQL-specific information needed to execute
77/// and decode results from a prepared statement.
78#[derive(Debug, Clone)]
79struct PreparedStmtMeta {
80    /// Server-assigned statement ID (stored for potential future use in close/reset)
81    #[allow(dead_code)]
82    statement_id: u32,
83    /// Parameter column definitions (for type encoding)
84    params: Vec<ColumnDef>,
85    /// Result column definitions (for binary decoding)
86    columns: Vec<ColumnDef>,
87}
88
89/// Connection stream wrapper for sync/async compatibility.
90// A TLS stream is ~1KB vs ~72B for plain TCP; boxing it would add a
91// pointer chase on every read/write of the hot I/O path, so keep it inline.
92#[allow(dead_code, clippy::large_enum_variant)]
93enum ConnectionStream {
94    /// Standard sync TCP stream (for initial connection)
95    Sync(StdTcpStream),
96    /// Async TCP stream (for async operations)
97    Async(TcpStream),
98    /// Async TLS stream (for encrypted async operations)
99    #[cfg(feature = "tls")]
100    Tls(AsyncTlsStream),
101}
102
103/// Async TLS stream built on rustls + asupersync TcpStream.
104///
105/// This is intentionally minimal: it provides enough read/write behavior for
106/// MySQL packet framing without depending on a tokio/futures I/O ecosystem.
107#[cfg(feature = "tls")]
108struct AsyncTlsStream {
109    tcp: TcpStream,
110    tls: rustls::ClientConnection,
111}
112
113#[cfg(feature = "tls")]
114impl std::fmt::Debug for AsyncTlsStream {
115    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
116        f.debug_struct("AsyncTlsStream")
117            .field("protocol_version", &self.tls.protocol_version())
118            .field("is_handshaking", &self.tls.is_handshaking())
119            .finish_non_exhaustive()
120    }
121}
122
123#[cfg(feature = "tls")]
124impl AsyncTlsStream {
125    async fn handshake(
126        mut tcp: TcpStream,
127        tls_config: &crate::config::TlsConfig,
128        host: &str,
129        ssl_mode: crate::config::SslMode,
130    ) -> Result<Self, Error> {
131        let config = crate::tls::build_client_config(tls_config, ssl_mode)?;
132
133        let sni = tls_config.server_name.as_deref().unwrap_or(host);
134        let server_name = sni
135            .to_string()
136            .try_into()
137            .map_err(|e| connection_error(format!("Invalid server name '{sni}': {e}")))?;
138
139        let mut tls = rustls::ClientConnection::new(std::sync::Arc::new(config), server_name)
140            .map_err(|e| connection_error(format!("Failed to create TLS connection: {e}")))?;
141
142        // Drive rustls handshake using async reads/writes on the TCP stream.
143        while tls.is_handshaking() {
144            while tls.wants_write() {
145                let mut out = Vec::new();
146                tls.write_tls(&mut out)
147                    .map_err(|e| connection_error(format!("TLS handshake write_tls error: {e}")))?;
148                if !out.is_empty() {
149                    write_all_async(&mut tcp, &out).await.map_err(|e| {
150                        Error::Connection(ConnectionError {
151                            kind: ConnectionErrorKind::Disconnected,
152                            message: format!("TLS handshake write error: {e}"),
153                            source: Some(Box::new(e)),
154                        })
155                    })?;
156                }
157            }
158
159            if tls.wants_read() {
160                let mut buf = [0u8; 8192];
161                let n = read_some_async(&mut tcp, &mut buf).await.map_err(|e| {
162                    Error::Connection(ConnectionError {
163                        kind: ConnectionErrorKind::Disconnected,
164                        message: format!("TLS handshake read error: {e}"),
165                        source: Some(Box::new(e)),
166                    })
167                })?;
168                if n == 0 {
169                    return Err(connection_error("Connection closed during TLS handshake"));
170                }
171
172                let mut cursor = std::io::Cursor::new(&buf[..n]);
173                tls.read_tls(&mut cursor)
174                    .map_err(|e| connection_error(format!("TLS handshake read_tls error: {e}")))?;
175                tls.process_new_packets()
176                    .map_err(|e| connection_error(format!("TLS handshake error: {e}")))?;
177            }
178        }
179
180        Ok(Self { tcp, tls })
181    }
182
183    async fn read_exact(&mut self, buf: &mut [u8]) -> io::Result<()> {
184        let mut read = 0;
185        while read < buf.len() {
186            let n = self.read_plain(&mut buf[read..]).await?;
187            if n == 0 {
188                return Err(io::Error::new(
189                    io::ErrorKind::UnexpectedEof,
190                    "connection closed",
191                ));
192            }
193            read += n;
194        }
195        Ok(())
196    }
197
198    async fn read_plain(&mut self, out: &mut [u8]) -> io::Result<usize> {
199        loop {
200            match self.tls.reader().read(out) {
201                Ok(n) if n > 0 => return Ok(n),
202                Ok(_) => {}
203                Err(e) if e.kind() == io::ErrorKind::WouldBlock => {}
204                Err(e) => return Err(e),
205            }
206
207            if !self.tls.wants_read() {
208                return Ok(0);
209            }
210
211            let mut enc = [0u8; 8192];
212            let n = read_some_async(&mut self.tcp, &mut enc).await?;
213            if n == 0 {
214                return Ok(0);
215            }
216
217            let mut cursor = std::io::Cursor::new(&enc[..n]);
218            self.tls.read_tls(&mut cursor)?;
219            self.tls
220                .process_new_packets()
221                .map_err(|e| io::Error::other(format!("TLS error: {e}")))?;
222        }
223    }
224
225    async fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
226        let mut written = 0;
227        while written < buf.len() {
228            let n = self.tls.writer().write(&buf[written..])?;
229            if n == 0 {
230                return Err(io::Error::new(io::ErrorKind::WriteZero, "TLS write zero"));
231            }
232            written += n;
233            self.flush().await?;
234        }
235        Ok(())
236    }
237
238    async fn flush(&mut self) -> io::Result<()> {
239        self.tls.writer().flush()?;
240        while self.tls.wants_write() {
241            let mut out = Vec::new();
242            self.tls.write_tls(&mut out)?;
243            if !out.is_empty() {
244                write_all_async(&mut self.tcp, &out).await?;
245            }
246        }
247        flush_async(&mut self.tcp).await
248    }
249}
250
251#[cfg(feature = "tls")]
252async fn read_some_async(stream: &mut TcpStream, buf: &mut [u8]) -> io::Result<usize> {
253    let mut read_buf = ReadBuf::new(buf);
254    std::future::poll_fn(|cx| std::pin::Pin::new(&mut *stream).poll_read(cx, &mut read_buf))
255        .await?;
256    Ok(read_buf.filled().len())
257}
258
259#[cfg(feature = "tls")]
260async fn write_all_async(stream: &mut TcpStream, buf: &[u8]) -> io::Result<()> {
261    let mut written = 0;
262    while written < buf.len() {
263        let n = std::future::poll_fn(|cx| {
264            std::pin::Pin::new(&mut *stream).poll_write(cx, &buf[written..])
265        })
266        .await?;
267        if n == 0 {
268            return Err(io::Error::new(
269                io::ErrorKind::WriteZero,
270                "connection closed",
271            ));
272        }
273        written += n;
274    }
275    Ok(())
276}
277
278#[cfg(feature = "tls")]
279async fn flush_async(stream: &mut TcpStream) -> io::Result<()> {
280    std::future::poll_fn(|cx| std::pin::Pin::new(&mut *stream).poll_flush(cx)).await
281}
282
283impl std::fmt::Debug for MySqlAsyncConnection {
284    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
285        f.debug_struct("MySqlAsyncConnection")
286            .field("state", &self.state)
287            .field("connection_id", &self.connection_id)
288            .field("host", &self.config.host)
289            .field("port", &self.config.port)
290            .field("database", &self.config.database)
291            .finish_non_exhaustive()
292    }
293}
294
295impl MySqlAsyncConnection {
296    /// Establish a new async connection to the MySQL server.
297    ///
298    /// This performs the complete connection handshake asynchronously:
299    /// 1. TCP connection
300    /// 2. Receive server handshake
301    /// 3. Send handshake response with authentication
302    /// 4. Handle auth result (possibly auth switch)
303    pub async fn connect(_cx: &Cx, config: MySqlConfig) -> Outcome<Self, Error> {
304        // Use async TCP connect
305        let addr = config.socket_addr();
306        let socket_addr: std::net::SocketAddr = match addr.parse() {
307            Ok(a) => a,
308            Err(e) => {
309                return Outcome::Err(Error::Connection(ConnectionError {
310                    kind: ConnectionErrorKind::Connect,
311                    message: format!("Invalid socket address: {}", e),
312                    source: None,
313                }));
314            }
315        };
316        let stream = match TcpStream::connect_timeout(socket_addr, config.connect_timeout).await {
317            Ok(s) => s,
318            Err(e) => {
319                let kind = if e.kind() == io::ErrorKind::ConnectionRefused {
320                    ConnectionErrorKind::Refused
321                } else {
322                    ConnectionErrorKind::Connect
323                };
324                return Outcome::Err(Error::Connection(ConnectionError {
325                    kind,
326                    message: format!("Failed to connect to {}: {}", addr, e),
327                    source: Some(Box::new(e)),
328                }));
329            }
330        };
331
332        // Set TCP options
333        stream.set_nodelay(true).ok();
334
335        let mut conn = Self {
336            stream: Some(ConnectionStream::Async(stream)),
337            state: ConnectionState::Connecting,
338            server_caps: None,
339            connection_id: 0,
340            status_flags: 0,
341            affected_rows: 0,
342            last_insert_id: 0,
343            warnings: 0,
344            config,
345            sequence_id: 0,
346            prepared_stmts: HashMap::new(),
347            #[cfg(feature = "console")]
348            console: None,
349        };
350
351        // 2. Receive server handshake
352        match conn.read_handshake_async().await {
353            Outcome::Ok(server_caps) => {
354                conn.connection_id = server_caps.connection_id;
355                conn.server_caps = Some(server_caps);
356                conn.state = ConnectionState::Authenticating;
357            }
358            Outcome::Err(e) => return Outcome::Err(e),
359            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
360            Outcome::Panicked(p) => return Outcome::Panicked(p),
361        }
362
363        // 3. Send handshake response
364        if let Outcome::Err(e) = conn.send_handshake_response_async().await {
365            return Outcome::Err(e);
366        }
367
368        // 4. Handle authentication result
369        if let Outcome::Err(e) = conn.handle_auth_result_async().await {
370            return Outcome::Err(e);
371        }
372
373        conn.state = ConnectionState::Ready;
374        Outcome::Ok(conn)
375    }
376
377    /// Get the current connection state.
378    pub fn state(&self) -> ConnectionState {
379        self.state
380    }
381
382    /// Check if the connection is ready for queries.
383    pub fn is_ready(&self) -> bool {
384        matches!(self.state, ConnectionState::Ready)
385    }
386
387    fn is_secure_transport(&self) -> bool {
388        #[cfg(feature = "tls")]
389        {
390            matches!(self.stream, Some(ConnectionStream::Tls(_)))
391        }
392        #[cfg(not(feature = "tls"))]
393        {
394            false
395        }
396    }
397
398    /// Get the connection ID.
399    pub fn connection_id(&self) -> u32 {
400        self.connection_id
401    }
402
403    /// Get the server version.
404    pub fn server_version(&self) -> Option<&str> {
405        self.server_caps
406            .as_ref()
407            .map(|caps| caps.server_version.as_str())
408    }
409
410    /// Get the number of affected rows from the last statement.
411    pub fn affected_rows(&self) -> u64 {
412        self.affected_rows
413    }
414
415    /// Get the last insert ID.
416    pub fn last_insert_id(&self) -> u64 {
417        self.last_insert_id
418    }
419
420    // === Async I/O methods ===
421
422    /// Read a complete packet from the stream asynchronously.
423    async fn read_packet_async(&mut self) -> Outcome<(Vec<u8>, u8), Error> {
424        // Read header (4 bytes) - must loop since TCP can fragment reads
425        let mut header_buf = [0u8; 4];
426
427        let Some(stream) = self.stream.as_mut() else {
428            return Outcome::Err(connection_error("Connection stream missing"));
429        };
430
431        match stream {
432            ConnectionStream::Async(stream) => {
433                let mut header_read = 0;
434                while header_read < 4 {
435                    let mut read_buf = ReadBuf::new(&mut header_buf[header_read..]);
436                    match std::future::poll_fn(|cx| {
437                        std::pin::Pin::new(&mut *stream).poll_read(cx, &mut read_buf)
438                    })
439                    .await
440                    {
441                        Ok(()) => {
442                            let n = read_buf.filled().len();
443                            if n == 0 {
444                                return Outcome::Err(Error::Connection(ConnectionError {
445                                    kind: ConnectionErrorKind::Disconnected,
446                                    message: "Connection closed while reading header".to_string(),
447                                    source: None,
448                                }));
449                            }
450                            header_read += n;
451                        }
452                        Err(e) => {
453                            return Outcome::Err(Error::Connection(ConnectionError {
454                                kind: ConnectionErrorKind::Disconnected,
455                                message: format!("Failed to read packet header: {}", e),
456                                source: Some(Box::new(e)),
457                            }));
458                        }
459                    }
460                }
461            }
462            ConnectionStream::Sync(stream) => {
463                if let Err(e) = stream.read_exact(&mut header_buf) {
464                    return Outcome::Err(Error::Connection(ConnectionError {
465                        kind: ConnectionErrorKind::Disconnected,
466                        message: format!("Failed to read packet header: {}", e),
467                        source: Some(Box::new(e)),
468                    }));
469                }
470            }
471            #[cfg(feature = "tls")]
472            ConnectionStream::Tls(stream) => {
473                if let Err(e) = stream.read_exact(&mut header_buf).await {
474                    return Outcome::Err(Error::Connection(ConnectionError {
475                        kind: ConnectionErrorKind::Disconnected,
476                        message: format!("Failed to read packet header: {e}"),
477                        source: Some(Box::new(e)),
478                    }));
479                }
480            }
481        }
482
483        let header = PacketHeader::from_bytes(&header_buf);
484        let payload_len = header.payload_length as usize;
485        self.sequence_id = header.sequence_id.wrapping_add(1);
486
487        // Read payload
488        let mut payload = vec![0u8; payload_len];
489        if payload_len > 0 {
490            let Some(stream) = self.stream.as_mut() else {
491                return Outcome::Err(connection_error("Connection stream missing"));
492            };
493            match stream {
494                ConnectionStream::Async(stream) => {
495                    let mut total_read = 0;
496                    while total_read < payload_len {
497                        let mut read_buf = ReadBuf::new(&mut payload[total_read..]);
498                        match std::future::poll_fn(|cx| {
499                            std::pin::Pin::new(&mut *stream).poll_read(cx, &mut read_buf)
500                        })
501                        .await
502                        {
503                            Ok(()) => {
504                                let n = read_buf.filled().len();
505                                if n == 0 {
506                                    return Outcome::Err(Error::Connection(ConnectionError {
507                                        kind: ConnectionErrorKind::Disconnected,
508                                        message: "Connection closed while reading payload"
509                                            .to_string(),
510                                        source: None,
511                                    }));
512                                }
513                                total_read += n;
514                            }
515                            Err(e) => {
516                                return Outcome::Err(Error::Connection(ConnectionError {
517                                    kind: ConnectionErrorKind::Disconnected,
518                                    message: format!("Failed to read packet payload: {}", e),
519                                    source: Some(Box::new(e)),
520                                }));
521                            }
522                        }
523                    }
524                }
525                ConnectionStream::Sync(stream) => {
526                    if let Err(e) = stream.read_exact(&mut payload) {
527                        return Outcome::Err(Error::Connection(ConnectionError {
528                            kind: ConnectionErrorKind::Disconnected,
529                            message: format!("Failed to read packet payload: {}", e),
530                            source: Some(Box::new(e)),
531                        }));
532                    }
533                }
534                #[cfg(feature = "tls")]
535                ConnectionStream::Tls(stream) => {
536                    if let Err(e) = stream.read_exact(&mut payload).await {
537                        return Outcome::Err(Error::Connection(ConnectionError {
538                            kind: ConnectionErrorKind::Disconnected,
539                            message: format!("Failed to read packet payload: {e}"),
540                            source: Some(Box::new(e)),
541                        }));
542                    }
543                }
544            }
545        }
546
547        // Handle multi-packet payloads
548        if payload_len == MAX_PACKET_SIZE {
549            loop {
550                // Read continuation header with loop (TCP can fragment)
551                let mut header_buf = [0u8; 4];
552                let Some(stream) = self.stream.as_mut() else {
553                    return Outcome::Err(connection_error("Connection stream missing"));
554                };
555                match stream {
556                    ConnectionStream::Async(stream) => {
557                        let mut header_read = 0;
558                        while header_read < 4 {
559                            let mut read_buf = ReadBuf::new(&mut header_buf[header_read..]);
560                            match std::future::poll_fn(|cx| {
561                                std::pin::Pin::new(&mut *stream).poll_read(cx, &mut read_buf)
562                            })
563                            .await
564                            {
565                                Ok(()) => {
566                                    let n = read_buf.filled().len();
567                                    if n == 0 {
568                                        return Outcome::Err(Error::Connection(ConnectionError {
569                                            kind: ConnectionErrorKind::Disconnected,
570                                            message: "Connection closed while reading continuation header".to_string(),
571                                            source: None,
572                                        }));
573                                    }
574                                    header_read += n;
575                                }
576                                Err(e) => {
577                                    return Outcome::Err(Error::Connection(ConnectionError {
578                                        kind: ConnectionErrorKind::Disconnected,
579                                        message: format!(
580                                            "Failed to read continuation header: {}",
581                                            e
582                                        ),
583                                        source: Some(Box::new(e)),
584                                    }));
585                                }
586                            }
587                        }
588                    }
589                    ConnectionStream::Sync(stream) => {
590                        if let Err(e) = stream.read_exact(&mut header_buf) {
591                            return Outcome::Err(Error::Connection(ConnectionError {
592                                kind: ConnectionErrorKind::Disconnected,
593                                message: format!("Failed to read continuation header: {}", e),
594                                source: Some(Box::new(e)),
595                            }));
596                        }
597                    }
598                    #[cfg(feature = "tls")]
599                    ConnectionStream::Tls(stream) => {
600                        if let Err(e) = stream.read_exact(&mut header_buf).await {
601                            return Outcome::Err(Error::Connection(ConnectionError {
602                                kind: ConnectionErrorKind::Disconnected,
603                                message: format!("Failed to read continuation header: {e}"),
604                                source: Some(Box::new(e)),
605                            }));
606                        }
607                    }
608                }
609
610                let cont_header = PacketHeader::from_bytes(&header_buf);
611                let cont_len = cont_header.payload_length as usize;
612                self.sequence_id = cont_header.sequence_id.wrapping_add(1);
613
614                if cont_len > 0 {
615                    let mut cont_payload = vec![0u8; cont_len];
616                    let Some(stream) = self.stream.as_mut() else {
617                        return Outcome::Err(connection_error("Connection stream missing"));
618                    };
619                    match stream {
620                        ConnectionStream::Async(stream) => {
621                            let mut total_read = 0;
622                            while total_read < cont_len {
623                                let mut read_buf = ReadBuf::new(&mut cont_payload[total_read..]);
624                                match std::future::poll_fn(|cx| {
625                                    std::pin::Pin::new(&mut *stream).poll_read(cx, &mut read_buf)
626                                })
627                                .await
628                                {
629                                    Ok(()) => {
630                                        let n = read_buf.filled().len();
631                                        if n == 0 {
632                                            return Outcome::Err(Error::Connection(ConnectionError {
633                                                kind: ConnectionErrorKind::Disconnected,
634                                                message: "Connection closed while reading continuation payload".to_string(),
635                                                source: None,
636                                            }));
637                                        }
638                                        total_read += n;
639                                    }
640                                    Err(e) => {
641                                        return Outcome::Err(Error::Connection(ConnectionError {
642                                            kind: ConnectionErrorKind::Disconnected,
643                                            message: format!(
644                                                "Failed to read continuation payload: {}",
645                                                e
646                                            ),
647                                            source: Some(Box::new(e)),
648                                        }));
649                                    }
650                                }
651                            }
652                        }
653                        ConnectionStream::Sync(stream) => {
654                            if let Err(e) = stream.read_exact(&mut cont_payload) {
655                                return Outcome::Err(Error::Connection(ConnectionError {
656                                    kind: ConnectionErrorKind::Disconnected,
657                                    message: format!("Failed to read continuation payload: {}", e),
658                                    source: Some(Box::new(e)),
659                                }));
660                            }
661                        }
662                        #[cfg(feature = "tls")]
663                        ConnectionStream::Tls(stream) => {
664                            if let Err(e) = stream.read_exact(&mut cont_payload).await {
665                                return Outcome::Err(Error::Connection(ConnectionError {
666                                    kind: ConnectionErrorKind::Disconnected,
667                                    message: format!("Failed to read continuation payload: {e}"),
668                                    source: Some(Box::new(e)),
669                                }));
670                            }
671                        }
672                    }
673                    payload.extend_from_slice(&cont_payload);
674                }
675
676                if cont_len < MAX_PACKET_SIZE {
677                    break;
678                }
679            }
680        }
681
682        Outcome::Ok((payload, header.sequence_id))
683    }
684
685    /// Write a packet to the stream asynchronously.
686    async fn write_packet_async(&mut self, payload: &[u8]) -> Outcome<(), Error> {
687        let writer = PacketWriter::new();
688        let packet = writer.build_packet_from_payload(payload, self.sequence_id);
689        self.sequence_id = self.sequence_id.wrapping_add(1);
690
691        let Some(stream) = self.stream.as_mut() else {
692            return Outcome::Err(connection_error("Connection stream missing"));
693        };
694
695        match stream {
696            ConnectionStream::Async(stream) => {
697                // Loop to handle partial writes (poll_write may return fewer bytes)
698                let mut written = 0;
699                while written < packet.len() {
700                    match std::future::poll_fn(|cx| {
701                        std::pin::Pin::new(&mut *stream).poll_write(cx, &packet[written..])
702                    })
703                    .await
704                    {
705                        Ok(n) => {
706                            if n == 0 {
707                                return Outcome::Err(Error::Connection(ConnectionError {
708                                    kind: ConnectionErrorKind::Disconnected,
709                                    message: "Connection closed while writing packet".to_string(),
710                                    source: None,
711                                }));
712                            }
713                            written += n;
714                        }
715                        Err(e) => {
716                            return Outcome::Err(Error::Connection(ConnectionError {
717                                kind: ConnectionErrorKind::Disconnected,
718                                message: format!("Failed to write packet: {}", e),
719                                source: Some(Box::new(e)),
720                            }));
721                        }
722                    }
723                }
724
725                match std::future::poll_fn(|cx| std::pin::Pin::new(&mut *stream).poll_flush(cx))
726                    .await
727                {
728                    Ok(()) => {}
729                    Err(e) => {
730                        return Outcome::Err(Error::Connection(ConnectionError {
731                            kind: ConnectionErrorKind::Disconnected,
732                            message: format!("Failed to flush stream: {}", e),
733                            source: Some(Box::new(e)),
734                        }));
735                    }
736                }
737            }
738            ConnectionStream::Sync(stream) => {
739                if let Err(e) = stream.write_all(&packet) {
740                    return Outcome::Err(Error::Connection(ConnectionError {
741                        kind: ConnectionErrorKind::Disconnected,
742                        message: format!("Failed to write packet: {}", e),
743                        source: Some(Box::new(e)),
744                    }));
745                }
746                if let Err(e) = stream.flush() {
747                    return Outcome::Err(Error::Connection(ConnectionError {
748                        kind: ConnectionErrorKind::Disconnected,
749                        message: format!("Failed to flush stream: {}", e),
750                        source: Some(Box::new(e)),
751                    }));
752                }
753            }
754            #[cfg(feature = "tls")]
755            ConnectionStream::Tls(stream) => {
756                if let Err(e) = stream.write_all(&packet).await {
757                    return Outcome::Err(Error::Connection(ConnectionError {
758                        kind: ConnectionErrorKind::Disconnected,
759                        message: format!("Failed to write packet: {e}"),
760                        source: Some(Box::new(e)),
761                    }));
762                }
763                if let Err(e) = stream.flush().await {
764                    return Outcome::Err(Error::Connection(ConnectionError {
765                        kind: ConnectionErrorKind::Disconnected,
766                        message: format!("Failed to flush stream: {e}"),
767                        source: Some(Box::new(e)),
768                    }));
769                }
770            }
771        }
772
773        Outcome::Ok(())
774    }
775
776    // === Handshake methods ===
777
778    /// Read the server handshake packet asynchronously.
779    async fn read_handshake_async(&mut self) -> Outcome<ServerCapabilities, Error> {
780        let (payload, _) = match self.read_packet_async().await {
781            Outcome::Ok(p) => p,
782            Outcome::Err(e) => return Outcome::Err(e),
783            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
784            Outcome::Panicked(p) => return Outcome::Panicked(p),
785        };
786
787        let mut reader = PacketReader::new(&payload);
788
789        // Protocol version
790        let Some(protocol_version) = reader.read_u8() else {
791            return Outcome::Err(protocol_error("Missing protocol version"));
792        };
793
794        if protocol_version != 10 {
795            return Outcome::Err(protocol_error(format!(
796                "Unsupported protocol version: {}",
797                protocol_version
798            )));
799        }
800
801        // Server version (null-terminated string)
802        let Some(server_version) = reader.read_null_string() else {
803            return Outcome::Err(protocol_error("Missing server version"));
804        };
805
806        // Connection ID
807        let Some(connection_id) = reader.read_u32_le() else {
808            return Outcome::Err(protocol_error("Missing connection ID"));
809        };
810
811        // Auth plugin data part 1 (8 bytes)
812        let Some(auth_data_1) = reader.read_bytes(8) else {
813            return Outcome::Err(protocol_error("Missing auth data"));
814        };
815
816        // Filler (1 byte)
817        reader.skip(1);
818
819        // Capability flags (lower 2 bytes)
820        let Some(caps_lower) = reader.read_u16_le() else {
821            return Outcome::Err(protocol_error("Missing capability flags"));
822        };
823
824        // Character set
825        let charset_val = reader.read_u8().unwrap_or(charset::UTF8MB4_0900_AI_CI);
826
827        // Status flags
828        let status_flags = reader.read_u16_le().unwrap_or(0);
829
830        // Capability flags (upper 2 bytes)
831        let caps_upper = reader.read_u16_le().unwrap_or(0);
832        let capabilities_val = u32::from(caps_lower) | (u32::from(caps_upper) << 16);
833
834        // Length of auth-plugin-data (if CLIENT_PLUGIN_AUTH)
835        let auth_data_len = if capabilities_val & capabilities::CLIENT_PLUGIN_AUTH != 0 {
836            reader.read_u8().unwrap_or(0) as usize
837        } else {
838            0
839        };
840
841        // Reserved (10 bytes)
842        reader.skip(10);
843
844        // Auth plugin data part 2 (if CLIENT_SECURE_CONNECTION)
845        let mut auth_data = auth_data_1.to_vec();
846        if capabilities_val & capabilities::CLIENT_SECURE_CONNECTION != 0 {
847            let len2 = if auth_data_len > 8 {
848                auth_data_len - 8
849            } else {
850                13 // Default length
851            };
852            if let Some(data2) = reader.read_bytes(len2) {
853                // Remove trailing NUL if present
854                let data2_clean = if data2.last() == Some(&0) {
855                    &data2[..data2.len() - 1]
856                } else {
857                    data2
858                };
859                auth_data.extend_from_slice(data2_clean);
860            }
861        }
862
863        // Auth plugin name (if CLIENT_PLUGIN_AUTH)
864        let auth_plugin = if capabilities_val & capabilities::CLIENT_PLUGIN_AUTH != 0 {
865            reader.read_null_string().unwrap_or_default()
866        } else {
867            auth::plugins::MYSQL_NATIVE_PASSWORD.to_string()
868        };
869
870        Outcome::Ok(ServerCapabilities {
871            capabilities: capabilities_val,
872            protocol_version,
873            server_version,
874            connection_id,
875            auth_plugin,
876            auth_data,
877            charset: charset_val,
878            status_flags,
879        })
880    }
881
882    /// Send the handshake response packet asynchronously.
883    async fn send_handshake_response_async(&mut self) -> Outcome<(), Error> {
884        let Some(server_caps) = self.server_caps.as_ref() else {
885            return Outcome::Err(protocol_error("No server handshake received"));
886        };
887
888        // Grab what we need up-front so we can mutably borrow `self` later.
889        let server_caps_bits = server_caps.capabilities;
890        let auth_plugin = server_caps.auth_plugin.clone();
891        let auth_data = server_caps.auth_data.clone();
892
893        // Determine client capabilities
894        let mut client_caps = self.config.capability_flags() & server_caps_bits;
895        #[cfg(feature = "tls")]
896        if let Outcome::Err(e) = self
897            .maybe_upgrade_tls_async(server_caps_bits, &mut client_caps)
898            .await
899        {
900            return Outcome::Err(e);
901        }
902
903        #[cfg(not(feature = "tls"))]
904        if let Outcome::Err(e) = self.maybe_upgrade_tls(server_caps_bits, &mut client_caps) {
905            return Outcome::Err(e);
906        }
907
908        // Build authentication response
909        let auth_response = self.compute_auth_response(&auth_plugin, &auth_data);
910
911        let mut writer = PacketWriter::new();
912
913        // Client capability flags (4 bytes)
914        writer.write_u32_le(client_caps);
915
916        // Max packet size (4 bytes)
917        writer.write_u32_le(self.config.max_packet_size);
918
919        // Character set (1 byte)
920        writer.write_u8(self.config.charset);
921
922        // Reserved (23 bytes of zeros)
923        writer.write_zeros(23);
924
925        // Username (null-terminated)
926        writer.write_null_string(&self.config.user);
927
928        // Auth response
929        if client_caps & capabilities::CLIENT_PLUGIN_AUTH_LENENC_CLIENT_DATA != 0 {
930            writer.write_lenenc_bytes(&auth_response);
931        } else if client_caps & capabilities::CLIENT_SECURE_CONNECTION != 0 {
932            #[allow(clippy::cast_possible_truncation)]
933            writer.write_u8(auth_response.len() as u8);
934            writer.write_bytes(&auth_response);
935        } else {
936            writer.write_bytes(&auth_response);
937            writer.write_u8(0); // Null terminator
938        }
939
940        // Database (if CLIENT_CONNECT_WITH_DB)
941        if client_caps & capabilities::CLIENT_CONNECT_WITH_DB != 0 {
942            if let Some(ref db) = self.config.database {
943                writer.write_null_string(db);
944            } else {
945                writer.write_u8(0); // Empty string
946            }
947        }
948
949        // Auth plugin name (if CLIENT_PLUGIN_AUTH)
950        if client_caps & capabilities::CLIENT_PLUGIN_AUTH != 0 {
951            writer.write_null_string(&auth_plugin);
952        }
953
954        // Connection attributes (if CLIENT_CONNECT_ATTRS)
955        if client_caps & capabilities::CLIENT_CONNECT_ATTRS != 0
956            && !self.config.attributes.is_empty()
957        {
958            let mut attrs_writer = PacketWriter::new();
959            for (key, value) in &self.config.attributes {
960                attrs_writer.write_lenenc_string(key);
961                attrs_writer.write_lenenc_string(value);
962            }
963            let attrs_data = attrs_writer.into_bytes();
964            writer.write_lenenc_bytes(&attrs_data);
965        }
966
967        self.write_packet_async(writer.as_bytes()).await
968    }
969
970    #[cfg(feature = "tls")]
971    async fn maybe_upgrade_tls_async(
972        &mut self,
973        server_caps: u32,
974        client_caps: &mut u32,
975    ) -> Outcome<(), Error> {
976        let ssl_mode = self.config.ssl_mode;
977
978        if !ssl_mode.should_try_ssl() {
979            *client_caps &= !capabilities::CLIENT_SSL;
980            return Outcome::Ok(());
981        }
982
983        let use_tls = match crate::tls::validate_ssl_mode(ssl_mode, server_caps) {
984            Ok(v) => v,
985            Err(e) => return Outcome::Err(e),
986        };
987
988        if !use_tls {
989            // Preferred but server doesn't support SSL: clear the bit so we don't lie.
990            *client_caps &= !capabilities::CLIENT_SSL;
991            return Outcome::Ok(());
992        }
993
994        if let Err(e) = crate::tls::validate_tls_config(ssl_mode, &self.config.tls_config) {
995            return Outcome::Err(e);
996        }
997
998        // Send SSLRequest packet (sequence id 1), then upgrade to TLS and continue
999        // the normal handshake (sequence id 2) over the encrypted stream.
1000        let packet = crate::tls::build_ssl_request_packet(
1001            *client_caps,
1002            self.config.max_packet_size,
1003            self.config.charset,
1004            self.sequence_id,
1005        );
1006        if let Outcome::Err(e) = self.write_packet_raw_async(&packet).await {
1007            return Outcome::Err(e);
1008        }
1009        self.sequence_id = self.sequence_id.wrapping_add(1);
1010
1011        let Some(stream) = self.stream.take() else {
1012            return Outcome::Err(connection_error("Connection stream missing"));
1013        };
1014        let ConnectionStream::Async(tcp) = stream else {
1015            return Outcome::Err(connection_error("TLS upgrade requires async TCP stream"));
1016        };
1017
1018        let tls = match AsyncTlsStream::handshake(
1019            tcp,
1020            &self.config.tls_config,
1021            &self.config.host,
1022            ssl_mode,
1023        )
1024        .await
1025        {
1026            Ok(s) => s,
1027            Err(e) => return Outcome::Err(e),
1028        };
1029
1030        self.stream = Some(ConnectionStream::Tls(tls));
1031        Outcome::Ok(())
1032    }
1033
1034    #[cfg(not(feature = "tls"))]
1035    fn maybe_upgrade_tls(&mut self, server_caps: u32, client_caps: &mut u32) -> Outcome<(), Error> {
1036        let ssl_mode = self.config.ssl_mode;
1037
1038        if !ssl_mode.should_try_ssl() {
1039            *client_caps &= !capabilities::CLIENT_SSL;
1040            return Outcome::Ok(());
1041        }
1042
1043        let use_tls = match crate::tls::validate_ssl_mode(ssl_mode, server_caps) {
1044            Ok(v) => v,
1045            Err(e) => return Outcome::Err(e),
1046        };
1047
1048        if !use_tls {
1049            // Preferred but server doesn't support SSL: clear the bit so we don't lie.
1050            *client_caps &= !capabilities::CLIENT_SSL;
1051            return Outcome::Ok(());
1052        }
1053
1054        // Preferred should fall back to plain, required/verify must error.
1055        if ssl_mode == crate::config::SslMode::Preferred {
1056            *client_caps &= !capabilities::CLIENT_SSL;
1057            Outcome::Ok(())
1058        } else {
1059            Outcome::Err(connection_error(
1060                "TLS requested but 'sqlmodel-mysql' was built without feature 'tls'",
1061            ))
1062        }
1063    }
1064
1065    /// Compute authentication response based on the plugin.
1066    fn compute_auth_response(&self, plugin: &str, auth_data: &[u8]) -> Vec<u8> {
1067        let pw = self.config.password_str();
1068
1069        match plugin {
1070            // UBS secret-heuristic false-positive: it matches `PASSWORD\s*=`; the block comment breaks that pattern.
1071            auth::plugins::MYSQL_NATIVE_PASSWORD /* ubs-fp */ => {
1072                auth::mysql_native_password(pw, auth_data)
1073            }
1074            auth::plugins::CACHING_SHA2_PASSWORD /* ubs-fp */ => {
1075                auth::caching_sha2_password(pw, auth_data)
1076            }
1077            auth::plugins::MYSQL_CLEAR_PASSWORD /* ubs-fp */ => {
1078                let mut result = pw.as_bytes().to_vec();
1079                result.push(0);
1080                result
1081            }
1082            _ => auth::mysql_native_password(pw, auth_data),
1083        }
1084    }
1085
1086    /// Handle authentication result asynchronously.
1087    /// Uses a loop to handle auth switches without recursion.
1088    async fn handle_auth_result_async(&mut self) -> Outcome<(), Error> {
1089        // Loop to handle potential auth switches without recursion
1090        loop {
1091            let (payload, _) = match self.read_packet_async().await {
1092                Outcome::Ok(p) => p,
1093                Outcome::Err(e) => return Outcome::Err(e),
1094                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1095                Outcome::Panicked(p) => return Outcome::Panicked(p),
1096            };
1097
1098            if payload.is_empty() {
1099                return Outcome::Err(protocol_error("Empty authentication response"));
1100            }
1101
1102            #[allow(clippy::cast_possible_truncation)] // MySQL packets are max 16MB
1103            match PacketType::from_first_byte(payload[0], payload.len() as u32) {
1104                PacketType::Ok => {
1105                    let mut reader = PacketReader::new(&payload);
1106                    if let Some(ok) = reader.parse_ok_packet() {
1107                        self.status_flags = ok.status_flags;
1108                        self.affected_rows = ok.affected_rows;
1109                    }
1110                    return Outcome::Ok(());
1111                }
1112                PacketType::Error => {
1113                    let mut reader = PacketReader::new(&payload);
1114                    let Some(err) = reader.parse_err_packet() else {
1115                        return Outcome::Err(protocol_error("Invalid error packet"));
1116                    };
1117                    return Outcome::Err(auth_error(format!(
1118                        "Authentication failed: {} ({})",
1119                        err.error_message, err.error_code
1120                    )));
1121                }
1122                PacketType::Eof => {
1123                    // Auth switch request - handle inline to avoid recursion
1124                    let data = &payload[1..];
1125                    let mut reader = PacketReader::new(data);
1126
1127                    let Some(plugin) = reader.read_null_string() else {
1128                        return Outcome::Err(protocol_error("Missing plugin name in auth switch"));
1129                    };
1130
1131                    let auth_data = reader.read_rest();
1132                    let response = self.compute_auth_response(&plugin, auth_data);
1133
1134                    if let Outcome::Err(e) = self.write_packet_async(&response).await {
1135                        return Outcome::Err(e);
1136                    }
1137                    // Continue loop to read next auth result
1138                }
1139                _ => {
1140                    // Handle additional auth data
1141                    match self.handle_additional_auth_async(&payload).await {
1142                        Outcome::Ok(()) => continue,
1143                        Outcome::Err(e) => return Outcome::Err(e),
1144                        Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1145                        Outcome::Panicked(p) => return Outcome::Panicked(p),
1146                    }
1147                }
1148            }
1149        }
1150    }
1151
1152    /// Handle additional auth data asynchronously.
1153    async fn handle_additional_auth_async(&mut self, data: &[u8]) -> Outcome<(), Error> {
1154        if data.is_empty() {
1155            return Outcome::Err(protocol_error("Empty additional auth data"));
1156        }
1157
1158        match data[0] {
1159            auth::caching_sha2::FAST_AUTH_SUCCESS => {
1160                // Server will send the final OK packet next; leave it for the main auth loop.
1161                Outcome::Ok(())
1162            }
1163            auth::caching_sha2::PERFORM_FULL_AUTH => {
1164                let Some(server_caps) = self.server_caps.as_ref() else {
1165                    return Outcome::Err(protocol_error("Missing server capabilities during auth"));
1166                };
1167
1168                let pw = self.config.password_owned();
1169                let seed = server_caps.auth_data.clone();
1170                let server_version = server_caps.server_version.clone();
1171
1172                if self.is_secure_transport() {
1173                    // On a secure transport (TLS), caching_sha2_password allows sending the
1174                    // password as a NUL-terminated string.
1175                    let mut clear = pw.as_bytes().to_vec();
1176                    clear.push(0);
1177                    if let Outcome::Err(e) = self.write_packet_async(&clear).await {
1178                        return Outcome::Err(e);
1179                    }
1180                    Outcome::Ok(())
1181                } else {
1182                    // Request server public key then send RSA-encrypted password.
1183                    if let Outcome::Err(e) = self
1184                        .write_packet_async(&[auth::caching_sha2::REQUEST_PUBLIC_KEY])
1185                        .await
1186                    {
1187                        return Outcome::Err(e);
1188                    }
1189
1190                    let (payload, _) = match self.read_packet_async().await {
1191                        Outcome::Ok(p) => p,
1192                        Outcome::Err(e) => return Outcome::Err(e),
1193                        Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1194                        Outcome::Panicked(p) => return Outcome::Panicked(p),
1195                    };
1196                    if payload.is_empty() {
1197                        return Outcome::Err(protocol_error("Empty public key response"));
1198                    }
1199
1200                    // Some servers wrap the PEM in an AuthMoreData packet (0x01 prefix).
1201                    let public_key = if payload[0] == 0x01 {
1202                        &payload[1..]
1203                    } else {
1204                        &payload[..]
1205                    };
1206
1207                    let use_oaep = mysql_server_uses_oaep(&server_version);
1208                    let encrypted =
1209                        match auth::sha256_password_rsa(&pw, &seed, public_key, use_oaep) {
1210                            Ok(v) => v,
1211                            Err(e) => return Outcome::Err(auth_error(e)),
1212                        };
1213
1214                    if let Outcome::Err(e) = self.write_packet_async(&encrypted).await {
1215                        return Outcome::Err(e);
1216                    }
1217                    Outcome::Ok(())
1218                }
1219            }
1220            _ => Outcome::Err(protocol_error(format!(
1221                "Unknown additional auth response: {:02X}",
1222                data[0]
1223            ))),
1224        }
1225    }
1226
1227    /// Execute a text protocol query asynchronously.
1228    pub async fn query_async(
1229        &mut self,
1230        _cx: &Cx,
1231        sql: &str,
1232        params: &[Value],
1233    ) -> Outcome<Vec<Row>, Error> {
1234        let sql = interpolate_params(sql, params);
1235        if !self.is_ready() && self.state != ConnectionState::InTransaction {
1236            return Outcome::Err(connection_error("Connection not ready for queries"));
1237        }
1238
1239        self.state = ConnectionState::InQuery;
1240        self.sequence_id = 0;
1241
1242        // Send COM_QUERY
1243        let mut writer = PacketWriter::new();
1244        writer.write_u8(Command::Query as u8);
1245        writer.write_bytes(sql.as_bytes());
1246
1247        if let Outcome::Err(e) = self.write_packet_async(writer.as_bytes()).await {
1248            return Outcome::Err(e);
1249        }
1250
1251        // Read response
1252        let (payload, _) = match self.read_packet_async().await {
1253            Outcome::Ok(p) => p,
1254            Outcome::Err(e) => return Outcome::Err(e),
1255            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1256            Outcome::Panicked(p) => return Outcome::Panicked(p),
1257        };
1258
1259        if payload.is_empty() {
1260            self.state = ConnectionState::Ready;
1261            return Outcome::Err(protocol_error("Empty query response"));
1262        }
1263
1264        #[allow(clippy::cast_possible_truncation)] // MySQL packets are max 16MB
1265        match PacketType::from_first_byte(payload[0], payload.len() as u32) {
1266            PacketType::Ok => {
1267                let mut reader = PacketReader::new(&payload);
1268                if let Some(ok) = reader.parse_ok_packet() {
1269                    self.affected_rows = ok.affected_rows;
1270                    self.last_insert_id = ok.last_insert_id;
1271                    self.status_flags = ok.status_flags;
1272                    self.warnings = ok.warnings;
1273                }
1274                self.state = if self.status_flags
1275                    & crate::protocol::server_status::SERVER_STATUS_IN_TRANS
1276                    != 0
1277                {
1278                    ConnectionState::InTransaction
1279                } else {
1280                    ConnectionState::Ready
1281                };
1282                Outcome::Ok(vec![])
1283            }
1284            PacketType::Error => {
1285                self.state = ConnectionState::Ready;
1286                let mut reader = PacketReader::new(&payload);
1287                let Some(err) = reader.parse_err_packet() else {
1288                    return Outcome::Err(protocol_error("Invalid error packet"));
1289                };
1290                Outcome::Err(query_error(&err))
1291            }
1292            PacketType::LocalInfile => {
1293                self.state = ConnectionState::Ready;
1294                Outcome::Err(query_error_msg("LOCAL INFILE not supported"))
1295            }
1296            _ => self.read_result_set_async(&payload).await,
1297        }
1298    }
1299
1300    /// Read a result set asynchronously.
1301    async fn read_result_set_async(&mut self, first_packet: &[u8]) -> Outcome<Vec<Row>, Error> {
1302        let mut reader = PacketReader::new(first_packet);
1303        #[allow(clippy::cast_possible_truncation)] // Column count fits in usize
1304        let Some(column_count) = reader.read_lenenc_int().map(|c| c as usize) else {
1305            return Outcome::Err(protocol_error("Invalid column count"));
1306        };
1307
1308        // Read column definitions
1309        let mut columns = Vec::with_capacity(column_count);
1310        for _ in 0..column_count {
1311            let (payload, _) = match self.read_packet_async().await {
1312                Outcome::Ok(p) => p,
1313                Outcome::Err(e) => return Outcome::Err(e),
1314                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1315                Outcome::Panicked(p) => return Outcome::Panicked(p),
1316            };
1317            match self.parse_column_def(&payload) {
1318                Ok(col) => columns.push(col),
1319                Err(e) => return Outcome::Err(e),
1320            }
1321        }
1322
1323        // Check for EOF packet
1324        let server_caps = self.server_caps.as_ref().map_or(0, |c| c.capabilities);
1325        if server_caps & capabilities::CLIENT_DEPRECATE_EOF == 0 {
1326            let (payload, _) = match self.read_packet_async().await {
1327                Outcome::Ok(p) => p,
1328                Outcome::Err(e) => return Outcome::Err(e),
1329                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1330                Outcome::Panicked(p) => return Outcome::Panicked(p),
1331            };
1332            if payload.first() == Some(&0xFE) {
1333                // EOF packet - continue to rows
1334            }
1335        }
1336
1337        // Read rows until EOF or OK
1338        let mut rows = Vec::new();
1339        loop {
1340            let (payload, _) = match self.read_packet_async().await {
1341                Outcome::Ok(p) => p,
1342                Outcome::Err(e) => return Outcome::Err(e),
1343                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1344                Outcome::Panicked(p) => return Outcome::Panicked(p),
1345            };
1346
1347            if payload.is_empty() {
1348                break;
1349            }
1350
1351            #[allow(clippy::cast_possible_truncation)] // MySQL packets are max 16MB
1352            match PacketType::from_first_byte(payload[0], payload.len() as u32) {
1353                PacketType::Eof | PacketType::Ok => {
1354                    let mut reader = PacketReader::new(&payload);
1355                    if payload[0] == 0x00 {
1356                        if let Some(ok) = reader.parse_ok_packet() {
1357                            self.status_flags = ok.status_flags;
1358                            self.warnings = ok.warnings;
1359                        }
1360                    } else if payload[0] == 0xFE
1361                        && let Some(eof) = reader.parse_eof_packet()
1362                    {
1363                        self.status_flags = eof.status_flags;
1364                        self.warnings = eof.warnings;
1365                    }
1366                    break;
1367                }
1368                PacketType::Error => {
1369                    let mut reader = PacketReader::new(&payload);
1370                    let Some(err) = reader.parse_err_packet() else {
1371                        return Outcome::Err(protocol_error("Invalid error packet"));
1372                    };
1373                    self.state = ConnectionState::Ready;
1374                    return Outcome::Err(query_error(&err));
1375                }
1376                _ => {
1377                    let row = self.parse_text_row(&payload, &columns);
1378                    rows.push(row);
1379                }
1380            }
1381        }
1382
1383        self.state =
1384            if self.status_flags & crate::protocol::server_status::SERVER_STATUS_IN_TRANS != 0 {
1385                ConnectionState::InTransaction
1386            } else {
1387                ConnectionState::Ready
1388            };
1389
1390        Outcome::Ok(rows)
1391    }
1392
1393    /// Parse a column definition packet.
1394    fn parse_column_def(&self, data: &[u8]) -> Result<ColumnDef, Error> {
1395        let mut reader = PacketReader::new(data);
1396
1397        let catalog = reader
1398            .read_lenenc_string()
1399            .ok_or_else(|| protocol_error("Missing catalog"))?;
1400        let schema = reader
1401            .read_lenenc_string()
1402            .ok_or_else(|| protocol_error("Missing schema"))?;
1403        let table = reader
1404            .read_lenenc_string()
1405            .ok_or_else(|| protocol_error("Missing table"))?;
1406        let org_table = reader
1407            .read_lenenc_string()
1408            .ok_or_else(|| protocol_error("Missing org_table"))?;
1409        let name = reader
1410            .read_lenenc_string()
1411            .ok_or_else(|| protocol_error("Missing name"))?;
1412        let org_name = reader
1413            .read_lenenc_string()
1414            .ok_or_else(|| protocol_error("Missing org_name"))?;
1415
1416        let _fixed_len = reader.read_lenenc_int();
1417
1418        let charset_val = reader
1419            .read_u16_le()
1420            .ok_or_else(|| protocol_error("Missing charset"))?;
1421        let column_length = reader
1422            .read_u32_le()
1423            .ok_or_else(|| protocol_error("Missing column_length"))?;
1424        let column_type = FieldType::from_u8(
1425            reader
1426                .read_u8()
1427                .ok_or_else(|| protocol_error("Missing column_type"))?,
1428        );
1429        let flags = reader
1430            .read_u16_le()
1431            .ok_or_else(|| protocol_error("Missing flags"))?;
1432        let decimals = reader
1433            .read_u8()
1434            .ok_or_else(|| protocol_error("Missing decimals"))?;
1435
1436        Ok(ColumnDef {
1437            catalog,
1438            schema,
1439            table,
1440            org_table,
1441            name,
1442            org_name,
1443            charset: charset_val,
1444            column_length,
1445            column_type,
1446            flags,
1447            decimals,
1448        })
1449    }
1450
1451    /// Parse a text protocol row.
1452    fn parse_text_row(&self, data: &[u8], columns: &[ColumnDef]) -> Row {
1453        let mut reader = PacketReader::new(data);
1454        let mut values = Vec::with_capacity(columns.len());
1455
1456        for col in columns {
1457            if reader.peek() == Some(0xFB) {
1458                reader.skip(1);
1459                values.push(Value::Null);
1460            } else if let Some(data) = reader.read_lenenc_bytes() {
1461                let is_unsigned = col.is_unsigned();
1462                let value = decode_text_value(col.column_type, &data, is_unsigned);
1463                values.push(value);
1464            } else {
1465                values.push(Value::Null);
1466            }
1467        }
1468
1469        let column_names: Vec<String> = columns.iter().map(|c| c.name.clone()).collect();
1470        Row::new(column_names, values)
1471    }
1472
1473    /// Execute a statement asynchronously and return affected rows.
1474    ///
1475    /// This is similar to `query_async` but returns the number of affected rows
1476    /// instead of the result set. Useful for INSERT, UPDATE, DELETE statements.
1477    pub async fn execute_async(
1478        &mut self,
1479        cx: &Cx,
1480        sql: &str,
1481        params: &[Value],
1482    ) -> Outcome<u64, Error> {
1483        // Execute the query
1484        match self.query_async(cx, sql, params).await {
1485            Outcome::Ok(_) => Outcome::Ok(self.affected_rows),
1486            Outcome::Err(e) => Outcome::Err(e),
1487            Outcome::Cancelled(c) => Outcome::Cancelled(c),
1488            Outcome::Panicked(p) => Outcome::Panicked(p),
1489        }
1490    }
1491
1492    /// Prepare a statement for later execution using the binary protocol.
1493    ///
1494    /// This sends COM_STMT_PREPARE to the server and stores the metadata
1495    /// needed for later execution via `query_prepared_async` or `execute_prepared_async`.
1496    pub async fn prepare_async(
1497        &mut self,
1498        _cx: &Cx,
1499        sql: &str,
1500    ) -> Outcome<PreparedStatement, Error> {
1501        if !self.is_ready() && self.state != ConnectionState::InTransaction {
1502            return Outcome::Err(connection_error("Connection not ready for prepare"));
1503        }
1504
1505        self.sequence_id = 0;
1506
1507        // Send COM_STMT_PREPARE
1508        let packet = prepared::build_stmt_prepare_packet(sql, self.sequence_id);
1509        if let Outcome::Err(e) = self.write_packet_raw_async(&packet).await {
1510            return Outcome::Err(e);
1511        }
1512
1513        // Read response
1514        let (payload, _) = match self.read_packet_async().await {
1515            Outcome::Ok(p) => p,
1516            Outcome::Err(e) => return Outcome::Err(e),
1517            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1518            Outcome::Panicked(p) => return Outcome::Panicked(p),
1519        };
1520
1521        // Check for error
1522        if payload.first() == Some(&0xFF) {
1523            let mut reader = PacketReader::new(&payload);
1524            let Some(err) = reader.parse_err_packet() else {
1525                return Outcome::Err(protocol_error("Invalid error packet"));
1526            };
1527            return Outcome::Err(query_error(&err));
1528        }
1529
1530        // Parse COM_STMT_PREPARE_OK
1531        let Some(prep_ok) = prepared::parse_stmt_prepare_ok(&payload) else {
1532            return Outcome::Err(protocol_error("Invalid prepare OK response"));
1533        };
1534
1535        // Read parameter column definitions
1536        let mut param_defs = Vec::with_capacity(prep_ok.num_params as usize);
1537        for _ in 0..prep_ok.num_params {
1538            let (payload, _) = match self.read_packet_async().await {
1539                Outcome::Ok(p) => p,
1540                Outcome::Err(e) => return Outcome::Err(e),
1541                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1542                Outcome::Panicked(p) => return Outcome::Panicked(p),
1543            };
1544            match self.parse_column_def(&payload) {
1545                Ok(col) => param_defs.push(col),
1546                Err(e) => return Outcome::Err(e),
1547            }
1548        }
1549
1550        // Read EOF after params (if not CLIENT_DEPRECATE_EOF)
1551        let server_caps = self.server_caps.as_ref().map_or(0, |c| c.capabilities);
1552        if prep_ok.num_params > 0 && server_caps & capabilities::CLIENT_DEPRECATE_EOF == 0 {
1553            let (payload, _) = match self.read_packet_async().await {
1554                Outcome::Ok(p) => p,
1555                Outcome::Err(e) => return Outcome::Err(e),
1556                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1557                Outcome::Panicked(p) => return Outcome::Panicked(p),
1558            };
1559            if payload.first() != Some(&0xFE) {
1560                return Outcome::Err(protocol_error("Expected EOF after param definitions"));
1561            }
1562        }
1563
1564        // Read column definitions
1565        let mut column_defs = Vec::with_capacity(prep_ok.num_columns as usize);
1566        for _ in 0..prep_ok.num_columns {
1567            let (payload, _) = match self.read_packet_async().await {
1568                Outcome::Ok(p) => p,
1569                Outcome::Err(e) => return Outcome::Err(e),
1570                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1571                Outcome::Panicked(p) => return Outcome::Panicked(p),
1572            };
1573            match self.parse_column_def(&payload) {
1574                Ok(col) => column_defs.push(col),
1575                Err(e) => return Outcome::Err(e),
1576            }
1577        }
1578
1579        // Read EOF after columns (if not CLIENT_DEPRECATE_EOF)
1580        if prep_ok.num_columns > 0 && server_caps & capabilities::CLIENT_DEPRECATE_EOF == 0 {
1581            let (payload, _) = match self.read_packet_async().await {
1582                Outcome::Ok(p) => p,
1583                Outcome::Err(e) => return Outcome::Err(e),
1584                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1585                Outcome::Panicked(p) => return Outcome::Panicked(p),
1586            };
1587            if payload.first() != Some(&0xFE) {
1588                return Outcome::Err(protocol_error("Expected EOF after column definitions"));
1589            }
1590        }
1591
1592        // Store metadata
1593        let meta = PreparedStmtMeta {
1594            statement_id: prep_ok.statement_id,
1595            params: param_defs,
1596            columns: column_defs.clone(),
1597        };
1598        self.prepared_stmts.insert(prep_ok.statement_id, meta);
1599
1600        // Return core PreparedStatement
1601        let column_names: Vec<String> = column_defs.iter().map(|c| c.name.clone()).collect();
1602        Outcome::Ok(PreparedStatement::with_columns(
1603            u64::from(prep_ok.statement_id),
1604            sql.to_string(),
1605            prep_ok.num_params as usize,
1606            column_names,
1607        ))
1608    }
1609
1610    /// Execute a prepared statement and return result rows (binary protocol).
1611    pub async fn query_prepared_async(
1612        &mut self,
1613        _cx: &Cx,
1614        stmt: &PreparedStatement,
1615        params: &[Value],
1616    ) -> Outcome<Vec<Row>, Error> {
1617        #[allow(clippy::cast_possible_truncation)] // Statement IDs are u32 in MySQL
1618        let stmt_id = stmt.id() as u32;
1619
1620        // Look up metadata
1621        let Some(meta) = self.prepared_stmts.get(&stmt_id).cloned() else {
1622            return Outcome::Err(connection_error("Unknown prepared statement"));
1623        };
1624
1625        // Verify param count
1626        if params.len() != meta.params.len() {
1627            return Outcome::Err(connection_error(format!(
1628                "Expected {} parameters, got {}",
1629                meta.params.len(),
1630                params.len()
1631            )));
1632        }
1633
1634        if !self.is_ready() && self.state != ConnectionState::InTransaction {
1635            return Outcome::Err(connection_error("Connection not ready for query"));
1636        }
1637
1638        self.state = ConnectionState::InQuery;
1639        self.sequence_id = 0;
1640
1641        // Build and send COM_STMT_EXECUTE
1642        let param_types: Vec<FieldType> = meta.params.iter().map(|c| c.column_type).collect();
1643        let packet = prepared::build_stmt_execute_packet(
1644            stmt_id,
1645            params,
1646            Some(&param_types),
1647            self.sequence_id,
1648        );
1649        if let Outcome::Err(e) = self.write_packet_raw_async(&packet).await {
1650            return Outcome::Err(e);
1651        }
1652
1653        // Read response
1654        let (payload, _) = match self.read_packet_async().await {
1655            Outcome::Ok(p) => p,
1656            Outcome::Err(e) => return Outcome::Err(e),
1657            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1658            Outcome::Panicked(p) => return Outcome::Panicked(p),
1659        };
1660
1661        if payload.is_empty() {
1662            self.state = ConnectionState::Ready;
1663            return Outcome::Err(protocol_error("Empty execute response"));
1664        }
1665
1666        #[allow(clippy::cast_possible_truncation)] // MySQL packets are max 16MB
1667        match PacketType::from_first_byte(payload[0], payload.len() as u32) {
1668            PacketType::Ok => {
1669                // Non-SELECT statement - parse OK packet
1670                let mut reader = PacketReader::new(&payload);
1671                if let Some(ok) = reader.parse_ok_packet() {
1672                    self.affected_rows = ok.affected_rows;
1673                    self.last_insert_id = ok.last_insert_id;
1674                    self.status_flags = ok.status_flags;
1675                    self.warnings = ok.warnings;
1676                }
1677                self.state = ConnectionState::Ready;
1678                Outcome::Ok(vec![])
1679            }
1680            PacketType::Error => {
1681                self.state = ConnectionState::Ready;
1682                let mut reader = PacketReader::new(&payload);
1683                let Some(err) = reader.parse_err_packet() else {
1684                    return Outcome::Err(protocol_error("Invalid error packet"));
1685                };
1686                Outcome::Err(query_error(&err))
1687            }
1688            _ => {
1689                // Result set - read binary protocol rows
1690                self.read_binary_result_set_async(&payload, &meta.columns)
1691                    .await
1692            }
1693        }
1694    }
1695
1696    /// Execute a prepared statement and return affected row count.
1697    pub async fn execute_prepared_async(
1698        &mut self,
1699        cx: &Cx,
1700        stmt: &PreparedStatement,
1701        params: &[Value],
1702    ) -> Outcome<u64, Error> {
1703        match self.query_prepared_async(cx, stmt, params).await {
1704            Outcome::Ok(_) => Outcome::Ok(self.affected_rows),
1705            Outcome::Err(e) => Outcome::Err(e),
1706            Outcome::Cancelled(c) => Outcome::Cancelled(c),
1707            Outcome::Panicked(p) => Outcome::Panicked(p),
1708        }
1709    }
1710
1711    /// Close a prepared statement.
1712    pub async fn close_prepared_async(&mut self, stmt: &PreparedStatement) {
1713        #[allow(clippy::cast_possible_truncation)] // Statement IDs are u32 in MySQL
1714        let stmt_id = stmt.id() as u32;
1715        self.prepared_stmts.remove(&stmt_id);
1716
1717        self.sequence_id = 0;
1718        let packet = prepared::build_stmt_close_packet(stmt_id, self.sequence_id);
1719        // Best effort - no response expected
1720        let _ = self.write_packet_raw_async(&packet).await;
1721    }
1722
1723    /// Read a binary protocol result set.
1724    async fn read_binary_result_set_async(
1725        &mut self,
1726        first_packet: &[u8],
1727        columns: &[ColumnDef],
1728    ) -> Outcome<Vec<Row>, Error> {
1729        // First packet contains column count
1730        let mut reader = PacketReader::new(first_packet);
1731        #[allow(clippy::cast_possible_truncation)] // Column count fits in usize
1732        let Some(column_count) = reader.read_lenenc_int().map(|c| c as usize) else {
1733            return Outcome::Err(protocol_error("Invalid column count"));
1734        };
1735
1736        // The column definitions were already provided from prepare
1737        // But server sends them again in binary result set - we need to read them
1738        let mut result_columns = Vec::with_capacity(column_count);
1739        for _ in 0..column_count {
1740            let (payload, _) = match self.read_packet_async().await {
1741                Outcome::Ok(p) => p,
1742                Outcome::Err(e) => return Outcome::Err(e),
1743                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1744                Outcome::Panicked(p) => return Outcome::Panicked(p),
1745            };
1746            match self.parse_column_def(&payload) {
1747                Ok(col) => result_columns.push(col),
1748                Err(e) => return Outcome::Err(e),
1749            }
1750        }
1751
1752        // Use the columns from the result set if available, otherwise use prepared metadata
1753        let cols = if result_columns.len() == columns.len() {
1754            &result_columns
1755        } else {
1756            columns
1757        };
1758
1759        // Check for EOF packet
1760        let server_caps = self.server_caps.as_ref().map_or(0, |c| c.capabilities);
1761        if server_caps & capabilities::CLIENT_DEPRECATE_EOF == 0 {
1762            let (payload, _) = match self.read_packet_async().await {
1763                Outcome::Ok(p) => p,
1764                Outcome::Err(e) => return Outcome::Err(e),
1765                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1766                Outcome::Panicked(p) => return Outcome::Panicked(p),
1767            };
1768            if payload.first() == Some(&0xFE) {
1769                // EOF packet - continue to rows
1770            }
1771        }
1772
1773        // Read binary rows until EOF or OK
1774        let mut rows = Vec::new();
1775        loop {
1776            let (payload, _) = match self.read_packet_async().await {
1777                Outcome::Ok(p) => p,
1778                Outcome::Err(e) => return Outcome::Err(e),
1779                Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1780                Outcome::Panicked(p) => return Outcome::Panicked(p),
1781            };
1782
1783            if payload.is_empty() {
1784                break;
1785            }
1786
1787            #[allow(clippy::cast_possible_truncation)] // MySQL packets are max 16MB
1788            match PacketType::from_first_byte(payload[0], payload.len() as u32) {
1789                PacketType::Eof | PacketType::Ok => {
1790                    let mut reader = PacketReader::new(&payload);
1791                    if payload[0] == 0x00 {
1792                        if let Some(ok) = reader.parse_ok_packet() {
1793                            self.status_flags = ok.status_flags;
1794                            self.warnings = ok.warnings;
1795                        }
1796                    } else if payload[0] == 0xFE
1797                        && let Some(eof) = reader.parse_eof_packet()
1798                    {
1799                        self.status_flags = eof.status_flags;
1800                        self.warnings = eof.warnings;
1801                    }
1802                    break;
1803                }
1804                PacketType::Error => {
1805                    let mut reader = PacketReader::new(&payload);
1806                    let Some(err) = reader.parse_err_packet() else {
1807                        return Outcome::Err(protocol_error("Invalid error packet"));
1808                    };
1809                    self.state = ConnectionState::Ready;
1810                    return Outcome::Err(query_error(&err));
1811                }
1812                _ => {
1813                    let row = self.parse_binary_row(&payload, cols);
1814                    rows.push(row);
1815                }
1816            }
1817        }
1818
1819        self.state =
1820            if self.status_flags & crate::protocol::server_status::SERVER_STATUS_IN_TRANS != 0 {
1821                ConnectionState::InTransaction
1822            } else {
1823                ConnectionState::Ready
1824            };
1825
1826        Outcome::Ok(rows)
1827    }
1828
1829    /// Parse a binary protocol row.
1830    fn parse_binary_row(&self, data: &[u8], columns: &[ColumnDef]) -> Row {
1831        // Binary row format:
1832        // - 0x00 header (1 byte)
1833        // - NULL bitmap ((column_count + 7 + 2) / 8 bytes)
1834        // - Column values (only non-NULL)
1835
1836        let mut values = Vec::with_capacity(columns.len());
1837        let mut column_names = Vec::with_capacity(columns.len());
1838
1839        if data.is_empty() {
1840            return Row::new(column_names, values);
1841        }
1842
1843        // Skip header byte (0x00)
1844        let mut pos = 1;
1845
1846        // NULL bitmap: (column_count + 7 + 2) / 8 bytes
1847        // The +2 offset is for the reserved bits at the beginning
1848        let null_bitmap_len = (columns.len() + 7 + 2) / 8;
1849        if pos + null_bitmap_len > data.len() {
1850            return Row::new(column_names, values);
1851        }
1852        let null_bitmap = &data[pos..pos + null_bitmap_len];
1853        pos += null_bitmap_len;
1854
1855        // Parse column values
1856        for (i, col) in columns.iter().enumerate() {
1857            column_names.push(col.name.clone());
1858
1859            // Check NULL bitmap (bit position is i + 2 due to offset)
1860            let bit_pos = i + 2;
1861            let is_null = (null_bitmap[bit_pos / 8] & (1 << (bit_pos % 8))) != 0;
1862
1863            if is_null {
1864                values.push(Value::Null);
1865            } else {
1866                let is_unsigned = col.flags & 0x20 != 0; // UNSIGNED_FLAG
1867                let (value, consumed) =
1868                    decode_binary_value_with_len(&data[pos..], col.column_type, is_unsigned);
1869                values.push(value);
1870                pos += consumed;
1871            }
1872        }
1873
1874        Row::new(column_names, values)
1875    }
1876
1877    /// Write a pre-built packet (with header already included).
1878    async fn write_packet_raw_async(&mut self, packet: &[u8]) -> Outcome<(), Error> {
1879        let Some(stream) = self.stream.as_mut() else {
1880            return Outcome::Err(connection_error("Connection stream missing"));
1881        };
1882        match stream {
1883            ConnectionStream::Async(stream) => {
1884                let mut written = 0;
1885                while written < packet.len() {
1886                    match std::future::poll_fn(|cx| {
1887                        std::pin::Pin::new(&mut *stream).poll_write(cx, &packet[written..])
1888                    })
1889                    .await
1890                    {
1891                        Ok(n) => written += n,
1892                        Err(e) => {
1893                            return Outcome::Err(Error::Connection(ConnectionError {
1894                                kind: ConnectionErrorKind::Disconnected,
1895                                message: format!("Failed to write packet: {}", e),
1896                                source: Some(Box::new(e)),
1897                            }));
1898                        }
1899                    }
1900                }
1901                // Flush
1902                if let Err(e) =
1903                    std::future::poll_fn(|cx| std::pin::Pin::new(&mut *stream).poll_flush(cx)).await
1904                {
1905                    return Outcome::Err(Error::Connection(ConnectionError {
1906                        kind: ConnectionErrorKind::Disconnected,
1907                        message: format!("Failed to flush: {}", e),
1908                        source: Some(Box::new(e)),
1909                    }));
1910                }
1911                Outcome::Ok(())
1912            }
1913            ConnectionStream::Sync(stream) => {
1914                if let Err(e) = stream.write_all(packet) {
1915                    return Outcome::Err(Error::Connection(ConnectionError {
1916                        kind: ConnectionErrorKind::Disconnected,
1917                        message: format!("Failed to write packet: {}", e),
1918                        source: Some(Box::new(e)),
1919                    }));
1920                }
1921                if let Err(e) = stream.flush() {
1922                    return Outcome::Err(Error::Connection(ConnectionError {
1923                        kind: ConnectionErrorKind::Disconnected,
1924                        message: format!("Failed to flush: {}", e),
1925                        source: Some(Box::new(e)),
1926                    }));
1927                }
1928                Outcome::Ok(())
1929            }
1930            #[cfg(feature = "tls")]
1931            ConnectionStream::Tls(_) => Outcome::Err(connection_error(
1932                "write_packet_raw_async called after TLS upgrade (bug)",
1933            )),
1934        }
1935    }
1936
1937    /// Ping the server asynchronously.
1938    pub async fn ping_async(&mut self, _cx: &Cx) -> Outcome<(), Error> {
1939        self.sequence_id = 0;
1940
1941        let mut writer = PacketWriter::new();
1942        writer.write_u8(Command::Ping as u8);
1943
1944        if let Outcome::Err(e) = self.write_packet_async(writer.as_bytes()).await {
1945            return Outcome::Err(e);
1946        }
1947
1948        let (payload, _) = match self.read_packet_async().await {
1949            Outcome::Ok(p) => p,
1950            Outcome::Err(e) => return Outcome::Err(e),
1951            Outcome::Cancelled(r) => return Outcome::Cancelled(r),
1952            Outcome::Panicked(p) => return Outcome::Panicked(p),
1953        };
1954
1955        if payload.first() == Some(&0x00) {
1956            Outcome::Ok(())
1957        } else {
1958            Outcome::Err(connection_error("Ping failed"))
1959        }
1960    }
1961
1962    /// Close the connection asynchronously.
1963    pub async fn close_async(mut self, _cx: &Cx) -> Result<(), Error> {
1964        if self.state == ConnectionState::Closed {
1965            return Ok(());
1966        }
1967
1968        self.sequence_id = 0;
1969
1970        let mut writer = PacketWriter::new();
1971        writer.write_u8(Command::Quit as u8);
1972
1973        // Best effort - ignore errors on close
1974        let _ = self.write_packet_async(writer.as_bytes()).await;
1975
1976        self.state = ConnectionState::Closed;
1977        Ok(())
1978    }
1979}
1980
1981// === Console integration ===
1982
1983#[cfg(feature = "console")]
1984impl ConsoleAware for MySqlAsyncConnection {
1985    fn set_console(&mut self, console: Option<Arc<SqlModelConsole>>) {
1986        self.console = console;
1987    }
1988
1989    fn console(&self) -> Option<&Arc<SqlModelConsole>> {
1990        self.console.as_ref()
1991    }
1992}
1993
1994// === Helper functions ===
1995
1996fn protocol_error(msg: impl Into<String>) -> Error {
1997    Error::Protocol(ProtocolError {
1998        message: msg.into(),
1999        raw_data: None,
2000        source: None,
2001    })
2002}
2003
2004fn auth_error(msg: impl Into<String>) -> Error {
2005    Error::Connection(ConnectionError {
2006        kind: ConnectionErrorKind::Authentication,
2007        message: msg.into(),
2008        source: None,
2009    })
2010}
2011
2012fn connection_error(msg: impl Into<String>) -> Error {
2013    Error::Connection(ConnectionError {
2014        kind: ConnectionErrorKind::Connect,
2015        message: msg.into(),
2016        source: None,
2017    })
2018}
2019
2020fn mysql_server_uses_oaep(server_version: &str) -> bool {
2021    // MySQL 8.0.5+ uses OAEP for caching_sha2_password RSA encryption.
2022    // Parse leading "major.minor.patch" prefix; if parsing fails, default to OAEP.
2023    let prefix: String = server_version
2024        .chars()
2025        .take_while(|c| c.is_ascii_digit() || *c == '.')
2026        .collect();
2027    let mut it = prefix.split('.').filter(|s| !s.is_empty());
2028    let major: u64 = match it.next().and_then(|s| s.parse().ok()) {
2029        Some(v) => v,
2030        None => return true,
2031    };
2032    let minor: u64 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
2033    let patch: u64 = it.next().and_then(|s| s.parse().ok()).unwrap_or(0);
2034
2035    (major, minor, patch) >= (8, 0, 5)
2036}
2037
2038fn query_error(err: &ErrPacket) -> Error {
2039    let kind = if err.is_duplicate_key() || err.is_foreign_key_violation() {
2040        QueryErrorKind::Constraint
2041    } else {
2042        QueryErrorKind::Syntax
2043    };
2044
2045    Error::Query(QueryError {
2046        kind,
2047        message: err.error_message.clone(),
2048        sqlstate: Some(err.sql_state.clone()),
2049        sql: None,
2050        detail: None,
2051        hint: None,
2052        position: None,
2053        source: None,
2054    })
2055}
2056
2057fn query_error_msg(msg: impl Into<String>) -> Error {
2058    Error::Query(QueryError {
2059        kind: QueryErrorKind::Syntax,
2060        message: msg.into(),
2061        sqlstate: None,
2062        sql: None,
2063        detail: None,
2064        hint: None,
2065        position: None,
2066        source: None,
2067    })
2068}
2069
2070/// Validate a savepoint name to prevent SQL injection.
2071///
2072/// MySQL identifiers must:
2073/// - Not be empty
2074/// - Start with a letter or underscore
2075/// - Contain only letters, digits, underscores, or dollar signs
2076/// - Be at most 64 characters
2077fn validate_savepoint_name(name: &str) -> Result<(), Error> {
2078    if name.is_empty() {
2079        return Err(query_error_msg("Savepoint name cannot be empty"));
2080    }
2081    if name.len() > 64 {
2082        return Err(query_error_msg(
2083            "Savepoint name exceeds maximum length of 64 characters",
2084        ));
2085    }
2086    let mut chars = name.chars();
2087    let Some(first) = chars.next() else {
2088        // Defensive: `is_empty()` was checked above.
2089        return Err(query_error_msg("Savepoint name cannot be empty"));
2090    };
2091    if !first.is_ascii_alphabetic() && first != '_' {
2092        return Err(query_error_msg(
2093            "Savepoint name must start with a letter or underscore",
2094        ));
2095    }
2096    for c in chars {
2097        if !c.is_ascii_alphanumeric() && c != '_' && c != '$' {
2098            return Err(query_error_msg(format!(
2099                "Savepoint name contains invalid character: '{}'",
2100                c
2101            )));
2102        }
2103    }
2104    Ok(())
2105}
2106
2107// === Shared connection wrapper ===
2108
2109/// A thread-safe, shared MySQL connection with interior mutability.
2110///
2111/// This wrapper allows the `Connection` trait to be implemented properly
2112/// by wrapping the raw `MySqlAsyncConnection` in an async mutex.
2113///
2114/// # Example
2115///
2116/// ```ignore
2117/// let conn = MySqlAsyncConnection::connect(&cx, config).await?;
2118/// let shared = SharedMySqlConnection::new(conn);
2119///
2120/// // Now you can use &shared with the Connection trait
2121/// let rows = shared.query(&cx, "SELECT * FROM users", &[]).await?;
2122/// ```
2123pub struct SharedMySqlConnection {
2124    inner: Arc<Mutex<MySqlAsyncConnection>>,
2125}
2126
2127impl SharedMySqlConnection {
2128    /// Create a new shared connection from a raw connection.
2129    pub fn new(conn: MySqlAsyncConnection) -> Self {
2130        Self {
2131            inner: Arc::new(Mutex::new(conn)),
2132        }
2133    }
2134
2135    /// Create a new shared connection by connecting to the server.
2136    pub async fn connect(cx: &Cx, config: MySqlConfig) -> Outcome<Self, Error> {
2137        match MySqlAsyncConnection::connect(cx, config).await {
2138            Outcome::Ok(conn) => Outcome::Ok(Self::new(conn)),
2139            Outcome::Err(e) => Outcome::Err(e),
2140            Outcome::Cancelled(c) => Outcome::Cancelled(c),
2141            Outcome::Panicked(p) => Outcome::Panicked(p),
2142        }
2143    }
2144
2145    /// Get the inner Arc for cloning.
2146    pub fn inner(&self) -> &Arc<Mutex<MySqlAsyncConnection>> {
2147        &self.inner
2148    }
2149}
2150
2151impl Clone for SharedMySqlConnection {
2152    fn clone(&self) -> Self {
2153        Self {
2154            inner: Arc::clone(&self.inner),
2155        }
2156    }
2157}
2158
2159impl std::fmt::Debug for SharedMySqlConnection {
2160    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2161        f.debug_struct("SharedMySqlConnection")
2162            .field("inner", &"Arc<Mutex<MySqlAsyncConnection>>")
2163            .finish()
2164    }
2165}
2166
2167/// Transaction type for SharedMySqlConnection.
2168///
2169/// This transaction holds a clone of the Arc to the connection and executes
2170/// transaction operations by acquiring the mutex lock for each operation.
2171/// The transaction must be committed or rolled back explicitly.
2172///
2173/// # Warning: Uncommitted Transactions
2174///
2175/// If a transaction is dropped without calling `commit()` or `rollback()`,
2176/// the underlying MySQL transaction will remain open until the connection
2177/// is closed or a new transaction is started. This is because Rust's `Drop`
2178/// trait cannot perform async operations.
2179///
2180/// **Always explicitly call `commit()` or `rollback()` before dropping.**
2181///
2182/// Note: The lifetime parameter is required by the Connection trait but the
2183/// actual implementation holds an owned Arc, so the transaction can outlive
2184/// the reference to SharedMySqlConnection if needed.
2185pub struct SharedMySqlTransaction<'conn> {
2186    inner: Arc<Mutex<MySqlAsyncConnection>>,
2187    committed: bool,
2188    _marker: std::marker::PhantomData<&'conn ()>,
2189}
2190
2191impl SharedMySqlConnection {
2192    /// Internal implementation for beginning a transaction.
2193    async fn begin_transaction_impl(
2194        &self,
2195        cx: &Cx,
2196        isolation: Option<IsolationLevel>,
2197    ) -> Outcome<SharedMySqlTransaction<'_>, Error> {
2198        let inner = Arc::clone(&self.inner);
2199
2200        // Acquire lock
2201        let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2202            return Outcome::Err(connection_error("Failed to acquire connection lock"));
2203        };
2204
2205        // Set isolation level if specified
2206        if let Some(level) = isolation {
2207            let isolation_sql = format!("SET TRANSACTION ISOLATION LEVEL {}", level.as_sql());
2208            match guard.execute_async(cx, &isolation_sql, &[]).await {
2209                Outcome::Ok(_) => {}
2210                Outcome::Err(e) => return Outcome::Err(e),
2211                Outcome::Cancelled(c) => return Outcome::Cancelled(c),
2212                Outcome::Panicked(p) => return Outcome::Panicked(p),
2213            }
2214        }
2215
2216        // Start transaction
2217        match guard.execute_async(cx, "BEGIN", &[]).await {
2218            Outcome::Ok(_) => {}
2219            Outcome::Err(e) => return Outcome::Err(e),
2220            Outcome::Cancelled(c) => return Outcome::Cancelled(c),
2221            Outcome::Panicked(p) => return Outcome::Panicked(p),
2222        }
2223
2224        drop(guard);
2225
2226        Outcome::Ok(SharedMySqlTransaction {
2227            inner,
2228            committed: false,
2229            _marker: std::marker::PhantomData,
2230        })
2231    }
2232}
2233
2234impl Connection for SharedMySqlConnection {
2235    type Tx<'conn>
2236        = SharedMySqlTransaction<'conn>
2237    where
2238        Self: 'conn;
2239
2240    fn dialect(&self) -> sqlmodel_core::Dialect {
2241        sqlmodel_core::Dialect::Mysql
2242    }
2243
2244    fn query(
2245        &self,
2246        cx: &Cx,
2247        sql: &str,
2248        params: &[Value],
2249    ) -> impl Future<Output = Outcome<Vec<Row>, Error>> + Send {
2250        let inner = Arc::clone(&self.inner);
2251        let sql = sql.to_string();
2252        let params = params.to_vec();
2253        async move {
2254            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2255                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2256            };
2257            guard.query_async(cx, &sql, &params).await
2258        }
2259    }
2260
2261    fn query_one(
2262        &self,
2263        cx: &Cx,
2264        sql: &str,
2265        params: &[Value],
2266    ) -> impl Future<Output = Outcome<Option<Row>, Error>> + Send {
2267        let inner = Arc::clone(&self.inner);
2268        let sql = sql.to_string();
2269        let params = params.to_vec();
2270        async move {
2271            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2272                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2273            };
2274            let rows = match guard.query_async(cx, &sql, &params).await {
2275                Outcome::Ok(r) => r,
2276                Outcome::Err(e) => return Outcome::Err(e),
2277                Outcome::Cancelled(c) => return Outcome::Cancelled(c),
2278                Outcome::Panicked(p) => return Outcome::Panicked(p),
2279            };
2280            Outcome::Ok(rows.into_iter().next())
2281        }
2282    }
2283
2284    fn execute(
2285        &self,
2286        cx: &Cx,
2287        sql: &str,
2288        params: &[Value],
2289    ) -> impl Future<Output = Outcome<u64, Error>> + Send {
2290        let inner = Arc::clone(&self.inner);
2291        let sql = sql.to_string();
2292        let params = params.to_vec();
2293        async move {
2294            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2295                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2296            };
2297            guard.execute_async(cx, &sql, &params).await
2298        }
2299    }
2300
2301    fn insert(
2302        &self,
2303        cx: &Cx,
2304        sql: &str,
2305        params: &[Value],
2306    ) -> impl Future<Output = Outcome<i64, Error>> + Send {
2307        let inner = Arc::clone(&self.inner);
2308        let sql = sql.to_string();
2309        let params = params.to_vec();
2310        async move {
2311            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2312                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2313            };
2314            match guard.execute_async(cx, &sql, &params).await {
2315                Outcome::Ok(_) => Outcome::Ok(guard.last_insert_id() as i64),
2316                Outcome::Err(e) => Outcome::Err(e),
2317                Outcome::Cancelled(c) => Outcome::Cancelled(c),
2318                Outcome::Panicked(p) => Outcome::Panicked(p),
2319            }
2320        }
2321    }
2322
2323    fn batch(
2324        &self,
2325        cx: &Cx,
2326        statements: &[(String, Vec<Value>)],
2327    ) -> impl Future<Output = Outcome<Vec<u64>, Error>> + Send {
2328        let inner = Arc::clone(&self.inner);
2329        let statements = statements.to_vec();
2330        async move {
2331            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2332                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2333            };
2334            let mut results = Vec::with_capacity(statements.len());
2335            for (sql, params) in &statements {
2336                match guard.execute_async(cx, sql, params).await {
2337                    Outcome::Ok(n) => results.push(n),
2338                    Outcome::Err(e) => return Outcome::Err(e),
2339                    Outcome::Cancelled(c) => return Outcome::Cancelled(c),
2340                    Outcome::Panicked(p) => return Outcome::Panicked(p),
2341                }
2342            }
2343            Outcome::Ok(results)
2344        }
2345    }
2346
2347    fn begin(&self, cx: &Cx) -> impl Future<Output = Outcome<Self::Tx<'_>, Error>> + Send {
2348        self.begin_transaction_impl(cx, None)
2349    }
2350
2351    fn begin_with(
2352        &self,
2353        cx: &Cx,
2354        isolation: IsolationLevel,
2355    ) -> impl Future<Output = Outcome<Self::Tx<'_>, Error>> + Send {
2356        self.begin_transaction_impl(cx, Some(isolation))
2357    }
2358
2359    fn prepare(
2360        &self,
2361        cx: &Cx,
2362        sql: &str,
2363    ) -> impl Future<Output = Outcome<PreparedStatement, Error>> + Send {
2364        let inner = Arc::clone(&self.inner);
2365        let sql = sql.to_string();
2366        async move {
2367            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2368                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2369            };
2370            guard.prepare_async(cx, &sql).await
2371        }
2372    }
2373
2374    fn query_prepared(
2375        &self,
2376        cx: &Cx,
2377        stmt: &PreparedStatement,
2378        params: &[Value],
2379    ) -> impl Future<Output = Outcome<Vec<Row>, Error>> + Send {
2380        let inner = Arc::clone(&self.inner);
2381        let stmt = stmt.clone();
2382        let params = params.to_vec();
2383        async move {
2384            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2385                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2386            };
2387            guard.query_prepared_async(cx, &stmt, &params).await
2388        }
2389    }
2390
2391    fn execute_prepared(
2392        &self,
2393        cx: &Cx,
2394        stmt: &PreparedStatement,
2395        params: &[Value],
2396    ) -> impl Future<Output = Outcome<u64, Error>> + Send {
2397        let inner = Arc::clone(&self.inner);
2398        let stmt = stmt.clone();
2399        let params = params.to_vec();
2400        async move {
2401            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2402                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2403            };
2404            guard.execute_prepared_async(cx, &stmt, &params).await
2405        }
2406    }
2407
2408    fn ping(&self, cx: &Cx) -> impl Future<Output = Outcome<(), Error>> + Send {
2409        let inner = Arc::clone(&self.inner);
2410        async move {
2411            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2412                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2413            };
2414            guard.ping_async(cx).await
2415        }
2416    }
2417
2418    fn close(self, cx: &Cx) -> impl Future<Output = Result<(), Error>> + Send {
2419        async move {
2420            // Try to get exclusive access - if we have the only Arc, we can close
2421            match Arc::try_unwrap(self.inner) {
2422                Ok(mutex) => {
2423                    let conn = mutex.into_inner().map_err(|_| {
2424                        connection_error("Cannot close: connection mutex is unavailable")
2425                    })?;
2426                    conn.close_async(cx).await
2427                }
2428                Err(_) => {
2429                    // Other references exist, can't close
2430                    Err(connection_error(
2431                        "Cannot close: other references to connection exist",
2432                    ))
2433                }
2434            }
2435        }
2436    }
2437}
2438
2439impl<'conn> TransactionOps for SharedMySqlTransaction<'conn> {
2440    fn query(
2441        &self,
2442        cx: &Cx,
2443        sql: &str,
2444        params: &[Value],
2445    ) -> impl Future<Output = Outcome<Vec<Row>, Error>> + Send {
2446        let inner = Arc::clone(&self.inner);
2447        let sql = sql.to_string();
2448        let params = params.to_vec();
2449        async move {
2450            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2451                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2452            };
2453            guard.query_async(cx, &sql, &params).await
2454        }
2455    }
2456
2457    fn query_one(
2458        &self,
2459        cx: &Cx,
2460        sql: &str,
2461        params: &[Value],
2462    ) -> impl Future<Output = Outcome<Option<Row>, Error>> + Send {
2463        let inner = Arc::clone(&self.inner);
2464        let sql = sql.to_string();
2465        let params = params.to_vec();
2466        async move {
2467            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2468                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2469            };
2470            let rows = match guard.query_async(cx, &sql, &params).await {
2471                Outcome::Ok(r) => r,
2472                Outcome::Err(e) => return Outcome::Err(e),
2473                Outcome::Cancelled(c) => return Outcome::Cancelled(c),
2474                Outcome::Panicked(p) => return Outcome::Panicked(p),
2475            };
2476            Outcome::Ok(rows.into_iter().next())
2477        }
2478    }
2479
2480    fn execute(
2481        &self,
2482        cx: &Cx,
2483        sql: &str,
2484        params: &[Value],
2485    ) -> impl Future<Output = Outcome<u64, Error>> + Send {
2486        let inner = Arc::clone(&self.inner);
2487        let sql = sql.to_string();
2488        let params = params.to_vec();
2489        async move {
2490            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2491                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2492            };
2493            guard.execute_async(cx, &sql, &params).await
2494        }
2495    }
2496
2497    fn savepoint(&self, cx: &Cx, name: &str) -> impl Future<Output = Outcome<(), Error>> + Send {
2498        let inner = Arc::clone(&self.inner);
2499        // Validate name before building SQL to prevent injection
2500        let validation_result = validate_savepoint_name(name);
2501        let sql = format!("SAVEPOINT {}", name);
2502        async move {
2503            // Return validation error if name was invalid
2504            if let Err(e) = validation_result {
2505                return Outcome::Err(e);
2506            }
2507            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2508                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2509            };
2510            match guard.execute_async(cx, &sql, &[]).await {
2511                Outcome::Ok(_) => Outcome::Ok(()),
2512                Outcome::Err(e) => Outcome::Err(e),
2513                Outcome::Cancelled(c) => Outcome::Cancelled(c),
2514                Outcome::Panicked(p) => Outcome::Panicked(p),
2515            }
2516        }
2517    }
2518
2519    fn rollback_to(&self, cx: &Cx, name: &str) -> impl Future<Output = Outcome<(), Error>> + Send {
2520        let inner = Arc::clone(&self.inner);
2521        // Validate name before building SQL to prevent injection
2522        let validation_result = validate_savepoint_name(name);
2523        let sql = format!("ROLLBACK TO SAVEPOINT {}", name);
2524        async move {
2525            // Return validation error if name was invalid
2526            if let Err(e) = validation_result {
2527                return Outcome::Err(e);
2528            }
2529            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2530                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2531            };
2532            match guard.execute_async(cx, &sql, &[]).await {
2533                Outcome::Ok(_) => Outcome::Ok(()),
2534                Outcome::Err(e) => Outcome::Err(e),
2535                Outcome::Cancelled(c) => Outcome::Cancelled(c),
2536                Outcome::Panicked(p) => Outcome::Panicked(p),
2537            }
2538        }
2539    }
2540
2541    fn release(&self, cx: &Cx, name: &str) -> impl Future<Output = Outcome<(), Error>> + Send {
2542        let inner = Arc::clone(&self.inner);
2543        // Validate name before building SQL to prevent injection
2544        let validation_result = validate_savepoint_name(name);
2545        let sql = format!("RELEASE SAVEPOINT {}", name);
2546        async move {
2547            // Return validation error if name was invalid
2548            if let Err(e) = validation_result {
2549                return Outcome::Err(e);
2550            }
2551            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&inner), cx).await else {
2552                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2553            };
2554            match guard.execute_async(cx, &sql, &[]).await {
2555                Outcome::Ok(_) => Outcome::Ok(()),
2556                Outcome::Err(e) => Outcome::Err(e),
2557                Outcome::Cancelled(c) => Outcome::Cancelled(c),
2558                Outcome::Panicked(p) => Outcome::Panicked(p),
2559            }
2560        }
2561    }
2562
2563    // Note: clippy incorrectly flags `self.committed = true` as unused, but
2564    // the Drop impl reads this field to determine if rollback logging is needed.
2565    #[allow(unused_assignments)]
2566    fn commit(mut self, cx: &Cx) -> impl Future<Output = Outcome<(), Error>> + Send {
2567        async move {
2568            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&self.inner), cx).await else {
2569                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2570            };
2571            match guard.execute_async(cx, "COMMIT", &[]).await {
2572                Outcome::Ok(_) => {
2573                    self.committed = true;
2574                    Outcome::Ok(())
2575                }
2576                Outcome::Err(e) => Outcome::Err(e),
2577                Outcome::Cancelled(c) => Outcome::Cancelled(c),
2578                Outcome::Panicked(p) => Outcome::Panicked(p),
2579            }
2580        }
2581    }
2582
2583    fn rollback(self, cx: &Cx) -> impl Future<Output = Outcome<(), Error>> + Send {
2584        async move {
2585            let Ok(mut guard) = OwnedMutexGuard::lock(Arc::clone(&self.inner), cx).await else {
2586                return Outcome::Err(connection_error("Failed to acquire connection lock"));
2587            };
2588            match guard.execute_async(cx, "ROLLBACK", &[]).await {
2589                Outcome::Ok(_) => Outcome::Ok(()),
2590                Outcome::Err(e) => Outcome::Err(e),
2591                Outcome::Cancelled(c) => Outcome::Cancelled(c),
2592                Outcome::Panicked(p) => Outcome::Panicked(p),
2593            }
2594        }
2595    }
2596}
2597
2598impl<'conn> Drop for SharedMySqlTransaction<'conn> {
2599    fn drop(&mut self) {
2600        if !self.committed {
2601            // WARNING: Transaction was dropped without commit() or rollback()!
2602            // We cannot do async work in Drop, so the MySQL transaction will
2603            // remain open until the connection is closed or a new transaction
2604            // is started. This may cause unexpected behavior.
2605            //
2606            // To fix: Always call tx.commit(cx).await or tx.rollback(cx).await
2607            // before the transaction goes out of scope.
2608            #[cfg(debug_assertions)]
2609            eprintln!(
2610                "WARNING: SharedMySqlTransaction dropped without commit/rollback. \
2611                 The MySQL transaction may still be open."
2612            );
2613        }
2614    }
2615}
2616
2617#[cfg(test)]
2618mod tests {
2619    use super::*;
2620
2621    #[test]
2622    fn test_connection_state() {
2623        assert_eq!(ConnectionState::Disconnected, ConnectionState::Disconnected);
2624    }
2625
2626    #[test]
2627    fn test_error_helpers() {
2628        let err = protocol_error("test");
2629        assert!(matches!(err, Error::Protocol(_)));
2630
2631        let err = auth_error("auth failed");
2632        assert!(matches!(err, Error::Connection(_)));
2633
2634        let err = connection_error("conn failed");
2635        assert!(matches!(err, Error::Connection(_)));
2636    }
2637
2638    #[test]
2639    fn test_validate_savepoint_name_valid() {
2640        // Valid names
2641        assert!(validate_savepoint_name("sp1").is_ok());
2642        assert!(validate_savepoint_name("_savepoint").is_ok());
2643        assert!(validate_savepoint_name("SavePoint_123").is_ok());
2644        assert!(validate_savepoint_name("sp$test").is_ok());
2645        assert!(validate_savepoint_name("a").is_ok());
2646        assert!(validate_savepoint_name("_").is_ok());
2647    }
2648
2649    #[test]
2650    fn test_validate_savepoint_name_invalid() {
2651        // Empty name
2652        assert!(validate_savepoint_name("").is_err());
2653
2654        // Starts with digit
2655        assert!(validate_savepoint_name("1savepoint").is_err());
2656
2657        // Contains invalid characters
2658        assert!(validate_savepoint_name("save-point").is_err());
2659        assert!(validate_savepoint_name("save point").is_err());
2660        assert!(validate_savepoint_name("save;drop table").is_err());
2661        assert!(validate_savepoint_name("sp'--").is_err());
2662
2663        // Too long (over 64 chars)
2664        let long_name = "a".repeat(65);
2665        assert!(validate_savepoint_name(&long_name).is_err());
2666    }
2667}