omni_orchestrator/schemas/v1/db/queries/region.rs
1use anyhow::Context;
2use sqlx::{MySql, Pool};
3
4use libomni::types::db::v1 as types;
5use types::region::Region;
6use types::provider::ProviderRegion;
7
8/// Retrieves a paginated list of deployment regions.
9///
10/// This function fetches regions from the database with optional pagination support.
11/// Results are ordered by creation time with the most recently created regions first.
12///
13/// # Arguments
14///
15/// * `pool` - Database connection pool for executing the query
16/// * `limit` - Optional maximum number of regions to return (defaults to 100 if not specified)
17/// * `offset` - Optional number of regions to skip (for pagination)
18///
19/// # Returns
20///
21/// * `Ok(Vec<Region>)` - Successfully retrieved list of regions
22/// * `Err(anyhow::Error)` - Failed to fetch regions
23///
24/// # Dynamic Query Building
25///
26/// This function uses SQLx's QueryBuilder to dynamically construct a SQL query
27/// based on whether pagination parameters are provided. This approach is more
28/// efficient than building strings manually and protects against SQL injection.
29///
30/// # Pagination
31///
32/// When both `limit` and `offset` are provided, standard SQL pagination is applied.
33/// If only `limit` is provided, just the first N records are returned.
34/// If neither is provided, all regions are returned (with a safety limit of 100).
35pub async fn list_regions(
36 pool: &Pool<MySql>,
37 limit: Option<i64>,
38 offset: Option<i64>,
39) -> anyhow::Result<Vec<Region>> {
40 let regions = sqlx::query_as::<_, Region>(
41 "SELECT * FROM regions ORDER BY created_at DESC LIMIT ? OFFSET ?",
42 )
43 .bind(limit.unwrap_or(100))
44 .bind(offset.unwrap_or(0))
45 .fetch_all(pool)
46 .await
47 .context("Failed to fetch regions")?;
48
49 Ok(regions)
50}
51
52pub async fn list_provider_regions(
53 pool: &Pool<MySql>,
54) -> anyhow::Result<Vec<ProviderRegion>> {
55 let regions = sqlx::query_as::<_, ProviderRegion>(
56 "SELECT regions.*, providers.name AS provider_name, providers_regions.status AS binding_status
57 FROM regions
58 JOIN providers ON regions.provider = providers.id
59 JOIN providers_regions ON regions.id = providers_regions.region_id AND providers.id = providers_regions.provider_id",
60 )
61 .fetch_all(pool)
62 .await
63 .context("Failed to fetch provider regions")?;
64
65 Ok(regions)
66}
67
68/// Retrieves a specific region by its unique identifier.
69///
70/// This function fetches detailed information about a single region record.
71/// It's typically used when specific region details are needed, such as
72/// for displaying region information or resource allocation.
73///
74/// # Arguments
75///
76/// * `pool` - Database connection pool for executing the query
77/// * `id` - Unique identifier of the region to retrieve
78///
79/// # Returns
80///
81/// * `Ok(Region)` - Successfully retrieved region information
82/// * `Err(anyhow::Error)` - Failed to fetch region (including if not found)
83///
84/// # Error Handling
85///
86/// Returns an error if no region with the given ID exists or if a database
87/// error occurs during the query execution.
88pub async fn get_region_by_id(pool: &Pool<MySql>, id: i64) -> anyhow::Result<Region> {
89 let region = sqlx::query_as::<_, Region>("SELECT * FROM regions WHERE id = ?")
90 .bind(id)
91 .fetch_one(pool)
92 .await
93 .context("Failed to fetch region")?;
94
95 Ok(region)
96}
97
98/// Creates a new deployment region in the system.
99///
100/// This function registers a new region for application deployments.
101/// Regions typically represent geographical deployment locations or
102/// distinct cloud provider environments.
103///
104/// # Arguments
105///
106/// * `pool` - Database connection pool for executing the query
107/// * `name` - Name of the region (typically a geographical identifier like "us-east", "eu-west")
108/// * `provider` - Cloud or infrastructure provider (e.g., "aws", "gcp", "azure", "on-prem")
109/// * `status` - Initial status of the region (e.g., "provisioning", "active", "maintenance")
110///
111/// # Returns
112///
113/// * `Ok(Region)` - Successfully created region record
114/// * `Err(anyhow::Error)` - Failed to create region record
115///
116/// # Transaction Handling
117///
118/// This function uses a database transaction to ensure atomicity of the operation.
119/// If any part of the operation fails, the entire operation is rolled back.
120///
121/// # Common Region States
122///
123/// Typical region status values include:
124/// - "provisioning" - Region is being set up and not yet ready for deployments
125/// - "active" - Region is fully operational and can accept deployments
126/// - "maintenance" - Region is temporarily unavailable for new deployments
127/// - "deprecated" - Region is being phased out, no new deployments accepted
128/// - "unavailable" - Region is not currently operational
129pub async fn create_region(
130 pool: &Pool<MySql>,
131 name: &str,
132 provider: &str,
133 status: &str,
134) -> anyhow::Result<Region> {
135 let mut tx = pool.begin().await?;
136
137 let region = sqlx::query_as::<_, Region>(
138 "INSERT INTO regions (name, provider, status) VALUES (?, ?, ?)",
139 )
140 .bind(name)
141 .bind(provider)
142 .bind(status)
143 .fetch_one(&mut *tx)
144 .await
145 .context("Failed to create region")?;
146
147 tx.commit().await?;
148 Ok(region)
149}
150
151/// Updates the status of an existing deployment region.
152///
153/// This function changes the operational status of a region, which affects
154/// whether new deployments can be directed to it. Status changes are critical
155/// operations that can affect application availability and deployment strategies.
156///
157/// # Arguments
158///
159/// * `pool` - Database connection pool for executing the query
160/// * `id` - Unique identifier of the region to update
161/// * `status` - New status for the region (e.g., "active", "maintenance", "unavailable")
162///
163/// # Returns
164///
165/// * `Ok(Region)` - Successfully updated region record
166/// * `Err(anyhow::Error)` - Failed to update region
167///
168/// # Transaction Handling
169///
170/// This function uses a database transaction to ensure atomicity of the operation.
171/// If any part of the operation fails, the entire operation is rolled back.
172///
173/// # Operational Impact
174///
175/// Changing a region's status may have significant operational impacts:
176/// - Setting to "maintenance" or "unavailable" prevents new deployments
177/// - Status changes should be coordinated with deployment schedules
178/// - Monitoring systems may need to be updated based on region status
179/// - Load balancers may need reconfiguration after status changes
180pub async fn update_region_status(
181 pool: &Pool<MySql>,
182 id: i64,
183 status: &str,
184) -> anyhow::Result<Region> {
185 let mut tx = pool.begin().await?;
186
187 let region = sqlx::query_as::<_, Region>("UPDATE regions SET status = ? WHERE id = ?")
188 .bind(status)
189 .bind(id)
190 .fetch_one(&mut *tx)
191 .await
192 .context("Failed to update region status")?;
193
194 tx.commit().await?;
195 Ok(region)
196}
197
198/// Deletes a deployment region from the system.
199///
200/// This function permanently removes a region record from the database.
201/// It should be used with extreme caution, as it may affect deployed applications
202/// and infrastructure allocation.
203///
204/// # Arguments
205///
206/// * `pool` - Database connection pool for executing the query
207/// * `id` - Unique identifier of the region to delete
208///
209/// # Returns
210///
211/// * `Ok(())` - Successfully deleted the region
212/// * `Err(anyhow::Error)` - Failed to delete the region
213///
214/// # Warning
215///
216/// This operation is irreversible and potentially dangerous. Instead of deleting
217/// regions, consider changing their status to "deprecated" or "unavailable" first,
218/// and ensure no active deployments exist in the region before deletion.
219///
220/// # Cascading Effects
221///
222/// Depending on the database schema and application logic:
223/// - Deployed applications in this region may lose their region reference
224/// - Foreign key constraints may prevent deletion if the region is in use
225/// - Monitoring, billing, and operational systems may be affected
226///
227/// # Transaction Handling
228///
229/// This function uses a database transaction to ensure atomicity of the operation.
230/// If any part of the operation fails, the entire operation is rolled back.
231pub async fn delete_region(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
232 let mut tx = pool.begin().await?;
233
234 sqlx::query("DELETE FROM regions WHERE id = ?")
235 .bind(id)
236 .execute(&mut *tx)
237 .await
238 .context("Failed to delete region")?;
239
240 tx.commit().await?;
241 Ok(())
242}
243
244/// Retrieves all active deployment regions.
245///
246/// This function fetches all regions with a status of "active", indicating
247/// they are available for new deployments. It's typically used for deployment
248/// target selection and region availability displays.
249///
250/// # Arguments
251///
252/// * `pool` - Database connection pool for executing the query
253///
254/// # Returns
255///
256/// * `Ok(Vec<Region>)` - Successfully retrieved list of active regions
257/// * `Err(anyhow::Error)` - Failed to fetch active regions
258///
259/// # Use Cases
260///
261/// Common use cases include:
262/// - Populating region selection dropdowns in deployment interfaces
263/// - Determining valid deployment targets for automated processes
264/// - Calculating resource availability across active regions
265/// - Health status dashboards showing operational deployment locations
266///
267/// # Query Details
268///
269/// Results are filtered by status="active" and ordered by creation time,
270/// with the most recently created regions appearing first in the list.
271pub async fn get_active_regions(pool: &Pool<MySql>) -> anyhow::Result<Vec<Region>> {
272 let regions = sqlx::query_as::<_, Region>(
273 "SELECT * FROM regions WHERE status = 'active' ORDER BY created_at DESC",
274 )
275 .fetch_all(pool)
276 .await
277 .context("Failed to fetch active regions")?;
278
279 Ok(regions)
280}