1use crate::error::{Error, Result};
25use crate::row::Value;
26use crate::statement::Statement;
27
28#[derive(Debug, Clone, Default)]
30pub struct BatchOptions {
31 pub batch_errors: bool,
33 pub array_dml_row_counts: bool,
35 pub auto_commit: bool,
37}
38
39impl BatchOptions {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn with_batch_errors(mut self) -> Self {
47 self.batch_errors = true;
48 self
49 }
50
51 pub fn with_row_counts(mut self) -> Self {
53 self.array_dml_row_counts = true;
54 self
55 }
56
57 pub fn with_auto_commit(mut self) -> Self {
59 self.auto_commit = true;
60 self
61 }
62}
63
64#[derive(Debug, Clone)]
66pub struct BatchBinds {
67 pub(crate) sql: String,
69 pub(crate) statement: Statement,
71 pub(crate) rows: Vec<Vec<Value>>,
73 pub(crate) num_columns: usize,
75 pub(crate) options: BatchOptions,
77}
78
79impl BatchBinds {
80 pub fn new(sql: impl Into<String>) -> Self {
82 let sql = sql.into();
83 let statement = Statement::new(&sql);
84 Self {
85 sql,
86 statement,
87 rows: Vec::new(),
88 num_columns: 0,
89 options: BatchOptions::default(),
90 }
91 }
92
93 pub fn add_row(&mut self, values: Vec<Value>) -> &mut Self {
95 if self.rows.is_empty() {
96 self.num_columns = values.len();
97 }
98 self.rows.push(values);
99 self
100 }
101
102 pub fn with_options(&mut self, options: BatchOptions) -> &mut Self {
104 self.options = options;
105 self
106 }
107
108 pub fn row_count(&self) -> usize {
110 self.rows.len()
111 }
112
113 pub fn column_count(&self) -> usize {
115 self.num_columns
116 }
117
118 pub fn sql(&self) -> &str {
120 &self.sql
121 }
122
123 pub fn is_empty(&self) -> bool {
125 self.rows.is_empty()
126 }
127
128 pub fn validate(&self) -> Result<()> {
130 if self.rows.is_empty() {
131 return Err(Error::Internal("Batch has no rows".to_string()));
132 }
133
134 for (i, row) in self.rows.iter().enumerate() {
135 if row.len() != self.num_columns {
136 return Err(Error::Internal(format!(
137 "Row {} has {} columns, expected {}",
138 i,
139 row.len(),
140 self.num_columns
141 )));
142 }
143 }
144
145 Ok(())
146 }
147}
148
149#[derive(Debug)]
151pub struct BatchBuilder {
152 batch: BatchBinds,
153}
154
155impl BatchBuilder {
156 pub fn new(sql: impl Into<String>) -> Self {
158 Self {
159 batch: BatchBinds::new(sql),
160 }
161 }
162
163 pub fn add_row(mut self, values: Vec<Value>) -> Self {
165 self.batch.add_row(values);
166 self
167 }
168
169 pub fn add_rows(mut self, rows: Vec<Vec<Value>>) -> Self {
171 for row in rows {
172 self.batch.add_row(row);
173 }
174 self
175 }
176
177 pub fn with_batch_errors(mut self) -> Self {
179 self.batch.options.batch_errors = true;
180 self
181 }
182
183 pub fn with_row_counts(mut self) -> Self {
185 self.batch.options.array_dml_row_counts = true;
186 self
187 }
188
189 pub fn with_auto_commit(mut self) -> Self {
191 self.batch.options.auto_commit = true;
192 self
193 }
194
195 pub fn build(self) -> BatchBinds {
197 self.batch
198 }
199}
200
201#[derive(Debug, Clone)]
203pub struct BatchResult {
204 pub row_counts: Option<Vec<u64>>,
206 pub total_rows_affected: u64,
208 pub errors: Vec<BatchError>,
210 pub success_count: usize,
212 pub failure_count: usize,
214}
215
216impl BatchResult {
217 pub fn new() -> Self {
219 Self {
220 row_counts: None,
221 total_rows_affected: 0,
222 errors: Vec::new(),
223 success_count: 0,
224 failure_count: 0,
225 }
226 }
227
228 pub fn with_row_counts(counts: Vec<u64>) -> Self {
230 let total: u64 = counts.iter().sum();
231 let success_count = counts.len();
232 Self {
233 row_counts: Some(counts),
234 total_rows_affected: total,
235 errors: Vec::new(),
236 success_count,
237 failure_count: 0,
238 }
239 }
240
241 pub fn is_success(&self) -> bool {
243 self.errors.is_empty()
244 }
245
246 pub fn has_errors(&self) -> bool {
248 !self.errors.is_empty()
249 }
250}
251
252impl Default for BatchResult {
253 fn default() -> Self {
254 Self::new()
255 }
256}
257
258#[derive(Debug, Clone)]
260pub struct BatchError {
261 pub row_index: usize,
263 pub code: u32,
265 pub message: String,
267}
268
269impl BatchError {
270 pub fn new(row_index: usize, code: u32, message: impl Into<String>) -> Self {
272 Self {
273 row_index,
274 code,
275 message: message.into(),
276 }
277 }
278}
279
280impl std::fmt::Display for BatchError {
281 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
282 write!(
283 f,
284 "Row {}: ORA-{:05}: {}",
285 self.row_index, self.code, self.message
286 )
287 }
288}
289
290impl std::error::Error for BatchError {}
291
292#[cfg(test)]
293mod tests {
294 use super::*;
295
296 #[test]
297 fn test_batch_builder() {
298 let batch = BatchBuilder::new("INSERT INTO t (a, b) VALUES (:1, :2)")
299 .add_row(vec![Value::Integer(1), Value::String("a".to_string())])
300 .add_row(vec![Value::Integer(2), Value::String("b".to_string())])
301 .build();
302
303 assert_eq!(batch.row_count(), 2);
304 assert_eq!(batch.column_count(), 2);
305 assert!(!batch.is_empty());
306 }
307
308 #[test]
309 fn test_batch_validation() {
310 let mut batch = BatchBinds::new("INSERT INTO t (a) VALUES (:1)");
311 batch.add_row(vec![Value::Integer(1)]);
312 batch.add_row(vec![Value::Integer(2)]);
313
314 assert!(batch.validate().is_ok());
315 }
316
317 #[test]
318 fn test_batch_validation_empty() {
319 let batch = BatchBinds::new("INSERT INTO t (a) VALUES (:1)");
320 assert!(batch.validate().is_err());
321 }
322
323 #[test]
324 fn test_batch_options() {
325 let opts = BatchOptions::new()
326 .with_batch_errors()
327 .with_row_counts()
328 .with_auto_commit();
329
330 assert!(opts.batch_errors);
331 assert!(opts.array_dml_row_counts);
332 assert!(opts.auto_commit);
333 }
334
335 #[test]
336 fn test_batch_result() {
337 let result = BatchResult::with_row_counts(vec![1, 2, 3]);
338
339 assert_eq!(result.total_rows_affected, 6);
340 assert_eq!(result.success_count, 3);
341 assert!(result.is_success());
342 assert!(!result.has_errors());
343 }
344
345 #[test]
346 fn test_batch_error_display() {
347 let err = BatchError::new(5, 1, "test error");
348 assert_eq!(err.to_string(), "Row 5: ORA-00001: test error");
349 }
350
351 #[test]
352 fn test_add_multiple_rows() {
353 let batch = BatchBuilder::new("INSERT INTO t (a) VALUES (:1)")
354 .add_rows(vec![
355 vec![Value::Integer(1)],
356 vec![Value::Integer(2)],
357 vec![Value::Integer(3)],
358 ])
359 .build();
360
361 assert_eq!(batch.row_count(), 3);
362 }
363}