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