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

MDEV-41146: Merge pseudo_bits_cond into outer_ref_cond

A join of only const tables stored the WHERE conjuncts that refer to a
column of an outer query or call rand() in pseudo_bits_cond.  The
executor read pseudo_bits_cond only when the query needed no temporary
table, and read outer_ref_cond otherwise.  A window function or
DISTINCT with ROLLUP needs a temporary table, so those conjuncts were
never checked, and the query returned rows that the WHERE clause
rejects.

pseudo_bits_cond came from the fix for LP bug 908269 in 2012.  For a
join of only const tables, that fix also collected conjuncts that call
rand(), and it stored them in a new field without stating a reason.
The code that filled the two fields and the code that read them chose
between them by the same test.  The tests have differed since
MDEV-8646 in 2016, which added the temporary table condition to the
executor's test.  Later changes only display the field, in EXPLAIN
FORMAT=JSON (MDEV-19714) and in the optimizer trace (MDEV-21095).  In
MDEV-19714, its author wrote that it was not clear why pseudo_bits_cond
was needed in addition to outer_ref_cond.

pseudo_bits_cond is removed, and outer_ref_cond holds these conjuncts
for every join.  The executor checks it on both paths, so where the
conjuncts are stored no longer depends on the executor's test.
Conjuncts that call rand() are still collected only for a join of only
const tables.  EXPLAIN FORMAT=JSON shows these conjuncts as
outer_ref_condition instead of pseudo_bits_condition.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dave Gosselin
2026-10-08 15:07:54 -04:00
committed by Dave Gosselin
parent cfc6a0e733
commit 26af7ea811
11 changed files with 148 additions and 48 deletions

View File

