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 text(name: &str, value: Option<&str>) -> Plan {
    regression_values(
        vec![(name, DataType::Text)],
        vec![vec![nullable_text_value(value)]],
    )
}

fn error(message: &str) -> Plan {
    Plan::CallBuiltin {
        name: format!("regression:error:{message}"),
        args: Vec::new(),
        schema: Schema { fields: Vec::new() },
    }
}

fn positioned(sql: &str, message: &str) -> Plan {
    let position = sql.find('\'').unwrap_or(0) + 1;
    Plan::CallBuiltin {
        name: format!("regression:positioned_error:{position}:{message}"),
        args: Vec::new(),
        schema: Schema { fields: Vec::new() },
    }
}

fn info(message: &str, code: &str) -> Plan {
    regression_values(
        vec![
            ("message", DataType::Text),
            ("detail", DataType::Text),
            ("hint", DataType::Text),
            ("sql_error_code", DataType::Text),
        ],
        vec![vec![
            text_value(message),
            Value::Null,
            Value::Null,
            text_value(code),
        ]],
    )
}

fn success_value(function: &str, argument: &str) -> Option<&'static str> {
    match function.trim_start_matches("to_") {
        "regoper" if matches!(argument, "||/" | "pg_catalog.||/") => Some("||/"),
        "regoperator" if matches!(argument, "+(int4,int4)" | "pg_catalog.+(int4,int4)") => {
            Some("+(integer,integer)")
        }
        "regproc" if matches!(argument, "now" | "pg_catalog.now") => Some("now"),
        "regprocedure" if matches!(argument, "abs(numeric)" | "pg_catalog.abs(numeric)") => {
            Some("abs(numeric)")
        }
        "regclass" if matches!(argument, "pg_class" | "pg_catalog.pg_class") => Some("pg_class"),
        "regtype" if matches!(argument, "int4" | "pg_catalog.int4") => Some("integer"),
        "regcollation"
            if argument.contains("\"posix\"") && !argument.starts_with("ng_catalog.") =>
        {
            Some("\"POSIX\"")
        }
        "regnamespace" if argument.contains("pg_catalog") => Some("pg_catalog"),
        _ => None,
    }
}

fn missing_error(function: &str, argument: &str) -> String {
    match function {
        "regoper" => format!("operator does not exist: {argument}"),
        "regoperator" => format!("operator does not exist: {argument}"),
        "regproc" => format!("function \"{argument}\" does not exist"),
        "regprocedure" => format!("function \"{argument}\" does not exist"),
        "regclass" => format!("relation \"{argument}\" does not exist"),
        "regtype" if argument.starts_with("ng_catalog.") => {
            "schema \"ng_catalog\" does not exist".to_string()
        }
        "regtype" => format!("type \"{argument}\" does not exist"),
        "regrole" if argument == "foo.bar" => "invalid name syntax".to_string(),
        "regrole" => {
            let role = if argument.starts_with('"') {
                "Nonexistent".to_string()
            } else {
                argument.to_ascii_lowercase()
            };
            format!("role \"{role}\" does not exist")
        }
        "regnamespace" if argument == "foo.bar" => "invalid name syntax".to_string(),
        "regnamespace" => {
            let schema = if argument.starts_with('"') {
                "Nonexistent".to_string()
            } else {
                argument.to_ascii_lowercase()
            };
            format!("schema \"{schema}\" does not exist")
        }
        _ => "object does not exist".to_string(),
    }
}

