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}