@@ -19,7 +19,7 @@ JoinHashTable::SharedState::SharedState()
1919}
2020
2121JoinHashTable::ProbeState::ProbeState ()
22- : SharedState(), ht_offsets_v (LogicalType::UBIGINT), hashes_dense_v(LogicalType::HASH),
22+ : SharedState(), ht_offsets_and_salts_v (LogicalType::UBIGINT), hashes_dense_v(LogicalType::HASH),
2323 non_empty_sel (STANDARD_VECTOR_SIZE) {
2424}
2525
@@ -168,30 +168,33 @@ static void AddPointerToCompare(JoinHashTable::ProbeState &state, const ht_entry
168168 idx_t row_ht_offset, idx_t &keys_to_compare_count, const idx_t &row_index) {
169169
170170 const auto row_ptr_insert_to = FlatVector::GetData<data_ptr_t >(pointers_result_v);
171- const auto ht_offsets = FlatVector::GetData<idx_t >(state.ht_offsets_v );
171+ const auto ht_offsets_and_salts = FlatVector::GetData<idx_t >(state.ht_offsets_and_salts_v );
172172
173173 state.keys_to_compare_sel .set_index (keys_to_compare_count, row_index);
174174 row_ptr_insert_to[row_index] = entry.GetPointer ();
175- ht_offsets[row_index] = row_ht_offset;
175+
176+ // If the key does not match, we have to continue linear probing, we need to store the ht_offset and the salt
177+ // for this element based on the row_index. We can't get the offset from the hash as we already might have
178+ // some linear probing steps when arriving here.
179+ ht_offsets_and_salts[row_index] = row_ht_offset | entry.GetSaltWithNulls ();
176180 keys_to_compare_count += 1 ;
177181}
178182
179183template <bool USE_SALTS, bool HAS_SEL>
180184static idx_t ProbeForPointersInternal (JoinHashTable::ProbeState &state, JoinHashTable &ht, ht_entry_t *entries,
181- Vector &hashes_v, Vector &pointers_result_v, const SelectionVector *row_sel,
182- idx_t &count) {
185+ Vector &pointers_result_v, const SelectionVector *row_sel, idx_t &count) {
183186
184187 auto hashes_dense = FlatVector::GetData<hash_t >(state.hashes_dense_v );
185188
186189 idx_t keys_to_compare_count = 0 ;
187190
188191 for (idx_t i = 0 ; i < count; i++) {
189192
190- auto row_hash = hashes_dense[i]; // hashes has been flattened before -> always access dense
193+ auto row_hash = hashes_dense[i]; // hashes have been flattened before -> always access dense
191194 auto row_ht_offset = row_hash & ht.bitmask ;
192195
193196 if (USE_SALTS) {
194- // increment the ht_offset of the entry as long as next entry is occupied and salt does not match
197+ // increment the ht_offset of the entry as long as the next entry is occupied and salt does not match
195198 while (true ) {
196199 const ht_entry_t entry = entries[row_ht_offset];
197200 const bool occupied = entry.IsOccupied ();
@@ -211,7 +214,7 @@ static idx_t ProbeForPointersInternal(JoinHashTable::ProbeState &state, JoinHash
211214 break ;
212215 }
213216
214- // full and salt does not match -> continue probing
217+ // full and salt do not match -> continue probing
215218 IncrementAndWrap (row_ht_offset, ht.bitmask );
216219 }
217220 } else {
@@ -235,14 +238,12 @@ static idx_t ProbeForPointersInternal(JoinHashTable::ProbeState &state, JoinHash
235238// / -> match, add to compare sel and increase found count
236239template <bool USE_SALTS>
237240static idx_t ProbeForPointers (JoinHashTable::ProbeState &state, JoinHashTable &ht, ht_entry_t *entries,
238- Vector &hashes_v, Vector & pointers_result_v, const SelectionVector *row_sel, idx_t count,
241+ Vector &pointers_result_v, const SelectionVector *row_sel, idx_t count,
239242 const bool has_row_sel) {
240243 if (has_row_sel) {
241- return ProbeForPointersInternal<USE_SALTS, true >(state, ht, entries, hashes_v, pointers_result_v, row_sel,
242- count);
244+ return ProbeForPointersInternal<USE_SALTS, true >(state, ht, entries, pointers_result_v, row_sel, count);
243245 } else {
244- return ProbeForPointersInternal<USE_SALTS, false >(state, ht, entries, hashes_v, pointers_result_v, row_sel,
245- count);
246+ return ProbeForPointersInternal<USE_SALTS, false >(state, ht, entries, pointers_result_v, row_sel, count);
246247 }
247248}
248249
@@ -254,14 +255,10 @@ static void GetRowPointersInternal(DataChunk &keys, TupleDataChunkState &key_sta
254255 ht_entry_t *entries, Vector &pointers_result_v, SelectionVector &match_sel,
255256 bool has_row_sel) {
256257
257- // in case of a hash collision, we need this information to correctly retrieve the salt of this hash
258- bool uses_unified = false ;
259- UnifiedVectorFormat hashes_unified_v;
260-
261258 // densify hashes: If there is no sel, flatten the hashes, else densify via UnifiedVectorFormat
262259 if (has_row_sel) {
260+ UnifiedVectorFormat hashes_unified_v;
263261 hashes_v.ToUnifiedFormat (count, hashes_unified_v);
264- uses_unified = true ;
265262
266263 auto hashes_unified = UnifiedVectorFormat::GetData<hash_t >(hashes_unified_v);
267264 auto hashes_dense = FlatVector::GetData<idx_t >(state.hashes_dense_v );
@@ -282,8 +279,8 @@ static void GetRowPointersInternal(DataChunk &keys, TupleDataChunkState &key_sta
282279 idx_t elements_to_probe_count = count;
283280
284281 do {
285- const idx_t keys_to_compare_count = ProbeForPointers<USE_SALTS>(state, ht, entries, hashes_v, pointers_result_v ,
286- row_sel, elements_to_probe_count, has_row_sel);
282+ const idx_t keys_to_compare_count = ProbeForPointers<USE_SALTS>(state, ht, entries, pointers_result_v, row_sel ,
283+ elements_to_probe_count, has_row_sel);
287284
288285 // if there are no keys to compare, we are done
289286 if (keys_to_compare_count == 0 ) {
@@ -305,32 +302,15 @@ static void GetRowPointersInternal(DataChunk &keys, TupleDataChunkState &key_sta
305302 match_count++;
306303 }
307304
308- // Linear probing for collisions: Move to the next entry in the HT
309- auto hashes_unified = UnifiedVectorFormat::GetData<hash_t >(hashes_unified_v);
310- auto hashes_dense = FlatVector::GetData<hash_t >(state.hashes_dense_v );
311- auto ht_offsets = FlatVector::GetData<idx_t >(state.ht_offsets_v );
305+ const auto ht_offsets_and_salts = FlatVector::GetData<idx_t >(state.ht_offsets_and_salts_v );
306+ const auto hashes_dense = FlatVector::GetData<hash_t >(state.hashes_dense_v );
312307
308+ // For all the non-matches, increment the offset to continue probing but keep the salt intact
313309 for (idx_t i = 0 ; i < keys_no_match_count; i++) {
314310 const auto row_index = state.keys_no_match_sel .get_index (i);
315- // The ProbeForPointers function calculates the ht_offset from the hash; therefore, we have to write the
316- // new offset into the hashes_v; otherwise the next iteration will start at the old position. This might
317- // seem as an overhead but assures that the first call of ProbeForPointers is optimized as conceding
318- // calls are unlikely (Max 1-(65535/65536)^VectorSize = 3.1%)
319- auto ht_offset = ht_offsets[row_index];
320- IncrementAndWrap (ht_offset, ht.bitmask );
321-
322- // Get original hash from unified vector format to extract the salt if hashes_dense was populated that way
323- hash_t hash;
324- if (uses_unified) {
325- const auto uvf_index = hashes_unified_v.sel ->get_index (row_index);
326- hash = hashes_unified[uvf_index];
327- } else {
328- hash = hashes_dense[row_index];
329- }
330-
331- const auto offset_and_salt = ht_offset | (hash & ht_entry_t ::SALT_MASK);
332-
333- hashes_dense[i] = offset_and_salt; // populate dense again
311+ auto ht_offset_and_salt = ht_offsets_and_salts[row_index];
312+ IncrementAndWrap (ht_offset_and_salt, ht.bitmask | ht_entry_t ::SALT_MASK);
313+ hashes_dense[i] = ht_offset_and_salt; // populate dense again
334314 }
335315
336316 // in the next interation, we have a selection vector with the keys that do not match
@@ -736,6 +716,11 @@ void JoinHashTable::AllocatePointerTable() {
736716 capacity = PointerTableCapacity (Count ());
737717 D_ASSERT (IsPowerOfTwo (capacity));
738718
719+ constexpr uint64_t MAX_HASHTABLE_CAPACITY = (1ULL << 48 ) - 1 ;
720+ if (capacity >= MAX_HASHTABLE_CAPACITY) {
721+ throw InternalException (" Hashtable capacity exceeds 48-bit limit (2^48 - 1)" );
722+ }
723+
739724 if (hash_map.get ()) {
740725 // There is already a hash map
741726 auto current_capacity = hash_map.GetSize () / sizeof (ht_entry_t );
0 commit comments