Skip to content

Commit ebe27ec

Browse files
mariadb-OlegSmirnovOlernov
authored andcommitted
MDEV-36761: Implement NULL-aware cardinality estimation for indexed columns
When all values in an indexed column are NULL, EITS statistics show avg_frequency == 0. This commit adds logic to distinguish between "no statistics available" and "all values are NULL" scenarios. For NULL-rejecting conditions (e.g., t1.col = t2.col), when statistics confirm all indexed values are NULL, the optimizer can now return a very low cardinality estimate (1.0) instead of unknown (0.0), since NULL = NULL never matches. For non-NULL-rejecting conditions (e.g., t1.col <=> t2.col), normal cardinality estimation continues to apply since matches are possible. Changes: - Added KEY::rec_per_key_null_aware() to check nulls_ratio from column statistics when avg_frequency is 0 - Modified best_access_path() in sql_select.cc to use the new rec_per_key_null_aware() method for ref access cost estimation - The optimization works with single-column and composite indexes, checking each key part's NULL-rejecting status via notnull_part bitmap
1 parent d0ac583 commit ebe27ec

File tree

5 files changed

+368
-9
lines changed

5 files changed

+368
-9
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
# Small driving table
2+
CREATE TABLE t1 (a INT, b INT);
3+
INSERT INTO t1 VALUES (1, 1), (2, 2000),(3,300);
4+
ANALYZE TABLE t1 PERSISTENT FOR ALL;
5+
Table Op Msg_type Msg_text
6+
test.t1 analyze status Engine-independent statistics collected
7+
test.t1 analyze status OK
8+
# Table that will be accessed by an index lookup (`ref` access)
9+
CREATE TABLE t2 (a INT, b INT, KEY key_b(b));
10+
# All t11.b values are NULL
11+
INSERT INTO t2 SELECT seq/100, NULL FROM seq_1_to_1000;
12+
ANALYZE TABLE t2 PERSISTENT FOR ALL;
13+
Table Op Msg_type Msg_text
14+
test.t2 analyze status Engine-independent statistics collected
15+
test.t2 analyze status Table is already up to date
16+
# NULL-rejecting equality t1.b = t2.b will not return any matches
17+
# because all values of t2.b are NULL. So "rows" = 1 for t2 where 1 is
18+
# a special value meaning "very few" rows
19+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b = t2.b;
20+
id select_type table type possible_keys key key_len ref rows filtered Extra
21+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
22+
1 SIMPLE t2 ref key_b key_b 5 test.t1.b 1 100.00 Using where
23+
Warnings:
24+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` join `test`.`t2` where `test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`
25+
# However, rows estimation for not NULL-rejecting conditions
26+
# must not be affected ("rows" > 1 is expected)
27+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b <=> t2.b;
28+
id select_type table type possible_keys key key_len ref rows filtered Extra
29+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00
30+
1 SIMPLE t2 ref key_b key_b 5 test.t1.b 11 100.00 Using index condition; Using where
31+
Warnings:
32+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` join `test`.`t2` where `test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t1`.`b` <=> `test`.`t2`.`b`
33+
ANALYZE SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b <=> t2.b;
34+
id select_type table type possible_keys key key_len ref rows r_rows filtered r_filtered Extra
35+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 3.00 100.00 100.00
36+
1 SIMPLE t2 ref key_b key_b 5 test.t1.b 11 0.00 100.00 100.00 Using index condition; Using where
37+
# Insert some non-NULL values and re-collect the stats
38+
INSERT INTO t2 SELECT 1, 1 FROM seq_1_to_100;
39+
ANALYZE TABLE t2 PERSISTENT FOR COLUMNS (b) INDEXES (key_b);
40+
Table Op Msg_type Msg_text
41+
test.t2 analyze status Engine-independent statistics collected
42+
test.t2 analyze status OK
43+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b = t2.b;
44+
id select_type table type possible_keys key key_len ref rows filtered Extra
45+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
46+
1 SIMPLE t2 ref key_b key_b 5 test.t1.b 100 100.00 Using where
47+
Warnings:
48+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t2`.`a` AS `a`,`test`.`t2`.`b` AS `b` from `test`.`t1` join `test`.`t2` where `test`.`t2`.`a` = `test`.`t1`.`a` and `test`.`t2`.`b` = `test`.`t1`.`b`
49+
# Test composite index for two columns. Key prefix is used for access
50+
CREATE TABLE t3 (a INT, b INT, KEY key_ab(a,b));
51+
# All t3.b values are NULL
52+
INSERT INTO t3 SELECT seq/100, NULL FROM seq_1_to_1000;
53+
ANALYZE TABLE t3 PERSISTENT FOR COLUMNS(b) INDEXES(key_ab);
54+
Table Op Msg_type Msg_text
55+
test.t3 analyze status Engine-independent statistics collected
56+
test.t3 analyze status Table is already up to date
57+
# NULL-rejecting equality t1.b = t3.b, same as above.
58+
# "rows" must be estimated to 1
59+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t1.b = t3.b;
60+
id select_type table type possible_keys key key_len ref rows filtered Extra
61+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
62+
1 SIMPLE t3 ref key_ab key_ab 10 test.t1.a,test.t1.b 1 100.00 Using index
63+
Warnings:
64+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t1` join `test`.`t3` where `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t1`.`b`
65+
# Rows estimation for not NULL-rejecting conditions are not affected
66+
# ("rows" > 1 is expected)
67+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a;
68+
id select_type table type possible_keys key key_len ref rows filtered Extra
69+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
70+
1 SIMPLE t3 ref key_ab key_ab 5 test.t1.a 90 100.00 Using index
71+
Warnings:
72+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t1` join `test`.`t3` where `test`.`t3`.`a` = `test`.`t1`.`a`
73+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t1.b <=> t3.b;
74+
id select_type table type possible_keys key key_len ref rows filtered Extra
75+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
76+
1 SIMPLE t3 ref key_ab key_ab 10 test.t1.a,test.t1.b 11 100.00 Using where; Using index
77+
Warnings:
78+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t1` join `test`.`t3` where `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t1`.`b` <=> `test`.`t3`.`b`
79+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t3.b is NULL;
80+
id select_type table type possible_keys key key_len ref rows filtered Extra
81+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
82+
1 SIMPLE t3 ref key_ab key_ab 10 test.t1.a,const 11 100.00 Using where; Using index
83+
Warnings:
84+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t1` join `test`.`t3` where `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` is null
85+
# Insert some non-NULL values and re-collect the stats
86+
INSERT INTO t3 SELECT 1, 1 FROM seq_1_to_100;
87+
ANALYZE TABLE t3 PERSISTENT FOR COLUMNS (b) INDEXES (key_ab);
88+
Table Op Msg_type Msg_text
89+
test.t3 analyze status Engine-independent statistics collected
90+
test.t3 analyze status OK
91+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t1.b = t3.b;
92+
id select_type table type possible_keys key key_len ref rows filtered Extra
93+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
94+
1 SIMPLE t3 ref key_ab key_ab 10 test.t1.a,test.t1.b 100 100.00 Using index
95+
Warnings:
96+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t3`.`a` AS `a`,`test`.`t3`.`b` AS `b` from `test`.`t1` join `test`.`t3` where `test`.`t3`.`a` = `test`.`t1`.`a` and `test`.`t3`.`b` = `test`.`t1`.`b`
97+
# Test composite index for 3 columns. Key prefix is used for access
98+
CREATE TABLE t4 (a INT, b INT, c INT, KEY key_abc(a,b,c));
99+
# All t3.b values are NULL
100+
INSERT INTO t4 SELECT seq/10, NULL, seq/10 FROM seq_1_to_1000;
101+
ANALYZE TABLE t4 PERSISTENT FOR COLUMNS(b) INDEXES(key_abc);
102+
Table Op Msg_type Msg_text
103+
test.t4 analyze status Engine-independent statistics collected
104+
test.t4 analyze status Table is already up to date
105+
# NULL-rejecting equality t1.b = t3.b, same as above.
106+
# "rows" must be estimated to 1
107+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a AND t1.b = t4.b;
108+
id select_type table type possible_keys key key_len ref rows filtered Extra
109+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
110+
1 SIMPLE t4 ref key_abc key_abc 10 test.t1.a,test.t1.b 1 100.00 Using index
111+
Warnings:
112+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t4`.`a` AS `a`,`test`.`t4`.`b` AS `b`,`test`.`t4`.`c` AS `c` from `test`.`t1` join `test`.`t4` where `test`.`t4`.`a` = `test`.`t1`.`a` and `test`.`t4`.`b` = `test`.`t1`.`b`
113+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a AND t1.b = t4.b and t1.b = t4.c;
114+
id select_type table type possible_keys key key_len ref rows filtered Extra
115+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
116+
1 SIMPLE t4 ref key_abc key_abc 15 test.t1.a,test.t1.b,test.t1.b 1 100.00 Using index
117+
Warnings:
118+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t4`.`a` AS `a`,`test`.`t4`.`b` AS `b`,`test`.`t4`.`c` AS `c` from `test`.`t1` join `test`.`t4` where `test`.`t4`.`a` = `test`.`t1`.`a` and `test`.`t4`.`b` = `test`.`t1`.`b` and `test`.`t4`.`c` = `test`.`t1`.`b`
119+
# "rows" expected to be > 1
120+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a;
121+
id select_type table type possible_keys key key_len ref rows filtered Extra
122+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
123+
1 SIMPLE t4 ref key_abc key_abc 5 test.t1.a 9 100.00 Using index
124+
Warnings:
125+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t4`.`a` AS `a`,`test`.`t4`.`b` AS `b`,`test`.`t4`.`c` AS `c` from `test`.`t1` join `test`.`t4` where `test`.`t4`.`a` = `test`.`t1`.`a`
126+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a AND t1.b <=> t4.c;
127+
id select_type table type possible_keys key key_len ref rows filtered Extra
128+
1 SIMPLE t1 ALL NULL NULL NULL NULL 3 100.00 Using where
129+
1 SIMPLE t4 ref key_abc key_abc 5 test.t1.a 9 100.00 Using where; Using index
130+
Warnings:
131+
Note 1003 select `test`.`t1`.`a` AS `a`,`test`.`t1`.`b` AS `b`,`test`.`t4`.`a` AS `a`,`test`.`t4`.`b` AS `b`,`test`.`t4`.`c` AS `c` from `test`.`t1` join `test`.`t4` where `test`.`t4`.`a` = `test`.`t1`.`a` and `test`.`t1`.`b` <=> `test`.`t4`.`c`
132+
DROP TABLE t1, t2, t3, t4;
133+
# Test for partially covered column
134+
CREATE TABLE t1 (a VARCHAR(10));
135+
INSERT INTO t1 SELECT seq FROM seq_1_to_10;
136+
CREATE TABLE t2 (
137+
a VARCHAR(10),
138+
b VARCHAR(10),
139+
INDEX i1(a, b(5))
140+
);
141+
INSERT INTO t2 SELECT seq, NULL FROM seq_1_to_1000;
142+
ANALYZE TABLE t2 PERSISTENT FOR COLUMNS (b) INDEXES (i1);
143+
Table Op Msg_type Msg_text
144+
test.t2 analyze status Engine-independent statistics collected
145+
test.t2 analyze status Table is already up to date
146+
EXPLAIN SELECT * FROM t1, t2 WHERE t2.a=t1.a AND t2.b=t1.a;
147+
id select_type table type possible_keys key key_len ref rows Extra
148+
1 SIMPLE t1 ALL NULL NULL NULL NULL 10 Using where
149+
1 SIMPLE t2 ref i1 i1 21 test.t1.a,test.t1.a 1 Using where
150+
DROP TABLE t1, t2;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
--source include/have_sequence.inc
2+
3+
--echo # Small driving table
4+
CREATE TABLE t1 (a INT, b INT);
5+
INSERT INTO t1 VALUES (1, 1), (2, 2000),(3,300);
6+
7+
ANALYZE TABLE t1 PERSISTENT FOR ALL;
8+
9+
--echo # Table that will be accessed by an index lookup (`ref` access)
10+
CREATE TABLE t2 (a INT, b INT, KEY key_b(b));
11+
--echo # All t11.b values are NULL
12+
INSERT INTO t2 SELECT seq/100, NULL FROM seq_1_to_1000;
13+
14+
ANALYZE TABLE t2 PERSISTENT FOR ALL;
15+
16+
--echo # NULL-rejecting equality t1.b = t2.b will not return any matches
17+
--echo # because all values of t2.b are NULL. So "rows" = 1 for t2 where 1 is
18+
--echo # a special value meaning "very few" rows
19+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b = t2.b;
20+
21+
--echo # However, rows estimation for not NULL-rejecting conditions
22+
--echo # must not be affected ("rows" > 1 is expected)
23+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b <=> t2.b;
24+
25+
ANALYZE SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b <=> t2.b;
26+
27+
--echo # Insert some non-NULL values and re-collect the stats
28+
INSERT INTO t2 SELECT 1, 1 FROM seq_1_to_100;
29+
30+
ANALYZE TABLE t2 PERSISTENT FOR COLUMNS (b) INDEXES (key_b);
31+
32+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t2 ON t1.a = t2.a AND t1.b = t2.b;
33+
34+
--echo # Test composite index for two columns. Key prefix is used for access
35+
CREATE TABLE t3 (a INT, b INT, KEY key_ab(a,b));
36+
--echo # All t3.b values are NULL
37+
INSERT INTO t3 SELECT seq/100, NULL FROM seq_1_to_1000;
38+
39+
ANALYZE TABLE t3 PERSISTENT FOR COLUMNS(b) INDEXES(key_ab);
40+
41+
--echo # NULL-rejecting equality t1.b = t3.b, same as above.
42+
--echo # "rows" must be estimated to 1
43+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t1.b = t3.b;
44+
45+
--echo # Rows estimation for not NULL-rejecting conditions are not affected
46+
--echo # ("rows" > 1 is expected)
47+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a;
48+
49+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t1.b <=> t3.b;
50+
51+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t3.b is NULL;
52+
53+
--echo # Insert some non-NULL values and re-collect the stats
54+
INSERT INTO t3 SELECT 1, 1 FROM seq_1_to_100;
55+
56+
ANALYZE TABLE t3 PERSISTENT FOR COLUMNS (b) INDEXES (key_ab);
57+
58+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t3 ON t1.a = t3.a AND t1.b = t3.b;
59+
60+
--echo # Test composite index for 3 columns. Key prefix is used for access
61+
CREATE TABLE t4 (a INT, b INT, c INT, KEY key_abc(a,b,c));
62+
63+
--echo # All t3.b values are NULL
64+
INSERT INTO t4 SELECT seq/10, NULL, seq/10 FROM seq_1_to_1000;
65+
66+
ANALYZE TABLE t4 PERSISTENT FOR COLUMNS(b) INDEXES(key_abc);
67+
68+
--echo # NULL-rejecting equality t1.b = t3.b, same as above.
69+
--echo # "rows" must be estimated to 1
70+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a AND t1.b = t4.b;
71+
72+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a AND t1.b = t4.b and t1.b = t4.c;
73+
74+
--echo # "rows" expected to be > 1
75+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a;
76+
77+
EXPLAIN EXTENDED SELECT * FROM t1 JOIN t4 ON t1.a = t4.a AND t1.b <=> t4.c;
78+
79+
DROP TABLE t1, t2, t3, t4;
80+
81+
--echo # Test for partially covered column
82+
CREATE TABLE t1 (a VARCHAR(10));
83+
INSERT INTO t1 SELECT seq FROM seq_1_to_10;
84+
85+
CREATE TABLE t2 (
86+
a VARCHAR(10),
87+
b VARCHAR(10),
88+
INDEX i1(a, b(5))
89+
);
90+
INSERT INTO t2 SELECT seq, NULL FROM seq_1_to_1000;
91+
ANALYZE TABLE t2 PERSISTENT FOR COLUMNS (b) INDEXES (i1);
92+
93+
EXPLAIN SELECT * FROM t1, t2 WHERE t2.a=t1.a AND t2.b=t1.a;
94+
95+
DROP TABLE t1, t2;

sql/sql_select.cc

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8696,7 +8696,26 @@ best_access_path(JOIN *join,
86968696
ulong key_flags;
86978697
uint key_parts;
86988698
key_part_map found_part= 0;
8699-
/* key parts which won't have NULL in lookup tuple */
8699+
8700+
/*
8701+
Bitmap indicating which key parts are used with NULL-rejecting
8702+
conditions.
8703+
8704+
A bit is set to 1 for a key part if it's used with a
8705+
NULL-rejecting condition (i.e., the condition will never be
8706+
satisfied when the indexed column contains NULL). A bit is 0 if
8707+
the key part is used with a non-NULL-rejecting condition (i.e.,
8708+
the condition can be satisfied even when the indexed column
8709+
contains NULL, e.g., is NULL or <=>).
8710+
8711+
Example: for condition
8712+
t1.keypart1 = t2.col1 AND t1.keypart2 <=> t2.col2 AND
8713+
t1.keypart3 = t2.col3
8714+
the notnull_part bitmap will be 101 (binary), because:
8715+
- keypart1: '=' is NULL-rejecting (bit 1)
8716+
- keypart2: '<=>' is NOT NULL-rejecting (bit 0)
8717+
- keypart3: '=' is NULL-rejecting (bit 1)
8718+
*/
87008719
key_part_map notnull_part=0;
87018720
table_map found_ref= 0;
87028721
uint key= keyuse->key;
@@ -8951,7 +8970,8 @@ best_access_path(JOIN *join,
89518970
}
89528971
else
89538972
{
8954-
if (!(records= keyinfo->actual_rec_per_key(key_parts-1)))
8973+
if (!(records=
8974+
keyinfo->rec_per_key_null_aware(key_parts-1, notnull_part)))
89558975
{ /* Prefer longer keys */
89568976
trace_access_idx.add("rec_per_key_stats_missing", true);
89578977
records=
@@ -9083,7 +9103,9 @@ best_access_path(JOIN *join,
90839103
else
90849104
{
90859105
/* Check if we have statistic about the distribution */
9086-
if ((records= keyinfo->actual_rec_per_key(max_key_part-1)))
9106+
if ((records=
9107+
keyinfo->rec_per_key_null_aware(max_key_part-1,
9108+
notnull_part)))
90879109
{
90889110
/*
90899111
Fix for the case where the index statistics is too

sql/structs.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,9 @@ typedef struct st_key {
171171
engine_option_value *option_list;
172172
ha_index_option_struct *option_struct; /* structure with parsed options */
173173

174-
double actual_rec_per_key(uint i) const;
174+
double actual_rec_per_key(uint last_key_part_in_prefix) const;
175+
double rec_per_key_null_aware(uint last_key_part_in_prefix,
176+
key_part_map notnull_part) const;
175177
} KEY;
176178

177179

0 commit comments

Comments
 (0)