Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
104 changes: 104 additions & 0 deletions mysql-test/main/opt_context_replay_basic.result
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,108 @@ select context like '%bar%' from information_schema.optimizer_context;
context like '%bar%'
1
drop table t1;
#
# MDEV-40383: innodb_gis.point_basic fails on replay
#
CREATE TABLE t1 (
a INT NOT NULL,
p POINT NOT NULL,
l LINESTRING NOT NULL,
g GEOMETRY NOT NULL,
PRIMARY KEY(p),
SPATIAL KEY `idx2` (p),
SPATIAL KEY `idx3` (l),
SPATIAL KEY `idx4` (g)
);
INSERT INTO t1 VALUES(
1, ST_GeomFromText('POINT(10 10)'),
ST_GeomFromText('LINESTRING(1 1, 5 5, 10 10)'),
ST_GeomFromText('POLYGON((30 30, 40 40, 50 50, 30 50, 30 40, 30 30))'));
INSERT INTO t1 VALUES(
2, ST_GeomFromText('POINT(20 20)'),
ST_GeomFromText('LINESTRING(2 3, 7 8, 9 10, 15 16)'),
ST_GeomFromText('POLYGON((10 30, 30 40, 40 50, 40 30, 30 20, 10 30))'));
set optimizer_record_context=1;
EXPLAIN SELECT a, ST_AsText(p) FROM t1 WHERE a = 2 AND p = ST_GeomFromText('POINT(20 20)');
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 const PRIMARY,idx2 PRIMARY 27 const 1
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';
# Same query as above, must have same explain:
EXPLAIN SELECT a, ST_AsText(p) FROM t1 WHERE a = 2 AND p = ST_GeomFromText('POINT(20 20)');
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE t1 const PRIMARY,idx2 PRIMARY 27 const 1
set optimizer_replay_context='';
SELECT a, ST_AsText(p), ST_AsText(l), ST_AsText(g) FROM t1;
a ST_AsText(p) ST_AsText(l) ST_AsText(g)
2 POINT(20 20) LINESTRING(2 3,7 8,9 10,15 16) POLYGON((10 30,30 40,40 50,40 30,30 20,10 30))
#
# MIN/MAX recording with geometry fields in the table
#
INSERT INTO t1 VALUES(
1, ST_GeomFromText('POINT(10 10)'),
ST_GeomFromText('LINESTRING(1 1, 5 5, 10 10)'),
ST_GeomFromText('POLYGON((30 30, 40 40, 50 50, 30 50, 30 40, 30 30))'));
alter table t1 add index(a);
select a from t1;
a
1
2
SELECT MIN(a) FROM t1;
MIN(a)
1
set optimizer_record_context=1;
EXPLAIN SELECT MIN(a) FROM t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away
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';
# Same query as above, must have same explain:
EXPLAIN SELECT MIN(a) FROM t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away
set optimizer_replay_context='';
SELECT a, ST_AsText(p), ST_AsText(l), ST_AsText(g) FROM t1;
a ST_AsText(p) ST_AsText(l) ST_AsText(g)
1 POINT(10 10) LINESTRING(1 1,5 5,10 10) POLYGON((30 30,40 40,50 50,30 50,30 40,30 30))
drop table t1;
#
# MIN/MAX recording with a virtual column present.
#
CREATE TABLE t1 (
a INT NOT NULL,
b INT NOT NULL,
v INT AS (a + 100) VIRTUAL,
KEY(a)
) ENGINE=MyISAM;
INSERT INTO t1 (a,b) VALUES (1,10),(2,20),(3,30),(1,40);
set optimizer_record_context=1;
EXPLAIN SELECT MIN(a) FROM t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away
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';
# Same query as above, must have same explain:
EXPLAIN SELECT MIN(a) FROM t1;
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE NULL NULL NULL NULL NULL NULL NULL Select tables optimized away
set optimizer_replay_context='';
# MIN(a) row is (1,10); the non-indexed NOT NULL column b must be
# captured (not defaulted to 0), and v must be recomputed as a+100=101:
SELECT a, b, v FROM t1;
a b v
1 10 101
drop table t1;
#
# End of 13.1 tests
#
drop database db1;
117 changes: 117 additions & 0 deletions mysql-test/main/opt_context_replay_basic.test
Original file line number Diff line number Diff line change
Expand Up @@ -351,4 +351,121 @@ explain select * from t1 where a >'foo' or a < 'bar';
select context like '%bar%' from information_schema.optimizer_context;

drop table t1;

--echo #
--echo # MDEV-40383: innodb_gis.point_basic fails on replay
--echo #

