Skip to content

Commit

Permalink
feat(expr): show function name and arguments when evaluation fails (#…
Browse files Browse the repository at this point in the history
…17083)

Signed-off-by: Bugen Zhao <[email protected]>
  • Loading branch information
BugenZhao authored Jun 5, 2024
1 parent e905000 commit 44c8b09
Show file tree
Hide file tree
Showing 10 changed files with 206 additions and 63 deletions.
44 changes: 5 additions & 39 deletions e2e_test/batch/functions/to_char.slt.part
Original file line number Diff line number Diff line change
Expand Up @@ -106,51 +106,17 @@ select to_char('-23:22:57.124562'::interval, 'HH12 MI SS MS US');
----
-11 -22 -57 -124 -124562

query error
query error invalid format specification for an interval value
select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MM SS tzhtzm');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates.


query error
query error invalid format specification for an interval value
select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS TZH:TZM');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates.


query error
query error invalid format specification for an interval value
select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS TZH');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates.


query error
query error invalid format specification for an interval value
select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS Month');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates.


query error
query error invalid format specification for an interval value
select to_char('1year 1month 1day 1hours 1minute 1second'::interval, 'IY MM DD AM HH12 MI SS Mon');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Invalid parameter pattern: invalid format specification for an interval value, HINT: Intervals are not tied to specific calendar dates.
3 changes: 2 additions & 1 deletion e2e_test/error_ui/extended/main.slt
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,5 @@ db error: ERROR: Failed to execute the statement

Caused by these errors (recent errors listed first):
1: Expr error
2: Division by zero
2: error while evaluating expression `general_div('1', '0')`
3: Division by zero
71 changes: 71 additions & 0 deletions e2e_test/error_ui/simple/expr.slt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Regular function
query error
select pow(114, 514);
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: error while evaluating expression `pow_f64('114', '514')`
3: Numeric out of range: overflow


# Nullable arguments
query error
select array_position(array[1, null, 2, null], null, null::int);
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: error while evaluating expression `array_position_start('{1,NULL,2,NULL}', NULL, NULL)`
3: Invalid parameter start: initial position must not be null


# Operator
query error
select 11111111444 * 51444444444444444;
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: error while evaluating expression `general_mul('11111111444', '51444444444444444')`
3: Numeric out of range


# Cast
query error
select 'foo'::bigint;
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: error while evaluating expression `str_parse('foo')`
3: Parse error: bigint invalid digit found in string


# Prebuild context
# TODO: not included in the error message
query error
select jsonb_path_exists('{"a":[1,2,3,4,5]}', '$.a[*] ? (@ >= $min && @ <= $max)', '"foo"');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: error while evaluating expression `jsonb_path_exists3('{"a": [1, 2, 3, 4, 5]}', '"foo"')`
3: Invalid parameter jsonpath: "vars" argument is not an object


# Variadic arguments
query error
select format('%L %s', 'Hello', 'World');
----
db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: error while evaluating expression `format('Hello', 'World')`
3: Unsupported function: unsupported specifier type 'L'
6 changes: 4 additions & 2 deletions e2e_test/error_ui/simple/main.slt
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Division by zero
2: error while evaluating expression `general_div('1', '0')`
3: Division by zero


query error
Expand All @@ -71,7 +72,8 @@ db error: ERROR: Failed to run the query

Caused by these errors (recent errors listed first):
1: Expr error
2: Division by zero
2: error while evaluating expression `general_div('1', '0')`
3: Division by zero


statement error
Expand Down
2 changes: 1 addition & 1 deletion e2e_test/error_ui/simple/recovery.slt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ with error as (
limit 1
)
select
case when error like '%Actor % exited unexpectedly: Executor error: Chunk operation error: Numeric out of range%' then 'ok'
case when error like '%Actor % exited unexpectedly: Executor error: %Numeric out of range%' then 'ok'
else error
end as result
from error;
Expand Down
61 changes: 58 additions & 3 deletions src/expr/core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
use std::fmt::{Debug, Display};

use risingwave_common::array::{ArrayError, ArrayRef};
use risingwave_common::types::DataType;
use risingwave_common::types::{DataType, DatumRef, ToText};
use risingwave_pb::PbFieldNotFound;
use thiserror::Error;
use thiserror_ext::AsReport;
Expand Down Expand Up @@ -115,12 +115,67 @@ pub enum ExprError {
Custom(String),

/// Error from a function call.
#[error("{0}")]
Function(#[source] Box<dyn std::error::Error + Send + Sync>),
///
/// Use [`ExprError::function`] to create this error.
#[error("error while evaluating expression `{display}`")]
Function {
display: Box<str>,
#[backtrace]
// We don't use `anyhow::Error` because we don't want to always capture the backtrace.
source: Box<dyn std::error::Error + Send + Sync>,
},
}

static_assertions::const_assert_eq!(std::mem::size_of::<ExprError>(), 40);

impl ExprError {
/// Constructs a [`ExprError::Function`] error with the given information for display.
pub fn function<'a>(
fn_name: &str,
args: impl IntoIterator<Item = DatumRef<'a>>,
source: impl Into<Box<dyn std::error::Error + Send + Sync>>,
) -> Self {
use std::fmt::Write;

let display = {
let mut s = String::new();
write!(s, "{}(", fn_name).unwrap();
for (i, arg) in args.into_iter().enumerate() {
if i > 0 {
write!(s, ", ").unwrap();
}
if let Some(arg) = arg {
// Act like `quote_literal(arg::varchar)`.
// Since this is mainly for debugging, we don't need to be too precise.
let arg = arg.to_text();
if arg.contains('\\') {
// use escape format: E'...'
write!(s, "E").unwrap();
}
write!(s, "'").unwrap();
for c in arg.chars() {
match c {
'\'' => write!(s, "''").unwrap(),
'\\' => write!(s, "\\\\").unwrap(),
_ => write!(s, "{}", c).unwrap(),
}
}
write!(s, "'").unwrap();
} else {
write!(s, "NULL").unwrap();
}
}
write!(s, ")").unwrap();
s
};

Self::Function {
display: display.into(),
source: source.into(),
}
}
}

