feat: V5a show relationship/index <name> detail views
Fold the singular per-item forms into Command::ShowList { kind,
name: Option<String> } (name: Some = one item). Two grammar
branches reuse the relationship/index completion sources; worker
do_show_one renders a labelled detail block or a friendly
"No ... named X." line, reusing the V5 render path. Help +
parse-usage entries, two ADR-0042 near-miss rows, 5 integration
tests. Mark V5a [x] — V5's [<name>] clause now complete.
This commit is contained in:
+13
-8
@@ -463,16 +463,21 @@ since ADR-0027.)
|
|||||||
per-item detail for relationships/indexes — is split out as
|
per-item detail for relationships/indexes — is split out as
|
||||||
**V5a** below so it has a tracked home rather than living as a
|
**V5a** below so it has a tracked home rather than living as a
|
||||||
footnote here.)*
|
footnote here.)*
|
||||||
- [ ] **V5a** Singular per-item detail views `show relationship
|
- [x] **V5a** Singular per-item detail views `show relationship
|
||||||
<name>` / `show index <name>` — the `[<name>]` half of V5 for
|
<name>` / `show index <name>` — the `[<name>]` half of V5 for
|
||||||
the relationship and index kinds (the table kind already has
|
the relationship and index kinds (the table kind already has
|
||||||
`show table <name>`). Each names one item and renders its
|
`show table <name>`).
|
||||||
detail (a relationship's endpoints + ON DELETE/UPDATE actions; an
|
*(Done 2026-06-07: folded into `Command::ShowList { kind, name:
|
||||||
index's table, columns, and uniqueness). Small follow-up on the
|
Option<String> }` — `name: Some(_)` is the singular form. Two
|
||||||
V5 machinery: a name slot after the `relationship` / `index`
|
grammar branches (`relationship <name>` / `index <name>`,
|
||||||
keyword in `SHOW_CHOICES`, a lookup-one in the worker, and a
|
reusing the `Relationships`/`Indexes` completion sources for the
|
||||||
detail render. Not yet built; raised 2026-06-07 when V5's
|
name slot), a worker `do_show_one` rendering a labelled detail
|
||||||
list-all family shipped.
|
block (endpoints + ON DELETE/UPDATE for a relationship; table,
|
||||||
|
columns, uniqueness for an index) or a friendly "No relationship/
|
||||||
|
index named `X`." line, reusing the V5 `ShowList` render path.
|
||||||
|
Help + parse-usage entries + two ADR-0042 near-miss matrix rows;
|
||||||
|
5 added integration tests. V5's `[<name>]` clause is now
|
||||||
|
complete across all three kinds.)*
|
||||||
- [x] **V6** Copy the output panel to the system clipboard
|
- [x] **V6** Copy the output panel to the system clipboard
|
||||||
(issue #11, ADR-0041). `copy` / `copy all` copy the whole
|
(issue #11, ADR-0041). `copy` / `copy all` copy the whole
|
||||||
panel; `copy last` copies the most recent command's output.
|
panel; `copy last` copies the most recent command's output.
|
||||||
|
|||||||
+5
-2
@@ -2890,6 +2890,8 @@ mod tests {
|
|||||||
"show tables",
|
"show tables",
|
||||||
"show relationships",
|
"show relationships",
|
||||||
"show indexes",
|
"show indexes",
|
||||||
|
"show relationship",
|
||||||
|
"show index",
|
||||||
] {
|
] {
|
||||||
app.update(key(KeyCode::Tab));
|
app.update(key(KeyCode::Tab));
|
||||||
assert_eq!(app.input, expected);
|
assert_eq!(app.input, expected);
|
||||||
@@ -2903,8 +2905,9 @@ mod tests {
|
|||||||
fn shift_tab_cycles_backward_starting_from_last() {
|
fn shift_tab_cycles_backward_starting_from_last() {
|
||||||
let mut app = App::new();
|
let mut app = App::new();
|
||||||
type_str(&mut app, "show ");
|
type_str(&mut app, "show ");
|
||||||
// Backward starts from the last candidate (`indexes`).
|
// Backward starts from the last candidate (`index`, the
|
||||||
for expected in ["show indexes", "show relationships", "show tables"] {
|
// V5a singular form).
|
||||||
|
for expected in ["show index", "show relationship", "show indexes"] {
|
||||||
app.update(key(KeyCode::BackTab));
|
app.update(key(KeyCode::BackTab));
|
||||||
assert_eq!(app.input, expected);
|
assert_eq!(app.input, expected);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1674,6 +1674,8 @@ mod tests {
|
|||||||
"tables".to_string(),
|
"tables".to_string(),
|
||||||
"relationships".to_string(),
|
"relationships".to_string(),
|
||||||
"indexes".to_string(),
|
"indexes".to_string(),
|
||||||
|
"relationship".to_string(),
|
||||||
|
"index".to_string(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -560,6 +560,9 @@ enum Request {
|
|||||||
/// from the same helpers the items panel and describe view use.
|
/// from the same helpers the items panel and describe view use.
|
||||||
ShowList {
|
ShowList {
|
||||||
kind: crate::dsl::command::ShowListKind,
|
kind: crate::dsl::command::ShowListKind,
|
||||||
|
/// `None` lists all items of the kind; `Some(name)` shows
|
||||||
|
/// one named relationship/index's detail (V5a).
|
||||||
|
name: Option<String>,
|
||||||
reply: oneshot::Sender<Result<Vec<String>, DbError>>,
|
reply: oneshot::Sender<Result<Vec<String>, DbError>>,
|
||||||
},
|
},
|
||||||
DescribeTable {
|
DescribeTable {
|
||||||
@@ -1330,9 +1333,10 @@ impl Database {
|
|||||||
pub async fn show_list(
|
pub async fn show_list(
|
||||||
&self,
|
&self,
|
||||||
kind: crate::dsl::command::ShowListKind,
|
kind: crate::dsl::command::ShowListKind,
|
||||||
|
name: Option<String>,
|
||||||
) -> Result<Vec<String>, DbError> {
|
) -> Result<Vec<String>, DbError> {
|
||||||
let (reply, recv) = oneshot::channel();
|
let (reply, recv) = oneshot::channel();
|
||||||
self.send(Request::ShowList { kind, reply }).await?;
|
self.send(Request::ShowList { kind, name, reply }).await?;
|
||||||
recv.await.map_err(|_| DbError::WorkerGone)?
|
recv.await.map_err(|_| DbError::WorkerGone)?
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2264,8 +2268,8 @@ fn handle_request(
|
|||||||
Request::ListTables { reply } => {
|
Request::ListTables { reply } => {
|
||||||
let _ = reply.send(do_list_tables(conn));
|
let _ = reply.send(do_list_tables(conn));
|
||||||
}
|
}
|
||||||
Request::ShowList { kind, reply } => {
|
Request::ShowList { kind, name, reply } => {
|
||||||
let _ = reply.send(do_show_list(conn, kind));
|
let _ = reply.send(do_show_list(conn, kind, name.as_deref()));
|
||||||
}
|
}
|
||||||
Request::DescribeTable {
|
Request::DescribeTable {
|
||||||
name,
|
name,
|
||||||
@@ -5865,8 +5869,13 @@ fn do_list_tables(conn: &Connection) -> Result<Vec<String>, DbError> {
|
|||||||
fn do_show_list(
|
fn do_show_list(
|
||||||
conn: &Connection,
|
conn: &Connection,
|
||||||
kind: crate::dsl::command::ShowListKind,
|
kind: crate::dsl::command::ShowListKind,
|
||||||
|
name: Option<&str>,
|
||||||
) -> Result<Vec<String>, DbError> {
|
) -> Result<Vec<String>, DbError> {
|
||||||
use crate::dsl::command::ShowListKind;
|
use crate::dsl::command::ShowListKind;
|
||||||
|
// V5a: a named item shows one relationship/index's detail.
|
||||||
|
if let Some(name) = name {
|
||||||
|
return do_show_one(conn, kind, name);
|
||||||
|
}
|
||||||
let mut lines = Vec::new();
|
let mut lines = Vec::new();
|
||||||
match kind {
|
match kind {
|
||||||
ShowListKind::Tables => {
|
ShowListKind::Tables => {
|
||||||
@@ -5929,6 +5938,64 @@ fn do_show_list(
|
|||||||
Ok(lines)
|
Ok(lines)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Detail lines for one named relationship or index (V5a): a
|
||||||
|
/// labelled block, or a friendly "no such item" line. `Tables` is
|
||||||
|
/// never routed here (the table singular is `ShowTable`); the
|
||||||
|
/// defensive arm keeps the match total without a panic.
|
||||||
|
fn do_show_one(
|
||||||
|
conn: &Connection,
|
||||||
|
kind: crate::dsl::command::ShowListKind,
|
||||||
|
name: &str,
|
||||||
|
) -> Result<Vec<String>, DbError> {
|
||||||
|
use crate::dsl::command::ShowListKind;
|
||||||
|
let mut lines = Vec::new();
|
||||||
|
match kind {
|
||||||
|
ShowListKind::Relationships => match read_all_relationships(conn)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|r| r.name == name)
|
||||||
|
{
|
||||||
|
None => lines.push(format!("No relationship named `{name}`.")),
|
||||||
|
Some(r) => {
|
||||||
|
lines.push(format!("Relationship `{}`:", r.name));
|
||||||
|
lines.push(format!(
|
||||||
|
" {}.{} → {}.{}",
|
||||||
|
r.parent_table, r.parent_column, r.child_table, r.child_column
|
||||||
|
));
|
||||||
|
lines.push(format!(" on delete {}", r.on_delete.keyword()));
|
||||||
|
lines.push(format!(" on update {}", r.on_update.keyword()));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
ShowListKind::Indexes => {
|
||||||
|
// Find the user-created index by name across all tables.
|
||||||
|
let mut found = None;
|
||||||
|
for table in do_list_tables(conn)? {
|
||||||
|
if let Some(ix) = read_table_indexes(conn, &table)?
|
||||||
|
.into_iter()
|
||||||
|
.find(|ix| ix.name == name)
|
||||||
|
{
|
||||||
|
found = Some((table, ix));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match found {
|
||||||
|
None => lines.push(format!("No index named `{name}`.")),
|
||||||
|
Some((table, ix)) => {
|
||||||
|
lines.push(format!("Index `{}` on {table}:", ix.name));
|
||||||
|
lines.push(format!(" columns: {}", ix.columns.join(", ")));
|
||||||
|
lines.push(format!(
|
||||||
|
" unique: {}",
|
||||||
|
if ix.unique { "yes" } else { "no" }
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ShowListKind::Tables => {
|
||||||
|
lines.push(format!("No relationship or index named `{name}`."));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(lines)
|
||||||
|
}
|
||||||
|
|
||||||
/// Internal full schema of a table, sufficient to regenerate
|
/// Internal full schema of a table, sufficient to regenerate
|
||||||
/// its `CREATE TABLE` statement during the rebuild dance.
|
/// its `CREATE TABLE` statement during the rebuild dance.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|||||||
+19
-5
@@ -332,12 +332,16 @@ pub enum Command {
|
|||||||
ShowTable {
|
ShowTable {
|
||||||
name: String,
|
name: String,
|
||||||
},
|
},
|
||||||
/// Re-display a whole schema collection — every table,
|
/// Re-display a schema collection in the output (V5/V5a). With
|
||||||
/// relationship, or index — as a list in the output (V5). The
|
/// `name: None`, the whole collection — every table,
|
||||||
/// read-only "all items" sibling of `ShowTable`; pure display,
|
/// relationship, or index (the list-all forms). With
|
||||||
/// no schema change.
|
/// `name: Some(_)`, one named item's detail — `show
|
||||||
|
/// relationship <name>` / `show index <name>` (V5a); the table
|
||||||
|
/// singular is the separate `ShowTable`, so a named `Tables`
|
||||||
|
/// never occurs. Read-only; pure display, no schema change.
|
||||||
ShowList {
|
ShowList {
|
||||||
kind: ShowListKind,
|
kind: ShowListKind,
|
||||||
|
name: Option<String>,
|
||||||
},
|
},
|
||||||
/// Insert a single row. `columns` is `None` for the natural-
|
/// Insert a single row. `columns` is `None` for the natural-
|
||||||
/// order short form (`insert into T values (...)`); the
|
/// order short form (`insert into T values (...)`); the
|
||||||
@@ -903,7 +907,17 @@ impl Command {
|
|||||||
Self::AddConstraint { .. } => "add constraint",
|
Self::AddConstraint { .. } => "add constraint",
|
||||||
Self::DropConstraint { .. } => "drop constraint",
|
Self::DropConstraint { .. } => "drop constraint",
|
||||||
Self::ShowTable { .. } => "show table",
|
Self::ShowTable { .. } => "show table",
|
||||||
Self::ShowList { kind } => kind.command_name(),
|
// A named item reports the singular verb; the list-all
|
||||||
|
// forms report the plural (`kind.command_name()`).
|
||||||
|
Self::ShowList {
|
||||||
|
kind: ShowListKind::Relationships,
|
||||||
|
name: Some(_),
|
||||||
|
} => "show relationship",
|
||||||
|
Self::ShowList {
|
||||||
|
kind: ShowListKind::Indexes,
|
||||||
|
name: Some(_),
|
||||||
|
} => "show index",
|
||||||
|
Self::ShowList { kind, .. } => kind.command_name(),
|
||||||
Self::Insert { .. } => "insert into",
|
Self::Insert { .. } => "insert into",
|
||||||
Self::Update { .. } => "update",
|
Self::Update { .. } => "update",
|
||||||
Self::Delete { .. } => "delete from",
|
Self::Delete { .. } => "delete from",
|
||||||
|
|||||||
@@ -107,12 +107,53 @@ const SHOW_TABLES: Node = Node::Word(Word::keyword("tables"));
|
|||||||
const SHOW_RELATIONSHIPS: Node = Node::Word(Word::keyword("relationships"));
|
const SHOW_RELATIONSHIPS: Node = Node::Word(Word::keyword("relationships"));
|
||||||
const SHOW_INDEXES: Node = Node::Word(Word::keyword("indexes"));
|
const SHOW_INDEXES: Node = Node::Word(Word::keyword("indexes"));
|
||||||
|
|
||||||
|
// `show relationship <name>` / `show index <name>` — singular
|
||||||
|
// per-item detail (V5a). The name slot reuses the existing
|
||||||
|
// completion sources (relationship / index names). Distinct
|
||||||
|
// keyword tokens from the plurals (`relationship` ≠
|
||||||
|
// `relationships`), so Choice ordering is irrelevant.
|
||||||
|
const SHOW_RELATIONSHIP_NAME: Node = Node::Ident {
|
||||||
|
source: IdentSource::Relationships,
|
||||||
|
role: "relationship_name",
|
||||||
|
validator: None,
|
||||||
|
highlight_override: None,
|
||||||
|
writes_table: false,
|
||||||
|
writes_column: false,
|
||||||
|
writes_user_listed_column: false,
|
||||||
|
writes_table_alias: false,
|
||||||
|
writes_cte_name: false,
|
||||||
|
writes_projection_alias: false,
|
||||||
|
};
|
||||||
|
const SHOW_RELATIONSHIP_NODES: &[Node] = &[
|
||||||
|
Node::Word(Word::keyword("relationship")),
|
||||||
|
SHOW_RELATIONSHIP_NAME,
|
||||||
|
];
|
||||||
|
const SHOW_RELATIONSHIP: Node = Node::Seq(SHOW_RELATIONSHIP_NODES);
|
||||||
|
|
||||||
|
const SHOW_INDEX_NAME: Node = Node::Ident {
|
||||||
|
source: IdentSource::Indexes,
|
||||||
|
role: "index_name",
|
||||||
|
validator: None,
|
||||||
|
highlight_override: None,
|
||||||
|
writes_table: false,
|
||||||
|
writes_column: false,
|
||||||
|
writes_user_listed_column: false,
|
||||||
|
writes_table_alias: false,
|
||||||
|
writes_cte_name: false,
|
||||||
|
writes_projection_alias: false,
|
||||||
|
};
|
||||||
|
const SHOW_INDEX_NODES: &[Node] =
|
||||||
|
&[Node::Word(Word::keyword("index")), SHOW_INDEX_NAME];
|
||||||
|
const SHOW_INDEX: Node = Node::Seq(SHOW_INDEX_NODES);
|
||||||
|
|
||||||
const SHOW_CHOICES: &[Node] = &[
|
const SHOW_CHOICES: &[Node] = &[
|
||||||
SHOW_DATA,
|
SHOW_DATA,
|
||||||
SHOW_TABLE,
|
SHOW_TABLE,
|
||||||
SHOW_TABLES,
|
SHOW_TABLES,
|
||||||
SHOW_RELATIONSHIPS,
|
SHOW_RELATIONSHIPS,
|
||||||
SHOW_INDEXES,
|
SHOW_INDEXES,
|
||||||
|
SHOW_RELATIONSHIP,
|
||||||
|
SHOW_INDEX,
|
||||||
];
|
];
|
||||||
const SHOW_SHAPE: Node = Node::Choice(SHOW_CHOICES);
|
const SHOW_SHAPE: Node = Node::Choice(SHOW_CHOICES);
|
||||||
|
|
||||||
@@ -576,12 +617,24 @@ fn build_show(path: &MatchedPath, _source: &str) -> Result<Command, ValidationEr
|
|||||||
}),
|
}),
|
||||||
Some("tables") => Ok(Command::ShowList {
|
Some("tables") => Ok(Command::ShowList {
|
||||||
kind: ShowListKind::Tables,
|
kind: ShowListKind::Tables,
|
||||||
|
name: None,
|
||||||
}),
|
}),
|
||||||
Some("relationships") => Ok(Command::ShowList {
|
Some("relationships") => Ok(Command::ShowList {
|
||||||
kind: ShowListKind::Relationships,
|
kind: ShowListKind::Relationships,
|
||||||
|
name: None,
|
||||||
}),
|
}),
|
||||||
Some("indexes") => Ok(Command::ShowList {
|
Some("indexes") => Ok(Command::ShowList {
|
||||||
kind: ShowListKind::Indexes,
|
kind: ShowListKind::Indexes,
|
||||||
|
name: None,
|
||||||
|
}),
|
||||||
|
// V5a singular per-item detail — carry the named item.
|
||||||
|
Some("relationship") => Ok(Command::ShowList {
|
||||||
|
kind: ShowListKind::Relationships,
|
||||||
|
name: Some(require_ident(path, "relationship_name")?),
|
||||||
|
}),
|
||||||
|
Some("index") => Ok(Command::ShowList {
|
||||||
|
kind: ShowListKind::Indexes,
|
||||||
|
name: Some(require_ident(path, "index_name")?),
|
||||||
}),
|
}),
|
||||||
_ => Err(ValidationError {
|
_ => Err(ValidationError {
|
||||||
message_key: "parse.error_wrapper",
|
message_key: "parse.error_wrapper",
|
||||||
@@ -1395,6 +1448,8 @@ pub static SHOW: CommandNode = CommandNode {
|
|||||||
"parse.usage.show_tables",
|
"parse.usage.show_tables",
|
||||||
"parse.usage.show_relationships",
|
"parse.usage.show_relationships",
|
||||||
"parse.usage.show_indexes",
|
"parse.usage.show_indexes",
|
||||||
|
"parse.usage.show_relationship",
|
||||||
|
"parse.usage.show_index",
|
||||||
],};
|
],};
|
||||||
|
|
||||||
pub static INSERT: CommandNode = CommandNode {
|
pub static INSERT: CommandNode = CommandNode {
|
||||||
|
|||||||
@@ -311,6 +311,8 @@ pub const KEYS_AND_PLACEHOLDERS: &[(&str, &[&str])] = &[
|
|||||||
("parse.usage.show_tables", &[]),
|
("parse.usage.show_tables", &[]),
|
||||||
("parse.usage.show_relationships", &[]),
|
("parse.usage.show_relationships", &[]),
|
||||||
("parse.usage.show_indexes", &[]),
|
("parse.usage.show_indexes", &[]),
|
||||||
|
("parse.usage.show_relationship", &[]),
|
||||||
|
("parse.usage.show_index", &[]),
|
||||||
("parse.usage.update", &[]),
|
("parse.usage.update", &[]),
|
||||||
("parse.usage.with", &[]),
|
("parse.usage.with", &[]),
|
||||||
("parse.expect.select_projection", &[]),
|
("parse.expect.select_projection", &[]),
|
||||||
|
|||||||
@@ -325,6 +325,8 @@ help:
|
|||||||
show tables — list all tables
|
show tables — list all tables
|
||||||
show relationships — list all relationships
|
show relationships — list all relationships
|
||||||
show indexes — list all indexes
|
show indexes — list all indexes
|
||||||
|
show relationship <name> — show one relationship's detail
|
||||||
|
show index <name> — show one index's detail
|
||||||
insert: |-
|
insert: |-
|
||||||
insert into <T> [(cols)] [values] (vals) — add a row
|
insert into <T> [(cols)] [values] (vals) — add a row
|
||||||
update: |-
|
update: |-
|
||||||
@@ -565,6 +567,8 @@ parse:
|
|||||||
show_tables: "show tables"
|
show_tables: "show tables"
|
||||||
show_relationships: "show relationships"
|
show_relationships: "show relationships"
|
||||||
show_indexes: "show indexes"
|
show_indexes: "show indexes"
|
||||||
|
show_relationship: "show relationship <name>"
|
||||||
|
show_index: "show index <name>"
|
||||||
insert: "insert into <Table> [(<col>[, ...])] [values] (<value>[, ...])"
|
insert: "insert into <Table> [(<col>[, ...])] [values] (<value>[, ...])"
|
||||||
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
|
update: "update <Table> set <col>=<value>[, ...] (where <col>=<value> | --all-rows)"
|
||||||
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
|
delete: "delete from <Table> (where <col>=<value> | --all-rows)"
|
||||||
|
|||||||
@@ -2061,6 +2061,8 @@ mod tests {
|
|||||||
"tables".to_string(),
|
"tables".to_string(),
|
||||||
"relationships".to_string(),
|
"relationships".to_string(),
|
||||||
"indexes".to_string(),
|
"indexes".to_string(),
|
||||||
|
"relationship".to_string(),
|
||||||
|
"index".to_string(),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
+4
-3
@@ -2774,9 +2774,10 @@ async fn execute_command_typed(
|
|||||||
.describe_table(name, src)
|
.describe_table(name, src)
|
||||||
.await
|
.await
|
||||||
.map(|d| CommandOutcome::Schema(Some(d))),
|
.map(|d| CommandOutcome::Schema(Some(d))),
|
||||||
Command::ShowList { kind } => {
|
Command::ShowList { kind, name } => database
|
||||||
database.show_list(kind).await.map(CommandOutcome::ShowList)
|
.show_list(kind, name)
|
||||||
}
|
.await
|
||||||
|
.map(CommandOutcome::ShowList),
|
||||||
Command::Insert {
|
Command::Insert {
|
||||||
table,
|
table,
|
||||||
columns,
|
columns,
|
||||||
|
|||||||
@@ -168,6 +168,8 @@ fn near_miss_matrix_committed_multiforms() {
|
|||||||
("drop index on T", false, &["after `drop index on T`, expected `(`", "drop index on <Table>"]),
|
("drop index on T", false, &["after `drop index on T`, expected `(`", "drop index on <Table>"]),
|
||||||
("drop relationship", false, &["after `drop relationship`, expected `from` or relationship name", "drop relationship <Name>"]),
|
("drop relationship", false, &["after `drop relationship`, expected `from` or relationship name", "drop relationship <Name>"]),
|
||||||
("show table", false, &["after `show table`, expected table name", "show table <Table>"]),
|
("show table", false, &["after `show table`, expected table name", "show table <Table>"]),
|
||||||
|
("show relationship", false, &["after `show relationship`, expected relationship name", "show relationship <name>"]),
|
||||||
|
("show index", false, &["after `show index`, expected index name", "show index <name>"]),
|
||||||
("change column in table T: c", false, &["after `change column in table T: c`, expected `(`", "change column [in] [table]"]),
|
("change column in table T: c", false, &["after `change column in table T: c`, expected `(`", "change column [in] [table]"]),
|
||||||
// advanced committed multi-forms
|
// advanced committed multi-forms
|
||||||
("create index on", true, &["after `create index on`, expected table name", "create [unique] index"]),
|
("create index on", true, &["after `create index on`, expected table name", "create [unique] index"]),
|
||||||
|
|||||||
+93
-10
@@ -33,7 +33,8 @@ fn show_tables_parses_as_show_list_tables() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command("show tables").expect("parses"),
|
parse_command("show tables").expect("parses"),
|
||||||
Command::ShowList {
|
Command::ShowList {
|
||||||
kind: ShowListKind::Tables
|
kind: ShowListKind::Tables,
|
||||||
|
name: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -43,7 +44,8 @@ fn show_relationships_parses_as_show_list_relationships() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command("show relationships").expect("parses"),
|
parse_command("show relationships").expect("parses"),
|
||||||
Command::ShowList {
|
Command::ShowList {
|
||||||
kind: ShowListKind::Relationships
|
kind: ShowListKind::Relationships,
|
||||||
|
name: None,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -53,7 +55,30 @@ fn show_indexes_parses_as_show_list_indexes() {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
parse_command("show indexes").expect("parses"),
|
parse_command("show indexes").expect("parses"),
|
||||||
Command::ShowList {
|
Command::ShowList {
|
||||||
kind: ShowListKind::Indexes
|
kind: ShowListKind::Indexes,
|
||||||
|
name: None,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_relationship_singular_parses_with_name() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_command("show relationship orders_customer").expect("parses"),
|
||||||
|
Command::ShowList {
|
||||||
|
kind: ShowListKind::Relationships,
|
||||||
|
name: Some("orders_customer".to_string()),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_index_singular_parses_with_name() {
|
||||||
|
assert_eq!(
|
||||||
|
parse_command("show index idx_orders_customer").expect("parses"),
|
||||||
|
Command::ShowList {
|
||||||
|
kind: ShowListKind::Indexes,
|
||||||
|
name: Some("idx_orders_customer".to_string()),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -145,7 +170,7 @@ fn show_tables_lists_all_user_tables_with_count_header() {
|
|||||||
let rt = rt();
|
let rt = rt();
|
||||||
rt.block_on(seed_schema(&db));
|
rt.block_on(seed_schema(&db));
|
||||||
let lines = rt
|
let lines = rt
|
||||||
.block_on(db.show_list(ShowListKind::Tables))
|
.block_on(db.show_list(ShowListKind::Tables, None))
|
||||||
.expect("show tables");
|
.expect("show tables");
|
||||||
assert_eq!(lines[0], "Tables (2):", "count header");
|
assert_eq!(lines[0], "Tables (2):", "count header");
|
||||||
assert!(
|
assert!(
|
||||||
@@ -164,7 +189,7 @@ fn show_relationships_lists_name_endpoints_and_nondefault_action() {
|
|||||||
let rt = rt();
|
let rt = rt();
|
||||||
rt.block_on(seed_schema(&db));
|
rt.block_on(seed_schema(&db));
|
||||||
let lines = rt
|
let lines = rt
|
||||||
.block_on(db.show_list(ShowListKind::Relationships))
|
.block_on(db.show_list(ShowListKind::Relationships, None))
|
||||||
.expect("show relationships");
|
.expect("show relationships");
|
||||||
assert_eq!(lines[0], "Relationships (1):");
|
assert_eq!(lines[0], "Relationships (1):");
|
||||||
// Name, both endpoints, and the non-default ON DELETE CASCADE
|
// Name, both endpoints, and the non-default ON DELETE CASCADE
|
||||||
@@ -182,7 +207,7 @@ fn show_indexes_lists_qualified_name_and_columns() {
|
|||||||
let rt = rt();
|
let rt = rt();
|
||||||
rt.block_on(seed_schema(&db));
|
rt.block_on(seed_schema(&db));
|
||||||
let lines = rt
|
let lines = rt
|
||||||
.block_on(db.show_list(ShowListKind::Indexes))
|
.block_on(db.show_list(ShowListKind::Indexes, None))
|
||||||
.expect("show indexes");
|
.expect("show indexes");
|
||||||
assert_eq!(lines[0], "Indexes (1):");
|
assert_eq!(lines[0], "Indexes (1):");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -197,20 +222,76 @@ fn show_lists_report_empty_collections_with_friendly_lines() {
|
|||||||
let rt = rt();
|
let rt = rt();
|
||||||
// No schema seeded — every kind is empty.
|
// No schema seeded — every kind is empty.
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rt.block_on(db.show_list(ShowListKind::Tables)).unwrap(),
|
rt.block_on(db.show_list(ShowListKind::Tables, None)).unwrap(),
|
||||||
vec!["No tables in this project yet.".to_string()],
|
vec!["No tables in this project yet.".to_string()],
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rt.block_on(db.show_list(ShowListKind::Relationships))
|
rt.block_on(db.show_list(ShowListKind::Relationships, None))
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
vec!["No relationships in this project yet.".to_string()],
|
vec!["No relationships in this project yet.".to_string()],
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
rt.block_on(db.show_list(ShowListKind::Indexes)).unwrap(),
|
rt.block_on(db.show_list(ShowListKind::Indexes, None)).unwrap(),
|
||||||
vec!["No indexes in this project yet.".to_string()],
|
vec!["No indexes in this project yet.".to_string()],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =================================================================
|
||||||
|
// V5a — singular per-item detail
|
||||||
|
// =================================================================
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_one_relationship_renders_detail_block() {
|
||||||
|
let (_p, db, _dir) = open_project_db();
|
||||||
|
let rt = rt();
|
||||||
|
rt.block_on(seed_schema(&db));
|
||||||
|
let lines = rt
|
||||||
|
.block_on(db.show_list(ShowListKind::Relationships, Some("orders_customer".to_string())))
|
||||||
|
.expect("show relationship");
|
||||||
|
assert_eq!(lines[0], "Relationship `orders_customer`:");
|
||||||
|
assert_eq!(lines[1], " Customers.id → Orders.customer_id");
|
||||||
|
assert!(
|
||||||
|
lines.iter().any(|l| l == " on delete cascade"),
|
||||||
|
"on-delete shown: {lines:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_one_index_renders_detail_block() {
|
||||||
|
let (_p, db, _dir) = open_project_db();
|
||||||
|
let rt = rt();
|
||||||
|
rt.block_on(seed_schema(&db));
|
||||||
|
let lines = rt
|
||||||
|
.block_on(db.show_list(ShowListKind::Indexes, Some("idx_orders_customer".to_string())))
|
||||||
|
.expect("show index");
|
||||||
|
assert_eq!(lines[0], "Index `idx_orders_customer` on Orders:");
|
||||||
|
assert!(
|
||||||
|
lines.iter().any(|l| l == " columns: customer_id"),
|
||||||
|
"columns shown: {lines:?}",
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
lines.iter().any(|l| l == " unique: no"),
|
||||||
|
"uniqueness shown: {lines:?}",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn show_one_unknown_name_reports_friendly_not_found() {
|
||||||
|
let (_p, db, _dir) = open_project_db();
|
||||||
|
let rt = rt();
|
||||||
|
rt.block_on(seed_schema(&db));
|
||||||
|
assert_eq!(
|
||||||
|
rt.block_on(db.show_list(ShowListKind::Relationships, Some("nope".to_string())))
|
||||||
|
.unwrap(),
|
||||||
|
vec!["No relationship named `nope`.".to_string()],
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
rt.block_on(db.show_list(ShowListKind::Indexes, Some("nope".to_string())))
|
||||||
|
.unwrap(),
|
||||||
|
vec!["No index named `nope`.".to_string()],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// =================================================================
|
// =================================================================
|
||||||
// App end-to-end
|
// App end-to-end
|
||||||
// =================================================================
|
// =================================================================
|
||||||
@@ -241,7 +322,8 @@ fn app_show_tables_dispatches_show_list_command() {
|
|||||||
a,
|
a,
|
||||||
Action::ExecuteDsl {
|
Action::ExecuteDsl {
|
||||||
command: Command::ShowList {
|
command: Command::ShowList {
|
||||||
kind: ShowListKind::Tables
|
kind: ShowListKind::Tables,
|
||||||
|
name: None,
|
||||||
},
|
},
|
||||||
..
|
..
|
||||||
}
|
}
|
||||||
@@ -262,6 +344,7 @@ fn app_renders_show_list_lines_as_system_output() {
|
|||||||
app.update(AppEvent::DslShowListSucceeded {
|
app.update(AppEvent::DslShowListSucceeded {
|
||||||
command: Command::ShowList {
|
command: Command::ShowList {
|
||||||
kind: ShowListKind::Tables,
|
kind: ShowListKind::Tables,
|
||||||
|
name: None,
|
||||||
},
|
},
|
||||||
lines: vec!["Tables (1):".to_string(), " Customers".to_string()],
|
lines: vec!["Tables (1):".to_string(), " Customers".to_string()],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ fn command_kind_label(cmd: &rdbms_playground::dsl::Command) -> String {
|
|||||||
AddConstraint { .. } => "AddConstraint".into(),
|
AddConstraint { .. } => "AddConstraint".into(),
|
||||||
DropConstraint { .. } => "DropConstraint".into(),
|
DropConstraint { .. } => "DropConstraint".into(),
|
||||||
ShowTable { .. } => "ShowTable".into(),
|
ShowTable { .. } => "ShowTable".into(),
|
||||||
ShowList { kind } => format!("ShowList({kind:?})"),
|
ShowList { kind, name } => format!("ShowList({kind:?}, {})", name.is_some()),
|
||||||
Insert { .. } => "Insert".into(),
|
Insert { .. } => "Insert".into(),
|
||||||
Update { .. } => "Update".into(),
|
Update { .. } => "Update".into(),
|
||||||
Delete { .. } => "Delete".into(),
|
Delete { .. } => "Delete".into(),
|
||||||
|
|||||||
Reference in New Issue
Block a user