CREATE TABLE t1 (
a INT NOT NULL,
p POINT NOT NULL,
l LINESTRING NOT NULL,
g GEOMETRY NOT NULL,
PRIMARY KEY(p),
SPATIAL KEY `idx2` (p),
SPATIAL KEY `idx3` (l),
SPATIAL KEY `idx4` (g)
);

INSERT INTO t1 VALUES(
1, ST_GeomFromText('POINT(10 10)'),
ST_GeomFromText('LINESTRING(1 1, 5 5, 10 10)'),
ST_GeomFromText('POLYGON((30 30, 40 40, 50 50, 30 50, 30 40, 30 30))'));

INSERT INTO t1 VALUES(
2, ST_GeomFromText('POINT(20 20)'),
ST_GeomFromText('LINESTRING(2 3, 7 8, 9 10, 15 16)'),
ST_GeomFromText('POLYGON((10 30, 30 40, 40 50, 40 30, 30 20, 10 30))'));

set optimizer_record_context=1;
EXPLAIN SELECT a, ST_AsText(p) FROM t1 WHERE a = 2 AND p = ST_GeomFromText('POINT(20 20)');
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 # Same query as above, must have same explain:
EXPLAIN SELECT a, ST_AsText(p) FROM t1 WHERE a = 2 AND p = ST_GeomFromText('POINT(20 20)');

set optimizer_replay_context='';
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
SELECT a, ST_AsText(p), ST_AsText(l), ST_AsText(g) FROM t1;

--echo #
--echo # MIN/MAX recording with geometry fields in the table
--echo #
INSERT INTO t1 VALUES(
1, ST_GeomFromText('POINT(10 10)'),
ST_GeomFromText('LINESTRING(1 1, 5 5, 10 10)'),
ST_GeomFromText('POLYGON((30 30, 40 40, 50 50, 30 50, 30 40, 30 30))'));

alter table t1 add index(a);

select a from t1;
SELECT MIN(a) FROM t1;

set optimizer_record_context=1;
EXPLAIN SELECT MIN(a) FROM t1;

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 # Same query as above, must have same explain:
EXPLAIN SELECT MIN(a) FROM t1;

set optimizer_replay_context='';
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
SELECT a, ST_AsText(p), ST_AsText(l), ST_AsText(g) FROM t1;
drop table t1;

--echo #
--echo # MIN/MAX recording with a virtual column present.
--echo #
CREATE TABLE t1 (
a INT NOT NULL,
b INT NOT NULL,
v INT AS (a + 100) VIRTUAL,
KEY(a)
) ENGINE=MyISAM;
INSERT INTO t1 (a,b) VALUES (1,10),(2,20),(3,30),(1,40);

set optimizer_record_context=1;
EXPLAIN SELECT MIN(a) FROM t1;
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 # Same query as above, must have same explain:
EXPLAIN SELECT MIN(a) FROM t1;

set optimizer_replay_context='';
--remove_file "$MYSQLTEST_VARDIR/tmp/dump1.sql"
--echo # MIN(a) row is (1,10); the non-indexed NOT NULL column b must be
--echo # captured (not defaulted to 0), and v must be recomputed as a+100=101:
SELECT a, b, v FROM t1;
drop table t1;

--echo #
--echo # End of 13.1 tests
--echo #

drop database db1;
44 changes: 38 additions & 6 deletions sql/filesort.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3024,6 +3024,25 @@ static uint make_packed_sortkey(Sort_param *param, uchar *to)
return length;
}

static bool is_charset_conversion_lossless(const CHARSET_INFO *from_cs,
const CHARSET_INFO *to_cs)
{
if (to_cs == &my_charset_bin)
return true; // binary swallows any bytes
if (from_cs == &my_charset_bin)
return false; // arbitrary bytes ⊄ text charset
if (my_charset_same(from_cs, to_cs))
return true; // same repertoire family
if (from_cs->state & MY_CS_PUREASCII) // ASCII-only source...
return my_charset_is_ascii_based(to_cs); // ...into any ASCII-based target
if ((to_cs->state &
MY_CS_UNICODE) && // Unicode target covers everything,M N?'b;
(to_cs->state &
MY_CS_UNICODE_SUPPLEMENT)) // incl. non-BMP (so utf8mb4/utf16/utf32,
return true; // but NOT plain utf8mb3/ucs2)
return false; // unknown → treat as possibly lossy
}

