Skip to main content

turso_orm/query/
update.rs

1//! The `UPDATE` builders, modeled by [`UpdateOne`] and [`UpdateMany`].
2//!
3//! [`UpdateOne`] writes the `Set` attributes of an active model back to the
4//! row its primary key names and returns the stored model through
5//! `RETURNING *`. [`UpdateMany`] is the free-form counterpart for bulk
6//! updates driven by an arbitrary `WHERE`.
7//!
8//! Both builders treat "nothing to set" as a no-op rather than rendering an
9//! `UPDATE` without a `SET` clause, which SQLite would reject; `UpdateOne`
10//! fetches the row instead so that callers still get a model back.
11
12use std::marker::PhantomData;
13
14use turso_orm_driver::ConnectionTrait;
15use turso_sql::{Build, Expr, IntoCondition, Returning, Statement, Value};
16
17use crate::entity::{
18    ActiveModelTrait, ActiveValue, EntityTrait, FromQueryResult, IdenStatic, Iterable,
19};
20use crate::query::select::pk_condition;
21use crate::{DbErr, Result};
22
23/// The outcome of an update.
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
25pub struct UpdateResult {
26    /// The number of rows changed.
27    pub rows_affected: u64,
28}
29
30/// An `UPDATE` of one active model, matched by primary key.
31#[derive(Clone, Debug)]
32pub struct UpdateOne<A: ActiveModelTrait> {
33    /// The active model whose `Set` attributes are written.
34    model: A,
35}
36
37impl<A: ActiveModelTrait> UpdateOne<A> {
38    /// Wraps the active model to update.
39    pub(crate) fn new(model: A) -> Self {
40        Self { model }
41    }
42
43    /// Builds the statement, or `None` when no attribute is `Set`.
44    ///
45    /// # Errors
46    ///
47    /// Returns [`DbErr::PrimaryKeyNotSet`] when a key attribute is `NotSet`.
48    fn statement(&self) -> Result<Option<turso_sql::Update>> {
49        let pk = self
50            .model
51            .get_primary_key_value()
52            .ok_or(DbErr::PrimaryKeyNotSet)?;
53        let mut update = turso_sql::Query::update().table(<A::Entity as EntityTrait>::TABLE_NAME);
54        let mut any = false;
55        for c in <<A::Entity as EntityTrait>::Column as Iterable>::iter() {
56            if let ActiveValue::Set(v) = self.model.get(c) {
57                update = update.value(c.as_str(), Expr::val(v));
58                any = true;
59            }
60        }
61        if !any {
62            return Ok(None);
63        }
64        update = update
65            .and_where(pk_condition::<A::Entity>(pk))
66            .returning(Returning::All);
67        Ok(Some(update))
68    }
69
70    /// Executes the update and returns the stored model.
71    ///
72    /// With no `Set` attribute the row is fetched instead of updated, so the
73    /// result is the same either way.
74    ///
75    /// # Errors
76    ///
77    /// Returns [`DbErr::PrimaryKeyNotSet`] when a key attribute is `NotSet`;
78    /// [`DbErr::RecordNotUpdated`] when no row matches the key;
79    /// [`DbErr::Driver`] when the statement fails or the returned row cannot
80    /// be decoded.
81    pub async fn exec<C: ConnectionTrait>(
82        self,
83        db: &C,
84    ) -> Result<<A::Entity as EntityTrait>::Model> {
85        if let Some(update) = self.statement()? {
86            let row = db
87                .query_one(update.to_statement())
88                .await?
89                .ok_or(DbErr::RecordNotUpdated)?;
90            <A::Entity as EntityTrait>::Model::from_query_result(&row, "")
91        } else {
92            let pk = self
93                .model
94                .get_primary_key_value()
95                .ok_or(DbErr::PrimaryKeyNotSet)?;
96            crate::query::Select::<A::Entity>::new()
97                .filter_by_pk(pk)
98                .one(db)
99                .await?
100                .ok_or(DbErr::RecordNotUpdated)
101        }
102    }
103}
104
105/// An `UPDATE` over any number of rows of an entity.
106#[derive(Clone, Debug)]
107pub struct UpdateMany<E: EntityTrait> {
108    /// The statement being assembled.
109    query: turso_sql::Update,
110    /// Ties the builder to its entity without storing it.
111    _e: PhantomData<E>,
112}
113
114impl<E: EntityTrait> UpdateMany<E> {
115    /// Starts `UPDATE table` with no `SET` and no condition.
116    pub(crate) fn new() -> Self {
117        Self {
118            query: turso_sql::Query::update().table(E::TABLE_NAME),
119            _e: PhantomData,
120        }
121    }
122
123    /// Adds `SET column = expr`.
124    #[must_use]
125    pub fn col_expr(mut self, column: E::Column, value: impl Into<Expr>) -> Self {
126        self.query = self.query.value(column.as_str(), value);
127        self
128    }
129
130    /// Adds `SET column = value` from a plain value.
131    #[must_use]
132    pub fn col(self, column: E::Column, value: impl Into<Value>) -> Self {
133        self.col_expr(column, Expr::val(value))
134    }
135
136    /// Adds a `SET` for every `Set` attribute of `model`.
137    #[must_use]
138    pub fn set<A: ActiveModelTrait<Entity = E>>(mut self, model: &A) -> Self {
139        for c in E::Column::iter() {
140            if let ActiveValue::Set(v) = model.get(c) {
141                self.query = self.query.value(c.as_str(), Expr::val(v));
142            }
143        }
144        self
145    }
146
147    /// Adds a `WHERE` condition, `AND`ed with the previous ones.
148    #[must_use]
149    pub fn filter(mut self, cond: impl IntoCondition) -> Self {
150        self.query = self.query.and_where(cond);
151        self
152    }
153
154    /// Renders the statement.
155    pub fn build(&self) -> Statement {
156        self.query.to_statement()
157    }
158
159    /// Executes the update; with nothing to set, reports zero rows without touching the database.
160    ///
161    /// # Errors
162    ///
163    /// Returns [`DbErr::Driver`] when the statement fails.
164    pub async fn exec<C: ConnectionTrait>(self, db: &C) -> Result<UpdateResult> {
165        if !self.query.has_sets() {
166            return Ok(UpdateResult { rows_affected: 0 });
167        }
168        let result = db.execute(self.build()).await?;
169        Ok(UpdateResult {
170            rows_affected: result.rows_affected,
171        })
172    }
173
174    /// Executes the update with `RETURNING *` and decodes the changed rows.
175    ///
176    /// With nothing to set, returns an empty list without touching the database.
177    ///
178    /// # Errors
179    ///
180    /// Returns [`DbErr::Driver`] when the statement fails or a returned row
181    /// cannot be decoded.
182    pub async fn exec_with_returning<C: ConnectionTrait>(
183        mut self,
184        db: &C,
185    ) -> Result<Vec<E::Model>> {
186        if !self.query.has_sets() {
187            return Ok(Vec::new());
188        }
189        self.query = self.query.returning(Returning::All);
190        db.query_all(self.build())
191            .await?
192            .iter()
193            .map(|r| E::Model::from_query_result(r, ""))
194            .collect()
195    }
196}