1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-40553 Print GIS ranges in optimizer trace and context

Ranges built over GIS (geometry) columns could not be printed in the
optimizer trace or the recorded optimizer context: Field_geom printed
every key value as the placeholder "unprintable_geometry_value",
regardless of whether the index stored the column's raw value (or a
prefix of it) or, for a SPATIAL index, its MBR (Minimum Bounding
Rectangle).

Field::print_key_part_value() now takes an image_type argument (see
Field::image_type()) that says which of the two the key holds. For a
SPATIAL index (image_type itMBR), Field_geom::print_key_part_value()
decodes the four doubles the key stores and prints them as a WKT
POLYGON. For every other index (image_type itRAW), the key holds the
value or a prefix of it, and it now prints in binary form, the same
way Field_blob does, instead of the placeholder.

print_mbr_range_operator() prints the spatial relation a GEOM range
carries (MBRWITHIN, MBRCONTAINS, MBRINTERSECTS, MBRDISJOINT,
MBREQUALS), inverted where needed so the indexed column reads on the
left. print_range() and print_key_value() thread the new image_type
argument through to Field_geom::print_key_part_value().
This commit is contained in:
bsrikanth-mariadb
2026-10-05 14:42:12 +05:30
committed by Sergei Petrunia
parent 67a5f3034f
commit ef384ed95d
16 changed files with 1159 additions and 31 deletions

View File

@@ -26,11 +26,13 @@ select *from json_table(
--echo # === Range accesses
set @list_ranges=
(select JSON_DETAILED(JSON_EXTRACT(@opt_context, '$**.multi_range_read_info_const_calls')));
# num_rows is bigint unsigned because MBRDISJOINT records HA_POS_ERROR
# (18446744073709551615) there.
select *from json_table(
@list_ranges,
'$[*][*]' columns(index_name text path '$.index_name',
ranges json path '$.ranges',
num_rows int path '$.num_rows',
num_rows bigint unsigned path '$.num_rows',
max_index_blocks int path '$.max_index_blocks',
max_row_blocks int path '$.max_row_blocks'
)) as jt;

View File

@@ -0,0 +1,121 @@
#
# SUMMARY
# MDEV-40553: printing of GIS ranges, shared between opt_trace.test
# (dumps range_scan_alternatives from the optimizer trace) and
# opt_context_store_stats.test (dumps the recorded optimizer context).
# Both files run the exact same queries over the exact same fixture;
# only the way each range is dumped differs, see
# include/opt_gis_range_print_dump.inc.
#
# PARAMETERS
# $opt_gis_range_print_use_trace See include/opt_gis_range_print_dump.inc
#
# EXAMPLE
# opt_trace.test, opt_context_store_stats.test
#
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 k_full prefix is long enough to hold every value this test
--echo # inserts or compares against (points and short linestrings),
--echo # so the index holds the whole value here. It is printed as a binary
--echo # string, like any other BLOB prefix.
explain
select id from t1 force index (k_full) where g1=point(2,2);
--source include/opt_gis_range_print_dump.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_gis_range_print_dump.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_gis_range_print_dump.inc
--echo # The index holds only a prefix of the value.
--echo # 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_gis_range_print_dump.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_gis_range_print_dump.inc
--echo # mbrwithin(column, value)
explain
select id from t2 where mbrwithin(g, geomfromtext('LINESTRING(1 1, 9 12)'));
--source include/opt_gis_range_print_dump.inc
--echo # The other spatial relations
explain
select id from t2 where mbrcontains(g, geomfromtext('POINT(2 2)'));
--source include/opt_gis_range_print_dump.inc
explain
select id from t2 where mbrequals(g, geomfromtext('POINT(7 7)'));
--source include/opt_gis_range_print_dump.inc
explain
select id from t2 where mbrintersects(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))'));
--source include/opt_gis_range_print_dump.inc
--echo # touches()/crosses()/overlaps() (and their MBRxxx aliases, where one
--echo # exists) 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_gis_range_print_dump.inc
explain
select id from t2 where crosses(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))'));
--source include/opt_gis_range_print_dump.inc
explain
select id from t2 where mbroverlaps(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))'));
--source include/opt_gis_range_print_dump.inc
--echo # MBRDISJOINT: the handler's records_in_range() cannot estimate this
--echo # relation and returns HA_POS_ERROR, so the range optimizer discards
--echo # this index as a candidate before it ever traces its ranges -- the
--echo # trace shows the index with no further detail. The range text is
--echo # still produced (and recorded) when the optimizer separately asks
--echo # the handler for a cost estimate, see multi_range_read_info_const().
explain
select id from t2 where mbrdisjoint(g, geomfromtext('POLYGON((0 0,0 2,2 2,2 0,0 0))'));
--source include/opt_gis_range_print_dump.inc
drop table t2;

