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