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}