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 =
40            pg_query::split_with_parser(sql).map_err(|e| RepositoryError::SqlParse(Box::new(e)))?;
41        Ok(statements
42            .into_iter()
43            .map(str::trim)
44            .filter(|s| !s.is_empty())
45            .map(str::to_owned)
46            .collect())
47    }
48
49    pub async fn execute_query(db: &Database, query: &str) -> DatabaseResult<QueryResult> {
50        db.query_raw(&query).await
51    }
52
53    pub async fn execute_file(db: &Database, file_path: &str) -> DatabaseResult<()> {
54        let sql = tokio::fs::read_to_string(file_path)
55            .await
56            .map_err(|source| RepositoryError::SqlFile {
57                path: file_path.to_owned(),
58                source,
59            })?;
60        Self::execute_statements(db, &sql).await
61    }
62}