Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions crates/dry_run_cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,12 @@ schema_file = ".dryrun/schema.json"
// if --db is provided, introspect and save schema
if let Some(db_url) = db {
let ctx = DryRun::connect(db_url).await?;
if ctx.is_standby().await? {
anyhow::bail!(
"`dryrun init --db` must run against the primary; \
planner and activity stats are not available on standbys"
);
}
let snapshot = ctx.introspect_schema().await?;

let schema_path = data_dir.join("schema.json");
Expand All @@ -338,12 +344,31 @@ schema_file = ".dryrun/schema.json"
let key = complete_key(&resolved, &snapshot.database);
store.put(&key, &snapshot).await?;

let planner = ctx.introspect_planner_stats(&snapshot.content_hash).await?;
store.put_planner_stats(&key, &planner).await?;

let activity = ctx
.introspect_activity_stats(&snapshot.content_hash, "primary")
.await?;
store.put_activity_stats(&key, &activity).await?;

eprintln!(
"Captured schema: {} tables, {} views, {} functions",
snapshot.tables.len(),
snapshot.views.len(),
snapshot.functions.len()
);
eprintln!(
" Planner stats: {} tables, {} columns, {} indexes",
planner.tables.len(),
planner.columns.len(),
planner.indexes.len()
);
eprintln!(
" Activity stats: {} tables, {} indexes (label=primary)",
activity.tables.len(),
activity.indexes.len()
);
eprintln!(" Schema: {}", schema_path.display());
eprintln!(
" project={} database={}",
Expand Down
30 changes: 26 additions & 4 deletions crates/dry_run_cli/src/mcp/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1541,9 +1541,27 @@ impl DryRunServer {
}

#[tool(
description = "Reload the on-disk schema without restarting. Run after `dryrun dump-schema`."
description = "Reload schema from history.db (with stats) or schema.json (DDL only) without restarting."
)]
async fn reload_schema(&self) -> Result<CallToolResult, McpError> {
// history.db first; the schema.json fallback drops planner/activity stats
if let (Some(store), Some(key)) = (self.history.as_ref(), self.snapshot_key.as_ref())
&& let Ok(annotated) = store.get_annotated(key, SnapshotRef::Latest).await
{
let body = format!(
"Schema loaded from history.db: {} tables, {} views, {} functions \
(planner: {}, activity nodes: {})",
annotated.schema.tables.len(),
annotated.schema.views.len(),
annotated.schema.functions.len(),
if annotated.planner.is_some() { "yes" } else { "no" },
annotated.activity_by_node.len(),
);
*self.schema.write().await = Some(annotated);
let text = self.wrap_text(&body, None);
return Ok(CallToolResult::success(vec![Content::text(text)]));
}

for candidate in &self.schema_candidates {
if !candidate.exists() {
continue;
Expand All @@ -1562,7 +1580,9 @@ impl DryRunServer {
})?;

let body = format!(
"Schema loaded from {}: {} tables, {} views, {} functions",
"Schema loaded from {} (planner/activity unavailable; \
run `dryrun snapshot take` or `dryrun init` to capture stats): \
{} tables, {} views, {} functions",
candidate.display(),
snapshot.tables.len(),
snapshot.views.len(),
Expand All @@ -1582,8 +1602,10 @@ impl DryRunServer {
.collect();
Err(McpError::internal_error(
format!(
"no schema file found at any expected location:\n{}\n\n\
Run `dryrun dump-schema --db <DATABASE_URL>` first.",
"no schema source available: history.db has no entry for the \
configured snapshot key, and no schema file was found at:\n{}\n\n\
Run `dryrun init --db <DATABASE_URL>` (with stats) or \
`dryrun dump-schema --db <DATABASE_URL>` (DDL only).",
paths.join("\n")
),
None,
Expand Down