mockgres 0.0.29

An in-memory database that replicates a reasonable subset of Postgres functionality to make unit tests that rely on a database to run.
Documentation
mod common;

use tokio_postgres::types::Type;

const LONG_ASCII_NAME: &str = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQR";
const TRUNCATED_ASCII_NAME: &str =
    "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890ABCDEFGHIJKLMNOPQ";

#[tokio::test(flavor = "multi_thread")]
async fn name_literals_truncate_and_compare_like_postgres() {
    assert_eq!(LONG_ASCII_NAME.len(), 64);
    assert_eq!(TRUNCATED_ASCII_NAME.len(), 63);

    let ctx = common::start().await;
    let row = ctx
        .client
        .query_one(
            "select name 'name string' = name 'name string', \
                    name 'name string' = name 'name string '",
            &[],
        )
        .await
        .expect("compare name literals");
    assert!(row.get::<_, bool>(0));
    assert!(!row.get::<_, bool>(1));

    ctx.client
        .execute(
            "create table name_values (id int4 primary key, value name)",
            &[],
        )
        .await
        .expect("create name table");
    ctx.client
        .execute(
            &format!("insert into name_values values (1, '{LONG_ASCII_NAME}')"),
            &[],
        )
        .await
        .expect("insert long name literal");

    let row = ctx
        .client
        .query_one("select value from name_values where id = 1", &[])
        .await
        .expect("select name value");
    assert_eq!(row.columns()[0].type_(), &Type::NAME);
    assert_eq!(row.get::<_, String>(0), TRUNCATED_ASCII_NAME);

    let matching_rows: i64 = ctx
        .client
        .query_one(
            &format!("select count(*) from name_values where value = '{LONG_ASCII_NAME}'"),
            &[],
        )
        .await
        .expect("compare name to overlong untyped literal")
        .get(0);
    assert_eq!(matching_rows, 1);

    let text: String = ctx
        .client
        .query_one(&format!("select '{LONG_ASCII_NAME}'::name::text"), &[])
        .await
        .expect("cast name to text")
        .get(0);
    assert_eq!(text, TRUNCATED_ASCII_NAME);

    let _ = ctx.shutdown.send(());
}

#[tokio::test(flavor = "multi_thread")]
async fn name_binary_parameters_preserve_utf8_boundaries_and_catalog_metadata() {
    let ctx = common::start().await;
    ctx.client
        .execute(
            "create table name_values (id int4 primary key, value name)",
            &[],
        )
        .await
        .expect("create name table");

    let two_byte_name = "é".repeat(40);
    let three_byte_name = "".repeat(30);
    ctx.client
        .execute(
            "insert into name_values values ($1, $2), ($3, $4)",
            &[&1_i32, &two_byte_name, &2_i32, &three_byte_name],
        )
        .await
        .expect("insert binary name parameters");

    let rows = ctx
        .client
        .query("select value from name_values order by id", &[])
        .await
        .expect("select truncated names");
    let expected_two_byte = "é".repeat(31);
    let expected_three_byte = "".repeat(21);
    assert_eq!(rows[0].get::<_, String>(0), expected_two_byte);
    assert_eq!(rows[1].get::<_, String>(0), expected_three_byte);
    assert_eq!(rows[0].get::<_, String>(0).len(), 62);
    assert_eq!(rows[1].get::<_, String>(0).len(), 63);

    let round_trip: String = ctx
        .client
        .query_one(
            "select value from name_values where id = 1 and value = $1",
            &[&two_byte_name],
        )
        .await
        .expect("compare binary name parameter")
        .get(0);
    assert_eq!(round_trip, expected_two_byte);

    let metadata = ctx
        .client
        .query_one(
            "select typname, oid, typlen, typbyval, typcategory, typalign, \
                    typstorage, typelem, typarray, typcollation \
             from pg_catalog.pg_type where oid = 19",
            &[],
        )
        .await
        .expect("select name metadata");
    assert_eq!(metadata.columns()[0].type_(), &Type::NAME);
    assert_eq!(metadata.get::<_, String>(0), "name");
    assert_eq!(metadata.get::<_, i32>(1), 19);
    assert_eq!(metadata.get::<_, i32>(2), 64);
    assert!(!metadata.get::<_, bool>(3));
    assert_eq!(metadata.get::<_, String>(4), "S");
    assert_eq!(metadata.get::<_, String>(5), "c");
    assert_eq!(metadata.get::<_, String>(6), "p");
    assert_eq!(metadata.get::<_, i32>(7), 18);
    assert_eq!(metadata.get::<_, i32>(8), 1003);
    assert_eq!(metadata.get::<_, i32>(9), 950);

    let _ = ctx.shutdown.send(());
}