Skip to main content

sntl_migrate/
migration.rs

1use std::fmt;
2use std::str::FromStr;
3
4use crate::error::Error;
5
6/// Transaction mode for a migration file.
7///
8/// Default `PerMigration` wraps each migration in `BEGIN/COMMIT`. Migrations
9/// with non-transactional DDL (`CREATE INDEX CONCURRENTLY`, `VACUUM`, etc.)
10/// can declare `up.notx.sql` instead of `up.sql`, which maps to `None`.
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
12pub enum TxMode {
13    PerMigration,
14    None,
15}
16
17/// A migration's identifier — `YYYYMMDD_HHMMSS_<snake_case_name>`.
18///
19/// Lexicographic ordering matches chronological ordering since the timestamp
20/// is fixed-width.
21#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
22pub struct Version(String);
23
24impl Version {
25    pub fn as_str(&self) -> &str {
26        &self.0
27    }
28
29    pub fn timestamp(&self) -> &str {
30        &self.0[..15]
31    }
32
33    pub fn name(&self) -> &str {
34        &self.0[16..]
35    }
36}
37
38impl fmt::Display for Version {
39    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
40        f.write_str(&self.0)
41    }
42}
43
44impl FromStr for Version {
45    type Err = Error;
46
47    fn from_str(s: &str) -> Result<Self, Self::Err> {
48        if s.len() < 17 {
49            return Err(Error::InvalidName {
50                name: s.to_string(),
51            });
52        }
53        let date = &s[0..8];
54        let sep1 = &s[8..9];
55        let time = &s[9..15];
56        let sep2 = &s[15..16];
57        if !date.chars().all(|c| c.is_ascii_digit())
58            || sep1 != "_"
59            || !time.chars().all(|c| c.is_ascii_digit())
60            || sep2 != "_"
61        {
62            return Err(Error::InvalidName {
63                name: s.to_string(),
64            });
65        }
66        let name = &s[16..];
67        if name.is_empty()
68            || !name
69                .chars()
70                .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_')
71        {
72            return Err(Error::InvalidName {
73                name: s.to_string(),
74            });
75        }
76        Ok(Self(s.to_string()))
77    }
78}
79
80/// A single discovered migration: identifier, SQL text, and tx mode.
81#[derive(Debug, Clone)]
82pub struct Migration {
83    pub version: Version,
84    pub sql: String,
85    pub tx_mode: TxMode,
86}