Commit 3b29cad0 authored by sergefp@mysql.com's avatar sergefp@mysql.com

Manual merge

parents 654bf99b a46d7542
...@@ -147,6 +147,12 @@ enum ha_extra_function { ...@@ -147,6 +147,12 @@ enum ha_extra_function {
*/ */
HA_EXTRA_CHANGE_KEY_TO_UNIQUE, HA_EXTRA_CHANGE_KEY_TO_UNIQUE,
HA_EXTRA_CHANGE_KEY_TO_DUP HA_EXTRA_CHANGE_KEY_TO_DUP
/*
When using HA_EXTRA_KEYREAD, overwrite only key member fields and keep
other fields intact. When this is off (by default) InnoDB will use memcpy
to overwrite entire row.
*/
HA_EXTRA_KEYREAD_PRESERVE_FIELDS
}; };
/* The following is parameter to ha_panic() */ /* The following is parameter to ha_panic() */
......
...@@ -46,6 +46,8 @@ extern my_bool bitmap_is_set(const MY_BITMAP *map, uint bitmap_bit); ...@@ -46,6 +46,8 @@ extern my_bool bitmap_is_set(const MY_BITMAP *map, uint bitmap_bit);
extern my_bool bitmap_is_set_all(const MY_BITMAP *map); extern my_bool bitmap_is_set_all(const MY_BITMAP *map);
extern my_bool bitmap_is_subset(const MY_BITMAP *map1, const MY_BITMAP *map2); extern my_bool bitmap_is_subset(const MY_BITMAP *map1, const MY_BITMAP *map2);
extern uint bitmap_set_next(MY_BITMAP *map); extern uint bitmap_set_next(MY_BITMAP *map);
extern uint bitmap_get_first(const MY_BITMAP *map);
extern uint bitmap_bits_set(const MY_BITMAP *map);
extern void bitmap_clear_all(MY_BITMAP *map); extern void bitmap_clear_all(MY_BITMAP *map);
extern void bitmap_clear_bit(MY_BITMAP *map, uint bitmap_bit); extern void bitmap_clear_bit(MY_BITMAP *map, uint bitmap_bit);
extern void bitmap_free(MY_BITMAP *map); extern void bitmap_free(MY_BITMAP *map);
......
...@@ -748,6 +748,7 @@ extern byte *my_compress_alloc(const byte *packet, ulong *len, ulong *complen); ...@@ -748,6 +748,7 @@ extern byte *my_compress_alloc(const byte *packet, ulong *len, ulong *complen);
extern ha_checksum my_checksum(ha_checksum crc, const byte *mem, uint count); extern ha_checksum my_checksum(ha_checksum crc, const byte *mem, uint count);
extern uint my_bit_log2(ulong value); extern uint my_bit_log2(ulong value);
extern uint my_count_bits(ulonglong v); extern uint my_count_bits(ulonglong v);
extern uint my_count_bits_ushort(ushort v);
extern void my_sleep(ulong m_seconds); extern void my_sleep(ulong m_seconds);
extern ulong crc32(ulong crc, const uchar *buf, uint len); extern ulong crc32(ulong crc, const uchar *buf, uint len);
extern uint my_set_max_open_files(uint files); extern uint my_set_max_open_files(uint files);
......
...@@ -554,6 +554,10 @@ struct row_prebuilt_struct { ...@@ -554,6 +554,10 @@ struct row_prebuilt_struct {
allocated mem buf start, because allocated mem buf start, because
there is a 4 byte magic number at the there is a 4 byte magic number at the
start and at the end */ start and at the end */
ibool keep_other_fields_on_keyread; /* when using fetch
cache with HA_EXTRA_KEYREAD, don't
overwrite other fields in mysql row
row buffer.*/
ulint fetch_cache_first;/* position of the first not yet ulint fetch_cache_first;/* position of the first not yet
fetched row in fetch_cache */ fetched row in fetch_cache */
ulint n_fetch_cached; /* number of not yet fetched rows ulint n_fetch_cached; /* number of not yet fetched rows
......
...@@ -2579,10 +2579,35 @@ row_sel_pop_cached_row_for_mysql( ...@@ -2579,10 +2579,35 @@ row_sel_pop_cached_row_for_mysql(
row */ row */
row_prebuilt_t* prebuilt) /* in: prebuilt struct */ row_prebuilt_t* prebuilt) /* in: prebuilt struct */
{ {
ut_ad(prebuilt->n_fetch_cached > 0); ulint i;
mysql_row_templ_t* templ;
byte* cached_rec;
ut_ad(prebuilt->n_fetch_cached > 0);
if (prebuilt->keep_other_fields_on_keyread)
{
/* Copy cache record field by field, don't touch fields that
are not covered by current key */
cached_rec =
prebuilt->fetch_cache[prebuilt->fetch_cache_first];
for (i = 0; i < prebuilt->n_template; i++) {
templ = prebuilt->mysql_template + i;
ut_memcpy(
buf + templ->mysql_col_offset,
cached_rec + templ->mysql_col_offset,
templ->mysql_col_len);
ut_memcpy(buf, prebuilt->fetch_cache[prebuilt->fetch_cache_first], if (templ->mysql_null_bit_mask)
prebuilt->mysql_row_len); buf[templ->mysql_null_byte_offset] &=
cached_rec[templ->mysql_null_byte_offset];
}
}
else
{
ut_memcpy(buf, prebuilt->fetch_cache[prebuilt->fetch_cache_first],
prebuilt->mysql_row_len);
}
prebuilt->n_fetch_cached--; prebuilt->n_fetch_cached--;
prebuilt->fetch_cache_first++; prebuilt->fetch_cache_first++;
......
This diff is collapsed.
drop table if exists t1;
create table t1
(
pk1 int not null,
pk2 int not null,
key1 int not null,
key2 int not null,
pktail1ok int not null,
pktail2ok int not null,
pktail3bad int not null,
pktail4bad int not null,
pktail5bad int not null,
pk2copy int not null,
badkey int not null,
filler1 char (200),
filler2 char (200),
key (key1),
key (key2),
/* keys with tails from CPK members */
key (pktail1ok, pk1),
key (pktail2ok, pk1, pk2),
key (pktail3bad, pk2, pk1),
key (pktail4bad, pk1, pk2copy),
key (pktail5bad, pk1, pk2, pk2copy),
primary key (pk1, pk2)
) engine=innodb;
explain select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref PRIMARY,key1 PRIMARY 4 const 1 Using where
select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
pk1 pk2 key1 key2 pktail1ok pktail2ok pktail3bad pktail4bad pktail5bad pk2copy badkey filler1 filler2
1 10 0 0 0 0 0 0 0 10 0 filler-data-10 filler2
1 11 0 0 0 0 0 0 0 11 0 filler-data-11 filler2
1 12 0 0 0 0 0 0 0 12 0 filler-data-12 filler2
1 13 0 0 0 0 0 0 0 13 0 filler-data-13 filler2
1 14 0 0 0 0 0 0 0 14 0 filler-data-14 filler2
1 15 0 0 0 0 0 0 0 15 0 filler-data-15 filler2
1 16 0 0 0 0 0 0 0 16 0 filler-data-16 filler2
1 17 0 0 0 0 0 0 0 17 0 filler-data-17 filler2
1 18 0 0 0 0 0 0 0 18 0 filler-data-18 filler2
1 19 0 0 0 0 0 0 0 19 0 filler-data-19 filler2
explain select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge key1,key2 intr(key1,key2) 4,4 NULL 1 Using where; Using index
select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
pk1 pk2
95 50
95 51
95 52
95 53
95 54
95 55
95 56
95 57
95 58
95 59
explain select * from t1 where badkey=1 and key1=10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref key1 key1 4 const 101 Using where
explain select * from t1 where pk1 < 7500 and key1 = 10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge PRIMARY,key1 intr(key1:PRIMARY) 4:4 NULL 38 Using where
explain select * from t1 where pktail1ok=1 and key1=10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge key1,pktail1ok intr(key1,pktail1ok) 4,4 NULL 1 Using where
explain select * from t1 where pktail2ok=1 and key1=10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge key1,pktail2ok intr(key1,pktail2ok) 4,4 NULL 1 Using where
explain select * from t1 where pktail3bad=1 and key1=10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref key1,pktail3bad pktail3bad 4 const 98 Using where
explain select * from t1 where pktail4bad=1 and key1=10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref key1,pktail4bad pktail4bad 4 const 99 Using where
explain select * from t1 where pktail5bad=1 and key1=10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 ref key1,pktail5bad pktail5bad 4 const 99 Using where
explain select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge key1,key2 intr(key1,key2) 4,4 NULL 1 Using where; Using index
select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
pk1 pk2 key1 key2
95 50 10 10
95 51 10 10
95 52 10 10
95 53 10 10
95 54 10 10
95 55 10 10
95 56 10 10
95 57 10 10
95 58 10 10
95 59 10 10
drop table t1;
drop table if exists t1, t2, t3,t4;
create table t1 (
pk1 int not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values (-5, 1, 1),
(-100, 1, 1),
(3, 1, 1),
(0, 1, 1),
(10, 1, 1);
explain select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge key1,key2 key1,key2 5,5 NULL 5 Using where
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 key1 key2
-100 1 1
-5 1 1
0 1 1
3 1 1
10 1 1
drop table t1;
create table t1 (
pk1 int unsigned not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values (0, 1, 1),
(0xFFFFFFFF, 1, 1),
(0xFFFFFFFE, 1, 1),
(1, 1, 1),
(2, 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 key1 key2
0 1 1
1 1 1
2 1 1
4294967294 1 1
4294967295 1 1
drop table t1;
create table t1 (
pk1 char(4) not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb collate latin2_general_ci;
insert into t1 values ('a1', 1, 1),
('b2', 1, 1),
('A3', 1, 1),
('B4', 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 key1 key2
a1 1 1
A3 1 1
b2 1 1
B4 1 1
drop table t1;
create table t1 (
pk1 int not NULL,
pk2 char(4) not NULL collate latin1_german1_ci,
pk3 char(4) not NULL collate latin1_bin,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1,pk2,pk3),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values
(1, 'u', 'u', 1, 1),
(1, 'u', char(0xEC), 1, 1),
(1, 'u', 'x', 1, 1);
insert ignore into t1 select pk1, char(0xEC), pk3, key1, key2 from t1;
insert ignore into t1 select pk1, 'x', pk3, key1, key2 from t1 where pk2='u';
insert ignore into t1 select 2, pk2, pk3, key1, key2 from t1;
select * from t1;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
alter table t1 drop primary key;
select * from t1;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
drop table t1;
create table t1 (
pk1 varchar(8) NOT NULL default '',
pk2 varchar(4) NOT NULL default '',
key1 int(11),
key2 int(11),
primary key(pk1, pk2),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values ('','empt',2,2),
('a','a--a',2,2),
('bb','b--b',2,2),
('ccc','c--c',2,2),
('dddd','d--d',2,2);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 pk2 key1 key2
empt 2 2
a a--a 2 2
bb b--b 2 2
ccc c--c 2 2
dddd d--d 2 2
drop table t1;
drop table if exists t1, t2, t3,t4;
create table t1 (
pk1 int not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values (-5, 1, 1),
(-100, 1, 1),
(3, 1, 1),
(0, 1, 1),
(10, 1, 1);
explain select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 index_merge key1,key2 key1,key2 5,5 NULL 4 Using where
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 key1 key2
-100 1 1
-5 1 1
0 1 1
3 1 1
10 1 1
drop table t1;
create table t1 (
pk1 int unsigned not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values (0, 1, 1),
(0xFFFFFFFF, 1, 1),
(0xFFFFFFFE, 1, 1),
(1, 1, 1),
(2, 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 key1 key2
0 1 1
1 1 1
2 1 1
4294967294 1 1
4294967295 1 1
drop table t1;
create table t1 (
pk1 char(4) not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb collate latin2_general_ci;
insert into t1 values ('a1', 1, 1),
('b2', 1, 1),
('A3', 1, 1),
('B4', 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 key1 key2
a1 1 1
A3 1 1
b2 1 1
B4 1 1
drop table t1;
create table t1 (
pk1 int not NULL,
pk2 char(4) not NULL collate latin1_german1_ci,
pk3 char(4) not NULL collate latin1_bin,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1,pk2,pk3),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values
(1, 'u', 'u', 1, 1),
(1, 'u', char(0xEC), 1, 1),
(1, 'u', 'x', 1, 1);
insert ignore into t1 select pk1, char(0xEC), pk3, key1, key2 from t1;
insert ignore into t1 select pk1, 'x', pk3, key1, key2 from t1 where pk2='u';
insert ignore into t1 select 2, pk2, pk3, key1, key2 from t1;
select * from t1;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
alter table t1 drop primary key;
select * from t1;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 pk2 pk3 key1 key2
1 u 1 1
1 x 1 1
1 1 1
1 u u 1 1
1 u x 1 1
1 u 1 1
1 x u 1 1
1 x x 1 1
1 x 1 1
2 u 1 1
2 x 1 1
2 1 1
2 u u 1 1
2 u x 1 1
2 u 1 1
2 x u 1 1
2 x x 1 1
2 x 1 1
drop table t1;
create table t1 (
pk1 varchar(8) NOT NULL default '',
pk2 varchar(4) NOT NULL default '',
key1 int(11),
key2 int(11),
primary key(pk1, pk2),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values ('','empt',2,2),
('a','a--a',2,2),
('bb','b--b',2,2),
('ccc','c--c',2,2),
('dddd','d--d',2,2);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
pk1 pk2 key1 key2
empt 2 2
a a--a 2 2
bb b--b 2 2
ccc c--c 2 2
dddd d--d 2 2
drop table t1;
#
# ROR-index_merge tests.
#
--disable_warnings
drop table if exists t1,t0;
--enable_warnings
--disable_query_log
create table t1
(
/* Field names reflect value(rowid) distribution, st=STairs, swt= SaWTooth */
st_a int not null,
swt1a int not null,
swt2a int not null,
st_b int not null,
swt1b int not null,
swt2b int not null,
/* fields/keys for row retrieval tests */
key1 int,
key2 int,
key3 int,
key4 int,
/* make rows much bigger then keys */
filler1 char (200),
filler2 char (200),
filler3 char (200),
filler4 char (200),
filler5 char (200),
filler6 char (200),
/* order of keys is important */
key sta_swt12a(st_a,swt1a,swt2a),
key sta_swt1a(st_a,swt1a),
key sta_swt2a(st_a,swt2a),
key sta_swt21a(st_a,swt2a,swt1a),
key st_a(st_a),
key stb_swt1a_2b(st_b,swt1b,swt2a),
key stb_swt1b(st_b,swt1b),
key st_b(st_b),
key(key1),
key(key2),
key(key3),
key(key4)
) ;
# Fill table
create table t0 as select * from t1;
let $cnt=1000;
while ($cnt)
{
eval insert into t0 values (1, 2, 3, 1, 2, 3, 0, 0, 0, 0, 'data1', 'data2', 'data3', 'data4', 'data5', 'data6');
dec $cnt;
}
alter table t1 disable keys;
let $1=4;
while ($1)
{
let $2=4;
while ($2)
{
let $3=4;
while ($3)
{
eval insert into t1 select $1, $2, $3, $1 ,$2, $3, key1, key2, key3, key4, filler1, filler2, filler3, filler4, filler5, filler6 from t0;
dec $3;
}
dec $2;
}
dec $1;
}
# Row retrieval tests
# -1 is used for values 'out of any range we are using'
# insert enough rows for index intersection to be used for (key1,key2)
insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 100, 100,'key1-key2-key3-key4');
let $cnt=400;
while ($cnt)
{
eval insert into t1 (key1, key2, key3, key4, filler1) values (100, -1, 100, -1,'key1-key3');
dec $cnt;
}
let $cnt=400;
while ($cnt)
{
eval insert into t1 (key1, key2, key3, key4, filler1) values (-1, 100, -1, 100,'key2-key4');
dec $cnt;
}
alter table t1 enable keys;
--enable_query_log
select count(*) from t1;
# One row results tests for cases where a single row matches all conditions
explain select key1,key2 from t1 where key1=100 and key2=100;
select key1,key2 from t1 where key1=100 and key2=100;
explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
# Several-rows results
insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, -1, -1, 'key1-key2');
insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, 100, 100, 'key4-key3');
# ROR-intersection, not covering
explain select key1,key2,filler1 from t1 where key1=100 and key2=100;
select key1,key2,filler1 from t1 where key1=100 and key2=100;
# ROR-intersection, covering
explain select key1,key2 from t1 where key1=100 and key2=100;
select key1,key2 from t1 where key1=100 and key2=100;
# ROR-union of ROR-intersections
explain select key1,key2,key3,key4 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
select key1,key2,key3,key4 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
# 3-way ROR-intersection
explain select key1,key2,key3 from t1 where key1=100 and key2=100 and key3=100;
select key1,key2,key3 from t1 where key1=100 and key2=100 and key3=100;
# ROR-union(ROR-intersection, ROR-range)
insert into t1 (key1,key2,key3,key4,filler1) values (101,101,101,101, 'key1234-101');
explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=101;
select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=101;
# Run some ROR updates/deletes
select key1,key2, filler1 from t1 where key1=100 and key2=100;
update t1 set filler1='to be deleted' where key1=100 and key2=100;
update t1 set key1=200,key2=200 where key1=100 and key2=100;
delete from t1 where key1=200 and key2=200;
select key1,key2,filler1 from t1 where key2=100 and key2=200;
# ROR-union(ROR-intersection) with one of ROR-intersection giving empty
# results
explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
delete from t1 where key3=100 and key4=100;
# ROR-union with all ROR-intersections giving empty results
explain select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
select key1,key2,key3,key4,filler1 from t1 where key1=100 and key2=100 or key3=100 and key4=100;
# ROR-intersection with empty result
explain select key1,key2 from t1 where key1=100 and key2=100;
select key1,key2 from t1 where key1=100 and key2=100;
# ROR-union tests with various cases.
# All scans returning duplicate rows:
insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-1');
insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-2');
insert into t1 (key1, key2, key3, key4, filler1) values (100, 100, 200, 200,'key1-key2-key3-key4-3');
explain select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, -1, 200,'key4');
explain select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
insert into t1 (key1, key2, key3, key4, filler1) values (-1, -1, 200, -1,'key3');
explain select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
select key1,key2,key3,key4,filler1 from t1 where key3=200 or (key1=100 and key2=100) or key4=200;
##
## Optimizer tests
##
# Check that the shortest key is used for ROR-intersection, covering and non-covering.
explain select * from t1 where st_a=1 and st_b=1;
explain select st_a,st_b from t1 where st_a=1 and st_b=1;
# Check if "ingore index" syntax works
explain select st_a from t1 ignore index (st_a) where st_a=1 and st_b=1;
# Do many tests
# Check that keys that don't improve selectivity are skipped.
#
explain select * from t1 where st_a=1 and swt1a=1 and swt2a=1;
explain select * from t1 where st_b=1 and swt1b=1 and swt2b=1;
explain select * from t1 where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
explain select * from t1 ignore index (sta_swt21a, stb_swt1a_2b)
where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
explain select * from t1 ignore index (sta_swt21a, sta_swt12a, stb_swt1a_2b)
where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
explain select * from t1 ignore index (sta_swt21a, sta_swt12a, stb_swt1a_2b, stb_swt1b)
where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1 and swt2b=1;
explain select * from t1
where st_a=1 and swt1a=1 and swt2a=1 and st_b=1 and swt1b=1;
explain select * from t1
where st_a=1 and swt1a=1 and st_b=1 and swt1b=1 and swt1b=1;
explain select st_a from t1
where st_a=1 and swt1a=1 and st_b=1 and swt1b=1 and swt1b=1;
explain select st_a from t1
where st_a=1 and swt1a=1 and st_b=1 and swt1b=1 and swt1b=1;
drop table t0,t1;
#
# Clustered PK ROR-index_merge tests
#
-- source include/have_innodb.inc
--disable_warnings
drop table if exists t1;
--enable_warnings
create table t1
(
pk1 int not null,
pk2 int not null,
key1 int not null,
key2 int not null,
pktail1ok int not null,
pktail2ok int not null,
pktail3bad int not null,
pktail4bad int not null,
pktail5bad int not null,
pk2copy int not null,
badkey int not null,
filler1 char (200),
filler2 char (200),
key (key1),
key (key2),
/* keys with tails from CPK members */
key (pktail1ok, pk1),
key (pktail2ok, pk1, pk2),
key (pktail3bad, pk2, pk1),
key (pktail4bad, pk1, pk2copy),
key (pktail5bad, pk1, pk2, pk2copy),
primary key (pk1, pk2)
) engine=innodb;
--disable_query_log
set autocommit=0;
let $1=10000;
while ($1)
{
eval insert into t1 values ($1 div 10,$1 mod 100, $1/100,$1/100, $1/100,$1/100,$1/100,$1/100,$1/100, $1 mod 100, $1/1000,'filler-data-$1','filler2');
dec $1;
}
set autocommit=1;
--enable_query_log
# Verify that range scan on CPK is ROR
# (use index_intersection because it is impossible to check that for index union)
explain select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
# CPK scan + 1 ROR range scan is a special case
select * from t1 where pk1 = 1 and pk2 < 80 and key1=0;
# Verify that CPK fields are considered to be covered by index scans
explain select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
select pk1,pk2 from t1 where key1 = 10 and key2=10 and 2*pk1+1 < 2*96+1;
# Verify that CPK is always used for index intersection scans
# (this is because it is used as a filter, not for retrieval)
explain select * from t1 where badkey=1 and key1=10;
explain select * from t1 where pk1 < 7500 and key1 = 10;
# Verify that keys with 'tails' of PK members are ok.
explain select * from t1 where pktail1ok=1 and key1=10;
explain select * from t1 where pktail2ok=1 and key1=10;
explain select * from t1 where pktail3bad=1 and key1=10;
explain select * from t1 where pktail4bad=1 and key1=10;
explain select * from t1 where pktail5bad=1 and key1=10;
# Test for problem with innodb key values prefetch buffer:
explain select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
select pk1,pk2,key1,key2 from t1 where key1 = 10 and key2=10 limit 10;
drop table t1;
#
# Test for rowid ordering (and comparison) functions.
# do index_merge select for tables with PK of various types.
#
--disable_warnings
drop table if exists t1, t2, t3,t4;
--enable_warnings
-- source include/have_bdb.inc
# Signed number as rowid
create table t1 (
pk1 int not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values (-5, 1, 1),
(-100, 1, 1),
(3, 1, 1),
(0, 1, 1),
(10, 1, 1);
explain select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Unsigned numbers as rowids
create table t1 (
pk1 int unsigned not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values (0, 1, 1),
(0xFFFFFFFF, 1, 1),
(0xFFFFFFFE, 1, 1),
(1, 1, 1),
(2, 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Case-insensitive char(N)
create table t1 (
pk1 char(4) not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb collate latin2_general_ci;
insert into t1 values ('a1', 1, 1),
('b2', 1, 1),
('A3', 1, 1),
('B4', 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Multi-part PK
create table t1 (
pk1 int not NULL,
pk2 char(4) not NULL collate latin1_german1_ci,
pk3 char(4) not NULL collate latin1_bin,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1,pk2,pk3),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values
(1, 'u', 'u', 1, 1),
(1, 'u', char(0xEC), 1, 1),
(1, 'u', 'x', 1, 1);
insert ignore into t1 select pk1, char(0xEC), pk3, key1, key2 from t1;
insert ignore into t1 select pk1, 'x', pk3, key1, key2 from t1 where pk2='u';
insert ignore into t1 select 2, pk2, pk3, key1, key2 from t1;
select * from t1;
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
# Hidden PK
alter table t1 drop primary key;
select * from t1;
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Variable-length PK
# this is also test for Bug#2688
create table t1 (
pk1 varchar(8) NOT NULL default '',
pk2 varchar(4) NOT NULL default '',
key1 int(11),
key2 int(11),
primary key(pk1, pk2),
KEY key1 (key1),
KEY key2 (key2)
) engine=bdb;
insert into t1 values ('','empt',2,2),
('a','a--a',2,2),
('bb','b--b',2,2),
('ccc','c--c',2,2),
('dddd','d--d',2,2);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
#
# Test for rowid ordering (and comparison) functions.
# do index_merge select for tables with PK of various types.
#
--disable_warnings
drop table if exists t1, t2, t3,t4;
--enable_warnings
-- source include/have_innodb.inc
# Signed number as rowid
create table t1 (
pk1 int not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values (-5, 1, 1),
(-100, 1, 1),
(3, 1, 1),
(0, 1, 1),
(10, 1, 1);
explain select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Unsigned numbers as rowids
create table t1 (
pk1 int unsigned not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values (0, 1, 1),
(0xFFFFFFFF, 1, 1),
(0xFFFFFFFE, 1, 1),
(1, 1, 1),
(2, 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Case-insensitive char(N)
create table t1 (
pk1 char(4) not NULL,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb collate latin2_general_ci;
insert into t1 values ('a1', 1, 1),
('b2', 1, 1),
('A3', 1, 1),
('B4', 1, 1);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Multi-part PK
create table t1 (
pk1 int not NULL,
pk2 char(4) not NULL collate latin1_german1_ci,
pk3 char(4) not NULL collate latin1_bin,
key1 int(11),
key2 int(11),
PRIMARY KEY (pk1,pk2,pk3),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values
(1, 'u', 'u', 1, 1),
(1, 'u', char(0xEC), 1, 1),
(1, 'u', 'x', 1, 1);
insert ignore into t1 select pk1, char(0xEC), pk3, key1, key2 from t1;
insert ignore into t1 select pk1, 'x', pk3, key1, key2 from t1 where pk2='u';
insert ignore into t1 select 2, pk2, pk3, key1, key2 from t1;
select * from t1;
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
# Hidden PK
alter table t1 drop primary key;
select * from t1;
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
# Variable-length PK
# this is also test for Bug#2688
create table t1 (
pk1 varchar(8) NOT NULL default '',
pk2 varchar(4) NOT NULL default '',
key1 int(11),
key2 int(11),
primary key(pk1, pk2),
KEY key1 (key1),
KEY key2 (key2)
) engine=innodb;
insert into t1 values ('','empt',2,2),
('a','a--a',2,2),
('bb','b--b',2,2),
('ccc','c--c',2,2),
('dddd','d--d',2,2);
select * from t1 force index(key1, key2) where key1 < 3 or key2 < 3;
drop table t1;
...@@ -71,3 +71,8 @@ uint my_count_bits(ulonglong v) ...@@ -71,3 +71,8 @@ uint my_count_bits(ulonglong v)
#endif #endif
} }
uint my_count_bits_ushort(ushort v)
{
return nbits[v];
}
...@@ -330,3 +330,59 @@ void bitmap_union(MY_BITMAP *map, const MY_BITMAP *map2) ...@@ -330,3 +330,59 @@ void bitmap_union(MY_BITMAP *map, const MY_BITMAP *map2)
bitmap_unlock(map); bitmap_unlock(map);
} }
/*
Get number of set bits in the bitmap
*/
uint bitmap_bits_set(const MY_BITMAP *map)
{
uchar *m= map->bitmap,
*end= map->bitmap+map->bitmap_size;
uint res= 0;
DBUG_ASSERT(map->bitmap);
bitmap_lock((MY_BITMAP *)map);
while (m < end)
{
res+= my_count_bits_ushort(*m++);
}
bitmap_unlock((MY_BITMAP *)map);
return res;
}
/*
Return number of first zero bit or MY_BIT_NONE if all bits are set.
*/
uint bitmap_get_first(const MY_BITMAP *map)
{
uchar *bitmap=map->bitmap;
uint bit_found = MY_BIT_NONE;
uint bitmap_size=map->bitmap_size*8;
uint i;
DBUG_ASSERT(map->bitmap);
bitmap_lock((MY_BITMAP *)map);
for (i=0; i < bitmap_size ; i++, bitmap++)
{
if (*bitmap != 0xff)
{ /* Found slot with free bit */
uint b;
for (b=0; ; b++)
{
if (!(*bitmap & (1 << b)))
{
bit_found = (i*8)+b;
break;
}
}
break; /* Found bit */
}
}
bitmap_unlock((MY_BITMAP *)map);
return bit_found;
}
...@@ -2499,4 +2499,29 @@ ha_rows ha_berkeley::estimate_number_of_rows() ...@@ -2499,4 +2499,29 @@ ha_rows ha_berkeley::estimate_number_of_rows()
return share->rows + HA_BERKELEY_EXTRA_ROWS; return share->rows + HA_BERKELEY_EXTRA_ROWS;
} }
int ha_berkeley::cmp_ref(const byte *ref1, const byte *ref2)
{
if (hidden_primary_key)
return memcmp(ref1, ref2, BDB_HIDDEN_PRIMARY_KEY_LENGTH);
int result;
Field *field;
KEY *key_info=table->key_info+table->primary_key;
KEY_PART_INFO *key_part=key_info->key_part;
KEY_PART_INFO *end=key_part+key_info->key_parts;
for (; key_part != end; key_part++)
{
field= key_part->field;
result= field->pack_cmp((const char*)ref1, (const char*)ref2,
key_part->length);
if (result)
return result;
ref1 += field->packed_col_length((const char*)ref1, key_part->length);
ref2 += field->packed_col_length((const char*)ref2, key_part->length);
}
return 0;
}
#endif /* HAVE_BERKELEY_DB */ #endif /* HAVE_BERKELEY_DB */
...@@ -169,6 +169,7 @@ class ha_berkeley: public handler ...@@ -169,6 +169,7 @@ class ha_berkeley: public handler
void print_error(int error, myf errflag); void print_error(int error, myf errflag);
uint8 table_cache_type() { return HA_CACHE_TBL_TRANSACT; } uint8 table_cache_type() { return HA_CACHE_TBL_TRANSACT; }
bool primary_key_is_clustered() { return true; } bool primary_key_is_clustered() { return true; }
int cmp_ref(const byte *ref1, const byte *ref2);
}; };
extern bool berkeley_shared_data; extern bool berkeley_shared_data;
......
...@@ -95,5 +95,10 @@ class ha_heap: public handler ...@@ -95,5 +95,10 @@ class ha_heap: public handler
THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to, THR_LOCK_DATA **store_lock(THD *thd, THR_LOCK_DATA **to,
enum thr_lock_type lock_type); enum thr_lock_type lock_type);
int cmp_ref(const byte *ref1, const byte *ref2)
{
HEAP_PTR ptr1=*(HEAP_PTR*)ref1;
HEAP_PTR ptr2=*(HEAP_PTR*)ref2;
return ptr1 < ptr2? -1 : (ptr1 > ptr2? 1 : 0);
}
}; };
...@@ -709,6 +709,8 @@ ha_innobase::init_table_handle_for_HANDLER(void) ...@@ -709,6 +709,8 @@ ha_innobase::init_table_handle_for_HANDLER(void)
prebuilt->read_just_key = FALSE; prebuilt->read_just_key = FALSE;
prebuilt->used_in_HANDLER = TRUE; prebuilt->used_in_HANDLER = TRUE;
prebuilt->keep_other_fields_on_keyread = FALSE;
} }
/************************************************************************* /*************************************************************************
...@@ -4515,9 +4517,11 @@ ha_innobase::extra( ...@@ -4515,9 +4517,11 @@ ha_innobase::extra(
if (prebuilt->blob_heap) { if (prebuilt->blob_heap) {
row_mysql_prebuilt_free_blob_heap(prebuilt); row_mysql_prebuilt_free_blob_heap(prebuilt);
} }
prebuilt->keep_other_fields_on_keyread = 0;
prebuilt->read_just_key = 0; prebuilt->read_just_key = 0;
break; break;
case HA_EXTRA_RESET_STATE: case HA_EXTRA_RESET_STATE:
prebuilt->keep_other_fields_on_keyread = 0;
prebuilt->read_just_key = 0; prebuilt->read_just_key = 0;
break; break;
case HA_EXTRA_NO_KEYREAD: case HA_EXTRA_NO_KEYREAD:
...@@ -4536,6 +4540,9 @@ ha_innobase::extra( ...@@ -4536,6 +4540,9 @@ ha_innobase::extra(
case HA_EXTRA_KEYREAD: case HA_EXTRA_KEYREAD:
prebuilt->read_just_key = 1; prebuilt->read_just_key = 1;
break; break;
case HA_EXTRA_KEYREAD_PRESERVE_FIELDS:
prebuilt->keep_other_fields_on_keyread = 1;
break;
default:/* Do nothing */ default:/* Do nothing */
; ;
} }
...@@ -4594,6 +4601,7 @@ ha_innobase::start_stmt( ...@@ -4594,6 +4601,7 @@ ha_innobase::start_stmt(
prebuilt->sql_stat_start = TRUE; prebuilt->sql_stat_start = TRUE;
prebuilt->hint_need_to_fetch_extra_cols = 0; prebuilt->hint_need_to_fetch_extra_cols = 0;
prebuilt->read_just_key = 0; prebuilt->read_just_key = 0;
prebuilt->keep_other_fields_on_keyread = FALSE;
if (!prebuilt->mysql_has_locked) { if (!prebuilt->mysql_has_locked) {
/* This handle is for a temporary table created inside /* This handle is for a temporary table created inside
...@@ -4672,6 +4680,7 @@ ha_innobase::external_lock( ...@@ -4672,6 +4680,7 @@ ha_innobase::external_lock(
prebuilt->hint_need_to_fetch_extra_cols = 0; prebuilt->hint_need_to_fetch_extra_cols = 0;
prebuilt->read_just_key = 0; prebuilt->read_just_key = 0;
prebuilt->keep_other_fields_on_keyread = FALSE;
if (lock_type == F_WRLCK) { if (lock_type == F_WRLCK) {
...@@ -5074,4 +5083,52 @@ ha_innobase::get_auto_increment() ...@@ -5074,4 +5083,52 @@ ha_innobase::get_auto_increment()
return(nr); return(nr);
} }
int
ha_innobase::cmp_ref(
const mysql_byte *ref1,
const mysql_byte *ref2)
{
row_prebuilt_t* prebuilt = (row_prebuilt_t*) innobase_prebuilt;
enum_field_types mysql_type;
Field* field;
int result;
if (prebuilt->clust_index_was_generated)
return memcmp(ref1, ref2, DATA_ROW_ID_LEN);
/* Do type-aware comparison of Primary Key members. PK members
are always NOT NULL, so no checks for NULL are performed */
KEY_PART_INFO *key_part= table->key_info[table->primary_key].key_part;
KEY_PART_INFO *key_part_end=
key_part + table->key_info[table->primary_key].key_parts;
for (; key_part != key_part_end; ++key_part) {
field = key_part->field;
mysql_type = field->type();
if (mysql_type == FIELD_TYPE_TINY_BLOB
|| mysql_type == FIELD_TYPE_MEDIUM_BLOB
|| mysql_type == FIELD_TYPE_BLOB
|| mysql_type == FIELD_TYPE_LONG_BLOB) {
ut_a(!ref1[1]);
ut_a(!ref2[1]);
byte len1= *ref1;
byte len2= *ref2;
ref1 += 2;
ref2 += 2;
result =
((Field_blob*)field)->cmp((const char*)ref1, len1,
(const char*)ref2, len2);
} else {
result =
field->cmp((const char*)ref1, (const char*)ref2);
}
if (result)
return result;
ref1 += key_part->length;
ref2 += key_part->length;
}
return 0;
}
#endif /* HAVE_INNOBASE_DB */ #endif /* HAVE_INNOBASE_DB */
...@@ -188,6 +188,7 @@ class ha_innobase: public handler ...@@ -188,6 +188,7 @@ class ha_innobase: public handler
longlong get_auto_increment(); longlong get_auto_increment();
uint8 table_cache_type() { return HA_CACHE_TBL_ASKTRANSACT; } uint8 table_cache_type() { return HA_CACHE_TBL_ASKTRANSACT; }
bool primary_key_is_clustered() { return true; } bool primary_key_is_clustered() { return true; }
int cmp_ref(const byte *ref1, const byte *ref2);
}; };
extern uint innobase_init_flags, innobase_lock_type; extern uint innobase_init_flags, innobase_lock_type;
......
...@@ -389,6 +389,11 @@ public: ...@@ -389,6 +389,11 @@ public:
false otherwise false otherwise
*/ */
virtual bool primary_key_is_clustered() { return false; } virtual bool primary_key_is_clustered() { return false; }
virtual int cmp_ref(const byte *ref1, const byte *ref2)
{
return memcmp(ref1, ref2, ref_length);
}
}; };
/* Some extern variables used with handlers */ /* Some extern variables used with handlers */
......
This diff is collapsed.
...@@ -79,10 +79,12 @@ public: ...@@ -79,10 +79,12 @@ public:
TABLE *head; TABLE *head;
/* /*
the only index this quick select uses, or MAX_KEY for index this quick select uses, or MAX_KEY for quick selects
QUICK_INDEX_MERGE_SELECT that use several indexes
*/ */
uint index; uint index;
/* applicable iff index!= MAX_KEY */
uint max_used_key_length, used_key_parts; uint max_used_key_length, used_key_parts;
QUICK_SELECT_I(); QUICK_SELECT_I();
...@@ -106,27 +108,64 @@ public: ...@@ -106,27 +108,64 @@ public:
QS_TYPE_RANGE = 0, QS_TYPE_RANGE = 0,
QS_TYPE_INDEX_MERGE = 1, QS_TYPE_INDEX_MERGE = 1,
QS_TYPE_RANGE_DESC = 2, QS_TYPE_RANGE_DESC = 2,
QS_TYPE_FULLTEXT = 3 QS_TYPE_FULLTEXT = 3,
QS_TYPE_ROR_INTERSECT = 4,
QS_TYPE_ROR_UNION = 5,
}; };
/* Get type of this quick select - one of the QS_* values */ /* Get type of this quick select - one of the QS_TYPE_* values */
virtual int get_type() = 0; virtual int get_type() = 0;
/*
Initialize this quick select as a child of a index union or intersection
scan. This call replaces init() call.
*/
virtual int init_ror_child_scan(bool reuse_handler)
{ DBUG_ASSERT(0); return 1; }
virtual void cleanup_ror_child_scan() { DBUG_ASSERT(0); }
virtual void save_last_pos(){};
/*
Fill key_names with list of keys this quick select used;
fill used_lenghth with correponding used lengths.
This is used by select_describe.
*/
virtual void fill_keys_and_lengths(String *key_names,
String *used_lengths)=0;
virtual bool check_if_keys_used(List<Item> *fields);
/*
rowid of last row retrieved by this quick select. This is used only
when doing ROR-index_merge selects
*/
byte *last_rowid;
byte *record;
#ifndef DBUG_OFF
/*
Print quick select information to DBUG_FILE. Caller is responsible
for locking DBUG_FILE before this call and unlocking it afterwards.
*/
virtual void dbug_dump(int indent, bool verbose)= 0;
#endif
}; };
struct st_qsel_param; struct st_qsel_param;
class SEL_ARG; class SEL_ARG;
class QUICK_RANGE_SELECT : public QUICK_SELECT_I class QUICK_RANGE_SELECT : public QUICK_SELECT_I
{ {
protected: protected:
bool next,dont_free; bool next,dont_free;
public: public:
int error; int error;
protected:
handler *file; handler *file;
byte *record; bool free_file; /* if true, this quick select "owns" file and will free it */
protected: protected:
friend void print_quick_sel_range(QUICK_RANGE_SELECT *quick,
const key_map* needed_reg);
friend friend
QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table, QUICK_RANGE_SELECT *get_quick_select_for_ref(THD *thd, TABLE *table,
struct st_table_ref *ref); struct st_table_ref *ref);
...@@ -140,17 +179,19 @@ protected: ...@@ -140,17 +179,19 @@ protected:
MEM_ROOT *alloc); MEM_ROOT *alloc);
friend class QUICK_SELECT_DESC; friend class QUICK_SELECT_DESC;
friend class QUICK_INDEX_MERGE_SELECT; friend class QUICK_INDEX_MERGE_SELECT;
friend class QUICK_ROR_INTERSECT_SELECT;
DYNAMIC_ARRAY ranges; /* ordered array of range ptrs */ DYNAMIC_ARRAY ranges; /* ordered array of range ptrs */
QUICK_RANGE **cur_range; /* current element in ranges */ QUICK_RANGE **cur_range; /* current element in ranges */
QUICK_RANGE *range; QUICK_RANGE *range;
MEM_ROOT alloc;
KEY_PART *key_parts; KEY_PART *key_parts;
int cmp_next(QUICK_RANGE *range); int cmp_next(QUICK_RANGE *range);
int cmp_prev(QUICK_RANGE *range); int cmp_prev(QUICK_RANGE *range);
bool row_in_ranges(); bool row_in_ranges();
public: public:
MEM_ROOT alloc;
QUICK_RANGE_SELECT(THD *thd, TABLE *table,uint index_arg,bool no_alloc=0, QUICK_RANGE_SELECT(THD *thd, TABLE *table,uint index_arg,bool no_alloc=0,
MEM_ROOT *parent_alloc=NULL); MEM_ROOT *parent_alloc=NULL);
~QUICK_RANGE_SELECT(); ~QUICK_RANGE_SELECT();
...@@ -166,7 +207,16 @@ public: ...@@ -166,7 +207,16 @@ public:
int get_next(); int get_next();
bool reverse_sorted() { return 0; } bool reverse_sorted() { return 0; }
bool unique_key_range(); bool unique_key_range();
int init_ror_child_scan(bool reuse_handler);
void save_last_pos()
{
file->position(record);
};
int get_type() { return QS_TYPE_RANGE; } int get_type() { return QS_TYPE_RANGE; }
void fill_keys_and_lengths(String *key_names, String *used_lengths);
#ifndef DBUG_OFF
virtual void dbug_dump(int indent, bool verbose);
#endif
}; };
...@@ -241,6 +291,11 @@ public: ...@@ -241,6 +291,11 @@ public:
bool reverse_sorted() { return false; } bool reverse_sorted() { return false; }
bool unique_key_range() { return false; } bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_INDEX_MERGE; } int get_type() { return QS_TYPE_INDEX_MERGE; }
void fill_keys_and_lengths(String *key_names, String *used_lengths);
bool check_if_keys_used(List<Item> *fields);
#ifndef DBUG_OFF
virtual void dbug_dump(int indent, bool verbose);
#endif
bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range); bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range);
...@@ -251,9 +306,6 @@ public: ...@@ -251,9 +306,6 @@ public:
List_iterator_fast<QUICK_RANGE_SELECT> cur_quick_it; List_iterator_fast<QUICK_RANGE_SELECT> cur_quick_it;
QUICK_RANGE_SELECT* cur_quick_select; QUICK_RANGE_SELECT* cur_quick_select;
/* last element in quick_selects list */
QUICK_RANGE_SELECT* last_quick_select;
/* quick select that uses clustered primary key (NULL if none) */ /* quick select that uses clustered primary key (NULL if none) */
QUICK_RANGE_SELECT* pk_quick_select; QUICK_RANGE_SELECT* pk_quick_select;
...@@ -271,6 +323,87 @@ public: ...@@ -271,6 +323,87 @@ public:
READ_RECORD read_record; READ_RECORD read_record;
}; };
/*
Rowid-Ordered Retrieval (ROR) index intersection quick select.
This quick select produces an intersection of records returned by several
QUICK_RANGE_SELECTs that return data ordered by rowid.
*/
class QUICK_ROR_INTERSECT_SELECT : public QUICK_SELECT_I
{
public:
QUICK_ROR_INTERSECT_SELECT(THD *thd, TABLE *table,
bool retrieve_full_rows,
MEM_ROOT *parent_alloc);
~QUICK_ROR_INTERSECT_SELECT();
int init();
int reset(void);
int get_next();
bool reverse_sorted() { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_ROR_INTERSECT; }
void fill_keys_and_lengths(String *key_names, String *used_lengths);
bool check_if_keys_used(List<Item> *fields);
#ifndef DBUG_OFF
virtual void dbug_dump(int indent, bool verbose);
#endif
int init_ror_child_scan(bool reuse_handler);
bool push_quick_back(QUICK_RANGE_SELECT *quick_sel_range);
/* range quick selects this intersection consists of */
List<QUICK_RANGE_SELECT> quick_selects;
QUICK_RANGE_SELECT *cpk_quick;
MEM_ROOT alloc;
THD *thd;
bool reset_called;
bool need_to_fetch_row;
};
/*
Rowid-Ordered Retrieval index union select.
*/
class QUICK_ROR_UNION_SELECT : public QUICK_SELECT_I
{
public:
QUICK_ROR_UNION_SELECT(THD *thd, TABLE *table);
~QUICK_ROR_UNION_SELECT();
int init();
int reset(void);
int get_next();
bool reverse_sorted() { return false; }
bool unique_key_range() { return false; }
int get_type() { return QS_TYPE_ROR_UNION; }
void fill_keys_and_lengths(String *key_names, String *used_lengths);
bool check_if_keys_used(List<Item> *fields);
#ifndef DBUG_OFF
virtual void dbug_dump(int indent, bool verbose);
#endif
bool push_quick_back(QUICK_SELECT_I *quick_sel_range);
/* range quick selects this index_merge read consists of */
List<QUICK_SELECT_I> quick_selects;
QUEUE queue;
MEM_ROOT alloc;
THD *thd;
byte *cur_rowid;
byte *prev_rowid;
uint rowid_length;
bool reset_called;
bool have_prev_rowid;
private:
static int queue_cmp(void *arg, byte *val1, byte *val2);
};
class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT class QUICK_SELECT_DESC: public QUICK_RANGE_SELECT
{ {
public: public:
......
...@@ -260,10 +260,10 @@ cleanup: ...@@ -260,10 +260,10 @@ cleanup:
#define MEM_STRIP_BUF_SIZE current_thd->variables.sortbuff_size #define MEM_STRIP_BUF_SIZE current_thd->variables.sortbuff_size
extern "C" int refposcmp2(void* arg, const void *a,const void *b) extern "C" int refpos_order_cmp(void* arg, const void *a,const void *b)
{ {
/* arg is a pointer to file->ref_length */ handler *file= (handler*)arg;
return memcmp(a,b, *(int*) arg); return file->cmp_ref((const byte*)a, (const byte*)b);
} }
multi_delete::multi_delete(THD *thd_arg, TABLE_LIST *dt, multi_delete::multi_delete(THD *thd_arg, TABLE_LIST *dt,
...@@ -327,8 +327,8 @@ multi_delete::initialize_tables(JOIN *join) ...@@ -327,8 +327,8 @@ multi_delete::initialize_tables(JOIN *join)
for (walk=walk->next ; walk ; walk=walk->next) for (walk=walk->next ; walk ; walk=walk->next)
{ {
TABLE *table=walk->table; TABLE *table=walk->table;
*tempfiles_ptr++= new Unique (refposcmp2, *tempfiles_ptr++= new Unique (refpos_order_cmp,
(void *) &table->file->ref_length, (void *) table->file,
table->file->ref_length, table->file->ref_length,
MEM_STRIP_BUF_SIZE); MEM_STRIP_BUF_SIZE);
} }
......
...@@ -8025,8 +8025,16 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, ...@@ -8025,8 +8025,16 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
} }
else if (select && select->quick) // Range found by opt_range else if (select && select->quick) // Range found by opt_range
{ {
/* assume results are not ordered when index merge is used */ int quick_type= select->quick->get_type();
if (select->quick->get_type() == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) /*
assume results are not ordered when index merge is used
TODO: sergeyp: Results of all index merge selects actually are ordered
by clustered PK values.
*/
if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE ||
quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION ||
quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT)
DBUG_RETURN(0); DBUG_RETURN(0);
ref_key= select->quick->index; ref_key= select->quick->index;
ref_key_parts= select->quick->used_key_parts; ref_key_parts= select->quick->used_key_parts;
...@@ -8087,9 +8095,11 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit, ...@@ -8087,9 +8095,11 @@ test_if_skip_sort_order(JOIN_TAB *tab,ORDER *order,ha_rows select_limit,
*/ */
if (!select->quick->reverse_sorted()) if (!select->quick->reverse_sorted())
{ {
int quick_type= select->quick->get_type();
if (table->file->index_flags(ref_key) & HA_NOT_READ_PREFIX_LAST || if (table->file->index_flags(ref_key) & HA_NOT_READ_PREFIX_LAST ||
(select->quick->get_type() == quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE ||
QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)) quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT ||
quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION)
DBUG_RETURN(0); // Use filesort DBUG_RETURN(0); // Use filesort
// ORDER BY range_key DESC // ORDER BY range_key DESC
...@@ -10067,6 +10077,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -10067,6 +10077,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
select_result *result=join->result; select_result *result=join->result;
Item *item_null= new Item_null(); Item *item_null= new Item_null();
CHARSET_INFO *cs= &my_charset_latin1; CHARSET_INFO *cs= &my_charset_latin1;
int quick_type= -1;
DBUG_ENTER("select_describe"); DBUG_ENTER("select_describe");
DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s", DBUG_PRINT("info", ("Select 0x%lx, type %s, message %s",
(ulong)join->select_lex, join->select_lex->type, (ulong)join->select_lex, join->select_lex->type,
...@@ -10112,8 +10123,10 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -10112,8 +10123,10 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
cs)); cs));
if (tab->type == JT_ALL && tab->select && tab->select->quick) if (tab->type == JT_ALL && tab->select && tab->select->quick)
{ {
if (tab->select->quick->get_type() == quick_type= tab->select->quick->get_type();
QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) if ((quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT) ||
(quick_type == QUICK_SELECT_I::QS_TYPE_ROR_UNION))
tab->type = JT_INDEX_MERGE; tab->type = JT_INDEX_MERGE;
else else
tab->type = JT_RANGE; tab->type = JT_RANGE;
...@@ -10134,6 +10147,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -10134,6 +10147,7 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
strlen(join_type_str[tab->type]), strlen(join_type_str[tab->type]),
cs)); cs));
uint j; uint j;
/* Build "possible_keys" value and add it to item_list */
if (!tab->keys.is_clear_all()) if (!tab->keys.is_clear_all())
{ {
for (j=0 ; j < table->keys ; j++) for (j=0 ; j < table->keys ; j++)
...@@ -10150,6 +10164,8 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -10150,6 +10164,8 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
item_list.push_back(new Item_string(tmp1.ptr(),tmp1.length(),cs)); item_list.push_back(new Item_string(tmp1.ptr(),tmp1.length(),cs));
else else
item_list.push_back(item_null); item_list.push_back(item_null);
/* Build key,key_len, and ref values and add them to item_list */
if (tab->ref.key_parts) if (tab->ref.key_parts)
{ {
KEY *key_info=table->key_info+ tab->ref.key; KEY *key_info=table->key_info+ tab->ref.key;
...@@ -10184,48 +10200,9 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -10184,48 +10200,9 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
} }
else if (tab->select && tab->select->quick) else if (tab->select && tab->select->quick)
{ {
if (tab->select->quick->get_type() == tab->select->quick->fill_keys_and_lengths(&tmp2, &tmp3);
QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
{
QUICK_INDEX_MERGE_SELECT *quick_imerge=
(QUICK_INDEX_MERGE_SELECT*)tab->select->quick;
QUICK_RANGE_SELECT *quick;
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_imerge->
quick_selects);
while ((quick= it++))
{
KEY *key_info= table->key_info + quick->index;
register uint length;
if (tmp3.length())
tmp3.append(',');
tmp3.append(key_info->name);
if (tmp2.length())
tmp2.append(',');
length= longlong2str(quick->max_used_key_length, keylen_str_buf,
10) -
keylen_str_buf;
tmp2.append(keylen_str_buf, length);
}
}
else
{
KEY *key_info= table->key_info + tab->select->quick->index;
register uint length;
tmp3.append(key_info->name);
length= longlong2str(tab->select->quick->max_used_key_length,
keylen_str_buf, 10) -
keylen_str_buf;
tmp2.append(keylen_str_buf, length);
}
item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs));
item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs)); item_list.push_back(new Item_string(tmp2.ptr(),tmp2.length(),cs));
item_list.push_back(new Item_string(tmp3.ptr(),tmp3.length(),cs));
item_list.push_back(item_null); item_list.push_back(item_null);
} }
else else
...@@ -10240,7 +10217,10 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order, ...@@ -10240,7 +10217,10 @@ static void select_describe(JOIN *join, bool need_tmp_table, bool need_order,
my_bool key_read=table->key_read; my_bool key_read=table->key_read;
if (tab->type == JT_NEXT && table->used_keys.is_set(tab->index)) if (tab->type == JT_NEXT && table->used_keys.is_set(tab->index))
key_read=1; key_read=1;
if (quick_type == QUICK_SELECT_I::QS_TYPE_ROR_INTERSECT &&
!((QUICK_ROR_INTERSECT_SELECT*)tab->select->quick)->need_to_fetch_row)
key_read=1;
if (tab->info) if (tab->info)
item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs)); item_list.push_back(new Item_string(tab->info,strlen(tab->info),cs));
else else
......
...@@ -334,7 +334,7 @@ uint find_shortest_key(TABLE *table, const key_map *usable_keys); ...@@ -334,7 +334,7 @@ uint find_shortest_key(TABLE *table, const key_map *usable_keys);
int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds); int opt_sum_query(TABLE_LIST *tables, List<Item> &all_fields,COND *conds);
/* from sql_delete.cc, used by opt_range.cc */ /* from sql_delete.cc, used by opt_range.cc */
extern "C" int refposcmp2(void* arg, const void *a,const void *b); extern "C" int refpos_order_cmp(void* arg, const void *a,const void *b);
/* class to copying an field/item to a key struct */ /* class to copying an field/item to a key struct */
......
...@@ -182,37 +182,8 @@ TEST_join(JOIN *join) ...@@ -182,37 +182,8 @@ TEST_join(JOIN *join)
tab->select->quick_keys.print(buf)); tab->select->quick_keys.print(buf));
else if (tab->select->quick) else if (tab->select->quick)
{ {
int quick_type= tab->select->quick->get_type(); fprintf(DBUG_FILE, " quick select used:\n");
if ((quick_type == QUICK_SELECT_I::QS_TYPE_RANGE) || tab->select->quick->dbug_dump(18, false);
(quick_type == QUICK_SELECT_I::QS_TYPE_RANGE_DESC))
{
fprintf(DBUG_FILE,
" quick select used on key %s, length: %d\n",
form->key_info[tab->select->quick->index].name,
tab->select->quick->max_used_key_length);
}
else if (quick_type == QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
{
QUICK_INDEX_MERGE_SELECT *quick_imerge=
(QUICK_INDEX_MERGE_SELECT*)tab->select->quick;
QUICK_RANGE_SELECT *quick;
fprintf(DBUG_FILE,
" index_merge quick select used\n");
List_iterator_fast<QUICK_RANGE_SELECT> it(quick_imerge->quick_selects);
while ((quick = it++))
{
fprintf(DBUG_FILE,
" range quick select: key %s, length: %d\n",
form->key_info[quick->index].name,
quick->max_used_key_length);
}
}
else
{
fprintf(DBUG_FILE,
" quick select of unknown nature used\n");
}
} }
else else
VOID(fputs(" select used\n",DBUG_FILE)); VOID(fputs(" select used\n",DBUG_FILE));
......
...@@ -175,18 +175,11 @@ int mysql_update(THD *thd, ...@@ -175,18 +175,11 @@ int mysql_update(THD *thd,
} }
init_ftfuncs(thd, &thd->lex->select_lex, 1); init_ftfuncs(thd, &thd->lex->select_lex, 1);
/* Check if we are modifying a key that we are used to search with */ /* Check if we are modifying a key that we are used to search with */
if (select && select->quick) if (select && select->quick)
{ {
if (select->quick->get_type() != QUICK_SELECT_I::QS_TYPE_INDEX_MERGE) used_key_is_modified= (!select->quick->unique_key_range() &&
{ select->quick->check_if_keys_used(&fields));
used_index= select->quick->index;
used_key_is_modified= (!select->quick->unique_key_range() &&
check_if_key_used(table,used_index,fields));
}
else
{
used_key_is_modified= true;
}
} }
else if ((used_index=table->file->key_used_on_scan) < MAX_KEY) else if ((used_index=table->file->key_used_on_scan) < MAX_KEY)
used_key_is_modified=check_if_key_used(table, used_index, fields); used_key_is_modified=check_if_key_used(table, used_index, fields);
...@@ -245,7 +238,7 @@ int mysql_update(THD *thd, ...@@ -245,7 +238,7 @@ int mysql_update(THD *thd,
if (open_cached_file(&tempfile, mysql_tmpdir,TEMP_PREFIX, if (open_cached_file(&tempfile, mysql_tmpdir,TEMP_PREFIX,
DISK_BUFFER_SIZE, MYF(MY_WME))) DISK_BUFFER_SIZE, MYF(MY_WME)))
goto err; goto err;
/* If quick select is used, initialize it before retrieving rows. */ /* If quick select is used, initialize it before retrieving rows. */
if (select && select->quick && select->quick->reset()) if (select && select->quick && select->quick->reset())
goto err; goto err;
...@@ -305,6 +298,9 @@ int mysql_update(THD *thd, ...@@ -305,6 +298,9 @@ int mysql_update(THD *thd,
if (handle_duplicates == DUP_IGNORE) if (handle_duplicates == DUP_IGNORE)
table->file->extra(HA_EXTRA_IGNORE_DUP_KEY); table->file->extra(HA_EXTRA_IGNORE_DUP_KEY);
if (select && select->quick && select->quick->reset())
goto err;
init_read_record(&info,thd,table,select,0,1); init_read_record(&info,thd,table,select,0,1);
updated= found= 0; updated= found= 0;
...@@ -792,26 +788,7 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields) ...@@ -792,26 +788,7 @@ static bool safe_update_on_fly(JOIN_TAB *join_tab, List<Item> *fields)
case JT_ALL: case JT_ALL:
/* If range search on index */ /* If range search on index */
if (join_tab->quick) if (join_tab->quick)
{ return !join_tab->quick->check_if_keys_used(fields);
if (join_tab->quick->get_type() != QUICK_SELECT_I::QS_TYPE_INDEX_MERGE)
{
return !check_if_key_used(table,join_tab->quick->index,*fields);
}
else
{
QUICK_INDEX_MERGE_SELECT *qsel_imerge=
(QUICK_INDEX_MERGE_SELECT*)(join_tab->quick);
List_iterator_fast<QUICK_RANGE_SELECT> it(qsel_imerge->quick_selects);
QUICK_RANGE_SELECT *quick;
while ((quick= it++))
{
if (check_if_key_used(table, quick->index, *fields))
return 0;
}
return 1;
}
}
/* If scanning in clustered key */ /* If scanning in clustered key */
if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) && if ((table->file->table_flags() & HA_PRIMARY_KEY_IN_READ_INDEX) &&
table->primary_key < MAX_KEY) table->primary_key < MAX_KEY)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment