risingwave_frontend/
error.rs

1// Copyright 2025 RisingWave Labs
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
15use risingwave_batch::error::BatchError;
16use risingwave_common::array::ArrayError;
17use risingwave_common::error::{BoxedError, NoFunction, NotImplemented};
18use risingwave_common::license::FeatureNotAvailable;
19use risingwave_common::secret::SecretError;
20use risingwave_common::session_config::SessionConfigError;
21use risingwave_common::util::value_encoding::error::ValueEncodingError;
22use risingwave_connector::error::ConnectorError;
23use risingwave_connector::sink::SinkError;
24use risingwave_expr::ExprError;
25use risingwave_pb::PbFieldNotFound;
26use risingwave_rpc_client::error::{RpcError, ToTonicStatus, TonicStatusWrapper};
27use thiserror::Error;
28use thiserror_ext::AsReport;
29use tokio::task::JoinError;
30
31use crate::expr::CastError;
32
33/// The error type for the frontend crate, acting as the top-level error type for the
34/// entire RisingWave project.
35// TODO(error-handling): this is migrated from the `common` crate, and there could
36// be some further refactoring to do:
37// - Some variants are never constructed.
38// - Some variants store a type-erased `BoxedError` to resolve the reverse dependency.
39//   It's not necessary anymore as the error type is now defined at the top-level.
40#[derive(Error, thiserror_ext::ReportDebug, thiserror_ext::Box, thiserror_ext::Macro)]
41#[thiserror_ext(newtype(name = RwError, backtrace), macro(path = "crate::error"))]
42pub enum ErrorCode {
43    #[error("internal error: {0}")]
44    InternalError(String),
45    // TODO: unify with the above
46    #[error(transparent)]
47    Uncategorized(
48        #[from]
49        #[backtrace]
50        anyhow::Error,
51    ),
52    #[error("connector error: {0}")]
53    ConnectorError(
54        #[source]
55        #[backtrace]
56        BoxedError,
57    ),
58    #[error(transparent)]
59    NotImplemented(#[from] NotImplemented),
60    // Tips: Use this only if it's intended to reject the query
61    #[error("Not supported: {0}\nHINT: {1}")]
62    NotSupported(String, String),
63    #[error(transparent)]
64    NoFunction(#[from] NoFunction),
65    #[error(transparent)]
66    IoError(#[from] std::io::Error),
67    #[error("Storage error: {0}")]
68    StorageError(
69        #[backtrace]
70        #[source]
71        BoxedError,
72    ),
73    #[error("Expr error: {0}")]
74    ExprError(
75        #[source]
76        #[backtrace]
77        BoxedError,
78    ),
79    // TODO(error-handling): there's a limitation that `#[transparent]` can't be used with `#[backtrace]` if no `#[from]`
80    // So we emulate a transparent error with "{0}" display here.
81    #[error("{0}")]
82    BatchError(
83        #[source]
84        #[backtrace]
85        // `BatchError`
86        BoxedError,
87    ),
88    #[error("Array error: {0}")]
89    ArrayError(
90        #[from]
91        #[backtrace]
92        ArrayError,
93    ),
94    #[error("Stream error: {0}")]
95    StreamError(
96        #[backtrace]
97        #[source]
98        BoxedError,
99    ),
100    // TODO(error-handling): there's a limitation that `#[transparent]` can't be used with `#[backtrace]` if no `#[from]`
101    // So we emulate a transparent error with "{0}" display here.
102    #[error("{0}")]
103    RpcError(
104        #[source]
105        #[backtrace]
106        // `tonic::transport::Error`, `TonicStatusWrapper`, or `RpcError`
107        BoxedError,
108    ),
109    // TODO: use a new type for bind error
110    // TODO(error-handling): should prefer use error types than strings.
111    #[error("Bind error: {0}")]
112    BindError(#[message] String),
113    // TODO: only keep this one
114    #[error("Failed to bind expression: {expr}: {error}")]
115    BindErrorRoot {
116        expr: String,
117        #[source]
118        #[backtrace]
119        error: BoxedError,
120    },
121    #[error(transparent)]
122    CastError(
123        #[from]
124        #[backtrace]
125        CastError,
126    ),
127    #[error("Catalog error: {0}")]
128    CatalogError(
129        #[source]
130        #[backtrace]
131        BoxedError,
132    ),
133    #[error("Protocol error: {0}")]
134    ProtocolError(String),
135    #[error("Scheduler error: {0}")]
136    SchedulerError(
137        #[source]
138        #[backtrace]
139        BoxedError,
140    ),
141    #[error("Task not found")]
142    TaskNotFound,
143    #[error("Session not found")]
144    SessionNotFound,
145    #[error("Item not found: {0}")]
146    ItemNotFound(String),
147    #[error("Invalid input syntax: {0}")]
148    InvalidInputSyntax(String),
149    #[error("Can not compare in memory: {0}")]
150    MemComparableError(#[from] memcomparable::Error),
151    #[error("Error while de/se values: {0}")]
152    ValueEncodingError(
153        #[from]
154        #[backtrace]
155        ValueEncodingError,
156    ),
157    #[error("Invalid value `{config_value}` for `{config_entry}`")]
158    InvalidConfigValue {
159        config_entry: String,
160        config_value: String,
161    },
162    #[error("Invalid Parameter Value: {0}")]
163    InvalidParameterValue(String),
164    #[error("Sink error: {0}")]
165    SinkError(
166        #[source]
167        #[backtrace]
168        BoxedError,
169    ),
170    #[error("Permission denied: {0}")]
171    PermissionDenied(String),
172    #[error("Failed to get/set session config: {0}")]
173    SessionConfig(
174        #[from]
175        #[backtrace]
176        SessionConfigError,
177    ),
178    #[error("Secret error: {0}")]
179    SecretError(
180        #[from]
181        #[backtrace]
182        SecretError,
183    ),
184    #[error("{0} has been deprecated, please use {1} instead.")]
185    Deprecated(String, String),
186    #[error(transparent)]
187    FeatureNotAvailable(
188        #[from]
189        #[backtrace]
190        FeatureNotAvailable,
191    ),
192}
193
194/// The result type for the frontend crate.
195pub type Result<T> = std::result::Result<T, RwError>;
196
197impl From<TonicStatusWrapper> for RwError {
198    fn from(status: TonicStatusWrapper) -> Self {
199        use tonic::Code;
200
201        let message = status.inner().message();
202
203        // TODO(error-handling): `message` loses the source chain.
204        match status.inner().code() {
205            Code::InvalidArgument => ErrorCode::InvalidParameterValue(message.to_owned()),
206            Code::NotFound | Code::AlreadyExists => ErrorCode::CatalogError(status.into()),
207            Code::PermissionDenied => ErrorCode::PermissionDenied(message.to_owned()),
208            Code::Cancelled => ErrorCode::SchedulerError(status.into()),
209            _ => ErrorCode::RpcError(status.into()),
210        }
211        .into()
212    }
213}
214
215impl From<RpcError> for RwError {
216    fn from(r: RpcError) -> Self {
217        match r {
218            RpcError::GrpcStatus(status) => TonicStatusWrapper::into(*status),
219            _ => ErrorCode::RpcError(r.into()).into(),
220        }
221    }
222}
223
224impl From<ExprError> for RwError {
225    fn from(s: ExprError) -> Self {
226        ErrorCode::ExprError(Box::new(s)).into()
227    }
228}
229
230impl From<SinkError> for RwError {
231    fn from(e: SinkError) -> Self {
232        ErrorCode::SinkError(Box::new(e)).into()
233    }
234}
235
236impl From<ConnectorError> for RwError {
237    fn from(e: ConnectorError) -> Self {
238        ErrorCode::ConnectorError(e.into()).into()
239    }
240}
241
242impl From<PbFieldNotFound> for RwError {
243    fn from(err: PbFieldNotFound) -> Self {
244        ErrorCode::InternalError(format!(
245            "Failed to decode prost: field not found `{}`",
246            err.0
247        ))
248        .into()
249    }
250}
251
252impl From<BatchError> for RwError {
253    fn from(s: BatchError) -> Self {
254        ErrorCode::BatchError(Box::new(s)).into()
255    }
256}
257
258impl From<JoinError> for RwError {
259    fn from(join_error: JoinError) -> Self {
260        ErrorCode::Uncategorized(join_error.into()).into()
261    }
262}
263
264// For errors without a concrete type, put them into `Uncategorized`.
265impl From<BoxedError> for RwError {
266    fn from(e: BoxedError) -> Self {
267        // Show that the error is of `BoxedKind`, instead of `AdhocKind` which loses the sources.
268        // This is essentially expanded from `anyhow::anyhow!(e)`.
269        let e = anyhow::__private::kind::BoxedKind::anyhow_kind(&e).new(e);
270        ErrorCode::Uncategorized(e).into()
271    }
272}
273
274impl From<risingwave_sqlparser::parser::ParserError> for ErrorCode {
275    fn from(e: risingwave_sqlparser::parser::ParserError) -> Self {
276        ErrorCode::InvalidInputSyntax(e.to_report_string())
277    }
278}
279
280impl From<RwError> for tonic::Status {
281    fn from(err: RwError) -> Self {
282        err.to_status(tonic::Code::Internal, "RwError")
283    }
284}