Skip to main content

pgevolve_core/catalog/
version.rs

1//! Postgres major-version detection.
2
3use crate::catalog::error::CatalogError;
4use crate::catalog::{CatalogQuerier, CatalogQuery};
5
6/// Major Postgres versions supported by the v0.1 catalog reader.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
8pub enum PgVersion {
9    /// Postgres 14.
10    Pg14,
11    /// Postgres 15.
12    Pg15,
13    /// Postgres 16.
14    Pg16,
15    /// Postgres 17.
16    Pg17,
17}
18
19impl PgVersion {
20    /// Detect the major version from `server_version_num`.
21    pub fn detect(querier: &dyn CatalogQuerier) -> Result<Self, CatalogError> {
22        let rows = querier.fetch(CatalogQuery::PgVersion, &[])?;
23        let row = rows.into_iter().next().ok_or(CatalogError::MissingResult {
24            query: CatalogQuery::PgVersion,
25        })?;
26        let n = row.get_int(CatalogQuery::PgVersion, "server_version_num")?;
27        Self::from_server_version_num(n)
28    }
29
30    /// Convert a `server_version_num` integer (e.g., `160000`) into the major
31    /// version. Returns [`CatalogError::UnsupportedPgVersion`] for anything
32    /// outside the 14–17 range.
33    pub fn from_server_version_num(n: i64) -> Result<Self, CatalogError> {
34        let major = n / 10_000;
35        match major {
36            14 => Ok(Self::Pg14),
37            15 => Ok(Self::Pg15),
38            16 => Ok(Self::Pg16),
39            17 => Ok(Self::Pg17),
40            v => Err(CatalogError::UnsupportedPgVersion(
41                v.try_into().unwrap_or(0),
42            )),
43        }
44    }
45
46    /// Display this version as a short tag (e.g., `pg16`).
47    #[must_use]
48    pub const fn as_tag(self) -> &'static str {
49        match self {
50            Self::Pg14 => "pg14",
51            Self::Pg15 => "pg15",
52            Self::Pg16 => "pg16",
53            Self::Pg17 => "pg17",
54        }
55    }
56
57    /// Major-version integer (14, 15, 16, 17).
58    #[must_use]
59    pub const fn major(self) -> u32 {
60        match self {
61            Self::Pg14 => 14,
62            Self::Pg15 => 15,
63            Self::Pg16 => 16,
64            Self::Pg17 => 17,
65        }
66    }
67}
68
69#[cfg(test)]
70mod tests {
71    use super::*;
72    use crate::catalog::rows::{Row, Value};
73
74    struct MockSingle(i64);
75    impl CatalogQuerier for MockSingle {
76        fn fetch(&self, _: CatalogQuery, _: &[&str]) -> Result<Vec<Row>, CatalogError> {
77            Ok(vec![
78                Row::new().with("server_version_num", Value::Integer(self.0)),
79            ])
80        }
81    }
82
83    #[test]
84    fn detects_each_supported_major() {
85        for (n, v) in [
86            (140_005, PgVersion::Pg14),
87            (150_002, PgVersion::Pg15),
88            (160_000, PgVersion::Pg16),
89            (170_001, PgVersion::Pg17),
90        ] {
91            assert_eq!(PgVersion::detect(&MockSingle(n)).unwrap(), v);
92        }
93    }
94
95    #[test]
96    fn rejects_unsupported() {
97        let err = PgVersion::detect(&MockSingle(130_004)).unwrap_err();
98        assert!(matches!(err, CatalogError::UnsupportedPgVersion(13)));
99        let err2 = PgVersion::detect(&MockSingle(180_000)).unwrap_err();
100        assert!(matches!(err2, CatalogError::UnsupportedPgVersion(18)));
101    }
102}