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 !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#[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}