Skip to main content

turso_orm/query/
insert.rs

1//! The `INSERT` builders, modeled by [`Insert`] and [`InsertMany`].
2//!
3//! Only `Set` attributes are sent, so that columns left `NotSet` take their
4//! database default; an active model with nothing set renders
5//! `DEFAULT VALUES`. [`Insert`] offers two execution paths: `exec` returns
6//! just the primary key, reconstructed from `last_insert_rowid()` for
7//! integer keys, while `exec_with_returning` uses `RETURNING *` and decodes
8//! the full stored row, which is the only way to observe defaults and
9//! non-integer keys.
10
11use std::marker::PhantomData;
12
13use turso_orm_driver::ConnectionTrait;
14use turso_sql::{Build, Expr, OnConflict, Returning, Statement};
15
16use crate::entity::{
17    ActiveModelTrait, ColumnTrait, EntityTrait, FromQueryResult, IdenStatic, Iterable,
18    PrimaryKeyTrait,
19};
20use crate::types::TryFromU64;
21use crate::{DbErr, Result};
22
23/// The outcome of an insert executed without `RETURNING`.
24#[derive(Clone, Debug)]
25pub struct InsertResult<E: EntityTrait> {
26    /// The primary key of the inserted row.
27    pub last_insert_id: <E::PrimaryKey as PrimaryKeyTrait>::ValueType,
28}
29
30/// An `INSERT` of one active model.
31#[derive(Clone, Debug)]
32pub struct Insert<A: ActiveModelTrait> {
33    /// The active model whose `Set` attributes are inserted.
34    model: A,
35    /// The optional `ON CONFLICT` clause.
36    on_conflict: Option<OnConflict>,
37}
38
39impl<A: ActiveModelTrait> Insert<A> {
40    /// Wraps the active model to insert.
41    pub(crate) fn one(model: A) -> Self {
42        Self {
43            model,
44            on_conflict: None,
45        }
46    }
47
48    /// Sets the `ON CONFLICT` clause.
49    #[must_use]
50    pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self {
51        self.on_conflict = Some(on_conflict);
52        self
53    }
54
55    /// Builds the statement, with or without `RETURNING *`.
56    fn statement(&self, returning: bool) -> turso_sql::Insert {
57        let mut columns = Vec::new();
58        let mut values = Vec::new();
59        for c in <<A::Entity as EntityTrait>::Column as Iterable>::iter() {
60            if let crate::entity::ActiveValue::Set(v) = self.model.get(c) {
61                columns.push(c.as_str());
62                values.push(Expr::val(v));
63            }
64        }
65        let mut insert =
66            turso_sql::Query::insert().into_table(<A::Entity as EntityTrait>::TABLE_NAME);
67        // An empty column list is not valid SQL; `DEFAULT VALUES` is how a
68        // row made only of defaults is inserted.
69        if columns.is_empty() {
70            insert = insert.default_values();
71        } else {
72            insert = insert.columns(columns).values(values);
73        }
74        if let Some(oc) = &self.on_conflict {
75            insert = insert.on_conflict(oc.clone());
76        }
77        if returning {
78            insert = insert.returning(Returning::All);
79        }
80        insert
81    }
82
83    /// Renders the statement without `RETURNING`.
84    pub fn build(&self) -> Statement {
85        self.statement(false).to_statement()
86    }
87
88    /// Executes the insert and returns the primary key.
89    ///
90    /// A key the caller supplied is echoed back; a generated one is read
91    /// from `last_insert_rowid()`. Non-integer keys cannot be reconstructed
92    /// this way, so use [`exec_with_returning`](Self::exec_with_returning)
93    /// for them.
94    ///
95    /// # Errors
96    ///
97    /// Returns [`DbErr::RecordNotInserted`] when the statement inserted
98    /// nothing, for example under `ON CONFLICT DO NOTHING`; [`DbErr::Type`]
99    /// when the key is not an integer or does not fit the key type;
100    /// [`DbErr::Driver`] when the statement fails.
101    pub async fn exec<C: ConnectionTrait>(self, db: &C) -> Result<InsertResult<A::Entity>> {
102        let result = db.execute(self.build()).await?;
103        if result.rows_affected == 0 {
104            return Err(DbErr::RecordNotInserted);
105        }
106        let last_insert_id = if let Some(values) = self.model.get_primary_key_value()
107            && !<A::Entity as EntityTrait>::PrimaryKey::auto_increment()
108        {
109            // The caller supplied the key, so decode it back from the bound
110            // values rather than trusting the row id.
111            decode_pk::<A::Entity>(&values)?
112        } else {
113            let id = u64::try_from(result.last_insert_id).unwrap_or_default();
114            <<A::Entity as EntityTrait>::PrimaryKey as PrimaryKeyTrait>::ValueType::try_from_u64(
115                id,
116            )?
117        };
118        Ok(InsertResult { last_insert_id })
119    }
120
121    /// Executes the insert with `RETURNING *` and returns the stored model.
122    ///
123    /// # Errors
124    ///
125    /// Returns [`DbErr::RecordNotInserted`] when the statement inserted
126    /// nothing; [`DbErr::Driver`] when the statement fails or the returned
127    /// row cannot be decoded.
128    pub async fn exec_with_returning<C: ConnectionTrait>(
129        self,
130        db: &C,
131    ) -> Result<<A::Entity as EntityTrait>::Model> {
132        let row = db
133            .query_one(self.statement(true).to_statement())
134            .await?
135            .ok_or(DbErr::RecordNotInserted)?;
136        <A::Entity as EntityTrait>::Model::from_query_result(&row, "")
137    }
138}
139
140/// Rebuilds a caller-supplied primary key from its bound values.
141///
142/// Only a single integer key can be rebuilt through `TryFromU64`; other
143/// shapes cannot be reconstructed generically, so callers that need them
144/// go through `exec_with_returning`.
145///
146/// # Errors
147///
148/// Returns [`DbErr::Type`] when the key is composite, not an integer,
149/// negative, or does not fit the key type.
150fn decode_pk<E: EntityTrait>(
151    values: &[turso_sql::Value],
152) -> Result<<E::PrimaryKey as PrimaryKeyTrait>::ValueType> {
153    match values.first() {
154        Some(turso_sql::Value::Integer(n)) if values.len() == 1 => {
155            let id = u64::try_from(*n).map_err(|_| DbErr::Type("negative primary key".into()))?;
156            <E::PrimaryKey as PrimaryKeyTrait>::ValueType::try_from_u64(id)
157        }
158        _ => Err(DbErr::Type(
159            "non-integer primary keys cannot be returned by exec(); use exec_with_returning()"
160                .into(),
161        )),
162    }
163}
164
165/// An `INSERT` of several active models in one statement.
166#[derive(Clone, Debug)]
167pub struct InsertMany<A: ActiveModelTrait> {
168    /// The active models to insert, in order.
169    models: Vec<A>,
170    /// The optional `ON CONFLICT` clause.
171    on_conflict: Option<OnConflict>,
172    /// Ties the builder to its active model type.
173    _a: PhantomData<A>,
174}
175
176impl<A: ActiveModelTrait> InsertMany<A> {
177    /// Collects the active models to insert.
178    pub(crate) fn many(models: impl IntoIterator<Item = A>) -> Self {
179        Self {
180            models: models.into_iter().collect(),
181            on_conflict: None,
182            _a: PhantomData,
183        }
184    }
185
186    /// Sets the `ON CONFLICT` clause.
187    #[must_use]
188    pub fn on_conflict(mut self, on_conflict: OnConflict) -> Self {
189        self.on_conflict = Some(on_conflict);
190        self
191    }
192
193    /// Whether there is nothing to insert.
194    pub fn is_empty(&self) -> bool {
195        self.models.is_empty()
196    }
197
198    /// Builds the statement, or `None` when there are no models.
199    fn statement(&self, returning: bool) -> Option<turso_sql::Insert> {
200        if self.models.is_empty() {
201            return None;
202        }
203        // A multi-row insert needs one column list, so it is the union of
204        // the `Set` columns across models. SQLite cannot ask for the default
205        // of one cell, so a model missing a column contributes the default
206        // the entity declares for it, or `NULL` when it declares none.
207        let columns: Vec<<A::Entity as EntityTrait>::Column> =
208            <<A::Entity as EntityTrait>::Column as Iterable>::iter()
209                .filter(|c| self.models.iter().any(|m| m.get(*c).is_set()))
210                .collect();
211        let mut insert = turso_sql::Query::insert()
212            .into_table(<A::Entity as EntityTrait>::TABLE_NAME)
213            .columns(columns.iter().map(IdenStatic::as_str));
214        for m in &self.models {
215            let row: Vec<Expr> = columns
216                .iter()
217                .map(|c| match m.get(*c).into_value() {
218                    Some(v) => Expr::val(v),
219                    None => c
220                        .def()
221                        .default
222                        .unwrap_or_else(|| Expr::val(turso_sql::Value::Null)),
223                })
224                .collect();
225            insert = insert.values(row);
226        }
227        if let Some(oc) = &self.on_conflict {
228            insert = insert.on_conflict(oc.clone());
229        }
230        if returning {
231            insert = insert.returning(Returning::All);
232        }
233        Some(insert)
234    }
235
236    /// Executes the insert and returns the number of rows inserted.
237    ///
238    /// With no models, returns zero without touching the database.
239    ///
240    /// # Errors
241    ///
242    /// Returns [`DbErr::Driver`] when the statement fails.
243    pub async fn exec<C: ConnectionTrait>(self, db: &C) -> Result<u64> {
244        match self.statement(false) {
245            None => Ok(0),
246            Some(stmt) => Ok(db.execute(stmt.to_statement()).await?.rows_affected),
247        }
248    }
249
250    /// Executes the insert with `RETURNING *` and decodes the stored rows.
251    ///
252    /// With no models, returns an empty list without touching the database.
253    ///
254    /// # Errors
255    ///
256    /// Returns [`DbErr::Driver`] when the statement fails or a returned row
257    /// cannot be decoded.
258    pub async fn exec_with_returning<C: ConnectionTrait>(
259        self,
260        db: &C,
261    ) -> Result<Vec<<A::Entity as EntityTrait>::Model>> {
262        match self.statement(true) {
263            None => Ok(Vec::new()),
264            Some(stmt) => db
265                .query_all(stmt.to_statement())
266                .await?
267                .iter()
268                .map(|r| <A::Entity as EntityTrait>::Model::from_query_result(r, ""))
269                .collect(),
270        }
271    }
272}