Skip to content

Commit 68ebfb1

Browse files
p1p1bearclaude
andauthored
Fix JSON function error handling when called from DuckDB engine (#139)
When JSON functions (json_overlaps, json_depth, json_unquote) are called from DuckDB engine with invalid input, they previously triggered MySQL error handling (my_error) which is not appropriate in the DuckDB context. This fix adds a `m_caller_is_duckdb` flag to the relevant JSON function classes so that when called from DuckDB, errors are thrown as C++ exceptions (duckdb::InvalidInputException) instead of MySQL errors, allowing DuckDB to handle them properly. Fixes #136 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 5992bff commit 68ebfb1

5 files changed

Lines changed: 63 additions & 13 deletions

File tree

sql-common/json_error_handler.cc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,17 @@
2929

3030
void JsonParseDefaultErrorHandler::operator()(const char *parse_err,
3131
size_t err_offset) const {
32+
if (m_caller_is_duckdb) {
33+
throw std::exception();
34+
}
3235
my_error(ER_INVALID_JSON_TEXT_IN_PARAM, MYF(0), m_arg_idx + 1, m_func_name,
3336
parse_err, err_offset, "");
3437
}
3538

3639
void JsonDocumentDefaultDepthHandler() {
3740
my_error(ER_JSON_DOCUMENT_TOO_DEEP, MYF(0));
3841
}
42+
43+
void JsonDocumentDefaultDepthHandlerDuckDB() {
44+
throw std::exception();
45+
}

sql-common/json_error_handler.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,13 +39,15 @@ class JsonParseDefaultErrorHandler {
3939
: m_func_name(func_name), m_arg_idx(arg_idx) {}
4040

4141
void operator()(const char *parse_err, size_t err_offset) const;
42+
bool m_caller_is_duckdb = false;
4243

4344
private:
4445
const char *m_func_name;
4546
const int m_arg_idx;
4647
};
4748

4849
void JsonDocumentDefaultDepthHandler();
50+
void JsonDocumentDefaultDepthHandlerDuckDB();
4951

5052
#endif // MYSQL_SERVER
5153
#endif // JSON_ERROR_HANDLER_INCLUDED

sql/duckdb/duckdb_mysql_udf.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ bool mysql_json_overlaps(duckdb::string_t json1, duckdb::string_t json2) {
5151
Item_duckdb_string item_json1(json1);
5252
Item_duckdb_string item_json2(json2);
5353
Item_func_json_overlaps json_overlaps(POS(), &item_json1, &item_json2);
54+
json_overlaps.m_caller_is_duckdb = true;
5455
json_overlaps.fixed = true;
5556
return json_overlaps.val_int();
5657
}
@@ -59,6 +60,7 @@ int64_t mysql_json_depth(duckdb::string_t json) {
5960
Item_duckdb_string item_json(json);
6061
Item_func_json_depth json_depth(POS(), &item_json);
6162
json_depth.fixed = true;
63+
json_depth.m_caller_is_duckdb = true;
6264
return json_depth.val_int();
6365
}
6466

@@ -80,6 +82,7 @@ void mysql_json_unquote(duckdb::DataChunk &input,
8082
Item_duckdb_string item_json(*ldata);
8183
Item_func_json_unquote json_unquote(POS(), &item_json);
8284
json_unquote.fixed = true;
85+
json_unquote.m_caller_is_duckdb = true;
8386
String tmp;
8487
String *func_result = json_unquote.val_str(&tmp);
8588
if (json_unquote.null_value) {
@@ -109,6 +112,7 @@ void mysql_json_unquote(duckdb::DataChunk &input,
109112
Item_duckdb_string item_json(data[i]);
110113
Item_func_json_unquote json_unquote(POS(), &item_json);
111114
json_unquote.fixed = true;
115+
json_unquote.m_caller_is_duckdb = true;
112116
String tmp;
113117
String *func_result = json_unquote.val_str(&tmp);
114118
if (json_unquote.null_value) {

sql/item_json_func.cc

Lines changed: 44 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
#include "sql/thd_raii.h"
7474
#include "sql/thr_malloc.h"
7575
#include "template_utils.h" // down_cast
76+
#include "duckdb/common/exception.hpp"
7677

7778
class PT_item_list;
7879

@@ -304,14 +305,18 @@ static bool check_convertible_to_json(const Item *item, int argument_number,
304305
*/
305306
static bool json_is_valid(Item **args, uint arg_idx, String *value,
306307
const char *func_name, Json_dom_ptr *dom,
307-
bool require_str_or_json, bool *valid) {
308+
bool require_str_or_json, bool *valid,
309+
bool caller_is_duckdb = false) {
308310
Item *const arg_item = args[arg_idx];
309311

310312
enum_field_types field_type = get_normalized_field_type(arg_item);
311313

312314
if (!is_convertible_to_json(arg_item)) {
313315
if (require_str_or_json) {
314316
*valid = false;
317+
if (caller_is_duckdb) {
318+
throw duckdb::InvalidInputException("Invalid input in json func");
319+
}
315320
my_error(ER_INVALID_TYPE_FOR_JSON, MYF(0), arg_idx + 1, func_name);
316321
return true;
317322
}
@@ -330,7 +335,9 @@ static bool json_is_valid(Item **args, uint arg_idx, String *value,
330335
return !*valid;
331336
} else {
332337
String *const res = arg_item->val_str(value);
333-
if (current_thd->is_error()) return true;
338+
if (!caller_is_duckdb) {
339+
if (current_thd->is_error()) return true;
340+
}
334341

335342
if (arg_item->null_value) {
336343
*valid = true;
@@ -340,8 +347,11 @@ static bool json_is_valid(Item **args, uint arg_idx, String *value,
340347
bool parse_error = false;
341348
const bool failure = parse_json(
342349
*res, dom, require_str_or_json,
343-
[&parse_error, arg_idx, func_name](const char *parse_err,
344-
size_t err_offset) {
350+
[&parse_error, arg_idx, func_name, caller_is_duckdb](
351+
const char *parse_err, size_t err_offset) {
352+
if (caller_is_duckdb) {
353+
throw duckdb::InvalidInputException("Invalid input in json func");
354+
}
345355
my_error(ER_INVALID_JSON_TEXT_IN_PARAM, MYF(0), arg_idx + 1,
346356
func_name, parse_err, err_offset, "");
347357
parse_error = true;
@@ -1084,7 +1094,8 @@ bool json_value(Item *arg, Json_wrapper *result, bool *has_value) {
10841094
}
10851095

10861096
bool get_json_wrapper(Item **args, uint arg_idx, String *str,
1087-
const char *func_name, Json_wrapper *wrapper) {
1097+
const char *func_name, Json_wrapper *wrapper,
1098+
bool caller_is_duckdb) {
10881099
Item *const arg = args[arg_idx];
10891100

10901101
bool has_value;
@@ -1104,10 +1115,14 @@ bool get_json_wrapper(Item **args, uint arg_idx, String *str,
11041115
Json_dom_ptr dom; //@< we'll receive a DOM here from a successful text parse
11051116

11061117
bool valid;
1107-
if (json_is_valid(args, arg_idx, str, func_name, &dom, true, &valid))
1118+
if (json_is_valid(args, arg_idx, str, func_name, &dom, true, &valid,
1119+
caller_is_duckdb))
11081120
return true;
11091121

11101122
if (!valid) {
1123+
if (caller_is_duckdb) {
1124+
throw duckdb::InvalidInputException("Invalid input in json func");
1125+
}
11111126
my_error(ER_INVALID_TYPE_FOR_JSON, MYF(0), arg_idx + 1, func_name);
11121127
return true;
11131128
}
@@ -1743,10 +1758,13 @@ longlong Item_func_json_depth::val_int() {
17431758
Json_wrapper wrapper;
17441759

17451760
try {
1746-
if (get_json_wrapper(args, 0, &m_doc_value, func_name(), &wrapper))
1761+
if (get_json_wrapper(args, 0, &m_doc_value, func_name(), &wrapper, m_caller_is_duckdb))
17471762
return error_int();
17481763
} catch (...) {
17491764
/* purecov: begin inspected */
1765+
if (m_caller_is_duckdb) {
1766+
throw duckdb::InvalidInputException("Invalid input in json func");
1767+
}
17501768
handle_std_exception(func_name());
17511769
return error_int();
17521770
/* purecov: end */
@@ -3249,7 +3267,7 @@ String *Item_func_json_unquote::val_str(String *str) {
32493267
try {
32503268
if (args[0]->data_type() == MYSQL_TYPE_JSON) {
32513269
Json_wrapper wr;
3252-
if (get_json_wrapper(args, 0, str, func_name(), &wr)) {
3270+
if (get_json_wrapper(args, 0, str, func_name(), &wr, m_caller_is_duckdb)) {
32533271
return error_str();
32543272
}
32553273

@@ -3260,8 +3278,13 @@ String *Item_func_json_unquote::val_str(String *str) {
32603278

32613279
m_value.length(0);
32623280

3263-
if (wr.to_string(&m_value, false, func_name(),
3264-
JsonDocumentDefaultDepthHandler)) {
3281+
if (m_caller_is_duckdb) {
3282+
if (wr.to_string(&m_value, false, func_name(),
3283+
JsonDocumentDefaultDepthHandlerDuckDB)) {
3284+
return error_str();
3285+
}
3286+
} else if (wr.to_string(&m_value, false, func_name(),
3287+
JsonDocumentDefaultDepthHandler)) {
32653288
return error_str();
32663289
}
32673290

@@ -3317,6 +3340,7 @@ String *Item_func_json_unquote::val_str(String *str) {
33173340

33183341
Json_dom_ptr dom;
33193342
JsonParseDefaultErrorHandler parse_handler(func_name(), 0);
3343+
parse_handler.m_caller_is_duckdb = m_caller_is_duckdb;
33203344
if (parse_json(*utf8str, &dom, true, parse_handler,
33213345
JsonDocumentDefaultDepthHandler)) {
33223346
return error_str();
@@ -3331,6 +3355,9 @@ String *Item_func_json_unquote::val_str(String *str) {
33313355
return error_str(); /* purecov: inspected */
33323356
} catch (...) {
33333357
/* purecov: begin inspected */
3358+
if (m_caller_is_duckdb) {
3359+
throw duckdb::InvalidInputException("Invalid input in json func");
3360+
}
33343361
handle_std_exception(func_name());
33353362
return error_str();
33363363
/* purecov: end */
@@ -3857,14 +3884,16 @@ longlong Item_func_json_overlaps::val_int() {
38573884
Json_wrapper *doc_b = &wr_b;
38583885

38593886
// arg 0 is the document 1
3860-
if (get_json_wrapper(args, 0, &m_doc_value, func_name(), doc_a) ||
3887+
if (get_json_wrapper(args, 0, &m_doc_value, func_name(), doc_a,
3888+
m_caller_is_duckdb) ||
38613889
args[0]->null_value) {
38623890
null_value = true;
38633891
return 0;
38643892
}
38653893

38663894
// arg 1 is the document 2
3867-
if (get_json_wrapper(args, 1, &m_doc_value, func_name(), doc_b) ||
3895+
if (get_json_wrapper(args, 1, &m_doc_value, func_name(), doc_b,
3896+
m_caller_is_duckdb) ||
38683897
args[1]->null_value) {
38693898
null_value = true;
38703899
return 0;
@@ -3913,6 +3942,9 @@ longlong Item_func_json_overlaps::val_int() {
39133942
}
39143943
/* purecov: begin inspected */
39153944
} catch (...) {
3945+
if (m_caller_is_duckdb) {
3946+
throw duckdb::InvalidInputException("Invalid invalid in json func");
3947+
}
39163948
handle_std_exception(func_name());
39173949
return error_int();
39183950
/* purecov: end */

sql/item_json_func.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,10 +250,12 @@ bool json_value(Item *arg, Json_wrapper *result, bool *has_value);
250250
@param[out] str the string buffer
251251
@param[in] func_name the name of the function we are executing
252252
@param[out] wrapper the JSON value wrapper
253+
@param[in] caller_is_duckdb the func caller is duckdb
253254
@returns false if we found a value or NULL, true if not.
254255
*/
255256
bool get_json_wrapper(Item **args, uint arg_idx, String *str,
256-
const char *func_name, Json_wrapper *wrapper);
257+
const char *func_name, Json_wrapper *wrapper,
258+
bool caller_is_duckdb = false);
257259

258260
/**
259261
Convert Json values or MySQL values to JSON.
@@ -534,6 +536,7 @@ class Item_func_json_depth final : public Item_int_func {
534536
}
535537

536538
longlong val_int() override;
539+
bool m_caller_is_duckdb = false;
537540
};
538541

539542
/**
@@ -915,6 +918,7 @@ class Item_func_json_unquote : public Item_str_func {
915918
}
916919

917920
String *val_str(String *str) override;
921+
bool m_caller_is_duckdb = false;
918922
};
919923

920924
/**
@@ -1054,6 +1058,7 @@ class Item_func_json_overlaps : public Item_bool_func {
10541058
enum_const_item_cache can_cache_json_arg(Item *arg) override {
10551059
return (arg == args[0] || arg == args[1]) ? CACHE_JSON_VALUE : CACHE_NONE;
10561060
}
1061+
bool m_caller_is_duckdb = false;
10571062
};
10581063

10591064
class Item_func_member_of : public Item_bool_func {

0 commit comments

Comments
 (0)