Skip to main content

rustlavel_db/mysql/
connection.rs

1//! A single MySQL connection.
2
3use super::auth::{self, FastAuth};
4use super::protocol::{self, Buffer, Column, OkPacket, Packet, ServerError};
5use super::types;
6use crate::config::DatabaseConfig;
7use crate::driver::{BoxFuture, Driver, DriverConnection, QueryResult};
8use crate::row::{Columns, Row};
9use crate::value::Value;
10use rustlavel_core::events::Event;
11use rustlavel_core::{Error, Result};
12use std::sync::Arc;
13use std::time::Instant;
14use tokio::net::TcpStream;
15
16/// MySQL's default port, which [`DatabaseConfig::from_url`] applies to a
17/// `mysql://` URL that names no port.
18pub const DEFAULT_PORT: u16 = 3306;
19
20/// The driver name a [`DatabaseConfig`] carries when it points at MySQL.
21pub const DRIVER_NAME: &str = "mysql";
22
23pub struct MySqlConnection {
24    stream: crate::tls::DbStream,
25    /// Bytes read from the socket but not yet consumed as a packet.
26    buffer: Vec<u8>,
27    config: DatabaseConfig,
28    connection_id: u32,
29    server_version: String,
30    /// The capabilities both sides agreed on, which decide the shape of every
31    /// packet from here on.
32    capabilities: u32,
33    status: u16,
34    /// The sequence id the next packet we send must carry. It restarts at zero
35    /// for each command, and the server rejects a packet that arrives with the
36    /// wrong one — which is how a desynchronised connection is caught early.
37    sequence: u8,
38    /// Set when the connection is known to be unusable, so the pool discards it.
39    broken: bool,
40}
41
42impl MySqlConnection {
43    /// Open a connection and complete the handshake.
44    pub async fn connect(config: &DatabaseConfig) -> Result<MySqlConnection> {
45        let address = format!("{}:{}", config.host, config.port);
46
47        let stream = tokio::time::timeout(config.connect_timeout, TcpStream::connect(&address))
48            .await
49            .map_err(|_| {
50                // 2003 is the code the MySQL client prints for this, so it is
51                // the string people will already have searched for.
52                Error::msg(format!(
53                    "MySQL error 2003: timed out connecting to {address}. Is MySQL running and \
54                     reachable?"
55                ))
56            })?
57            .map_err(|e| {
58                Error::msg(format!(
59                    "MySQL error 2003: cannot connect to {address}: {e}\n  \
60                     Check that the server is running and that DATABASE_URL points at it ({}).",
61                    config.redacted_url()
62                ))
63            })?;
64
65        let _ = stream.set_nodelay(true);
66
67        let mut connection = MySqlConnection {
68            stream: crate::tls::DbStream::Plain(stream),
69            buffer: Vec::with_capacity(8 * 1024),
70            config: config.clone(),
71            connection_id: 0,
72            server_version: String::new(),
73            capabilities: 0,
74            status: 0,
75            sequence: 0,
76            broken: false,
77        };
78
79        connection.handshake().await?;
80        Ok(connection)
81    }
82
83    pub fn is_broken(&self) -> bool {
84        self.broken
85    }
86
87    /// True while a transaction is open, so the pool never hands back a
88    /// connection that would leak one.
89    ///
90    /// Read from the server's own status flag rather than from tracking
91    /// `begin`/`commit`, which stays right even when a statement commits
92    /// implicitly — DDL in MySQL always does.
93    pub fn in_transaction(&self) -> bool {
94        self.status & protocol::SERVER_STATUS_IN_TRANS != 0
95    }
96
97    /// The server's version string, as it introduced itself.
98    pub fn server_version(&self) -> &str {
99        &self.server_version
100    }
101
102    /// The connection id, which is what `kill <id>` and the slow query log use.
103    pub fn connection_id(&self) -> u32 {
104        self.connection_id
105    }
106
107    /// The capabilities the handshake settled on, which decide what every
108    /// packet after it looks like. Useful when a packet does not parse.
109    pub fn capabilities(&self) -> u32 {
110        self.capabilities
111    }
112
113    /// The handshake: read the server's greeting, answer it, and follow the
114    /// authentication plugin wherever it goes.
115    /// Whether this connection is encrypted.
116    pub fn is_encrypted(&self) -> bool {
117        self.stream.is_encrypted()
118    }
119
120    /// Ask MySQL to encrypt the connection, and return the capabilities to
121    /// carry forward.
122    ///
123    /// MySQL has no separate "please encrypt" message the way PostgreSQL does.
124    /// The request *is* the first 32 bytes of the handshake response with
125    /// `CLIENT_SSL` set: the server reads that much, sees the flag, and expects
126    /// a TLS handshake next instead of the rest of the packet. The credentials
127    /// then go inside the tunnel.
128    ///
129    /// The packet sequence number keeps counting across the boundary — it is
130    /// not reset by the upgrade — which [`Self::write_packet`] already does,
131    /// and getting it wrong makes the server drop the connection without
132    /// saying why.
133    async fn negotiate_tls(&mut self, capabilities: u32, server: u32) -> Result<u32> {
134        let mode = self.config.tls_mode;
135        if !mode.wants_tls() {
136            return Ok(capabilities);
137        }
138
139        if server & protocol::CLIENT_SSL == 0 {
140            if mode.demands_tls() {
141                self.broken = true;
142                return Err(Error::msg(format!(
143                    "sslmode is `{mode}` but this MySQL does not offer TLS: it did not advertise                      CLIENT_SSL in its handshake. The server was built or configured without a                      certificate — set `ssl_cert` and `ssl_key` on it, or set sslmode=prefer to                      accept a connection in clear text."
144                )));
145            }
146            // Nothing to ask for, so the connection stays as it is.
147            return Ok(capabilities);
148        }
149
150        let mut request = Buffer::new();
151        request.ssl_request(capabilities);
152        self.write_packet(request).await?;
153
154        let plain = self.stream.take_plain()?;
155        let encrypted = crate::tls::upgrade(plain, &self.config.host, &self.config).await?;
156        self.stream = crate::tls::DbStream::Tls(Box::new(encrypted));
157
158        Ok(capabilities | protocol::CLIENT_SSL)
159    }
160
161    async fn handshake(&mut self) -> Result<()> {
162        let greeting = self.read_packet().await?;
163        if protocol::is_err(&greeting) {
164            // A server that is out of connections, or that has banned this
165            // host, refuses before it ever says hello.
166            self.broken = true;
167            return Err(server_error(protocol::parse_err(&greeting)?, &self.config, None));
168        }
169
170        let handshake = protocol::parse_handshake(&greeting)?;
171        self.connection_id = handshake.connection_id;
172        self.server_version = handshake.server_version.clone();
173
174        let mut capabilities = protocol::CLIENT_CAPABILITIES & handshake.capabilities;
175        // The plugin flags are ours to assert: a server that does not advertise
176        // them still has to be told which plugin we answered with.
177        capabilities |= protocol::CLIENT_PROTOCOL_41 | protocol::CLIENT_SECURE_CONNECTION;
178        if !self.config.database.is_empty() {
179            capabilities |= protocol::CLIENT_CONNECT_WITH_DB;
180        }
181        // Before the credentials, and after the capabilities are settled: the
182        // server has to be told we want TLS in the same field that tells it
183        // everything else about the connection.
184        self.capabilities = self.negotiate_tls(capabilities, handshake.capabilities).await?;
185        let capabilities = self.capabilities;
186
187        // Default to the SHA-1 plugin only when the server named nothing, which
188        // is what a pre-4.1 greeting looks like.
189        let mut plugin = if handshake.auth_plugin.is_empty() {
190            auth::MYSQL_NATIVE_PASSWORD.to_string()
191        } else {
192            handshake.auth_plugin.clone()
193        };
194
195        if !auth::is_supported(&plugin) {
196            self.broken = true;
197            return Err(auth::insecure_plugin_error(&plugin));
198        }
199
200        let response = auth::respond(&plugin, &self.config.password, &handshake.scramble)?;
201
202        let mut reply = Buffer::new();
203        reply.handshake_response(
204            capabilities,
205            &self.config.user,
206            &response,
207            Some(&self.config.database),
208            &plugin,
209            &[
210                ("_client_name", "rustlavel"),
211                ("program_name", self.config.application_name.as_str()),
212            ],
213        );
214        self.write_packet(reply).await?;
215
216        // Whatever the plugin, the exchange ends at an OK or an ERR; everything
217        // between is the plugin negotiating.
218        loop {
219            let payload = self.read_packet().await?;
220
221            match Packet::parse(&payload)? {
222                Packet::Ok(ok) => {
223                    self.status = ok.status;
224                    return Ok(());
225                }
226                Packet::Err(error) => {
227                    self.broken = true;
228                    return Err(authentication_error(error, &self.config));
229                }
230                Packet::AuthSwitch { plugin: wanted, data } => {
231                    if !auth::is_supported(&wanted) {
232                        self.broken = true;
233                        return Err(auth::insecure_plugin_error(&wanted));
234                    }
235                    // The switch carries a fresh scramble; the old one belongs
236                    // to a plugin we are no longer speaking.
237                    plugin = wanted;
238                    let response = auth::respond(&plugin, &self.config.password, &data)?;
239                    let mut reply = Buffer::new();
240                    reply.auth_response(&response);
241                    self.write_packet(reply).await?;
242                }
243                Packet::AuthMoreData(data) if plugin == auth::CACHING_SHA2_PASSWORD => {
244                    match auth::fast_auth_status(&data)? {
245                        // Nothing to send: the OK packet is already on its way.
246                        FastAuth::Succeeded => continue,
247                        // The password itself, and only ever inside the
248                        // tunnel. This is what MySQL's own client does, and
249                        // the check is on the stream rather than on the
250                        // configured mode: `prefer` against a server that
251                        // declined leaves the mode saying "tls" and the socket
252                        // saying otherwise, and the socket is the one telling
253                        // the truth.
254                        FastAuth::FullAuthRequired if self.stream.is_encrypted() => {
255                            let mut reply = Buffer::new();
256                            reply.auth_response(&auth::cleartext_password(
257                                &self.config.password,
258                            ));
259                            self.write_packet(reply).await?;
260                        }
261                        FastAuth::FullAuthRequired => {
262                            self.broken = true;
263                            return Err(auth::full_auth_error(
264                                &self.config.user,
265                                &format!("{}:{}", self.config.host, self.config.port),
266                            ));
267                        }
268                    }
269                }
270                Packet::AuthMoreData(_) | Packet::Eof(_) | Packet::Other(_) => {
271                    self.broken = true;
272                    return Err(Error::Protocol(format!(
273                        "unexpected packet during {plugin} authentication"
274                    )));
275                }
276            }
277        }
278    }
279
280    /// Run a statement with no parameters, as text.
281    ///
282    /// Used for DDL and for transaction control — the statements MySQL will not
283    /// let a client prepare.
284    pub async fn simple_query(&mut self, sql: &str) -> Result<QueryResult> {
285        let started = Instant::now();
286
287        let mut command = Buffer::new();
288        command.com_query(sql);
289        self.sequence = 0;
290        let result = match self.write_packet(command).await {
291            Ok(()) => self.read_result_set(sql, false).await,
292            Err(e) => Err(e),
293        };
294
295        self.record(sql, &[], started, &result);
296        result
297    }
298
299    /// Run a parameterised statement as a prepared statement.
300    ///
301    /// Prepare, execute, close. The values travel in their own typed section of
302    /// the execute packet and never enter the statement text, which is what
303    /// makes SQL injection structurally impossible rather than a matter of
304    /// remembering to escape. Statements with no parameters go the same way, so
305    /// there is one code path to be right about rather than two.
306    pub async fn query(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
307        let started = Instant::now();
308        let result = self.prepared(sql, params).await;
309        self.record(sql, params, started, &result);
310        result
311    }
312
313    async fn prepared(&mut self, sql: &str, params: &[Value]) -> Result<QueryResult> {
314        let statement = self.prepare(sql).await?;
315
316        if statement.params as usize != params.len() {
317            self.close_statement(statement.statement_id).await?;
318            return Err(Error::msg(format!(
319                "the statement takes {} parameter(s) but {} were bound.\n  SQL: {sql}",
320                statement.params,
321                params.len()
322            )));
323        }
324
325        let mut command = Buffer::new();
326        command.com_stmt_execute(statement.statement_id, params);
327        self.sequence = 0;
328        let result = match self.write_packet(command).await {
329            Ok(()) => self.read_result_set(sql, true).await,
330            Err(e) => Err(e),
331        };
332
333        // Closed whether or not the execute worked: the handle is the server's
334        // memory, and an error is no reason to leak it for the session's life.
335        self.close_statement(statement.statement_id).await?;
336        result
337    }
338
339    /// Ask the server to parse a statement, and read back its metadata.
340    async fn prepare(&mut self, sql: &str) -> Result<protocol::PrepareOk> {
341        let mut command = Buffer::new();
342        command.com_stmt_prepare(sql);
343        self.sequence = 0;
344        self.write_packet(command).await?;
345
346        let payload = self.read_packet().await?;
347        if protocol::is_err(&payload) {
348            return Err(server_error(protocol::parse_err(&payload)?, &self.config, Some(sql)));
349        }
350
351        let prepared = protocol::parse_prepare_ok(&payload)?;
352
353        // The parameter and column metadata follow, each section closed by an
354        // EOF. The driver does not need them — the execute response describes
355        // the columns again — but they must be consumed or the next read would
356        // pick one of them up as a result.
357        if prepared.params > 0 {
358            self.skip_metadata(prepared.params as usize).await?;
359        }
360        if prepared.columns > 0 {
361            self.skip_metadata(prepared.columns as usize).await?;
362        }
363
364        Ok(prepared)
365    }
366
367    async fn skip_metadata(&mut self, count: usize) -> Result<()> {
368        for _ in 0..count {
369            self.read_packet().await?;
370        }
371        // The trailing EOF, present because the driver does not negotiate
372        // CLIENT_DEPRECATE_EOF.
373        let payload = self.read_packet().await?;
374        if !protocol::is_eof(&payload) {
375            return Err(Error::Protocol(
376                "expected an EOF after a metadata block from the server".into(),
377            ));
378        }
379        Ok(())
380    }
381
382    async fn close_statement(&mut self, statement_id: u32) -> Result<()> {
383        let mut command = Buffer::new();
384        command.com_stmt_close(statement_id);
385        self.sequence = 0;
386        // COM_STMT_CLOSE is the one command the server never answers.
387        self.write_packet(command).await
388    }
389
390    /// Read one command's response: an OK, an error, or a whole result set.
391    ///
392    /// `binary` selects the row format — text for `COM_QUERY`, binary for
393    /// `COM_STMT_EXECUTE` — which is the only thing that differs between them.
394    async fn read_result_set(&mut self, sql: &str, binary: bool) -> Result<QueryResult> {
395        let mut result = QueryResult::default();
396        let mut first = true;
397
398        loop {
399            let payload = self.read_packet().await?;
400
401            if protocol::is_err(&payload) {
402                return Err(server_error(protocol::parse_err(&payload)?, &self.config, Some(sql)));
403            }
404
405            if payload.first() == Some(&0x00) {
406                // No result set: an OK packet, carrying the row count and any
407                // generated key.
408                let ok = protocol::parse_ok(&payload)?;
409                self.status = ok.status;
410                if first {
411                    apply_ok(&mut result, &ok);
412                }
413                if ok.status & protocol::SERVER_MORE_RESULTS_EXISTS != 0 {
414                    first = false;
415                    continue;
416                }
417                return Ok(result);
418            }
419
420            let count = protocol::Reader::new(&payload).lenenc_int()? as usize;
421            let columns = self.read_columns(count).await?;
422            let more = self.read_rows(&columns, binary, first.then_some(&mut result)).await?;
423
424            if !more {
425                return Ok(result);
426            }
427            first = false;
428        }
429    }
430
431    /// Read `count` column definitions and the EOF that closes them.
432    async fn read_columns(&mut self, count: usize) -> Result<Vec<Column>> {
433        let mut columns = Vec::with_capacity(count);
434        for _ in 0..count {
435            let payload = self.read_packet().await?;
436            columns.push(protocol::parse_column(&payload)?);
437        }
438
439        let payload = self.read_packet().await?;
440        if !protocol::is_eof(&payload) {
441            return Err(Error::Protocol(
442                "expected an EOF after the column definitions".into(),
443            ));
444        }
445        Ok(columns)
446    }
447
448    /// Read rows until the EOF that closes them, returning whether another
449    /// result set follows.
450    ///
451    /// `into` is `None` for the second and later result sets of a multi-result
452    /// response: they are drained so the connection stays in step, but only the
453    /// first one's rows are handed back.
454    async fn read_rows(
455        &mut self,
456        columns: &[Column],
457        binary: bool,
458        into: Option<&mut QueryResult>,
459    ) -> Result<bool> {
460        let names: Columns = Arc::new(columns.iter().map(|c| c.name.clone()).collect());
461        let mut rows = Vec::new();
462
463        let status = loop {
464            let payload = self.read_packet().await?;
465
466            if protocol::is_err(&payload) {
467                return Err(server_error(protocol::parse_err(&payload)?, &self.config, None));
468            }
469            if protocol::is_eof(&payload) {
470                let eof = protocol::parse_eof(&payload)?;
471                self.status = eof.status;
472                break eof.status;
473            }
474
475            let values = if binary {
476                decode_binary_row(&payload, columns)?
477            } else {
478                protocol::parse_text_row(&payload, columns.len())?
479                    .iter()
480                    .zip(columns)
481                    .map(|(raw, column)| types::decode_text(column, raw.as_deref()))
482                    .collect()
483            };
484            rows.push(Row::new(Arc::clone(&names), values));
485        };
486
487        if let Some(result) = into {
488            // A select reports the rows it returned; MySQL's OK-less result set
489            // has no affected count of its own.
490            result.affected = rows.len() as u64;
491            result.rows = rows;
492        }
493
494        Ok(status & protocol::SERVER_MORE_RESULTS_EXISTS != 0)
495    }
496
497    /// Ask the server whether it is still there.
498    pub async fn ping(&mut self) -> Result<()> {
499        let mut command = Buffer::new();
500        command.com_ping();
501        self.sequence = 0;
502        self.write_packet(command).await?;
503
504        let payload = self.read_packet().await?;
505        match Packet::parse(&payload)? {
506            Packet::Ok(ok) => {
507                self.status = ok.status;
508                Ok(())
509            }
510            Packet::Err(error) => Err(server_error(error, &self.config, None)),
511            _ => Err(Error::Protocol("the server answered a ping with something else".into())),
512        }
513    }
514
515    /// Publish the query on the event bus for Telescope and slow-query logging.
516    fn record(&self, sql: &str, params: &[Value], started: Instant, result: &Result<QueryResult>) {
517        let elapsed = started.elapsed();
518
519        if rustlavel_core::events::has_subscribers() {
520            // The same switch as the PostgreSQL driver's, deliberately: it is
521            // process-wide, and one application should not have to turn binding
522            // capture off once per database it talks to.
523            let bindings = if crate::postgres::connection::log_bindings() {
524                params.iter().map(Value::to_display).collect::<Vec<_>>().join(", ")
525            } else {
526                format!("{} value(s) hidden", params.len())
527            };
528            Event::new("db.query")
529                .with("sql", sql)
530                .with("bindings", bindings)
531                .with("rows", result.as_ref().map(|r| r.rows.len()).unwrap_or(0))
532                .with("ok", result.is_ok())
533                .took(elapsed)
534                .dispatch();
535        }
536
537        rustlavel_core::debug!("db: {sql} ({:.1}ms)", elapsed.as_secs_f64() * 1000.0);
538    }
539
540    /// Frame a payload with the next sequence id and send it.
541    async fn write_packet(&mut self, buffer: Buffer) -> Result<()> {
542        let (bytes, next) = protocol::frame(&buffer.into_bytes(), self.sequence);
543        self.sequence = next;
544
545        if let Err(e) = self.stream.write_all(&bytes).await {
546            self.broken = true;
547            return Err(Error::Io(e));
548        }
549        if let Err(e) = self.stream.flush().await {
550            self.broken = true;
551            return Err(Error::Io(e));
552        }
553        Ok(())
554    }
555
556    /// Read exactly one logical packet, rejoining a payload that was split
557    /// across frames.
558    async fn read_packet(&mut self) -> Result<Vec<u8>> {
559        let mut payload = Vec::new();
560
561        loop {
562            self.fill_to(4).await?;
563            let length =
564                u32::from_le_bytes([self.buffer[0], self.buffer[1], self.buffer[2], 0]) as usize;
565            let sequence = self.buffer[3];
566
567            self.fill_to(4 + length).await?;
568            payload.extend_from_slice(&self.buffer[4..4 + length]);
569            self.buffer.drain(..4 + length);
570            self.sequence = sequence.wrapping_add(1);
571
572            // Only a maximum-length frame can have a continuation; anything
573            // shorter is the end of the payload.
574            if length < protocol::MAX_PAYLOAD {
575                return Ok(payload);
576            }
577        }
578    }
579
580    /// Read from the socket until the buffer holds at least `wanted` bytes.
581    async fn fill_to(&mut self, wanted: usize) -> Result<()> {
582        while self.buffer.len() < wanted {
583            let mut chunk = [0u8; 8192];
584            let read = match self.stream.read(&mut chunk).await {
585                Ok(read) => read,
586                Err(e) => {
587                    self.broken = true;
588                    return Err(Error::Io(e));
589                }
590            };
591            if read == 0 {
592                self.broken = true;
593                return Err(Error::Protocol(
594                    "the database closed the connection unexpectedly".into(),
595                ));
596            }
597            self.buffer.extend_from_slice(&chunk[..read]);
598        }
599        Ok(())
600    }
601
602    /// Say goodbye politely, then hang up.
603    pub async fn close(mut self) {
604        let mut command = Buffer::new();
605        command.com_quit();
606        self.sequence = 0;
607        let _ = self.write_packet(command).await;
608        let _ = self.stream.shutdown().await;
609    }
610}
611
612/// Turn a server error into one that says what to do about it.
613fn server_error(error: ServerError, config: &DatabaseConfig, sql: Option<&str>) -> Error {
614    let advice = match error.code {
615        1049 => Some(format!(
616            "Database `{}` does not exist. Create it, or point DATABASE_URL at one that does.",
617            config.database
618        )),
619        1044 => Some(format!(
620            "User `{}` has no rights on `{}`. Grant them, or connect as a user who has.",
621            config.user, config.database
622        )),
623        1146 => Some("The table is missing. Have the migrations been run?".to_string()),
624        _ => None,
625    };
626
627    let base = error.into_error(sql);
628    match advice {
629        Some(advice) => Error::msg(format!("{base}\n  {advice}")),
630        None => base,
631    }
632}
633
634/// Turn an authentication failure into something the developer can act on.
635fn authentication_error(error: ServerError, config: &DatabaseConfig) -> Error {
636    let advice = match error.code {
637        1045 => Some(format!(
638            "The password for `{}` was rejected. Check DATABASE_URL in your .env.",
639            config.user
640        )),
641        1049 => Some(format!(
642            "Database `{}` does not exist. Create it, or point DATABASE_URL at one that does.",
643            config.database
644        )),
645        1130 | 1698 => Some(format!(
646            "The server will not let `{}` in from this host. Check the account's host pattern \
647             and its authentication plugin.",
648            config.user
649        )),
650        1040 | 1203 => Some(
651            "The server is out of connections. Lower max_connections in DATABASE_URL, or raise \
652             the server's."
653                .to_string(),
654        ),
655        _ => None,
656    };
657
658    let base = error.into_error(None);
659    match advice {
660        Some(advice) => Error::msg(format!("{base}\n  {advice}")),
661        None => base,
662    }
663}
664
665/// Split a binary-protocol row into its values.
666///
667/// The row opens with a `0x00` header and a NULL bitmap. The bitmap is offset
668/// by two bits — a quirk left over from the header having once been two bytes —
669/// so column `i` is at bit `i + 2`, and getting that wrong shifts every NULL in
670/// the row by one column.
671fn decode_binary_row(payload: &[u8], columns: &[Column]) -> Result<Vec<Value>> {
672    let mut reader = protocol::Reader::new(payload);
673    reader.skip(1)?; // the 0x00 header
674
675    let bitmap = reader.take((columns.len() + 2).div_ceil(8))?;
676    let mut values = Vec::with_capacity(columns.len());
677
678    for (index, column) in columns.iter().enumerate() {
679        let bit = index + 2;
680        if bitmap[bit / 8] & (1 << (bit % 8)) != 0 {
681            values.push(Value::Null);
682        } else {
683            values.push(types::decode_binary(column, &mut reader)?);
684        }
685    }
686
687    Ok(values)
688}
689
690fn apply_ok(result: &mut QueryResult, ok: &OkPacket) {
691    result.affected = ok.affected_rows;
692    // Zero means "this statement generated no key", not "the key was zero":
693    // an `auto_increment` column never issues zero.
694    result.last_insert_id = (ok.last_insert_id != 0).then_some(ok.last_insert_id as i64);
695}
696
697/// Opens MySQL connections.
698pub struct MySqlDriver {
699    config: DatabaseConfig,
700    dialect: Arc<dyn crate::dialect::Dialect>,
701}
702
703impl MySqlDriver {
704    pub fn new(mut config: DatabaseConfig) -> Self {
705        // A config assembled by hand keeps `DatabaseConfig::default`'s driver
706        // name, and that name is what `redacted_url` prints as the scheme — so
707        // it is corrected here rather than leaving `describe()` claiming to be
708        // a PostgreSQL connection.
709        config.driver = DRIVER_NAME.into();
710        MySqlDriver { config, dialect: Arc::new(crate::dialect::MySql) }
711    }
712
713    /// Build a driver from `mysql://user:password@host:port/database`.
714    pub fn from_url(url: &str) -> Result<Self> {
715        let config = DatabaseConfig::from_url(url)?;
716
717        // A `postgres://` URL handed to the MySQL driver would otherwise fail
718        // much later, as a handshake that makes no sense.
719        if config.driver != DRIVER_NAME {
720            return Err(Error::msg(format!(
721                "`{url}` is a {} URL, not a MySQL one. Expected \
722                 mysql://user:password@host:port/database",
723                config.driver
724            )));
725        }
726
727        Ok(MySqlDriver::new(config))
728    }
729
730    pub fn config(&self) -> &DatabaseConfig {
731        &self.config
732    }
733}
734
735impl Driver for MySqlDriver {
736    fn generation(&self) -> u64 {
737        self.config.generation()
738    }
739
740    fn dialect(&self) -> Arc<dyn crate::dialect::Dialect> {
741        Arc::clone(&self.dialect)
742    }
743
744    fn connect(&self) -> BoxFuture<'_, Result<Box<dyn DriverConnection>>> {
745        Box::pin(async move {
746            let connection = MySqlConnection::connect(&self.config.resolved()).await?;
747            Ok(Box::new(connection) as Box<dyn DriverConnection>)
748        })
749    }
750
751    fn describe(&self) -> String {
752        self.config.redacted_url()
753    }
754
755    fn max_connections(&self) -> usize {
756        self.config.max_connections
757    }
758}
759
760impl DriverConnection for MySqlConnection {
761    fn query<'a>(
762        &'a mut self,
763        sql: &'a str,
764        params: &'a [Value],
765    ) -> BoxFuture<'a, Result<QueryResult>> {
766        Box::pin(MySqlConnection::query(self, sql, params))
767    }
768
769    fn simple_query<'a>(&'a mut self, sql: &'a str) -> BoxFuture<'a, Result<QueryResult>> {
770        Box::pin(MySqlConnection::simple_query(self, sql))
771    }
772
773    fn is_broken(&self) -> bool {
774        MySqlConnection::is_broken(self)
775    }
776
777    fn in_transaction(&self) -> bool {
778        MySqlConnection::in_transaction(self)
779    }
780
781    fn close(self: Box<Self>) -> BoxFuture<'static, ()> {
782        Box::pin(async move { MySqlConnection::close(*self).await })
783    }
784}
785
786#[cfg(test)]
787mod tests {
788    use super::*;
789    use crate::mysql::protocol::{CHARSET_BINARY, CHARSET_UTF8MB4};
790
791    fn column(name: &str, column_type: u8) -> Column {
792        Column {
793            name: name.into(),
794            column_type,
795            charset: CHARSET_UTF8MB4 as u16,
796            ..Column::default()
797        }
798    }
799
800    #[test]
801    fn a_generated_key_of_zero_means_no_key_was_generated() {
802        let mut result = QueryResult::default();
803        apply_ok(&mut result, &OkPacket { affected_rows: 1, last_insert_id: 0, ..OkPacket::default() });
804        assert_eq!(result.last_insert_id, None);
805        assert_eq!(result.affected, 1);
806
807        apply_ok(&mut result, &OkPacket { affected_rows: 1, last_insert_id: 42, ..OkPacket::default() });
808        assert_eq!(result.last_insert_id, Some(42));
809    }
810
811    #[test]
812    fn a_binary_row_reads_its_nulls_from_a_bitmap_offset_by_two_bits() {
813        let columns = vec![column("id", types::LONGLONG), column("name", types::VAR_STRING)];
814
815        // Header, bitmap marking column 1 (bit 3) NULL, then only column 0.
816        let mut payload = vec![0x00, 0b0000_1000];
817        payload.extend_from_slice(&7i64.to_le_bytes());
818
819        let values = decode_binary_row(&payload, &columns).unwrap();
820        assert_eq!(values, vec![Value::Int(7), Value::Null]);
821    }
822
823    #[test]
824    fn a_binary_row_with_no_nulls_decodes_every_column() {
825        let columns = vec![
826            column("id", types::LONGLONG),
827            column("name", types::VAR_STRING),
828            Column { charset: CHARSET_BINARY, ..column("blob", types::BLOB) },
829        ];
830
831        let mut payload = vec![0x00, 0x00];
832        payload.extend_from_slice(&7i64.to_le_bytes());
833        payload.extend_from_slice(b"\x03ada");
834        payload.extend_from_slice(b"\x02\xde\xad");
835
836        let values = decode_binary_row(&payload, &columns).unwrap();
837        assert_eq!(
838            values,
839            vec![Value::Int(7), Value::Text("ada".into()), Value::Bytes(vec![0xDE, 0xAD])]
840        );
841    }
842
843    #[test]
844    fn a_wide_row_needs_more_than_one_bitmap_byte() {
845        // Seven columns plus the two offset bits is nine, so the bitmap is two
846        // bytes; a one-byte bitmap would silently truncate.
847        let columns: Vec<Column> =
848            (0..7).map(|i| column(&format!("c{i}"), types::TINY)).collect();
849
850        // Mark the last column (bit 8) NULL.
851        let mut payload = vec![0x00, 0b0000_0000, 0b0000_0001];
852        payload.extend_from_slice(&[1, 2, 3, 4, 5, 6]);
853
854        let values = decode_binary_row(&payload, &columns).unwrap();
855        assert_eq!(values.len(), 7);
856        assert_eq!(values[6], Value::Null);
857        assert_eq!(values[0], Value::Int(1));
858    }
859
860    #[test]
861    fn a_truncated_row_is_an_error_rather_than_a_panic() {
862        let columns = vec![column("id", types::LONGLONG)];
863        assert!(decode_binary_row(&[0x00], &columns).is_err());
864        assert!(decode_binary_row(&[0x00, 0x00, 1, 2], &columns).is_err());
865    }
866
867    #[test]
868    fn a_url_defaults_to_mysqls_port() {
869        let driver = MySqlDriver::from_url("mysql://localhost/blog").unwrap();
870
871        assert_eq!(driver.config().host, "localhost");
872        assert_eq!(driver.config().port, DEFAULT_PORT);
873        assert_eq!(driver.config().database, "blog");
874        assert_eq!(driver.config().driver, DRIVER_NAME);
875    }
876
877    #[test]
878    fn a_url_keeps_everything_it_states() {
879        let driver =
880            MySqlDriver::from_url("mysql://ada:hunter2@db.internal:3307/blog").unwrap();
881        let config = driver.config();
882
883        assert_eq!(config.user, "ada");
884        assert_eq!(config.password, "hunter2");
885        assert_eq!(config.host, "db.internal");
886        assert_eq!(config.port, 3307);
887        assert_eq!(config.database, "blog");
888    }
889
890    #[test]
891    fn mariadb_urls_are_accepted_too() {
892        // MariaDB speaks the same wire protocol, so it is the same driver.
893        let driver = MySqlDriver::from_url("mariadb://host/blog").unwrap();
894
895        assert_eq!(driver.config().database, "blog");
896        assert_eq!(driver.config().port, DEFAULT_PORT);
897    }
898
899    #[test]
900    fn rejects_a_url_that_points_at_another_database() {
901        // Better here than as a handshake that makes no sense later.
902        let error = match MySqlDriver::from_url("postgres://host/blog") {
903            Err(error) => error.to_string(),
904            Ok(driver) => panic!("accepted a {} URL", driver.config().driver),
905        };
906        assert!(error.contains("not a MySQL one"), "{error}");
907    }
908
909    #[test]
910    fn a_hand_built_config_still_describes_itself_as_mysql() {
911        // `DatabaseConfig::default` says `postgres`, and that name is the
912        // scheme in every log line and error message this driver prints.
913        let driver = MySqlDriver::new(DatabaseConfig::default());
914        assert!(driver.describe().starts_with("mysql://"), "{}", driver.describe());
915    }
916
917    #[test]
918    fn the_driver_speaks_the_mysql_dialect_and_never_prints_the_password() {
919        let driver =
920            MySqlDriver::from_url("mysql://ada:hunter2@host/blog?max_connections=4").unwrap();
921
922        assert_eq!(driver.dialect().name(), "mysql");
923        assert!(driver.dialect().booleans_are_integers());
924        assert_eq!(driver.max_connections(), 4);
925
926        let shown = driver.describe();
927        assert!(!shown.contains("hunter2"), "{shown}");
928        assert!(shown.starts_with("mysql://ada:***@"), "{shown}");
929    }
930
931    #[test]
932    fn a_wrong_password_explains_where_to_look() {
933        let config = DatabaseConfig { user: "ada".into(), ..DatabaseConfig::default() };
934        let error = authentication_error(
935            ServerError {
936                code: 1045,
937                sql_state: "28000".into(),
938                message: "Access denied for user 'ada'@'localhost'".into(),
939            },
940            &config,
941        )
942        .to_string();
943
944        assert!(error.contains("1045"), "{error}");
945        assert!(error.contains("28000"), "{error}");
946        assert!(error.contains("Access denied"), "{error}");
947        assert!(error.contains("DATABASE_URL"), "{error}");
948    }
949
950    #[test]
951    fn an_unknown_database_names_the_one_that_was_asked_for() {
952        let config = DatabaseConfig { database: "blog".into(), ..DatabaseConfig::default() };
953        let error = server_error(
954            ServerError {
955                code: 1049,
956                sql_state: "42000".into(),
957                message: "Unknown database 'blog'".into(),
958            },
959            &config,
960            None,
961        )
962        .to_string();
963
964        assert!(error.contains("1049"), "{error}");
965        assert!(error.contains("`blog` does not exist"), "{error}");
966    }
967
968    #[test]
969    fn a_statement_error_carries_the_sql_that_caused_it() {
970        let error = server_error(
971            ServerError {
972                code: 1146,
973                sql_state: "42S02".into(),
974                message: "Table 'blog.nope' doesn't exist".into(),
975            },
976            &DatabaseConfig::default(),
977            Some("select * from nope"),
978        )
979        .to_string();
980
981        assert!(error.contains("SQL: select * from nope"), "{error}");
982        assert!(error.contains("migrations"), "{error}");
983    }
984
985    #[tokio::test]
986    async fn connecting_to_a_closed_port_names_the_server_and_the_client_error_code() {
987        let config = DatabaseConfig { port: 1, ..DatabaseConfig::default() };
988        let error = match MySqlConnection::connect(&config).await {
989            Err(error) => error.to_string(),
990            Ok(_) => panic!("nothing should be listening on port 1"),
991        };
992
993        assert!(error.contains("127.0.0.1:1"), "{error}");
994        assert!(error.contains("2003"), "{error}");
995    }
996
997    #[tokio::test]
998    async fn a_connection_that_never_opened_reports_no_transaction() {
999        // Built without a socket so the state machine can be inspected offline.
1000        let connection = offline(DatabaseConfig::default());
1001
1002        assert!(!connection.in_transaction());
1003        assert!(!connection.is_broken());
1004        assert_eq!(connection.connection_id(), 0);
1005        assert_eq!(connection.server_version(), "");
1006    }
1007
1008    /// A connection whose socket is a closed loopback pair, for testing the
1009    /// parts that never touch the network.
1010    fn offline(config: DatabaseConfig) -> MySqlConnection {
1011        let listener = std::net::TcpListener::bind("127.0.0.1:0").expect("a free port");
1012        let address = listener.local_addr().expect("an address");
1013        let stream = std::net::TcpStream::connect(address).expect("a loopback connection");
1014        stream.set_nonblocking(true).expect("non-blocking");
1015
1016        MySqlConnection {
1017            stream: crate::tls::DbStream::Plain(
1018                TcpStream::from_std(stream).expect("a tokio stream"),
1019            ),
1020            buffer: Vec::new(),
1021            config,
1022            connection_id: 0,
1023            server_version: String::new(),
1024            capabilities: protocol::CLIENT_CAPABILITIES,
1025            status: 0,
1026            sequence: 0,
1027            broken: false,
1028        }
1029    }
1030}