rust_ef/change_executor/
executor.rs1use crate::entity::{IEntitySnapshot, IEntityType, IGetKeyValues};
4use crate::error::EFResult;
5use crate::metadata::EntityTypeMeta;
6use crate::provider::{DbValue, IAsyncConnection, IDatabaseProvider};
7
8pub struct ChangeExecutor;
10
11impl ChangeExecutor {
12 pub async fn execute_inserts<E, F>(
27 conn: &mut dyn IAsyncConnection,
28 provider: &dyn IDatabaseProvider,
29 entities: &[(&E, &EntityTypeMeta)],
30 mut on_key_backfill: F,
31 ) -> EFResult<usize>
32 where
33 E: IEntityType + IEntitySnapshot + IGetKeyValues,
34 F: FnMut(usize, i64),
35 {
36 if entities.is_empty() {
37 return Ok(0);
38 }
39 let gen = provider.sql_generator();
40 let meta = entities[0].1;
41 let scalar_props: Vec<_> = meta.mapped_scalar_properties().collect();
42 if scalar_props.is_empty() {
43 return Ok(0);
44 }
45 let insert_cols: Vec<&str> = scalar_props
46 .iter()
47 .filter(|p| !p.is_primary_key || (!p.is_auto_increment && !p.is_sequence))
48 .map(|p| p.column_name.as_ref())
49 .collect();
50 if insert_cols.is_empty() {
51 return Ok(0);
52 }
53
54 let auto_inc_pk = scalar_props
56 .iter()
57 .find(|p| (p.is_auto_increment || p.is_sequence) && p.is_primary_key);
58
59 const MAX_PARAMS: usize = 900;
61 let batch_size = (MAX_PARAMS / insert_cols.len()).max(1);
62
63 let mut inserted = 0usize;
64 let mut start = 0usize;
65 while start < entities.len() {
66 let end = (start + batch_size).min(entities.len());
67 let batch = &entities[start..end];
68 let row_count = batch.len();
69
70 let sql = gen.insert_batch(meta.table_name.as_ref(), &insert_cols, row_count);
71 let mut params: Vec<DbValue> = Vec::with_capacity(row_count * insert_cols.len());
72 for (entity, _) in batch {
73 let snap = entity.snapshot();
74 for p in &scalar_props {
75 if !p.is_primary_key || (!p.is_auto_increment && !p.is_sequence) {
76 params.push(
77 snap.get(p.field_name.as_ref())
78 .cloned()
79 .unwrap_or(DbValue::Null),
80 );
81 }
82 }
83 }
84
85 if let Some(_pk_prop) = auto_inc_pk {
86 if gen.supports_returning() {
87 let rows = conn.query(&sql, ¶ms).await?;
90 let affected = rows.len().min(row_count);
91 for (i, row) in rows.iter().enumerate().take(affected) {
92 if let Some(pk_val) = row.first() {
93 if let Ok(key) = pk_val.clone().try_into() {
94 on_key_backfill(start + i, key);
95 }
96 }
97 }
98 inserted += affected;
99 } else if let Some(last_id_sql) = gen.last_insert_id_sql() {
100 conn.execute(&sql, ¶ms).await?;
103 let id_rows = conn.query(last_id_sql, &[]).await?;
104 if let Some(id_row) = id_rows.first() {
105 if let Some(id_val) = id_row.first() {
106 let raw_id: i64 = id_val.clone().try_into().unwrap_or(0);
107 let first_id = if gen.last_insert_id_returns_first() {
108 raw_id
109 } else {
110 raw_id - row_count as i64 + 1
111 };
112 for i in 0..row_count {
113 on_key_backfill(start + i, first_id + i as i64);
114 }
115 }
116 }
117 inserted += row_count;
118 } else {
119 let rows = conn.execute(&sql, ¶ms).await?;
121 let affected = (rows as usize).min(row_count);
122 for i in 0..affected {
123 on_key_backfill(start + i, 0);
124 }
125 inserted += affected;
126 }
127 } else {
128 let rows = conn.execute(&sql, ¶ms).await?;
130 let affected = (rows as usize).min(row_count);
131 for i in 0..affected {
132 on_key_backfill(start + i, 0);
133 }
134 inserted += affected;
135 }
136
137 start = end;
138 }
139
140 Ok(inserted)
141 }
142
143 pub async fn execute_upserts<E>(
152 conn: &mut dyn IAsyncConnection,
153 provider: &dyn IDatabaseProvider,
154 entities: &[(&E, &EntityTypeMeta)],
155 ) -> EFResult<usize>
156 where
157 E: IEntityType + IEntitySnapshot + IGetKeyValues,
158 {
159 if entities.is_empty() {
160 return Ok(0);
161 }
162 let gen = provider.sql_generator();
163 let meta = entities[0].1;
164 let scalar_props: Vec<_> = meta.mapped_scalar_properties().collect();
165 if scalar_props.is_empty() {
166 return Ok(0);
167 }
168 let insert_cols: Vec<&str> = scalar_props
172 .iter()
173 .map(|p| p.column_name.as_ref())
174 .collect();
175 if insert_cols.is_empty() {
176 return Ok(0);
177 }
178
179 let conflict_cols: Vec<&str> = scalar_props
181 .iter()
182 .filter(|p| p.is_primary_key)
183 .map(|p| p.column_name.as_ref())
184 .collect();
185 if conflict_cols.is_empty() {
186 return Err(crate::error::EFError::configuration(
187 "Upsert requires at least one primary key column as conflict target.",
188 ));
189 }
190
191 const MAX_PARAMS: usize = 900;
192 let batch_size = (MAX_PARAMS / insert_cols.len()).max(1);
193
194 let mut upserted = 0usize;
195 let mut start = 0usize;
196 while start < entities.len() {
197 let end = (start + batch_size).min(entities.len());
198 let batch = &entities[start..end];
199 let row_count = batch.len();
200
201 let sql = gen.upsert_batch(
202 meta.table_name.as_ref(),
203 &insert_cols,
204 &conflict_cols,
205 row_count,
206 );
207 let mut params: Vec<DbValue> = Vec::with_capacity(row_count * insert_cols.len());
208 for (entity, _) in batch {
209 let snap = entity.snapshot();
210 for p in &scalar_props {
211 params.push(
212 snap.get(p.field_name.as_ref())
213 .cloned()
214 .unwrap_or(DbValue::Null),
215 );
216 }
217 }
218
219 let affected = conn.execute(&sql, ¶ms).await?;
220 upserted += (affected as usize).min(row_count);
221
222 start = end;
223 }
224
225 Ok(upserted)
226 }
227}