mysql_async

repository·master·Indexed 19 days ago

https://github.com/blackbeam/mysql_async

A Tokio-based asynchronous MySQL client library for Rust. It provides high-performance, non-blocking database access with support for connection pooling, transactions, prepared statements, and LOCAL INFILE handlers. The library supports both native-tls and rustls for TLS/SSL, and offers client-side named parameters using the :name syntax.

Tokens
18.4K
Snippets
50
Records
73
Agent score
64%

What's inside mysql_async

  1. Configure TLS/SSL support

    master

    You can choose between two TLS implementations:

    1. native-tls: The default option (via native-tls-tls feature). Generally works without pitfalls.
    2. rustls: A Rust-based TLS backend (via rustls-tls feature).

    Important considerations for rustls:

    • You must connect using a hostname; connecting via IP address will fail.
    • It may not work on Windows with default server certificates generated by the MySQL installer.
  2. Understand MySql query protocols: Text vs Binary

    master

    mysql_async supports two primary communication protocols:

    Text Protocol

    Implemented via Queryable::query* methods and the prelude::Query trait.

    • Use case: Best for queries that do not require parameters.
    • Performance Note: All values in a text protocol result set are encoded as strings by the server, which may incur additional parsing costs during from_value conversion.

    Binary Protocol (Prepared Statements)

    Implemented via exec* methods. Prepared statements are the only way to pass Rust values to the MySQL server.

    • Use case: Use this when you need to pass parameters to a query.
    • Placeholder: Use the ? symbol as a parameter placeholder.
    • Limitation: You cannot use a single parameter to represent a collection (e.g., WHERE id IN ? is not supported). You must construct a query with the appropriate number of placeholders, such as WHERE id IN (?, ?, ...), and pass each element individually.
  3. How LOCAL INFILE handlers work

    master

    When the server issues a LOCAL INFILE request, the driver searches for a handler in the following order of priority:

    1. Local Handler: A one-time handler installed directly on the connection via Conn::set_infile_handler. This has the highest priority.
    2. Global Handler: A handler specified via OptsBuilder::local_infile_handler. This is used if no local handler is present.
    3. Error: If neither is found, the driver emits LocalInfileError::NoHandler.

    Security Warning: Always review Security Considerations for LOAD DATA LOCAL before implementing handlers.

  4. How prepared statements work in mysql_async

    master
    In MySQL, each prepared statement is tied to a specific connection and cannot be executed on a different connection. The mysql_async driver does not automatically manage this binding for you. To ensure a statement is executed on the correct connection, you can verify the connection ID stored within the Statement structure.
  5. Run a test MySQL server using Docker

    master

    To run tests properly, you must use a MySQL server configured with specific parameters (max allowed packet, local-infile, and binary logging). Use the following command to start a compatible container:

    docker run -d --name container \
        -v `pwd`:/root \
        -p 3307:3306 \
        -e MYSQL_ROOT_PASSWORD=password \
        mysql:8.0 \
        --max-allowed-packet=36700160 \
        --local-infile \
        --log-bin=mysql-bin \
        --log-slave-updates \
        --gtid_mode=ON \
        --enforce-gtid-consistency=ON \
        --server-id=1
  6. Handle multi-runtime environments with Pool

    master

    The Pool must not be shared across independent tokio runtimes. Each connection's underlying TcpStream is bound to the I/O driver of the runtime that established it. If a runtime shuts down, those connections become invalid.

    Best Practice: If your framework (like actix-server) runs each worker on its own current_thread runtime, create a separate Pool instance per worker instead of sharing one across all workers.

  7. Configure mysql_async crate features

    master

    mysql_async uses Cargo features to manage dependencies like TLS backends and compression. By default, flate2/zlib and derive are enabled.

    Common Feature Sets

    • minimal: Only enables flate2/zlib. Use this for a lightweight setup.
    • minimal-rust: Same as minimal but uses a Rust-based flate2 backend.
    • default: Enables flate2/zlib and derive.
    • default-rustls: Default features plus TLS via rustls/aws-lc-rs.
    • default-rustls-ring: Default features plus TLS via rustls/ring.
    • native-tls-tls: Enables TLS via native-tls.
    • rustls-tls: Enables rustls TLS backend without a provider. You must also enable a provider like ring or aws-lc-rs.
    • tracing: Enables instrumentation. query, prepare, and exec are logged at INFO level; other operations like get_conn are at DEBUG level.
    • binlog: Enables binlog-related functionality.

    Proxied Features (from mysql_common)

    These features enable specific type support via the underlying mysql_common crate:

    • derive
    • chrono
    • time
    • bigdecimal
    • rust_decimal
    • frunk
    # Example: Minimal setup with native-tls
    [dependencies]
    mysql_async = { version = "*", default-features = false, features = ["minimal", "native-tls-tls"] }
    
    # Example: Rustls with ring provider
    [dependencies]
    mysql_async = { version = "*", default-features = false, features = ["minimal-rust", "rustls-tls", "ring"] }
  8. How AsQuery works

    master

    The AsQuery trait defines types that can be treated as a MySQL query. Because MySQL does not require queries to be valid UTF-8, this trait is implemented for both string-like types and byte-slice types.

    Supported types include:

    • Strings: &str, String, Box<str>, Cow<'_, str>, std::sync::Arc<str>
    • Bytes: &[u8], Vec<u8>, Box<[u8]>, Cow<'_, [u8]>, std::sync::Arc<[u8]>
  9. Use the Pool for asynchronous connections

    master

    The Pool structure manages an asynchronous connection pool.

    Key characteristics:

    • Pool is a smart pointer; cloning it points to the same underlying pool instance.
    • It is Send + Sync + 'static and can be safely passed between tasks.
    • Pool::new is lazy and does not verify if the server is available immediately.
    • Use pool.disconnect().await to gracefully close the pool.

    Example usage:

    let pool = mysql_async::Pool::new(database_url);
    let mut conn = pool.get_conn().await?;
    // ... use connection ...
    drop(conn);
    pool.disconnect().await?;
  10. How `ToConnection` and `Connection` work together

    master

    The ToConnection trait provides a polymorphic way to acquire a Connection.

    • Pool or &Pool: Calling to_connection() returns a ToConnectionResult::Mediate containing a future that fetches a connection from the pool via get_conn().
    • Conn: Calling to_connection() returns a ToConnectionResult::Immediate because the connection is already owned.
    • Transaction: Can also be converted into a Connection to allow transaction-scoped queries.

    By using ToConnection, you can write APIs that are agnostic to whether the caller provides a single connection or a connection pool.

  11. Execute queries with named and positional parameters

    master

    The driver supports two main protocols:

    1. Text Protocol: Used via query* methods. Best for queries without parameters. Note that all values are encoded as strings by the server, which may incur parsing costs.
    2. Binary Protocol: Used via exec* methods and prepared statements. This is the only way to pass Rust values to the server. It uses ? as a placeholder.

    Named Parameters

    Since MySQL does not natively support named parameters, mysql_async implements them on the client side using the :name syntax.

    Naming Rules:

    • Must start with _ or a..z.
    • May continue with _, a..z, or 0..9.
    • Warning: A statement like SELECT :fooBar will be translated to SELECT ?Bar. Ensure names follow the convention to avoid unexpected behavior.
    • Named parameters can be repeated (e.g., SELECT :foo, :foo).
    • Note: You cannot mix positional (?) and named (:name) parameters in a single statement.

    Use the params! macro to build parameter sets.