8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-02-02 10:40:38 +01:00

Implement #6992: Transform OUTER joins into INNER ones if the WHERE condition violates the outer join rules

This commit is contained in:
Dmitry Yemanov 2022-10-16 11:39:41 +03:00
parent cd49a7644f
commit 8938222964
7 changed files with 136 additions and 29 deletions

View File

@ -96,9 +96,11 @@ public:
virtual BoolExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& streams) const
{
return blrOp == blr_equiv ? true : BoolExprNode::possiblyUnknown();
return (blrOp == blr_equiv) ?
arg1->containsAnyStream(streams) || arg2->containsAnyStream(streams) :
BoolExprNode::possiblyUnknown(streams);
}
virtual BoolExprNode* copy(thread_db* tdbb, NodeCopier& copier) const;
@ -144,9 +146,9 @@ public:
virtual BoolExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& streams) const
{
return true;
return arg->containsAnyStream(streams);
}
virtual BoolExprNode* copy(thread_db* tdbb, NodeCopier& copier) const;
@ -177,11 +179,6 @@ public:
virtual BoolExprNode* dsqlPass(DsqlCompilerScratch* dsqlScratch);
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual bool possiblyUnknown() const
{
return true;
}
virtual BoolExprNode* copy(thread_db* tdbb, NodeCopier& copier) const;
virtual BoolExprNode* pass1(thread_db* tdbb, CompilerScratch* csb);
virtual bool execute(thread_db* tdbb, Request* request) const;
@ -225,7 +222,7 @@ public:
return true;
}
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return true;
}

View File

@ -289,14 +289,14 @@ bool ExprNode::sameAs(const ExprNode* other, bool ignoreStreams) const
return true;
}
bool ExprNode::possiblyUnknown() const
bool ExprNode::possiblyUnknown(const StreamList& streams) const
{
NodeRefsHolder holder;
getChildren(holder, false);
for (auto i : holder.refs)
{
if (*i && (*i)->possiblyUnknown())
if (*i && (*i)->possiblyUnknown(streams))
return true;
}

View File

@ -312,9 +312,15 @@ public:
virtual ValueExprNode* pass2(thread_db* tdbb, CompilerScratch* csb);
virtual dsc* execute(thread_db* tdbb, Request* request) const;
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& streams) const
{
return true;
for (const auto& item : args->items)
{
if (item->containsAnyStream(streams))
return true;
}
return false;
}
public:
@ -776,7 +782,7 @@ public:
dsqlDesc = desc;
}
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return false;
}
@ -1650,7 +1656,7 @@ public:
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc);
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return false;
}
@ -1913,7 +1919,7 @@ public:
return false;
}
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return true;
}
@ -2122,7 +2128,7 @@ public:
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc);
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return true;
}
@ -2171,9 +2177,11 @@ public:
virtual void genBlr(DsqlCompilerScratch* dsqlScratch);
virtual void make(DsqlCompilerScratch* dsqlScratch, dsc* desc);
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& streams) const
{
return true;
return condition->containsAnyStream(streams) ||
trueValue->containsAnyStream(streams) ||
falseValue->containsAnyStream(streams);
}
virtual void getDesc(thread_db* tdbb, CompilerScratch* csb, dsc* desc);

View File

@ -658,7 +658,7 @@ public:
}
// Check if expression could return NULL or expression can turn NULL into a true/false.
virtual bool possiblyUnknown() const;
virtual bool possiblyUnknown(const StreamList& streams) const;
// Verify if this node is allowed in an unmapped boolean.
virtual bool unmappable(const MapNode* mapNode, StreamType shellStream) const;
@ -666,12 +666,26 @@ public:
// Return all streams referenced by the expression.
virtual void collectStreams(SortedStreamList& streamList) const;
virtual bool containsStream(StreamType stream) const
bool containsStream(StreamType stream) const
{
SortedStreamList streams;
collectStreams(streams);
SortedStreamList nodeStreams;
collectStreams(nodeStreams);
return streams.exist(stream);
return nodeStreams.exist(stream);
}
bool containsAnyStream(const StreamList& streams) const
{
SortedStreamList nodeStreams;
collectStreams(nodeStreams);
for (const auto stream : streams)
{
if (nodeStreams.exist(stream))
return true;
}
return false;
}
virtual bool dsqlMatch(DsqlCompilerScratch* dsqlScratch, const ExprNode* other, bool ignoreMapCast) const;
@ -1026,7 +1040,7 @@ public:
virtual AggNode* pass2(thread_db* tdbb, CompilerScratch* csb);
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return true;
}
@ -1155,12 +1169,13 @@ public:
virtual RecordSourceNode* pass2(thread_db* tdbb, CompilerScratch* csb) = 0;
virtual void pass2Rse(thread_db* tdbb, CompilerScratch* csb) = 0;
virtual bool containsStream(StreamType checkStream) const = 0;
virtual void genBlr(DsqlCompilerScratch* /*dsqlScratch*/)
{
fb_assert(false);
}
virtual bool possiblyUnknown() const
virtual bool possiblyUnknown(const StreamList& /*streams*/) const
{
return true;
}

