diff --git a/mysql-test/main/opt_context_replay_basic.result b/mysql-test/main/opt_context_replay_basic.result index e8a091036c596..03317ac8409d5 100644 --- a/mysql-test/main/opt_context_replay_basic.result +++ b/mysql-test/main/opt_context_replay_basic.result @@ -549,4 +549,43 @@ 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=''; +drop table t1; +# +# End of 13.1 tests +# drop database db1; diff --git a/mysql-test/main/opt_context_replay_basic.test b/mysql-test/main/opt_context_replay_basic.test index b8a17c0bf5758..17a6354e27655 100644 --- a/mysql-test/main/opt_context_replay_basic.test +++ b/mysql-test/main/opt_context_replay_basic.test @@ -351,4 +351,53 @@ 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" +drop table t1; + +--echo # +--echo # End of 13.1 tests +--echo # + drop database db1; diff --git a/sql/filesort.cc b/sql/filesort.cc index 0b8fbfb83feeb..a2c828b41e20d 100644 --- a/sql/filesort.cc +++ b/sql/filesort.cc @@ -3024,6 +3024,25 @@ static uint make_packed_sortkey(Sort_param *param, uchar *to) return length; } +static bool is_charset_conversion_lossless(CHARSET_INFO *from_cs, + 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 @@ -3148,12 +3167,26 @@ 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()); + } + else + { + if (require_quote) + output.append('\''); + output.append_for_single_quote_opt_convert(tmp.ptr(), tmp.length(), + field->charset()); + if (require_quote) + output.append('\''); + } } } output.append(')'); diff --git a/sql/opt_context_store_replay.h b/sql/opt_context_store_replay.h index 55b587dc2a166..719ff8525ef8e 100644 --- a/sql/opt_context_store_replay.h +++ b/sql/opt_context_store_replay.h @@ -66,11 +66,6 @@ class Optimizer_context_recorder const KEY_PART_INFO *key_part, uint keynr, const key_range *min_range, const key_range *max_range, ha_rows records); - void record_const_table_row(TABLE *tbl) - { - /* use table->record[1] */ - record_table_row(tbl, 1); - } void record_current_table_row(TABLE *tbl) { /* use table->record[0] */ diff --git a/sql/sql_select.cc b/sql/sql_select.cc index a82fb09ab0832..4d4d1d6a03315 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -25066,6 +25066,64 @@ join_read_const_table(THD *thd, JOIN_TAB *tab, POSITION *pos) DBUG_RETURN(0); } +/* + 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. +*/ +static 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; + + MY_BITMAP *saved_read_set= table->read_set; + uint saved_status= table->status; + 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); + + 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; +} /** Read a constant table when there is at most one matching row, using a table @@ -25095,9 +25153,9 @@ join_read_system(JOIN_TAB *tab) empty_record(table); // Make empty record return -1; } - store_record(table,record[1]); - if (Optimizer_context_recorder *rec= tab->join->thd->opt_ctx_recorder) - rec->record_const_table_row(table); + store_record(table, record[1]); // cache the row the optimizer used + if (tab->join->thd->opt_ctx_recorder) + record_const_row_full(tab, true); // re-read full row, record, restore } else if (!table->status) // Only happens with left join restore_record(table,record[1]); // restore old record @@ -25152,10 +25210,9 @@ join_read_const(JOIN_TAB *tab) return report_error(table, error); return -1; } - store_record(table,record[1]); - - if (Optimizer_context_recorder *rec= tab->join->thd->opt_ctx_recorder) - rec->record_const_table_row(table); + store_record(table, record[1]); // cache the row the optimizer used + if (tab->join->thd->opt_ctx_recorder) + record_const_row_full(tab, false); // re-read full row, record, restore } else if (!(table->status & ~STATUS_NULL_ROW)) // Only happens with left join {