BuildDuckDBCopyFromSQL generates a DuckDB COPY FROM statement
(tableName, columnList, filePath string, opts *CopyFromOptions)
| 159 | opts.HasHeader = copyWithCSVRegex.MatchString(upperQuery) && copyWithHeaderRegex.MatchString(upperQuery) |
| 160 | |
| 161 | return opts, nil |
| 162 | } |
| 163 | |
| 164 | // BuildDuckDBCopyFromSQL generates a DuckDB COPY FROM statement |
| 165 | func BuildDuckDBCopyFromSQL(tableName, columnList, filePath string, opts *CopyFromOptions) string { |
| 166 | // DuckDB syntax: COPY table FROM 'file' (FORMAT CSV, HEADER, NULL 'value', DELIMITER ',', QUOTE '"') |
| 167 | // AUTO_DETECT FALSE disables sniffer to prevent it from overriding our settings |
| 168 | // STRICT_MODE FALSE allows reading rows that don't strictly comply with CSV standard |
| 169 | // PARALLEL FALSE avoids "Parallel CSV Reader does not support full read" errors |
| 170 | // on files streamed from COPY FROM STDIN (temp files with no seek support for sniffing) |
| 171 | copyOptions := []string{ |
| 172 | "FORMAT CSV", |
| 173 | "AUTO_DETECT FALSE", |
| 174 | "STRICT_MODE FALSE", |
| 175 | "PARALLEL FALSE", |
| 176 | fmt.Sprintf("MAX_LINE_SIZE %d", copyMaxLineSizeBytes), |
| 177 | } |
| 178 | if opts.HasHeader { |
| 179 | copyOptions = append(copyOptions, "HEADER") |
| 180 | } |
| 181 | // Always specify NULL string - DuckDB doesn't recognize \N by default |
| 182 | copyOptions = append(copyOptions, fmt.Sprintf("NULL '%s'", opts.NullString)) |
| 183 | // Always specify DELIMITER explicitly (required when AUTO_DETECT is FALSE) |
| 184 | copyOptions = append(copyOptions, fmt.Sprintf("DELIMITER '%s'", opts.Delimiter)) |
| 185 | // Always specify QUOTE for CSV to ensure proper quote handling |
| 186 | if opts.Quote != "" { |
| 187 | copyOptions = append(copyOptions, fmt.Sprintf("QUOTE '%s'", opts.Quote)) |
| 188 | // Set ESCAPE to match QUOTE for RFC 4180 compliance (doubled quotes = escaped quote) |
| 189 | escape := opts.Escape |
| 190 | if escape == "" { |
| 191 | escape = opts.Quote |
| 192 | } |
| 193 | copyOptions = append(copyOptions, fmt.Sprintf("ESCAPE '%s'", escape)) |
no outgoing calls