systemprompt_cli/commands/infrastructure/db/
mod.rs1mod admin;
11mod admin_migrate;
12mod admin_migrate_down;
13mod admin_migrate_mark_applied;
14mod admin_migrate_plan;
15mod admin_migrate_repair;
16mod admin_migrate_status;
17mod admin_migrations;
18mod admin_squash;
19mod commands;
20mod dispatch;
21mod doctor;
22mod helpers;
23mod introspect;
24mod query;
25mod schema;
26mod types;
27
28use anyhow::{Context, Result, bail};
29use std::sync::Arc;
30use systemprompt_database::{DatabaseAdminService, DbPool, QueryExecutor};
31
32use crate::cli_settings::CliConfig;
33use crate::context::CommandContext;
34use crate::shared::render_result;
35use dispatch::{dispatch_profile_migration, dispatch_standalone_migration};
36
37pub use commands::{DbCommands, MigrationsCommands};
38pub use types::*;
39
40pub(crate) use helpers::format_bytes;
41
42pub async fn execute(cmd: DbCommands, ctx: &CommandContext) -> Result<()> {
43 let config = &ctx.cli;
44 let Some(cmd) = (match ctx.database_context() {
45 Some(db_ctx) => dispatch_standalone_migration(cmd, db_ctx, config).await?,
46 None => dispatch_profile_migration(cmd, config).await?,
47 }) else {
48 return Ok(());
49 };
50
51 let (pool, admin_service, query_executor) = connect_services(ctx).await?;
52
53 match cmd {
54 DbCommands::Query {
55 sql,
56 limit,
57 offset,
58 format: _,
59 } => run_query(&query_executor, &sql, limit, offset, config).await,
60 DbCommands::Execute { sql, format: _ } => run_write(&query_executor, &sql, config).await,
61 DbCommands::Tables { filter } => {
62 schema::execute_tables(&admin_service, filter, config).await
63 },
64 DbCommands::Describe { table_name } => {
65 schema::execute_describe(&admin_service, &table_name, config).await
66 },
67 DbCommands::Info => schema::execute_info(&admin_service, config).await,
68 DbCommands::Migrate { .. }
69 | DbCommands::MigrateDown { .. }
70 | DbCommands::MigrateSquash { .. }
71 | DbCommands::MigrateRepair { .. }
72 | DbCommands::MigrateMarkApplied { .. } => {
73 bail!("migration command was not consumed by the migration dispatcher")
74 },
75 DbCommands::Migrations { cmd } => {
76 admin::execute_migrations(ctx.app_context().await?, cmd, config).await
77 },
78 DbCommands::MigratePlan { extension, json } => {
79 admin::execute_migrate_plan(
80 ctx.app_context().await?,
81 extension.as_deref(),
82 json,
83 config,
84 )
85 .await
86 },
87 DbCommands::MigrateStatus { extension, json } => {
88 admin::execute_migrate_status(
89 ctx.app_context().await?,
90 extension.as_deref(),
91 json,
92 config,
93 )
94 .await
95 },
96 DbCommands::AssignAdmin { user } => {
97 if ctx.is_database_scoped() {
98 bail!("assign-admin requires full profile context");
99 }
100 admin::execute_assign_admin(ctx.app_context().await?, &user, config).await
101 },
102 DbCommands::Status => admin::execute_status(&admin_service, config).await,
103 DbCommands::Validate => schema::execute_validate(&admin_service, config).await,
104 DbCommands::Count { table_name } => {
105 schema::execute_count(&admin_service, &table_name, config).await
106 },
107 DbCommands::Indexes { table } => {
108 introspect::execute_indexes(&admin_service, table, config).await
109 },
110 DbCommands::Size => introspect::execute_size(&admin_service, config).await,
111 DbCommands::Doctor => doctor::execute_doctor(&pool, config).await,
112 }
113}
114
115async fn connect_services(
116 ctx: &CommandContext,
117) -> Result<(DbPool, DatabaseAdminService, QueryExecutor)> {
118 let pool = ctx
119 .db_pool()
120 .await
121 .context("Failed to connect to database. Check your profile configuration.")?;
122 let write_pool = pool
123 .write_pool_arc()
124 .context("Database must be PostgreSQL")?;
125 let admin_service = DatabaseAdminService::new(Arc::clone(&write_pool));
126 let query_executor = QueryExecutor::new(write_pool);
127 Ok((pool, admin_service, query_executor))
128}
129
130async fn run_query(
131 executor: &QueryExecutor,
132 sql: &str,
133 limit: Option<u32>,
134 offset: Option<u32>,
135 config: &CliConfig,
136) -> Result<()> {
137 let params = query::QueryParams { sql, limit, offset };
138 let result = query::execute_query(executor, ¶ms, config).await?;
139 render_result(&result, config);
140 Ok(())
141}
142
143async fn run_write(executor: &QueryExecutor, sql: &str, config: &CliConfig) -> Result<()> {
144 let result = query::execute_write(executor, sql, config).await?;
145 render_result(&result, config);
146 Ok(())
147}