chuchi_postgres/
migrations.rs1use std::borrow::Cow;
9
10use crate::{
11 Error,
12 connection::{Connection, ConnectionOwned},
13 filter,
14 table::Table,
15};
16
17use chuchi_postgres_derive::{FromRow, row};
18use tracing::debug;
19use types::time::DateTime;
20
21#[derive(Debug, FromRow)]
22pub struct ExecutedMigration {
23 datetime: DateTime,
24}
25
26#[derive(Debug, Clone)]
30pub struct Migrations {
31 table: Table,
32}
33
34impl Migrations {
35 pub(super) fn new(table_name: Option<String>) -> Self {
37 Self {
38 table: Table::new(
39 table_name.map(Cow::Owned).unwrap_or("migrations".into()),
40 ),
41 }
42 }
43
44 pub(super) async fn init(
45 &self,
46 db: &mut ConnectionOwned,
47 ) -> Result<(), Error> {
48 let db = db.transaction().await?;
49 let conn = db.connection();
50
51 let table_exists =
53 TABLE_EXISTS.replace("migrations", self.table.name());
54
55 let [result] =
57 conn.query_one::<[bool; 1], _>(&table_exists, &[]).await?;
58
59 if !result {
60 let create_table =
61 CREATE_TABLE.replace("migrations", self.table.name());
62 conn.batch_execute(&create_table).await?;
63 }
64
65 db.commit().await?;
66
67 Ok(())
68 }
69
70 pub async fn add(
71 &self,
72 conn: &mut ConnectionOwned,
73 name: &str,
74 sql: &str,
75 ) -> Result<(), Error> {
76 let trans = conn.transaction().await?;
77 let conn = trans.connection();
78
79 let executed = self.get(conn, name).await?;
81 if let Some(mig) = executed {
82 debug!("migration {} was executed at {}", name, mig.datetime);
83 return Ok(());
84 }
85
86 conn.batch_execute(&sql).await?;
88
89 self.set(conn, name).await?;
90
91 trans.commit().await?;
92
93 Ok(())
94 }
95
96 pub async fn get(
97 &self,
98 conn: Connection<'_>,
99 name: &str,
100 ) -> Result<Option<ExecutedMigration>, Error> {
101 let table = self.table.with_conn(conn);
102
103 table.select_opt(filter!(&name)).await
105 }
106
107 pub async fn set(
108 &self,
109 conn: Connection<'_>,
110 name: &str,
111 ) -> Result<(), Error> {
112 let table = self.table.with_conn(conn);
113
114 table
115 .insert(row! {
116 name,
117 "datetime": DateTime::now(),
118 })
119 .await?;
120
121 Ok(())
122 }
123}
124
125const TABLE_EXISTS: &str = "\
126SELECT EXISTS (
127 SELECT FROM information_schema.tables
128 WHERE table_schema = 'public'
129 AND table_name = 'migrations'
130);";
131
132const CREATE_TABLE: &str = "\
133CREATE TABLE migrations (
134 name text PRIMARY KEY,
135 datetime timestamp
136);
137
138CREATE INDEX ON migrations (datetime);";