View File

@ -2810,10 +2810,16 @@ RseNode* RseNode::pass1(thread_db* tdbb, CompilerScratch* csb)
ValueExprNode* skip = rse_skip;
PlanNode* plan = rse_plan;
if (!rse_jointype)
csb->csb_inner_booleans.push(rse_boolean);
// zip thru RseNode expanding views and inner joins
for (auto sub : rse_relations)
processSource(tdbb, csb, this, sub, &boolean, stack);
if (!rse_jointype)
csb->csb_inner_booleans.pop();
// Now, rebuild the RseNode block.
rse_relations.resize(stack.getCount());
@ -2880,6 +2886,63 @@ RseNode* RseNode::pass1(thread_db* tdbb, CompilerScratch* csb)
void RseNode::pass1Source(thread_db* tdbb, CompilerScratch* csb, RseNode* rse,
BoolExprNode** boolean, RecordSourceNodeStack& stack)
{
if (rse_jointype)
{
// Check whether any of the upper level booleans (those belonging to the WHERE clause)
// is able to filter out rows from the "inner" streams. If this is the case,
// transform the join type accordingly (LEFT -> INNER, FULL -> LEFT or INNER).
fb_assert(rse_relations.getCount() == 2);
const auto rse1 = rse_relations[0];
const auto rse2 = rse_relations[1];
fb_assert(rse1 && rse2);
StreamList streams;
// First check the left stream of the full outer join
if (rse_jointype == blr_full)
{
rse1->computeRseStreams(streams);
for (const auto boolean : csb->csb_inner_booleans)
{
if (boolean &&
boolean->containsAnyStream(streams) &&
!boolean->possiblyUnknown(streams))
{
rse_jointype = blr_left;
break;
}
}
}
// Then check the right stream of both left and full outer joins
streams.clear();
rse2->computeRseStreams(streams);
for (const auto boolean : csb->csb_inner_booleans)
{
if (boolean &&
boolean->containsAnyStream(streams) &&
!boolean->possiblyUnknown(streams))
{
if (rse_jointype == blr_full)
{
// We should transform FULL join to RIGHT join,
// but as we don't allow them inside the engine
// just swap the sides and insist it's LEFT join
std::swap(rse_relations[0], rse_relations[1]);
rse_jointype = blr_left;
}
else
rse_jointype = blr_inner;
break;
}
}
}
// in the case of an RseNode, it is possible that a new RseNode will be generated,
// so wait to process the source before we push it on the stack (bug 8039)

View File

@ -479,6 +479,7 @@ public:
csb_current_nodes(p),
csb_current_for_nodes(p),
csb_computing_fields(p),
csb_inner_booleans(p),
csb_variables_used_in_subroutines(p),
csb_pool(p),
csb_map_field_info(p),
@ -557,6 +558,7 @@ public:
// candidates within whose scope we are
Firebird::Array<ForNode*> csb_current_for_nodes;
Firebird::SortedArray<jrd_fld*> csb_computing_fields; // Computed fields being compiled
Firebird::Array<BoolExprNode*> csb_inner_booleans; // Inner booleans at the current scope
Firebird::SortedArray<USHORT> csb_variables_used_in_subroutines;
StreamType csb_n_stream; // Next available stream
USHORT csb_msg_number; // Highest used message number

View File

@ -682,9 +682,31 @@ RecordSource* Optimizer::compile(BoolExprNodeStack* parentStack)
for (BoolExprNodeStack::iterator iter(*parentStack);
iter.hasData() && conjunctCount < MAX_CONJUNCTS; ++iter)
{
BoolExprNode* const node = iter.object();
const auto node = iter.object();
if (!isInnerJoin() && node->possiblyUnknown())
StreamList streams;
if (!isInnerJoin())
{
fb_assert(rse->rse_relations.getCount() == 2);
const auto rse1 = rse->rse_relations[0];
const auto rse2 = rse->rse_relations[1];
fb_assert(rse1 && rse2);
if (isFullJoin())
{
rse1->computeRseStreams(streams);
rse2->computeRseStreams(streams);
}
else // left outer join
{
fb_assert(rse->rse_jointype == blr_left);
rse2->computeRseStreams(streams);
}
}
if (streams.hasData() && node->possiblyUnknown(streams))
{
// parent missing conjunctions shouldn't be
// distributed to FULL OUTER JOIN streams at all