@@ -63,6 +63,11 @@ HashBuild::HashBuild(
6363 joinType_{joinNode_->joinType ()},
6464 nullAware_{joinNode_->isNullAware ()},
6565 needProbedFlagSpill_{needRightSideJoin (joinType_)},
66+ dropDuplicates_ (joinNode_->canDropDuplicates ()),
67+ abandonHashBuildDedupMinRows_(
68+ driverCtx->queryConfig ().abandonHashBuildDedupMinRows()),
69+ abandonHashBuildDedupMinPct_(
70+ driverCtx->queryConfig ().abandonHashBuildDedupMinPct()),
6671 joinBridge_(operatorCtx_->task ()->getHashJoinBridgeLocked(
6772 operatorCtx_->driverCtx ()->splitGroupId,
6873 planNodeId())),
@@ -86,19 +91,22 @@ HashBuild::HashBuild(
8691
8792 // Identify the non-key build side columns and make a decoder for each.
8893 const int32_t numDependents = inputType->size () - numKeys;
89- if (numDependents > 0 ) {
90- // Number of join keys (numKeys) may be less then number of input columns
91- // (inputType->size()). In this case numDependents is negative and cannot be
92- // used to call 'reserve'. This happens when we join different probe side
93- // keys with the same build side key: SELECT * FROM t LEFT JOIN u ON t.k1 =
94- // u.k AND t.k2 = u.k.
95- dependentChannels_.reserve (numDependents);
96- decoders_.reserve (numDependents);
97- }
98- for (auto i = 0 ; i < inputType->size (); ++i) {
99- if (keyChannelMap_.find (i) == keyChannelMap_.end ()) {
100- dependentChannels_.emplace_back (i);
101- decoders_.emplace_back (std::make_unique<DecodedVector>());
94+ if (!dropDuplicates_) {
95+ if (numDependents > 0 ) {
96+ // Number of join keys (numKeys) may be less then number of input columns
97+ // (inputType->size()). In this case numDependents is negative and cannot
98+ // be used to call 'reserve'. This happens when we join different probe
99+ // side keys with the same build side key: SELECT * FROM t LEFT JOIN u ON
100+ // t.k1 = u.k AND t.k2 = u.k.
101+ dependentChannels_.reserve (numDependents);
102+ decoders_.reserve (numDependents);
103+ }
104+
105+ for (auto i = 0 ; i < inputType->size (); ++i) {
106+ if (keyChannelMap_.find (i) == keyChannelMap_.end ()) {
107+ dependentChannels_.emplace_back (i);
108+ decoders_.emplace_back (std::make_unique<DecodedVector>());
109+ }
102110 }
103111 }
104112
@@ -146,11 +154,6 @@ void HashBuild::setupTable() {
146154 .minTableRowsForParallelJoinBuild (),
147155 pool ());
148156 } else {
149- // (Left) semi and anti join with no extra filter only needs to know whether
150- // there is a match. Hence, no need to store entries with duplicate keys.
151- const bool dropDuplicates = !joinNode_->filter () &&
152- (joinNode_->isLeftSemiFilterJoin () ||
153- joinNode_->isLeftSemiProjectJoin () || isAntiJoin (joinType_));
154157 // Right semi join needs to tag build rows that were probed.
155158 const bool needProbedFlag = joinNode_->isRightSemiFilterJoin ();
156159 if (isLeftNullAwareJoinWithFilter (joinNode_)) {
@@ -159,7 +162,7 @@ void HashBuild::setupTable() {
159162 table_ = HashTable<false >::createForJoin (
160163 std::move (keyHashers),
161164 dependentTypes,
162- !dropDuplicates , // allowDuplicates
165+ !dropDuplicates_ , // allowDuplicates
163166 needProbedFlag, // hasProbedFlag
164167 operatorCtx_->driverCtx ()
165168 ->queryConfig ()
@@ -170,7 +173,7 @@ void HashBuild::setupTable() {
170173 table_ = HashTable<true >::createForJoin (
171174 std::move (keyHashers),
172175 dependentTypes,
173- !dropDuplicates , // allowDuplicates
176+ !dropDuplicates_ , // allowDuplicates
174177 needProbedFlag, // hasProbedFlag
175178 operatorCtx_->driverCtx ()
176179 ->queryConfig ()
@@ -179,6 +182,15 @@ void HashBuild::setupTable() {
179182 }
180183 }
181184 analyzeKeys_ = table_->hashMode () != BaseHashTable::HashMode::kHash ;
185+ if (abandonHashBuildDedupMinPct_ == 0 ) {
186+ // Building a HashTable without duplicates is disabled if
187+ // abandonBuildNoDupHashMinPct_ is 0.
188+ abandonHashBuildDedup_ = true ;
189+ table_->setAllowDuplicates (true );
190+ return ;
191+ }
192+ // Only create HashLookup when dedup is enabled.
193+ lookup_ = std::make_unique<HashLookup>(table_->hashers (), pool ());
182194}
183195
184196void HashBuild::setupSpiller (SpillPartition* spillPartition) {
@@ -377,6 +389,25 @@ void HashBuild::addInput(RowVectorPtr input) {
377389 return ;
378390 }
379391
392+ if (dropDuplicates_ && !abandonHashBuildDedup_) {
393+ const bool abandonEarly = abandonHashBuildDedupEarly (table_->numDistinct ());
394+ if (!abandonEarly) {
395+ numHashInputRows_ += activeRows_.countSelected ();
396+ table_->prepareForGroupProbe (
397+ *lookup_,
398+ input,
399+ activeRows_,
400+ BaseHashTable::kNoSpillInputStartPartitionBit );
401+ if (lookup_->rows .empty ()) {
402+ return ;
403+ }
404+ table_->groupProbe (
405+ *lookup_, BaseHashTable::kNoSpillInputStartPartitionBit );
406+ return ;
407+ }
408+ abandonHashBuildDedup ();
409+ }
410+
380411 if (analyzeKeys_ && hashes_.size () < activeRows_.end ()) {
381412 hashes_.resize (activeRows_.end ());
382413 }
@@ -755,6 +786,7 @@ bool HashBuild::finishHashBuild() {
755786 std::move (otherTables),
756787 isInputFromSpill () ? spillConfig ()->startPartitionBit
757788 : BaseHashTable::kNoSpillInputStartPartitionBit ,
789+ dropDuplicates_,
758790 allowParallelJoinBuild ? operatorCtx_->task ()->queryCtx ()->executor ()
759791 : nullptr );
760792 }
@@ -879,6 +911,7 @@ void HashBuild::setupSpillInput(HashJoinBridge::SpillInput spillInput) {
879911 setupTable ();
880912 setupSpiller (spillInput.spillPartition .get ());
881913 stateCleared_ = false ;
914+ numHashInputRows_ = 0 ;
882915
883916 // Start to process spill input.
884917 processSpillInput ();
@@ -1240,4 +1273,21 @@ void HashBuildSpiller::extractSpill(
12401273 rows.data (), rows.size (), false , false , result->childAt (types.size ()));
12411274 }
12421275}
1276+
1277+ bool HashBuild::abandonHashBuildDedupEarly (int64_t numDistinct) const {
1278+ VELOX_CHECK (dropDuplicates_);
1279+ return numHashInputRows_ > abandonHashBuildDedupMinRows_ &&
1280+ 100 * numDistinct / numHashInputRows_ >= abandonHashBuildDedupMinPct_;
1281+ }
1282+
1283+ void HashBuild::abandonHashBuildDedup () {
1284+ // The hash table is no longer directly constructed in addInput. The data
1285+ // that was previously inserted into the hash table is already in the
1286+ // RowContainer.
1287+ addRuntimeStat (" abandonBuildNoDupHash" , RuntimeCounter (1 ));
1288+ abandonHashBuildDedup_ = true ;
1289+ table_->setAllowDuplicates (true );
1290+ lookup_.reset ();
1291+ }
1292+
12431293} // namespace facebook::velox::exec
0 commit comments