Skip to main content

systemprompt_database/lifecycle/installation/
module.rs

1//! Schema and seed installation from on-disk
2//! [`systemprompt_models::modules::Module`] descriptors (legacy loader path).
3
4use std::path::Path;
5
6use systemprompt_extension::{SchemaSource, SeedSource};
7use systemprompt_models::modules::{Module, ModuleSchema};
8
9use super::util::table_exists;
10use crate::error::{DatabaseResult, RepositoryError};
11use crate::services::{DatabaseProvider, SqlExecutor};
12
13#[derive(Debug, Clone, Copy)]
14pub struct ModuleInstaller;
15
16impl ModuleInstaller {
17    pub async fn install(module: &Module, db: &dyn DatabaseProvider) -> DatabaseResult<()> {
18        install_module_schemas_from_source(module, db).await?;
19        install_module_seeds_from_path(module, db).await?;
20        Ok(())
21    }
22}
23
24pub async fn install_module_schemas_from_source(
25    module: &Module,
26    db: &dyn DatabaseProvider,
27) -> DatabaseResult<()> {
28    let Some(schemas) = &module.schemas else {
29        return Ok(());
30    };
31
32    for schema in schemas {
33        if schema.table.is_empty() {
34            let sql = read_module_schema_sql(module, schema)?;
35            SqlExecutor::execute_statements_parsed(db, &sql).await?;
36            continue;
37        }
38
39        if !table_exists(db, &schema.table).await? {
40            let sql = read_module_schema_sql(module, schema)?;
41            SqlExecutor::execute_statements_parsed(db, &sql).await?;
42        }
43    }
44
45    Ok(())
46}
47
48fn read_module_schema_sql(module: &Module, schema: &ModuleSchema) -> DatabaseResult<String> {
49    match &schema.sql {
50        SchemaSource::Inline(sql) => Ok(sql.clone()),
51        SchemaSource::File(relative_path) => {
52            let full_path = module.path.join(relative_path);
53            std::fs::read_to_string(&full_path).map_err(|e| {
54                RepositoryError::Internal(format!(
55                    "Failed to read schema file '{}' for module '{}': {e}",
56                    full_path.display(),
57                    module.name
58                ))
59            })
60        },
61    }
62}
63
64pub async fn install_module_seeds_from_path(
65    module: &Module,
66    db: &dyn DatabaseProvider,
67) -> DatabaseResult<()> {
68    let Some(seeds) = &module.seeds else {
69        return Ok(());
70    };
71
72    for seed in seeds {
73        let sql = match &seed.sql {
74            SeedSource::Inline(sql) => sql.clone(),
75            SeedSource::File(relative_path) => {
76                let seed_path = module.path.join(relative_path);
77                if !seed_path.exists() {
78                    return Err(RepositoryError::Internal(format!(
79                        "Seed file not found for module '{}': {}",
80                        module.name,
81                        seed_path.display()
82                    )));
83                }
84                std::fs::read_to_string(&seed_path).map_err(|e| {
85                    RepositoryError::Internal(format!(
86                        "Failed to read seed file '{}' for module '{}': {e}",
87                        seed_path.display(),
88                        module.name
89                    ))
90                })?
91            },
92        };
93        SqlExecutor::execute_statements_parsed(db, &sql).await?;
94    }
95
96    Ok(())
97}
98
99pub async fn install_schema(db: &dyn DatabaseProvider, schema_path: &Path) -> DatabaseResult<()> {
100    let schema_content = std::fs::read_to_string(schema_path).map_err(|e| {
101        RepositoryError::Internal(format!(
102            "Failed to read schema file '{}': {e}",
103            schema_path.display()
104        ))
105    })?;
106    SqlExecutor::execute_statements_parsed(db, &schema_content).await
107}
108
109pub async fn install_seed(db: &dyn DatabaseProvider, seed_path: &Path) -> DatabaseResult<()> {
110    let seed_content = std::fs::read_to_string(seed_path).map_err(|e| {
111        RepositoryError::Internal(format!(
112            "Failed to read seed file '{}': {e}",
113            seed_path.display()
114        ))
115    })?;
116    SqlExecutor::execute_statements_parsed(db, &seed_content).await
117}