@@ -33,10 +33,9 @@ use crate::display_utils::{indented_list, Indent, SpaceOrNewline};
3333
3434use super :: {
3535 display_comma_separated, display_separated, helpers:: attached_token:: AttachedToken ,
36- query:: InputFormatClause , Assignment , CopyLegacyCsvOption , CopyLegacyOption , CopyOption ,
37- CopySource , CopyTarget , Expr , FromTable , Ident , InsertAliases , MysqlInsertPriority , ObjectName ,
38- OnInsert , OrderByExpr , Query , SelectItem , Setting , SqliteOnConflict , TableObject ,
39- TableWithJoins , UpdateTableFromKind ,
36+ query:: InputFormatClause , Assignment , CopyLegacyOption , CopyOption , CopySource , CopyTarget ,
37+ Expr , FromTable , Ident , InsertAliases , MysqlInsertPriority , ObjectName , OnInsert , OrderByExpr ,
38+ Query , SelectItem , Setting , SqliteOnConflict , TableObject , TableWithJoins , UpdateTableFromKind ,
4039} ;
4140
4241/// INSERT statement.
@@ -317,191 +316,6 @@ impl Display for Update {
317316 }
318317}
319318
320- /// CSV formatting options extracted from COPY options.
321- ///
322- /// This struct encapsulates the CSV formatting settings used when parsing
323- /// or formatting COPY statement data. It extracts relevant options from both
324- /// modern [`CopyOption`] and legacy [`CopyLegacyOption`] variants.
325- #[ derive( Debug , Clone , PartialEq , Eq ) ]
326- pub struct CsvFormatOptions {
327- /// The field delimiter character (default: tab)
328- pub ( crate ) delimiter : char ,
329- /// The quote character used to enclose fields (default: `"`)
330- pub ( crate ) quote : char ,
331- /// The escape character (default: `\`)
332- pub ( crate ) escape : char ,
333- /// The string representing NULL values (default: `\\N`)
334- pub ( crate ) null_symbol : String ,
335- }
336-
337- impl Default for CsvFormatOptions {
338- fn default ( ) -> Self {
339- Self {
340- delimiter : '\t' ,
341- quote : '"' ,
342- escape : '\\' ,
343- null_symbol : "\\ N" . to_string ( ) ,
344- }
345- }
346- }
347-
348- impl CsvFormatOptions {
349- /// Extract CSV format options from CopyOption and CopyLegacyOption lists.
350- ///
351- /// This method processes both modern and legacy COPY options to determine
352- /// the CSV formatting settings. Later options in the lists override earlier ones.
353- ///
354- /// # Arguments
355- ///
356- /// * `options` - Modern COPY options (PostgreSQL 9.0+)
357- /// * `legacy_options` - Legacy COPY options (pre-PostgreSQL 9.0)
358- ///
359- /// # Returns
360- ///
361- /// A `CsvFormatOptions` instance with the extracted settings, using defaults
362- /// for any options not specified.
363- pub ( crate ) fn from_copy_options (
364- options : & [ CopyOption ] ,
365- legacy_options : & [ CopyLegacyOption ] ,
366- ) -> Self {
367- let mut csv_options = Self :: default ( ) ;
368-
369- // Apply options
370- for option in options {
371- match option {
372- CopyOption :: Delimiter ( c) => {
373- csv_options. delimiter = * c;
374- }
375- CopyOption :: Quote ( c) => {
376- csv_options. quote = * c;
377- }
378- CopyOption :: Escape ( c) => {
379- csv_options. escape = * c;
380- }
381- CopyOption :: Null ( null) => {
382- csv_options. null_symbol = null. clone ( ) ;
383- }
384- // These options don't affect CSV formatting
385- CopyOption :: Format ( _)
386- | CopyOption :: Freeze ( _)
387- | CopyOption :: Header ( _)
388- | CopyOption :: ForceQuote ( _)
389- | CopyOption :: ForceNotNull ( _)
390- | CopyOption :: ForceNull ( _)
391- | CopyOption :: Encoding ( _) => { }
392- }
393- }
394-
395- // Apply legacy options
396- for option in legacy_options {
397- match option {
398- CopyLegacyOption :: Delimiter ( c) => {
399- csv_options. delimiter = * c;
400- }
401- CopyLegacyOption :: Null ( null) => {
402- csv_options. null_symbol = null. clone ( ) ;
403- }
404- CopyLegacyOption :: Csv ( csv_opts) => {
405- for csv_option in csv_opts {
406- match csv_option {
407- CopyLegacyCsvOption :: Quote ( c) => {
408- csv_options. quote = * c;
409- }
410- CopyLegacyCsvOption :: Escape ( c) => {
411- csv_options. escape = * c;
412- }
413- // These CSV options don't affect CSV formatting
414- CopyLegacyCsvOption :: Header
415- | CopyLegacyCsvOption :: ForceQuote ( _)
416- | CopyLegacyCsvOption :: ForceNotNull ( _) => { }
417- }
418- }
419- }
420- // These legacy options don't affect CSV formatting
421- CopyLegacyOption :: AcceptAnyDate
422- | CopyLegacyOption :: AcceptInvChars ( _)
423- | CopyLegacyOption :: AddQuotes
424- | CopyLegacyOption :: AllowOverwrite
425- | CopyLegacyOption :: Binary
426- | CopyLegacyOption :: BlankAsNull
427- | CopyLegacyOption :: Bzip2
428- | CopyLegacyOption :: CleanPath
429- | CopyLegacyOption :: CompUpdate { .. }
430- | CopyLegacyOption :: DateFormat ( _)
431- | CopyLegacyOption :: EmptyAsNull
432- | CopyLegacyOption :: Encrypted { .. }
433- | CopyLegacyOption :: Escape
434- | CopyLegacyOption :: Extension ( _)
435- | CopyLegacyOption :: FixedWidth ( _)
436- | CopyLegacyOption :: Gzip
437- | CopyLegacyOption :: Header
438- | CopyLegacyOption :: IamRole ( _)
439- | CopyLegacyOption :: IgnoreHeader ( _)
440- | CopyLegacyOption :: Json
441- | CopyLegacyOption :: Manifest { .. }
442- | CopyLegacyOption :: MaxFileSize ( _)
443- | CopyLegacyOption :: Parallel ( _)
444- | CopyLegacyOption :: Parquet
445- | CopyLegacyOption :: PartitionBy ( _)
446- | CopyLegacyOption :: Region ( _)
447- | CopyLegacyOption :: RemoveQuotes
448- | CopyLegacyOption :: RowGroupSize ( _)
449- | CopyLegacyOption :: StatUpdate ( _)
450- | CopyLegacyOption :: TimeFormat ( _)
451- | CopyLegacyOption :: TruncateColumns
452- | CopyLegacyOption :: Zstd => { }
453- }
454- }
455-
456- csv_options
457- }
458-
459- /// Format a single CSV field, adding quotes and escaping if necessary.
460- ///
461- /// This method handles CSV field formatting according to the configured options:
462- /// - Writes NULL values using the configured `null_symbol`
463- /// - Adds quotes around fields containing delimiters, quotes, or newlines
464- /// - Escapes quote characters by doubling them
465- /// - Escapes escape characters
466- ///
467- /// # Arguments
468- ///
469- /// * `f` - The formatter to write to
470- /// * `field` - The field value to format, or `None` for NULL
471- ///
472- /// # Returns
473- ///
474- /// A `fmt::Result` indicating success or failure of the write operation.
475- fn format_csv_field ( & self , f : & mut fmt:: Formatter , field : Option < & str > ) -> fmt:: Result {
476- let field_value = field. unwrap_or ( & self . null_symbol ) ;
477-
478- // Check if field needs quoting
479- let needs_quoting = field_value. contains ( self . delimiter )
480- || field_value. contains ( self . quote )
481- || field_value. contains ( '\n' )
482- || field_value. contains ( '\r' ) ;
483-
484- if needs_quoting {
485- write ! ( f, "{}" , self . quote) ?;
486- for ch in field_value. chars ( ) {
487- if ch == self . quote {
488- // Escape quote by doubling it
489- write ! ( f, "{}{}" , self . quote, self . quote) ?;
490- } else if ch == self . escape {
491- // Escape escape character
492- write ! ( f, "{}{}" , self . escape, self . escape) ?;
493- } else {
494- write ! ( f, "{}" , ch) ?;
495- }
496- }
497- write ! ( f, "{}" , self . quote) ?;
498- } else {
499- write ! ( f, "{}" , field_value) ?;
500- }
501- Ok ( ( ) )
502- }
503- }
504-
505319/// COPY statement.
506320///
507321/// Represents a PostgreSQL COPY statement for bulk data transfer between
@@ -550,7 +364,7 @@ pub struct Copy {
550364 /// CSV data rows for COPY FROM STDIN statements.
551365 /// Each row is a vector of optional strings (None represents NULL).
552366 /// Populated only when copying from STDIN with inline data.
553- pub values : Vec < Vec < Option < String > > > ,
367+ pub values : Option < String > ,
554368}
555369
556370impl Display for Copy {
@@ -581,24 +395,8 @@ impl Display for Copy {
581395 write ! ( f, " {}" , display_separated( & self . legacy_options, " " ) ) ?;
582396 }
583397
584- if !self . values . is_empty ( ) {
585- writeln ! ( f, ";" ) ?;
586-
587- let csv_options =
588- CsvFormatOptions :: from_copy_options ( & self . options , & self . legacy_options ) ;
589-
590- // Write CSV data
591- for row in & self . values {
592- for ( idx, column) in row. iter ( ) . enumerate ( ) {
593- if idx > 0 {
594- write ! ( f, "{}" , csv_options. delimiter) ?;
595- }
596- csv_options. format_csv_field ( f, column. as_deref ( ) ) ?;
597- }
598- writeln ! ( f) ?;
599- }
600-
601- write ! ( f, "\\ ." ) ?;
398+ if let Some ( values) = & self . values {
399+ write ! ( f, ";{values}\\ ." ) ?;
602400 }
603401 Ok ( ( ) )
604402 }
0 commit comments