turso_orm/query/
insert.rs1use 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#[derive(Clone, Debug)]
25pub struct InsertResult<E: EntityTrait> {
26 pub last_insert_id: <E::PrimaryKey as PrimaryKeyTrait>::ValueType,
28}
29
30#[derive(Clone, Debug)]
32pub struct Insert<A: ActiveModelTrait> {
33 model: A,
35 on_conflict: Option<OnConflict>,
37}
38
39impl<A: ActiveModelTrait> Insert<A> {
40 pub(crate) fn one(model: A) -> Self {
42 Self {
43 model,
44 on_conflict: None,
45 }
46 }
47
48 #[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 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 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 pub fn build(&self) -> Statement {
85 self.statement(false).to_statement()
86 }
87
88 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 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 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
140fn 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#[derive(Clone, Debug)]
167pub struct InsertMany<A: ActiveModelTrait> {
168 models: Vec<A>,
170 on_conflict: Option<OnConflict>,
172 _a: PhantomData<A>,
174}
175
176impl<A: ActiveModelTrait> InsertMany<A> {
177 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 #[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 pub fn is_empty(&self) -> bool {
195 self.models.is_empty()
196 }
197
198 fn statement(&self, returning: bool) -> Option<turso_sql::Insert> {
200 if self.models.is_empty() {
201 return None;
202 }
203 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 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 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}