1#![allow(unsafe_op_in_unsafe_fn)]
2use std::path::PathBuf;
3use std::sync::Arc;
4
5use polars_buffer::Buffer;
6use polars_core::datatypes::{DataType, Field};
7use polars_core::schema::{Schema, SchemaRef};
8use polars_error::PolarsResult;
9use polars_utils::pl_str::PlSmallStr;
10#[cfg(feature = "serde")]
11use serde::{Deserialize, Serialize};
12
13use crate::RowIndex;
14
15#[derive(Clone, Debug, PartialEq, Eq, Hash)]
16#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
17#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
18pub struct CsvReadOptions {
19 pub path: Option<PathBuf>,
20 pub rechunk: bool,
22 pub n_threads: Option<usize>,
23 pub low_memory: bool,
24 pub n_rows: Option<usize>,
26 pub row_index: Option<RowIndex>,
27 pub columns: Option<Arc<[PlSmallStr]>>,
29 pub projection: Option<Arc<Vec<usize>>>,
30 pub schema: Option<SchemaRef>,
31 pub schema_overwrite: Option<SchemaRef>,
32 pub column_names_overwrite: Option<Buffer<PlSmallStr>>,
34 pub dtype_overwrite: Option<Arc<Vec<DataType>>>,
35 pub parse_options: Arc<CsvParseOptions>,
37 pub has_header: bool,
38 pub chunk_size: usize,
39 pub skip_rows: usize,
41 pub skip_lines: usize,
43 pub skip_rows_after_header: usize,
44 pub infer_schema_length: Option<usize>,
45 pub raise_if_empty: bool,
46 pub ignore_errors: bool,
47 pub fields_to_cast: Vec<Field>,
48}
49
50#[derive(Clone, Debug, PartialEq, Eq, Hash)]
51#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
52#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
53pub struct CsvParseOptions {
54 pub separator: u8,
55 pub quote_char: Option<u8>,
56 pub eol_char: u8,
57 pub encoding: CsvEncoding,
58 pub null_values: Option<NullValues>,
59 pub missing_is_null: bool,
60 pub truncate_ragged_lines: bool,
61 pub comment_prefix: Option<CommentPrefix>,
62 pub try_parse_dates: bool,
63 pub decimal_comma: bool,
64}
65
66impl Default for CsvReadOptions {
67 fn default() -> Self {
68 Self {
69 path: None,
70
71 rechunk: false,
72 n_threads: None,
73 low_memory: false,
74
75 n_rows: None,
76 row_index: None,
77
78 columns: None,
79 projection: None,
80 schema: None,
81 schema_overwrite: None,
82 column_names_overwrite: None,
83 dtype_overwrite: None,
84
85 parse_options: Default::default(),
86 has_header: true,
87 chunk_size: 1 << 18,
88 skip_rows: 0,
89 skip_lines: 0,
90 skip_rows_after_header: 0,
91 infer_schema_length: Some(100),
92 raise_if_empty: true,
93 ignore_errors: false,
94 fields_to_cast: vec![],
95 }
96 }
97}
98
99impl Default for CsvParseOptions {
101 fn default() -> Self {
102 Self {
103 separator: b',',
104 quote_char: Some(b'"'),
105 eol_char: b'\n',
106 encoding: Default::default(),
107 null_values: None,
108 missing_is_null: true,
109 truncate_ragged_lines: false,
110 comment_prefix: None,
111 try_parse_dates: false,
112 decimal_comma: false,
113 }
114 }
115}
116
117impl CsvReadOptions {
118 pub fn get_parse_options(&self) -> Arc<CsvParseOptions> {
119 self.parse_options.clone()
120 }
121
122 pub fn with_path<P: Into<PathBuf>>(mut self, path: Option<P>) -> Self {
123 self.path = path.map(|p| p.into());
124 self
125 }
126
127 pub fn with_rechunk(mut self, rechunk: bool) -> Self {
129 self.rechunk = rechunk;
130 self
131 }
132
133 pub fn with_n_threads(mut self, n_threads: Option<usize>) -> Self {
136 self.n_threads = n_threads;
137 self
138 }
139
140 pub fn with_low_memory(mut self, low_memory: bool) -> Self {
142 self.low_memory = low_memory;
143 self
144 }
145
146 pub fn with_n_rows(mut self, n_rows: Option<usize>) -> Self {
148 self.n_rows = n_rows;
149 self
150 }
151
152 pub fn with_row_index(mut self, row_index: Option<RowIndex>) -> Self {
154 self.row_index = row_index;
155 self
156 }
157
158 pub fn with_columns(mut self, columns: Option<Arc<[PlSmallStr]>>) -> Self {
160 self.columns = columns;
161 self
162 }
163
164 pub fn with_projection(mut self, projection: Option<Arc<Vec<usize>>>) -> Self {
167 self.projection = projection;
168 self
169 }
170
171 pub fn with_schema(mut self, schema: Option<SchemaRef>) -> Self {
175 self.schema = schema;
176 self
177 }
178
179 pub fn with_schema_overwrite(mut self, schema_overwrite: Option<SchemaRef>) -> Self {
181 self.schema_overwrite = schema_overwrite;
182 self
183 }
184
185 pub fn with_column_names_overwrite(
187 mut self,
188 column_names_overwrite: Buffer<PlSmallStr>,
189 ) -> Self {
190 self.column_names_overwrite = Some(column_names_overwrite);
191 self
192 }
193
194 pub fn with_dtype_overwrite(mut self, dtype_overwrite: Option<Arc<Vec<DataType>>>) -> Self {
197 self.dtype_overwrite = dtype_overwrite;
198 self
199 }
200
201 pub fn with_parse_options(mut self, parse_options: CsvParseOptions) -> Self {
204 self.parse_options = Arc::new(parse_options);
205 self
206 }
207
208 pub fn with_has_header(mut self, has_header: bool) -> Self {
210 self.has_header = has_header;
211 self
212 }
213
214 pub fn with_chunk_size(mut self, chunk_size: usize) -> Self {
216 self.chunk_size = chunk_size;
217 self
218 }
219
220 pub fn with_skip_rows(mut self, skip_rows: usize) -> Self {
224 self.skip_rows = skip_rows;
225 self
226 }
227
228 pub fn with_skip_lines(mut self, skip_lines: usize) -> Self {
232 self.skip_lines = skip_lines;
233 self
234 }
235
236 pub fn with_skip_rows_after_header(mut self, skip_rows_after_header: usize) -> Self {
238 self.skip_rows_after_header = skip_rows_after_header;
239 self
240 }
241
242 pub fn with_infer_schema_length(mut self, infer_schema_length: Option<usize>) -> Self {
246 self.infer_schema_length = infer_schema_length;
247 self
248 }
249
250 pub fn with_raise_if_empty(mut self, raise_if_empty: bool) -> Self {
253 self.raise_if_empty = raise_if_empty;
254 self
255 }
256
257 pub fn with_ignore_errors(mut self, ignore_errors: bool) -> Self {
259 self.ignore_errors = ignore_errors;
260 self
261 }
262
263 pub fn map_parse_options<F: Fn(CsvParseOptions) -> CsvParseOptions>(
265 mut self,
266 map_func: F,
267 ) -> Self {
268 let parse_options = Arc::unwrap_or_clone(self.parse_options);
269 self.parse_options = Arc::new(map_func(parse_options));
270 self
271 }
272}
273
274impl CsvParseOptions {
275 pub fn with_separator(mut self, separator: u8) -> Self {
278 self.separator = separator;
279 self
280 }
281
282 pub fn with_quote_char(mut self, quote_char: Option<u8>) -> Self {
285 self.quote_char = quote_char;
286 self
287 }
288
289 pub fn with_eol_char(mut self, eol_char: u8) -> Self {
291 self.eol_char = eol_char;
292 self
293 }
294
295 pub fn with_encoding(mut self, encoding: CsvEncoding) -> Self {
297 self.encoding = encoding;
298 self
299 }
300
301 pub fn with_null_values(mut self, null_values: Option<NullValues>) -> Self {
306 self.null_values = null_values;
307 self
308 }
309
310 pub fn with_missing_is_null(mut self, missing_is_null: bool) -> Self {
312 self.missing_is_null = missing_is_null;
313 self
314 }
315
316 pub fn with_truncate_ragged_lines(mut self, truncate_ragged_lines: bool) -> Self {
318 self.truncate_ragged_lines = truncate_ragged_lines;
319 self
320 }
321
322 pub fn with_comment_prefix<T: Into<CommentPrefix>>(
325 mut self,
326 comment_prefix: Option<T>,
327 ) -> Self {
328 self.comment_prefix = comment_prefix.map(Into::into);
329 self
330 }
331
332 pub fn with_try_parse_dates(mut self, try_parse_dates: bool) -> Self {
335 self.try_parse_dates = try_parse_dates;
336 self
337 }
338
339 pub fn with_decimal_comma(mut self, decimal_comma: bool) -> Self {
341 self.decimal_comma = decimal_comma;
342 self
343 }
344}
345
346#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Hash)]
347#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
348#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
349pub enum CsvEncoding {
350 #[default]
352 Utf8,
353 LossyUtf8,
355}
356
357#[derive(Clone, Debug, Eq, PartialEq, Hash)]
358#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
359#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
360pub enum CommentPrefix {
361 Single(u8),
363 Multi(PlSmallStr),
366}
367
368impl CommentPrefix {
369 pub fn new_single(prefix: u8) -> Self {
371 CommentPrefix::Single(prefix)
372 }
373
374 pub fn new_multi(prefix: PlSmallStr) -> Self {
376 CommentPrefix::Multi(prefix)
377 }
378
379 pub fn new_from_str(prefix: &str) -> Self {
381 assert!(!prefix.contains("\n"));
382 if prefix.len() == 1 && prefix.chars().next().unwrap().is_ascii() {
383 let c = prefix.as_bytes()[0];
384 CommentPrefix::Single(c)
385 } else {
386 CommentPrefix::Multi(PlSmallStr::from_str(prefix))
387 }
388 }
389}
390
391impl From<&str> for CommentPrefix {
392 fn from(value: &str) -> Self {
393 Self::new_from_str(value)
394 }
395}
396
397#[derive(Clone, Debug, Eq, PartialEq, Hash)]
398#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
399#[cfg_attr(feature = "dsl-schema", derive(schemars::JsonSchema))]
400pub enum NullValues {
401 AllColumnsSingle(PlSmallStr),
403 AllColumns(Vec<PlSmallStr>),
405 Named(Vec<(PlSmallStr, PlSmallStr)>),
407}
408
409impl NullValues {
410 pub fn compile(self, schema: &Schema) -> PolarsResult<NullValuesCompiled> {
411 Ok(match self {
412 NullValues::AllColumnsSingle(v) => NullValuesCompiled::AllColumnsSingle(v),
413 NullValues::AllColumns(v) => NullValuesCompiled::AllColumns(v),
414 NullValues::Named(v) => {
415 let mut null_values = vec![PlSmallStr::from_static(""); schema.len()];
416 for (name, null_value) in v {
417 let i = schema.try_index_of(&name)?;
418 null_values[i] = null_value;
419 }
420 NullValuesCompiled::Columns(null_values)
421 },
422 })
423 }
424}
425
426#[derive(Debug, Clone)]
427pub enum NullValuesCompiled {
428 AllColumnsSingle(PlSmallStr),
430 AllColumns(Vec<PlSmallStr>),
432 Columns(Vec<PlSmallStr>),
434}
435
436impl NullValuesCompiled {
437 pub(super) unsafe fn is_null(&self, field: &[u8], index: usize) -> bool {
441 use NullValuesCompiled::*;
442 match self {
443 AllColumnsSingle(v) => v.as_bytes() == field,
444 AllColumns(v) => v.iter().any(|v| v.as_bytes() == field),
445 Columns(v) => {
446 debug_assert!(index < v.len());
447 v.get_unchecked(index).as_bytes() == field
448 },
449 }
450 }
451}