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