View File

@@ -0,0 +1,22 @@
#
# SUMMARY
# Print the range(s) built for the query just run, either from the
# optimizer trace or from the recorded optimizer context, so that the
# two range-printing paths can share the same probe queries.
#
# PARAMETERS
# $opt_gis_range_print_use_trace If set, dump range_scan_alternatives
# from the optimizer trace. Otherwise,
# dump the recorded optimizer context.
#
# EXAMPLE
# opt_trace.test, opt_context_store_stats.test
#
if ($opt_gis_range_print_use_trace)
{
select json_detailed(json_extract(trace, '$**.range_scan_alternatives')) as rsa from information_schema.optimizer_trace;
}
if (!$opt_gis_range_print_use_trace)
{
--source include/opt_context_list_tables_and_ranges.inc
}

View File

@@ -705,4 +705,97 @@ id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range a_idx a_idx 13 NULL 4 Using where; Using index
set optimizer_replay_context='';
drop table t1;
#
# MDEV-40553: unprintable gis ranges in trace and context
#
CREATE TABLE t1 (
id INT,
g GEOMETRY NOT NULL,
KEY k_prefix (g(6))
) ENGINE=MyISAM;
INSERT INTO t1 SELECT seq, POINT(seq, seq) FROM seq_1_to_5;
INSERT INTO t1 VALUES (6, LINESTRING(POINT(1,1), POINT(2,2)));
analyze table t1;
Table Op Msg_type Msg_text
db1.t1 analyze status Engine-independent statistics collected
db1.t1 analyze status OK
set optimizer_record_context=1;
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k_prefix k_prefix 8 NULL 6 Using where
select context into dumpfile "../../tmp/dump1.sql"
from information_schema.optimizer_context;
set optimizer_record_context=0;
drop table t1;
set optimizer_replay_context='opt_context';
# The ranges must match the recorded ones, so no warning is produced
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k_prefix k_prefix 8 NULL 6 Using where
set optimizer_replay_context='';
drop table t1;
# The same, replayed from the context read straight out of
# INFORMATION_SCHEMA.OPTIMIZER_CONTEXT rather than from the script.
# The literal in the script must read back as the JSON itself, so
# both ways of getting at it produce the same ranges.
CREATE TABLE t1 (
id INT,
g GEOMETRY NOT NULL,
KEY k_prefix (g(6))
) ENGINE=MyISAM;
INSERT INTO t1 SELECT seq, POINT(seq, seq) FROM seq_1_to_5;
INSERT INTO t1 VALUES (6, LINESTRING(POINT(1,1), POINT(2,2)));
analyze table t1;
Table Op Msg_type Msg_text
db1.t1 analyze status Engine-independent statistics collected
db1.t1 analyze status OK
set optimizer_record_context=1;
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k_prefix k_prefix 8 NULL 6 Using where
SET @opt_context=(value from information_schema.optimizer_context)
set optimizer_record_context=0;
set optimizer_replay_context='opt_context';
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range k_prefix k_prefix 8 NULL 6 Using where
set optimizer_replay_context='';
drop table t1;
#
# The same, but recording and replaying a range over a SPATIAL index,
# whose key stores an MBR (image_type itMBR) rather than the column
# value or its prefix.
#
CREATE TABLE t1 (
id INT,
g GEOMETRY NOT NULL,
SPATIAL KEY g_idx (g)
) ENGINE=MyISAM;
INSERT INTO t1 SELECT seq, POINT(seq, seq) FROM seq_1_to_20;
analyze table t1;
Table Op Msg_type Msg_text
db1.t1 analyze status Engine-independent statistics collected
db1.t1 analyze status OK
set optimizer_record_context=1;
EXPLAIN SELECT id FROM t1 FORCE INDEX(g_idx)
WHERE MBRWITHIN(g, GEOMFROMTEXT('POLYGON((0 0,0 5,5 5,5 0,0 0))'));
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range g_idx g_idx 34 NULL 5 Using where
select context into dumpfile "../../tmp/dump1.sql"
from information_schema.optimizer_context;
set optimizer_record_context=0;
drop table t1;
set optimizer_replay_context='opt_context';
# The ranges must match the recorded ones, so no warning is produced
EXPLAIN SELECT id FROM t1 FORCE INDEX(g_idx)
WHERE MBRWITHIN(g, GEOMFROMTEXT('POLYGON((0 0,0 5,5 5,5 0,0 0))'));
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 range g_idx g_idx 34 NULL 5 Using where
set optimizer_replay_context='';
drop table t1;
# End of 13.2 tests
drop database db1;

