8
0
mirror of https://github.com/FirebirdSQL/firebird.git synced 2025-01-22 18:43:02 +01:00

Frontport the reworked implementation of the full outer join

This commit is contained in:
Dmitry Yemanov 2025-01-15 10:08:42 +03:00
parent e4c56776c0
commit fe178a1404
5 changed files with 59 additions and 23 deletions

View File

@ -922,7 +922,7 @@ public:
RecordSource* generate(); RecordSource* generate();
private: private:
RecordSource* process(const JoinType joinType); RecordSource* process(StreamList* outerStreams = nullptr);
thread_db* const tdbb; thread_db* const tdbb;
Optimizer* const optimizer; Optimizer* const optimizer;

View File

@ -94,15 +94,25 @@ OuterJoin::OuterJoin(thread_db* aTdbb, Optimizer* opt,
RecordSource* OuterJoin::generate() RecordSource* OuterJoin::generate()
{ {
const auto outerJoinRsb = process(OUTER_JOIN);
if (!optimizer->isFullJoin()) if (!optimizer->isFullJoin())
return outerJoinRsb; {
fb_assert(optimizer->isLeftJoin());
return process();
}
// A FULL JOIN B is currently implemented similar to (A LEFT JOIN B) UNION ALL (B ANTI-JOIN A). StreamList outerStreams;
const auto outerJoinRsb = process(&outerStreams);
// A FULL JOIN B is currently implemented similar to:
//
// (A LEFT JOIN B)
// UNION ALL
// (B LEFT JOIN A WHERE A.* IS NULL)
//
// See also FullOuterJoin class implementation.
// //
// At this point we already have the first part -- (A LEFT JOIN B) -- ready, // At this point we already have the first part -- (A LEFT JOIN B) -- ready,
// so just swap the sides and make an anti-join. // so just swap the sides and make the second (inverted) join.
auto& outerStream = joinStreams[0]; auto& outerStream = joinStreams[0];
auto& innerStream = joinStreams[1]; auto& innerStream = joinStreams[1];
@ -131,15 +141,15 @@ RecordSource* OuterJoin::generate()
iter.reset(CMP_clone_node_opt(tdbb, csb, iter)); iter.reset(CMP_clone_node_opt(tdbb, csb, iter));
} }
const auto antiJoinRsb = process(ANTI_JOIN); const auto antiJoinRsb = process();
// Allocate and return the final join record source // Allocate and return the final join record source
return FB_NEW_POOL(getPool()) FullOuterJoin(csb, outerJoinRsb, antiJoinRsb); return FB_NEW_POOL(getPool()) FullOuterJoin(csb, outerJoinRsb, antiJoinRsb, outerStreams);
} }
RecordSource* OuterJoin::process(const JoinType joinType) RecordSource* OuterJoin::process(StreamList* outerStreams)
{ {
BoolExprNode* boolean = nullptr; BoolExprNode* boolean = nullptr;
@ -153,8 +163,7 @@ RecordSource* OuterJoin::process(const JoinType joinType)
{ {
fb_assert(!outerStream.rsb); fb_assert(!outerStream.rsb);
outerStream.rsb = optimizer->generateRetrieval(outerStream.number, outerStream.rsb = optimizer->generateRetrieval(outerStream.number,
optimizer->isFullJoin() ? nullptr : sortPtr, optimizer->isFullJoin() ? nullptr : sortPtr, true, false, &boolean);
true, false, &boolean);
} }
else else
{ {
@ -173,13 +182,15 @@ RecordSource* OuterJoin::process(const JoinType joinType)
boolean = optimizer->composeBoolean(); boolean = optimizer->composeBoolean();
} }
if (outerStreams)
outerStream.rsb->findUsedStreams(*outerStreams);
if (innerStream.number != INVALID_STREAM) if (innerStream.number != INVALID_STREAM)
{ {
fb_assert(!innerStream.rsb); fb_assert(!innerStream.rsb);
// AB: the sort clause for the inner stream of an OUTER JOIN // AB: the sort clause for the inner stream of an OUTER JOIN
// should never be used for the index retrieval // should never be used for the index retrieval
innerStream.rsb = optimizer->generateRetrieval(innerStream.number, nullptr, innerStream.rsb = optimizer->generateRetrieval(innerStream.number, nullptr, false, true);
false, (joinType == OUTER_JOIN) ? true : false);
} }
// Generate a parent filter record source for any remaining booleans that // Generate a parent filter record source for any remaining booleans that
@ -189,8 +200,7 @@ RecordSource* OuterJoin::process(const JoinType joinType)
// Allocate and return the join record source // Allocate and return the join record source
return FB_NEW_POOL(getPool()) return FB_NEW_POOL(getPool()) NestedLoopJoin(csb, outerStream.rsb, innerRsb, boolean);
NestedLoopJoin(csb, outerStream.rsb, innerRsb, boolean, joinType);
}; };

