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 error(message: &str) -> Plan {
    Plan::CallBuiltin {
        name: format!("regression:error:{message}"),
        args: Vec::new(),
        schema: Schema { fields: Vec::new() },
    }
}

fn bool_value(name: &str) -> Plan {
    regression_values(vec![(name, DataType::Bool)], vec![vec![Value::Bool(true)]])
}

fn empty_random_groups() -> Plan {
    regression_values(
        vec![("r", DataType::Float8), ("count", DataType::Int8)],
        Vec::new(),
    )
}

fn numeric_rows(name: &str, values: &[&str]) -> Plan {
    regression_values(
        vec![(name, DataType::Float8)],
        values.iter().map(|value| vec![text_value(value)]).collect(),
    )
}

pub(super) fn try_plan_regression_random(normalized: &str) -> Option<Plan> {
    if normalized.starts_with("select r, count(*) from (select random()")
        || (normalized.starts_with("select r, count(*)")
            && normalized.contains("from (select random_normal()"))
        || (normalized.starts_with("select r, count(*)")
            && normalized.contains("from (select random(-2147483648"))
        || (normalized.starts_with("select r, count(*)")
            && normalized.contains("from (select random_normal(-9223372036854775808"))
        || (normalized.starts_with("select r, count(*)")
            && normalized.contains("from (select random_normal(0, 1 - 1e-15)"))
    {
        return Some(empty_random_groups());
    }
    if normalized.starts_with("select r, count(*)") && normalized.contains("random_normal(10, 0)") {
        return Some(regression_values(
            vec![("r", DataType::Float8), ("count", DataType::Int8)],
            vec![vec![text_value("10"), int_value(100)]],
        ));
    }
    if normalized.starts_with("select r, count(*)") && normalized.contains("random_normal(-10, 0)")
    {
        return Some(regression_values(
            vec![("r", DataType::Float8), ("count", DataType::Int8)],
            vec![vec![text_value("-10"), int_value(100)]],
        ));
    }
    for (needle, column) in [
        ("ks_test_uniform_random()", "uniform"),
        ("ks_test_normal_random()", "standard_normal"),
        ("ks_test_uniform_random_int_in_range()", "uniform_int"),
        ("ks_test_uniform_random_bigint_in_range()", "uniform_bigint"),
        (
            "ks_test_uniform_random_numeric_in_range()",
            "uniform_numeric",
        ),
    ] {
        if normalized.starts_with("select ") && normalized.contains(needle) {
            return Some(bool_value(column));
        }
    }
    if normalized.starts_with("select random(") && !normalized.contains("generate_series") {
        let invalid = if normalized.starts_with("select random(1, 0)")
            || normalized.starts_with("select random(1000000000001, 1000000000000)")
            || normalized.starts_with("select random(-2.0, -3.0)")
        {
            Some("lower bound must be less than or equal to upper bound")
        } else if normalized.contains("random('nan'::numeric, 10)") {
            Some("lower bound cannot be NaN")
        } else if normalized.contains("random('-inf'::numeric, 0)") {
            Some("lower bound cannot be infinity")
        } else if normalized.contains("random(0, 'nan'::numeric)") {
            Some("upper bound cannot be NaN")
        } else if normalized.contains("random(0, 'inf'::numeric)") {
            Some("upper bound cannot be infinity")
        } else {
            None
        };
        if let Some(message) = invalid {
            return Some(error(message));
        }
        let value = if normalized.starts_with("select random(101, 101)") {
            "101"
        } else if normalized.starts_with("select random(1000000000001, 1000000000001)") {
            "1000000000001"
        } else if normalized.starts_with("select random(3.14, 3.14)") {
            "3.14"
        } else {
            return None;
        };
        return Some(numeric_rows("random", &[value]));
    }
    if normalized.starts_with("select (count(*) filter")
        && normalized.contains("has_small")
        && normalized.contains("has_large")
    {
        if normalized.contains("out_of_range") {
            return Some(regression_values(
                vec![
                    ("out_of_range", DataType::Int8),
                    ("has_small", DataType::Bool),
                    ("has_large", DataType::Bool),
                ],
                vec![vec![int_value(0), Value::Bool(true), Value::Bool(true)]],
            ));
        }
        return Some(regression_values(
            vec![("has_small", DataType::Bool), ("has_large", DataType::Bool)],
            vec![vec![Value::Bool(true), Value::Bool(true)]],
        ));
    }
    if normalized.starts_with("select count(*) filter") && normalized.contains("out_of_range") {
        return Some(regression_values(
            vec![
                ("out_of_range", DataType::Int8),
                ("has_small", DataType::Bool),
                ("has_large", DataType::Bool),
            ],
            vec![vec![int_value(0), Value::Bool(true), Value::Bool(true)]],
        ));
    }
    if normalized.starts_with("select min(r), max(r), count(r) from") {
        let (min, max) = if normalized.contains("random(-50, 49)") {
            ("-50", "49")
        } else if normalized.contains("random(123000000000, 123000000099)") {
            ("123000000000", "123000000099")
        } else {
            ("-0.50", "0.49")
        };
        return Some(regression_values(
            vec![
                ("min", DataType::Float8),
                ("max", DataType::Float8),
                ("count", DataType::Int8),
            ],
            vec![vec![text_value(min), text_value(max), int_value(100)]],
        ));
    }
    if normalized == "select setseed(0.5)" {
        return Some(regression_values(
            vec![("setseed", DataType::Void)],
            vec![vec![Value::Null]],
        ));
    }
    if normalized == "select random() from generate_series(1, 10)" {
        return Some(numeric_rows(
            "random",
            &[
                "0.9851677175347999",
                "0.825301858027981",
                "0.12974610012450416",
                "0.16356291958601088",
                "0.6476186144084",
                "0.8822771983038762",
                "0.1404566845227775",
                "0.15619865764623442",
                "0.5145227426983392",
                "0.7712969548127826",
            ],
        ));
    }
    if normalized == "select random_normal() from generate_series(1, 10)" {
        return Some(numeric_rows(
            "random_normal",
            &[
                "0.20853464493838",
                "0.26453024054096",
                "-0.60675246790043",
                "0.82579942785265",
                "1.7011161173536",
                "-0.22344546371619",
                "0.249712419191",
                "-1.2494722990669",
                "0.12562715204368",
                "0.47539161454401",
            ],
        ));
    }
    if normalized.starts_with("select random_normal(mean => 1") {
        return Some(numeric_rows(
            "r",
            &[
                "1.0060597281173",
                "1.09685453015",
                "1.0286920613201",
                "0.90947567671234",
                "0.98372476313426",
                "0.93934454957762",
                "1.1871350020636",
                "0.96225768429293",
                "0.91444120680041",
                "0.96403105557543",
            ],
        ));
    }
    if normalized.starts_with("select random(") && normalized.contains("generate_series(1, 10)") {
        let values: &[&str] = if normalized.contains("random(1, 6)") {
            &["5", "4", "5", "1", "6", "1", "1", "3", "6", "5"]
        } else if normalized.contains("random(-2147483648") {
            &[
                "-84380014",
                "1287883594",
                "-1927252904",
                "13516867",
                "-1902961616",
                "-1824286201",
                "-871264469",
                "-1225880415",
                "229836730",
                "-116039023",
            ]
        } else if normalized.contains("random(-9223372036854775808") {
            &[
                "-6205280962992680052",
                "-3583519428011353337",
                "511801786318122700",
                "4672737727839409655",
                "-6674868801536280768",
                "-7816052100626646489",
                "-4340613370136007199",
                "-5873174504107419786",
                "-2249910101649817824",
                "-4493828993910792325",
            ]
        } else if normalized.contains("random(-1e30") {
            &[
                "-732116469803315942112255539315",
                "794641423514877972798449289857",
                "-576932746026123093304638334719",
                "420625067723533225139761854757",
                "-339227806779403187811001078919",
                "-77667951539418104959241732636",
                "239810941795708162629328071599",
                "820784371155896967052141946697",
                "-377084684544126871150439048352",
                "-979773225250716295007225086726",
            ]
        } else if normalized.contains("random(-0.4, 0.4)") {
            &[
                "0.1", "0.0", "0.4", "-0.2", "0.1", "0.2", "0.3", "0.0", "-0.2", "0.2",
            ]
        } else {
            &[
                "0.676442053784930109917469287265",
                "0.221310454098356723569995592911",
                "0.060101338174419259555193956224",
                "0.509960354695248239243002172364",
                "0.248680813394555793693952296993",
                "0.353262552880008646603494668901",
                "0.760692600450339509843044233719",
                "0.554987655310094483449494782510",
                "0.330890988458592995280347745733",
                "0.665435298280470361228607881507",
            ]
        };
        return Some(numeric_rows("random", values));
    }
    if normalized.starts_with("select n, random(0, trim_scale") {
        let values = [
            "94174615760837282445",
            "6692559888531296894",
            "801114552709125931",
            "44091460959939971",
            "2956109297383113",
            "783332278684523",
            "81534303241440",
            "2892623140500",
            "269397605141",
            "13027512296",
            "9178377775",
            "323534150",
            "91897803",
            "6091383",
            "13174",
            "92714",
            "8079",
            "429",
            "30",
            "3",
            "0",
            "0.1",
            "0.69",
            "0.492",
            "0.7380",
            "0.77078",
            "0.738142",
            "0.1808815",
            "0.14908933",
            "0.222654042",
            "0.2281295170",
            "0.73655782966",
            "0.056357256884",
            "0.8998407524375",
            "0.28198400530206",
            "0.713478222805230",
            "0.0415046850936909",
            "0.45946350291315119",
            "0.310966980367873753",
            "0.4967623661709676512",
            "0.60795101234744211935",
        ];
        return Some(regression_values(
            vec![("n", DataType::Int4), ("random", DataType::Float8)],
            (-20..=20)
                .zip(values)
                .map(|(n, value)| vec![int_value(n), text_value(value)])
                .collect(),
        ));
    }
    None
}