1use sea_query::{ColumnDef as SeaColumnDef, ColumnType, PostgresQueryBuilder, Table as SeaTable};
6
7#[derive(Default)]
8pub struct Schema {
9 pub statements: Vec<String>,
10}
11
12impl Schema {
13 pub fn new() -> Self {
14 Self::default()
15 }
16
17 pub fn create<F>(&mut self, table: &str, build: F)
18 where
19 F: FnOnce(&mut Table),
20 {
21 let mut t = Table::new(table);
22 build(&mut t);
23 let (create_sql, post_sqls) = t.into_sql();
24 self.statements.push(create_sql);
25 self.statements.extend(post_sqls);
26 }
27
28 pub fn drop(&mut self, table: &str) {
29 self.statements
30 .push(format!("DROP TABLE IF EXISTS {} CASCADE", table));
31 }
32
33 pub fn drop_if_exists(&mut self, table: &str) {
34 self.drop(table);
35 }
36
37 pub fn raw(&mut self, sql: impl Into<String>) {
38 self.statements.push(sql.into());
39 }
40}
41
42pub struct Table {
44 name: String,
45 columns: Vec<Box<ColumnDef>>,
46 indexes: Vec<String>,
47 foreign_keys: Vec<String>,
48}
49
50impl Table {
51 pub fn new(name: impl Into<String>) -> Self {
52 Self {
53 name: name.into(),
54 columns: Vec::new(),
55 indexes: Vec::new(),
56 foreign_keys: Vec::new(),
57 }
58 }
59
60 fn push_column(&mut self, name: &str, ty: ColumnType) -> &mut ColumnDef {
61 let sea_def = SeaColumnDef::new_with_type(sea_query::Alias::new(name), ty);
62 let boxed = Box::new(ColumnDef {
66 sea_def,
67 name: name.to_string(),
68 });
69 self.columns.push(boxed);
70 self.columns.last_mut().unwrap().as_mut()
71 }
72
73 pub fn id(&mut self) -> &mut ColumnDef {
74 let cd = self.push_column("id", ColumnType::BigInteger);
75 cd.sea_def.not_null().primary_key().auto_increment();
76 cd
77 }
78
79 pub fn uuid_id(&mut self) -> &mut ColumnDef {
80 let cd = self.push_column("id", ColumnType::Uuid);
81 cd.sea_def.not_null().primary_key();
82 cd
83 }
84
85 pub fn string(&mut self, name: &str) -> &mut ColumnDef {
86 self.push_column(name, ColumnType::String(sea_query::StringLen::N(255)))
87 }
88
89 pub fn text(&mut self, name: &str) -> &mut ColumnDef {
90 self.push_column(name, ColumnType::Text)
91 }
92
93 pub fn integer(&mut self, name: &str) -> &mut ColumnDef {
94 self.push_column(name, ColumnType::Integer)
95 }
96
97 pub fn big_integer(&mut self, name: &str) -> &mut ColumnDef {
98 self.push_column(name, ColumnType::BigInteger)
99 }
100
101 pub fn boolean(&mut self, name: &str) -> &mut ColumnDef {
102 self.push_column(name, ColumnType::Boolean)
103 }
104
105 pub fn timestamp(&mut self, name: &str) -> &mut ColumnDef {
106 self.push_column(name, ColumnType::Timestamp)
107 }
108
109 pub fn timestamp_tz(&mut self, name: &str) -> &mut ColumnDef {
110 self.push_column(name, ColumnType::TimestampWithTimeZone)
111 }
112
113 pub fn timestamps(&mut self) {
114 self.push_column("created_at", ColumnType::TimestampWithTimeZone)
115 .nullable()
116 .default("CURRENT_TIMESTAMP");
117 self.push_column("updated_at", ColumnType::TimestampWithTimeZone)
118 .nullable()
119 .default("CURRENT_TIMESTAMP");
120 }
121
122 pub fn soft_deletes(&mut self) {
123 self.push_column("deleted_at", ColumnType::TimestampWithTimeZone)
124 .nullable();
125 }
126
127 pub fn json(&mut self, name: &str) -> &mut ColumnDef {
128 self.push_column(name, ColumnType::Json)
129 }
130
131 pub fn uuid(&mut self, name: &str) -> &mut ColumnDef {
132 self.push_column(name, ColumnType::Uuid)
133 }
134
135 pub fn foreign_id_for(&mut self, name: &str, references: &str) -> &mut ColumnDef {
137 let fk_sql = format!(
138 "ALTER TABLE {} ADD CONSTRAINT fk_{}_{} FOREIGN KEY ({}) REFERENCES {} (id) ON DELETE CASCADE",
139 self.name, self.name, name, name, references
140 );
141 self.foreign_keys.push(fk_sql);
142 self.push_column(name, ColumnType::BigInteger)
143 }
144
145 pub fn index(&mut self, columns: &[&str]) -> &mut Self {
146 let idx_name = format!("idx_{}_{}", self.name, columns.join("_"));
147 let sql = format!(
148 "CREATE INDEX {} ON {} ({})",
149 idx_name,
150 self.name,
151 columns.join(", ")
152 );
153 self.indexes.push(sql);
154 self
155 }
156
157 pub fn unique_index(&mut self, columns: &[&str]) -> &mut Self {
158 let idx_name = format!("uq_{}_{}", self.name, columns.join("_"));
159 let sql = format!(
160 "CREATE UNIQUE INDEX {} ON {} ({})",
161 idx_name,
162 self.name,
163 columns.join(", ")
164 );
165 self.indexes.push(sql);
166 self
167 }
168
169 fn into_sql(self) -> (String, Vec<String>) {
170 let mut t = SeaTable::create();
171 t.table(sea_query::Alias::new(&self.name)).if_not_exists();
172 for col in &self.columns {
173 t.col(&mut col.sea_def.clone());
174 }
175 let create_sql = t.build(PostgresQueryBuilder);
176 let mut post = self.indexes;
177 post.extend(self.foreign_keys);
178 (create_sql, post)
179 }
180}
181
182pub struct ColumnDef {
183 sea_def: SeaColumnDef,
184 pub name: String,
185}
186
187impl ColumnDef {
188 pub fn not_null(&mut self) -> &mut Self {
189 self.sea_def.not_null();
190 self
191 }
192
193 pub fn nullable(&mut self) -> &mut Self {
194 self.sea_def.null();
195 self
196 }
197
198 pub fn unique(&mut self) -> &mut Self {
199 self.sea_def.unique_key();
200 self
201 }
202
203 pub fn primary_key(&mut self) -> &mut Self {
204 self.sea_def.primary_key();
205 self
206 }
207
208 pub fn default(&mut self, value: impl Into<String>) -> &mut Self {
209 self.sea_def.default(sea_query::Expr::cust(value.into()));
210 self
211 }
212
213 pub fn default_value<T>(&mut self, value: T) -> &mut Self
214 where
215 T: Into<sea_query::Value>,
216 {
217 self.sea_def.default(value);
218 self
219 }
220}