toasty_core/schema/db/
migration.rs1#[derive(Debug)]
16pub enum Migration {
17 Sql(String),
19}
20
21impl Migration {
22 pub fn new_sql(sql: String) -> Self {
24 Migration::Sql(sql)
25 }
26
27 pub fn new_sql_with_breakpoints<S: AsRef<str>>(statements: &[S]) -> Self {
30 let sql = statements
31 .iter()
32 .map(|s| s.as_ref())
33 .collect::<Vec<_>>()
34 .join(&format!("\n{BREAKPOINT}\n"));
35 Migration::Sql(sql)
36 }
37
38 pub fn statements(&self) -> Vec<&str> {
40 match self {
41 Migration::Sql(sql) => split_on_breakpoints(sql),
42 }
43 }
44}
45
46const BREAKPOINT: &str = "-- #[toasty::breakpoint]";
48
49fn split_on_breakpoints(sql: &str) -> Vec<&str> {
55 let mut statements = Vec::new();
56 let mut statement_start = 0;
57 let mut line_start = 0;
58
59 for line in sql.split_inclusive('\n') {
60 let line_end = line_start + line.len();
61
62 if line.trim() == BREAKPOINT {
63 statements.push(strip_line_ending(&sql[statement_start..line_start]));
64 statement_start = line_end;
65 }
66
67 line_start = line_end;
68 }
69
70 statements.push(&sql[statement_start..]);
71 statements.retain(|s| !s.trim().is_empty());
72 statements
73}
74
75fn strip_line_ending(s: &str) -> &str {
76 s.strip_suffix("\r\n")
77 .or_else(|| s.strip_suffix('\n'))
78 .unwrap_or(s)
79}
80
81pub struct AppliedMigration {
94 id: u64,
95}
96
97impl AppliedMigration {
98 pub fn new(id: u64) -> Self {
100 Self { id }
101 }
102
103 pub fn id(&self) -> u64 {
105 self.id
106 }
107}
108
109#[cfg(test)]
110mod tests {
111 use super::*;
112
113 const CREATE_TABLE: &str = "CREATE TABLE users (id INTEGER PRIMARY KEY);";
114 const CREATE_INDEX: &str = "CREATE INDEX idx ON users (id);";
115
116 #[test]
117 fn generated_breakpoints_round_trip() {
118 let m = Migration::new_sql_with_breakpoints(&[CREATE_TABLE, CREATE_INDEX]);
119 let Migration::Sql(sql) = &m;
120 assert_eq!(
121 sql,
122 "CREATE TABLE users (id INTEGER PRIMARY KEY);\n-- #[toasty::breakpoint]\nCREATE INDEX idx ON users (id);"
123 );
124 assert_eq!(m.statements(), vec![CREATE_TABLE, CREATE_INDEX]);
125 }
126
127 #[test]
128 fn splits_on_lf_and_crlf_breakpoints() {
129 let sql = "CREATE TABLE users (\r\n id INTEGER PRIMARY KEY\r\n);\r\n\
132 -- #[toasty::breakpoint]\n\
133 CREATE INDEX idx ON users (id);\n\
134 -- #[toasty::breakpoint]\r\n\
135 SELECT 1;\r\n";
136 let m = Migration::new_sql(sql.to_string());
137 assert_eq!(
138 m.statements(),
139 vec![
140 "CREATE TABLE users (\r\n id INTEGER PRIMARY KEY\r\n);",
141 CREATE_INDEX,
142 "SELECT 1;\r\n",
143 ]
144 );
145 }
146
147 #[test]
148 fn only_whole_marker_lines_split() {
149 let sql = format!(
152 "SELECT '-- #[toasty::breakpoint]';\n -- #[toasty::breakpoint] \n{CREATE_INDEX} -- #[toasty::breakpoint]"
153 );
154 let m = Migration::new_sql(sql);
155 assert_eq!(
156 m.statements(),
157 vec![
158 "SELECT '-- #[toasty::breakpoint]';",
159 "CREATE INDEX idx ON users (id); -- #[toasty::breakpoint]",
160 ]
161 );
162 }
163
164 #[test]
165 fn drops_empty_statements() {
166 let sql = format!(
168 "-- #[toasty::breakpoint]\n{CREATE_TABLE}\n-- #[toasty::breakpoint]\n\n-- #[toasty::breakpoint]\n{CREATE_INDEX}\n-- #[toasty::breakpoint]\n"
169 );
170 let m = Migration::new_sql(sql);
171 assert_eq!(m.statements(), vec![CREATE_TABLE, CREATE_INDEX]);
172 assert!(Migration::new_sql(String::new()).statements().is_empty());
173 }
174}