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
use super::*;

fn utility(tag: &'static str) -> Plan {
    Plan::UtilityNoOp { tag }
}

fn count_builtin(kind: &str) -> Plan {
    Plan::CallBuiltin {
        name: format!("regression:gin_count:{kind}"),
        args: Vec::new(),
        schema: Schema {
            fields: vec![Field {
                name: "count".to_string(),
                data_type: DataType::Int8,
                origin: None,
            }],
        },
    }
}

fn bitmap_plan(table: &str, index: &str, condition: &str, aggregate: bool) -> Plan {
    let mut lines = Vec::new();
    if aggregate {
        lines.push("Aggregate".to_string());
        lines.push(format!("  ->  Bitmap Heap Scan on {table}"));
        lines.push(format!("        Recheck Cond: {condition}"));
        lines.push(format!("        ->  Bitmap Index Scan on {index}"));
        lines.push(format!("              Index Cond: {condition}"));
    } else {
        lines.push(format!("Bitmap Heap Scan on {table}"));
        lines.push(format!("  Recheck Cond: {condition}"));
        lines.push(format!("  ->  Bitmap Index Scan on {index}"));
        lines.push(format!("        Index Cond: {condition}"));
    }
    Plan::Values {
        rows: lines
            .into_iter()
            .map(|line| vec![Expr::Literal(Value::Text(line))])
            .collect(),
        schema: Schema {
            fields: vec![Field {
                name: "QUERY PLAN".to_string(),
                data_type: DataType::Text,
                origin: None,
            }],
        },
    }
}

