use crate::pg::pool::DjogiPool;
use crate::{DbError, DjogiError};
pub const MINIMUM_PG_MAJOR: u32 = 18;
#[derive(Debug, Clone, Copy)]
pub struct PreflightReport {
pub server_version_num: u32,
pub major: u32,
pub minor: u32,
}
fn parse_version_num(num: u32) -> (u32, u32) {
(num / 10000, (num % 10000) / 100)
}
fn evaluate_version(version_num: u32) -> Result<PreflightReport, DjogiError> {
let (major, minor) = parse_version_num(version_num);
if major < MINIMUM_PG_MAJOR {
return Err(DjogiError::unsupported_postgres_version(
major,
minor,
MINIMUM_PG_MAJOR,
));
}
Ok(PreflightReport {
server_version_num: version_num,
major,
minor,
})
}
pub async fn check_postgres_version(pool: &DjogiPool) -> Result<PreflightReport, DjogiError> {
let version_num = query_server_version_num(pool).await?;
evaluate_version(version_num)
}
#[allow(clippy::disallowed_methods)]
async fn query_server_version_num(pool: &DjogiPool) -> Result<u32, DjogiError> {
pool.with_client(|client| {
Box::pin(async move {
let row = client
.query_one("SHOW server_version_num", &[])
.await
.map_err(|e| DjogiError::Db(DbError::from(e)))?;
let version_str: &str = row.get(0);
let num: u32 = version_str.parse().map_err(|_| {
DjogiError::Db(DbError::other(format!(
"preflight: server_version_num returned \
non-integer value: {version_str:?}"
)))
})?;
Ok(num)
})
})
.await
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_version_num_pg18_0() {
let (major, minor) = parse_version_num(180000);
assert_eq!(major, 18);
assert_eq!(minor, 0);
}
#[test]
fn parse_version_num_pg18_3() {
let (major, minor) = parse_version_num(180300);
assert_eq!(major, 18);
assert_eq!(minor, 3);
}
#[test]
fn parse_version_num_pg17_4() {
let (major, minor) = parse_version_num(170400);
assert_eq!(major, 17);
assert_eq!(minor, 4);
}
#[test]
fn parse_version_num_pg16_1() {
let (major, minor) = parse_version_num(160100);
assert_eq!(major, 16);
assert_eq!(minor, 1);
}
#[test]
fn parse_version_num_pg16_0() {
let (major, minor) = parse_version_num(160000);
assert_eq!(major, 16);
assert_eq!(minor, 0);
}
#[test]
fn parse_version_num_pg15_8() {
let (major, minor) = parse_version_num(150800);
assert_eq!(major, 15);
assert_eq!(minor, 8);
}
#[test]
fn parse_version_num_pg19_1() {
let (major, minor) = parse_version_num(190100);
assert_eq!(major, 19);
assert_eq!(minor, 1);
}
#[test]
fn evaluate_version_below_minimum_returns_unsupported_error() {
for &(num, expected_major, expected_minor) in &[
(170400u32, 17, 4),
(160100, 16, 1),
(150800, 15, 8),
(140000, 14, 0),
(100000, 10, 0),
] {
let err =
evaluate_version(num).expect_err(&format!("version_num {num} should be rejected"));
let msg = err.to_string();
match err {
DjogiError::UnsupportedPostgresVersion {
detected_major,
detected_minor,
minimum_major,
..
} => {
assert_eq!(detected_major, expected_major, "detected_major for {num}");
assert_eq!(detected_minor, expected_minor, "detected_minor for {num}");
assert_eq!(minimum_major, MINIMUM_PG_MAJOR, "minimum_major for {num}");
assert!(
msg.contains(&expected_major.to_string()),
"display must name detected major for {num}, got: {msg}"
);
assert!(
msg.contains(&MINIMUM_PG_MAJOR.to_string()),
"display must name minimum for {num}, got: {msg}"
);
assert!(
msg.contains("Upgrade"),
"display must suggest upgrade for {num}, got: {msg}"
);
}
other => panic!("expected UnsupportedPostgresVersion for {num}, got: {other:?}"),
}
}
}
#[test]
fn evaluate_version_at_minimum_returns_report() {
let report = evaluate_version(180000).expect("PG 18.0 should be accepted");
assert_eq!(report.major, 18);
assert_eq!(report.minor, 0);
assert_eq!(report.server_version_num, 180000);
}
#[test]
fn evaluate_version_above_minimum_returns_report() {
for &num in &[180300u32, 190000, 190100, 200000] {
let report = evaluate_version(num)
.unwrap_or_else(|_| panic!("version_num {num} should be accepted"));
assert!(
report.major >= MINIMUM_PG_MAJOR,
"major for {num} should be >= {MINIMUM_PG_MAJOR}"
);
assert_eq!(
report.server_version_num, num,
"server_version_num should round-trip for {num}"
);
}
}
#[test]
fn version_below_minimum_is_rejected() {
for &num in &[170400u32, 160100, 150800, 140000, 100000] {
let (major, _minor) = parse_version_num(num);
assert!(
major < MINIMUM_PG_MAJOR,
"version_num {num} (major {major}) should be below minimum {MINIMUM_PG_MAJOR}"
);
}
}
#[test]
fn version_at_minimum_is_accepted() {
let (major, _) = parse_version_num(180000);
assert!(major >= MINIMUM_PG_MAJOR, "PG 18.0 should be accepted");
}
#[test]
fn version_above_minimum_is_accepted() {
for &num in &[180300u32, 190000, 190100, 200000] {
let (major, _) = parse_version_num(num);
assert!(
major >= MINIMUM_PG_MAJOR,
"version_num {num} (major {major}) should be accepted"
);
}
}
}