View File

@@ -511,4 +511,102 @@ set optimizer_replay_context='opt_context';
explain select * from t1 where a=0x2700 or a=0x2701 or a=0x2702 or a=0x2703;
set optimizer_replay_context='';
drop table t1;
--echo #
--echo # MDEV-40553: unprintable gis ranges in trace and context
--echo #
CREATE TABLE t1 (
id INT,
g GEOMETRY NOT NULL,
KEY k_prefix (g(6))
) ENGINE=MyISAM;
INSERT INTO t1 SELECT seq, POINT(seq, seq) FROM seq_1_to_5;
INSERT INTO t1 VALUES (6, LINESTRING(POINT(1,1), POINT(2,2)));
analyze table t1;
set optimizer_record_context=1;
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
select context into dumpfile "../../tmp/dump1.sql"
from information_schema.optimizer_context;
set optimizer_record_context=0;
drop table t1;
--disable_query_log
--disable_result_log
--source "$MYSQLTEST_VARDIR/tmp/dump1.sql"
--enable_query_log
--enable_result_log
set optimizer_replay_context='opt_context';
--echo # The ranges must match the recorded ones, so no warning is produced
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
set optimizer_replay_context='';
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
drop table t1;
--echo # The same, replayed from the context read straight out of
--echo # INFORMATION_SCHEMA.OPTIMIZER_CONTEXT rather than from the script.
--echo # The literal in the script must read back as the JSON itself, so
--echo # both ways of getting at it produce the same ranges.
CREATE TABLE t1 (
id INT,
g GEOMETRY NOT NULL,
KEY k_prefix (g(6))
) ENGINE=MyISAM;
INSERT INTO t1 SELECT seq, POINT(seq, seq) FROM seq_1_to_5;
INSERT INTO t1 VALUES (6, LINESTRING(POINT(1,1), POINT(2,2)));
analyze table t1;
set optimizer_record_context=1;
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
--source include/opt_context_save_in_var.inc
set optimizer_record_context=0;
set optimizer_replay_context='opt_context';
EXPLAIN SELECT id FROM t1 FORCE INDEX(k_prefix)
WHERE g < POINT(1,1) OR g > LINESTRING(POINT(9,9), POINT(8,8));
set optimizer_replay_context='';
drop table t1;
--echo #
--echo # The same, but recording and replaying a range over a SPATIAL index,
--echo # whose key stores an MBR (image_type itMBR) rather than the column
--echo # value or its prefix.
--echo #
CREATE TABLE t1 (
id INT,
g GEOMETRY NOT NULL,
SPATIAL KEY g_idx (g)
) ENGINE=MyISAM;
INSERT INTO t1 SELECT seq, POINT(seq, seq) FROM seq_1_to_20;
analyze table t1;
set optimizer_record_context=1;
EXPLAIN SELECT id FROM t1 FORCE INDEX(g_idx)
WHERE MBRWITHIN(g, GEOMFROMTEXT('POLYGON((0 0,0 5,5 5,5 0,0 0))'));
select context into dumpfile "../../tmp/dump1.sql"
from information_schema.optimizer_context;
set optimizer_record_context=0;
drop table t1;
--disable_query_log
--disable_result_log
--source "$MYSQLTEST_VARDIR/tmp/dump1.sql"
--enable_query_log
--enable_result_log
set optimizer_replay_context='opt_context';
--echo # The ranges must match the recorded ones, so no warning is produced
EXPLAIN SELECT id FROM t1 FORCE INDEX(g_idx)
WHERE MBRWITHIN(g, GEOMFROMTEXT('POLYGON((0 0,0 5,5 5,5 0,0 0))'));
set optimizer_replay_context='';
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
drop table t1;
--echo # End of 13.2 tests
drop database db1;

