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