forge_runtime/pg/migration/
builtin.rs1use super::runner::Migration;
17
18pub const SYSTEM_MIGRATION_PREFIX: &str = "__forge_v";
20
21const V001_INITIAL: &str = include_str!("../../../migrations/system/v001_initial.sql");
22
23#[derive(Debug, Clone)]
25pub struct SystemMigration {
26 pub version: u32,
27 pub sql: &'static str,
28 pub description: &'static str,
29}
30
31impl SystemMigration {
32 pub fn name(&self) -> String {
34 format!("{}{:03}", SYSTEM_MIGRATION_PREFIX, self.version)
35 }
36
37 pub fn to_migration(&self) -> Migration {
38 Migration::new(self.name(), self.sql)
39 }
40}
41
42pub fn get_system_migrations() -> Vec<SystemMigration> {
44 vec![SystemMigration {
45 version: 1,
46 sql: V001_INITIAL,
47 description: "FORGE internal schema",
48 }]
49}
50
51pub fn get_builtin_migrations() -> Vec<Migration> {
52 get_system_migrations()
53 .into_iter()
54 .map(|m| m.to_migration())
55 .collect()
56}
57
58pub fn get_all_system_sql() -> String {
60 get_system_migrations()
61 .into_iter()
62 .map(|m| m.sql)
63 .collect::<Vec<_>>()
64 .join("\n\n")
65}
66
67pub fn is_system_migration(name: &str) -> bool {
68 name.starts_with(SYSTEM_MIGRATION_PREFIX)
69}
70
71pub fn extract_version(name: &str) -> Option<u32> {
73 name.strip_prefix(SYSTEM_MIGRATION_PREFIX)
74 .and_then(|suffix| suffix.parse().ok())
75}
76
77#[cfg(test)]
78#[allow(clippy::unwrap_used, clippy::indexing_slicing, clippy::panic)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn test_get_system_migrations() {
84 let migrations = get_system_migrations();
85 assert_eq!(migrations.len(), 1);
86 assert_eq!(migrations[0].version, 1);
87 assert_eq!(migrations[0].name(), "__forge_v001");
88 }
89
90 #[test]
91 fn test_migration_sql_not_empty() {
92 let migrations = get_system_migrations();
93 for m in migrations {
94 assert!(!m.sql.is_empty(), "Migration v{} has empty SQL", m.version);
95 }
96 }
97
98 #[test]
99 fn test_migration_sql_contains_expected_objects() {
100 let v001 = get_system_migrations()[0].sql;
101
102 for table in [
104 "forge_nodes",
105 "forge_leaders",
106 "forge_kv",
107 "forge_kv_counters",
108 "forge_jobs",
109 "forge_jobs_history",
110 "forge_cron_runs",
111 "forge_workflow_definitions",
112 "forge_workflow_runs",
113 "forge_workflow_state",
114 "forge_workflow_events",
115 "forge_workflow_steps",
116 "forge_admin_audit",
117 "forge_paused_queues",
118 "forge_rate_limits",
119 "forge_change_log",
120 "forge_daemons",
121 "forge_webhook_events",
122 "forge_refresh_tokens",
123 "forge_oauth_clients",
124 "forge_oauth_codes",
125 "forge_signals_events",
126 "forge_signals_sessions",
127 "forge_signals_users",
128 "forge_signals_hourly_stats",
129 "forge_signals_daily_rollup",
130 ] {
131 assert!(v001.contains(table), "expected table {table} in v001 SQL");
132 }
133
134 for fn_name in [
136 "forge_validate_identifier",
137 "forge_notify_change",
138 "forge_notify_change_statement",
139 "forge_notify_job_available",
140 "forge_enable_reactivity",
141 "forge_disable_reactivity",
142 "forge_workflow_event_notify",
143 "forge_workflow_runs_cancel_notify",
144 "forge_cleanup_webhook_events",
145 "forge_cleanup_expired_jobs",
146 "forge_archive_completed_jobs",
147 "forge_purge_expired_refresh_tokens",
148 "forge_purge_expired_oauth_codes",
149 "forge_trim_change_log",
150 "forge_signals_ensure_partition",
151 "forge_signals_drop_old_partitions",
152 "forge_signals_roll_up_hour",
153 "forge_signals_roll_up_day",
154 ] {
155 assert!(
156 v001.contains(fn_name),
157 "expected function {fn_name} in v001 SQL"
158 );
159 }
160
161 for needle in [
163 "owner_subject",
164 "token_family",
165 "raw_body",
166 "raw_headers",
167 "tenant_id",
168 ] {
169 assert!(v001.contains(needle), "expected {needle} in v001 SQL");
170 }
171 }
172
173 #[test]
174 fn test_is_system_migration() {
175 assert!(is_system_migration("__forge_v001"));
176 assert!(is_system_migration("__forge_v002"));
177 assert!(is_system_migration("__forge_v100"));
178 assert!(!is_system_migration("0001_create_users"));
179 assert!(!is_system_migration("user_migration"));
180 }
181
182 #[test]
183 fn test_extract_version() {
184 assert_eq!(extract_version("__forge_v001"), Some(1));
185 assert_eq!(extract_version("__forge_v002"), Some(2));
186 assert_eq!(extract_version("__forge_v100"), Some(100));
187 assert_eq!(extract_version("0001_create_users"), None);
188 assert_eq!(extract_version("invalid"), None);
189 }
190
191 #[test]
192 fn test_system_migration_to_migration() {
193 let sys = SystemMigration {
194 version: 1,
195 sql: "SELECT 1;",
196 description: "Test",
197 };
198 let m = sys.to_migration();
199 assert_eq!(m.version, "__forge_v001");
200 assert_eq!(m.up_sql, "SELECT 1;");
201 }
202}