Skip to main content

datasources/
batch_command_i_bulk_write_strategy.rs

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(&parameters))
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