View File

@@ -647,3 +647,235 @@ a ["(10) <= (a) <= (10)"] 49 1 11
# == End of optimizer context
drop table t1;
# End of 13.1 tests
#
# MDEV-40553: 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 k_full prefix is long enough to hold every value this test
# inserts or compares against (points and short linestrings),
# so the index holds the whole value here. It is printed as a binary
# string, like any other BLOB prefix.
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 ["(\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@) <= (g1) <= (\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@)"] 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 [
"(\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@) <= (g1) <= (\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@)",
"(\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x02\\\\x00\\\\x00\\\\x00\\\\x02\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@) <= (g1) <= (\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x02\\\\x00\\\\x00\\\\x00\\\\x02\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@)"
] 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)",
"(\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@) <= (g2) <= (\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x01\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x08@)",
"(\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x02\\\\x00\\\\x00\\\\x00\\\\x02\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@) <= (g2) <= (\\\\x00\\\\x00\\\\x00\\\\x00\\\\x01\\\\x02\\\\x00\\\\x00\\\\x00\\\\x02\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\xF0?\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00\\\\x00@)"
] 3 3 1
# == End of optimizer context
# The index holds only a prefix of the value.
# 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
# touches()/crosses()/overlaps() (and their MBRxxx aliases, where one
# exists) 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 crosses(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
# MBRDISJOINT: the handler's records_in_range() cannot estimate this
# relation and returns HA_POS_ERROR, so the range optimizer discards
# this index as a candidate before it ever traces its ranges -- the
# trace shows the index with no further detail. The range text is
# still produced (and recorded) when the optimizer separately asks
# the handler for a cost estimate, see multi_range_read_info_const().
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)))"] 18446744073709551615 0 0
# == End of optimizer context
drop table t2;
# End of 13.2 tests

View File

@@ -471,3 +471,10 @@ select * from t1 partition (p1) where a=10;
drop table t1;
--echo # End of 13.1 tests
--echo #
--echo # MDEV-40553: unprintable gis ranges in trace and context
--echo #
--source include/opt_gis_range_print.inc
--echo # End of 13.2 tests

View File

@@ -13904,6 +13904,337 @@ select json_valid(@trace);
json_valid(@trace)
1
drop table t1;
# End of 13.1 tests
#
# MDEV-40553: 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 k_full prefix is long enough to hold every value this test
# inserts or compares against (points and short linestrings),
# so the index holds the whole value here. It is printed as a binary
# string, like any other BLOB prefix.
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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"index": "k_full",
"ranges":
["(\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00@\\x00\\x00\\x00\\x00\\x00\\x00\\x00@) <= (g1) <= (\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00@\\x00\\x00\\x00\\x00\\x00\\x00\\x00@)"],
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"index": "k_full",
"ranges":
[
"(\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x08@) <= (g1) <= (\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x08@)",
"(\\x00\\x00\\x00\\x00\\x01\\x02\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\x00@\\x00\\x00\\x00\\x00\\x00\\x00\\x00@) <= (g1) <= (\\x00\\x00\\x00\\x00\\x01\\x02\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\x00@\\x00\\x00\\x00\\x00\\x00\\x00\\x00@)"
],
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"index": "k_null",
"ranges":
[
"(NULL) <= (g2) <= (NULL)",
"(\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x08@) <= (g2) <= (\\x00\\x00\\x00\\x00\\x01\\x01\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x08@\\x00\\x00\\x00\\x00\\x00\\x00\\x08@)",
"(\\x00\\x00\\x00\\x00\\x01\\x02\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\x00@\\x00\\x00\\x00\\x00\\x00\\x00\\x00@) <= (g2) <= (\\x00\\x00\\x00\\x00\\x01\\x02\\x00\\x00\\x00\\x02\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\xF0?\\x00\\x00\\x00\\x00\\x00\\x00\\x00@\\x00\\x00\\x00\\x00\\x00\\x00\\x00@)"
],
"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.
# 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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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
}
]
]
# touches()/crosses()/overlaps() (and their MBRxxx aliases, where one
# exists) 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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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 crosses(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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"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
}
]
]
# MBRDISJOINT: the handler's records_in_range() cannot estimate this
# relation and returns HA_POS_ERROR, so the range optimizer discards
# this index as a candidate before it ever traces its ranges -- the
# trace shows the index with no further detail. The range text is
# still produced (and recorded) when the optimizer separately asks
# the handler for a cost estimate, see multi_range_read_info_const().
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')) as rsa from information_schema.optimizer_trace;
rsa
[
[
{
"index": "g"
}
]
]
drop table t2;
# End of 13.2 tests
set @@optimizer_switch= @save_optimizer_switch;
set @@use_stat_tables= @save_use_stat_tables;
set @@histogram_size= @save_histogram_size;

