diff --git a/mysql-test/main/opt_context_store_stats.result b/mysql-test/main/opt_context_store_stats.result index 99cc4b02a98a6..b1c3dd717e306 100644 --- a/mysql-test/main/opt_context_store_stats.result +++ b/mysql-test/main/opt_context_store_stats.result @@ -646,4 +646,214 @@ index_name ranges num_rows max_index_blocks max_row_blocks a ["(10) <= (a) <= (10)"] 49 1 11 # == End of optimizer context drop table t1; +# +# MDEV-40533: unprintable gis ranges in trace and context +# +create table t1 ( +id int, +g1 geometry not null, +g2 geometry, +key k_prefix (g1(5)), +key k_full (g1(200)), +key k_null (g2(200)) +) engine=myisam; +insert into t1 select seq, point(seq, seq), point(seq, seq) from seq_1_to_5; +insert into t1 values (6, point(6,6), NULL); +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status OK +# The index holds the whole value: it is printed in the WKT format +explain +select id from t1 force index (k_full) where g1=point(2,2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref k_full k_full 202 const 1 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t1 6 NULL NULL +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +k_full ["(POINT(2 2)) <= (g1) <= (POINT(2 2))"] 1 1 1 +# == End of optimizer context +# multiple ranges +explain +select id from t1 force index (k_full) +where g1=point(3,3) or g1=linestring(point(1,1), point(2,2)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range k_full k_full 202 NULL 2 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t1 6 NULL NULL +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +k_full [ + "(POINT(3 3)) <= (g1) <= (POINT(3 3))", + "(LINESTRING(1 1,2 2)) <= (g1) <= (LINESTRING(1 1,2 2))" + ] 2 2 1 +# == End of optimizer context +# NULL values and multiple ranges +explain +select id from t1 force index (k_null) +where g2=point(3,3) or g2 is null or g2=linestring(point(1,1), point(2,2)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range k_null k_null 203 NULL 3 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t1 6 NULL NULL +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +k_null [ + "(NULL) <= (g2) <= (NULL)", + "(POINT(3 3)) <= (g2) <= (POINT(3 3))", + "(LINESTRING(1 1,2 2)) <= (g2) <= (LINESTRING(1 1,2 2))" + ] 3 3 1 +# == End of optimizer context +# The index holds only a prefix of the value, so it cannot be converted +# to WKT. It is printed as a binary string, like any other BLOB prefix. +explain +select id from t1 force index (k_prefix) where g1=point(2,2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref k_prefix k_prefix 7 const 6 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t1 6 NULL NULL +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +k_prefix ["(\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01) <= (g1) <= (\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01)"] 6 1 1 +# == End of optimizer context +drop table t1; +# +# A SPATIAL index stores the MBR of the value. It is printed as the +# WKT of the MBR together with the spatial relation the range uses. +# +create table t2 ( +id int, +g geometry not null, +spatial key (g) +) engine=myisam; +insert into t2 select seq, point(seq, seq) from seq_1_to_20; +insert into t2 values +(21, geomfromtext('LINESTRING(0 0, 5 8)')), +(22, geomfromtext('POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))')); +analyze table t2; +Table Op Msg_type Msg_text +test.t2 analyze status OK +# mbrcontains(value, column) +explain +select id from t2 where mbrcontains(geomfromtext('POLYGON((0 0,0 3,3 3,3 0,0 0))'), g); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRWITHIN (POLYGON((0 0,3 0,3 3,0 3,0 0)))"] 4 1 1 +# == End of optimizer context +# mbrwithin(column, value) +explain +select id from t2 where mbrwithin(g, geomfromtext('LINESTRING(1 1, 9 12)')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 9 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRWITHIN (POLYGON((1 1,9 1,9 12,1 12,1 1)))"] 9 1 1 +# == End of optimizer context +# The other spatial relations +explain +select id from t2 where mbrcontains(g, geomfromtext('POINT(2 2)')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 3 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRCONTAINS (POLYGON((2 2,2 2,2 2,2 2,2 2)))"] 3 1 1 +# == End of optimizer context +explain +select id from t2 where mbrequals(g, geomfromtext('POINT(7 7)')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 1 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBREQUALS (POLYGON((7 7,7 7,7 7,7 7,7 7)))"] 1 1 1 +# == End of optimizer context +explain +select id from t2 where mbrintersects(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRINTERSECTS (POLYGON((0 0,2 0,2 2,0 2,0 0)))"] 4 1 1 +# == End of optimizer context +# mbrtouches/mbroverlaps all use the mbrintersects relation +explain +select id from t2 where mbrtouches(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRINTERSECTS (POLYGON((0 0,2 0,2 2,0 2,0 0)))"] 4 1 1 +# == End of optimizer context +explain +select id from t2 where mbroverlaps(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRINTERSECTS (POLYGON((0 0,2 0,2 2,0 2,0 0)))"] 4 1 1 +# == End of optimizer context +explain +select id from t2 where mbrdisjoint(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL g NULL NULL NULL 22 Using where +# == Optimizer Context +# === Tables +# Tables in the context +table_name file_stat_records index_name rec_per_key +test.t2 22 g ["22"] +# === Range accesses +index_name ranges num_rows max_index_blocks max_row_blocks +g ["(g) MBRDISJOINT (POLYGON((0 0,2 0,2 2,0 2,0 0)))"] 2147483647 0 0 +Warnings: +Warning 1264 Out of range value for column 'num_rows' at row 1 +# == End of optimizer context +drop table t2; # End of 13.1 tests diff --git a/mysql-test/main/opt_context_store_stats.test b/mysql-test/main/opt_context_store_stats.test index cce698d51692a..981eed702ab36 100644 --- a/mysql-test/main/opt_context_store_stats.test +++ b/mysql-test/main/opt_context_store_stats.test @@ -470,4 +470,100 @@ select * from t1 partition (p1) where a=10; drop table t1; +--echo # +--echo # MDEV-40533: unprintable gis ranges in trace and context +--echo # +create table t1 ( + id int, + g1 geometry not null, + g2 geometry, + key k_prefix (g1(5)), + key k_full (g1(200)), + key k_null (g2(200)) +) engine=myisam; + +insert into t1 select seq, point(seq, seq), point(seq, seq) from seq_1_to_5; +insert into t1 values (6, point(6,6), NULL); +analyze table t1; + +--echo # The index holds the whole value: it is printed in the WKT format +explain +select id from t1 force index (k_full) where g1=point(2,2); +--source include/opt_context_list_tables_and_ranges.inc + +--echo # multiple ranges +explain +select id from t1 force index (k_full) +where g1=point(3,3) or g1=linestring(point(1,1), point(2,2)); +--source include/opt_context_list_tables_and_ranges.inc + +--echo # NULL values and multiple ranges +explain +select id from t1 force index (k_null) +where g2=point(3,3) or g2 is null or g2=linestring(point(1,1), point(2,2)); +--source include/opt_context_list_tables_and_ranges.inc + +--echo # The index holds only a prefix of the value, so it cannot be converted +--echo # to WKT. It is printed as a binary string, like any other BLOB prefix. +explain +select id from t1 force index (k_prefix) where g1=point(2,2); +--source include/opt_context_list_tables_and_ranges.inc + +drop table t1; + +--echo # +--echo # A SPATIAL index stores the MBR of the value. It is printed as the +--echo # WKT of the MBR together with the spatial relation the range uses. +--echo # + +create table t2 ( + id int, + g geometry not null, + spatial key (g) +) engine=myisam; + +insert into t2 select seq, point(seq, seq) from seq_1_to_20; +insert into t2 values + (21, geomfromtext('LINESTRING(0 0, 5 8)')), + (22, geomfromtext('POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))')); +analyze table t2; + +--echo # mbrcontains(value, column) +explain +select id from t2 where mbrcontains(geomfromtext('POLYGON((0 0,0 3,3 3,3 0,0 0))'), g); +--source include/opt_context_list_tables_and_ranges.inc + +--echo # mbrwithin(column, value) +explain +select id from t2 where mbrwithin(g, geomfromtext('LINESTRING(1 1, 9 12)')); +--source include/opt_context_list_tables_and_ranges.inc + +--echo # The other spatial relations +explain +select id from t2 where mbrcontains(g, geomfromtext('POINT(2 2)')); +--source include/opt_context_list_tables_and_ranges.inc + +explain +select id from t2 where mbrequals(g, geomfromtext('POINT(7 7)')); +--source include/opt_context_list_tables_and_ranges.inc + +explain +select id from t2 where mbrintersects(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +--source include/opt_context_list_tables_and_ranges.inc + +--echo # mbrtouches/mbroverlaps all use the mbrintersects relation +explain +select id from t2 where mbrtouches(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +--source include/opt_context_list_tables_and_ranges.inc + +explain +select id from t2 where mbroverlaps(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +--source include/opt_context_list_tables_and_ranges.inc + +explain +select id from t2 where mbrdisjoint(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +--source include/opt_context_list_tables_and_ranges.inc + +drop table t2; + --echo # End of 13.1 tests diff --git a/mysql-test/main/opt_trace.result b/mysql-test/main/opt_trace.result index 6de4d363f02c9..f2f6a6135fd35 100644 --- a/mysql-test/main/opt_trace.result +++ b/mysql-test/main/opt_trace.result @@ -13845,6 +13845,305 @@ select json_valid(@trace); json_valid(@trace) 1 drop table t1; +# +# MDEV-40533: unprintable gis ranges in trace and context +# +create table t1 ( +id int, +g1 geometry not null, +g2 geometry, +key k_prefix (g1(5)), +key k_full (g1(200)), +key k_null (g2(200)) +) engine=myisam; +insert into t1 select seq, point(seq, seq), point(seq, seq) from seq_1_to_5; +insert into t1 values (6, point(6,6), NULL); +analyze table t1; +Table Op Msg_type Msg_text +test.t1 analyze status Engine-independent statistics collected +test.t1 analyze status OK +# The index holds the whole value: it is printed in the WKT format +explain +select id from t1 force index (k_full) where g1=point(2,2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref k_full k_full 202 const 1 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "k_full", + "ranges": + ["(POINT(2 2)) <= (g1) <= (POINT(2 2))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 1, + "cost": 0.002574553, + "chosen": true + } + ] +] +# multiple ranges +explain +select id from t1 force index (k_full) +where g1=point(3,3) or g1=linestring(point(1,1), point(2,2)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range k_full k_full 202 NULL 2 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "k_full", + "ranges": + [ + "(POINT(3 3)) <= (g1) <= (POINT(3 3))", + "(LINESTRING(1 1,2 2)) <= (g1) <= (LINESTRING(1 1,2 2))" + ], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 2, + "cost": 0.004394164, + "chosen": true + } + ] +] +# NULL values and multiple ranges +explain +select id from t1 force index (k_null) +where g2=point(3,3) or g2 is null or g2=linestring(point(1,1), point(2,2)); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 range k_null k_null 203 NULL 3 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "k_null", + "ranges": + [ + "(NULL) <= (g2) <= (NULL)", + "(POINT(3 3)) <= (g2) <= (POINT(3 3))", + "(LINESTRING(1 1,2 2)) <= (g2) <= (LINESTRING(1 1,2 2))" + ], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 3, + "cost": 0.006213775, + "chosen": true + } + ] +] +# The index holds only a prefix of the value, so it cannot be converted +# to WKT. It is printed as a binary string, like any other BLOB prefix. +explain +select id from t1 force index (k_prefix) where g1=point(2,2); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t1 ref k_prefix k_prefix 7 const 6 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "k_prefix", + "ranges": + ["(\\x00\\x00\\x00\\x00\\x01) <= (g1) <= (\\x00\\x00\\x00\\x00\\x01)"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 6, + "cost": 0.008743898, + "chosen": true + } + ] +] +drop table t1; +# +# A SPATIAL index stores the MBR of the value. It is printed as the +# WKT of the MBR together with the spatial relation the range uses. +# +create table t2 ( +id int, +g geometry not null, +spatial key (g) +) engine=myisam; +insert into t2 select seq, point(seq, seq) from seq_1_to_20; +insert into t2 values +(21, geomfromtext('LINESTRING(0 0, 5 8)')), +(22, geomfromtext('POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))')); +analyze table t2; +Table Op Msg_type Msg_text +test.t2 analyze status Engine-independent statistics collected +test.t2 analyze status OK +# mbrcontains(value, column) +explain +select id from t2 where mbrcontains(geomfromtext('POLYGON((0 0,0 3,3 3,3 0,0 0))'), g); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBRWITHIN (POLYGON((0 0,3 0,3 3,0 3,0 0)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 4, + "cost": 0.00627616, + "chosen": true + } + ] +] +# mbrwithin(column, value) +explain +select id from t2 where mbrwithin(g, geomfromtext('LINESTRING(1 1, 9 12)')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 9 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBRWITHIN (POLYGON((1 1,9 1,9 12,1 12,1 1)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 9, + "cost": 0.012445505, + "chosen": true + } + ] +] +# The other spatial relations +explain +select id from t2 where mbrcontains(g, geomfromtext('POINT(2 2)')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 3 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBRCONTAINS (POLYGON((2 2,2 2,2 2,2 2,2 2)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 3, + "cost": 0.005042291, + "chosen": true + } + ] +] +explain +select id from t2 where mbrequals(g, geomfromtext('POINT(7 7)')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 1 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBREQUALS (POLYGON((7 7,7 7,7 7,7 7,7 7)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 1, + "cost": 0.002574553, + "chosen": true + } + ] +] +explain +select id from t2 where mbrintersects(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBRINTERSECTS (POLYGON((0 0,2 0,2 2,0 2,0 0)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 4, + "cost": 0.00627616, + "chosen": true + } + ] +] +# mbrtouches/mbroverlaps all use the mbrintersects relation +explain +select id from t2 where mbrtouches(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBRINTERSECTS (POLYGON((0 0,2 0,2 2,0 2,0 0)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 4, + "cost": 0.00627616, + "chosen": true + } + ] +] +explain +select id from t2 where mbroverlaps(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 range g g 34 NULL 4 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g", + "ranges": + ["(g) MBRINTERSECTS (POLYGON((0 0,2 0,2 2,0 2,0 0)))"], + "rowid_ordered": false, + "using_mrr": false, + "index_only": false, + "rows": 4, + "cost": 0.00627616, + "chosen": true + } + ] +] +explain +select id from t2 where mbrdisjoint(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +id select_type table type possible_keys key key_len ref rows Extra +1 SIMPLE t2 ALL g NULL NULL NULL 22 Using where +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; +json_detailed(json_extract(trace, '$**.range_scan_alternatives')) +[ + [ + { + "index": "g" + } + ] +] +drop table t2; +# End of 13.1 tests set @@optimizer_switch= @save_optimizer_switch; set @@use_stat_tables= @save_use_stat_tables; set @@histogram_size= @save_histogram_size; diff --git a/mysql-test/main/opt_trace.test b/mysql-test/main/opt_trace.test index ce811547f3f1d..08a8ab835c209 100644 --- a/mysql-test/main/opt_trace.test +++ b/mysql-test/main/opt_trace.test @@ -1364,6 +1364,104 @@ select json_valid(@trace); drop table t1; +--echo # +--echo # MDEV-40533: unprintable gis ranges in trace and context +--echo # +create table t1 ( + id int, + g1 geometry not null, + g2 geometry, + key k_prefix (g1(5)), + key k_full (g1(200)), + key k_null (g2(200)) +) engine=myisam; + +insert into t1 select seq, point(seq, seq), point(seq, seq) from seq_1_to_5; +insert into t1 values (6, point(6,6), NULL); +analyze table t1; + +--echo # The index holds the whole value: it is printed in the WKT format +explain +select id from t1 force index (k_full) where g1=point(2,2); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +--echo # multiple ranges +explain +select id from t1 force index (k_full) +where g1=point(3,3) or g1=linestring(point(1,1), point(2,2)); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +--echo # NULL values and multiple ranges +explain +select id from t1 force index (k_null) +where g2=point(3,3) or g2 is null or g2=linestring(point(1,1), point(2,2)); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +--echo # The index holds only a prefix of the value, so it cannot be converted +--echo # to WKT. It is printed as a binary string, like any other BLOB prefix. +explain +select id from t1 force index (k_prefix) where g1=point(2,2); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +drop table t1; + +--echo # +--echo # A SPATIAL index stores the MBR of the value. It is printed as the +--echo # WKT of the MBR together with the spatial relation the range uses. +--echo # + +create table t2 ( + id int, + g geometry not null, + spatial key (g) +) engine=myisam; + +insert into t2 select seq, point(seq, seq) from seq_1_to_20; +insert into t2 values + (21, geomfromtext('LINESTRING(0 0, 5 8)')), + (22, geomfromtext('POLYGON((0 0, 0 3, 3 3, 3 0, 0 0))')); +analyze table t2; + +--echo # mbrcontains(value, column) +explain +select id from t2 where mbrcontains(geomfromtext('POLYGON((0 0,0 3,3 3,3 0,0 0))'), g); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +--echo # mbrwithin(column, value) +explain +select id from t2 where mbrwithin(g, geomfromtext('LINESTRING(1 1, 9 12)')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +--echo # The other spatial relations +explain +select id from t2 where mbrcontains(g, geomfromtext('POINT(2 2)')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +explain +select id from t2 where mbrequals(g, geomfromtext('POINT(7 7)')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +explain +select id from t2 where mbrintersects(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +--echo # mbrtouches/mbroverlaps all use the mbrintersects relation +explain +select id from t2 where mbrtouches(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +explain +select id from t2 where mbroverlaps(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +explain +select id from t2 where mbrdisjoint(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))')); +select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) from information_schema.optimizer_trace; + +drop table t2; + +--echo # End of 13.1 tests + set @@optimizer_switch= @save_optimizer_switch; set @@use_stat_tables= @save_use_stat_tables; set @@histogram_size= @save_histogram_size; diff --git a/sql/field.cc b/sql/field.cc index 590253ac49bdd..680a337b77177 100644 --- a/sql/field.cc +++ b/sql/field.cc @@ -11833,11 +11833,14 @@ void Field_blob::print_key_value(String *out, uint32 length) key value of the key length Length of field in bytes, excluding NULL flag and length bytes + image_type Form in which the value is stored in the key: + itRAW for regular indexes, itMBR for SPATIAL indexes */ void -Field::print_key_part_value(String *out, const uchar* key, uint32 length) +Field::print_key_part_value(String *out, const uchar* key, uint32 length, + imagetype image_type) { StringBuffer<128> tmp(system_charset_info); uint null_byte= 0; diff --git a/sql/field.h b/sql/field.h index 2797cad5da427..e09619b76c1d9 100644 --- a/sql/field.h +++ b/sql/field.h @@ -1759,7 +1759,8 @@ class Field: public Value_source bool set_warning(Sql_condition::enum_warning_level, unsigned int code, int cuted_increment, ulong current_row=0) const; virtual void print_key_value(String *out, uint32 length); - void print_key_part_value(String *out, const uchar *key, uint32 length); + virtual void print_key_part_value(String *out, const uchar *key, uint32 length, + imagetype image_type); void print_key_value_binary(String *out, const uchar* key, uint32 length); void raise_note_cannot_use_key_part(THD *thd, uint keynr, uint part, const LEX_CSTRING &op, diff --git a/sql/opt_context_store_replay.cc b/sql/opt_context_store_replay.cc index c89933fcc71c9..3b79b86154e48 100644 --- a/sql/opt_context_store_replay.cc +++ b/sql/opt_context_store_replay.cc @@ -1130,8 +1130,12 @@ void Optimizer_context_recorder::record_records_in_range( rec_in_range_ctx->keynr= keynr; String min_key; String max_key; - print_key_value(&min_key, key_part, min_range->key, min_range->length); - print_key_value(&max_key, key_part, max_range->key, max_range->length); + Field::imagetype image_type= + Field::image_type(tbl->key_info[keynr].algorithm); + print_key_value(&min_key, key_part, min_range->key, min_range->length, + image_type); + print_key_value(&max_key, key_part, max_range->key, max_range->length, + image_type); if (!(rec_in_range_ctx->min_key= strdup_root(mem_root, &min_key))) return; // OOM @@ -1965,8 +1969,12 @@ bool Optimizer_context_replay::infuse_records_in_range( String min_key; String max_key; String tbl_name; - print_key_value(&min_key, key_part, min_range->key, min_range->length); - print_key_value(&max_key, key_part, max_range->key, max_range->length); + Field::imagetype image_type= + Field::image_type(tbl->key_info[keynr].algorithm); + print_key_value(&min_key, key_part, min_range->key, min_range->length, + image_type); + print_key_value(&max_key, key_part, max_range->key, max_range->length, + image_type); append_base_table_name(tbl, &tbl_name); if (table_context_for_replay *tbl_ctx= diff --git a/sql/opt_range.cc b/sql/opt_range.cc index ab003b70c5afd..822a0736ad198 100644 --- a/sql/opt_range.cc +++ b/sql/opt_range.cc @@ -17595,6 +17595,43 @@ static void print_max_range_operator(String *out, const ha_rkey_function flag) out->append(STRING_WITH_LEN(" ? ")); } +/* + @brief Print the spatial relation used by a range over a SPATIAL index + + @detail + The relation is printed the way it reads with the indexed column on the + left side, that is, the range + (g) mbrwithin (POLYGON(...)) + reads rows whose MBR is within the MBR of the printed value. + Note that mbrtouches/mbrcrosses/mbroverlaps all use the same + HA_READ_MBR_INTERSECT relation, so they are all printed as mbrintersects. +*/ + +static void print_mbr_range_operator(String *out, const ha_rkey_function flag) +{ + switch (flag) + { + case HA_READ_MBR_CONTAIN: + out->append(STRING_WITH_LEN(" MBRWITHIN ")); + break; + case HA_READ_MBR_WITHIN: + out->append(STRING_WITH_LEN(" MBRCONTAINS ")); + break; + case HA_READ_MBR_INTERSECT: + out->append(STRING_WITH_LEN(" MBRINTERSECTS ")); + break; + case HA_READ_MBR_DISJOINT: + out->append(STRING_WITH_LEN(" MBRDISJOINT ")); + break; + case HA_READ_MBR_EQUAL: + out->append(STRING_WITH_LEN(" MBREQUALS ")); + break; + default: + out->append(STRING_WITH_LEN(" ? ")); + break; + } +} + void print_range(String *out, const KEY_PART_INFO *key_part, KEY_MULTI_RANGE *range, uint n_key_parts) { @@ -17608,21 +17645,22 @@ void print_range(String *out, const KEY_PART_INFO *key_part, if (flag & GEOM_FLAG) { /* - The flags of GEOM ranges do not work the same way as for other - range types, so printing "col < some_geom" doesn't make sense. - Just print the column name, not operator. + The flags of GEOM ranges do not work the same way as for other range + types: there is no min/max bound, the range is the set of rows whose + MBR has the given spatial relation with the MBR of the value. + Print it as "col mbr_relation value". */ print_keyparts_name(out, key_part, n_key_parts, keypart_map); - out->append(STRING_WITH_LEN(" ")); + print_mbr_range_operator(out, range->start_key.flag); print_key_value(out, key_part, range->start_key.key, - range->start_key.length); + range->start_key.length, Field::itMBR); return; } if (range->start_key.length) { print_key_value(out, key_part, range->start_key.key, - range->start_key.length); + range->start_key.length, Field::itRAW); print_min_range_operator(out, range->start_key.flag); } @@ -17631,8 +17669,8 @@ void print_range(String *out, const KEY_PART_INFO *key_part, if (range->end_key.length) { print_max_range_operator(out, range->end_key.flag); - print_key_value(out, key_part, range->end_key.key, - range->end_key.length); + print_key_value(out, key_part, range->end_key.key, range->end_key.length, + Field::itRAW); } } @@ -17656,7 +17694,8 @@ void print_range_for_non_indexed_field(String *out, Field *field, if (range->start_key.length) { - field->print_key_part_value(out, range->start_key.key, field->key_length()); + field->print_key_part_value(out, range->start_key.key, field->key_length(), + Field::itRAW); print_min_range_operator(out, range->start_key.flag); } @@ -17665,7 +17704,8 @@ void print_range_for_non_indexed_field(String *out, Field *field, if (range->end_key.length) { print_max_range_operator(out, range->end_key.flag); - field->print_key_part_value(out, range->end_key.key, field->key_length()); + field->print_key_part_value(out, range->end_key.key, field->key_length(), + Field::itRAW); } dbug_tmp_restore_column_maps(&table->read_set, &table->write_set, old_sets); } @@ -17703,10 +17743,13 @@ static void trace_ranges(Json_writer_array *range_trace, PARAM *param, @param[in] key_part Index components description @param[in] key Key tuple @param[in] used_length length of the key tuple + @param[in] image_type Form in which the key stores the column values: + itRAW for regular indexes, itMBR for SPATIAL ones */ void print_key_value(String *out, const KEY_PART_INFO *key_part, - const uchar *key, uint used_length) + const uchar *key, uint used_length, + Field::imagetype image_type) { out->append(STRING_WITH_LEN("(")); Field *field= key_part->field; @@ -17722,7 +17765,7 @@ void print_key_value(String *out, const KEY_PART_INFO *key_part, field= key_part->field; store_length= key_part->store_length; - field->print_key_part_value(out, key, key_part->length); + field->print_key_part_value(out, key, key_part->length, image_type); if (key + store_length < key_end) out->append(STRING_WITH_LEN(",")); diff --git a/sql/opt_range.h b/sql/opt_range.h index 50cbc27c67f28..343d49f10f3d6 100644 --- a/sql/opt_range.h +++ b/sql/opt_range.h @@ -2044,7 +2044,8 @@ void print_range(String *out, const KEY_PART_INFO *key_part, KEY_MULTI_RANGE *range, uint n_key_parts); void print_key_value(String *out, const KEY_PART_INFO *key_part, - const uchar *key, uint used_length); + const uchar *key, uint used_length, + Field::imagetype image_type); #ifdef WITH_PARTITION_STORAGE_ENGINE bool prune_partitions(THD *thd, TABLE *table, Item *pprune_cond); diff --git a/sql/sql_type_geom.cc b/sql/sql_type_geom.cc index d18c6d6194d29..e7b10a4344014 100644 --- a/sql/sql_type_geom.cc +++ b/sql/sql_type_geom.cc @@ -1001,6 +1001,109 @@ uint Field_geom::get_key_image(uchar *buff,uint length, const uchar *ptr_arg, return Field_blob::get_key_image_itRAW(ptr_arg, buff, length); } +/* + @brief + Print the geometry value stored in this field in the WKT format. + + @detail + This is used to print index key values in the optimizer trace and in the + Optimizer Context. The value comes from an index, so it may be just a + prefix of the original column value (for indexes like KEY(g(10))). + Such a truncated value cannot be converted to WKT, print it as a + binary string then. +*/ + +void Field_geom::print_key_value(String *out, uint32 length) +{ + Geometry_buffer buffer; + Geometry *geom; + const char *dummy; + const uchar *wkb= get_ptr(); + uint32 wkb_length= get_length(); + StringBuffer<128> wkt(&my_charset_latin1); + + if ((geom= Geometry::construct(&buffer, (const char *) wkb, wkb_length)) && + geom->is_binary_valid() && + !geom->as_wkt(&wkt, &dummy)) + out->append(wkt.ptr(), wkt.length(), wkt.charset()); + else + print_key_value_binary(out, wkb, wkb_length); +} + + +/* + @brief + Print the value of a key part over a GEOMETRY column. + + @detail + SPATIAL indexes do not store the column value. They store its MBR + (Minimum Bounding Rectangle) instead, as four doubles: + xmin, xmax, ymin, ymax + Print the MBR as a WKT POLYGON, the same way ST_Envelope() would + return it. + For the other index types the key holds the value (or its prefix), + which is printed by Field_geom::print_key_value(). +*/ + +void Field_geom::print_key_part_value(String *out, const uchar *key, + uint32 length, imagetype image_type) +{ + if (image_type != itMBR) + { + Field_blob::print_key_part_value(out, key, length, image_type); + return; + } + + if (real_maybe_null()) + { + /* + SPATIAL keys do not support NULL, but be graceful here as this is + only used for printing. + */ + if (*key) + { + out->append(NULL_clex_str); + return; + } + key++; // Skip the null byte + } + + if (length < SIZEOF_STORED_DOUBLE * 4) + { + /* + The key part is too short to hold a full MBR (four doubles). This should + not happen for a SPATIAL index, but be graceful and print whatever bytes + are there as a binary string instead of reading past the key. + TODO: Should we assert? + */ + print_key_value_binary(out, key, length); + return; + } + + double xmin, xmax, ymin, ymax; + float8get(xmin, key); + float8get(xmax, key + SIZEOF_STORED_DOUBLE); + float8get(ymin, key + SIZEOF_STORED_DOUBLE * 2); + float8get(ymax, key + SIZEOF_STORED_DOUBLE * 3); + + if (out->reserve(MY_GCVT_MAX_FIELD_WIDTH * 10 + 32)) + return; // Out of memory + + out->qs_append(STRING_WITH_LEN("POLYGON((")); + const double points[5][2]= {{xmin, ymin}, {xmax, ymin}, {xmax, ymax}, + {xmin, ymax}, {xmin, ymin}}; + for (uint i= 0; i < array_elements(points); i++) + { + if (i) + out->qs_append(','); + out->qs_append(points[i][0]); + out->qs_append(' '); + out->qs_append(points[i][1]); + } + out->qs_append(STRING_WITH_LEN("))")); +} + + Binlog_type_info Field_geom::binlog_type_info() const { DBUG_ASSERT(Field_geom::type() == binlog_type()); diff --git a/sql/sql_type_geom.h b/sql/sql_type_geom.h index 9dc4a55edaa46..d0c31c632e249 100644 --- a/sql/sql_type_geom.h +++ b/sql/sql_type_geom.h @@ -463,10 +463,9 @@ class Field_geom :public Field_blob, bool load_data_set_null(THD *thd) override; bool load_data_set_no_data(THD *thd, bool fixed_format) override; - void print_key_value(String *out, uint32 length) override - { - out->append(STRING_WITH_LEN("unprintable_geometry_value")); - } + void print_key_value(String *out, uint32 length) override; + void print_key_part_value(String *out, const uchar *key, uint32 length, + imagetype image_type) override; Binlog_type_info binlog_type_info() const override; };