diesel/query_builder/insert_statement/
batch_insert.rs

1use super::ValuesClause;
2use crate::backend::{sql_dialect, Backend, SqlDialect};
3use crate::insertable::CanInsertInSingleQuery;
4use crate::query_builder::{AstPass, QueryFragment, QueryId};
5use crate::QueryResult;
6use std::marker::PhantomData;
7
8/// This type represents a batch insert clause, which allows
9/// to insert multiple rows at once.
10///
11/// Custom backends can specialize the [`QueryFragment`]
12/// implementation via [`SqlDialect::BatchInsertSupport`]
13/// or provide fully custom [`ExecuteDsl`](crate::query_dsl::methods::ExecuteDsl)
14/// and [`LoadQuery`](crate::query_dsl::methods::LoadQuery) implementations
15#[cfg_attr(
16    feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
17    cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")
18)]
19#[derive(Debug)]
20pub struct BatchInsert<V, Tab, QId, const STABLE_QUERY_ID: bool> {
21    /// List of values that should be inserted
22    pub values: V,
23    _marker: PhantomData<(QId, Tab)>,
24}
25
26impl<V, Tab, QId, const STABLE_QUERY_ID: bool> BatchInsert<V, Tab, QId, STABLE_QUERY_ID> {
27    pub(crate) fn new(values: V) -> Self {
28        Self {
29            values,
30            _marker: PhantomData,
31        }
32    }
33}
34
35impl<V, QId: 'static, Tab: 'static, const STABLE_QUERY_ID: bool> QueryId
36    for BatchInsert<V, Tab, QId, STABLE_QUERY_ID>
37{
38    type QueryId = QId;
39
40    const HAS_STATIC_QUERY_ID: bool = STABLE_QUERY_ID;
41}
42
43impl<T, Table, QId, DB, const HAS_STATIC_QUERY_ID: bool> CanInsertInSingleQuery<DB>
44    for BatchInsert<T, Table, QId, HAS_STATIC_QUERY_ID>
45where
46    T: CanInsertInSingleQuery<DB>,
47DB: Backend+ SqlDialect<InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::IsoSqlDefaultKeyword>
48{
49    fn rows_to_insert(&self) -> Option<usize> {
50        self.values.rows_to_insert()
51    }
52}
53
54impl<T, DB, const N: usize> CanInsertInSingleQuery<DB> for [T; N]
55where
56DB: Backend+ SqlDialect<InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::IsoSqlDefaultKeyword>
57{
58    fn rows_to_insert(&self) -> Option<usize> {
59        Some(N)
60    }
61}
62
63impl<T, DB, const N: usize> CanInsertInSingleQuery<DB> for Box<[T; N]>
64where
65    DB: Backend+ SqlDialect<InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::IsoSqlDefaultKeyword>
66{
67    fn rows_to_insert(&self) -> Option<usize> {
68        Some(N)
69    }
70}
71
72impl<T, DB> CanInsertInSingleQuery<DB> for [T]
73where
74    DB: Backend+ SqlDialect<InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::IsoSqlDefaultKeyword>
75{
76    fn rows_to_insert(&self) -> Option<usize> {
77        Some(self.len())
78    }
79}
80
81impl<T, DB> CanInsertInSingleQuery<DB> for Vec<T>
82where
83    DB: Backend+ SqlDialect<InsertWithDefaultKeyword = sql_dialect::default_keyword_for_insert::IsoSqlDefaultKeyword>,
84{
85    fn rows_to_insert(&self) -> Option<usize> {
86        Some(self.len())
87    }
88}
89
90impl<Tab, DB, V, QId, const HAS_STATIC_QUERY_ID: bool> QueryFragment<DB>
91    for BatchInsert<V, Tab, QId, HAS_STATIC_QUERY_ID>
92where
93    DB: Backend,
94    Self: QueryFragment<DB, DB::BatchInsertSupport>,
95{
96    fn walk_ast<'b>(&'b self, pass: AstPass<'_, 'b, DB>) -> QueryResult<()> {
97        <Self as QueryFragment<DB, DB::BatchInsertSupport>>::walk_ast(self, pass)
98    }
99}
100
101impl<Tab, DB, V, QId, const HAS_STATIC_QUERY_ID: bool>
102    QueryFragment<DB, sql_dialect::batch_insert_support::PostgresLikeBatchInsertSupport>
103    for BatchInsert<Vec<ValuesClause<V, Tab>>, Tab, QId, HAS_STATIC_QUERY_ID>
104where
105    DB: Backend
106        + SqlDialect<
107            BatchInsertSupport = sql_dialect::batch_insert_support::PostgresLikeBatchInsertSupport,
108        >,
109    DB::InsertWithDefaultKeyword: sql_dialect::default_keyword_for_insert::SupportsDefaultKeyword,
110    ValuesClause<V, Tab>: QueryFragment<DB>,
111    V: QueryFragment<DB>,
112{
113    fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
114        if !HAS_STATIC_QUERY_ID {
115            out.unsafe_to_cache_prepared();
116        }
117
118        let mut values = self.values.iter();
119        if let Some(value) = values.next() {
120            value.walk_ast(out.reborrow())?;
121        }
122        for value in values {
123            out.push_sql(", (");
124            value.values.walk_ast(out.reborrow())?;
125            out.push_sql(")");
126        }
127        Ok(())
128    }
129}