//! The Command AST. //! //! `Command` is the parser's output and the database worker's //! input. Each variant carries fully validated data — the parser //! is responsible for shape, the database worker for semantics //! (e.g. "table does not exist"). //! //! The shape supports compound primary keys natively even though //! only the dedicated `with pk a:int,b:int` grammar exposes them //! today. Future grammar extensions (inline column specs, `set //! primary key`, junction-table convenience commands) emit into //! the same shape. use crate::dsl::action::ReferentialAction; use crate::dsl::types::Type; use crate::dsl::value::Value; /// A column at table-creation time: a name and a user-facing /// type. Constraints beyond `PRIMARY KEY` (NOT NULL, UNIQUE, /// CHECK, DEFAULT) come in later iterations. #[derive(Debug, Clone, PartialEq, Eq)] pub struct ColumnSpec { pub name: String, pub ty: Type, } #[derive(Debug, Clone, PartialEq, Eq)] pub enum Command { CreateTable { name: String, /// Columns to create, in declaration order. columns: Vec, /// Names of columns forming the primary key. Length 1 is /// a single PK; length >= 2 is a compound PK; length 0 /// indicates no primary key (a future grammar option, /// not produced by today's parser). primary_key: Vec, }, DropTable { name: String, }, AddColumn { table: String, column: String, ty: Type, }, /// Remove a column from a table. Refused if the column is /// part of the primary key or is involved in a declared /// relationship — drop the relationship first. DropColumn { table: String, column: String, }, /// Rename a column. SQLite handles cascading renames in /// FK references on other tables; the executor mirrors /// the change into our `__rdbms_playground_columns` and /// `__rdbms_playground_relationships` metadata tables in /// the same transaction. RenameColumn { table: String, old: String, new: String, }, /// Change a column's type. Implemented via the /// rebuild-table primitive (ADR-0013) since SQLite's /// ALTER TABLE does not support type changes. Refused /// for PK columns and for columns involved in a declared /// relationship — those would require cascading FK /// type updates the v1 surface deliberately doesn't /// expose. ChangeColumnType { table: String, column: String, ty: Type, }, /// Establish a 1:n relationship: parent_table.parent_column /// is the primary-key side; child_table.child_column is the /// foreign-key side. `name` is optional — when `None`, the /// executor auto-generates one (`__to_`). /// `create_fk` requests the child column be created /// automatically with the appropriate type if it is missing. AddRelationship { name: Option, parent_table: String, parent_column: String, child_table: String, child_column: String, on_delete: ReferentialAction, on_update: ReferentialAction, create_fk: bool, }, /// Drop a relationship by either user-given/auto-generated /// name, or by positional reference to the FK endpoints. DropRelationship { selector: RelationshipSelector, }, /// Re-display a table's structure in the output. Doesn't /// change schema; useful when the user wants to look at a /// table they aren't currently DDL'ing on. ShowTable { name: String, }, /// Insert a single row. `columns` is `None` for the natural- /// order short form (`insert into T values (...)`); the /// executor fills in the column list by walking the schema. Insert { table: String, columns: Option>, values: Vec, }, /// Update rows matching the WHERE clause (or all rows when /// `all_rows` is set, per ADR-0009 opt-in convention). Update { table: String, assignments: Vec<(String, Value)>, filter: RowFilter, }, Delete { table: String, filter: RowFilter, }, /// Render the rows of a table as a data view in the output. ShowData { name: String, }, } /// How an UPDATE / DELETE selects which rows to operate on. /// `Where` is the default safe form. `AllRows` is the explicit /// `--all-rows` flag opt-in for unfiltered operations. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RowFilter { Where { column: String, value: Value }, AllRows, } /// How a `drop relationship` command identifies the relationship /// to remove. Both forms are accepted; the executor resolves to /// a single row in the metadata table. #[derive(Debug, Clone, PartialEq, Eq)] pub enum RelationshipSelector { Named { name: String }, Endpoints { parent_table: String, parent_column: String, child_table: String, child_column: String, }, } impl std::fmt::Display for RelationshipSelector { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Self::Named { name } => write!(f, "{name}"), Self::Endpoints { parent_table, parent_column, child_table, child_column, } => write!( f, "from {parent_table}.{parent_column} to {child_table}.{child_column}" ), } } } impl Command { /// Short label for log output and result rendering. #[must_use] pub const fn verb(&self) -> &'static str { match self { Self::CreateTable { .. } => "create table", Self::DropTable { .. } => "drop table", Self::AddColumn { .. } => "add column", Self::DropColumn { .. } => "drop column", Self::RenameColumn { .. } => "rename column", Self::ChangeColumnType { .. } => "change column", Self::AddRelationship { .. } => "add relationship", Self::DropRelationship { .. } => "drop relationship", Self::ShowTable { .. } => "show table", Self::Insert { .. } => "insert into", Self::Update { .. } => "update", Self::Delete { .. } => "delete from", Self::ShowData { .. } => "show data", } } /// The table whose structure most directly reflects the /// outcome of this command. For relationships this is the /// child table, since the FK constraint physically belongs /// there and our describe view shows both sides anyway. #[must_use] pub fn target_table(&self) -> &str { match self { Self::CreateTable { name, .. } | Self::DropTable { name } | Self::ShowTable { name } | Self::ShowData { name } => name, Self::AddColumn { table, .. } | Self::DropColumn { table, .. } | Self::RenameColumn { table, .. } | Self::ChangeColumnType { table, .. } | Self::Insert { table, .. } | Self::Update { table, .. } | Self::Delete { table, .. } => table, // For relationships we focus on the parent (1-side): // the structure rendering after add/drop shows that // table's "Referenced by" entry, which is what the // user looks at to confirm the relationship. Self::AddRelationship { parent_table, .. } => parent_table, Self::DropRelationship { selector } => match selector { RelationshipSelector::Endpoints { parent_table, .. } => parent_table, // For a named drop we don't know the parent table // until the executor resolves it; the name itself // is a sensible fallback for logging. RelationshipSelector::Named { name } => name, }, } } /// Human-readable subject for the `[ok] ` /// summary line. Most commands target a single table, but /// relationship commands are better described by their /// endpoints than by either side alone. #[must_use] pub fn display_subject(&self) -> String { match self { Self::AddRelationship { parent_table, parent_column, child_table, child_column, .. } => format!("from {parent_table}.{parent_column} to {child_table}.{child_column}"), Self::DropRelationship { selector } => match selector { RelationshipSelector::Named { name } => name.clone(), RelationshipSelector::Endpoints { parent_table, parent_column, child_table, child_column, } => format!( "from {parent_table}.{parent_column} to {child_table}.{child_column}" ), }, _ => self.target_table().to_string(), } } }