View File

@@ -1407,6 +1407,17 @@ select json_valid(@trace);
drop table t1;
--echo # End of 13.1 tests
--echo #
--echo # MDEV-40553: unprintable gis ranges in trace and context
--echo #
--let $opt_gis_range_print_use_trace= 1
--source include/opt_gis_range_print.inc
--let $opt_gis_range_print_use_trace=
--echo # End of 13.2 tests
set @@optimizer_switch= @save_optimizer_switch;
set @@use_stat_tables= @save_use_stat_tables;
set @@histogram_size= @save_histogram_size;

View File

@@ -11895,11 +11895,16 @@ 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,
see Field::image_type(). Ignored by this base
implementation; overridden by Field_geom to
interpret itMBR key images.
*/
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;

View File

@@ -860,6 +860,11 @@ public:
TMYSQL_COMPRESSED= 24, // Compatibility with TMySQL
};
enum imagetype { itRAW, itMBR};
/*
Form in which an index stores its column values: itMBR for a SPATIAL
(RTREE) index, which stores the column's MBR instead of its value;
itRAW for every other index algorithm.
*/
static enum imagetype image_type(enum ha_key_alg alg)
{ return alg == HA_KEY_ALG_RTREE ? itMBR : itRAW; }
@@ -1772,7 +1777,14 @@ public:
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);
/*
Field_geom overrides this rather than print_key_value(), because its
itMBR key image is four raw doubles, not the field's normal on-disk
representation, so it cannot be loaded via set_key_image() and handed
to print_key_value() the way every other Field subclass does.
*/
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,

View File

