From 1a7bfa78630ad1221fe32be59c95ca317bd14b38 Mon Sep 17 00:00:00 2001 From: VasuBhakt Date: Fri, 24 Jul 2026 18:35:24 +0000 Subject: [PATCH] MDEV-40180 Cache constant args in JSON_EQUALS and JSON_OVERLAPS * Optimize JSON_EQUALS by caching the normalized DYNAMIC_STRING of constant arguments within val_bool(), preventing redundant re-parsing. * Fix JSON_OVERLAPS constant caching, which was improperly cached in fix_length_and_dec (causing --ps-protocol failures), by moving it to val_bool(). * Ensure cleanup() correctly resets caching state for both functions for prepared statements. * Note: This commit intentionally skips the nested objects short-circuit logic, which will be implemented incrementally in a future update. Signed-off-by: VasuBhakt --- sql/item_jsonfunc.cc | 118 +++++++++++++++++++++++++++++++------------ sql/item_jsonfunc.h | 21 ++++++-- 2 files changed, 104 insertions(+), 35 deletions(-) diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index cfe244dcd8e31..ab2754f525b5c 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -622,48 +622,92 @@ bool Item_func_json_equals::val_bool() longlong result= 0; int arg_num= 0; String a_tmp, b_tmp; + String *a= nullptr, *b= nullptr; THD *thd; json_engine_t je; if ((null_value= args[0]->null_value || args[1]->null_value)) return 1; - String *a= args[0]->val_json(&a_tmp); - if ((null_value= a == nullptr)) - return 1; - String *b= args[1]->val_json(&b_tmp); - if ((null_value= b == nullptr)) - return 1; - DYNAMIC_STRING a_res; - if (init_dynamic_string(&a_res, NULL, 0, 0)) + thd= current_thd; + JSON_DO_PAUSE_EXECUTION(thd, 0.0002); + je.killed_ptr= (uint32_t *) &thd->killed; + + /* Process First Argument */ + if (args[0]->const_item() && a_parsed) { - null_value= 1; - return 1; + if(a_null) goto return_null; } - - DYNAMIC_STRING b_res; - if (init_dynamic_string(&b_res, NULL, 0, 0)) + else { + a= args[0]->val_json(&a_tmp); + if (!a) goto set_a_null; + DYNAMIC_STRING a_res; + if (init_dynamic_string(&a_res, NULL, 0, 0)) goto set_a_null; + + if (json_normalize_engine(&je, &a_res, a->ptr(), a->length(), a->charset())) + { + dynstr_free(&a_res); + goto set_a_null; + } + + cached_a.copy(a_res.str, a_res.length, a->charset()); dynstr_free(&a_res); - null_value= 1; - return 1; + if (args[0]->const_item()) + { + a_null= false; + a_parsed= true; + } } - thd= current_thd; - JSON_DO_PAUSE_EXECUTION(thd, 0.0002); - je.killed_ptr= (uint32_t *) &thd->killed; + arg_num++; - if (json_normalize_engine(&je, &a_res, a->ptr(), a->length(), a->charset())) - goto return_null; + /* Process Second Argument */ + if (args[1]->const_item() && b_parsed) + { + if(b_null) goto return_null; + } + else + { + b= args[1]->val_json(&b_tmp); + if (!b) goto set_b_null; + DYNAMIC_STRING b_res; + if (init_dynamic_string(&b_res, NULL, 0, 0)) goto set_b_null; - arg_num++; - if (json_normalize_engine(&je, &b_res, b->ptr(), b->length(), b->charset())) - goto return_null; + if (json_normalize_engine(&je, &b_res, b->ptr(), b->length(), b->charset())) + { + dynstr_free(&b_res); + goto set_b_null; + } - result= strcmp(a_res.str, b_res.str) ? 0 : 1; + cached_b.copy(b_res.str, b_res.length, b->charset()); + dynstr_free(&b_res); + if (args[1]->const_item()) + { + b_null= false; + b_parsed= true; + } + } + + result= strcmp(cached_a.c_ptr_safe(), cached_b.c_ptr_safe()) ? 0 : 1; goto end; +set_a_null: + if (args[0]->const_item()) + { + a_null= true; + a_parsed= true; + } + goto return_null; + +set_b_null: + if (args[1]->const_item()) + { + b_null= true; + b_parsed= true; + } + return_null: null_value= 1; @@ -675,8 +719,6 @@ bool Item_func_json_equals::val_bool() a= b; report_json_error(a, &je, arg_num); } - dynstr_free(&b_res); - dynstr_free(&a_res); return result; } @@ -5053,10 +5095,26 @@ bool Item_func_json_overlaps::val_bool() thd= current_thd; JSON_DO_PAUSE_EXECUTION(thd, 0.0002); - if (!a2_parsed) + if (args[1]->const_item() && a2_parsed) { - val= args[1]->val_json(&tmp_val); - a2_parsed= a2_constant; + val= (cached_val.is_alloced() || cached_val.length()) ? &cached_val : 0; + } + else + { + String *v= args[1]->val_json(&tmp_val); + if (v) + { + cached_val.copy(v->ptr(), v->length(), v->charset()); + val= &cached_val; + } + else + { + cached_val.length(0); + val= 0; + } + + if (args[1]->const_item()) + a2_parsed= true; } if (val == 0) @@ -5092,8 +5150,6 @@ bool Item_func_json_overlaps::val_bool() bool Item_func_json_overlaps::fix_length_and_dec(THD *thd) { - a2_constant= args[1]->const_item(); - a2_parsed= FALSE; set_maybe_null(); return Item_bool_func::fix_length_and_dec(thd); diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h index cfa6a15077fe3..8340f9e191126 100644 --- a/sql/item_jsonfunc.h +++ b/sql/item_jsonfunc.h @@ -126,9 +126,12 @@ class Item_func_json_valid: public Item_bool_func class Item_func_json_equals: public Item_bool_func { + String cached_a, cached_b; + bool a_parsed, b_parsed; + bool a_null, b_null; public: Item_func_json_equals(THD *thd, Item *a, Item *b): - Item_bool_func(thd, a, b) {} + Item_bool_func(thd, a, b), a_parsed(false), b_parsed(false), a_null(false), b_null(false) {} LEX_CSTRING func_name_cstring() const override { static LEX_CSTRING name= {STRING_WITH_LEN("json_equals") }; @@ -138,6 +141,11 @@ class Item_func_json_equals: public Item_bool_func Item *shallow_copy(THD *thd) const override { return get_item_copy(thd, this); } bool val_bool() override; + void cleanup() override + { + a_parsed= b_parsed= false; + Item_bool_func::cleanup(); + } }; @@ -848,11 +856,11 @@ extern bool is_json_type(const Item *item); class Item_func_json_overlaps: public Item_bool_func { String tmp_js; - bool a2_constant, a2_parsed; - String tmp_val, *val; + bool a2_parsed; + String tmp_val, cached_val, *val; public: Item_func_json_overlaps(THD *thd, Item *a, Item *b): - Item_bool_func(thd, a, b) {} + Item_bool_func(thd, a, b), a2_parsed(false) {} LEX_CSTRING func_name_cstring() const override { static LEX_CSTRING name= {STRING_WITH_LEN("json_overlaps") }; @@ -860,6 +868,11 @@ class Item_func_json_overlaps: public Item_bool_func } bool fix_length_and_dec(THD *thd) override; bool val_bool() override; + void cleanup() override + { + a2_parsed= false; + Item_bool_func::cleanup(); + } Item *shallow_copy(THD *thd) const override { return get_item_copy(thd, this); } };