View File

@ -37,10 +37,13 @@ using namespace Jrd;
// Data access: full outer join // Data access: full outer join
// ---------------------------- // ----------------------------
FullOuterJoin::FullOuterJoin(CompilerScratch* csb, RecordSource* arg1, RecordSource* arg2) FullOuterJoin::FullOuterJoin(CompilerScratch* csb,
RecordSource* arg1, RecordSource* arg2,
const StreamList& checkStreams)
: RecordSource(csb), : RecordSource(csb),
m_arg1(arg1), m_arg1(arg1),
m_arg2(arg2) m_arg2(arg2),
m_checkStreams(csb->csb_pool, checkStreams)
{ {
fb_assert(m_arg1 && m_arg2); fb_assert(m_arg1 && m_arg2);
@ -97,7 +100,27 @@ bool FullOuterJoin::internalGetRecord(thread_db* tdbb) const
m_arg2->open(tdbb); m_arg2->open(tdbb);
} }
return m_arg2->getRecord(tdbb); // We should exclude matching records from the right-joined (second) record source,
// as they're already returned from the left-joined (first) record source
while (m_arg2->getRecord(tdbb))
{
bool matched = false;
for (const auto stream : m_checkStreams)
{
if (request->req_rpb[stream].rpb_number.isValid())
{
matched = true;
break;
}
}
if (!matched)
return true;
}
return false;
} }
bool FullOuterJoin::refetchRecord(thread_db* /*tdbb*/) const bool FullOuterJoin::refetchRecord(thread_db* /*tdbb*/) const

View File

@ -53,10 +53,11 @@ NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, FB_SIZE_T count, RecordSour
} }
} }
NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, RecordSource* inner, NestedLoopJoin::NestedLoopJoin(CompilerScratch* csb,
BoolExprNode* boolean, JoinType joinType) RecordSource* outer, RecordSource* inner,
BoolExprNode* boolean)
: RecordSource(csb), : RecordSource(csb),
m_joinType(joinType), m_joinType(OUTER_JOIN),
m_args(csb->csb_pool), m_args(csb->csb_pool),
m_boolean(boolean) m_boolean(boolean)
{ {

View File

@ -1137,7 +1137,7 @@ namespace Jrd
public: public:
NestedLoopJoin(CompilerScratch* csb, FB_SIZE_T count, RecordSource* const* args); NestedLoopJoin(CompilerScratch* csb, FB_SIZE_T count, RecordSource* const* args);
NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, RecordSource* inner, NestedLoopJoin(CompilerScratch* csb, RecordSource* outer, RecordSource* inner,
BoolExprNode* boolean, JoinType joinType); BoolExprNode* boolean);
void close(thread_db* tdbb) const override; void close(thread_db* tdbb) const override;
@ -1168,7 +1168,8 @@ namespace Jrd
class FullOuterJoin : public RecordSource class FullOuterJoin : public RecordSource
{ {
public: public:
FullOuterJoin(CompilerScratch* csb, RecordSource* arg1, RecordSource* arg2); FullOuterJoin(CompilerScratch* csb, RecordSource* arg1, RecordSource* arg2,
const StreamList& checkStreams);
void close(thread_db* tdbb) const override; void close(thread_db* tdbb) const override;
@ -1191,6 +1192,7 @@ namespace Jrd
private: private:
NestConst<RecordSource> m_arg1; NestConst<RecordSource> m_arg1;
NestConst<RecordSource> m_arg2; NestConst<RecordSource> m_arg2;
const StreamList m_checkStreams;
}; };
class HashJoin : public RecordSource class HashJoin : public RecordSource