impl From<chrono::ParseError> for ExprError {
fn from(e: chrono::ParseError) -> Self {
Self::Parse(e.to_report_string().into())
Expand Down
47 changes: 44 additions & 3 deletions src/expr/macro/src/gen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,45 @@ impl FunctionAttr {
// no prebuilt argument
(None, _) => quote! {},
};
let variadic_args = variadic.then(|| quote! { variadic_row, });
let variadic_args = variadic.then(|| quote! { &variadic_row, });
let context = user_fn.context.then(|| quote! { &self.context, });
let writer = user_fn.write.then(|| quote! { &mut writer, });
let await_ = user_fn.async_.then(|| quote! { .await });

let record_error = {
// Uniform arguments into `DatumRef`.
#[allow(clippy::disallowed_methods)] // allow zip
let inputs_args = inputs
.iter()
.zip(user_fn.args_option.iter())
.map(|(input, opt)| {
if *opt {
quote! { #input.map(|s| ScalarRefImpl::from(s)) }
} else {
quote! { Some(ScalarRefImpl::from(#input)) }
}
});
let inputs_args = quote! {
let args: &[DatumRef<'_>] = &[#(#inputs_args),*];
let args = args.iter().copied();
};
let var_args = variadic.then(|| {
quote! {
let args = args.chain(variadic_row.iter());
}
});

quote! {
#inputs_args
#var_args
errors.push(ExprError::function(
stringify!(#fn_name),
args,
e,
));
}
};

// call the user defined function
// inputs: [ Option<impl ScalarRef> ]
let mut output = quote! { #fn_name #generic(
Expand All @@ -365,13 +400,19 @@ impl FunctionAttr {
ReturnTypeKind::Result => quote! {
match #output {
Ok(x) => Some(x),
Err(e) => { errors.push(ExprError::Function(Box::new(e))); None }
Err(e) => {
#record_error
None
}
}
},
ReturnTypeKind::ResultOption => quote! {
match #output {
Ok(x) => x,
Err(e) => { errors.push(ExprError::Function(Box::new(e))); None }
Err(e) => {
#record_error
None
}
}
},
};
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/planner_test/tests/testdata/output/expr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -593,8 +593,9 @@
batch_error: |
Expr error
Caused by:
Division by zero
Caused by these errors (recent errors listed first):
1: error while evaluating expression `general_div('1', '0')`
2: Division by zero
- sql: |
select * from abs(-1);
batch_plan: 'BatchValues { rows: [[1:Int32]] }'
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/planner_test/tests/testdata/output/format.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@
batch_error: |
Expr error
Caused by:
too few arguments for format()
Caused by these errors (recent errors listed first):
1: error while evaluating expression `format('one', 'two')`
2: too few arguments for format()
- sql: |
SELECT format('Testing %s, %s, %s, %', 'one', 'two', 'three');
batch_error: |
Expand Down
25 changes: 15 additions & 10 deletions src/frontend/planner_test/tests/testdata/output/range_scan.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,35 +35,39 @@
batch_error: |
Expr error
Caused by:
Division by zero
Caused by these errors (recent errors listed first):
1: error while evaluating expression `general_div('1', '0')`
2: Division by zero
- before:
- create_table_and_mv
sql: |
SELECT * FROM orders_count_by_user WHERE user_id = 2147483647 + 1
batch_error: |
Expr error
Caused by:
Numeric out of range
Caused by these errors (recent errors listed first):
1: error while evaluating expression `general_add('2147483647', '1')`
2: Numeric out of range
- before:
- create_table_and_mv
sql: |
SELECT * FROM orders_count_by_user WHERE user_id = 'a'
batch_error: |
Expr error
Caused by:
Parse error: bigint invalid digit found in string
Caused by these errors (recent errors listed first):
1: error while evaluating expression `str_parse('a')`
2: Parse error: bigint invalid digit found in string
- before:
- create_table_and_mv
sql: |
SELECT * FROM orders_count_by_user WHERE user_id > 'a'
batch_error: |
Expr error
Caused by:
Parse error: bigint invalid digit found in string
Caused by these errors (recent errors listed first):
1: error while evaluating expression `str_parse('a')`
2: Parse error: bigint invalid digit found in string
- before:
- create_table_and_mv
sql: |
Expand Down Expand Up @@ -147,8 +151,9 @@
batch_error: |
Expr error
Caused by:
Parse error: bigint invalid digit found in string
Caused by these errors (recent errors listed first):
1: error while evaluating expression `str_parse('43.0')`
2: Parse error: bigint invalid digit found in string
- before:
- create_table_and_mv
sql: |
Expand Down

0 comments on commit 44c8b09

Please sign in to comment.