omni_orchestrator/schemas/v1/db/queries/build.rs
1use crate::models::build::Build;
2use anyhow::Context;
3use sqlx::{FromRow, MySql, Pool};
4
5/// Retrieves a paginated list of all builds in the system.
6///
7/// This function fetches builds with pagination support, ordering them by ID
8/// in ascending order (oldest first). It provides a way to browse through
9/// potentially large numbers of build records without loading them all at once.
10///
11/// # Arguments
12///
13/// * `pool` - Database connection pool for executing the query
14/// * `per_page` - Number of builds to return per page
15/// * `page` - Zero-based page number (e.g., 0 for first page, 1 for second page)
16///
17/// # Returns
18///
19/// * `Ok(Vec<Build>)` - Successfully retrieved list of builds for the requested page
20/// * `Err(anyhow::Error)` - Failed to fetch builds
21///
22/// # Pagination
23///
24/// The function calculates the appropriate OFFSET as `page * per_page`.
25/// For example, with per_page = 10:
26/// - page 0 → entries 0-9
27/// - page 1 → entries 10-19
28/// - page 2 → entries 20-29
29pub async fn list_builds_paginated(
30 pool: &Pool<MySql>,
31 per_page: i64,
32 page: i64,
33) -> anyhow::Result<Vec<Build>> {
34 let builds =
35 sqlx::query_as::<_, Build>("SELECT * FROM builds ORDER BY id ASC LIMIT ? OFFSET ?")
36 .bind(per_page)
37 .bind(page)
38 .fetch_all(pool)
39 .await
40 .context("Failed to fetch builds")?;
41
42 Ok(builds)
43}
44
45/// Retrieves the total number of builds in the system.
46pub async fn get_total_build_count(pool: &Pool<MySql>) -> anyhow::Result<i64> {
47 let count = sqlx::query_scalar::<_, i64>("SELECT COUNT(*) FROM builds")
48 .fetch_one(pool)
49 .await
50 .context("Failed to fetch build count")?;
51
52 Ok(count)
53}
54
55
56/// Retrieves a paginated list of builds for a specific application.
57///
58/// This function fetches builds associated with a particular application,
59/// with pagination support. Results are ordered by build ID in ascending order
60/// (oldest first). This is useful for viewing the build history of a specific app.
61///
62/// # Arguments
63///
64/// * `pool` - Database connection pool for executing the query
65/// * `app_id` - Unique identifier of the application whose builds to retrieve
66/// * `per_page` - Number of builds to return per page
67/// * `offset` - Number of builds to skip (for pagination)
68///
69/// # Returns
70///
71/// * `Ok(Vec<Build>)` - Successfully retrieved list of builds for the application
72/// * `Err(anyhow::Error)` - Failed to fetch builds
73///
74/// # Note
75///
76/// Unlike `list_builds_paginated`, this function uses a direct offset value
77/// rather than calculating it from a page number. The caller must calculate
78/// the appropriate offset based on their pagination scheme (typically `page * per_page`).
79pub async fn list_builds_for_app_paginated(
80 pool: &Pool<MySql>,
81 app_id: i64,
82 per_page: i64,
83 offset: i64,
84) -> anyhow::Result<Vec<Build>> {
85 let builds = sqlx::query_as::<_, Build>(
86 "SELECT * FROM builds WHERE app_id = ? ORDER BY id ASC LIMIT ? OFFSET ?",
87 )
88 .bind(app_id)
89 .bind(per_page)
90 .bind(offset)
91 .fetch_all(pool)
92 .await
93 .context("Failed to fetch builds")?;
94
95 Ok(builds)
96}
97
98/// Retrieves a specific build by its unique identifier.
99///
100/// This function fetches detailed information about a single build record.
101/// It's typically used when specific build details are needed, such as
102/// viewing build logs or checking build status.
103///
104/// # Arguments
105///
106/// * `pool` - Database connection pool for executing the query
107/// * `id` - Unique identifier of the build to retrieve
108///
109/// # Returns
110///
111/// * `Ok(Build)` - Successfully retrieved build information
112/// * `Err(anyhow::Error)` - Failed to fetch build (including if not found)
113///
114/// # Error Handling
115///
116/// Returns an error if no build with the given ID exists or if a database
117/// error occurs during the query execution.
118pub async fn get_build_by_id(pool: &Pool<MySql>, id: i64) -> anyhow::Result<Build> {
119 let build = sqlx::query_as::<_, Build>("SELECT * FROM builds WHERE id = ?")
120 .bind(id)
121 .fetch_one(pool)
122 .await
123 .context("Failed to fetch build")?;
124
125 Ok(build)
126}
127
128/// Creates a new build record in the database.
129///
130/// This function inserts a new build entry with the provided parameters.
131/// It's typically called when a new build process is initiated for an application.
132///
133/// # Arguments
134///
135/// * `pool` - Database connection pool for executing the query
136/// * `app_id` - Identifier of the application this build belongs to
137/// * `git_commit` - Git commit hash or identifier for this build
138/// * `git_branch` - Git branch name used for this build
139/// * `git_repo` - Git repository URL or identifier
140/// * `status` - Initial status of the build (e.g., "pending", "in_progress")
141/// * `build_log` - Initial build log content (may be empty or contain setup information)
142///
143/// # Returns
144///
145/// * `Ok(Vec<Build>)` - Successfully created build record(s)
146/// * `Err(anyhow::Error)` - Failed to create build record
147///
148/// # Note
149///
150/// The function returns a vector of builds, which is unusual for a creation operation
151/// that typically returns a single record. This may be due to specific implementation
152/// requirements or to accommodate batch creation scenarios.
153///
154/// # Important
155///
156/// This function doesn't take a transaction parameter, so it commits changes
157/// immediately. For operations that need to be part of a larger transaction,
158/// consider enhancing this function to accept a transaction parameter.
159pub async fn create_build(
160 pool: &Pool<MySql>,
161 app_id: i64,
162 git_commit: &str,
163 git_branch: &str,
164 git_repo: &str,
165 status: &str,
166 build_log: &str,
167) -> anyhow::Result<Vec<Build>> {
168 let builds = sqlx::query_as::<_, Build>(
169 "INSERT INTO builds (app_id, git_commit, git_branch, git_repo, status, build_log) VALUES (?, ?, ?, ?, ?, ?)"
170 )
171 .bind(app_id)
172 .bind(git_commit)
173 .bind(git_branch)
174 .bind(git_repo)
175 .bind(status)
176 .bind(build_log)
177 .fetch_all(pool)
178 .await
179 .context("Failed to update build")?;
180
181 Ok(builds)
182}
183
184/// Updates an existing build record with new status and log information.
185///
186/// This function modifies a build record to reflect the current state of the
187/// build process. It's typically called during or after a build process to
188/// update its status and append to the build log.
189///
190/// # Arguments
191///
192/// * `pool` - Database connection pool for executing the query
193/// * `id` - Unique identifier of the build to update
194/// * `status` - New status of the build (e.g., "success", "failed", "in_progress")
195/// * `build_log` - Updated build log content
196///
197/// # Returns
198///
199/// * `Ok(Build)` - Successfully updated build record
200/// * `Err(anyhow::Error)` - Failed to update build
201///
202/// # Use Cases
203///
204/// Common use cases for this function include:
205/// - Updating build status as it progresses through different stages
206/// - Appending build output to the log as it becomes available
207/// - Marking a build as complete with its final status
208///
209/// # Note
210///
211/// This function replaces the entire build log content rather than appending to it.
212/// If incremental updates are needed, the caller should fetch the current log,
213/// append to it, and then pass the complete updated log to this function.
214pub async fn update_build(
215 pool: &Pool<MySql>,
216 id: i64,
217 status: &str,
218 build_log: &str,
219) -> anyhow::Result<Build> {
220 let build =
221 sqlx::query_as::<_, Build>("UPDATE builds SET status = ?, build_log = ? WHERE id = ?")
222 .bind(status)
223 .bind(build_log)
224 .bind(id)
225 .fetch_one(pool)
226 .await
227 .context("Failed to update build")?;
228
229 Ok(build)
230}
231
232/// Deletes a specific build record from the database.
233///
234/// This function permanently removes a build record identified by its ID.
235/// It's typically used for cleanup operations or when a build was created erroneously.
236///
237/// # Arguments
238///
239/// * `pool` - Database connection pool for executing the query
240/// * `id` - Unique identifier of the build to delete
241///
242/// # Returns
243///
244/// * `Ok(())` - Successfully deleted the build
245/// * `Err(anyhow::Error)` - Failed to delete the build
246///
247/// # Warning
248///
249/// This operation is irreversible. Once a build is deleted, all associated
250/// information including build logs and status history is permanently lost.
251///
252/// # Note
253///
254/// This function does not verify if the build exists before attempting deletion.
255/// If the build does not exist, the operation will still succeed (as far as SQL is concerned),
256/// but no rows will be affected.
257pub async fn delete_build(pool: &Pool<MySql>, id: i64) -> anyhow::Result<()> {
258 sqlx::query("DELETE FROM builds WHERE id = ?")
259 .bind(id)
260 .execute(pool)
261 .await
262 .context("Failed to delete build")?;
263
264 Ok(())
265}
266
267/// Deletes all build records associated with a specific application.
268///
269/// This function permanently removes all build records for a given application.
270/// It's typically used when an application is being deleted, or when a complete
271/// build history reset is desired.
272///
273/// # Arguments
274///
275/// * `pool` - Database connection pool for executing the query
276/// * `app_id` - Unique identifier of the application whose builds should be deleted
277///
278/// # Returns
279///
280/// * `Ok(())` - Successfully deleted the builds for the application
281/// * `Err(anyhow::Error)` - Failed to delete the builds
282///
283/// # Warning
284///
285/// This operation is irreversible and bulk in nature. It will delete all build
286/// records for the specified application without any additional confirmation.
287/// Use with caution, especially in production environments.
288///
289/// # Use Cases
290///
291/// Common scenarios for using this function include:
292/// - Application deletion (cleanup of associated data)
293/// - Build history purging for storage optimization
294/// - Resetting an application's build history before migration or major changes
295///
296/// # Note
297///
298/// If the application has no builds, this operation will succeed but affect zero rows.
299pub async fn delete_builds_for_app(pool: &Pool<MySql>, app_id: i64) -> anyhow::Result<()> {
300 sqlx::query("DELETE FROM builds WHERE app_id = ?")
301 .bind(app_id)
302 .execute(pool)
303 .await
304 .context("Failed to delete builds for app")?;
305
306 Ok(())
307}