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};
12mod signals;
13
14pub use signals::{check_signals, set_signals_function};
15pub use warning::*;
16
17enum ErrorStrategy {
18    Panic,
19    WithBacktrace,
20    Normal,
21}
22
23static ERROR_STRATEGY: LazyLock<ErrorStrategy> = LazyLock::new(|| {
24    if env::var("POLARS_PANIC_ON_ERR").as_deref() == Ok("1") {
25        ErrorStrategy::Panic
26    } else if env::var("POLARS_BACKTRACE_IN_ERR").as_deref() == Ok("1") {
27        ErrorStrategy::WithBacktrace
28    } else {
29        ErrorStrategy::Normal
30    }
31});
32
33#[derive(Debug, Clone)]
34pub struct ErrString(Cow<'static, str>);
35
36impl ErrString {
37    pub const fn new_static(s: &'static str) -> Self {
38        Self(Cow::Borrowed(s))
39    }
40}
41
42impl<T> From<T> for ErrString
43where
44    T: Into<Cow<'static, str>>,
45{
46    fn from(msg: T) -> Self {
47        match &*ERROR_STRATEGY {
48            ErrorStrategy::Panic => panic!("{}", msg.into()),
49            ErrorStrategy::WithBacktrace => ErrString(Cow::Owned(format!(
50                "{}\n\nRust backtrace:\n{}",
51                msg.into(),
52                std::backtrace::Backtrace::force_capture()
53            ))),
54            ErrorStrategy::Normal => ErrString(msg.into()),
55        }
56    }
57}
58
59impl AsRef<str> for ErrString {
60    fn as_ref(&self) -> &str {
61        &self.0
62    }
63}
64
65impl Deref for ErrString {
66    type Target = str;
67
68    fn deref(&self) -> &Self::Target {
69        &self.0
70    }
71}
72
73impl Display for ErrString {
74    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
75        write!(f, "{}", self.0)
76    }
77}
78
79#[derive(Debug, Clone, thiserror::Error)]
80pub enum PolarsError {
81    #[error("not found: {0}")]
82    ColumnNotFound(ErrString),
83    #[error("{0}")]
84    ComputeError(ErrString),
85    #[error("duplicate: {0}")]
86    Duplicate(ErrString),
87    #[error("{0}")]
88    InvalidOperation(ErrString),
89    #[error("{}", match msg {
90        Some(msg) => format!("{}", msg),
91        None => format!("{}", error)
92    })]
93    IO {
94        error: Arc<io::Error>,
95        msg: Option<ErrString>,
96    },
97    #[error("no data: {0}")]
98    NoData(ErrString),
99    #[error("{0}")]
100    OutOfBounds(ErrString),
101    #[error("field not found: {0}")]
102    SchemaFieldNotFound(ErrString),
103    #[error("{0}")]
104    SchemaMismatch(ErrString),
105    #[error("lengths don't match: {0}")]
106    ShapeMismatch(ErrString),
107    #[error("{0}")]
108    SQLInterface(ErrString),
109    #[error("{0}")]
110    SQLSyntax(ErrString),
111    #[error("string caches don't match: {0}")]
112    StringCacheMismatch(ErrString),
113    #[error("field not found: {0}")]
114    StructFieldNotFound(ErrString),
115    #[error("{error}: {msg}")]
116    Context {
117        error: Box<PolarsError>,
118        msg: ErrString,
119    },
120}
121
122impl From<io::Error> for PolarsError {
123    fn from(value: io::Error) -> Self {
124        PolarsError::IO {
125            error: Arc::new(value),
126            msg: None,
127        }
128    }
129}
130
131#[cfg(feature = "regex")]
132impl From<regex::Error> for PolarsError {
133    fn from(err: regex::Error) -> Self {
134        PolarsError::ComputeError(format!("regex error: {err}").into())
135    }
136}
137
138#[cfg(feature = "object_store")]
139impl From<object_store::Error> for PolarsError {
140    fn from(err: object_store::Error) -> Self {
141        std::io::Error::new(
142            std::io::ErrorKind::Other,
143            format!("object-store error: {err:?}"),
144        )
145        .into()
146    }
147}
148
149#[cfg(feature = "avro-schema")]
150impl From<avro_schema::error::Error> for PolarsError {
151    fn from(value: avro_schema::error::Error) -> Self {
152        polars_err!(ComputeError: "avro-error: {}", value)
153    }
154}
155
156impl From<simdutf8::basic::Utf8Error> for PolarsError {
157    fn from(value: simdutf8::basic::Utf8Error) -> Self {
158        polars_err!(ComputeError: "invalid utf8: {}", value)
159    }
160}
161#[cfg(feature = "arrow-format")]
162impl From<arrow_format::ipc::planus::Error> for PolarsError {
163    fn from(err: arrow_format::ipc::planus::Error) -> Self {
164        polars_err!(ComputeError: "parquet error: {err:?}")
165    }
166}
167
168impl From<TryReserveError> for PolarsError {
169    fn from(value: TryReserveError) -> Self {
170        polars_err!(ComputeError: "OOM: {}", value)
171    }
172}
173
174impl From<Infallible> for PolarsError {
175    fn from(_: Infallible) -> Self {
176        unreachable!()
177    }
178}
179
180pub type PolarsResult<T> = Result<T, PolarsError>;
181
182impl PolarsError {
183    pub fn context_trace(self) -> Self {
184        use PolarsError::*;
185        match self {
186            Context { error, msg } => {
187                // If context is 1 level deep, just return error.
188                if !matches!(&*error, PolarsError::Context { .. }) {
189                    return *error;
190                }
191                let mut current_error = &*error;
192                let material_error = error.get_err();
193
194                let mut messages = vec![&msg];
195
196                while let PolarsError::Context { msg, error } = current_error {
197                    current_error = error;
198                    messages.push(msg)
199                }
200
201                let mut bt = String::new();
202
203                let mut count = 0;
204                while let Some(msg) = messages.pop() {
205                    count += 1;
206                    writeln!(&mut bt, "\t[{count}] {}", msg).unwrap();
207                }
208                material_error.wrap_msg(move |msg| {
209                    format!("{msg}\n\nThis error occurred with the following context stack:\n{bt}")
210                })
211            },
212            err => err,
213        }
214    }
215
216    pub fn wrap_msg<F: FnOnce(&str) -> String>(&self, func: F) -> Self {
217        use PolarsError::*;
218        match self {
219            ColumnNotFound(msg) => ColumnNotFound(func(msg).into()),
220            ComputeError(msg) => ComputeError(func(msg).into()),
221            Duplicate(msg) => Duplicate(func(msg).into()),
222            InvalidOperation(msg) => InvalidOperation(func(msg).into()),
223            IO { error, msg } => {
224                let msg = match msg {
225                    Some(msg) => func(msg),
226                    None => func(&format!("{}", error)),
227                };
228                IO {
229                    error: error.clone(),
230                    msg: Some(msg.into()),
231                }
232            },
233            NoData(msg) => NoData(func(msg).into()),
234            OutOfBounds(msg) => OutOfBounds(func(msg).into()),
235            SchemaFieldNotFound(msg) => SchemaFieldNotFound(func(msg).into()),
236            SchemaMismatch(msg) => SchemaMismatch(func(msg).into()),
237            ShapeMismatch(msg) => ShapeMismatch(func(msg).into()),
238            StringCacheMismatch(msg) => StringCacheMismatch(func(msg).into()),
239            StructFieldNotFound(msg) => StructFieldNotFound(func(msg).into()),
240            SQLInterface(msg) => SQLInterface(func(msg).into()),
241            SQLSyntax(msg) => SQLSyntax(func(msg).into()),
242            Context { error, .. } => error.wrap_msg(func),
243        }
244    }
245
246    fn get_err(&self) -> &Self {
247        use PolarsError::*;
248        match self {
249            Context { error, .. } => error.get_err(),
250            err => err,
251        }
252    }
253
254    pub fn context(self, msg: ErrString) -> Self {
255        PolarsError::Context {
256            msg,
257            error: Box::new(self),
258        }
259    }
260}
261
262pub fn map_err<E: Error>(error: E) -> PolarsError {
263    PolarsError::ComputeError(format!("{error}").into())
264}
265
266#[macro_export]
267macro_rules! polars_err {
268    ($variant:ident: $fmt:literal $(, $arg:expr)* $(,)?) => {
269        $crate::__private::must_use(
270            $crate::PolarsError::$variant(format!($fmt, $($arg),*).into())
271        )
272    };
273    ($variant:ident: $err:expr $(,)?) => {
274        $crate::__private::must_use(
275            $crate::PolarsError::$variant($err.into())
276        )
277    };
278    (expr = $expr:expr, $variant:ident: $err:expr $(,)?) => {
279        $crate::__private::must_use(
280            $crate::PolarsError::$variant(
281                format!("{}\n\nError originated in expression: '{:?}'", $err, $expr).into()
282            )
283        )
284    };
285    (expr = $expr:expr, $variant:ident: $fmt:literal, $($arg:tt)+) => {
286        polars_err!(expr = $expr, $variant: format!($fmt, $($arg)+))
287    };
288    (op = $op:expr, got = $arg:expr, expected = $expected:expr) => {
289        $crate::polars_err!(
290            InvalidOperation: "{} operation not supported for dtype `{}` (expected: {})",
291            $op, $arg, $expected
292        )
293    };
294    (opq = $op:ident, got = $arg:expr, expected = $expected:expr) => {
295        $crate::polars_err!(
296            op = concat!("`", stringify!($op), "`"), got = $arg, expected = $expected
297        )
298    };
299    (un_impl = $op:ident) => {
300        $crate::polars_err!(
301            InvalidOperation: "{} operation is not implemented.", concat!("`", stringify!($op), "`")
302        )
303    };
304    (op = $op:expr, $arg:expr) => {
305        $crate::polars_err!(
306            InvalidOperation: "{} operation not supported for dtype `{}`", $op, $arg
307        )
308    };
309    (op = $op:expr, $arg:expr, hint = $hint:literal) => {
310        $crate::polars_err!(
311            InvalidOperation: "{} operation not supported for dtype `{}`\n\nHint: {}", $op, $arg, $hint
312        )
313    };
314    (op = $op:expr, $lhs:expr, $rhs:expr) => {
315        $crate::polars_err!(
316            InvalidOperation: "{} operation not supported for dtypes `{}` and `{}`", $op, $lhs, $rhs
317        )
318    };
319    (oos = $($tt:tt)+) => {
320        $crate::polars_err!(ComputeError: "out-of-spec: {}", $($tt)+)
321    };
322    (nyi = $($tt:tt)+) => {
323        $crate::polars_err!(ComputeError: "not yet implemented: {}", format!($($tt)+) )
324    };
325    (opq = $op:ident, $arg:expr) => {
326        $crate::polars_err!(op = concat!("`", stringify!($op), "`"), $arg)
327    };
328    (opq = $op:ident, $lhs:expr, $rhs:expr) => {
329        $crate::polars_err!(op = stringify!($op), $lhs, $rhs)
330    };
331    (append) => {
332        polars_err!(SchemaMismatch: "cannot append series, data types don't match")
333    };
334    (extend) => {
335        polars_err!(SchemaMismatch: "cannot extend series, data types don't match")
336    };
337    (unpack) => {
338        polars_err!(SchemaMismatch: "cannot unpack series, data types don't match")
339    };
340    (not_in_enum,value=$value:expr,categories=$categories:expr) =>{
341        polars_err!(ComputeError: "value '{}' is not present in Enum: {:?}",$value,$categories)
342    };
343    (string_cache_mismatch) => {
344        polars_err!(StringCacheMismatch: r#"
345cannot compare categoricals coming from different sources, consider setting a global StringCache.
346
347Help: if you're using Python, this may look something like:
348
349    with pl.StringCache():
350        # Initialize Categoricals.
351        df1 = pl.DataFrame({'a': ['1', '2']}, schema={'a': pl.Categorical})
352        df2 = pl.DataFrame({'a': ['1', '3']}, schema={'a': pl.Categorical})
353    # Your operations go here.
354    pl.concat([df1, df2])
355
356Alternatively, if the performance cost is acceptable, you could just set:
357
358    import polars as pl
359    pl.enable_string_cache()
360
361on startup."#.trim_start())
362    };
363    (duplicate = $name:expr) => {
364        polars_err!(Duplicate: "column with name '{}' has more than one occurrence", $name)
365    };
366    (col_not_found = $name:expr) => {
367        polars_err!(ColumnNotFound: "{:?} not found", $name)
368    };
369    (oob = $idx:expr, $len:expr) => {
370        polars_err!(OutOfBounds: "index {} is out of bounds for sequence of length {}", $idx, $len)
371    };
372    (agg_len = $agg_len:expr, $groups_len:expr) => {
373        polars_err!(
374            ComputeError:
375            "returned aggregation is of different length: {} than the groups length: {}",
376            $agg_len, $groups_len
377        )
378    };
379    (parse_fmt_idk = $dtype:expr) => {
380        polars_err!(
381            ComputeError: "could not find an appropriate format to parse {}s, please define a format",
382            $dtype,
383        )
384    };
385}
386
387#[macro_export]
388macro_rules! polars_bail {
389    ($($tt:tt)+) => {
390        return Err($crate::polars_err!($($tt)+))
391    };
392}
393
394#[macro_export]
395macro_rules! polars_ensure {
396    ($cond:expr, $($tt:tt)+) => {
397        if !$cond {
398            $crate::polars_bail!($($tt)+);
399        }
400    };
401}
402
403#[inline]
404#[cold]
405#[must_use]
406pub fn to_compute_err(err: impl Display) -> PolarsError {
407    PolarsError::ComputeError(err.to_string().into())
408}
409#[macro_export]
410macro_rules! feature_gated {
411    ($($feature:literal);*, $content:expr) => {{
412        #[cfg(all($(feature = $feature),*))]
413        {
414            $content
415        }
416        #[cfg(not(all($(feature = $feature),*)))]
417        {
418            panic!("activate '{}' feature", concat!($($feature, ", "),*))
419        }
420    }};
421}
422
423// Not public, referenced by macros only.
424#[doc(hidden)]
425pub mod __private {
426    #[doc(hidden)]
427    #[inline]
428    #[cold]
429    #[must_use]
430    pub fn must_use(error: crate::PolarsError) -> crate::PolarsError {
431        error
432    }
433}