feat: V5 show tables / relationships / indexes list commands

Add the list-all show family as one Command::ShowList { kind }
variant. A read-only worker show_list formats count-headed lists
(reusing do_list_tables / read_all_relationships /
read_table_indexes, so it never drifts from the items panel);
internal __rdbms_* tables excluded. Help + parse-usage entries
added; 10 integration tests in tests/it/show_list.rs.

Mark V5 [x]. Split the singular show relationship/index <name>
detail forms (the [<name>] half) into a new tracked V5a [ ] item
rather than leaving them as an untracked footnote.
This commit is contained in:
claude@clouddev1
2026-06-07 13:20:52 +00:00
parent 28e75961aa
commit 8dec784080
15 changed files with 555 additions and 27 deletions
+95
View File
@@ -554,6 +554,14 @@ enum Request {
ListTables {
reply: oneshot::Sender<Result<Vec<String>, DbError>>,
},
/// List every item of a schema kind (tables / relationships /
/// indexes) as pre-formatted display lines for the `show
/// <kind>` commands (V5). Read-only; formats in the worker
/// from the same helpers the items panel and describe view use.
ShowList {
kind: crate::dsl::command::ShowListKind,
reply: oneshot::Sender<Result<Vec<String>, DbError>>,
},
DescribeTable {
name: String,
source: Option<String>,
@@ -1317,6 +1325,17 @@ impl Database {
recv.await.map_err(|_| DbError::WorkerGone)?
}
/// Pre-formatted display lines for `show tables` /
/// `show relationships` / `show indexes` (V5). Read-only.
pub async fn show_list(
&self,
kind: crate::dsl::command::ShowListKind,
) -> Result<Vec<String>, DbError> {
let (reply, recv) = oneshot::channel();
self.send(Request::ShowList { kind, reply }).await?;
recv.await.map_err(|_| DbError::WorkerGone)?
}
pub async fn describe_table(
&self,
name: String,
@@ -2245,6 +2264,9 @@ fn handle_request(
Request::ListTables { reply } => {
let _ = reply.send(do_list_tables(conn));
}
Request::ShowList { kind, reply } => {
let _ = reply.send(do_show_list(conn, kind));
}
Request::DescribeTable {
name,
source,
@@ -5834,6 +5856,79 @@ fn do_list_tables(conn: &Connection) -> Result<Vec<String>, DbError> {
Ok(out)
}
/// Pre-formatted display lines for the `show <kind>` list commands
/// (V5). A count header followed by one indented item per line, or a
/// single friendly "none yet" line for an empty collection. Reuses
/// the same helpers the items panel / describe view read from, so the
/// list never drifts from those views. Engine-neutral wording per the
/// ADR-0002 user-facing posture.
fn do_show_list(
conn: &Connection,
kind: crate::dsl::command::ShowListKind,
) -> Result<Vec<String>, DbError> {
use crate::dsl::command::ShowListKind;
let mut lines = Vec::new();
match kind {
ShowListKind::Tables => {
let tables = do_list_tables(conn)?;
if tables.is_empty() {
lines.push("No tables in this project yet.".to_string());
} else {
lines.push(format!("Tables ({}):", tables.len()));
for name in tables {
lines.push(format!(" {name}"));
}
}
}
ShowListKind::Relationships => {
let rels = read_all_relationships(conn)?;
if rels.is_empty() {
lines.push("No relationships in this project yet.".to_string());
} else {
lines.push(format!("Relationships ({}):", rels.len()));
for r in rels {
let mut line = format!(
" {}: {}.{} → {}.{}",
r.name, r.parent_table, r.parent_column, r.child_table, r.child_column
);
if r.on_delete != ReferentialAction::default_action() {
line.push_str(&format!(" on delete {}", r.on_delete.keyword()));
}
if r.on_update != ReferentialAction::default_action() {
line.push_str(&format!(" on update {}", r.on_update.keyword()));
}
lines.push(line);
}
}
}
ShowListKind::Indexes => {
// Each table's user-created indexes (origin "c"), the
// same set the items panel shows. Ordered by table, then
// by index name (read_table_indexes orders by name).
let tables = do_list_tables(conn)?;
let mut entries: Vec<String> = Vec::new();
for table in &tables {
for ix in read_table_indexes(conn, table)? {
let unique = if ix.unique { " [unique]" } else { "" };
entries.push(format!(
" {}.{} ({}){unique}",
table,
ix.name,
ix.columns.join(", ")
));
}
}
if entries.is_empty() {
lines.push("No indexes in this project yet.".to_string());
} else {
lines.push(format!("Indexes ({}):", entries.len()));
lines.extend(entries);
}
}
}
Ok(lines)
}
/// Internal full schema of a table, sufficient to regenerate
/// its `CREATE TABLE` statement during the rebuild dance.
#[derive(Debug, Clone)]