/*
@brief
Format the row record and store it in the output
Expand Down Expand Up @@ -3148,12 +3167,25 @@ void format_and_store_row(TABLE *table, const uchar *rec, bool print_names,
}
field->val_str(&tmp);
}
if (require_quote)
output.append('\'');
output.append_for_single_quote_opt_convert(tmp.ptr(), tmp.length(),
field->charset());
if (require_quote)
output.append('\'');
/*
Emit non-empty values as a hex literal whenever converting field's
charset to the output charset conversion is lossy; otherwise emit the
charset-converted value, quoted only when the type requires it.
*/
if (require_quote && tmp.length() &&
!is_charset_conversion_lossless(tmp.charset(), output.charset()))
{
output.append(STRING_WITH_LEN("0x"));
output.append_hex(tmp.ptr(), tmp.length());
Comment thread
bsrikanth-mariadb marked this conversation as resolved.
}
else
{
if (require_quote)
output.append('\'');
output.append_for_single_quote_opt_convert(tmp);
if (require_quote)
output.append('\'');
}
}
}
output.append(')');
Expand Down
84 changes: 84 additions & 0 deletions sql/opt_context_store_replay.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2337,3 +2337,87 @@ void clean_captured_ctx(THD *thd)
delete thd->captured_opt_ctx;
thd->captured_opt_ctx= nullptr;
}

/*
Point table->read_set at a private bitmap (table->tmp_set) covering every
stored column, so that a subsequent read/record captures the full row.

Virtual columns are excluded: they cannot be assigned in REPLACE INTO and are
recomputed on read. We copy s->all_set into the per-table tmp_set rather than
aliasing s->all_set directly -- s->all_set is shared across the whole
TABLE_SHARE and must never be mutated.

Precondition: table->tmp_set must stay free for the caller's use until
read_set is restored. This holds on the const-row and MIN/MAX read paths
precisely because we clear the virtual-column bits: with those bits unset,
TABLE::update_virtual_fields(VCOL_UPDATE_FOR_READ) skips them during the read
and so never reuses tmp_set as its own scratch. A caller on a path that
evaluates virtual columns into tmp_set would corrupt the widened read_set.

@return the previous read_set, which the caller must restore (via
column_bitmaps_set) once the row has been read and recorded.
*/
MY_BITMAP *widen_read_set_no_vcols(TABLE *table)
{
MY_BITMAP *saved_read_set= table->read_set;
bitmap_copy(&table->tmp_set, &table->s->all_set);
for (Field **pfield= table->field; *pfield; pfield++)
{
/* virtual columns need not be stored. */
if ((*pfield)->vcol_info)
bitmap_clear_bit(&table->tmp_set, (*pfield)->field_index);
}
table->column_bitmaps_set(&table->tmp_set, table->write_set);
return saved_read_set;
}

/*
Re-read a const/system table row with ALL but Virtual columns and
record it for the optimizer context.

join_read_system()/join_read_const() only fetch the columns present in
table->read_set. That's sufficient for execution, but when we record the
const row for replay it yields an incomplete REPLACE INTO -- columns that
are NOT NULL and have no default then depend on a relaxed sql_mode (see
Optimizer_context_recorder::record_table_row()). Widen read_set to all
columns, re-read the single row, record it, then restore read_set so the
chosen plan is not disturbed.

The caller must have cached the row the optimizer actually used in
record[1] before calling this. We re-read the full row into record[0] only
to record it, then unconditionally restore record[0] from record[1] so that
recording leaves execution's record[0] byte-for-byte identical to the
non-recording case -- regardless of whether the re-read found a row.
*/
void record_const_row_full(JOIN_TAB *tab, bool is_system)
{
TABLE *table= tab->table;
Optimizer_context_recorder *rec= tab->join->thd->opt_ctx_recorder;

uint saved_status= table->status;
MY_BITMAP *saved_read_set= widen_read_set_no_vcols(table);

int error;
/*
Re-read the single const row with the widened read_set, mirroring how the
optimizer originally fetched it: a system table (join_read_system) has at
most one row, read via the primary key; a const eq_ref table
(join_read_const) is fetched by an exact lookup on tab->ref.
*/
if (is_system)
error= table->file->ha_read_first_row(table->record[0],
table->s->primary_key);
else
error= table->file->ha_index_read_idx_map(
table->record[0], tab->ref.key, (uchar *) tab->ref.key_buff,
make_prev_keypart_map(tab->ref.key_parts), HA_READ_KEY_EXACT);

if (likely(!error))
rec->record_current_table_row(table); // records the full record[0]

/* Recording must not perturb the row execution uses: restore it. */
restore_record(table, record[1]);

table->column_bitmaps_set(saved_read_set, table->write_set);
table->status= saved_status;
}
Loading
Loading