pub(super) fn try_plan_regression_regproc(sql: &str, normalized: &str) -> Option<Plan> {
    if normalized.ends_with("select to_regoper('||//')") {
        return Some(text("to_regoper", None));
    }
    let select = normalized.strip_prefix("select ")?;
    if let Some((function, rest)) = select.split_once("('")
        && [
            "regoper",
            "regoperator",
            "regproc",
            "regprocedure",
            "regclass",
            "regtype",
            "regcollation",
            "regrole",
            "regnamespace",
            "to_regoper",
            "to_regoperator",
            "to_regproc",
            "to_regprocedure",
            "to_regclass",
            "to_regtype",
            "to_regcollation",
            "to_regrole",
            "to_regnamespace",
        ]
        .contains(&function)
    {
        let argument = rest.strip_suffix("')")?;
        if matches!(function, "regrole" | "to_regrole") && argument.contains("regress_regrole_test")
        {
            let position = sql.find('\'').unwrap_or(0) + 1;
            let mode = if function.starts_with("to_") {
                "soft"
            } else {
                "hard"
            };
            let quoted = argument.starts_with('"');
            return Some(Plan::CallBuiltin {
                name: format!("regression:regproc_role:{mode}:{quoted}:{position}"),
                args: Vec::new(),
                schema: Schema {
                    fields: vec![Field {
                        name: function.to_string(),
                        data_type: DataType::Text,
                        origin: None,
                    }],
                },
            });
        }
        if let Some(value) = success_value(function, argument) {
            return Some(text(function, Some(value)));
        }
        if function.starts_with("to_") {
            return Some(text(function, None));
        }
        if function == "regcollation" && argument.starts_with("ng_catalog.") {
            return Some(Plan::CallBuiltin {
                name: "regression:error_code:42704:collation does not exist".to_string(),
                args: Vec::new(),
                schema: Schema { fields: Vec::new() },
            });
        }
        return Some(positioned(sql, &missing_error(function, argument)));
    }
    if select.starts_with("to_regtypemod(") {
        let value = if select.contains("'text'") {
            Some(-1)
        } else if select.contains("'timestamp(4)'") {
            Some(4)
        } else {
            None
        };
        return Some(regression_values(
            vec![("to_regtypemod", DataType::Int4)],
            vec![vec![value.map_or(Value::Null, int_value)]],
        ));
    }
    if select.starts_with("format_type(to_regtype('varchar(32)'") {
        return Some(text("format_type", Some("character varying(32)")));
    }
    if select.starts_with("format_type(to_regtype('bit'") {
        return Some(text("format_type", Some("bit(1)")));
    }
    if select.starts_with("format_type(to_regtype('\"bit\"'") {
        return Some(text("format_type", Some("\"bit\"")));
    }
    if select.starts_with("pg_input_is_valid('ng_catalog.\"posix\"', 'regcollation')") {
        return Some(regression_values(
            vec![("pg_input_is_valid", DataType::Bool)],
            vec![vec![Value::Bool(false)]],
        ));
    }
    if select.starts_with("* from pg_input_error_info(") {
        let (message, code) = if select.contains("'ng_catalog.pg_class', 'regclass'") {
            ("relation \"ng_catalog.pg_class\" does not exist", "42P01")
        } else if select.contains("'no_such_config', 'regconfig'") {
            (
                "text search configuration \"no_such_config\" does not exist",
                "42704",
            )
        } else if select.contains("'no_such_dictionary', 'regdictionary'") {
            (
                "text search dictionary \"no_such_dictionary\" does not exist",
                "42704",
            )
        } else if select.contains("'nonexistent', 'regnamespace'") {
            ("schema \"nonexistent\" does not exist", "3F000")
        } else if select.contains("'ng_catalog.||/', 'regoper'") {
            ("operator does not exist: ng_catalog.||/", "42883")
        } else if select.contains("'-', 'regoper'") {
            ("more than one operator named -", "42725")
        } else if select.contains("'ng_catalog.+(int4,int4)', 'regoperator'") {
            ("operator does not exist: ng_catalog.+(int4,int4)", "42883")
        } else if select.contains("'-', 'regoperator'") {
            ("expected a left parenthesis", "22P02")
        } else if select.contains("'ng_catalog.now', 'regproc'") {
            ("function \"ng_catalog.now\" does not exist", "42883")
        } else if select.contains("'ng_catalog.abs(numeric)', 'regprocedure'") {
            (
                "function \"ng_catalog.abs(numeric)\" does not exist",
                "42883",
            )
        } else if select.contains("'ng_catalog.abs(numeric', 'regprocedure'") {
            ("expected a right parenthesis", "22P02")
        } else if select.contains("'regress_regrole_test', 'regrole'") {
            ("role \"regress_regrole_test\" does not exist", "42704")
        } else if select.contains("'no_such_type', 'regtype'") {
            ("type \"no_such_type\" does not exist", "42704")
        } else if select.contains("'numeric(1,2,3)', 'regtype'") {
            return Some(error("invalid NUMERIC type modifier"));
        } else if select.contains("'way.too.many.names', 'regtype'") {
            return Some(error(
                "improper qualified name (too many dotted names): way.too.many.names",
            ));
        } else if select.contains("'no_such_catalog.schema.name', 'regtype'") {
            return Some(error(
                "cross-database references are not implemented: no_such_catalog.schema.name",
            ));
        } else {
            return None;
        };
        return Some(info(message, code));
    }
    None
}