Skip to main content

polars_error/
lib.rs

1pub mod constants;
2mod warning;
3
4use std::borrow::Cow;
5use std::collections::TryReserveError;
6use std::convert::Infallible;
7use std::error::Error;
8use std::fmt::{self, Display, Formatter, Write};
9use std::ops::Deref;
10use std::sync::{Arc, LazyLock};
11use std::{env, io};
12pub mod signals;
13
14pub use warning::*;
15
16#[cfg(feature = "python")]
17mod python;
18
19enum ErrorStrategy {
20    Panic,
21    WithBacktrace,
22    Normal,
23}
24
25static ERROR_STRATEGY: LazyLock<ErrorStrategy> = LazyLock::new(|| {
26    if env::var("POLARS_PANIC_ON_ERR").as_deref() == Ok("1") {
27        ErrorStrategy::Panic
28    } else if env::var("POLARS_BACKTRACE_IN_ERR").as_deref() == Ok("1") {
29        ErrorStrategy::WithBacktrace
30    } else {
31        ErrorStrategy::Normal
32    }
33});
34
35#[derive(Debug, Clone)]
36pub struct ErrString(Cow<'static, str>);
37
38impl ErrString {
39    pub const fn new_static(s: &'static str) -> Self {
40        Self(Cow::Borrowed(s))
41    }
42}
43
44impl<T> From<T> for ErrString
45where
46    T: Into<Cow<'static, str>>,
47{
48    #[track_caller]
49    fn from(msg: T) -> Self {
50        match &*ERROR_STRATEGY {
51            ErrorStrategy::Panic => panic!("{}", msg.into()),
52            ErrorStrategy::WithBacktrace => ErrString(Cow::Owned(format!(
53                "{}\n\nRust backtrace:\n{}",
54                msg.into(),
55                std::backtrace::Backtrace::force_capture()
56            ))),
57            ErrorStrategy::Normal => ErrString(msg.into()),
58        }
59    }
60}
61
62impl AsRef<str> for ErrString {
63    fn as_ref(&self) -> &str {
64        &self.0
65    }
66}
67
68impl Deref for ErrString {
69    type Target = str;
70
71    fn deref(&self) -> &Self::Target {
72        &self.0
73    }
74}
75
76impl Display for ErrString {
77    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
78        write!(f, "{}", self.0)
79    }
80}
81
82#[derive(Debug, Clone)]
83pub enum PolarsError {
84    AssertionError(ErrString),
85    ColumnNotFound(ErrString),
86    ComputeError(ErrString),
87    Duplicate(ErrString),
88    InvalidOperation(ErrString),
89    IO {
90        error: Arc<io::Error>,
91        msg: Option<ErrString>,
92    },
93    NoData(ErrString),
94    OutOfBounds(ErrString),
95    SchemaFieldNotFound(ErrString),
96    SchemaMismatch(ErrString),
97    ShapeMismatch(ErrString),
98    SQLInterface(ErrString),
99    SQLSyntax(ErrString),
100    StringCacheMismatch(ErrString),
101    StructFieldNotFound(ErrString),
102    Context {
103        error: Box<PolarsError>,
104        msg: ErrString,
105    },
106    #[cfg(feature = "python")]
107    Python {
108        error: python::PyErrWrap,
109    },
110}
111
112impl Error for PolarsError {}
113
114impl Display for PolarsError {
115    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
116        use PolarsError::*;
117        match self {
118            ComputeError(msg)
119            | InvalidOperation(msg)
120            | OutOfBounds(msg)
121            | SchemaMismatch(msg)
122            | SQLInterface(msg)
123            | SQLSyntax(msg) => write!(f, "{msg}"),
124
125            AssertionError(msg) => write!(f, "assertion failed: {msg}"),
126            ColumnNotFound(msg) => write!(f, "not found: {msg}"),
127            Duplicate(msg) => write!(f, "duplicate: {msg}"),
128            IO { error, msg } => match msg {
129                Some(m) => write!(f, "{m}"),
130                None => write!(f, "{error}"),
131            },
132            NoData(msg) => write!(f, "no data: {msg}"),
133            SchemaFieldNotFound(msg) => write!(f, "field not found: {msg}"),
134            ShapeMismatch(msg) => write!(f, "lengths don't match: {msg}"),
135            StringCacheMismatch(msg) => write!(f, "string caches don't match: {msg}"),
136            StructFieldNotFound(msg) => write!(f, "field not found: {msg}"),
137            Context { error, msg } => write!(f, "{error}: {msg}"),
138            #[cfg(feature = "python")]
139            Python { error } => write!(f, "python: {error}"),
140        }
141    }
142}
143
144impl From<io::Error> for PolarsError {
145    fn from(value: io::Error) -> Self {
146        PolarsError::IO {
147            error: Arc::new(value),
148            msg: None,
149        }
150    }
151}
152
153#[cfg(feature = "regex")]
154impl From<regex::Error> for PolarsError {
155    fn from(err: regex::Error) -> Self {
156        PolarsError::ComputeError(format!("regex error: {err}").into())
157    }
158}
159
160#[cfg(feature = "object_store")]
161impl From<object_store::Error> for PolarsError {
162    fn from(err: object_store::Error) -> Self {
163        if let object_store::Error::Generic { store, source } = &err {
164            if let Some(polars_err) = source.as_ref().downcast_ref::<PolarsError>() {
165                return polars_err.wrap_msg(|s| format!("{s} (store: {store})"));
166            }
167        }
168
169        std::io::Error::other(format!("object-store error: {err}")).into()
170    }
171}
172
173#[cfg(feature = "avro-schema")]
174impl From<avro_schema::error::Error> for PolarsError {
175    fn from(value: avro_schema::error::Error) -> Self {
176        polars_err!(ComputeError: "avro-error: {}", value)
177    }
178}
179
180impl From<simdutf8::basic::Utf8Error> for PolarsError {
181    fn from(value: simdutf8::basic::Utf8Error) -> Self {
182        polars_err!(ComputeError: "invalid utf8: {}", value)
183    }
184}
185#[cfg(feature = "arrow-format")]
186impl From<arrow_format::ipc::planus::Error> for PolarsError {
187    fn from(err: arrow_format::ipc::planus::Error) -> Self {
188        polars_err!(ComputeError: "parquet error: {err:?}")
189    }
190}
191
192impl From<TryReserveError> for PolarsError {
193    fn from(value: TryReserveError) -> Self {
194        polars_err!(ComputeError: "OOM: {}", value)
195    }
196}
197
198impl From<Infallible> for PolarsError {
199    fn from(_: Infallible) -> Self {
200        unreachable!()
201    }
202}
203
204pub type PolarsResult<T> = Result<T, PolarsError>;
205
206impl PolarsError {
207    pub fn context_trace(self) -> Self {
208        use PolarsError::*;
209        match self {
210            Context { error, msg } => {
211                // If context is 1 level deep, just return error.
212                if !matches!(&*error, PolarsError::Context { .. }) {
213                    return *error;
214                }
215                let mut current_error = &*error;
216                let material_error = error.get_err();
217
218                let mut messages = vec![&msg];
219
220                while let PolarsError::Context { msg, error } = current_error {
221                    current_error = error;
222                    messages.push(msg)
223                }
224
225                let mut bt = String::new();
226
227                let mut count = 0;
228                while let Some(msg) = messages.pop() {
229                    count += 1;
230                    writeln!(&mut bt, "\t[{count}] {msg}").unwrap();
231                }
232                material_error.wrap_msg(move |msg| {
233                    format!("{msg}\n\nThis error occurred with the following context stack:\n{bt}")
234                })
235            },
236            err => err,
237        }
238    }
239
240    pub fn wrap_msg<F: FnOnce(&str) -> String>(&self, func: F) -> Self {
241        use PolarsError::*;
242        match self {
243            AssertionError(msg) => AssertionError(func(msg).into()),
244            ColumnNotFound(msg) => ColumnNotFound(func(msg).into()),
245            ComputeError(msg) => ComputeError(func(msg).into()),
246            Duplicate(msg) => Duplicate(func(msg).into()),
247            InvalidOperation(msg) => InvalidOperation(func(msg).into()),
248            IO { error, msg } => {
249                let msg = match msg {
250                    Some(msg) => func(msg),
251                    None => func(&format!("{error}")),
252                };
253                IO {
254                    error: error.clone(),
255                    msg: Some(msg.into()),
256                }
257            },
258            NoData(msg) => NoData(func(msg).into()),
259            OutOfBounds(msg) => OutOfBounds(func(msg).into()),
260            SchemaFieldNotFound(msg) => SchemaFieldNotFound(func(msg).into()),
261            SchemaMismatch(msg) => SchemaMismatch(func(msg).into()),
262            ShapeMismatch(msg) => ShapeMismatch(func(msg).into()),
263            StringCacheMismatch(msg) => StringCacheMismatch(func(msg).into()),
264            StructFieldNotFound(msg) => StructFieldNotFound(func(msg).into()),
265            SQLInterface(msg) => SQLInterface(func(msg).into()),
266            SQLSyntax(msg) => SQLSyntax(func(msg).into()),
267            Context { error, .. } => error.wrap_msg(func),
268            #[cfg(feature = "python")]
269            Python { error } => pyo3::Python::attach(|py| {
270                use pyo3::types::{PyAnyMethods, PyStringMethods};
271                use pyo3::{IntoPyObject, PyErr};
272
273                let value = error.value(py);
274
275                let msg = if let Ok(s) = value.str() {
276                    func(&s.to_string_lossy())
277                } else {
278                    func("<exception str() failed>")
279                };
280
281                let cls = value.get_type();
282
283                let out = PyErr::from_type(cls, (msg,));
284
285                let out = if let Ok(out_with_traceback) = (|| {
286                    out.clone_ref(py)
287                        .into_pyobject(py)?
288                        .getattr("with_traceback")
289                        .unwrap()
290                        .call1((value.getattr("__traceback__").unwrap(),))
291                })() {
292                    PyErr::from_value(out_with_traceback)
293                } else {
294                    out
295                };
296
297                Python {
298                    error: python::PyErrWrap(out),
299                }
300            }),
301        }
302    }
303
304    fn get_err(&self) -> &Self {
305        use PolarsError::*;
306        match self {
307            Context { error, .. } => error.get_err(),
308            err => err,
309        }
310    }
311
312    pub fn context(self, msg: ErrString) -> Self {
313        PolarsError::Context {
314            msg,
315            error: Box::new(self),
316        }
317    }
318
319    pub fn remove_context(mut self) -> Self {
320        while let Self::Context { error, .. } = self {
321            self = *error;
322        }
323        self
324    }
325}
326
327pub fn map_err<E: Error>(error: E) -> PolarsError {
328    PolarsError::ComputeError(format!("{error}").into())
329}
330
331#[macro_export]
332macro_rules! polars_err {
333    ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
334        $crate::__private::must_use(
335            $crate::PolarsError::$variant(format!($fmt, $($arg),*).into())
336        )
337    };
338    ($variant:ident: $fmt:literal $(, $arg:expr)*, hint = $hint:literal) => {
339        $crate::__private::must_use(
340            $crate::PolarsError::$variant(format!(concat_str!($fmt, "\n\nHint: ", $hint), $($arg),*).into())
341        )
342    };
343    ($variant:ident: $err:expr $(,)?) => {
344        $crate::__private::must_use(
345            $crate::PolarsError::$variant($err.into())
346        )
347    };
348    (expr = $expr:expr, $variant:ident: $err:expr $(,)?) => {
349        $crate::__private::must_use(
350            $crate::PolarsError::$variant(
351                format!("{}\n\nError originated in expression: '{:?}'", $err, $expr).into()
352            )
353        )
354    };
355    (expr = $expr:expr, $variant:ident: $fmt:literal, $($arg:tt)+) => {
356        polars_err!(expr = $expr, $variant: format!($fmt, $($arg)+))
357    };
358    (op = $op:expr, got = $arg:expr, expected = $expected:expr) => {
359        $crate::polars_err!(
360            InvalidOperation: "{} operation not supported for dtype `{}` (expected: {})",
361            $op, $arg, $expected
362        )
363    };
364    (opq = $op:ident, got = $arg:expr, expected = $expected:expr) => {
365        $crate::polars_err!(
366            op = concat!("`", stringify!($op), "`"), got = $arg, expected = $expected
367        )
368    };
369    (un_impl = $op:ident) => {
370        $crate::polars_err!(
371            InvalidOperation: "{} operation is not implemented.", concat!("`", stringify!($op), "`")
372        )
373    };
374    (op = $op:expr, $arg:expr) => {
375        $crate::polars_err!(
376            InvalidOperation: "{} operation not supported for dtype `{}`", $op, $arg
377        )
378    };
379    (op = $op:expr, $arg:expr, hint = $hint:literal) => {
380        $crate::polars_err!(
381            InvalidOperation: "{} operation not supported for dtype `{}`\n\nHint: {}", $op, $arg, $hint
382        )
383    };
384    (op = $op:expr, $lhs:expr, $rhs:expr) => {
385        $crate::polars_err!(
386            InvalidOperation: "{} operation not supported for dtypes `{}` and `{}`", $op, $lhs, $rhs
387        )
388    };
389    (op = $op:expr, $arg1:expr, $arg2:expr, $arg3:expr) => {
390        $crate::polars_err!(
391            InvalidOperation: "{} operation not supported for dtypes `{}`, `{}` and `{}`", $op, $arg1, $arg2, $arg3
392        )
393    };
394    (opidx = $op:expr, idx = $idx:expr, $arg:expr) => {
395        $crate::polars_err!(
396            InvalidOperation: "`{}` operation not supported for dtype `{}` as argument {}", $op, $arg, $idx
397        )
398    };
399    (oos = $($tt:tt)+) => {
400        $crate::polars_err!(ComputeError: "out-of-spec: {}", $($tt)+)
401    };
402    (nyi = $($tt:tt)+) => {
403        $crate::polars_err!(ComputeError: "not yet implemented: {}", format!($($tt)+) )
404    };
405    (opq = $op:ident, $arg:expr) => {
406        $crate::polars_err!(op = concat!("`", stringify!($op), "`"), $arg)
407    };
408    (opq = $op:ident, $lhs:expr, $rhs:expr) => {
409        $crate::polars_err!(op = stringify!($op), $lhs, $rhs)
410    };
411    (bigidx, ctx = $ctx:expr, size = $size:expr) => {
412        $crate::polars_err!(ComputeError: "\
413{} produces {} rows which is more than maximum allowed pow(2, 32)-1 rows; \
414consider compiling with bigidx feature (pip install polars[rt64])",
415            $ctx,
416            $size,
417        )
418    };
419    (append) => {
420        polars_err!(SchemaMismatch: "cannot append series, data types don't match")
421    };
422    (extend) => {
423        polars_err!(SchemaMismatch: "cannot extend series, data types don't match")
424    };
425    (unpack) => {
426        polars_err!(SchemaMismatch: "cannot unpack series, data types don't match")
427    };
428    (not_in_enum,value=$value:expr,categories=$categories:expr) =>{
429        polars_err!(ComputeError: "value '{}' is not present in Enum: {:?}",$value,$categories)
430    };
431    (string_cache_mismatch) => {
432        polars_err!(StringCacheMismatch: r#"
433cannot compare categoricals coming from different sources, consider setting a global StringCache.
434
435Help: if you're using Python, this may look something like:
436
437    with pl.StringCache():
438        df1 = pl.DataFrame({'a': ['1', '2']}, schema={'a': pl.Categorical})
439        df2 = pl.DataFrame({'a': ['1', '3']}, schema={'a': pl.Categorical})
440        pl.concat([df1, df2])
441
442Alternatively, if the performance cost is acceptable, you could just set:
443
444    import polars as pl
445    pl.enable_string_cache()
446
447on startup."#.trim_start())
448    };
449    (duplicate = $name:expr) => {
450        $crate::polars_err!(Duplicate: "column with name '{}' has more than one occurrence", $name)
451    };
452    (duplicate_field = $name:expr) => {
453        $crate::polars_err!(Duplicate: "multiple fields with name '{}' found", $name)
454    };
455    (col_not_found = $name:expr) => {
456        $crate::polars_err!(ColumnNotFound: "{:?} not found", $name)
457    };
458    (mismatch, col=$name:expr, expected=$expected:expr, found=$found:expr) => {
459        $crate::polars_err!(
460            SchemaMismatch: "data type mismatch for column {}: expected: {}, found: {}",
461            $name,
462            $expected,
463            $found,
464        )
465    };
466    (oob = $idx:expr, $len:expr) => {
467        polars_err!(OutOfBounds: "index {} is out of bounds for sequence of length {}", $idx, $len)
468    };
469    (agg_len = $agg_len:expr, $groups_len:expr) => {
470        polars_err!(
471            ComputeError:
472            "returned aggregation is of different length: {} than the groups length: {}",
473            $agg_len, $groups_len
474        )
475    };
476    (parse_fmt_idk = $dtype:expr) => {
477        polars_err!(
478            ComputeError: "could not find an appropriate format to parse {}s, please define a format",
479            $dtype,
480        )
481    };
482    (length_mismatch = $operation:expr, $lhs:expr, $rhs:expr) => {
483        $crate::polars_err!(
484            ShapeMismatch: "arguments for `{}` have different lengths ({} != {})",
485            $operation, $lhs, $rhs
486        )
487    };
488    (length_mismatch = $operation:expr, $lhs:expr, $rhs:expr, argument = $argument:expr, argument_idx = $argument_idx:expr) => {
489        $crate::polars_err!(
490            ShapeMismatch: "argument {} called '{}' for `{}` have different lengths ({} != {})",
491            $argument_idx, $argument, $operation, $lhs, $rhs
492        )
493    };
494    (invalid_element_use) => {
495        $crate::polars_err!(InvalidOperation: "`element` is not allowed in this context")
496    };
497    (invalid_field_use) => {
498        $crate::polars_err!(InvalidOperation: "`field` is not allowed in this context")
499    };
500    (non_utf8_path) => {
501        $crate::polars_err!(ComputeError: "encountered non UTF-8 path characters")
502    };
503    (assertion_error = $objects:expr, $detail:expr, $lhs:expr, $rhs:expr) => {
504        $crate::polars_err!(
505            AssertionError: "{} are different ({})\n[left]: {}\n[right]: {}",
506            $objects, $detail, $lhs, $rhs
507        )
508    };
509    (to_datetime_tz_mismatch) => {
510        $crate::polars_err!(
511            ComputeError: "`strptime` / `to_datetime` was called with no format and no time zone, but a time zone is part of the data.\n\nThis was previously allowed but led to unpredictable and erroneous results. Give a format string, set a time zone or perform the operation eagerly on a Series instead of on an Expr."
512        )
513    };
514    (item_agg_count_not_one = $n:expr, allow_empty = $allow_empty:expr) => {
515        if $n == 0 && !$allow_empty {
516            polars_err!(ComputeError:
517                "aggregation 'item' expected a single value, got none"
518            )
519        } else if $n > 100 {
520            if $allow_empty {
521                polars_err!(ComputeError: "aggregation 'item' expected no or a single value, got 100+ values")
522            } else {
523                polars_err!(ComputeError: "aggregation 'item' expected a single value, got 100+ values")
524            }
525        } else if $n > 1 {
526            if $allow_empty {
527                polars_err!(ComputeError:
528                    "aggregation 'item' expected no or a single value, got {} values", $n
529                )
530            } else {
531                polars_err!(ComputeError:
532                    "aggregation 'item' expected a single value, got {} values", $n
533                )
534            }
535        } else {
536            unreachable!()
537        }
538    };
539}
540
541#[macro_export]
542macro_rules! polars_bail {
543    ($($tt:tt)+) => {
544        return Err($crate::polars_err!($($tt)+))
545    };
546}
547
548#[macro_export]
549macro_rules! polars_ensure {
550    ($cond:expr, $($tt:tt)+) => {
551        if !$cond {
552            $crate::polars_bail!($($tt)+);
553        }
554    };
555}
556
557#[inline]
558#[cold]
559#[must_use]
560pub fn to_compute_err(err: impl Display) -> PolarsError {
561    PolarsError::ComputeError(err.to_string().into())
562}
563
564#[macro_export]
565macro_rules! feature_gated {
566    ($($feature:literal);*, $content:expr) => {{
567        #[cfg(all($(feature = $feature),*))]
568        {
569            $content
570        }
571        #[cfg(not(all($(feature = $feature),*)))]
572        {
573            panic!("activate '{}' feature", concat!($($feature, ", "),*))
574        }
575    }};
576}
577
578// Not public, referenced by macros only.
579#[doc(hidden)]
580pub mod __private {
581    #[doc(hidden)]
582    #[inline]
583    #[cold]
584    #[must_use]
585    pub fn must_use(error: crate::PolarsError) -> crate::PolarsError {
586        error
587    }
588}