@@ -1122,6 +1122,26 @@ const uchar *Optimizer_context_recorder::get_tbl_ctx_key(const void *entry_,
return reinterpret_cast<const uchar *>(entry->name);
}
/*
@brief
Print the min/max keys of a records_in_range() call the way they are
recorded in / replayed from the optimizer context.
*/
static void print_records_in_range_keys(const TABLE *tbl,
const KEY_PART_INFO *key_part,
uint keynr, const key_range *min_range,
const key_range *max_range,
String *min_key, String *max_key)
{
DBUG_ASSERT(min_range && max_range);
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);
}
void Optimizer_context_recorder::record_records_in_range(
const TABLE *tbl, const KEY_PART_INFO *key_part,
uint keynr, const key_range *min_range, const key_range *max_range,
@@ -1136,8 +1156,8 @@ 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);
print_records_in_range_keys(tbl, key_part, keynr, min_range, max_range,
&min_key, &max_key);
if (!(rec_in_range_ctx->min_key= strdup_root(mem_root, &min_key)))
return; // OOM
@@ -1754,6 +1774,11 @@ bool Optimizer_context_replay::infuse_multi_range_read_info_const(
const char *idx_name= keyinfo->name.str;
const KEY_PART_INFO *key_part= keyinfo->key_part;
uint n_key_parts= table->actual_n_key_parts(keyinfo);
/*
SEL_ARG_RANGE_SEQ (whose key_parts already holds this value) is only
forward-declared here, so recompute it instead of reaching into *seq.
*/
Field::imagetype image_type= Field::image_type(keyinfo->algorithm);
KEY_MULTI_RANGE multi_range;
range_seq_t seq_it;
List<Multi_range_read_const_call_record> mrr_const_calls;
@@ -1766,7 +1791,7 @@ bool Optimizer_context_replay::infuse_multi_range_read_info_const(
while (!seq_if->next(seq_it, &multi_range))
{
StringBuffer<128> range_info(system_charset_info);
print_range(&range_info, key_part, &multi_range, n_key_parts);
print_range(&range_info, key_part, &multi_range, n_key_parts, image_type);
char *r1= range_info.c_ptr_safe();
text_ranges.push_back(strdup_root(thd->mem_root, &range_info));
act_ranges.append(r1, strlen(r1));
@@ -1971,8 +1996,8 @@ 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);
print_records_in_range_keys(tbl, key_part, keynr, min_range, max_range,
&min_key, &max_key);
append_base_table_name(tbl, &tbl_name);
if (table_context_for_replay *tbl_ctx=

View File

@@ -12391,7 +12391,7 @@ public:
range_info.length(0);
if (seq_if.next(seq_it, &range))
return true; // No more ranges
print_range(&range_info, cur_key_part, &range, n_key_parts);
print_range(&range_info, cur_key_part, &range, n_key_parts, image_type);
return false;
}
const String& get_interval_str() override { return range_info; }
@@ -12403,6 +12403,7 @@ public:
n_key_parts= param->table->actual_n_key_parts(keyinfo);
seq.keyno= idx;
seq.key_parts= param->key[idx];
image_type= seq.key_parts->image_type;
seq.real_keyno= param->real_keynr[idx];
seq.param= param;
seq.start= keypart;
@@ -12421,6 +12422,8 @@ private:
StringBuffer<128> range_info;
uint n_key_parts;
const KEY_PART_INFO *cur_key_part;
/* Form in which this index stores the column values, see print_range() */
Field::imagetype image_type;
};
@@ -17686,34 +17689,104 @@ 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 touches(), crosses() and overlaps() (and their MBRxxx
aliases, where one exists, e.g. MBRTOUCHES, MBROVERLAPS -- there is
no MBRCROSSES) 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:
/* The five values above are the only ones a GEOM range can carry. */
DBUG_ASSERT(0);
out->append(STRING_WITH_LEN(" ? "));
break;
}
}
/*
@brief Print one range of an index
@param[out] out String the range is appended to
@param[in] key_part Index components description
@param[in] range The range to print
@param[in] n_key_parts Number of keyparts in the index
@param[in] image_type Form in which the index stores the column values,
see Field::image_type()
@detail
image_type is a property of the index, not of the range, so it is passed
in rather than derived from range->range_flag. Today a GEOM_FLAG range
and an itMBR keypart always coincide: Item_bool_func::get_mm_leaf() and
get_mm_leaf_for_LIKE() build no range for a non-itRAW keypart, and
Item_func_spatial_rel::get_mm_leaf() builds none for a non-itMBR one.
Passing image_type covers the direction the GEOM_FLAG assert below does
not: a non-GEOM range over an itMBR keypart.
*/
void print_range(String *out, const KEY_PART_INFO *key_part,
KEY_MULTI_RANGE *range, uint n_key_parts)
KEY_MULTI_RANGE *range, uint n_key_parts,
Field::imagetype image_type)
{
Check_level_instant_set check_field(current_thd, CHECK_FIELD_IGNORE);
uint flag= range->range_flag;
String key_name;
key_name.set_charset(system_charset_info);
key_part_map keypart_map= range->start_key.keypart_map |
range->end_key.keypart_map;
/* GEOM ranges are only produced for SPATIAL indexes */
DBUG_ASSERT(!(flag & GEOM_FLAG) || image_type == Field::itMBR);
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".
Only start_key is filled in for a GEOM range, see
sel_arg_range_seq_next(), so end_key must not be looked at here.
*/
print_keyparts_name(out, key_part, n_key_parts, keypart_map);
out->append(STRING_WITH_LEN(" "));
print_keyparts_name(out, key_part, n_key_parts,
range->start_key.keypart_map);
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, image_type);
return;
}
key_part_map keypart_map= range->start_key.keypart_map |
range->end_key.keypart_map;
if (range->start_key.length)
{
print_key_value(out, key_part, range->start_key.key,
range->start_key.length);
range->start_key.length, image_type);
print_min_range_operator(out, range->start_key.flag);
}
@@ -17722,8 +17795,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,
image_type);
}
}
@@ -17747,7 +17820,9 @@ 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());
/* There is no index here, so the value is in its raw form */
field->print_key_part_value(out, range->start_key.key, field->key_length(),
Field::itRAW);
print_min_range_operator(out, range->start_key.flag);
}
@@ -17756,7 +17831,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);
}
@@ -17794,10 +17870,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,
see Field::image_type()
*/
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;
@@ -17813,7 +17892,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(","));

