1use std::marker::PhantomData;
4
5use crate::error::QueryResult;
6use crate::filter::FilterValue;
7use crate::nested::NestedWriteOp;
8use crate::traits::{Model, ModelWithPk, QueryEngine};
9use crate::types::Select;
10
11pub struct CreateOperation<E: QueryEngine, M: Model> {
26 engine: E,
27 columns: Vec<String>,
28 values: Vec<FilterValue>,
29 select: Select,
30 nested: Vec<NestedWriteOp>,
34 _model: PhantomData<M>,
35}
36
37impl<E: QueryEngine, M: Model + crate::row::FromRow> CreateOperation<E, M> {
38 pub fn new(engine: E) -> Self {
40 Self {
41 engine,
42 columns: Vec::new(),
43 values: Vec::new(),
44 select: Select::All,
45 nested: Vec::new(),
46 _model: PhantomData,
47 }
48 }
49
50 pub fn set(mut self, column: impl Into<String>, value: impl Into<FilterValue>) -> Self {
52 self.columns.push(column.into());
53 self.values.push(value.into());
54 self
55 }
56
57 pub fn set_many(
59 mut self,
60 values: impl IntoIterator<Item = (impl Into<String>, impl Into<FilterValue>)>,
61 ) -> Self {
62 for (col, val) in values {
63 self.columns.push(col.into());
64 self.values.push(val.into());
65 }
66 self
67 }
68
69 pub fn select(mut self, select: impl Into<Select>) -> Self {
71 self.select = select.into();
72 self
73 }
74
75 pub fn with(mut self, nw: NestedWriteOp) -> Self {
91 self.nested.push(nw);
92 self
93 }
94
95 pub fn build_sql(
97 &self,
98 dialect: &dyn crate::dialect::SqlDialect,
99 ) -> (String, Vec<FilterValue>) {
100 Self::build_insert_sql(&self.columns, &self.values, &self.select, dialect)
101 }
102
103 fn build_insert_sql(
107 columns: &[String],
108 values: &[FilterValue],
109 select: &Select,
110 dialect: &dyn crate::dialect::SqlDialect,
111 ) -> (String, Vec<FilterValue>) {
112 let mut sql = String::new();
113
114 sql.push_str("INSERT INTO ");
116 sql.push_str(M::TABLE_NAME);
117
118 sql.push_str(" (");
120 sql.push_str(&columns.join(", "));
121 sql.push(')');
122
123 sql.push_str(" VALUES (");
125 let placeholders: Vec<_> = (1..=values.len()).map(|i| dialect.placeholder(i)).collect();
126 sql.push_str(&placeholders.join(", "));
127 sql.push(')');
128
129 sql.push_str(&dialect.returning_clause(&select.to_sql()));
131
132 (sql, values.to_vec())
133 }
134
135 pub async fn exec(self) -> QueryResult<M>
148 where
149 M: Send + 'static + ModelWithPk,
150 {
151 let CreateOperation {
152 engine,
153 columns,
154 values,
155 select,
156 nested,
157 _model,
158 } = self;
159
160 if nested.is_empty() {
162 let dialect = engine.dialect();
163 let (sql, params) = Self::build_insert_sql(&columns, &values, &select, dialect);
164 return engine.execute_insert::<M>(&sql, params).await;
165 }
166
167 engine
172 .transaction(move |tx| async move {
173 let dialect = tx.dialect();
174 let (sql, params) = Self::build_insert_sql(&columns, &values, &select, dialect);
175 let parent: M = tx.execute_insert::<M>(&sql, params).await?;
176 let parent_pk = parent.pk_value();
177 for nw in nested {
178 nw.execute(&tx, &parent_pk).await?;
179 }
180 Ok(parent)
181 })
182 .await
183 }
184}
185
186pub struct CreateManyOperation<E: QueryEngine, M: Model> {
188 engine: E,
189 columns: Vec<String>,
190 rows: Vec<Vec<FilterValue>>,
191 skip_duplicates: bool,
192 _model: PhantomData<M>,
193}
194
195impl<E: QueryEngine, M: Model> CreateManyOperation<E, M> {
196 pub fn new(engine: E) -> Self {
198 Self {
199 engine,
200 columns: Vec::new(),
201 rows: Vec::new(),
202 skip_duplicates: false,
203 _model: PhantomData,
204 }
205 }
206
207 pub fn columns(mut self, columns: impl IntoIterator<Item = impl Into<String>>) -> Self {
209 self.columns = columns.into_iter().map(Into::into).collect();
210 self
211 }
212
213 pub fn row(mut self, values: impl IntoIterator<Item = impl Into<FilterValue>>) -> Self {
215 self.rows.push(values.into_iter().map(Into::into).collect());
216 self
217 }
218
219 pub fn rows(
221 mut self,
222 rows: impl IntoIterator<Item = impl IntoIterator<Item = impl Into<FilterValue>>>,
223 ) -> Self {
224 for row in rows {
225 self.rows.push(row.into_iter().map(Into::into).collect());
226 }
227 self
228 }
229
230 pub fn skip_duplicates(mut self) -> Self {
232 self.skip_duplicates = true;
233 self
234 }
235
236 pub fn build_sql(
238 &self,
239 dialect: &dyn crate::dialect::SqlDialect,
240 ) -> (String, Vec<FilterValue>) {
241 let mut sql = String::new();
242 let mut all_params = Vec::new();
243
244 sql.push_str("INSERT INTO ");
246 sql.push_str(M::TABLE_NAME);
247
248 sql.push_str(" (");
250 sql.push_str(&self.columns.join(", "));
251 sql.push(')');
252
253 sql.push_str(" VALUES ");
255
256 let mut value_groups = Vec::new();
257 let mut param_idx = 1;
258
259 for row in &self.rows {
260 let placeholders: Vec<_> = row
261 .iter()
262 .map(|v| {
263 all_params.push(v.clone());
264 let placeholder = dialect.placeholder(param_idx);
265 param_idx += 1;
266 placeholder
267 })
268 .collect();
269 value_groups.push(format!("({})", placeholders.join(", ")));
270 }
271
272 sql.push_str(&value_groups.join(", "));
273
274 if self.skip_duplicates {
276 sql.push_str(" ON CONFLICT DO NOTHING");
277 }
278
279 (sql, all_params)
280 }
281
282 pub async fn exec(self) -> QueryResult<u64> {
284 let dialect = self.engine.dialect();
285 let (sql, params) = self.build_sql(dialect);
286 self.engine.execute_raw(&sql, params).await
287 }
288}
289
290#[cfg(test)]
291mod tests {
292 use super::*;
293 use crate::error::QueryError;
294
295 struct TestModel;
296
297 impl Model for TestModel {
298 const MODEL_NAME: &'static str = "TestModel";
299 const TABLE_NAME: &'static str = "test_models";
300 const PRIMARY_KEY: &'static [&'static str] = &["id"];
301 const COLUMNS: &'static [&'static str] = &["id", "name", "email"];
302 }
303
304 impl crate::row::FromRow for TestModel {
305 fn from_row(_row: &impl crate::row::RowRef) -> Result<Self, crate::row::RowError> {
306 Ok(TestModel)
307 }
308 }
309
310 impl crate::traits::ModelWithPk for TestModel {
315 fn pk_value(&self) -> FilterValue {
316 FilterValue::Int(0)
317 }
318 fn get_column_value(&self, _column: &str) -> Option<FilterValue> {
319 None
320 }
321 }
322
323 #[derive(Clone)]
324 struct MockEngine {
325 insert_count: u64,
326 }
327
328 impl MockEngine {
329 fn new() -> Self {
330 Self { insert_count: 0 }
331 }
332
333 fn with_count(count: u64) -> Self {
334 Self {
335 insert_count: count,
336 }
337 }
338 }
339
340 impl QueryEngine for MockEngine {
341 fn dialect(&self) -> &dyn crate::dialect::SqlDialect {
342 &crate::dialect::Postgres
343 }
344
345 fn query_many<T: Model + crate::row::FromRow + Send + 'static>(
346 &self,
347 _sql: &str,
348 _params: Vec<FilterValue>,
349 ) -> crate::traits::BoxFuture<'_, QueryResult<Vec<T>>> {
350 Box::pin(async { Ok(Vec::new()) })
351 }
352
353 fn query_one<T: Model + crate::row::FromRow + Send + 'static>(
354 &self,
355 _sql: &str,
356 _params: Vec<FilterValue>,
357 ) -> crate::traits::BoxFuture<'_, QueryResult<T>> {
358 Box::pin(async { Err(QueryError::not_found("test")) })
359 }
360
361 fn query_optional<T: Model + crate::row::FromRow + Send + 'static>(
362 &self,
363 _sql: &str,
364 _params: Vec<FilterValue>,
365 ) -> crate::traits::BoxFuture<'_, QueryResult<Option<T>>> {
366 Box::pin(async { Ok(None) })
367 }
368
369 fn execute_insert<T: Model + crate::row::FromRow + Send + 'static>(
370 &self,
371 _sql: &str,
372 _params: Vec<FilterValue>,
373 ) -> crate::traits::BoxFuture<'_, QueryResult<T>> {
374 Box::pin(async { Err(QueryError::not_found("test")) })
375 }
376
377 fn execute_update<T: Model + crate::row::FromRow + Send + 'static>(
378 &self,
379 _sql: &str,
380 _params: Vec<FilterValue>,
381 ) -> crate::traits::BoxFuture<'_, QueryResult<Vec<T>>> {
382 Box::pin(async { Ok(Vec::new()) })
383 }
384
385 fn execute_delete(
386 &self,
387 _sql: &str,
388 _params: Vec<FilterValue>,
389 ) -> crate::traits::BoxFuture<'_, QueryResult<u64>> {
390 Box::pin(async { Ok(0) })
391 }
392
393 fn execute_raw(
394 &self,
395 _sql: &str,
396 _params: Vec<FilterValue>,
397 ) -> crate::traits::BoxFuture<'_, QueryResult<u64>> {
398 let count = self.insert_count;
399 Box::pin(async move { Ok(count) })
400 }
401
402 fn count(
403 &self,
404 _sql: &str,
405 _params: Vec<FilterValue>,
406 ) -> crate::traits::BoxFuture<'_, QueryResult<u64>> {
407 Box::pin(async { Ok(0) })
408 }
409 }
410
411 #[test]
414 fn test_create_new() {
415 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new());
416 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
417
418 assert!(sql.contains("INSERT INTO test_models"));
419 assert!(sql.contains("RETURNING *"));
420 assert!(params.is_empty());
421 }
422
423 #[test]
424 fn test_create_basic() {
425 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
426 .set("name", "Alice")
427 .set("email", "alice@example.com");
428
429 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
430
431 assert!(sql.contains("INSERT INTO test_models"));
432 assert!(sql.contains("(name, email)"));
433 assert!(sql.contains("VALUES ($1, $2)"));
434 assert!(sql.contains("RETURNING *"));
435 assert_eq!(params.len(), 2);
436 }
437
438 #[test]
439 fn test_create_single_field() {
440 let op =
441 CreateOperation::<MockEngine, TestModel>::new(MockEngine::new()).set("name", "Alice");
442
443 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
444
445 assert!(sql.contains("(name)"));
446 assert!(sql.contains("VALUES ($1)"));
447 assert_eq!(params.len(), 1);
448 }
449
450 #[test]
451 fn test_create_with_set_many() {
452 let values = vec![
453 ("name", FilterValue::String("Bob".to_string())),
454 ("email", FilterValue::String("bob@test.com".to_string())),
455 ("age", FilterValue::Int(25)),
456 ];
457 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new()).set_many(values);
458
459 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
460
461 assert!(sql.contains("(name, email, age)"));
462 assert!(sql.contains("VALUES ($1, $2, $3)"));
463 assert_eq!(params.len(), 3);
464 }
465
466 #[test]
467 fn test_create_with_select() {
468 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
469 .set("name", "Alice")
470 .select(Select::fields(["id", "name"]));
471
472 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
473
474 assert!(sql.contains("RETURNING id, name"));
475 assert!(!sql.contains("RETURNING *"));
476 }
477
478 #[test]
479 fn test_create_with_null_value() {
480 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
481 .set("name", "Alice")
482 .set("nickname", FilterValue::Null);
483
484 let (_sql, params) = op.build_sql(&crate::dialect::Postgres);
485
486 assert_eq!(params.len(), 2);
487 assert_eq!(params[1], FilterValue::Null);
488 }
489
490 #[test]
491 fn test_create_with_boolean_value() {
492 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
493 .set("active", FilterValue::Bool(true));
494
495 let (_, params) = op.build_sql(&crate::dialect::Postgres);
496
497 assert_eq!(params[0], FilterValue::Bool(true));
498 }
499
500 #[test]
501 fn test_create_with_numeric_values() {
502 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
503 .set("count", FilterValue::Int(42))
504 .set("price", FilterValue::Float(99.99));
505
506 let (_, params) = op.build_sql(&crate::dialect::Postgres);
507
508 assert_eq!(params[0], FilterValue::Int(42));
509 assert_eq!(params[1], FilterValue::Float(99.99));
510 }
511
512 #[test]
513 fn test_create_with_json_value() {
514 let json = serde_json::json!({"key": "value", "nested": {"a": 1}});
515 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
516 .set("metadata", FilterValue::Json(json.clone()));
517
518 let (_, params) = op.build_sql(&crate::dialect::Postgres);
519
520 assert_eq!(params[0], FilterValue::Json(json));
521 }
522
523 #[tokio::test]
524 async fn test_create_exec() {
525 let op =
526 CreateOperation::<MockEngine, TestModel>::new(MockEngine::new()).set("name", "Alice");
527
528 let result = op.exec().await;
529
530 assert!(result.is_err());
532 }
533
534 #[test]
537 fn test_create_many_new() {
538 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new());
539 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
540
541 assert!(sql.contains("INSERT INTO test_models"));
542 assert!(!sql.contains("RETURNING")); assert!(params.is_empty());
544 }
545
546 #[test]
547 fn test_create_many() {
548 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
549 .columns(["name", "email"])
550 .row(["Alice", "alice@example.com"])
551 .row(["Bob", "bob@example.com"]);
552
553 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
554
555 assert!(sql.contains("INSERT INTO test_models"));
556 assert!(sql.contains("(name, email)"));
557 assert!(sql.contains("VALUES ($1, $2), ($3, $4)"));
558 assert_eq!(params.len(), 4);
559 }
560
561 #[test]
562 fn test_create_many_single_row() {
563 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
564 .columns(["name"])
565 .row(["Alice"]);
566
567 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
568
569 assert!(sql.contains("VALUES ($1)"));
570 assert_eq!(params.len(), 1);
571 }
572
573 #[test]
574 fn test_create_many_skip_duplicates() {
575 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
576 .columns(["name", "email"])
577 .row(["Alice", "alice@example.com"])
578 .skip_duplicates();
579
580 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
581
582 assert!(sql.contains("ON CONFLICT DO NOTHING"));
583 }
584
585 #[test]
586 fn test_create_many_without_skip_duplicates() {
587 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
588 .columns(["name"])
589 .row(["Alice"]);
590
591 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
592
593 assert!(!sql.contains("ON CONFLICT"));
594 }
595
596 #[test]
597 fn test_create_many_with_rows() {
598 let rows = vec![
599 vec!["Alice", "alice@test.com"],
600 vec!["Bob", "bob@test.com"],
601 vec!["Charlie", "charlie@test.com"],
602 ];
603 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
604 .columns(["name", "email"])
605 .rows(rows);
606
607 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
608
609 assert!(sql.contains("VALUES ($1, $2), ($3, $4), ($5, $6)"));
610 assert_eq!(params.len(), 6);
611 }
612
613 #[test]
614 fn test_create_many_param_ordering() {
615 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
616 .columns(["a", "b"])
617 .row(["1", "2"])
618 .row(["3", "4"]);
619
620 let (_, params) = op.build_sql(&crate::dialect::Postgres);
621
622 assert_eq!(params[0], FilterValue::String("1".to_string()));
624 assert_eq!(params[1], FilterValue::String("2".to_string()));
625 assert_eq!(params[2], FilterValue::String("3".to_string()));
626 assert_eq!(params[3], FilterValue::String("4".to_string()));
627 }
628
629 #[tokio::test]
630 async fn test_create_many_exec() {
631 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::with_count(3))
632 .columns(["name"])
633 .row(["Alice"])
634 .row(["Bob"])
635 .row(["Charlie"]);
636
637 let result = op.exec().await;
638
639 assert!(result.is_ok());
640 assert_eq!(result.unwrap(), 3);
641 }
642
643 #[test]
646 fn test_create_sql_structure() {
647 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
648 .set("name", "Alice")
649 .select(Select::fields(["id"]));
650
651 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
652
653 let insert_pos = sql.find("INSERT INTO").unwrap();
654 let columns_pos = sql.find("(name)").unwrap();
655 let values_pos = sql.find("VALUES").unwrap();
656 let returning_pos = sql.find("RETURNING").unwrap();
657
658 assert!(insert_pos < columns_pos);
659 assert!(columns_pos < values_pos);
660 assert!(values_pos < returning_pos);
661 }
662
663 #[test]
664 fn test_create_many_sql_structure() {
665 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
666 .columns(["name", "email"])
667 .row(["Alice", "alice@test.com"])
668 .skip_duplicates();
669
670 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
671
672 let insert_pos = sql.find("INSERT INTO").unwrap();
673 let columns_pos = sql.find("(name, email)").unwrap();
674 let values_pos = sql.find("VALUES").unwrap();
675 let conflict_pos = sql.find("ON CONFLICT").unwrap();
676
677 assert!(insert_pos < columns_pos);
678 assert!(columns_pos < values_pos);
679 assert!(values_pos < conflict_pos);
680 }
681
682 #[test]
683 fn test_create_table_name() {
684 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new());
685 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
686
687 assert!(sql.contains("test_models"));
688 }
689
690 #[test]
693 fn test_create_method_chaining() {
694 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
695 .set("name", "Alice")
696 .set("email", "alice@test.com")
697 .select(Select::fields(["id", "name"]));
698
699 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
700
701 assert!(sql.contains("(name, email)"));
702 assert!(sql.contains("VALUES ($1, $2)"));
703 assert!(sql.contains("RETURNING id, name"));
704 assert_eq!(params.len(), 2);
705 }
706
707 #[test]
708 fn test_create_many_method_chaining() {
709 let op = CreateManyOperation::<MockEngine, TestModel>::new(MockEngine::new())
710 .columns(["a", "b"])
711 .row(["1", "2"])
712 .row(["3", "4"])
713 .skip_duplicates();
714
715 let (sql, params) = op.build_sql(&crate::dialect::Postgres);
716
717 assert!(sql.contains("ON CONFLICT DO NOTHING"));
718 assert_eq!(params.len(), 4);
719 }
720
721 #[test]
724 fn create_mssql_emits_output_inserted() {
725 let op =
726 CreateOperation::<MockEngine, TestModel>::new(MockEngine::new()).set("name", "Alice");
727 let (sql, _) = op.build_sql(&crate::dialect::Mssql);
728 assert!(
729 sql.contains(" OUTPUT INSERTED.*"),
730 "expected OUTPUT INSERTED.*, got: {sql}"
731 );
732 }
733
734 #[test]
735 fn create_mssql_emits_output_inserted_for_multiple_columns() {
736 let op = CreateOperation::<MockEngine, TestModel>::new(MockEngine::new())
745 .set("name", "Alice")
746 .set("email", "alice@example.com")
747 .select(Select::fields(["id", "email"]));
748
749 let (sql, params) = op.build_sql(&crate::dialect::Mssql);
750 assert!(
751 sql.contains(" OUTPUT INSERTED.id, INSERTED.email"),
752 "expected OUTPUT INSERTED.id, INSERTED.email, got: {sql}"
753 );
754 assert!(
755 !sql.contains("INSERTED.*"),
756 "narrow Select must not fall back to INSERTED.*: {sql}"
757 );
758 assert_eq!(params.len(), 2);
759 }
760
761 #[test]
762 fn create_postgres_emits_returning() {
763 let op =
764 CreateOperation::<MockEngine, TestModel>::new(MockEngine::new()).set("name", "Alice");
765 let (sql, _) = op.build_sql(&crate::dialect::Postgres);
766 assert!(sql.contains("RETURNING "), "expected RETURNING, got: {sql}");
767 }
768}