Skip to main content

systemprompt_database/services/
executor.rs

1//! SQL batch and statement-by-statement execution helpers.
2//!
3//! Statements are split with the Postgres parser (`pg_query`), so quoted
4//! identifiers, escape strings and dollar-quoted bodies never split
5//! mid-token and malformed SQL is refused rather than partially executed.
6//!
7//! Copyright (c) systemprompt.io — Business Source License 1.1.
8//! See <https://systemprompt.io> for licensing details.
9
10use super::database::Database;
11use super::provider::DatabaseProvider;
12use crate::error::{DatabaseResult, RepositoryError};
13use crate::models::QueryResult;
14
15#[derive(Debug, Copy, Clone)]
16pub struct SqlExecutor;
17
18impl SqlExecutor {
19    pub async fn execute_statements(db: &Database, sql: &str) -> DatabaseResult<()> {
20        db.execute_batch(sql).await
21    }
22
23    pub async fn execute_statements_parsed(
24        db: &dyn DatabaseProvider,
25        sql: &str,
26    ) -> DatabaseResult<()> {
27        for statement in Self::parse_sql_statements(sql)? {
28            db.execute_raw(&statement)
29                .await
30                .map_err(|source| RepositoryError::Statement {
31                    statement: statement.clone(),
32                    source: Box::new(source),
33                })?;
34        }
35        Ok(())
36    }
37
38    pub fn parse_sql_statements(sql: &str) -> DatabaseResult<Vec<String>> {
39        let statements = pg_query::split_with_parser(sql).map_err(RepositoryError::SqlSplit)?;
40        Ok(statements
41            .into_iter()
42            .map(str::trim)
43            .filter(|s| !s.is_empty())
44            .map(str::to_owned)
45            .collect())
46    }
47
48    pub async fn execute_query(db: &Database, query: &str) -> DatabaseResult<QueryResult> {
49        db.query_raw(&query)
50            .await
51            .map_err(|e| RepositoryError::QueryExecution(Box::new(e)))
52    }
53
54    pub async fn execute_file(db: &Database, file_path: &str) -> DatabaseResult<()> {
55        let sql = tokio::fs::read_to_string(file_path)
56            .await
57            .map_err(|source| RepositoryError::SqlFile {
58                path: file_path.to_owned(),
59                source,
60            })?;
61        Self::execute_statements(db, &sql).await
62    }
63}