@@ -3038,7 +3038,7 @@ EXPLAIN
{
"query_block": {
"select_id": 1,
"pseudo_bits_condition": "if(uuid_short(),NULL,1)",
"outer_ref_condition": "if(uuid_short(),NULL,1)",
"nested_loop": [
{
"table": {
@@ -3064,7 +3064,7 @@ EXPLAIN
{
"query_block": {
"select_id": 1,
"pseudo_bits_condition": "10 = rand()",
"outer_ref_condition": "10 = rand()",
"nested_loop": [
{
"table": {

View File

@@ -905,3 +905,18 @@ NULL
NULL
DROP TABLE t1;
# End of 10.3 Tests
#
# MDEV-41146: WHERE is not checked when every table is const and
# the query needs a temporary table
#
create table t1 (a int primary key);
insert into t1 values (1),(2),(3);
# rand() < 0 is never true, so the result is empty.
select distinct a from t1 where a=1 and rand() < 0 group by a with rollup;
a
select distinct a from t1 where a=1 and rand() >= 0 group by a with rollup;
a
1
NULL
drop table t1;
# End of 11.4 tests

View File

@@ -533,3 +533,19 @@ SELECT NULLIF( CAST( 'foo' AS DATE ), NULL & 'bar' ) AS f FROM t1 GROUP BY f WIT
DROP TABLE t1;
--echo # End of 10.3 Tests
--echo #
--echo # MDEV-41146: WHERE is not checked when every table is const and
--echo # the query needs a temporary table
--echo #
create table t1 (a int primary key);
insert into t1 values (1),(2),(3);
--echo # rand() < 0 is never true, so the result is empty.
select distinct a from t1 where a=1 and rand() < 0 group by a with rollup;
select distinct a from t1 where a=1 and rand() >= 0 group by a with rollup;
drop table t1;
--echo # End of 11.4 tests

View File

@@ -7668,7 +7668,7 @@ EXPLAIN
{
"query_block": {
"select_id": 2,
"pseudo_bits_condition": "1 = t1.a or <in_optimizer>(1,<exists>(subquery#3))",
"outer_ref_condition": "1 = t1.a or <in_optimizer>(1,<exists>(subquery#3))",
"nested_loop": [
{
"table": {

View File

@@ -4866,3 +4866,27 @@ DROP TABLE t1;
#
# End of 10.11 tests
#
#
# MDEV-41146: WHERE is not checked when every table is const and
# the query needs a temporary table
#
create table t1 (a int primary key);
insert into t1 values (1),(2),(3);
create table t2 (b int);
insert into t2 values (1),(10);
# rand() < 0 is never true, so the result is empty.
select a, row_number() over () from t1 where a=1 and rand() < 0;
a row_number() over ()
select a, row_number() over () from t1 where a=1 and rand() >= 0;
a row_number() over ()
1 1
# t2.b > 5 is true only for the row where t2.b is 10.
select b, (select row_number() over () from t1 where t1.a=1 and t2.b > 5) x
from t2;
b x
1 NULL
10 1
drop table t1, t2;
#
# End of 11.4 tests
#

View File

@@ -3150,3 +3150,28 @@ DROP TABLE t1;
--echo #
--echo # End of 10.11 tests
--echo #
--echo #
--echo # MDEV-41146: WHERE is not checked when every table is const and
--echo # the query needs a temporary table
--echo #
create table t1 (a int primary key);
insert into t1 values (1),(2),(3);
create table t2 (b int);
insert into t2 values (1),(10);
--echo # rand() < 0 is never true, so the result is empty.
select a, row_number() over () from t1 where a=1 and rand() < 0;
select a, row_number() over () from t1 where a=1 and rand() >= 0;
--echo # t2.b > 5 is true only for the row where t2.b is 10.
--sorted_result
select b, (select row_number() over () from t1 where t1.a=1 and t2.b > 5) x
from t2;
drop table t1, t2;
--echo #
--echo # End of 11.4 tests
--echo #

View File

@@ -4873,6 +4873,30 @@ DROP TABLE t1;
# End of 10.11 tests
#
#
# MDEV-41146: WHERE is not checked when every table is const and
# the query needs a temporary table
#
create table t1 (a int primary key);
insert into t1 values (1),(2),(3);
create table t2 (b int);
insert into t2 values (1),(10);
# rand() < 0 is never true, so the result is empty.
select a, row_number() over () from t1 where a=1 and rand() < 0;
a row_number() over ()
select a, row_number() over () from t1 where a=1 and rand() >= 0;
a row_number() over ()
1 1
# t2.b > 5 is true only for the row where t2.b is 10.
select b, (select row_number() over () from t1 where t1.a=1 and t2.b > 5) x
from t2;
b x
1 NULL
10 1
drop table t1, t2;
#
# End of 11.4 tests
#
#
# MDEV-23867: select crash in compute_window_func
#
set @save_sort_buffer_size=@@sort_buffer_size;

View File

@@ -1200,11 +1200,6 @@ void Explain_select::print_explain_json(Explain_query *query,
writer->add_member("outer_ref_condition");
write_item(writer, outer_ref_cond);
}
if (pseudo_bits_cond)
{
writer->add_member("pseudo_bits_condition");
write_item(writer, pseudo_bits_cond);
}
/* we do not print HAVING which always evaluates to TRUE */
if (having || (having_value == Item::COND_FALSE))

View File

@@ -248,7 +248,6 @@ public:
/* Expensive constant condition */
Item *exec_const_cond;
Item *outer_ref_cond;
Item *pseudo_bits_cond;
/* HAVING condition */
Item *having;

View File

@@ -570,7 +570,7 @@ void JOIN::init(THD *thd_arg, List<Item> &fields_arg,
first_select= sub_select;
group_sent= 0;
outer_ref_cond= pseudo_bits_cond= NULL;
outer_ref_cond= NULL;
in_to_exists_where= NULL;
in_to_exists_having= NULL;
emb_sjm_nest= NULL;
@@ -14588,40 +14588,37 @@ make_join_select(JOIN *join,SQL_SELECT *select,COND *cond)
join->exec_const_cond= const_cond;
}
if (join->table_count != join->const_tables)
{
COND *outer_ref_cond= make_cond_for_table(thd, cond,
join->const_table_map |
OUTER_REF_TABLE_BIT,
OUTER_REF_TABLE_BIT,
-1, FALSE, FALSE);
if (outer_ref_cond)
/*
Collect the conjuncts that are checked once for each execution
of the query, before the join reads a table that is not const.
Each refers to at least one pseudo table in pseudo_bits and to
no table that is not const.
A conjunct that refers to a column of an outer query is always
collected, because that column has one value for each
execution. rand() takes a new value on each call, and a
conjunct that calls it is checked for each row of the last top
level table that is neither const nor a materialized semijoin
nest. A join of only const tables has no such table and
returns at most one row, and its rand() conjuncts are collected
here.
*/
table_map pseudo_bits= OUTER_REF_TABLE_BIT;
if (join->only_const_tables())
pseudo_bits= PSEUDO_TABLE_BITS;
COND *pseudo_cond= make_cond_for_table(thd, cond,
join->const_table_map |
pseudo_bits,
pseudo_bits,
-1, FALSE, FALSE);
if (pseudo_cond)
{
add_cond_and_fix(thd, &outer_ref_cond, join->outer_ref_cond);
join->outer_ref_cond= outer_ref_cond;
add_cond_and_fix(thd, &pseudo_cond, join->outer_ref_cond);
join->outer_ref_cond= pseudo_cond;
Json_writer_object trace(thd);
trace.add("outer_ref_cond", outer_ref_cond);
}
else if (thd->is_error())
DBUG_RETURN(1);
}
else
{
COND *pseudo_bits_cond=
make_cond_for_table(thd, cond,
join->const_table_map |
PSEUDO_TABLE_BITS,
PSEUDO_TABLE_BITS,
-1, FALSE, FALSE);
if (pseudo_bits_cond)
{
add_cond_and_fix(thd, &pseudo_bits_cond,
join->pseudo_bits_cond);
join->pseudo_bits_cond= pseudo_bits_cond;
Json_writer_object trace(thd);
trace.add("pseudo_bits_cond", pseudo_bits_cond);
trace.add("outer_ref_cond", pseudo_cond);
}
else if (thd->is_error())
DBUG_RETURN(1);
@@ -24056,12 +24053,11 @@ do_select(JOIN *join, Procedure *procedure)
HAVING will be checked after processing aggregate functions,
But WHERE should checked here (we alredy have read tables).
Notice that make_join_select() splits all conditions in this case
into two groups exec_const_cond and outer_ref_cond.
If join->table_count == join->const_tables then it is
sufficient to check only the condition pseudo_bits_cond.
into two groups exec_const_cond and outer_ref_cond. exec_const_cond
is checked before the join starts, and it is sufficient to check
only the condition outer_ref_cond.
*/
DBUG_ASSERT(join->outer_ref_cond == NULL);
if (!join->pseudo_bits_cond || join->pseudo_bits_cond->val_bool())
if (!join->outer_ref_cond || join->outer_ref_cond->val_bool())
{
// HAVING will be checked by end_select
error= (*end_select)(join, 0, 0);
@@ -31667,7 +31663,6 @@ int JOIN::save_explain_data_intern(Explain_query *output,
xpl_sel->exec_const_cond= exec_const_cond;
xpl_sel->outer_ref_cond= outer_ref_cond;
xpl_sel->pseudo_bits_cond= pseudo_bits_cond;
if (tmp_having)
xpl_sel->having= tmp_having;
else

View File

@@ -1648,8 +1648,15 @@ public:
ORDER *order, *group_list, *proc_param; //hold parameters of mysql_select
COND *conds; // ---"---
Item *conds_history; // store WHERE for explain
COND *outer_ref_cond; ///<part of conds containing only outer references
COND *pseudo_bits_cond; // part of conds containing special bita
/*
Condition to be checked once for every join execution, before reading any
non-const tables. (Do not confuse this with exec_const_cond which is
constant across all executions). Normally, this is condition that refers
to outer tables only.
For degenerate joins that only have constant tables, this includes
conditions with RAND_TABLE_BIT.
*/
Item *outer_ref_cond;
TABLE_LIST *tables_list; ///<hold 'tables' parameter of mysql_select
List<TABLE_LIST> *join_list; ///< list of joined tables in reverse order
COND_EQUAL *cond_equal;