toasty_core/stmt/
update.rs

1use super::{Assignments, Node, Query, Returning, Statement, Visit, VisitMut};
2use crate::{
3    schema::{app::ModelId, db::TableId},
4    stmt::{self, Condition, Filter},
5};
6
7/// An `UPDATE` statement that modifies existing records.
8///
9/// Combines a target (what to update), assignments (how to change fields),
10/// a filter (which records to update), an optional condition (a guard that
11/// must hold for the update to apply), and an optional returning clause.
12///
13/// # Examples
14///
15/// ```ignore
16/// use toasty_core::stmt::{Update, UpdateTarget, Assignments, Filter, Condition};
17/// use toasty_core::schema::app::ModelId;
18///
19/// let update = Update {
20///     target: UpdateTarget::Model(ModelId(0)),
21///     assignments: Assignments::default(),
22///     filter: Filter::default(),
23///     condition: Condition::default(),
24///     returning: None,
25/// };
26/// ```
27#[derive(Debug, Clone, PartialEq)]
28pub struct Update {
29    /// The target to update (model, table, or query).
30    pub target: UpdateTarget,
31
32    /// The field assignments to apply.
33    pub assignments: Assignments,
34
35    /// Filter selecting which records to update (`WHERE` clause).
36    pub filter: Filter,
37
38    /// An optional condition that must be satisfied for the update to apply.
39    /// Unlike `filter`, a condition failing does not produce an error but
40    /// silently skips the update.
41    pub condition: Condition,
42
43    /// Optional `RETURNING` clause.
44    pub returning: Option<Returning>,
45}
46
47impl Statement {
48    /// Returns `true` if this statement is an [`Update`].
49    pub fn is_update(&self) -> bool {
50        matches!(self, Self::Update(_))
51    }
52}
53
54/// The target of an [`Update`] statement.
55///
56/// Specifies what entity is being updated. At the model level this is a model
57/// ID or a scoped query. After lowering, it becomes a table ID.
58///
59/// # Examples
60///
61/// ```ignore
62/// use toasty_core::stmt::UpdateTarget;
63/// use toasty_core::schema::app::ModelId;
64///
65/// let target = UpdateTarget::Model(ModelId(0));
66/// assert_eq!(target.model_id(), Some(ModelId(0)));
67/// ```
68#[derive(Debug, Clone, PartialEq)]
69pub enum UpdateTarget {
70    /// Update records returned by a query. The query must select a model.
71    Query(Box<Query>),
72
73    /// Update a model by its ID.
74    Model(ModelId),
75
76    /// Update a database table (lowered form).
77    Table(TableId),
78}
79
80impl Update {
81    /// Returns a [`Query`] that selects the records this update would modify.
82    pub fn selection(&self) -> Query {
83        stmt::Query::new_select(self.target.model_id_unwrap(), self.filter.clone())
84    }
85}
86
87impl UpdateTarget {
88    /// Returns the model ID for this target, if applicable.
89    pub fn model_id(&self) -> Option<ModelId> {
90        match self {
91            Self::Model(model_id) => Some(*model_id),
92            Self::Query(query) => query
93                .body
94                .as_select()
95                .and_then(|select| select.source.model_id()),
96            _ => None,
97        }
98    }
99
100    /// Returns the model ID for this target.
101    ///
102    /// # Panics
103    ///
104    /// Panics if this is a `Table` variant.
105    #[track_caller]
106    pub fn model_id_unwrap(&self) -> ModelId {
107        match self {
108            Self::Model(model_id) => *model_id,
109            Self::Query(query) => query.body.as_select_unwrap().source.model_id_unwrap(),
110            _ => todo!("not a model"),
111        }
112    }
113
114    /// Returns `true` if this target is a `Table` variant.
115    pub fn is_table(&self) -> bool {
116        matches!(self, UpdateTarget::Table(..))
117    }
118
119    /// Creates a `Table` target from a table ID.
120    pub fn table(table: impl Into<TableId>) -> Self {
121        Self::Table(table.into())
122    }
123
124    /// Returns the table ID if this is a `Table` variant.
125    pub fn as_table(&self) -> Option<TableId> {
126        match self {
127            Self::Table(table) => Some(*table),
128            _ => None,
129        }
130    }
131
132    /// Returns the table ID, panicking if this is not a `Table` variant.
133    ///
134    /// # Panics
135    ///
136    /// Panics if this is not a `Table` variant.
137    #[track_caller]
138    pub fn as_table_unwrap(&self) -> TableId {
139        self.as_table()
140            .unwrap_or_else(|| panic!("expected UpdateTarget::Table; actual={self:#?}"))
141    }
142}
143
144impl From<Update> for Statement {
145    fn from(src: Update) -> Self {
146        Self::Update(src)
147    }
148}
149
150impl Node for Update {
151    fn visit<V: Visit>(&self, mut visit: V) {
152        visit.visit_stmt_update(self);
153    }
154
155    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
156        visit.visit_stmt_update_mut(self);
157    }
158}