From 94021abc368d9fc621d09c2904fc7fd609d3a055 Mon Sep 17 00:00:00 2001 From: Yuchen Pei Date: Wed, 22 Jul 2026 17:50:44 +1000 Subject: [PATCH] MDEV-39525 [wip] Add supertype checks to vcol index substitution in WHERE - check if both the vcol expr and vcol field are supertypes to the RHS - check if vcol field is a supertype to vcol expr If either check returns true, then the substitution is safe. Skip the checks for IS NULL / IS NOT NULL. TODO: Does not yet work for the JSON_EXTRACT/JSON_VALUE vcol examples in vcol_sargable because capacity_limit_is_in_characters returns different values for varchar and blob/text --- mysql-test/suite/vcol/r/vcol_sargable.result | 62 ++++++++++++---- mysql-test/suite/vcol/t/vcol_sargable.test | 19 +++++ sql/field.cc | 8 ++ sql/field.h | 1 + sql/item.h | 8 ++ sql/opt_vcol_substitution.cc | 77 ++++++++++++-------- 6 files changed, 128 insertions(+), 47 deletions(-) diff --git a/mysql-test/suite/vcol/r/vcol_sargable.result b/mysql-test/suite/vcol/r/vcol_sargable.result index 427c71d743e74..ecc02f3b674dd 100644 --- a/mysql-test/suite/vcol/r/vcol_sargable.result +++ b/mysql-test/suite/vcol/r/vcol_sargable.result @@ -10,16 +10,24 @@ id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE TBL ref vcol1 vcol1 5 const 1 explain select * from t1 where a+1<=2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range vcol1 vcol1 5 NULL 1 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression `t1`.`a` + 1 -> vcol1 due to failed supertype check explain select * from t1 where a+1<2; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range vcol1 vcol1 5 NULL 1 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression `t1`.`a` + 1 -> vcol1 due to failed supertype check explain select * from t1 where a+1>100; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range vcol1 vcol1 5 NULL 6 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression `t1`.`a` + 1 -> vcol1 due to failed supertype check explain select * from t1 where a+1>=100; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 range vcol1 vcol1 5 NULL 13 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression `t1`.`a` + 1 -> vcol1 due to failed supertype check explain select * from t1 where a+1 between 10 and 12; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 range vcol1 vcol1 5 NULL 2 Using index condition @@ -150,7 +158,7 @@ explain select * from t3 where concat('100', a)=10010; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t3 ALL NULL NULL NULL NULL 100 Using where Warnings: -Note 1105 Cannot substitute virtual column expression concat('100',`t3`.`a`) -> vcol1 due to type mismatch +Note 1105 Cannot substitute virtual column expression concat('100',`t3`.`a`) -> vcol1 due to collation mismatch # Character set change explain select * from t3 where concat('hello-', a)='abcd'; id select_type table type possible_keys key key_len ref rows Extra @@ -182,9 +190,20 @@ a js1 size1 color 2 {"size":2, "color":"hue2"} 2 hue2 3 {"size":3, "color":"hue3"} 3 hue3 # Index is used: +## TODO: actually does not work because +## capacity_limit_is_in_characters() returns false for +## the expression with type mediumtext, and true for +## Item_string 'hue5' +## set collation_connection so that the constant string has the +## correct collation +set @old_collation_connection=@@collation_connection; +set collation_connection=utf8mb4_bin; explain select * from t1 where json_unquote(json_extract(js1, '$.color'))='hue5'; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ref color color 403 const 1 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression json_unquote(json_extract(`t1`.`js1`,'$.color')) -> color due to failed supertype check +set collation_connection=@old_collation_connection; explain select * from t1 where json_unquote(json_extract(js1, '$.color')) IS NULL; id select_type table type possible_keys key key_len ref rows Extra 1 SIMPLE t1 ref color color 403 const 1 Using index condition @@ -228,12 +247,18 @@ select collation(json_value(js1, '$.color')) from t1 limit 1; collation(json_value(js1, '$.color')) utf8mb4_bin # If one is fine with _bin comparisons, they can use index access: +## same problem with capacity_limit_is_in_characters alter table t1 add color1 varchar(100) collate utf8mb4_bin as (json_value(js1, '$.color')), add index(color1); +set @old_collation_connection=@@collation_connection; +set collation_connection=utf8mb4_bin; explain select * from t1 where json_value(js1, '$.color')='hue10'; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ref color1 color1 403 const 1 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression json_value(`t1`.`js1`,'$.color') -> color1 due to failed supertype check +set collation_connection=@old_collation_connection; alter table t1 drop column color1; # Using different collation in column substitution prevents # the optimization from working: @@ -247,19 +272,26 @@ Warnings: Note 1105 Cannot substitute virtual column expression json_value(`t1`.`js1`,'$.color') -> color2 due to collation mismatch alter table t1 drop column color2; # Explicitly specifying the collation helps: +## TODO: same problem with capacity_limit_is_in_characters alter table t1 add color3 varchar(100) collate utf8mb4_unicode_ci as (json_value(js1, '$.color') collate utf8mb4_unicode_ci), add index(color3); +set @old_collation_connection=@@collation_connection; +set collation_connection=utf8mb4_unicode_ci; explain select * from t1 where json_value(js1, '$.color') collate utf8mb4_unicode_ci='hue10'; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t1 ref color3 color3 403 const 1 Using index condition +1 SIMPLE t1 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression json_value(`t1`.`js1`,'$.color') collate utf8mb4_unicode_ci -> color3 due to failed supertype check +set collation_connection=@old_collation_connection; # # Alternatively, one can store JSON in a column with the same # collation as default and then casts are not needed: # +## TODO: same problem with capacity_limit_is_in_characters create table t2( js1 longtext COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL CHECK (json_valid(`js1`)) ); @@ -271,7 +303,9 @@ explain select * from t2 where json_value(js1, '$.color')='hue10'; id select_type table type possible_keys key key_len ref rows Extra -1 SIMPLE t2 ref color3 color3 403 const 1 Using index condition +1 SIMPLE t2 ALL NULL NULL NULL NULL 100 Using where +Warnings: +Note 1105 Cannot substitute virtual column expression json_value(`t2`.`js1`,'$.color') -> color3 due to failed supertype check drop table t1,t2; # # Test interplay with sargable_casefold optimization: @@ -387,16 +421,12 @@ EXPLAIN "table": { "delete": 1, "table_name": "t1", - "access_type": "range", - "possible_keys": ["a1"], - "key": "a1", - "key_length": "5", - "used_key_parts": ["a1"], + "access_type": "ALL", "rows": 2, - "attached_condition": "t1.a1 > 'a'" + "attached_condition": "t1.v2 + t1.v3 > 'a'" } } } Warnings: -Warning 1292 Truncated incorrect DECIMAL value: 'a' +Note 1105 Cannot substitute virtual column expression `t1`.`v2` + `t1`.`v3` -> a1 due to failed supertype check drop table t1; diff --git a/mysql-test/suite/vcol/t/vcol_sargable.test b/mysql-test/suite/vcol/t/vcol_sargable.test index 904a1f5631630..f3b751ab19750 100644 --- a/mysql-test/suite/vcol/t/vcol_sargable.test +++ b/mysql-test/suite/vcol/t/vcol_sargable.test @@ -127,7 +127,17 @@ alter table t1 add index(color); select * from t1 limit 3; --echo # Index is used: +--echo ## TODO: actually does not work because +--echo ## capacity_limit_is_in_characters() returns false for +--echo ## the expression with type mediumtext, and true for +--echo ## Item_string 'hue5' + +--echo ## set collation_connection so that the constant string has the +--echo ## correct collation +set @old_collation_connection=@@collation_connection; +set collation_connection=utf8mb4_bin; explain select * from t1 where json_unquote(json_extract(js1, '$.color'))='hue5'; +set collation_connection=@old_collation_connection; explain select * from t1 where json_unquote(json_extract(js1, '$.color')) IS NULL; explain select * from t1 force index(color) where json_unquote(json_extract(js1, '$.color')) IS NOT NULL; @@ -162,10 +172,14 @@ select * from t1 where size1=10; select collation(json_value(js1, '$.color')) from t1 limit 1; --echo # If one is fine with _bin comparisons, they can use index access: +--echo ## same problem with capacity_limit_is_in_characters alter table t1 add color1 varchar(100) collate utf8mb4_bin as (json_value(js1, '$.color')), add index(color1); +set @old_collation_connection=@@collation_connection; +set collation_connection=utf8mb4_bin; explain select * from t1 where json_value(js1, '$.color')='hue10'; +set collation_connection=@old_collation_connection; alter table t1 drop column color1; --echo # Using different collation in column substitution prevents @@ -178,18 +192,23 @@ alter table t1 drop column color2; --echo # Explicitly specifying the collation helps: +--echo ## TODO: same problem with capacity_limit_is_in_characters alter table t1 add color3 varchar(100) collate utf8mb4_unicode_ci as (json_value(js1, '$.color') collate utf8mb4_unicode_ci), add index(color3); +set @old_collation_connection=@@collation_connection; +set collation_connection=utf8mb4_unicode_ci; explain select * from t1 where json_value(js1, '$.color') collate utf8mb4_unicode_ci='hue10'; +set collation_connection=@old_collation_connection; --echo # --echo # Alternatively, one can store JSON in a column with the same --echo # collation as default and then casts are not needed: --echo # +--echo ## TODO: same problem with capacity_limit_is_in_characters create table t2( js1 longtext COLLATE utf8mb4_uca1400_ai_ci DEFAULT NULL CHECK (json_valid(`js1`)) ); diff --git a/sql/field.cc b/sql/field.cc index 3030845e28672..55e9bc87a84b9 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -11600,6 +11600,14 @@ void Field::raise_note_cannot_use_key_part(THD *thd, } } +bool Field::is_supertype(Item *item) const +{ + return type_handler()->is_supertype(type_std_attributes(), + type_extra_attributes(), + item->type_handler(), + *item /*Type_std_attributes*/, + item->type_extra_attributes()); +} /* Give warning for unusable key diff --git a/sql/field.h b/sql/field.h index cada0e2ceb37d..32fa136a12cf6 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1776,6 +1776,7 @@ class Field: public Value_source const Data_type_compatibility reason) const; void raise_note_key_become_unused(THD *thd, const String &expr) const; + bool is_supertype(Item *item) const; protected: bool set_warning(unsigned int code, int cuted_increment) const { diff --git a/sql/item.h b/sql/item.h index 3db1c49c06adc..f9599b9edbbc3 100644 --- a/sql/item.h +++ b/sql/item.h @@ -2854,6 +2854,14 @@ class Item :public Value_source, DBUG_ASSERT(fixed()); return false; } + bool is_supertype(Item *item) const + { + return type_handler()->is_supertype(*this /*Type_std_attributes*/, + type_extra_attributes(), + item->type_handler(), + *item /*Type_std_attributes*/, + item->type_extra_attributes()); + } protected: /* diff --git a/sql/opt_vcol_substitution.cc b/sql/opt_vcol_substitution.cc index 3e86936f4c7c0..972d3cf2bf457 100644 --- a/sql/opt_vcol_substitution.cc +++ b/sql/opt_vcol_substitution.cc @@ -120,7 +120,8 @@ static void subst_vcol_if_compatible(Vcol_subst_context *ctx, Item_bool_func *cond, Item **vcol_expr_ref, - Field *vcol_field); + Field *vcol_field, + bool skip_supertype_check= false); static bool collect_indexed_vcols_for_table(TABLE *table, List *vcol_fields) @@ -440,41 +441,29 @@ static void subst_vcol_if_compatible(Vcol_subst_context *ctx, Item_bool_func *cond, Item **vcol_expr_ref, - Field *vcol_field) + Field *vcol_field, + bool skip_supertype_check) { Item *vcol_expr= *vcol_expr_ref; THD *thd= ctx->thd; const char *fail_cause= NULL; - if (cond) /* WHERE substitution (MDEV-39525) */ + if (cond) { - if (vcol_expr->type_handler_for_comparison() != - vcol_field->type_handler_for_comparison() || - (vcol_expr->maybe_null() && !vcol_field->maybe_null())) - fail_cause="type mismatch"; - else - { - CHARSET_INFO *cs= cond ? cond->compare_collation() : NULL; - if (vcol_expr->collation.collation != vcol_field->charset() && - cs != vcol_field->charset()) - fail_cause= "collation mismatch"; - } + if (vcol_expr->collation.collation != vcol_field->charset() && + cond->compare_collation() != vcol_field->charset()) + fail_cause= "collation mismatch"; } - else /* ORDER/GROUP BY substitution */ + if (!fail_cause && !skip_supertype_check && + !vcol_field->is_supertype(vcol_expr)) { - if (!vcol_field->type_handler()->is_supertype( - vcol_field->type_std_attributes(), - vcol_field->type_extra_attributes(), - vcol_expr->type_handler(), - *vcol_expr /*Type_std_attributes*/, - vcol_expr->type_extra_attributes())) fail_cause="failed supertype check"; - else if (vcol_expr->maybe_null() && !vcol_field->maybe_null()) - { - /* NOT NULL is not allowed for vcol definition */ - DBUG_ASSERT(0); - fail_cause= "nullability mismatch"; - } + } + if (!fail_cause && vcol_expr->maybe_null() && !vcol_field->maybe_null()) + { + /* NOT NULL is not allowed for vcol definition */ + DBUG_ASSERT(0); + fail_cause= "nullability mismatch"; } if (fail_cause) @@ -530,6 +519,10 @@ bool Item::vcol_subst_analyzer(uchar **) (((Item_func*)this)->bitmap_bit() & allowed_cmp_funcs))); // (2) } +static bool vcol_type_both_bounded(Field *vcol_field, Item *vcol_expr, Item *rhs) +{ + return vcol_field->is_supertype(rhs) && vcol_expr->is_supertype(rhs); +} Item* Item_bool_rowready_func2::vcol_subst_transformer(THD *thd, uchar *arg) { @@ -537,18 +530,30 @@ Item* Item_bool_rowready_func2::vcol_subst_transformer(THD *thd, uchar *arg) Vcol_subst_context *ctx= (Vcol_subst_context*)arg; Field *vcol_field; Item **vcol_expr; + Item *other= NULL; if (!args[0]->used_tables() && (vcol_field= is_vcol_expr(ctx, args[1]))) + { vcol_expr= &args[1]; + if (functype() == EQ_FUNC) + other= args[0]; + } else if (!args[1]->used_tables() && (vcol_field= is_vcol_expr(ctx, args[0]))) + { vcol_expr= &args[0]; + if (functype() == EQ_FUNC) + other= args[1]; + } else return this; /* No substitution */ DBUG_EXECUTE_IF("vcol_subst_simulate_oom", DBUG_SET("+d,simulate_out_of_memory");); - subst_vcol_if_compatible(ctx, this, vcol_expr, vcol_field); + if (other && vcol_type_both_bounded(vcol_field, *vcol_expr, other)) + subst_vcol_if_compatible(ctx, this, vcol_expr, vcol_field, true); + else + subst_vcol_if_compatible(ctx, this, vcol_expr, vcol_field, false); DBUG_EXECUTE_IF("vcol_subst_simulate_oom", DBUG_SET("-d,vcol_subst_simulate_oom");); @@ -564,7 +569,11 @@ Item* Item_func_between::vcol_subst_transformer(THD *thd, uchar *arg) !args[2]->used_tables() && (vcol_field= is_vcol_expr(ctx, args[0]))) { - subst_vcol_if_compatible(ctx, this, &args[0], vcol_field); + if (vcol_type_both_bounded(vcol_field, args[0], args[1]) && + vcol_type_both_bounded(vcol_field, args[0], args[2])) + subst_vcol_if_compatible(ctx, this, &args[0], vcol_field, true); + else + subst_vcol_if_compatible(ctx, this, &args[0], vcol_field, false); } return this; } @@ -575,7 +584,7 @@ Item* Item_func_null_predicate::vcol_subst_transformer(THD *thd, uchar *arg) Vcol_subst_context *ctx= (Vcol_subst_context*)arg; Field *vcol_field; if ((vcol_field= is_vcol_expr(ctx, args[0]))) - subst_vcol_if_compatible(ctx, this, &args[0], vcol_field); + subst_vcol_if_compatible(ctx, this, &args[0], vcol_field, true); return this; } @@ -584,6 +593,7 @@ Item* Item_func_in::vcol_subst_transformer(THD *thd, uchar *arg) { Vcol_subst_context *ctx= (Vcol_subst_context*)arg; Field *vcol_field= nullptr; + uint i= 1; /* Check that the left hand side of IN() is a virtual column expression and @@ -592,8 +602,13 @@ Item* Item_func_in::vcol_subst_transformer(THD *thd, uchar *arg) if (!(vcol_field= is_vcol_expr(ctx, args[0])) || !compatible_types_scalar_bisection_possible()) return this; + for (; i < arg_count; i++) + { + if (!vcol_type_both_bounded(vcol_field, args[0], args[i])) + break; + } - subst_vcol_if_compatible(ctx, this, &args[0], vcol_field); + subst_vcol_if_compatible(ctx, this, &args[0], vcol_field, i == arg_count); return this; }