View File

@@ -2054,10 +2054,12 @@ bool calculate_cond_selectivity_for_table(THD *thd, TABLE *table, Item **cond);
bool eq_ranges_exceeds_limit(RANGE_SEQ_IF *seq, void *seq_init_param,
uint limit);
void print_range(String *out, const KEY_PART_INFO *key_part,
KEY_MULTI_RANGE *range, uint n_key_parts);
KEY_MULTI_RANGE *range, uint n_key_parts,
Field::imagetype image_type);
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);

View File

@@ -1001,6 +1001,96 @@ 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 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, with the same vertex order
ST_Envelope() uses (see Geometry::envelope()). That does not hold for
an unparsable geometry (or a value shorter than SRID_SIZE): its key
image is all-zero (see Geometry::get_key_image_itMBR()), which this
function prints as the same POLYGON a real MBR at the origin would
produce. An empty GEOMETRYCOLLECTION has a successful but default MBR
(DBL_MAX/-DBL_MAX bounds, see MBR::MBR()), which prints as a huge
inverted POLYGON. ST_Envelope() is unambiguous for the empty case, as
it returns an empty GEOMETRYCOLLECTION for a geometry with no MBR.
For the other index types the key holds the value (or its prefix),
which is printed by Field::print_key_part_value().
*/
void Field_geom::print_key_part_value(String *out, const uchar *key,
uint32 length, imagetype image_type)
{
if (image_type != itMBR)
{
Field::print_key_part_value(out, key, length, image_type);
return;
}
/*
The key part is expected to hold a full MBR (four doubles). A shorter
key part should not happen for a SPATIAL index, whose key_part->length
is always SIZEOF_STORED_DOUBLE * 4. The assert documents that; the
fallback is kept because this runs while producing optimizer trace and
context output, which must not read past the key (or crash) even if
an invariant is broken elsewhere.
*/
DBUG_ASSERT(length >= SIZEOF_STORED_DOUBLE * 4);
if (length < SIZEOF_STORED_DOUBLE * 4)
{
out->append(STRING_WITH_LEN("<invalid MBR key length>"));
return;
}
/*
SPATIAL keys do not support NULL: the column must be NOT NULL
(ER_INDEX_CANNOT_HAVE_NULL, raised in mysql_prepare_create_table()),
and Item_func_spatial_rel::get_mm_leaf() builds no range otherwise.
*/
DBUG_ASSERT(!real_maybe_null() || !*key);
if (real_maybe_null())
{
// Be graceful about a NULL key here, as this is only used for printing.
if (*key)
{
out->append(NULL_clex_str);
return;
}
key++; // Skip the null byte
}
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);
const double points[5][2]= {{xmin, ymin}, {xmax, ymin}, {xmax, ymax},
{xmin, ymax}, {xmin, ymin}};
/*
2 * array_elements(points) doubles, plus "POLYGON((", "))", the commas and
spaces between them (32 bytes is ample slack for those separators).
*/
if (out->reserve(MY_GCVT_MAX_FIELD_WIDTH * array_elements(points) * 2 + 32))
return; // Out of memory, the allocator has already raised the error
out->qs_append(STRING_WITH_LEN("POLYGON(("));
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());

View File

@@ -479,10 +479,8 @@ public:
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_part_value(String *out, const uchar *key, uint32 length,
imagetype image_type) override;
Binlog_type_info binlog_type_info() const override;
};