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