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