#api-bindings #sql #mock

msql-srv

Bindings for emulating a MySQL/MariaDB server

40 releases

0.11.0 Dec 17, 2023
0.10.0 Jan 7, 2023
0.9.6 Aug 28, 2021
0.9.5 Jun 6, 2021
0.5.2 Dec 27, 2017

#187 in Database interfaces

Download history 88/week @ 2023-10-30 44/week @ 2023-11-06 78/week @ 2023-11-13 60/week @ 2023-11-20 130/week @ 2023-11-27 53/week @ 2023-12-04 43/week @ 2023-12-11 70/week @ 2023-12-18 104/week @ 2023-12-25 52/week @ 2024-01-01 17/week @ 2024-01-08 15/week @ 2024-01-15 76/week @ 2024-01-22 93/week @ 2024-01-29 28/week @ 2024-02-05 220/week @ 2024-02-12

418 downloads per month
Used in piedb

MIT/Apache

325KB
5.5K SLoC

msql-srv-rs

Crates.io Documentation Codecov Dependency status Maintenance

Bindings for emulating a MySQL/MariaDB server.

When developing new databases or caching layers, it can be immensely useful to test your system using existing applications. However, this often requires significant work modifying applications to use your database over the existing ones. This crate solves that problem by acting as a MySQL server, and delegating operations such as querying and query execution to user-defined logic.

To start, implement MysqlShim for your backend, and create a MysqlIntermediary over an instance of your backend and a connection stream. The appropriate methods will be called on your backend whenever a client issues a QUERY, PREPARE, or EXECUTE command, and you will have a chance to respond appropriately. For example, to write a shim that always responds to all commands with a "no results" reply:

extern crate mysql;
use msql_srv::*;
use mysql::prelude::*;

struct Backend;
impl<W: io::Write> MysqlShim<W> for Backend {
    type Error = io::Error;

    fn on_prepare(&mut self, _: &str, info: StatementMetaWriter<W>) -> io::Result<()> {
        info.reply(42, &[], &[])
    }
    fn on_execute(
        &mut self,
        _: u32,
        _: ParamParser,
        results: QueryResultWriter<W>,
    ) -> io::Result<()> {
        results.completed(0, 0)
    }
    fn on_close(&mut self, _: u32) {}

    fn on_init(&mut self, _: &str, writer: InitWriter<W>) -> io::Result<()> { Ok(()) }

    fn on_query(&mut self, _: &str, results: QueryResultWriter<W>) -> io::Result<()> {
        let cols = [
            Column {
                table: "foo".to_string(),
                column: "a".to_string(),
                coltype: ColumnType::MYSQL_TYPE_LONGLONG,
                colflags: ColumnFlags::empty(),
            },
            Column {
                table: "foo".to_string(),
                column: "b".to_string(),
                coltype: ColumnType::MYSQL_TYPE_STRING,
                colflags: ColumnFlags::empty(),
            },
        ];

        let mut rw = results.start(&cols)?;
        rw.write_col(42)?;
        rw.write_col("b's value")?;
        rw.finish()
    }
}

fn main() {
    let listener = net::TcpListener::bind("127.0.0.1:0").unwrap();
    let port = listener.local_addr().unwrap().port();

    let jh = thread::spawn(move || {
        if let Ok((s, _)) = listener.accept() {
            MysqlIntermediary::run_on_tcp(Backend, s).unwrap();
        }
    });

    let mut db = mysql::Conn::new(&format!("mysql://127.0.0.1:{}", port)).unwrap();
    assert_eq!(db.ping(), true);
    assert_eq!(db.query_iter("SELECT a, b FROM foo").unwrap().count(), 1);
    drop(db);
    jh.join().unwrap();
}

Dependencies

~16–31MB
~454K SLoC