ferro_deployments/
migration.rs1use sea_orm_migration::prelude::*;
20
21pub struct CreateDeploymentsTable;
23
24impl sea_orm_migration::MigrationName for CreateDeploymentsTable {
25 fn name(&self) -> &str {
26 "m_create_deployments_table"
27 }
28}
29
30#[async_trait::async_trait]
31impl MigrationTrait for CreateDeploymentsTable {
32 async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
33 manager
34 .create_table(
35 Table::create()
36 .table(Deployments::Table)
37 .if_not_exists()
38 .col(
39 ColumnDef::new(Deployments::Id)
40 .big_integer()
41 .not_null()
42 .auto_increment()
43 .primary_key(),
44 )
45 .col(
46 ColumnDef::new(Deployments::Identifier)
47 .string()
48 .not_null()
49 .unique_key(),
50 )
51 .col(ColumnDef::new(Deployments::OwnerKey).string().not_null())
52 .col(ColumnDef::new(Deployments::SourceRef).string().null())
53 .col(
54 ColumnDef::new(Deployments::ArtifactLocation)
55 .string()
56 .null(),
57 )
58 .col(ColumnDef::new(Deployments::ByteSize).big_integer().null())
59 .col(
60 ColumnDef::new(Deployments::Status)
61 .string()
62 .not_null()
63 .default("building"),
64 )
65 .col(
66 ColumnDef::new(Deployments::ArtifactDeletedAt)
67 .timestamp_with_time_zone()
68 .null(),
69 )
70 .col(
71 ColumnDef::new(Deployments::TerminatedAt)
72 .timestamp_with_time_zone()
73 .null(),
74 )
75 .col(
76 ColumnDef::new(Deployments::CreatedAt)
77 .timestamp_with_time_zone()
78 .not_null(),
79 )
80 .to_owned(),
81 )
82 .await
83 }
84
85 async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
86 manager
87 .drop_table(Table::drop().table(Deployments::Table).to_owned())
88 .await
89 }
90}
91
92#[derive(DeriveIden)]
93pub(crate) enum Deployments {
94 Table,
95 Id,
96 Identifier,
97 OwnerKey,
98 SourceRef,
99 ArtifactLocation,
100 ByteSize,
101 Status,
102 ArtifactDeletedAt,
103 TerminatedAt,
104 CreatedAt,
105}
106
107pub struct CreateDeploymentPointersTable;
109
110impl sea_orm_migration::MigrationName for CreateDeploymentPointersTable {
111 fn name(&self) -> &str {
112 "m_create_deployment_pointers_table"
113 }
114}
115
116#[async_trait::async_trait]
117impl MigrationTrait for CreateDeploymentPointersTable {
118 async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
119 manager
120 .create_table(
121 Table::create()
122 .table(DeploymentPointers::Table)
123 .if_not_exists()
124 .col(
125 ColumnDef::new(DeploymentPointers::OwnerKey)
126 .string()
127 .not_null()
128 .primary_key(),
129 )
130 .col(
131 ColumnDef::new(DeploymentPointers::DeploymentId)
132 .big_integer()
133 .not_null(),
134 )
135 .col(
136 ColumnDef::new(DeploymentPointers::PreviousDeploymentId)
137 .big_integer()
138 .null(),
139 )
140 .col(
141 ColumnDef::new(DeploymentPointers::UpdatedAt)
142 .timestamp_with_time_zone()
143 .not_null(),
144 )
145 .to_owned(),
146 )
147 .await
148 }
149
150 async fn down(&self, manager: &SchemaManager) -> Result<(), DbErr> {
151 manager
152 .drop_table(Table::drop().table(DeploymentPointers::Table).to_owned())
153 .await
154 }
155}
156
157#[derive(DeriveIden)]
158pub(crate) enum DeploymentPointers {
159 Table,
160 OwnerKey,
161 DeploymentId,
162 PreviousDeploymentId,
163 UpdatedAt,
164}
165
166#[cfg(test)]
167mod tests {
168 use sea_orm::{ConnectionTrait, Database, DatabaseBackend, Statement};
169 use sea_orm_migration::MigratorTrait;
170
171 struct TestMigrator;
172
173 #[async_trait::async_trait]
174 impl MigratorTrait for TestMigrator {
175 fn migrations() -> Vec<Box<dyn sea_orm_migration::MigrationTrait>> {
176 vec![
177 Box::new(super::CreateDeploymentsTable),
178 Box::new(super::CreateDeploymentPointersTable),
179 ]
180 }
181 }
182
183 #[tokio::test]
184 async fn migration_creates_deployments_table() {
185 let conn = Database::connect("sqlite::memory:")
186 .await
187 .expect("connect to in-memory sqlite");
188
189 TestMigrator::up(&conn, None)
190 .await
191 .expect("run migration up");
192
193 let table_row = conn
195 .query_one(Statement::from_string(
196 DatabaseBackend::Sqlite,
197 "SELECT name FROM sqlite_master WHERE type='table' AND name='deployments'"
198 .to_string(),
199 ))
200 .await
201 .expect("query sqlite_master for deployments table");
202 assert!(
203 table_row.is_some(),
204 "deployments table not created by migration"
205 );
206
207 let pointers_row = conn
209 .query_one(Statement::from_string(
210 DatabaseBackend::Sqlite,
211 "SELECT name FROM sqlite_master WHERE type='table' AND name='deployment_pointers'"
212 .to_string(),
213 ))
214 .await
215 .expect("query sqlite_master for deployment_pointers table");
216 assert!(
217 pointers_row.is_some(),
218 "deployment_pointers table not created by migration"
219 );
220
221 let pragma_rows = conn
223 .query_all(Statement::from_string(
224 DatabaseBackend::Sqlite,
225 "PRAGMA table_info(deployments)".to_string(),
226 ))
227 .await
228 .expect("PRAGMA table_info(deployments)");
229 let col_names: Vec<String> = pragma_rows
230 .iter()
231 .filter_map(|row| row.try_get_by::<String, _>("name").ok())
232 .collect();
233 assert!(
234 col_names.iter().any(|c| c == "artifact_deleted_at"),
235 "artifact_deleted_at column not found in deployments table; columns: {col_names:?}"
236 );
237
238 TestMigrator::down(&conn, None)
240 .await
241 .expect("run migration down");
242
243 let deployments_after_down = conn
244 .query_one(Statement::from_string(
245 DatabaseBackend::Sqlite,
246 "SELECT name FROM sqlite_master WHERE type='table' AND name='deployments'"
247 .to_string(),
248 ))
249 .await
250 .expect("query sqlite_master after down");
251 assert!(
252 deployments_after_down.is_none(),
253 "deployments table should be dropped by down()"
254 );
255
256 let pointers_after_down = conn
257 .query_one(Statement::from_string(
258 DatabaseBackend::Sqlite,
259 "SELECT name FROM sqlite_master WHERE type='table' AND name='deployment_pointers'"
260 .to_string(),
261 ))
262 .await
263 .expect("query sqlite_master for deployment_pointers after down");
264 assert!(
265 pointers_after_down.is_none(),
266 "deployment_pointers table should be dropped by down()"
267 );
268 }
269}