Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
78f3529
MDEV-40168 [wip] Add multi valued index over fulltext
mariadb-YuchenPei Sep 4, 2026
2b86847
MDEV-40168 [wip] mvi quick
mariadb-YuchenPei Sep 4, 2026
99b2b44
Add comments
spetrunia Sep 4, 2026
f589644
Factor out common code into get_mvi_index()
spetrunia Sep 4, 2026
a33437a
Factor out common code into Item_func_json_contains::get_mvi_access()
spetrunia Sep 4, 2026
dae9007
Make the MVI scan a real access method: QUICK_MVI_SELECT
spetrunia Sep 4, 2026
39ceedf
Make optimizer trace print "range", not "index_merge" for MVI quick s…
spetrunia Sep 5, 2026
287527f
Add optimizer trace for the multi-valued index access
spetrunia Sep 5, 2026
1d93190
Make JSON_OVERLAPS sargable for multi-valued indexes
spetrunia Sep 5, 2026
84f2cbf
Move the JSON function MVI code into opt_mvi_jsonfuncs.cc
spetrunia Sep 6, 2026
8d84dfd
MDEV-40168: JSON-over-fulltext: add estimates.
spetrunia Sep 2, 2026
b41e910
Estimate the number of records an MVI access will read
spetrunia Sep 6, 2026
d6fbb2c
MDEV-40168: JSON-over-fulltext: let the estimate consult the FTS cache.
spetrunia Sep 6, 2026
6351aaf
MDEV-40168: JSON-over-fulltext: move the estimator into fts0est.cc.
spetrunia Sep 6, 2026
4308bb6
Adjust the MVI tests to the estimate that reads the FTS cache
spetrunia Sep 6, 2026
8f16f79
Trivial cleanups and comments
spetrunia Sep 6, 2026
409a649
Move QUICK_MVI_SELECT into opt_multi_valued_index.cc
spetrunia Sep 6, 2026
d059022
Keep the MVI access of a table in its JOIN_TAB
spetrunia Sep 6, 2026
cc80405
Do the MVI analysis one table at a time
spetrunia Sep 7, 2026
d4c8ff4
Make innodb_fts.estimate's clamped count deterministic
spetrunia Sep 7, 2026
aef91d6
Rename collect_mvi_vcols_for_table to collect_mvi_indexes_for_table
spetrunia Sep 7, 2026
aab99cd
Keep only the chosen Mvi_access in JOIN_TAB
spetrunia Sep 7, 2026
2496f06
Show multi-valued indexes in SHOW CREATE TABLE
spetrunia Sep 7, 2026
32833cd
Only allow one key part in an index over an ARRAY
spetrunia Sep 7, 2026
3ff5272
A plain KEY is the only index type allowed over an ARRAY
spetrunia Sep 7, 2026
bdf696e
MDEV-40168 Fix cast to int arrays
mariadb-YuchenPei Sep 8, 2026
5bf3ce5
MDEV-40168 nested array handling and json validation
mariadb-YuchenPei Sep 8, 2026
a2d80ea
Undo whitespace changes to reduce diff size
spetrunia Sep 8, 2026
520d274
Inline the mvi_key variable into its only use
spetrunia Sep 8, 2026
1c57220
Move the multi-valued key part DDL out of the grammar
spetrunia Sep 8, 2026
2d4e9d5
opt_mvi_jsonfuncs.cc: Move the code, const-ify, add comments.
spetrunia Sep 8, 2026
b8ccf8b
Improve comments, formatting.
spetrunia Sep 8, 2026
c5bbce6
MDEV-40168 Add testcases for when there's both a ft index and an mvi
mariadb-YuchenPei Sep 9, 2026
f490dd7
MDEV-40168 Add some DDL tests
mariadb-YuchenPei Sep 9, 2026
c9d8277
MDEV-40168 Resolve two TODOs
mariadb-YuchenPei Sep 9, 2026
67c5546
More comments, code readability. No functional changes.
spetrunia Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions libmysqld/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ SET(SQL_EMBEDDED_SOURCES emb_qcache.cc libmysqld.c lib_sql.cc
../sql/mf_iocache.cc ../sql/my_decimal.cc
../sql/net_serv.cc ../sql/opt_range.cc
../sql/opt_group_by_cardinality.cc
../sql/opt_multi_valued_index.cc
../sql/opt_rewrite_date_cmp.cc
../sql/opt_rewrite_remove_casefold.cc
../sql/opt_sargable_left.cc
Expand Down
1 change: 1 addition & 0 deletions mysql-test/main/multi_valued_index.opt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
--innodb_ft_index_cache
865 changes: 865 additions & 0 deletions mysql-test/main/multi_valued_index.result

Large diffs are not rendered by default.

543 changes: 543 additions & 0 deletions mysql-test/main/multi_valued_index.test

Large diffs are not rendered by default.

145 changes: 145 additions & 0 deletions mysql-test/main/multi_valued_index_notembedded.result
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
create table t1 (c int, j json,
key idx ((CAST(j->'$.tags' AS CHAR(6) ARRAY))))engine=innodb;
insert into t1 values (1,'{"tags": ["aaa"]}'),(2,'{"tags": ["bbb","ccc"]}');
# The trace prints the element keys in their encoded form, which is not
# readable. These are the tokens the index is built from, so the values
# the trace prints below must be found here:
select c, mvi_encode(j->'$.tags', char(6)) from t1;
c mvi_encode(j->'$.tags', char(6))
1 616161
2 626262 636363
set optimizer_trace=1;
#
# One element key
#
explain select * from t1 where json_contains(j->'$.tags','"aaa"');
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range idx idx 0 NULL 1 Using where
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
jd
[
{
"table": "t1",
"index": "idx",
"match": "all",
"rows": 1,
"cost": 0.00171364,
"ranges":
["616161"]
}
]
#
# Several element keys. A row has to have all of them, so the rarest
# one alone already reads every row we may return: only that key is
# left in "ranges", and the JSON predicate in the WHERE clause discards
# whatever else the shorter search finds.
#
create table t3 (c int, j json,
key idx3 ((CAST(j->'$.tags' AS CHAR(6) ARRAY))))engine=innodb;
insert into t3 values (1,'{"tags": ["aaa","bbb"]}'),(2,'{"tags": ["bbb"]}'),
(3,'{"tags": ["bbb"]}'),(4,'{"tags": ["bbb","ccc"]}');
select c, mvi_encode(j->'$.tags', char(6)) from t3;
c mvi_encode(j->'$.tags', char(6))
1 616161 626262
2 626262
3 626262
4 626262 636363
# "bbb" is in every row and "aaa" in one, so "aaa" is what we search for
explain select * from t3 where json_contains(j->'$.tags','["aaa","bbb"]');
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t3 range idx3 idx3 0 NULL 1 Using where
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
jd
[
{
"table": "t3",
"index": "idx3",
"match": "all",
"rows": 1,
"cost": 0.00171364,
"ranges":
["616161"]
}
]
select * from t3 where json_contains(j->'$.tags','["aaa","bbb"]') order by c;
c j
1 {"tags": ["aaa","bbb"]}
# the same rows without the index:
select * from t3 ignore index(idx3)
where json_contains(j->'$.tags','["aaa","bbb"]') order by c;
c j
1 {"tags": ["aaa","bbb"]}
drop table t3;
#
# Two tables: each entry names the table it belongs to
#
create table t2 (c int, j json,
key idx2 ((CAST(j->'$.tags' AS CHAR(6) ARRAY))))engine=innodb;
insert into t2 values (1,'{"tags": ["zzz"]}');
explain select * from t1,t2 where json_contains(t1.j->'$.tags','"aaa"')
and json_contains(t2.j->'$.tags','"zzz"');
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range idx idx 0 NULL 1 Using where
1 SIMPLE t2 range idx2 idx2 0 NULL 1 Using where; Using join buffer (flat, BNL join)
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
jd
[
{
"table": "t1",
"index": "idx",
"match": "all",
"rows": 1,
"cost": 0.00171364,
"ranges":
["616161"]
},
{
"table": "t2",
"index": "idx2",
"match": "all",
"rows": 1,
"cost": 0.00171364,
"ranges":
["7a7a7a"]
}
]
#
# JSON_OVERLAPS: the keys are ORed, so "match" is "any". A row matches
# through any one of them, so the estimates add up and every key stays
# in the query.
#
explain select * from t1 where json_overlaps(j->'$.tags','["bbb","ccc"]');
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range idx idx 0 NULL 2 Using where
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
jd
[
{
"table": "t1",
"index": "idx",
"match": "any",
"rows": 2,
"cost": 0.00260808,
"ranges":
[
"626262",
"636363"
]
}
]
#
# A predicate under a top-level OR gives no access, so nothing is printed
#
explain select * from t1 where json_contains(t1.j->'$.tags','"aaa"') or c=2;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ALL NULL NULL NULL NULL 2 Using where
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
jd
NULL
set optimizer_trace=default;
drop table t1,t2;
90 changes: 90 additions & 0 deletions mysql-test/main/multi_valued_index_notembedded.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
--source include/have_innodb.inc
# The test uses the optimizer trace:
--source include/not_embedded.inc


create table t1 (c int, j json,
key idx ((CAST(j->'$.tags' AS CHAR(6) ARRAY))))engine=innodb;
insert into t1 values (1,'{"tags": ["aaa"]}'),(2,'{"tags": ["bbb","ccc"]}');

--echo # The trace prints the element keys in their encoded form, which is not
--echo # readable. These are the tokens the index is built from, so the values
--echo # the trace prints below must be found here:
select c, mvi_encode(j->'$.tags', char(6)) from t1;

set optimizer_trace=1;

--echo #
--echo # One element key
--echo #
--disable_replay next_query Need to preserve optimizer trace
explain select * from t1 where json_contains(j->'$.tags','"aaa"');
--disable_view_protocol
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
--enable_view_protocol

--echo #
--echo # Several element keys. A row has to have all of them, so the rarest
--echo # one alone already reads every row we may return: only that key is
--echo # left in "ranges", and the JSON predicate in the WHERE clause discards
--echo # whatever else the shorter search finds.
--echo #
create table t3 (c int, j json,
key idx3 ((CAST(j->'$.tags' AS CHAR(6) ARRAY))))engine=innodb;
insert into t3 values (1,'{"tags": ["aaa","bbb"]}'),(2,'{"tags": ["bbb"]}'),
(3,'{"tags": ["bbb"]}'),(4,'{"tags": ["bbb","ccc"]}');
select c, mvi_encode(j->'$.tags', char(6)) from t3;

--echo # "bbb" is in every row and "aaa" in one, so "aaa" is what we search for
--disable_replay next_query Need to preserve optimizer trace
explain select * from t3 where json_contains(j->'$.tags','["aaa","bbb"]');
--disable_view_protocol
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
--enable_view_protocol
select * from t3 where json_contains(j->'$.tags','["aaa","bbb"]') order by c;
--echo # the same rows without the index:
select * from t3 ignore index(idx3)
where json_contains(j->'$.tags','["aaa","bbb"]') order by c;

drop table t3;

--echo #
--echo # Two tables: each entry names the table it belongs to
--echo #
create table t2 (c int, j json,
key idx2 ((CAST(j->'$.tags' AS CHAR(6) ARRAY))))engine=innodb;
insert into t2 values (1,'{"tags": ["zzz"]}');
--disable_replay next_query Need to preserve optimizer trace
explain select * from t1,t2 where json_contains(t1.j->'$.tags','"aaa"')
and json_contains(t2.j->'$.tags','"zzz"');
--disable_view_protocol
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
--enable_view_protocol

--echo #
--echo # JSON_OVERLAPS: the keys are ORed, so "match" is "any". A row matches
--echo # through any one of them, so the estimates add up and every key stays
--echo # in the query.
--echo #
--disable_replay next_query Need to preserve optimizer trace
explain select * from t1 where json_overlaps(j->'$.tags','["bbb","ccc"]');
--disable_view_protocol
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
--enable_view_protocol

--echo #
--echo # A predicate under a top-level OR gives no access, so nothing is printed
--echo #
--disable_replay next_query Need to preserve optimizer trace
explain select * from t1 where json_contains(t1.j->'$.tags','"aaa"') or c=2;
--disable_view_protocol
select json_detailed(json_extract(trace, '$**.multi_value_index_use')) as jd
from information_schema.optimizer_trace;
--enable_view_protocol

set optimizer_trace=default;
drop table t1,t2;
Loading
Loading