systemprompt_cli/commands/infrastructure/db/
mod.rs1mod admin;
2mod helpers;
3mod introspect;
4mod query;
5mod schema;
6mod types;
7
8use anyhow::{bail, Context, Result};
9use clap::Subcommand;
10use std::sync::Arc;
11use systemprompt_database::{DatabaseAdminService, QueryExecutor};
12use systemprompt_runtime::{AppContext, DatabaseContext};
13
14use crate::cli_settings::CliConfig;
15use crate::shared::render_result;
16
17pub use types::*;
18
19#[derive(Debug, Subcommand)]
20pub enum DbCommands {
21 #[command(about = "Execute SQL query (read-only)")]
22 Query {
23 sql: String,
24 #[arg(long)]
25 limit: Option<u32>,
26 #[arg(long)]
27 offset: Option<u32>,
28 #[arg(long)]
29 format: Option<String>,
30 },
31 #[command(about = "Execute write operation (INSERT, UPDATE, DELETE)")]
32 Execute {
33 sql: String,
34 #[arg(long)]
35 format: Option<String>,
36 },
37 #[command(about = "List all tables with row counts and sizes")]
38 Tables {
39 #[arg(long, help = "Filter tables by pattern")]
40 filter: Option<String>,
41 },
42 #[command(about = "Describe table schema with columns and indexes")]
43 Describe { table_name: String },
44 #[command(about = "Show database information")]
45 Info,
46 #[command(about = "Run database migrations")]
47 Migrate,
48 #[command(about = "Show migration status and history")]
49 Migrations {
50 #[command(subcommand)]
51 cmd: MigrationsCommands,
52 },
53 #[command(about = "Assign admin role to a user")]
54 AssignAdmin { user: String },
55 #[command(about = "Show database connection status")]
56 Status,
57 #[command(about = "Validate database schema against expected tables")]
58 Validate,
59 #[command(about = "Get row count for a table")]
60 Count { table_name: String },
61 #[command(about = "List all indexes")]
62 Indexes {
63 #[arg(long, help = "Filter by table name")]
64 table: Option<String>,
65 },
66 #[command(about = "Show database and table sizes")]
67 Size,
68}
69
70#[derive(Debug, Subcommand)]
71pub enum MigrationsCommands {
72 #[command(about = "Show migration status for all extensions")]
73 Status,
74 #[command(about = "Show migration history for an extension")]
75 History {
76 #[arg(help = "Extension ID")]
77 extension: String,
78 },
79}
80
81struct DatabaseTool {
82 ctx: AppContext,
83 admin_service: DatabaseAdminService,
84 query_executor: QueryExecutor,
85}
86
87impl DatabaseTool {
88 async fn new() -> Result<Self> {
89 let ctx = AppContext::new()
90 .await
91 .context("Failed to connect to database. Check your profile configuration.")?;
92 let pool = ctx.db_pool().write_pool_arc()?;
93 let admin_service = DatabaseAdminService::new(Arc::clone(&pool));
94 let query_executor = QueryExecutor::new(pool);
95 Ok(Self {
96 ctx,
97 admin_service,
98 query_executor,
99 })
100 }
101}
102
103pub async fn execute(cmd: DbCommands, config: &CliConfig) -> Result<()> {
104 if matches!(cmd, DbCommands::Migrate) {
105 return admin::execute_migrate(config).await;
106 }
107
108 let db = DatabaseTool::new().await?;
109
110 match cmd {
111 DbCommands::Query {
112 sql,
113 limit,
114 offset,
115 format: _,
116 } => {
117 let params = query::QueryParams {
118 sql: &sql,
119 limit,
120 offset,
121 };
122 let result = query::execute_query(&db.query_executor, ¶ms, config).await?;
123 render_result(&result);
124 Ok(())
125 },
126 DbCommands::Execute { sql, format: _ } => {
127 let result = query::execute_write(&db.query_executor, &sql, config).await?;
128 render_result(&result);
129 Ok(())
130 },
131 DbCommands::Tables { filter } => {
132 schema::execute_tables(&db.admin_service, filter, config).await
133 },
134 DbCommands::Describe { table_name } => {
135 schema::execute_describe(&db.admin_service, &table_name, config).await
136 },
137 DbCommands::Info => schema::execute_info(&db.admin_service, config).await,
138 DbCommands::Migrate => unreachable!(),
139 DbCommands::Migrations { cmd } => admin::execute_migrations(&db.ctx, cmd, config).await,
140 DbCommands::AssignAdmin { user } => {
141 admin::execute_assign_admin(&db.ctx, &user, config).await
142 },
143 DbCommands::Status => admin::execute_status(&db.admin_service, config).await,
144 DbCommands::Validate => schema::execute_validate(&db.admin_service, config).await,
145 DbCommands::Count { table_name } => {
146 schema::execute_count(&db.admin_service, &table_name, config).await
147 },
148 DbCommands::Indexes { table } => {
149 introspect::execute_indexes(&db.admin_service, table, config).await
150 },
151 DbCommands::Size => introspect::execute_size(&db.admin_service, config).await,
152 }
153}
154
155pub async fn execute_with_db(
156 cmd: DbCommands,
157 db_ctx: &DatabaseContext,
158 config: &CliConfig,
159) -> Result<()> {
160 let pool = db_ctx
161 .db_pool()
162 .write_pool_arc()
163 .context("Database must be PostgreSQL")?;
164 let admin_service = DatabaseAdminService::new(Arc::clone(&pool));
165 let query_executor = QueryExecutor::new(pool);
166
167 match cmd {
168 DbCommands::Query {
169 sql,
170 limit,
171 offset,
172 format: _,
173 } => {
174 let params = query::QueryParams {
175 sql: &sql,
176 limit,
177 offset,
178 };
179 let result = query::execute_query(&query_executor, ¶ms, config).await?;
180 render_result(&result);
181 Ok(())
182 },
183 DbCommands::Execute { sql, format: _ } => {
184 let result = query::execute_write(&query_executor, &sql, config).await?;
185 render_result(&result);
186 Ok(())
187 },
188 DbCommands::Tables { filter } => {
189 schema::execute_tables(&admin_service, filter, config).await
190 },
191 DbCommands::Describe { table_name } => {
192 schema::execute_describe(&admin_service, &table_name, config).await
193 },
194 DbCommands::Info => schema::execute_info(&admin_service, config).await,
195 DbCommands::Migrate => admin::execute_migrate_standalone(db_ctx, config).await,
196 DbCommands::Migrations { cmd } => {
197 admin::execute_migrations_standalone(db_ctx, cmd, config).await
198 },
199 DbCommands::AssignAdmin { .. } => {
200 bail!("assign-admin requires full profile context")
201 },
202 DbCommands::Status => admin::execute_status(&admin_service, config).await,
203 DbCommands::Validate => schema::execute_validate(&admin_service, config).await,
204 DbCommands::Count { table_name } => {
205 schema::execute_count(&admin_service, &table_name, config).await
206 },
207 DbCommands::Indexes { table } => {
208 introspect::execute_indexes(&admin_service, table, config).await
209 },
210 DbCommands::Size => introspect::execute_size(&admin_service, config).await,
211 }
212}