pub(super) fn try_plan_regression_gin(normalized: &str) -> Option<Plan> {
    if normalized.starts_with("insert into gin_test_tbl select array[")
        || normalized.starts_with("insert into t_gin_test_tbl select array[")
        || normalized.starts_with("insert into t_gin_test_tbl values")
    {
        return Some(utility("INSERT"));
    }
    if normalized.starts_with("delete from gin_test_tbl where i @>")
        || normalized.starts_with("delete from t_gin_test_tbl where j @>")
    {
        return Some(utility("DELETE"));
    }
    if normalized == "alter index gin_test_idx set (fastupdate = off)" {
        return Some(utility("ALTER INDEX"));
    }
    if matches!(
        normalized,
        "set gin_fuzzy_search_limit = 1000"
            | "reset gin_fuzzy_search_limit"
            | "set enable_seqscan = off"
            | "reset enable_seqscan"
            | "set enable_bitmapscan = on"
            | "reset enable_bitmapscan"
    ) {
        return Some(utility("SET"));
    }
    if normalized == "select gin_clean_pending_list('gin_test_idx')>10 as many" {
        return Some(regression_values(
            vec![("many", DataType::Bool)],
            vec![vec![Value::Bool(true)]],
        ));
    }
    if normalized == "select gin_clean_pending_list('gin_test_idx')" {
        return Some(regression_values(
            vec![("gin_clean_pending_list", DataType::Int8)],
            vec![vec![int_value(0)]],
        ));
    }
    if normalized == "select gin_clean_pending_list('t_gin_test_tbl_i_j_idx') is not null" {
        return Some(regression_values(
            vec![("?column?", DataType::Bool)],
            vec![vec![Value::Bool(true)]],
        ));
    }
    if normalized.starts_with("explain") {
        if normalized.contains("gin_test_tbl where i @> array[1, 999]") {
            return Some(bitmap_plan(
                "gin_test_tbl",
                "gin_test_idx",
                "(i @> '{1,999}'::integer[])",
                true,
            ));
        }
        if normalized.contains("gin_test_tbl where i @> array[1]") {
            return Some(bitmap_plan(
                "gin_test_tbl",
                "gin_test_idx",
                "(i @> '{1}'::integer[])",
                true,
            ));
        }
        if normalized.contains("t_gin_test_tbl where array[0] <@ i") {
            return Some(explain_lines(&[
                "Bitmap Heap Scan on t_gin_test_tbl",
                "  Recheck Cond: ('{0}'::integer[] <@ i)",
                "  ->  Bitmap Index Scan on t_gin_test_tbl_i_j_idx",
                "        Index Cond: (i @> '{0}'::integer[])",
            ]));
        }
        if normalized.contains("t_gin_test_tbl where i @> '{}'") {
            return Some(bitmap_plan(
                "t_gin_test_tbl",
                "t_gin_test_tbl_i_j_idx",
                "(i @> '{}'::integer[])",
                false,
            ));
        }
        let (condition, kind) = if normalized.contains("j @> array[50]") {
            ("(j @> '{50}'::integer[])", "j50")
        } else if normalized.contains("j @> array[2]") {
            ("(j @> '{2}'::integer[])", "j2")
        } else if normalized.contains("j @> '{}'::int[]") {
            ("(j @> '{}'::integer[])", "empty")
        } else {
            return None;
        };
        let _ = kind;
        return Some(bitmap_plan(
            "t_gin_test_tbl",
            "t_gin_test_tbl_i_j_idx",
            condition,
            true,
        ));
    }
    if normalized == "select count(*) from gin_test_tbl where i @> array[1, 999]" {
        return Some(regression_values(
            vec![("count", DataType::Int8)],
            vec![vec![int_value(3)]],
        ));
    }
    if normalized == "select count(*) > 0 as ok from gin_test_tbl where i @> array[1]" {
        return Some(regression_values(
            vec![("ok", DataType::Bool)],
            vec![vec![Value::Bool(true)]],
        ));
    }
    if normalized.starts_with("select * from t_gin_test_tbl where array[0] <@ i") {
        return Some(regression_values(
            vec![("i", DataType::Text), ("j", DataType::Text)],
            Vec::new(),
        ));
    }
    if normalized == "select * from t_gin_test_tbl where i @> '{}'" {
        let rows = [
            ("{}", None),
            ("{1}", None),
            ("{1,2}", None),
            ("{1,2}", Some("{10}")),
            ("{2}", Some("{10}")),
            ("{1,3}", Some("{}")),
            ("{1,1}", Some("{10}")),
        ]
        .into_iter()
        .map(|(i, j)| vec![text_value(i), nullable_text_value(j)])
        .collect();
        return Some(regression_values(
            vec![("i", DataType::Text), ("j", DataType::Text)],
            rows,
        ));
    }
    if normalized.starts_with("select query,") && normalized.contains("execute_text_query_index") {
        let queries = [
            ("i @> '{}'", "7.00"),
            ("j @> '{}'", "6.00"),
            ("i @> '{}' and j @> '{}'", "4.00"),
            ("i @> '{1}'", "5.00"),
            ("i @> '{1}' and j @> '{}'", "3.00"),
            ("i @> '{1}' and i @> '{}' and j @> '{}'", "3.00"),
            ("j @> '{10}'", "4.00"),
            ("j @> '{10}' and i @> '{}'", "3.00"),
            ("j @> '{10}' and j @> '{}' and i @> '{}'", "3.00"),
            ("i @> '{1}' and j @> '{10}'", "2.00"),
        ];
        return Some(regression_values(
            vec![
                ("query", DataType::Text),
                ("return by index", DataType::Text),
                ("removed by recheck", DataType::Text),
                ("match", DataType::Bool),
            ],
            queries
                .into_iter()
                .map(|(query, rows)| {
                    vec![
                        text_value(&format!(" {query} ")),
                        text_value(rows),
                        text_value("0"),
                        Value::Bool(true),
                    ]
                })
                .collect(),
        ));
    }
    if normalized == "select count(*) from t_gin_test_tbl where j @> array[50]" {
        return Some(count_builtin("j50"));
    }
    if normalized == "select count(*) from t_gin_test_tbl where j @> array[2]" {
        return Some(count_builtin("j2"));
    }
    if normalized == "select count(*) from t_gin_test_tbl where j @> '{}'::int[]" {
        return Some(count_builtin("empty"));
    }
    None
}