systemprompt_database/lifecycle/installation/
module.rs1use 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}