@@ -97,10 +97,10 @@ void InnerJoin::calculateStreamInfo()
9797
9898 for (auto innerStream : innerStreams)
9999 {
100- streams.add (innerStream->stream );
101- csb->csb_rpt [innerStream->stream ].activate ();
100+ streams.add (innerStream->number );
101+ csb->csb_rpt [innerStream->number ].activate ();
102102
103- Retrieval retrieval (tdbb, optimizer, innerStream->stream , false , false , sort, true );
103+ Retrieval retrieval (tdbb, optimizer, innerStream->number , false , false , sort, true );
104104 const auto candidate = retrieval.getInversion ();
105105
106106 innerStream->baseCost = candidate->cost ;
@@ -109,7 +109,7 @@ void InnerJoin::calculateStreamInfo()
109109 innerStream->baseUnique = candidate->unique ;
110110 innerStream->baseNavigated = candidate->navigated ;
111111
112- csb->csb_rpt [innerStream->stream ].deactivate ();
112+ csb->csb_rpt [innerStream->number ].deactivate ();
113113 }
114114
115115 // Collect dependencies between every pair of streams
@@ -120,7 +120,7 @@ void InnerJoin::calculateStreamInfo()
120120
121121 for (const auto innerStream : innerStreams)
122122 {
123- const StreamType testStream = innerStream->stream ;
123+ const StreamType testStream = innerStream->number ;
124124
125125 if (baseStream != testStream)
126126 {
@@ -145,7 +145,7 @@ void InnerJoin::calculateStreamInfo()
145145 }
146146
147147 // Unless PLAN is enforced, sort the streams based on independency and cost
148- if (!plan && ( innerStreams.getCount () > 1 ) )
148+ if (!plan && innerStreams.getCount () > 1 )
149149 {
150150 StreamInfoList tempStreams;
151151
@@ -172,23 +172,75 @@ void InnerJoin::calculateStreamInfo()
172172
173173void InnerJoin::estimateCost (unsigned position,
174174 const StreamInfo* stream,
175- double * cost,
176- double * resultingCardinality) const
175+ double & cost,
176+ double & cardinality)
177177{
178- const auto sort = (position == 0 && sortPtr) ? *sortPtr : nullptr ;
178+ fb_assert (joinedStreams[position].number == stream->number );
179+
180+ const auto sort = (!position && sortPtr) ? *sortPtr : nullptr ;
179181
180182 // Create the optimizer retrieval generation class and calculate
181183 // which indexes will be used and the total estimated selectivity will be returned
182- Retrieval retrieval (tdbb, optimizer, stream->stream , false , false , sort, true );
184+ Retrieval retrieval (tdbb, optimizer, stream->number , false , false , sort, true );
183185 const auto candidate = retrieval.getInversion ();
186+ fb_assert (!position || candidate->dependencies );
187+
188+ // Calculate the relationship selectivity
189+ double selectivity = candidate->selectivity ;
190+ if (selectivity < stream->baseSelectivity )
191+ selectivity /= stream->baseSelectivity ;
192+
193+ joinedStreams[position].selectivity = selectivity;
194+
195+ // Get the stream cardinality
196+ const auto tail = &csb->csb_rpt [stream->number ];
197+ const auto streamCardinality = tail->csb_cardinality ;
184198
185- *cost = candidate->cost ;
199+ // Calculate the nested loop cost, it's our default option
200+ const auto loopCost = candidate->cost * cardinality;
201+ cost = loopCost;
186202
187- // Calculate cardinality
188- const auto tail = &csb->csb_rpt [stream->stream ];
189- const double cardinality = tail->csb_cardinality * candidate->selectivity ;
203+ if (position)
204+ {
205+ // Calculate the hashing cost. It's estimated as the hashed stream retrieval cost
206+ // plus two cardinalities. Hashed stream cardinality means the cost of copying rows
207+ // into the hash table and the outer cardinality represents probing the hash table.
208+ const auto hashCardinality = stream->baseSelectivity * streamCardinality;
209+ const auto hashCost = stream->baseCost + hashCardinality + cardinality;
210+
211+ if (hashCost <= loopCost && hashCardinality <= HashJoin::maxCapacity ())
212+ {
213+ auto & equiMatches = joinedStreams[position].equiMatches ;
214+ fb_assert (!equiMatches.hasData ());
190215
191- *resultingCardinality = MAX (cardinality, MINIMUM_CARDINALITY);
216+ // Scan the matches for possible equi-join conditions
217+ for (const auto match : candidate->matches )
218+ {
219+ // Check whether we have an equivalence operation
220+ if (!optimizer->checkEquiJoin (match))
221+ continue ;
222+
223+ // Check whether the match references priorly joined streams
224+ const auto end = joinedStreams.begin () + position;
225+ for (auto iter = joinedStreams.begin (); iter != end; ++iter)
226+ {
227+ if (match->containsStream (iter->number ) &&
228+ equiMatches.getCount () < equiMatches.getCapacity ())
229+ {
230+ equiMatches.add (match);
231+ break ;
232+ }
233+ }
234+ }
235+
236+ // Adjust the actual cost value, if hash joining is both possible and preferrable
237+ if (equiMatches.hasData ())
238+ cost = hashCost;
239+ }
240+ }
241+
242+ const auto resultingCardinality = streamCardinality * candidate->selectivity ;
243+ cardinality = MAX (resultingCardinality, MINIMUM_CARDINALITY);
192244}
193245
194246
@@ -231,7 +283,8 @@ bool InnerJoin::findJoinOrder()
231283 {
232284 if (!bestCount || innerStream->baseCost < bestCost)
233285 {
234- joinedStreams[0 ].bestStream = innerStream->stream ;
286+ bestStreams.resize (1 );
287+ bestStreams.front ().number = innerStream->number ;
235288 bestCount = 1 ;
236289 bestCost = innerStream->baseCost ;
237290 }
@@ -270,11 +323,10 @@ bool InnerJoin::findJoinOrder()
270323 }
271324
272325 // Mark streams as used
273- for (unsigned i = 0 ; i < bestCount; i++ )
326+ for (const auto & stream : bestStreams )
274327 {
275- auto streamInfo = getStreamInfo (joinedStreams[i]. bestStream );
328+ auto streamInfo = getStreamInfo (stream. number );
276329 streamInfo->used = true ;
277- bestStreams.add (joinedStreams[i].bestStream );
278330 }
279331
280332#ifdef OPT_DEBUG
@@ -299,11 +351,11 @@ void InnerJoin::findBestOrder(unsigned position,
299351 double cost,
300352 double cardinality)
301353{
302- const auto tail = &csb->csb_rpt [stream->stream ];
354+ const auto tail = &csb->csb_rpt [stream->number ];
303355
304356 // Do some initializations
305357 tail->activate ();
306- joinedStreams[position].number = stream->stream ;
358+ joinedStreams[position].reset ( stream->number ) ;
307359
308360 // Save the various flag bits from the optimizer block to reset its
309361 // state after each test
@@ -312,13 +364,13 @@ void InnerJoin::findBestOrder(unsigned position,
312364 streamFlags.add (innerStream->used );
313365
314366 // Compute delta and total estimate cost to fetch this stream
315- double positionCost = 0 , positionCardinality = 0 , newCost = 0 , newCardinality = 0 ;
367+ double positionCost = 0 , positionCardinality = cardinality , newCost = 0 , newCardinality = 0 ;
316368
317369 if (!plan)
318370 {
319- estimateCost (position, stream, & positionCost, & positionCardinality);
320- newCost = cost + cardinality * positionCost;
321- newCardinality = positionCardinality * cardinality ;
371+ estimateCost (position, stream, positionCost, positionCardinality);
372+ newCost = cost + positionCost;
373+ newCardinality = cardinality * positionCardinality ;
322374 }
323375
324376 position++;
@@ -329,13 +381,7 @@ void InnerJoin::findBestOrder(unsigned position,
329381 {
330382 bestCount = position;
331383 bestCost = newCost;
332-
333- const auto end = joinedStreams.begin () + position;
334- for (auto iter = joinedStreams.begin (); iter != end; ++iter)
335- {
336- auto & joinedStream = *iter;
337- joinedStream.bestStream = joinedStream.number ;
338- }
384+ bestStreams.assign (joinedStreams.begin (), position);
339385 }
340386
341387#ifdef OPT_DEBUG
@@ -398,7 +444,7 @@ void InnerJoin::findBestOrder(unsigned position,
398444 IndexRelationship* processRelationship = processList.begin ();
399445 for (FB_SIZE_T index = 0 ; index < processList.getCount (); index++)
400446 {
401- if (relationStreamInfo->stream == processRelationship[index].stream )
447+ if (relationStreamInfo->number == processRelationship[index].stream )
402448 {
403449 // If the cost of this relationship is cheaper then remove the
404450 // old relationship and add this one
@@ -445,25 +491,103 @@ void InnerJoin::findBestOrder(unsigned position,
445491River* InnerJoin::formRiver ()
446492{
447493 fb_assert (bestCount);
448- fb_assert (bestStreams.hasData () );
494+ fb_assert (bestStreams.getCount () == bestCount );
449495
450- if (bestStreams.getCount () != innerStreams.getCount ())
496+ const auto orgSortPtr = sortPtr;
497+
498+ if (bestCount != innerStreams.getCount ())
451499 sortPtr = nullptr ;
452500
501+ RecordSource* rsb;
502+ StreamList streams;
453503 HalfStaticArray<RecordSource*, OPT_STATIC_ITEMS> rsbs;
454504
455- for (const auto stream : bestStreams)
505+ for (const auto & stream : bestStreams)
456506 {
457- const auto rsb = optimizer->generateRetrieval (stream, sortPtr, false , false );
507+ // We use hash join instead of nested loop join if:
508+ // - stream has equivalence relationship(s) with the prior streams
509+ // (and hashing was estimated to be cheaper)
510+ // AND
511+ // - optimization for first rows is not requested
512+ // OR
513+ // - existing sort was not utilized using an index
514+
515+ if (rsbs.hasData () && // this is not the first stream
516+ stream.equiMatches .hasData () &&
517+ (!optimizer->favorFirstRows () || (orgSortPtr && *orgSortPtr)))
518+ {
519+ fb_assert (streams.hasData ());
520+
521+ // Deactivate priorly joined streams
522+ StreamStateHolder stateHolder (csb, streams);
523+ stateHolder.deactivate ();
524+
525+ // Create an independent retrieval
526+ rsb = optimizer->generateRetrieval (stream.number , sortPtr, false , false );
527+
528+ // Create a nested loop join from the priorly processed streams
529+ const auto priorRsb = (rsbs.getCount () == 1 ) ? rsbs[0 ] :
530+ FB_NEW_POOL (getPool ()) NestedLoopJoin (csb, rsbs.getCount (), rsbs.begin ());
531+
532+ const River priorRiver (csb, priorRsb, nullptr , streams);
533+
534+ // Prepare record sources and corresponding equivalence keys for hash-joining
535+ RecordSource* hashJoinRsbs[] = {priorRsb, rsb};
536+
537+ HalfStaticArray<NestValueArray*, OPT_STATIC_ITEMS> keys;
538+
539+ keys.add (FB_NEW_POOL (getPool ()) NestValueArray (getPool ()));
540+ keys.add (FB_NEW_POOL (getPool ()) NestValueArray (getPool ()));
541+
542+ for (const auto match : stream.equiMatches )
543+ {
544+ NestConst<ValueExprNode> node1;
545+ NestConst<ValueExprNode> node2;
546+
547+ if (!optimizer->getEquiJoinKeys (match, &node1, &node2))
548+ fb_assert (false );
549+
550+ if (!priorRiver.isReferenced (node1))
551+ {
552+ fb_assert (priorRiver.isReferenced (node2));
553+
554+ // Swap the sides
555+ std::swap (node1, node2);
556+ }
557+
558+ keys[0 ]->add (node1);
559+ keys[1 ]->add (node2);
560+ }
561+
562+ // Ensure the smallest stream is the one to be hashed
563+ if (rsb->getCardinality () > priorRsb->getCardinality ())
564+ {
565+ // Swap the sides
566+ std::swap (hashJoinRsbs[0 ], hashJoinRsbs[1 ]);
567+ std::swap (keys[0 ], keys[1 ]);
568+ }
569+
570+ // Create a hash join
571+ rsb = FB_NEW_POOL (getPool ())
572+ HashJoin (tdbb, csb, 2 , hashJoinRsbs, keys.begin (), stream.selectivity );
573+
574+ // Clear priorly processed rsb's, as they're already incorporated into a hash join
575+ rsbs.clear ();
576+ }
577+ else
578+ rsb = optimizer->generateRetrieval (stream.number , sortPtr, false , false );
579+
458580 rsbs.add (rsb);
581+ streams.add (stream.number );
459582 sortPtr = nullptr ;
460583 }
461584
462- const auto rsb = (rsbs.getCount () == 1 ) ? rsbs[0 ] :
585+ // Create a nested loop join from the processed streams
586+ rsb = (rsbs.getCount () == 1 ) ? rsbs[0 ] :
463587 FB_NEW_POOL (getPool ()) NestedLoopJoin (csb, rsbs.getCount (), rsbs.begin ());
464588
465589 // Allocate a river block and move the best order into it
466- const auto river = FB_NEW_POOL (getPool ()) River (csb, rsb, nullptr , bestStreams );
590+ const auto river = FB_NEW_POOL (getPool ()) River (csb, rsb, nullptr , streams );
467591 river->deactivate (csb);
468592 return river;
469593}
@@ -484,21 +608,21 @@ void InnerJoin::getIndexedRelationships(StreamInfo* testStream)
484608 testStream->stream , name.c_str ());
485609#endif
486610
487- const auto tail = &csb->csb_rpt [testStream->stream ];
611+ const auto tail = &csb->csb_rpt [testStream->number ];
488612
489- Retrieval retrieval (tdbb, optimizer, testStream->stream , false , false , nullptr , true );
613+ Retrieval retrieval (tdbb, optimizer, testStream->number , false , false , nullptr , true );
490614 const auto candidate = retrieval.getInversion ();
491615
492616 for (const auto baseStream : innerStreams)
493617 {
494- if (baseStream->stream != testStream->stream &&
495- candidate->dependentFromStreams .exist (baseStream->stream ))
618+ if (baseStream->number != testStream->number &&
619+ candidate->dependentFromStreams .exist (baseStream->number ))
496620 {
497621 // If the base stream already depends on the testing stream, don't store it again
498622 bool found = false ;
499623 for (const auto & relationship : baseStream->indexedRelationships )
500624 {
501- if (relationship.stream == testStream->stream )
625+ if (relationship.stream == testStream->number )
502626 {
503627 found = true ;
504628 break ;
@@ -515,7 +639,7 @@ void InnerJoin::getIndexedRelationships(StreamInfo* testStream)
515639 // with the base stream active as without the base stream
516640 // then the test stream has a indexed relationship with the base stream.
517641 IndexRelationship indexRelationship;
518- indexRelationship.stream = testStream->stream ;
642+ indexRelationship.stream = testStream->number ;
519643 indexRelationship.unique = candidate->unique ;
520644 indexRelationship.cost = candidate->cost ;
521645 indexRelationship.cardinality = candidate->unique ?
@@ -540,7 +664,7 @@ InnerJoin::StreamInfo* InnerJoin::getStreamInfo(StreamType stream)
540664{
541665 for (FB_SIZE_T i = 0 ; i < innerStreams.getCount (); i++)
542666 {
543- if (innerStreams[i]->stream == stream)
667+ if (innerStreams[i]->number == stream)
544668 return innerStreams[i];
545669 }
546670
@@ -561,8 +685,8 @@ void InnerJoin::printBestOrder() const
561685 const auto end = bestStreams.end ();
562686 for (auto iter = bestStreams.begin (); iter != end; iter++)
563687 {
564- const auto name = optimizer->getStreamName (* iter);
565- optimizer->printf (" %u (%s)" , * iter, name.c_str ());
688+ const auto name = optimizer->getStreamName (iter-> number );
689+ optimizer->printf (" %u (%s)" , iter-> number , name.c_str ());
566690
567691 if (iter != end - 1 )
568692 optimizer->printf (" ," );
@@ -616,9 +740,9 @@ void InnerJoin::printStartOrder() const
616740 const auto innerStream = *iter;
617741 if (!innerStream->used )
618742 {
619- const auto name = optimizer->getStreamName (innerStream->stream );
743+ const auto name = optimizer->getStreamName (innerStream->number );
620744 optimizer->printf (" %u (%s) base cost (%1.2f)" ,
621- innerStream->stream , name.c_str (), innerStream->baseCost );
745+ innerStream->number , name.c_str (), innerStream->baseCost );
622746
623747 if (iter != end - 1 )
624748 optimizer->printf (" ," );
0 commit comments