1
0
mirror of https://github.com/mariadb-corporation/mariadb-columnstore-engine.git synced 2025-07-29 08:21:15 +03:00

Merge pull request #1912 from tntnatbry/MCOL-4680-dev

MCOL-4680 FROM subquery containing nested joins returns an error.
This commit is contained in:
Roman Nozdrin
2021-05-06 13:52:36 +03:00
committed by GitHub
6 changed files with 273 additions and 272 deletions

View File

@ -134,6 +134,15 @@ public:
namespace cal_impl_if
{
CalpontSystemCatalog::TableAliasName makeTableAliasName(TABLE_LIST* table)
{
return make_aliasview(
(table->db.length ? table->db.str : ""),
(table->table_name.length ? table->table_name.str : ""),
(table->alias.length ? table->alias.str : ""),
getViewName(table), true, lower_case_table_names);
}
//@bug5228. need to escape backtick `
string escapeBackTick(const char* str)
{
@ -1239,207 +1248,73 @@ void debug_walk(const Item* item, void* arg)
}
#endif
void buildNestedTableOuterJoin(gp_walk_info& gwi, TABLE_LIST* table_ptr)
void buildNestedJoinLeafTables(List<TABLE_LIST>& join_list,
std::set<execplan::CalpontSystemCatalog::TableAliasName>& leafTables)
{
TABLE_LIST* table;
List_iterator<TABLE_LIST> li(table_ptr->nested_join->join_list);
TABLE_LIST *table;
List_iterator<TABLE_LIST> li(join_list);
while ((table = li++))
{
gwi.innerTables.clear();
if (table->outer_join)
{
CalpontSystemCatalog::TableAliasName ta = make_aliasview(
(table->db.length ? table->db.str : ""),
(table->table_name.length ? table->table_name.str : ""),
(table->alias.length ? table->alias.str : ""),
getViewName(table), true, lower_case_table_names);
gwi.innerTables.insert(ta);
}
if (table->nested_join)
buildNestedJoinLeafTables(table->nested_join->join_list, leafTables);
else
{
TABLE_LIST* tab;
List_iterator<TABLE_LIST> li(table->nested_join->join_list);
while ((tab = li++))
{
CalpontSystemCatalog::TableAliasName ta = make_aliasview(
(tab->db.length ? tab->db.str : ""),
(tab->table_name.length ? tab->table_name.str : ""),
(tab->alias.length ? tab->alias.str : ""),
getViewName(tab), true, lower_case_table_names);
gwi.innerTables.insert(ta);
}
}
if (table->on_expr)
{
Item_cond* expr = reinterpret_cast<Item_cond*>(table->on_expr);
#ifdef DEBUG_WALK_COND
expr->traverse_cond(debug_walk, &gwi, Item::POSTFIX);
#endif
expr->traverse_cond(gp_walk, &gwi, Item::POSTFIX);
}
if (table->nested_join && &(table->nested_join->join_list))
{
buildNestedTableOuterJoin(gwi, table);
CalpontSystemCatalog::TableAliasName tan = makeTableAliasName(table);
leafTables.insert(tan);
}
}
}
uint32_t buildOuterJoin(gp_walk_info& gwi, SELECT_LEX& select_lex)
uint32_t buildJoin(gp_walk_info& gwi, List<TABLE_LIST>& join_list,
std::stack<execplan::ParseTree*>& outerJoinStack)
{
// check non-collapsed outer join
// this set contains all processed embedded joins. duplicate joins are ignored
set<TABLE_LIST*> embeddingSet;
TABLE_LIST* table_ptr = select_lex.get_table_list();
TABLE_LIST *table;
List_iterator<TABLE_LIST> li(join_list);
while ((table = li++))
{
// Make sure we don't process the derived table nests again,
// they were already handled by FromSubQuery::transform()
if (table->nested_join && !table->derived)
buildJoin(gwi, table->nested_join->join_list, outerJoinStack);
if (table->on_expr)
{
Item_cond* expr = reinterpret_cast<Item_cond*>(table->on_expr);
if (table->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT))
{
// inner tables block
gp_walk_info gwi_outer = gwi;
gwi_outer.subQuery = NULL;
gwi_outer.hasSubSelect = false;
vector <Item_field*> tmpVec;
for (; table_ptr; table_ptr = table_ptr->next_local)
{
gwi_outer.innerTables.clear();
clearStacks(gwi_outer);
gwi_outer.subQuery = NULL;
gwi_outer.hasSubSelect = false;
// View is already processed in view::transform
// @bug5319. view is sometimes treated as derived table and
// fromSub::transform does not build outer join filters.
if (!table_ptr->derived && table_ptr->view && !gwi.subQuery)
continue;
CalpontSystemCatalog:: TableAliasName tan = make_aliasview(
(table_ptr->db.length ? table_ptr->db.str : ""),
(table_ptr->table_name.length ? table_ptr->table_name.str : ""),
(table_ptr->alias.length ? table_ptr->alias.str : ""),
getViewName(table_ptr), true, lower_case_table_names);
if ((table_ptr->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) && table_ptr->on_expr)
// recursively build the leaf tables for this nested join node
if (table->nested_join)
buildNestedJoinLeafTables(table->nested_join->join_list,
gwi_outer.innerTables);
else // this is a leaf table
{
// inner tables block
Item_cond* expr = reinterpret_cast<Item_cond*>(table_ptr->on_expr);
CalpontSystemCatalog::TableAliasName tan = makeTableAliasName(table);
gwi_outer.innerTables.insert(tan);
if (table_ptr->nested_join && &(table_ptr->nested_join->join_list))
{
TABLE_LIST* table;
List_iterator<TABLE_LIST> li(table_ptr->nested_join->join_list);
while ((table = li++))
{
CalpontSystemCatalog::TableAliasName ta = make_aliasview(
(table->db.length ? table->db.str : ""),
(table->table_name.length ? table->table_name.str : ""),
(table->alias.length ? table->alias.str : ""),
getViewName(table), true, lower_case_table_names);
gwi_outer.innerTables.insert(ta);
}
}
#ifdef DEBUG_WALK_COND
if (table_ptr->alias.length)
cerr << table_ptr->alias.str;
else if (table_ptr->alias.length)
cerr << table_ptr->alias.str;
cerr << " outer table expression: " << endl;
expr->traverse_cond(debug_walk, &gwi_outer, Item::POSTFIX);
#endif
expr->traverse_cond(gp_walk, &gwi_outer, Item::POSTFIX);
}
else if (table_ptr->nested_join && &(table_ptr->nested_join->join_list))
{
buildNestedTableOuterJoin(gwi_outer, table_ptr);
}
// this part is ambiguous. Not quite sure how MySQL's lay out the outer join filters in the structure
else if (table_ptr->embedding && table_ptr->embedding->outer_join && table_ptr->embedding->on_expr)
{
// all the tables in nested_join are inner tables.
TABLE_LIST* table;
List_iterator<TABLE_LIST> li(table_ptr->embedding->nested_join->join_list);
gwi_outer.innerTables.clear();
while ((table = li++))
{
CalpontSystemCatalog:: TableAliasName ta = make_aliasview(
(table->db.length ? table->db.str : ""),
(table->table_name.length ? table->table_name.str : ""),
(table->alias.length ? table->alias.str : ""),
getViewName(table), true, lower_case_table_names);
gwi_outer.innerTables.insert(ta);
}
if (embeddingSet.find(table_ptr->embedding) != embeddingSet.end())
continue;
embeddingSet.insert(table_ptr->embedding);
Item_cond* expr = reinterpret_cast<Item_cond*>(table_ptr->embedding->on_expr);
#ifdef DEBUG_WALK_COND
cerr << "inner tables: " << endl;
set<CalpontSystemCatalog::TableAliasName>::const_iterator it;
for (it = gwi_outer.innerTables.begin(); it != gwi_outer.innerTables.end(); ++it)
cerr << (*it) << " ";
cerr << endl;
cerr << " outer table expression: " << endl;
expr->traverse_cond(debug_walk, &gwi_outer, Item::POSTFIX);
#endif
expr->traverse_cond(gp_walk, &gwi_outer, Item::POSTFIX);
}
// *DRRTUY This part is to be removed in 1.4.3
#if 0
// @bug 2849
/*else if (table_ptr->embedding && table_ptr->embedding->nested_join)
{
// if this is dervied table process phase, mysql may have not developed the plan
// completely. Return and let it finish. It will come to rnd_init again.
if (table_ptr->embedding->is_natural_join && table_ptr->derived)
{
if (gwi.thd->derived_tables_processing)
{
// MCOL-2178 isUnion member only assigned, never used
//MIGR::infinidb_vtable.isUnion = false;
gwi.cs_vtable_is_update_with_derive = true;
return -1;
}
}
if (embeddingSet.find(table_ptr->embedding) != embeddingSet.end())
continue;
gwi_outer.innerTables.clear();
gwi_outer.innerTables.insert(tan);
embeddingSet.insert(table_ptr->embedding);
List<TABLE_LIST>* inners = &(table_ptr->embedding->nested_join->join_list);
List_iterator_fast<TABLE_LIST> li(*inners);
TABLE_LIST* curr;
while ((curr = li++))
{
if (curr->on_expr)
{
if (!curr->outer_join) // only handle nested JOIN for now
{
gwi_outer.innerTables.clear();
Item_cond* expr = reinterpret_cast<Item_cond*>(curr->on_expr);
#ifdef DEBUG_WALK_COND
expr->traverse_cond(debug_walk, &gwi_outer, Item::POSTFIX);
#endif
expr->traverse_cond(gp_walk, &gwi_outer, Item::POSTFIX);
}
}
}
} */
#endif
// Error out subquery in outer join on filter for now
if (gwi_outer.hasSubSelect)
{
@ -1448,8 +1323,9 @@ uint32_t buildOuterJoin(gp_walk_info& gwi, SELECT_LEX& select_lex)
setError(gwi.thd, ER_INTERNAL_ERROR, gwi.parseErrorText);
return -1;
}
// build outerjoinon filter
ParseTree* filters = NULL, *ptp = NULL, *lhs = NULL;
ParseTree* filters = NULL, *ptp = NULL, *rhs = NULL;
while (!gwi_outer.ptWorkStack.empty())
{
@ -1460,10 +1336,10 @@ uint32_t buildOuterJoin(gp_walk_info& gwi, SELECT_LEX& select_lex)
break;
ptp = new ParseTree(new LogicOperator("and"));
ptp->right(filters);
lhs = gwi_outer.ptWorkStack.top();
ptp->left(filters);
rhs = gwi_outer.ptWorkStack.top();
gwi_outer.ptWorkStack.pop();
ptp->left(lhs);
ptp->right(rhs);
gwi_outer.ptWorkStack.push(ptp);
}
@ -1473,11 +1349,21 @@ uint32_t buildOuterJoin(gp_walk_info& gwi, SELECT_LEX& select_lex)
SPTP on_sp(filters);
OuterJoinOnFilter* onFilter = new OuterJoinOnFilter(on_sp);
ParseTree* pt = new ParseTree(onFilter);
gwi.ptWorkStack.push(pt);
outerJoinStack.push(pt);
}
}
else // inner join
{
expr->traverse_cond(gp_walk, &gwi, Item::POSTFIX);
#ifdef DEBUG_WALK_COND
cerr << " inner join expression: " << endl;
expr->traverse_cond(debug_walk, &gwi, Item::POSTFIX);
#endif
}
}
}
embeddingSet.clear();
return 0;
}
@ -6289,16 +6175,13 @@ void setExecutionParams(gp_walk_info &gwi, SCSEP &csep)
* FROM part of the query.
* isUnion tells that CS processes FROM taken from UNION UNIT.
* The notion is described in MDB code.
* on_expr_list ON expressions used in OUTER JOINs. These are
* later used in processWhere()
* RETURNS
* error id as an int
***********************************************************/
int processFrom(bool &isUnion,
SELECT_LEX &select_lex,
gp_walk_info &gwi,
SCSEP &csep,
List<Item> &on_expr_list)
SCSEP &csep)
{
// populate table map and trigger syscolumn cache for all the tables (@bug 1637).
// all tables on FROM list must have at least one col in colmap
@ -6328,12 +6211,6 @@ int processFrom(bool &isUnion,
return ER_CHECK_NOT_IMPLEMENTED;
}
// Save on_expr to use it for WHERE processing
if (!(table_ptr->outer_join & (JOIN_TYPE_LEFT | JOIN_TYPE_RIGHT)) && table_ptr->on_expr)
{
on_expr_list.push_back(table_ptr->on_expr);
}
string viewName = getViewName(table_ptr);
if (lower_case_table_names)
{
@ -6483,15 +6360,12 @@ int processFrom(bool &isUnion,
* DESCRIPTION:
* This function processes conditions from either JOIN->conds
* or SELECT_LEX->where|prep_where
* on_expr_list ON expressions used in OUTER JOINs. These are
* populated used in processFrom()
* RETURNS
* error id as an int
***********************************************************/
int processWhere(SELECT_LEX &select_lex,
gp_walk_info &gwi,
SCSEP &csep,
List<Item> &on_expr_list,
const std::vector<COND*>& condStack)
{
JOIN* join = select_lex.join;
@ -6609,27 +6483,6 @@ int processWhere(SELECT_LEX &select_lex,
(dynamic_cast<ConstantColumn*>(gwi.rcWorkStack.top()))->timeZone(gwi.thd->variables.time_zone->get_name()->ptr());
}
#ifdef DEBUG_WALK_COND
std::cerr << "------------------ ON_EXPR -----------------------" << endl;
#endif
// MCOL-3593 MDB now doesn't rewrite and/or consolidate ON and WHERE expressions
// and CS handles INNER ON expressions here.
if (!on_expr_list.is_empty())
{
List_iterator<Item> on_expr_it(on_expr_list);
Item_cond *on_expr = NULL;
while((on_expr = reinterpret_cast<Item_cond*>(on_expr_it++)))
{
on_expr->traverse_cond(gp_walk, &gwi, Item::POSTFIX);
#ifdef DEBUG_WALK_COND
on_expr->traverse_cond(debug_walk, &gwi, Item::POSTFIX);
#endif
}
}
#ifdef DEBUG_WALK_COND
std::cerr << "-------------------------------------------------\n" << std::endl;
#endif
// ZZ - the followinig debug shows the structure of nested outer join. should
// use a recursive function.
#ifdef OUTER_JOIN_DEBUG
@ -6694,26 +6547,31 @@ int processWhere(SELECT_LEX &select_lex,
}
#endif
uint32_t failed = buildOuterJoin(gwi, select_lex);
uint32_t failed = 0;
if (failed) return failed;
// InfiniDB bug5764 requires outer joins to be appended to the
// end of the filter list. This causes outer join filters to
// have a higher join id than inner join filters.
// TODO MCOL-4680 Figure out why this is the case, and possibly
// eliminate this requirement.
std::stack<execplan::ParseTree*> outerJoinStack;
// @bug5764. build outer join for view, make sure outerjoin filter is appended
// to the end of the filter list.
for (uint i = 0; i < gwi.viewList.size(); i++)
{
failed = gwi.viewList[i]->processOuterJoin(gwi);
if (failed)
break;
}
if (failed != 0)
if ((failed = buildJoin(gwi, select_lex.top_join_list, outerJoinStack)))
return failed;
if (gwi.subQuery)
{
for (uint i = 0; i < gwi.viewList.size(); i++)
{
if ((failed = gwi.viewList[i]->processJoin(gwi, outerJoinStack)))
return failed;
}
}
ParseTree* filters = NULL;
ParseTree* outerJoinFilters = NULL;
ParseTree* ptp = NULL;
ParseTree* lhs = NULL;
ParseTree* rhs = NULL;
// @bug 2932. for "select * from region where r_name" case. if icp not null and
// ptWorkStack empty, the item is in rcWorkStack.
@ -6734,13 +6592,45 @@ int processWhere(SELECT_LEX &select_lex,
break;
ptp = new ParseTree(new LogicOperator("and"));
ptp->right(filters);
lhs = gwi.ptWorkStack.top();
ptp->left(filters);
rhs = gwi.ptWorkStack.top();
gwi.ptWorkStack.pop();
ptp->left(lhs);
ptp->right(rhs);
gwi.ptWorkStack.push(ptp);
}
while (!outerJoinStack.empty())
{
outerJoinFilters = outerJoinStack.top();
outerJoinStack.pop();
if (outerJoinStack.empty())
break;
ptp = new ParseTree(new LogicOperator("and"));
ptp->left(outerJoinFilters);
rhs = outerJoinStack.top();
outerJoinStack.pop();
ptp->right(rhs);
outerJoinStack.push(ptp);
}
// Append outer join filters at the end of inner join filters.
// JLF_ExecPlanToJobList::walkTree processes ParseTree::left
// before ParseTree::right which is what we intend to do in the
// below.
if (filters && outerJoinFilters)
{
ptp = new ParseTree(new LogicOperator("and"));
ptp->left(filters);
ptp->right(outerJoinFilters);
filters = ptp;
}
else if (outerJoinFilters)
{
filters = outerJoinFilters;
}
if (filters)
{
csep->filters(filters);
@ -6928,15 +6818,15 @@ int getSelectPlan(gp_walk_info& gwi, SELECT_LEX& select_lex,
CalpontSelectExecutionPlan::SelectList derivedTbList;
// @bug 1796. Remember table order on the FROM list.
gwi.clauseType = FROM;
List<Item> on_expr_list;
if ((rc = processFrom(isUnion, select_lex, gwi, csep, on_expr_list)))
if ((rc = processFrom(isUnion, select_lex, gwi, csep)))
{
return rc;
}
bool unionSel = (!isUnion && select_lex.master_unit()->is_unit_op()) ? true : false;
gwi.clauseType = WHERE;
if ((rc = processWhere(select_lex, gwi, csep, on_expr_list, condStack)))
if ((rc = processWhere(select_lex, gwi, csep, condStack)))
{
return rc;
}
@ -8628,26 +8518,36 @@ int getGroupPlan(gp_walk_info& gwi, SELECT_LEX& select_lex, SCSEP& csep, cal_gro
SELECT_LEX tmp_select_lex;
tmp_select_lex.table_list.first = gi.groupByTables;
uint32_t failed = buildOuterJoin(gwi, tmp_select_lex);
// InfiniDB bug5764 requires outer joins to be appended to the
// end of the filter list. This causes outer join filters to
// have a higher join id than inner join filters.
// TODO MCOL-4680 Figure out why this is the case, and possibly
// eliminate this requirement.
std::stack<execplan::ParseTree*> outerJoinStack;
uint32_t failed = buildJoin(gwi, tmp_select_lex.top_join_list, outerJoinStack);
if (failed) return failed;
// @bug5764. build outer join for view, make sure outerjoin filter is appended
// to the end of the filter list.
if (gwi.subQuery)
{
for (uint i = 0; i < gwi.viewList.size(); i++)
{
failed = gwi.viewList[i]->processOuterJoin(gwi);
failed = gwi.viewList[i]->processJoin(gwi, outerJoinStack);
if (failed)
break;
}
}
if (failed != 0)
return failed;
ParseTree* filters = NULL;
ParseTree* outerJoinFilters = NULL;
ParseTree* ptp = NULL;
ParseTree* lhs = NULL;
ParseTree* rhs = NULL;
// @bug 2932. for "select * from region where r_name" case. if icp not null and
// ptWorkStack empty, the item is in rcWorkStack.
@ -8668,15 +8568,45 @@ int getGroupPlan(gp_walk_info& gwi, SELECT_LEX& select_lex, SCSEP& csep, cal_gro
break;
ptp = new ParseTree(new LogicOperator("and"));
//ptp->left(filters);
ptp->right(filters);
lhs = gwi.ptWorkStack.top();
ptp->left(filters);
rhs = gwi.ptWorkStack.top();
gwi.ptWorkStack.pop();
//ptp->right(rhs);
ptp->left(lhs);
ptp->right(rhs);
gwi.ptWorkStack.push(ptp);
}
while (!outerJoinStack.empty())
{
outerJoinFilters = outerJoinStack.top();
outerJoinStack.pop();
if (outerJoinStack.empty())
break;
ptp = new ParseTree(new LogicOperator("and"));
ptp->left(outerJoinFilters);
rhs = outerJoinStack.top();
outerJoinStack.pop();
ptp->right(rhs);
outerJoinStack.push(ptp);
}
// Append outer join filters at the end of inner join filters.
// JLF_ExecPlanToJobList::walkTree processes ParseTree::left
// before ParseTree::right which is what we intend to do in the
// below.
if (filters && outerJoinFilters)
{
ptp = new ParseTree(new LogicOperator("and"));
ptp->left(filters);
ptp->right(outerJoinFilters);
filters = ptp;
}
else if (outerJoinFilters)
{
filters = outerJoinFilters;
}
if (filters)
{
csep->filters(filters);

View File

@ -373,7 +373,7 @@ bool buildRowColumnFilter(gp_walk_info* gwip, execplan::RowColumn* rhs, execplan
bool buildPredicateItem(Item_func* ifp, gp_walk_info* gwip);
void collectAllCols(gp_walk_info& gwi, Item_field* ifp);
void buildSubselectFunc(Item_func* ifp, gp_walk_info* gwip);
uint32_t buildOuterJoin(gp_walk_info& gwi, SELECT_LEX& select_lex);
uint32_t buildJoin(gp_walk_info& gwi, List<TABLE_LIST>& join_list, std::stack<execplan::ParseTree*>& outerJoinStack);
std::string getViewName(TABLE_LIST* table_ptr);
bool buildConstPredicate(Item_func* ifp, execplan::ReturnedColumn* rhs, gp_walk_info* gwip);
execplan::CalpontSystemCatalog::ColType fieldType_MysqlToIDB (const Field* field);

View File

@ -44,7 +44,8 @@ using namespace execplan;
namespace cal_impl_if
{
extern uint32_t buildOuterJoin(gp_walk_info& gwi, SELECT_LEX& select_lex);
extern uint32_t buildJoin(gp_walk_info& gwi, List<TABLE_LIST>& join_list,
std::stack<execplan::ParseTree*>& outerJoinStack);
extern string getViewName(TABLE_LIST* table_ptr);
CalpontSystemCatalog::TableAliasName& View::viewName()
@ -182,9 +183,9 @@ void View::transform()
}
}
uint32_t View::processOuterJoin(gp_walk_info& gwi)
uint32_t View::processJoin(gp_walk_info& gwi, std::stack<execplan::ParseTree*>& outerJoinStack)
{
return buildOuterJoin(gwi, fSelect);
return buildJoin(gwi, fSelect.top_join_list, outerJoinStack);
}
}

View File

@ -53,7 +53,7 @@ public:
parent select.
*/
void transform();
uint32_t processOuterJoin(gp_walk_info& gwi);
uint32_t processJoin(gp_walk_info& gwi, std::stack<execplan::ParseTree*>&);
private:
SELECT_LEX fSelect;

View File

@ -0,0 +1,29 @@
#
# FROM subquery containing nested joins returns an error
#
DROP DATABASE IF EXISTS mcol4680;
CREATE DATABASE mcol4680;
USE mcol4680;
create table t1 (a int);
insert into t1 values (1), (2), (3);
create table t2 (a int);
insert into t2 values (2);
create table t3 (a int);
create table t4 (a int);
create table t5 (a int);
select * from
(
select t1.a as col1, t2.a as col2 from
t1 left join
(
(t2 left join t3 on t2.a=t3.a) left join
(t4 left join t5 on t4.a=t5.a)
on t2.a=t4.a
)
on t1.a=t2.a
) h order by col1;
col1 col2
1 NULL
2 2
3 NULL
DROP DATABASE mcol4680;

View File

@ -0,0 +1,41 @@
--source ../include/have_columnstore.inc
--source ctype_cmp_combinations.inc
--source default_storage_engine_by_combination.inc
--echo #
--echo # FROM subquery containing nested joins returns an error
--echo #
--disable_warnings
DROP DATABASE IF EXISTS mcol4680;
--enable_warnings
CREATE DATABASE mcol4680;
USE mcol4680;
create table t1 (a int);
insert into t1 values (1), (2), (3);
create table t2 (a int);
insert into t2 values (2);
create table t3 (a int);
create table t4 (a int);
create table t5 (a int);
select * from
(
select t1.a as col1, t2.a as col2 from
t1 left join
(
(t2 left join t3 on t2.a=t3.a) left join
(t4 left join t5 on t4.a=t5.a)
on t2.a=t4.a
)
on t1.a=t2.a
) h order by col1;
DROP DATABASE mcol4680;