1use radixdb_core::{time_compat::Instant, CompactArc, SmartString};
21use radixdb_core::{DataType, Error, Result, Row, Schema, Value};
22use radixdb_sql::ast::{CopyFormat, CopyStatement};
23use radixdb_storage::traits::{Engine, QueryResult, Table};
24use rustc_hash::{FxHashMap, FxHashSet};
25
26use super::dml_support::evaluate_default_expr;
27use crate::context::{
28 invalidate_in_subquery_cache_for_table, invalidate_scalar_subquery_cache_for_table,
29 invalidate_semi_join_cache_for_table, ExecutionContext,
30};
31use crate::mutation::host::MutationHost;
32use crate::mutation::validation::{
33 compile_table_check_constraints, prepare_insert_row_constraints,
34};
35use crate::result::ExecResult;
36
37const COPY_TRANSACTION_MEMORY_AMPLIFICATION: usize = 8;
42const COPY_INSERT_BATCH_ROWS: usize = 4096;
47
48#[inline]
49fn flush_copy_insert_batch(table: &mut Box<dyn Table>, rows: &mut Vec<Row>) -> Result<()> {
50 if rows.is_empty() {
51 return Ok(());
52 }
53 let next = Vec::with_capacity(COPY_INSERT_BATCH_ROWS);
54 table.insert_batch(std::mem::replace(rows, next))
55}
56
57fn account_copy_transaction_row(
58 used_bytes: &mut usize,
59 limit_bytes: usize,
60 row: &Row,
61 row_number: i64,
62) -> Result<()> {
63 let row_bytes = radixdb_storage::mvcc::version_store::estimate_row_hot_bytes(row)
64 .saturating_mul(COPY_TRANSACTION_MEMORY_AMPLIFICATION);
65 let attempted_bytes = used_bytes.saturating_add(row_bytes);
66 if attempted_bytes > limit_bytes {
67 return Err(Error::CopyTransactionMemoryLimit {
68 row: row_number,
69 limit_bytes,
70 attempted_bytes,
71 });
72 }
73 *used_bytes = attempted_bytes;
74 Ok(())
75}
76
77#[inline]
80fn parse_field(field: &str, target_type: DataType, col_name: &str) -> Result<Value> {
81 match target_type {
82 DataType::Integer => field.parse::<i64>().map(Value::Integer).map_err(|_| {
83 Error::Type(format!(
84 "cannot convert value '{}' to INTEGER for column '{}'",
85 field, col_name
86 ))
87 }),
88 DataType::Float => field.parse::<f64>().map(Value::Float).map_err(|_| {
89 Error::Type(format!(
90 "cannot convert value '{}' to FLOAT for column '{}'",
91 field, col_name
92 ))
93 }),
94 DataType::Boolean => {
95 if field.eq_ignore_ascii_case("true")
96 || field.eq_ignore_ascii_case("t")
97 || field.eq_ignore_ascii_case("yes")
98 || field.eq_ignore_ascii_case("y")
99 || field == "1"
100 {
101 Ok(Value::Boolean(true))
102 } else if field.eq_ignore_ascii_case("false")
103 || field.eq_ignore_ascii_case("f")
104 || field.eq_ignore_ascii_case("no")
105 || field.eq_ignore_ascii_case("n")
106 || field == "0"
107 {
108 Ok(Value::Boolean(false))
109 } else {
110 Err(Error::Type(format!(
111 "cannot convert value '{}' to BOOLEAN for column '{}'",
112 field, col_name
113 )))
114 }
115 }
116 DataType::Timestamp => radixdb_core::parse_timestamp(field)
117 .map(Value::Timestamp)
118 .map_err(|_| {
119 Error::Type(format!(
120 "cannot convert value '{}' to TIMESTAMP for column '{}'",
121 field, col_name
122 ))
123 }),
124 DataType::Decimal => radixdb_core::value::parse_decimal_str(field)
125 .and_then(|(unscaled, precision, scale)| {
126 Value::try_decimal(unscaled, precision, scale).ok()
127 })
128 .ok_or_else(|| {
129 Error::Type(format!(
130 "cannot convert value '{}' to DECIMAL for column '{}'",
131 field, col_name
132 ))
133 }),
134 DataType::Date => radixdb_core::value::parse_date_days_since_unix_epoch(field)
135 .map(Value::date)
136 .ok_or_else(|| {
137 Error::Type(format!(
138 "cannot convert value '{}' to DATE for column '{}'",
139 field, col_name
140 ))
141 }),
142 DataType::Bytes => Ok(Value::bytes(field.as_bytes().to_vec())),
143 DataType::Text => {
144 Ok(Value::Text(SmartString::new(field)))
147 }
148 DataType::Json => Value::try_json(field).map_err(|_| {
149 Error::Type(format!(
150 "cannot convert value '{}' to JSON for column '{}'",
151 field, col_name
152 ))
153 }),
154 DataType::Uuid => radixdb_core::value::parse_uuid_str(field)
155 .map(Value::uuid)
156 .ok_or_else(|| {
157 Error::Type(format!(
158 "cannot convert value '{}' to UUID for column '{}'",
159 field, col_name
160 ))
161 }),
162 _ => {
163 let text_val = Value::text(field);
165 let coerced = text_val.coerce_to_type(target_type);
166 if !text_val.is_null() && coerced.is_null() {
167 return Err(Error::Type(format!(
168 "cannot convert value '{}' to {:?} for column '{}'",
169 field, target_type, col_name
170 )));
171 }
172 Ok(coerced)
173 }
174 }
175}
176
177#[doc(hidden)]
178pub trait CopyExecutorExt: MutationHost {
179 fn execute_copy(
181 &self,
182 stmt: &CopyStatement,
183 _ctx: &ExecutionContext,
184 ) -> Result<Box<dyn QueryResult>> {
185 let copy_started = Instant::now();
186 let table_name = &stmt.table_name.value_lower;
187
188 {
190 let active_tx = self.mutation_active_transaction().lock().unwrap();
191 if active_tx.is_some() {
192 return Err(Error::InvalidArgument(
193 "COPY FROM cannot be used inside an explicit transaction".to_string(),
194 ));
195 }
196 }
197
198 let mut transaction = self.mutation_engine().begin_transaction()?;
202 let mut table = transaction.get_table(table_name)?;
203 let schema = table.schema().clone();
204 let schema_column_count = schema.columns.len();
205 let copy_max_transaction_bytes = self
206 .mutation_engine()
207 .config()
208 .persistence
209 .copy_max_transaction_bytes;
210 let mut copy_transaction_bytes = 0usize;
211
212 let all_column_types: Vec<DataType> = schema.columns.iter().map(|c| c.data_type).collect();
213 let all_vector_dims: Vec<u16> =
214 schema.columns.iter().map(|c| c.vector_dimensions).collect();
215 let default_exprs: Vec<Option<String>> = schema
216 .columns
217 .iter()
218 .map(|c| c.default_expr.clone())
219 .collect();
220 let column_indices: Vec<usize> = if stmt.columns.is_empty() {
221 (0..schema_column_count).collect()
222 } else {
223 let mut seen = FxHashSet::default();
224 for identifier in &stmt.columns {
225 if !seen.insert(identifier.value_lower.clone()) {
226 return Err(Error::InvalidArgument(format!(
227 "duplicate COPY target column '{}'",
228 identifier.value
229 )));
230 }
231 }
232 let col_map = schema.column_index_map();
233 stmt.columns
234 .iter()
235 .map(|id| {
236 col_map
237 .get(id.value_lower.as_str())
238 .copied()
239 .ok_or_else(|| Error::ColumnNotFound(id.value.to_string()))
240 })
241 .collect::<Result<Vec<_>>>()?
242 };
243
244 let fk_schema: Option<CompactArc<Schema>> = if !schema.foreign_keys.is_empty() {
246 Some(CompactArc::new(schema.clone()))
247 } else {
248 None
249 };
250
251 let parse_started = Instant::now();
252 let outcome = match stmt.format {
253 CopyFormat::Csv => self.copy_from_csv(
254 stmt,
255 &mut table,
256 &schema,
257 &column_indices,
258 &all_column_types,
259 &all_vector_dims,
260 &default_exprs,
261 &fk_schema,
262 schema_column_count,
263 &mut copy_transaction_bytes,
264 copy_max_transaction_bytes,
265 ),
266 CopyFormat::Json => self.copy_from_json(
267 stmt,
268 &mut table,
269 &schema,
270 &column_indices,
271 &all_column_types,
272 &all_vector_dims,
273 &default_exprs,
274 &fk_schema,
275 schema_column_count,
276 &mut copy_transaction_bytes,
277 copy_max_transaction_bytes,
278 ),
279 };
280 let parse_elapsed = parse_started.elapsed();
281
282 drop(table);
283 let rows_affected = match outcome {
284 Ok(rows_affected) => {
285 let commit_started = Instant::now();
286 let commit_result = transaction.commit();
287 let commit_elapsed = commit_started.elapsed();
288 if let Err(error) = commit_result {
289 radixdb_storage::instrumentation::record_copy(
290 rows_affected.max(0) as u64,
291 parse_elapsed,
292 commit_elapsed,
293 copy_started.elapsed(),
294 );
295 return Err(error);
296 }
297 if rows_affected > 0 {
298 self.invalidate_copy_caches(table_name);
299 }
300 radixdb_storage::instrumentation::record_copy(
301 rows_affected.max(0) as u64,
302 parse_elapsed,
303 commit_elapsed,
304 copy_started.elapsed(),
305 );
306 rows_affected
307 }
308 Err(error) => {
309 transaction.rollback()?;
310 radixdb_storage::instrumentation::record_copy(
311 0,
312 parse_elapsed,
313 std::time::Duration::ZERO,
314 copy_started.elapsed(),
315 );
316 return Err(error);
317 }
318 };
319
320 Ok(Box::new(ExecResult::with_rows_affected(rows_affected)))
321 }
322
323 fn invalidate_copy_caches(&self, table_name: &str) {
325 self.mutation_invalidate_semantic_cache(table_name);
326 invalidate_semi_join_cache_for_table(table_name);
327 invalidate_scalar_subquery_cache_for_table(table_name);
328 invalidate_in_subquery_cache_for_table(table_name);
329 }
330
331 #[allow(clippy::too_many_arguments)]
333 fn copy_from_csv(
334 &self,
335 stmt: &CopyStatement,
336 table: &mut Box<dyn Table>,
337 schema: &Schema,
338 column_indices: &[usize],
339 all_column_types: &[DataType],
340 all_vector_dims: &[u16],
341 default_exprs: &[Option<String>],
342 fk_schema: &Option<CompactArc<Schema>>,
343 schema_column_count: usize,
344 copy_transaction_bytes: &mut usize,
345 copy_max_transaction_bytes: usize,
346 ) -> Result<i64> {
347 let file = std::fs::File::open(&stmt.file_path).map_err(|e| {
348 Error::InvalidArgument(format!("cannot open file '{}': {}", stmt.file_path, e))
349 })?;
350
351 let mut reader = csv::ReaderBuilder::new()
352 .has_headers(stmt.header)
353 .delimiter(stmt.delimiter)
354 .from_reader(std::io::BufReader::new(file));
355
356 let field_to_col: Option<Vec<usize>> = if stmt.header && stmt.columns.is_empty() {
358 let headers = reader
359 .headers()
360 .map_err(|e| Error::InvalidArgument(format!("cannot read CSV headers: {}", e)))?;
361 let col_map = {
362 let m = schema.column_index_map();
363 m.clone()
364 };
365 let mut mapping = Vec::with_capacity(headers.len());
366 let mut seen_headers = FxHashSet::default();
367 for h in headers.iter() {
368 let lower = h.to_lowercase();
369 if !seen_headers.insert(lower.clone()) {
370 return Err(Error::InvalidArgument(format!(
371 "duplicate CSV header '{}'",
372 h
373 )));
374 }
375 if let Some(&idx) = col_map.get(lower.as_str()) {
376 mapping.push(idx);
377 } else {
378 return Err(Error::ColumnNotFound(h.to_string()));
379 }
380 }
381 Some(mapping)
382 } else {
383 None
384 };
385
386 let null_str = stmt.null_string.as_deref().unwrap_or("");
387 let mut rows_affected = 0i64;
388
389 let compiled_table_checks = compile_table_check_constraints(schema)?;
390 let mut table_check_vm = crate::expression::ExprVM::new();
391 let mut insert_batch = Vec::with_capacity(COPY_INSERT_BATCH_ROWS);
392
393 for result in reader.records() {
394 let record = result.map_err(|e| {
395 Error::InvalidArgument(format!(
396 "CSV parse error at row {}: {}",
397 rows_affected + 1,
398 e
399 ))
400 })?;
401
402 let effective_indices = field_to_col.as_deref().unwrap_or(column_indices);
403
404 if record.len() != effective_indices.len() {
405 return Err(Error::InvalidArgument(format!(
406 "CSV row {} has {} fields but expected {}",
407 rows_affected + 1,
408 record.len(),
409 effective_indices.len()
410 )));
411 }
412
413 let mut row_values =
416 build_default_row(default_exprs, all_column_types, schema_column_count)?;
417
418 for (i, field) in record.iter().enumerate() {
420 let col_idx = effective_indices[i];
421
422 if field == null_str {
423 row_values[col_idx] = Value::null_unknown();
424 continue;
425 }
426
427 let target_type = all_column_types[col_idx];
428 let col_name = schema.columns[col_idx].name.as_str();
429 let value = parse_field(field, target_type, col_name)?;
430 validate_vector_dims(&value, target_type, all_vector_dims[col_idx])?;
431 row_values[col_idx] = value;
432 }
433
434 let mut row = Row::from_values(row_values);
435 prepare_insert_row_constraints(
436 table.as_mut(),
437 schema,
438 &compiled_table_checks,
439 &mut row,
440 &mut table_check_vm,
441 )?;
442
443 if let Some(ref fks) = fk_schema {
445 crate::mutation::foreign_key::check_parent_exists(
446 self.mutation_engine(),
447 table.txn_id(),
448 fks,
449 &row,
450 )?;
451 }
452
453 account_copy_transaction_row(
454 copy_transaction_bytes,
455 copy_max_transaction_bytes,
456 &row,
457 rows_affected + 1,
458 )?;
459 insert_batch.push(row);
460 if fk_schema.is_some() || insert_batch.len() == COPY_INSERT_BATCH_ROWS {
465 flush_copy_insert_batch(table, &mut insert_batch)?;
466 }
467 rows_affected += 1;
468 }
469
470 flush_copy_insert_batch(table, &mut insert_batch)?;
471
472 Ok(rows_affected)
473 }
474
475 #[allow(clippy::too_many_arguments)]
477 fn copy_from_json(
478 &self,
479 stmt: &CopyStatement,
480 table: &mut Box<dyn Table>,
481 schema: &Schema,
482 column_indices: &[usize],
483 all_column_types: &[DataType],
484 all_vector_dims: &[u16],
485 default_exprs: &[Option<String>],
486 fk_schema: &Option<CompactArc<Schema>>,
487 schema_column_count: usize,
488 copy_transaction_bytes: &mut usize,
489 copy_max_transaction_bytes: usize,
490 ) -> Result<i64> {
491 let null_str = stmt.null_string.as_deref();
492 let use_columns = !stmt.columns.is_empty();
493
494 let compiled_table_checks = compile_table_check_constraints(schema)?;
495 let mut table_check_vm = crate::expression::ExprVM::new();
496 let mut insert_batch = Vec::with_capacity(COPY_INSERT_BATCH_ROWS);
497
498 let col_name_lower_map: FxHashMap<String, usize> = if use_columns {
500 stmt.columns
501 .iter()
502 .enumerate()
503 .map(|(i, column)| (column.value_lower.to_string(), column_indices[i]))
504 .collect()
505 } else {
506 schema
507 .columns
508 .iter()
509 .enumerate()
510 .map(|(idx, c)| (c.name.to_lowercase(), idx))
511 .collect()
512 };
513
514 let file = std::fs::File::open(&stmt.file_path).map_err(|e| {
519 Error::InvalidArgument(format!("cannot open file '{}': {}", stmt.file_path, e))
520 })?;
521 let reader = JsonArrayStripper::new(std::io::BufReader::new(file));
522 let stream = serde_json::Deserializer::from_reader(reader).into_iter::<serde_json::Value>();
523
524 let mut rows_affected = 0i64;
525 for (idx, result) in stream.enumerate() {
526 let item = result.map_err(|e| {
527 Error::InvalidArgument(format!("JSON parse error at object {}: {}", idx + 1, e))
528 })?;
529
530 let obj = item.as_object().ok_or_else(|| {
531 Error::InvalidArgument(format!("JSON item {} is not an object", idx + 1))
532 })?;
533 let row = self.prepare_json_row(
534 obj,
535 table,
536 schema,
537 default_exprs,
538 schema_column_count,
539 &col_name_lower_map,
540 use_columns,
541 all_column_types,
542 all_vector_dims,
543 null_str,
544 &compiled_table_checks,
545 &mut table_check_vm,
546 fk_schema,
547 copy_transaction_bytes,
548 copy_max_transaction_bytes,
549 rows_affected + 1,
550 )?;
551 insert_batch.push(row);
552 if fk_schema.is_some() || insert_batch.len() == COPY_INSERT_BATCH_ROWS {
553 flush_copy_insert_batch(table, &mut insert_batch)?;
554 }
555 rows_affected += 1;
556 }
557
558 flush_copy_insert_batch(table, &mut insert_batch)?;
559
560 Ok(rows_affected)
561 }
562
563 #[allow(clippy::too_many_arguments)]
565 fn prepare_json_row(
566 &self,
567 obj: &serde_json::Map<String, serde_json::Value>,
568 table: &mut Box<dyn Table>,
569 schema: &Schema,
570 default_exprs: &[Option<String>],
571 schema_column_count: usize,
572 col_name_lower_map: &FxHashMap<String, usize>,
573 use_columns: bool,
574 all_column_types: &[DataType],
575 all_vector_dims: &[u16],
576 null_str: Option<&str>,
577 compiled_table_checks: &[(String, crate::expression::SharedProgram)],
578 table_check_vm: &mut crate::expression::ExprVM,
579 fk_schema: &Option<CompactArc<Schema>>,
580 copy_transaction_bytes: &mut usize,
581 copy_max_transaction_bytes: usize,
582 row_number: i64,
583 ) -> Result<Row> {
584 let mut normalized_obj = FxHashMap::default();
585 for (key, value) in obj {
586 let normalized = key.to_lowercase();
587 if normalized_obj.insert(normalized, value).is_some() {
588 return Err(Error::InvalidArgument(format!(
589 "duplicate JSON key '{}' after case normalization",
590 key
591 )));
592 }
593 }
594
595 for lower_key in normalized_obj.keys() {
598 if !col_name_lower_map.contains_key(lower_key) {
599 return Err(Error::ColumnNotFound(lower_key.clone()));
600 }
601 }
602
603 let mut row_values =
604 build_default_row(default_exprs, all_column_types, schema_column_count)?;
605
606 if use_columns {
607 for (lower_name, col_idx) in col_name_lower_map {
609 let target_type = all_column_types[*col_idx];
610 if let Some(v) = normalized_obj.get(lower_name) {
611 let value = json_value_to_radixdb(v, target_type, lower_name, null_str)?;
612 validate_vector_dims(&value, target_type, all_vector_dims[*col_idx])?;
613 row_values[*col_idx] = value;
614 }
615 }
617 } else {
618 for (lower_key, json_val) in normalized_obj {
620 let col_idx = col_name_lower_map[&lower_key];
621 let target_type = all_column_types[col_idx];
622 let value = json_value_to_radixdb(json_val, target_type, &lower_key, null_str)?;
623 validate_vector_dims(&value, target_type, all_vector_dims[col_idx])?;
624 row_values[col_idx] = value;
625 }
626 }
627
628 let mut row = Row::from_values(row_values);
629 prepare_insert_row_constraints(
630 &mut **table,
631 schema,
632 compiled_table_checks,
633 &mut row,
634 table_check_vm,
635 )?;
636
637 if let Some(ref fks) = fk_schema {
638 crate::mutation::foreign_key::check_parent_exists(
639 self.mutation_engine(),
640 table.txn_id(),
641 fks,
642 &row,
643 )?;
644 }
645
646 account_copy_transaction_row(
647 copy_transaction_bytes,
648 copy_max_transaction_bytes,
649 &row,
650 row_number,
651 )?;
652 Ok(row)
653 }
654}
655
656impl<T: MutationHost + ?Sized> CopyExecutorExt for T {}
657
658fn build_default_row(
660 default_exprs: &[Option<String>],
661 all_column_types: &[DataType],
662 schema_column_count: usize,
663) -> Result<Vec<Value>> {
664 let mut row = Vec::with_capacity(schema_column_count);
665 for i in 0..schema_column_count {
666 if let Some(ref default_expr) = default_exprs[i] {
667 let default_type = all_column_types[i];
668 row.push(evaluate_default_expr(default_expr, default_type)?);
669 } else {
670 row.push(Value::null_unknown());
671 }
672 }
673 Ok(row)
674}
675
676#[inline]
678fn validate_vector_dims(value: &Value, target_type: DataType, expected_dims: u16) -> Result<()> {
679 if target_type == DataType::Vector && expected_dims > 0 {
680 if let Value::Extension(data) = value {
681 if data.first() == Some(&(DataType::Vector as u8)) {
682 let got_dim = u16::try_from((data.len() - 1) / 4).unwrap_or(u16::MAX);
683 if got_dim != expected_dims {
684 return Err(Error::VectorDimensionMismatch {
685 expected: expected_dims,
686 got: got_dim,
687 });
688 }
689 }
690 }
691 }
692 Ok(())
693}
694
695fn json_value_to_radixdb(
698 v: &serde_json::Value,
699 target_type: DataType,
700 col_name: &str,
701 null_str: Option<&str>,
702) -> Result<Value> {
703 let val = match v {
704 serde_json::Value::Null => return Ok(Value::null_unknown()),
705 serde_json::Value::Bool(b) => Value::Boolean(*b),
706 serde_json::Value::Number(n) => {
707 Value::text(n.to_string())
711 }
712 serde_json::Value::String(s) => {
713 if let Some(ns) = null_str {
714 if s == ns {
715 return Ok(Value::null_unknown());
716 }
717 }
718 Value::text(s)
719 }
720 serde_json::Value::Object(_) | serde_json::Value::Array(_) => Value::text(v.to_string()),
721 };
722
723 val.try_coerce_to_type(target_type).map_err(|error| {
724 Error::Type(format!(
725 "cannot convert value '{}' to {:?} for column '{}': {}",
726 val, target_type, col_name, error
727 ))
728 })
729}
730
731struct JsonArrayStripper<R> {
737 inner: R,
738 is_array: bool,
739 depth: u32,
741 in_string: bool,
743 escape: bool,
745 pending: Option<u8>,
747 expect_value: bool,
749 seen_value: bool,
750 array_closed: bool,
751 terminal_checked: bool,
752}
753
754impl<R: std::io::Read> JsonArrayStripper<R> {
755 fn new(mut inner: R) -> Self {
756 let mut first = [0u8; 1];
758 let (is_array, pending) = loop {
759 match inner.read(&mut first) {
760 Ok(1) if first[0].is_ascii_whitespace() => continue,
761 Ok(1) if first[0] == b'[' => break (true, None), Ok(1) => break (false, Some(first[0])), _ => break (false, None), }
765 };
766
767 JsonArrayStripper {
768 inner,
769 is_array,
770 depth: 0,
771 in_string: false,
772 escape: false,
773 pending,
774 expect_value: true,
775 seen_value: false,
776 array_closed: false,
777 terminal_checked: false,
778 }
779 }
780}
781
782impl<R: std::io::Read> std::io::Read for JsonArrayStripper<R> {
783 fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
784 if let Some(b) = self.pending.take() {
786 buf[0] = b;
787 if buf.len() == 1 {
788 return Ok(1);
789 }
790 let n = self.inner.read(&mut buf[1..])?;
791 return Ok(1 + n);
792 }
793
794 let n = self.inner.read(buf)?;
795 if self.is_array {
796 if n == 0 {
797 if !self.terminal_checked {
798 self.terminal_checked = true;
799 if !self.array_closed || self.in_string || self.depth != 0 {
800 return Err(std::io::Error::new(
801 std::io::ErrorKind::InvalidData,
802 "unterminated JSON array",
803 ));
804 }
805 }
806 } else {
807 self.strip_array_syntax(buf, n)?;
808 }
809 }
810 Ok(n)
811 }
812}
813
814impl<R> JsonArrayStripper<R> {
815 fn strip_array_syntax(&mut self, buf: &mut [u8], len: usize) -> std::io::Result<()> {
819 for b in &mut buf[..len] {
820 if self.array_closed {
821 if !b.is_ascii_whitespace() {
822 return Err(std::io::Error::new(
823 std::io::ErrorKind::InvalidData,
824 "trailing data after JSON array",
825 ));
826 }
827 continue;
828 }
829
830 if self.in_string {
831 if self.escape {
832 self.escape = false;
833 } else if *b == b'\\' {
834 self.escape = true;
835 } else if *b == b'"' {
836 self.in_string = false;
837 }
838 continue;
839 }
840
841 match *b {
842 b'"' => {
843 if self.depth == 0 {
844 return Err(std::io::Error::new(
845 std::io::ErrorKind::InvalidData,
846 "JSON array COPY items must be objects",
847 ));
848 }
849 self.in_string = true;
850 }
851 b'{' if self.depth == 0 => {
852 if !self.expect_value {
853 return Err(std::io::Error::new(
854 std::io::ErrorKind::InvalidData,
855 "missing comma between JSON array items",
856 ));
857 }
858 self.expect_value = false;
859 self.seen_value = true;
860 self.depth = 1;
861 }
862 b'{' | b'[' => {
863 self.depth += 1;
864 }
865 b'}' | b']' => {
866 if self.depth > 0 {
867 self.depth -= 1;
868 } else if *b == b']' && (!self.expect_value || !self.seen_value) {
869 *b = b' ';
871 self.array_closed = true;
872 } else {
873 return Err(std::io::Error::new(
874 std::io::ErrorKind::InvalidData,
875 "invalid JSON array closing delimiter",
876 ));
877 }
878 }
879 b',' if self.depth == 0 => {
880 if self.expect_value || !self.seen_value {
881 return Err(std::io::Error::new(
882 std::io::ErrorKind::InvalidData,
883 "unexpected comma in JSON array",
884 ));
885 }
886 *b = b' ';
888 self.expect_value = true;
889 }
890 _ if self.depth == 0 && !b.is_ascii_whitespace() => {
891 return Err(std::io::Error::new(
892 std::io::ErrorKind::InvalidData,
893 "JSON array COPY items must be objects",
894 ));
895 }
896 _ => {}
897 }
898 }
899 Ok(())
900 }
901}