1use crate::bulk_command::BulkCommand;
2use crate::bulk_write_strategy::BulkWriteStrategy;
3use crate::command::Command;
4use crate::connection::Connection;
5use crate::crud_repository_builder::{write_statements, CRUDRepositoryBuilder};
6use crate::crud_repository_options::CRUDRepositoryOptions;
7use crate::crud_repository_statements::CRUDRepositoryStatements;
8use crate::datasource_error::DatasourceError;
9use crate::IWhere;
10use crate::mappers::Mapper;
11use crate::native::Native;
12use crate::parameter::Parameter;
13use crate::response::Response;
14use crate::result::Result;
15use crate::row::Row;
16use crate::value::Value;
17use crate::{
18 BulkDeleteStatement, BulkInsertStatement, BulkUpdateRow, BulkUpdateStatement,
19 DefaultStatementTextRenderer, Expression, Literal, Operator, Statement, Where,
20};
21use async_trait::async_trait;
22use std::collections::HashSet;
23use std::sync::{Arc, OnceLock};
24
25const PARAMETER_BUDGET: usize = 8000;
26const MAX_ROWS: usize = 250;
27
28struct Inserted {
29 table: String,
30 fields: Vec<Parameter>,
31}
32
33struct Updated {
34 table: String,
35 fields: Vec<Parameter>,
36 ids: Vec<Parameter>,
37}
38
39struct Deleted {
40 table: String,
41 ids: Vec<Parameter>,
42}
43
44#[derive(Default)]
45pub struct BatchCommandIBulkWriteStrategy {
46 command: OnceLock<Arc<dyn Command>>,
47 options: OnceLock<CRUDRepositoryOptions>,
48 connection: OnceLock<Arc<dyn Connection>>,
49 statements: OnceLock<CRUDRepositoryStatements>,
50}
51
52impl BatchCommandIBulkWriteStrategy {
53 pub fn new() -> Self {
54 Self::default()
55 }
56
57 fn command(&self) -> Result<Arc<dyn Command>> {
58 self.command
59 .get()
60 .cloned()
61 .ok_or_else(|| DatasourceError::message("Command required"))
62 }
63
64 fn options(&self) -> Result<&CRUDRepositoryOptions> {
65 self.options
66 .get()
67 .ok_or_else(|| DatasourceError::message("Options required"))
68 }
69
70 fn connection(&self) -> Result<Arc<dyn Connection>> {
71 self.connection
72 .get()
73 .cloned()
74 .ok_or_else(|| DatasourceError::message("Connection required"))
75 }
76
77 fn statements(&self) -> Result<&CRUDRepositoryStatements> {
78 if let Some(statements) = self.statements.get() {
79 return Ok(statements);
80 }
81 let built = CRUDRepositoryBuilder::parts(self.connection()?, self.options()?.clone())?;
82 let _ = self.statements.set(write_statements(&built.statements));
83 self.statements
84 .get()
85 .ok_or_else(|| DatasourceError::message("Statements required"))
86 }
87
88 fn ids(&self) -> Result<&[String]> {
89 Ok(&self.options()?.ids)
90 }
91
92 fn reverse(&self, item: &Row) -> Result<Row> {
93 let options = self.options()?;
94 let row = match &options.object_converter {
95 Some(converter) => converter.reverse_convert(item)?,
96 None => item.clone(),
97 };
98 match &options.row_converter {
99 Some(converter) => converter.reverse_convert(&row),
100 None => Ok(row),
101 }
102 }
103
104 fn cell(&self, property: &str, value: &Value, fields_applied: bool) -> Result<Value> {
105 if fields_applied {
106 return Ok(value.clone());
107 }
108 match self
109 .options()?
110 .row_converter
111 .as_ref()
112 .and_then(|converter| converter.fields.get(property))
113 {
114 Some(field) => field.reverse_convert(value),
115 None => Ok(value.clone()),
116 }
117 }
118
119 fn fields(&self, record: &Row, include: impl Fn(&str, &Value) -> bool, omit_null: bool) -> Result<Vec<Parameter>> {
120 let columns = self.statements()?.read_columns().to_vec();
121 let mapped: Vec<String> = match &self.options()?.property_mappings {
122 Some(map) => columns.iter().map(|name| map.map(name.clone())).collect(),
123 None => Vec::new(),
124 };
125 let mut pairs: Vec<_> = record
126 .iter()
127 .filter(|(key, value)| {
128 include(key, value)
129 && (columns.is_empty()
130 || columns.iter().any(|name| name == *key)
131 || mapped.iter().any(|name| name == *key))
132 && !(omit_null && matches!(value, Value::Null))
133 })
134 .map(|(key, value)| (key.clone(), value.clone()))
135 .collect();
136 pairs.sort_by(|left, right| left.0.cmp(&right.0));
137 pairs
138 .into_iter()
139 .map(|(name, value)| Ok(Parameter::new(name.clone(), self.cell(&name, &value, true)?)))
140 .collect()
141 }
142
143 fn id_where(&self, record: &Row) -> Result<Where> {
144 let children = self
145 .ids()?
146 .iter()
147 .map(|col| {
148 let value = record.get(col).ok_or_else(|| {
149 DatasourceError::message(format!("Missing Id value \"{col}\""))
150 })?;
151 Ok(Expression::from(Operator::equals(col.clone(), value.clone())))
152 })
153 .collect::<Result<Vec<_>>>()?;
154 Ok(Where::new(and_all(children)))
155 }
156
157 fn mapped_insert(&self, item: &Row) -> Result<Inserted> {
158 let record = self.reverse(item)?;
159 let ids = self.ids()?.to_vec();
160 let create = self.options()?.create_properties.clone();
161 let fields = self.fields(
162 &record,
163 |key, value| {
164 listed(
165 &create,
166 key,
167 !ids.iter().any(|id| id == key) || assigned(value),
168 )
169 },
170 true,
171 )?;
172 let statement = self.statements()?.insert(&fields);
173 Ok(Inserted {
174 table: statement.table().to_string(),
175 fields: statement.fields().to_vec(),
176 })
177 }
178
179 fn mapped_update(&self, item: &Row) -> Result<Updated> {
180 let record = self.reverse(item)?;
181 let ids = self.ids()?.to_vec();
182 let update = self.options()?.update_properties.clone();
183 let fields = self.fields(
184 &record,
185 |key, _| listed(&update, key, !ids.iter().any(|id| id == key)),
186 false,
187 )?;
188 if fields.is_empty() {
189 return Err(DatasourceError::message(
190 "Update requires at least one non-Id field",
191 ));
192 }
193 let clause = self.id_where(&record)?;
194 let statement = self.statements()?.update(&fields, &clause, &|name, value| {
195 self.cell(name, value, true)
196 })?;
197 Ok(Updated {
198 table: statement.table().to_string(),
199 fields: statement.fields().to_vec(),
200 ids: statement
201 .where_clause()
202 .map(|clause| clause.parameters())
203 .unwrap_or_default(),
204 })
205 }
206
207 fn mapped_delete(&self, item: &Row) -> Result<Deleted> {
208 let record = self.reverse(item)?;
209 let clause = self.id_where(&record)?;
210 let statement = self.statements()?.delete(&clause, &|name, value| {
211 self.cell(name, value, true)
212 })?;
213 Ok(Deleted {
214 table: statement.table().to_string(),
215 ids: statement
216 .where_clause()
217 .map(|clause| clause.parameters())
218 .unwrap_or_default(),
219 })
220 }
221
222 fn update_command(&self, item: &Row, by_where: Option<&dyn IWhere>) -> Result<BulkCommand> {
223 let record = self.reverse(item)?;
224 let ids = self.ids()?.to_vec();
225 let update = self.options()?.update_properties.clone();
226 let fields = self.fields(
227 &record,
228 |key, _| listed(&update, key, !ids.iter().any(|id| id == key)),
229 false,
230 )?;
231 if fields.is_empty() {
232 return Err(DatasourceError::message(
233 "Update requires at least one non-Id field",
234 ));
235 }
236 let owned;
237 let clause: &dyn IWhere = match by_where {
238 Some(clause) => clause,
239 None => {
240 owned = self.id_where(&record)?;
241 &owned
242 }
243 };
244 let applied = by_where.is_none();
245 let statement = self.statements()?.update(&fields, clause, &|name, value| {
246 self.cell(name, value, applied)
247 })?;
248 Ok(BulkCommand {
249 text: DefaultStatementTextRenderer::instance().render(&statement),
250 parameters: statement.parameters(),
251 })
252 }
253
254 fn delete_command(&self, item: &Row, by_where: Option<&dyn IWhere>) -> Result<BulkCommand> {
255 let record = self.reverse(item)?;
256 let owned;
257 let clause: &dyn IWhere = match by_where {
258 Some(clause) => clause,
259 None => {
260 owned = self.id_where(&record)?;
261 &owned
262 }
263 };
264 let applied = by_where.is_none();
265 let statement = self.statements()?.delete(clause, &|name, value| {
266 self.cell(name, value, applied)
267 })?;
268 let parameters = statement
269 .where_clause()
270 .map(|clause| clause.parameters())
271 .unwrap_or_default();
272 let rendered = DefaultStatementTextRenderer::instance().render(&statement);
273 let parameters = if parameters.is_empty() && rendered.contains('$') {
274 self.fields(&record, |_, _| true, true)?
275 } else {
276 parameters
277 };
278 Ok(BulkCommand {
279 text: rendered,
280 parameters,
281 })
282 }
283
284 async fn execute(&self, statement: &dyn Statement, parameters: Vec<Parameter>) -> Result<Response> {
285 self.command()?
286 .execute_statement(statement, Some(¶meters))
287 .await
288 }
289
290 async fn chunked<T, F, Fut>(&self, rows: &[T], width: usize, write: F) -> Result<Response>
291 where
292 F: Fn(&[T]) -> Fut,
293 Fut: std::future::Future<Output = Result<Response>>,
294 {
295 let size = (PARAMETER_BUDGET / width.max(1)).max(1).min(MAX_ROWS);
296 let mut rows_affected = 0;
297 for chunk in rows.chunks(size) {
298 rows_affected += write(chunk).await?.rows_affected;
299 }
300 Ok(Response::new(Value::Null, rows_affected, None))
301 }
302
303 async fn send_each(&self, commands: Vec<BulkCommand>) -> Result<Response> {
304 let mut rows_affected = 0;
305 for command in commands {
306 rows_affected += self
307 .command()?
308 .execute_native(&Native::new(command.text), Some(&command.parameters))
309 .await?
310 .rows_affected;
311 }
312 Ok(Response::new(Value::Null, rows_affected, None))
313 }
314
315 async fn write_creates(&self, items: &[Row]) -> Result<Response> {
316 if items.is_empty() {
317 return Ok(Response::new(Value::Null, 0, None));
318 }
319 let rows = items
320 .iter()
321 .map(|item| self.mapped_insert(item))
322 .collect::<Result<Vec<_>>>()?;
323 let columns = columns(rows.iter().map(|row| row.fields.iter()));
324 let table = rows[0].table.clone();
325 self.chunked(&rows, columns.len(), |chunk| {
326 let table = table.clone();
327 let columns = columns.clone();
328 let fields = chunk.iter().map(|row| row.fields.clone()).collect::<Vec<_>>();
329 async move {
330 let statement = BulkInsertStatement::new(&table, &columns, fields);
331 let parameters = statement.parameters();
332 self.execute(&statement, parameters).await
333 }
334 })
335 .await
336 }
337
338 async fn write_updates(&self, items: &[Row]) -> Result<Response> {
339 if items.is_empty() {
340 return Ok(Response::new(Value::Null, 0, None));
341 }
342 let rows = items
343 .iter()
344 .map(|item| self.mapped_update(item))
345 .collect::<Result<Vec<_>>>()?;
346 let set = columns(rows.iter().map(|row| row.fields.iter()));
347 let ids = columns(rows.iter().map(|row| row.ids.iter()));
348 let table = rows[0].table.clone();
349 self.chunked(&rows, set.len() + ids.len(), |chunk| {
350 let table = table.clone();
351 let set = set.clone();
352 let ids = ids.clone();
353 let updated = chunk
354 .iter()
355 .map(|row| BulkUpdateRow {
356 fields: row.fields.clone(),
357 ids: row.ids.clone(),
358 })
359 .collect::<Vec<_>>();
360 async move {
361 let statement = BulkUpdateStatement::new(&table, &set, &ids, updated);
362 let parameters = statement.parameters();
363 self.execute(&statement, parameters).await
364 }
365 })
366 .await
367 }
368
369 async fn write_deletes(&self, items: &[Row]) -> Result<Response> {
370 if items.is_empty() {
371 return Ok(Response::new(Value::Null, 0, None));
372 }
373 let rows = items
374 .iter()
375 .map(|item| self.mapped_delete(item))
376 .collect::<Result<Vec<_>>>()?;
377 let ids = columns(rows.iter().map(|row| row.ids.iter()));
378 let table = rows[0].table.clone();
379 self.chunked(&rows, ids.len(), |chunk| {
380 let table = table.clone();
381 let ids = ids.clone();
382 let deleted = chunk.iter().map(|row| row.ids.clone()).collect::<Vec<_>>();
383 async move {
384 let statement = BulkDeleteStatement::new(&table, &ids, deleted);
385 let parameters = statement.parameters();
386 self.execute(&statement, parameters).await
387 }
388 })
389 .await
390 }
391}
392
393#[async_trait]
394impl BulkWriteStrategy for BatchCommandIBulkWriteStrategy {
395 fn attach(
396 &self,
397 command: Arc<dyn Command>,
398 options: CRUDRepositoryOptions,
399 connection: Arc<dyn Connection>,
400 ) {
401 let _ = self.command.set(command);
402 let _ = self.options.set(options);
403 let _ = self.connection.set(connection);
404 }
405
406 fn persist_children(&self) -> bool {
407 true
408 }
409
410 async fn create(&self, items: &mut [Row]) -> Result<Response> {
411 self.write_creates(items).await
412 }
413
414 async fn update(&self, items: &[Row], by_where: Option<&dyn IWhere>) -> Result<Response> {
415 match by_where {
416 Some(_) => {
417 let commands = items
418 .iter()
419 .map(|item| self.update_command(item, by_where))
420 .collect::<Result<Vec<_>>>()?;
421 self.send_each(commands).await
422 }
423 None => self.write_updates(items).await,
424 }
425 }
426
427 async fn delete(&self, items: &[Row], by_where: Option<&dyn IWhere>) -> Result<Response> {
428 match by_where {
429 Some(_) => {
430 let commands = items
431 .iter()
432 .map(|item| self.delete_command(item, by_where))
433 .collect::<Result<Vec<_>>>()?;
434 self.send_each(commands).await
435 }
436 None => self.write_deletes(items).await,
437 }
438 }
439}
440
441fn listed(list: &Option<Vec<String>>, key: &str, fallback: bool) -> bool {
442 match list {
443 Some(names) => names.iter().any(|name| name == key),
444 None => fallback,
445 }
446}
447
448fn assigned(value: &Value) -> bool {
449 match value {
450 Value::Null => false,
451 Value::Text(text) => !text.is_empty(),
452 Value::Int(0) => false,
453 _ => true,
454 }
455}
456
457fn and_all(parts: Vec<Expression>) -> Expression {
458 parts
459 .into_iter()
460 .reduce(|left, right| Operator::and(left, right).into())
461 .unwrap_or_else(|| {
462 Operator::Equals {
463 left: Box::new(Expression::Literal(Literal::new(1_i64))),
464 right: Box::new(Expression::Literal(Literal::new(1_i64))),
465 }
466 .into()
467 })
468}
469
470fn columns<'a>(rows: impl IntoIterator<Item = impl IntoIterator<Item = &'a Parameter>>) -> Vec<String> {
471 let mut seen = HashSet::new();
472 let mut columns = Vec::new();
473 for field in rows.into_iter().flatten() {
474 if seen.insert(field.name.clone()) {
475 columns.push(field.name.clone());
476 }
477 }
478 columns
479}
480