Skip to content

Commit e02e024

Browse files
committed
Backport #18374 engine fix to v1.3.2 branch
This PR contains a cherry-pick of the duckdb/duckdb#18752 engine PR applied on top of the `v1.3.2.0` release. `v1.3.2` branch is created for it so the new release built from this branch will be compatible with existing `v1.3.2` extensions. Testing: test case included with the original PR is run manually on patched JDBC.
1 parent c74b88d commit e02e024

File tree

3 files changed

+33
-44
lines changed

3 files changed

+33
-44
lines changed

src/duckdb/src/execution/join_hashtable.cpp

Lines changed: 28 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ JoinHashTable::SharedState::SharedState()
1919
}
2020

2121
JoinHashTable::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

179183
template <bool USE_SALTS, bool HAS_SEL>
180184
static 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
236239
template <bool USE_SALTS>
237240
static 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);

src/duckdb/src/include/duckdb/execution/ht_entry.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ struct ht_entry_t { // NOLINT
7979
return ExtractSalt(value);
8080
}
8181

82+
inline hash_t GetSaltWithNulls() const {
83+
return value & SALT_MASK;
84+
}
85+
8286
inline void SetSalt(const hash_t &salt) {
8387
// Shouldn't be occupied when we set this
8488
D_ASSERT(!IsOccupied());

src/duckdb/src/include/duckdb/execution/join_hashtable.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ class JoinHashTable {
149149
struct ProbeState : SharedState {
150150
ProbeState();
151151

152-
Vector ht_offsets_v;
152+
Vector ht_offsets_and_salts_v;
153153
Vector hashes_dense_v;
154154
SelectionVector non_empty_sel;
155155
};

0 commit comments

Comments
 (0)