Skip to main content

common_recordbatch/
error.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Error of record batch.
16use std::any::Any;
17
18use common_error::ext::{BoxedError, ErrorExt, RetryHint};
19use common_error::status_code::StatusCode;
20use common_macro::stack_trace_debug;
21use datafusion_common::ScalarValue;
22use datatypes::prelude::ConcreteDataType;
23use datatypes::schema::SchemaRef;
24use snafu::{Location, Snafu};
25
26pub type Result<T, E = Error> = std::result::Result<T, E>;
27
28#[derive(Snafu)]
29#[snafu(visibility(pub))]
30#[stack_trace_debug]
31pub enum Error {
32    #[snafu(display("Fail to create datafusion record batch"))]
33    NewDfRecordBatch {
34        #[snafu(source)]
35        error: datatypes::arrow::error::ArrowError,
36        #[snafu(implicit)]
37        location: Location,
38    },
39
40    #[snafu(display("Data types error"))]
41    DataTypes {
42        #[snafu(implicit)]
43        location: Location,
44        source: datatypes::error::Error,
45    },
46
47    #[snafu(display("External error"))]
48    External {
49        #[snafu(implicit)]
50        location: Location,
51        source: BoxedError,
52    },
53
54    #[snafu(display("Failed to create RecordBatches, reason: {}", reason))]
55    CreateRecordBatches {
56        reason: String,
57        #[snafu(implicit)]
58        location: Location,
59    },
60
61    #[snafu(display("Failed to convert Arrow schema"))]
62    SchemaConversion {
63        source: datatypes::error::Error,
64        #[snafu(implicit)]
65        location: Location,
66    },
67
68    #[snafu(transparent)]
69    PollStream {
70        #[snafu(source)]
71        error: datafusion::error::DataFusionError,
72        #[snafu(implicit)]
73        location: Location,
74    },
75
76    #[snafu(display("Create physical expr error"))]
77    PhysicalExpr {
78        #[snafu(source)]
79        error: datafusion::error::DataFusionError,
80        #[snafu(implicit)]
81        location: Location,
82    },
83
84    #[snafu(display("Fail to format record batch"))]
85    Format {
86        #[snafu(source)]
87        error: datatypes::arrow::error::ArrowError,
88        #[snafu(implicit)]
89        location: Location,
90    },
91
92    #[snafu(display("Failed to convert {v:?} to Arrow scalar"))]
93    ToArrowScalar {
94        v: ScalarValue,
95        #[snafu(source)]
96        error: datafusion_common::DataFusionError,
97        #[snafu(implicit)]
98        location: Location,
99    },
100
101    #[snafu(display(
102        "Failed to project Arrow RecordBatch with schema {:?} and projection {:?}",
103        schema,
104        projection,
105    ))]
106    ProjectArrowRecordBatch {
107        #[snafu(source)]
108        error: datatypes::arrow::error::ArrowError,
109        #[snafu(implicit)]
110        location: Location,
111        schema: datatypes::schema::SchemaRef,
112        projection: Vec<usize>,
113    },
114
115    #[snafu(display("Column {} not exists in table {}", column_name, table_name))]
116    ColumnNotExists {
117        column_name: String,
118        table_name: String,
119        #[snafu(implicit)]
120        location: Location,
121    },
122
123    #[snafu(display(
124        "Failed to cast vector of type '{:?}' to type '{:?}'",
125        from_type,
126        to_type,
127    ))]
128    CastVector {
129        from_type: ConcreteDataType,
130        to_type: ConcreteDataType,
131        #[snafu(implicit)]
132        location: Location,
133        source: datatypes::error::Error,
134    },
135
136    #[snafu(display("Error occurs when performing arrow computation"))]
137    ArrowCompute {
138        #[snafu(source)]
139        error: datatypes::arrow::error::ArrowError,
140        #[snafu(implicit)]
141        location: Location,
142    },
143
144    #[snafu(display("Unsupported operation: {}", reason))]
145    UnsupportedOperation {
146        reason: String,
147        #[snafu(implicit)]
148        location: Location,
149    },
150
151    #[snafu(display("Cannot construct an empty stream"))]
152    EmptyStream {
153        #[snafu(implicit)]
154        location: Location,
155    },
156
157    #[snafu(display("Schema not match, left: {:?}, right: {:?}", left, right))]
158    SchemaNotMatch {
159        left: SchemaRef,
160        right: SchemaRef,
161        #[snafu(implicit)]
162        location: Location,
163    },
164
165    #[snafu(display("Stream timeout"))]
166    StreamTimeout {
167        #[snafu(implicit)]
168        location: Location,
169    },
170
171    #[snafu(display("RecordBatch slice index overflow: {visit_index} > {size}"))]
172    RecordBatchSliceIndexOverflow {
173        #[snafu(implicit)]
174        location: Location,
175        size: usize,
176        visit_index: usize,
177    },
178
179    #[snafu(display("Stream has been cancelled"))]
180    StreamCancelled {
181        #[snafu(implicit)]
182        location: Location,
183    },
184
185    #[snafu(display("Exceeded memory limit: {}", msg))]
186    ExceedMemoryLimit {
187        msg: String,
188        #[snafu(implicit)]
189        location: Location,
190    },
191}
192
193impl ErrorExt for Error {
194    fn status_code(&self) -> StatusCode {
195        match self {
196            Error::NewDfRecordBatch { .. }
197            | Error::EmptyStream { .. }
198            | Error::SchemaNotMatch { .. } => StatusCode::InvalidArguments,
199
200            Error::DataTypes { .. }
201            | Error::CreateRecordBatches { .. }
202            | Error::Format { .. }
203            | Error::ToArrowScalar { .. }
204            | Error::ProjectArrowRecordBatch { .. }
205            | Error::PhysicalExpr { .. }
206            | Error::RecordBatchSliceIndexOverflow { .. } => StatusCode::Internal,
207
208            Error::PollStream { error, .. } => {
209                let mut error = error;
210                loop {
211                    error = match error {
212                        datafusion::error::DataFusionError::Shared(inner) => inner,
213                        datafusion::error::DataFusionError::Context(_, inner)
214                        | datafusion::error::DataFusionError::Diagnostic(_, inner) => inner,
215                        _ => break,
216                    };
217                }
218
219                match error {
220                    datafusion::error::DataFusionError::External(source) => source
221                        .downcast_ref::<BoxedError>()
222                        .map_or(StatusCode::EngineExecuteQuery, |source| {
223                            source.status_code()
224                        }),
225                    _ => StatusCode::EngineExecuteQuery,
226                }
227            }
228
229            Error::ArrowCompute { .. } => StatusCode::IllegalState,
230
231            Error::ColumnNotExists { .. } => StatusCode::TableColumnNotFound,
232
233            Error::External { source, .. } => source.status_code(),
234
235            Error::UnsupportedOperation { .. } => StatusCode::Unsupported,
236
237            Error::SchemaConversion { source, .. } | Error::CastVector { source, .. } => {
238                source.status_code()
239            }
240
241            Error::StreamTimeout { .. } => StatusCode::Cancelled,
242
243            Error::StreamCancelled { .. } => StatusCode::Cancelled,
244
245            Error::ExceedMemoryLimit { .. } => StatusCode::RuntimeResourcesExhausted,
246        }
247    }
248
249    fn as_any(&self) -> &dyn Any {
250        self
251    }
252
253    fn retry_hint(&self) -> RetryHint {
254        match self {
255            Error::ExceedMemoryLimit { .. } => RetryHint::Retryable,
256            Error::External { source, .. } => source.retry_hint(),
257            Error::SchemaConversion { source, .. } | Error::CastVector { source, .. } => {
258                source.retry_hint()
259            }
260            _ => RetryHint::NonRetryable,
261        }
262    }
263}
264
265#[cfg(test)]
266mod tests {
267    use std::sync::Arc;
268
269    use common_error::ext::PlainError;
270    use datafusion::error::DataFusionError;
271
272    use super::*;
273
274    #[test]
275    fn poll_stream_status_code_preserves_boxed_error_through_wrappers() {
276        let boxed_error = |status| {
277            DataFusionError::External(Box::new(BoxedError::new(PlainError::new(
278                "neutral error".to_string(),
279                status,
280            ))))
281        };
282
283        for status in [
284            StatusCode::RequestOutdated,
285            StatusCode::Unknown,
286            StatusCode::Unsupported,
287        ] {
288            let errors = [
289                boxed_error(status),
290                DataFusionError::Shared(Arc::new(boxed_error(status))),
291                DataFusionError::Context("context".to_string(), Box::new(boxed_error(status))),
292                DataFusionError::Diagnostic(
293                    Box::new(datafusion::common::Diagnostic::new_error(
294                        "diagnostic",
295                        None,
296                    )),
297                    Box::new(boxed_error(status)),
298                ),
299                DataFusionError::Shared(Arc::new(DataFusionError::Context(
300                    "context".to_string(),
301                    Box::new(DataFusionError::Diagnostic(
302                        Box::new(datafusion::common::Diagnostic::new_error(
303                            "diagnostic",
304                            None,
305                        )),
306                        Box::new(boxed_error(status)),
307                    )),
308                ))),
309            ];
310
311            for error in errors {
312                let error = Error::PollStream {
313                    error,
314                    location: Location::default(),
315                };
316                assert_eq!(error.status_code(), status);
317            }
318        }
319
320        let error = Error::PollStream {
321            error: DataFusionError::Shared(Arc::new(DataFusionError::External(Box::new(
322                BoxedError::new(Error::PhysicalExpr {
323                    error: DataFusionError::NotImplemented("inner error".to_string()),
324                    location: Location::default(),
325                }),
326            )))),
327            location: Location::default(),
328        };
329        assert_eq!(error.status_code(), StatusCode::Internal);
330    }
331
332    #[test]
333    fn poll_stream_status_code_defaults_for_other_datafusion_errors() {
334        let wrap = |error| {
335            DataFusionError::Shared(Arc::new(DataFusionError::Context(
336                "context".to_string(),
337                Box::new(DataFusionError::Diagnostic(
338                    Box::new(datafusion::common::Diagnostic::new_error(
339                        "diagnostic",
340                        None,
341                    )),
342                    Box::new(error),
343                )),
344            )))
345        };
346        let errors = || {
347            [
348                DataFusionError::External(Box::new(std::io::Error::other("neutral io error"))),
349                DataFusionError::Internal("neutral internal error".to_string()),
350                DataFusionError::NotImplemented("neutral not implemented error".to_string()),
351                DataFusionError::Plan("neutral plan error".to_string()),
352                DataFusionError::External(Box::new(DataFusionError::Internal(
353                    "inner error".to_string(),
354                ))),
355            ]
356        };
357
358        for error in errors().into_iter().chain(errors().map(wrap)) {
359            let error = Error::PollStream {
360                error,
361                location: Location::default(),
362            };
363            assert_eq!(error.status_code(), StatusCode::EngineExecuteQuery);
364        }
365    }
366}