summaryrefslogtreecommitdiff
path: root/src/dict.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/dict.c')
-rw-r--r--src/dict.c350
1 files changed, 269 insertions, 81 deletions
diff --git a/src/dict.c b/src/dict.c
index ec2d4dca0..d46e30588 100644
--- a/src/dict.c
+++ b/src/dict.c
@@ -74,12 +74,19 @@ struct dictEntry {
* by dictType's dictEntryMetadataBytes(). */
};
+typedef struct {
+ void *key;
+ dictEntry *next;
+} dictEntryNoValue;
+
/* -------------------------- private prototypes ---------------------------- */
static int _dictExpandIfNeeded(dict *d);
static signed char _dictNextExp(unsigned long size);
-static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing);
static int _dictInit(dict *d, dictType *type);
+static dictEntry *dictGetNext(const dictEntry *de);
+static dictEntry **dictGetNextRef(dictEntry *de);
+static void dictSetNext(dictEntry *de, dictEntry *next);
/* -------------------------- hash functions -------------------------------- */
@@ -107,6 +114,63 @@ uint64_t dictGenCaseHashFunction(const unsigned char *buf, size_t len) {
return siphash_nocase(buf,len,dict_hash_function_seed);
}
+/* --------------------- dictEntry pointer bit tricks ---------------------- */
+
+/* The 3 least significant bits in a pointer to a dictEntry determines what the
+ * pointer actually points to. If the least bit is set, it's a key. Otherwise,
+ * the bit pattern of the least 3 significant bits mark the kind of entry. */
+
+#define ENTRY_PTR_MASK 7 /* 111 */
+#define ENTRY_PTR_NORMAL 0 /* 000 */
+#define ENTRY_PTR_NO_VALUE 2 /* 010 */
+
+/* Returns 1 if the entry pointer is a pointer to a key, rather than to an
+ * allocated entry. Returns 0 otherwise. */
+static inline int entryIsKey(const dictEntry *de) {
+ return (uintptr_t)(void *)de & 1;
+}
+
+/* Returns 1 if the pointer is actually a pointer to a dictEntry struct. Returns
+ * 0 otherwise. */
+static inline int entryIsNormal(const dictEntry *de) {
+ return ((uintptr_t)(void *)de & ENTRY_PTR_MASK) == ENTRY_PTR_NORMAL;
+}
+
+/* Returns 1 if the entry is a special entry with key and next, but without
+ * value. Returns 0 otherwise. */
+static inline int entryIsNoValue(const dictEntry *de) {
+ return ((uintptr_t)(void *)de & ENTRY_PTR_MASK) == ENTRY_PTR_NO_VALUE;
+}
+
+/* Creates an entry without a value field. */
+static inline dictEntry *createEntryNoValue(void *key, dictEntry *next) {
+ dictEntryNoValue *entry = zmalloc(sizeof(*entry));
+ entry->key = key;
+ entry->next = next;
+ return (dictEntry *)(void *)((uintptr_t)(void *)entry | ENTRY_PTR_NO_VALUE);
+}
+
+static inline dictEntry *encodeMaskedPtr(const void *ptr, unsigned int bits) {
+ assert(((uintptr_t)ptr & ENTRY_PTR_MASK) == 0);
+ return (dictEntry *)(void *)((uintptr_t)ptr | bits);
+}
+
+static inline void *decodeMaskedPtr(const dictEntry *de) {
+ assert(!entryIsKey(de));
+ return (void *)((uintptr_t)(void *)de & ~ENTRY_PTR_MASK);
+}
+
+/* Decodes the pointer to an entry without value, when you know it is an entry
+ * without value. Hint: Use entryIsNoValue to check. */
+static inline dictEntryNoValue *decodeEntryNoValue(const dictEntry *de) {
+ return decodeMaskedPtr(de);
+}
+
+/* Returns 1 if the entry has a value field and 0 otherwise. */
+static inline int entryHasValue(const dictEntry *de) {
+ return entryIsNormal(de);
+}
+
/* ----------------------------- API implementation ------------------------- */
/* Reset hash table parameters already initialized with _dictInit()*/
@@ -252,17 +316,42 @@ int dictRehash(dict *d, int n) {
while(de) {
uint64_t h;
- nextde = de->next;
+ nextde = dictGetNext(de);
+ void *key = dictGetKey(de);
/* Get the index in the new hash table */
if (d->ht_size_exp[1] > d->ht_size_exp[0]) {
- h = dictHashKey(d, de->key) & DICTHT_SIZE_MASK(d->ht_size_exp[1]);
+ h = dictHashKey(d, key) & DICTHT_SIZE_MASK(d->ht_size_exp[1]);
} else {
/* We're shrinking the table. The tables sizes are powers of
* two, so we simply mask the bucket index in the larger table
* to get the bucket index in the smaller table. */
h = d->rehashidx & DICTHT_SIZE_MASK(d->ht_size_exp[1]);
}
- de->next = d->ht_table[1][h];
+ if (d->type->no_value) {
+ if (d->type->keys_are_odd && !d->ht_table[1][h]) {
+ /* Destination bucket is empty and we can store the key
+ * directly without an allocated entry. Free the old entry
+ * if it's an allocated entry.
+ *
+ * TODO: Add a flag 'keys_are_even' and if set, we can use
+ * this optimization for these dicts too. We can set the LSB
+ * bit when stored as a dict entry and clear it again when
+ * we need the key back. */
+ assert(entryIsKey(key));
+ if (!entryIsKey(de)) zfree(decodeMaskedPtr(de));
+ de = key;
+ } else if (entryIsKey(de)) {
+ /* We don't have an allocated entry but we need one. */
+ de = createEntryNoValue(key, d->ht_table[1][h]);
+ } else {
+ /* Just move the existing entry to the destination table and
+ * update the 'next' field. */
+ assert(entryIsNoValue(de));
+ dictSetNext(de, d->ht_table[1][h]);
+ }
+ } else {
+ dictSetNext(de, d->ht_table[1][h]);
+ }
d->ht_table[1][h] = de;
d->ht_used[0]--;
d->ht_used[1]++;
@@ -334,7 +423,7 @@ int dictAdd(dict *d, void *key, void *val)
dictEntry *entry = dictAddRaw(d,key,NULL);
if (!entry) return DICT_ERR;
- dictSetVal(d, entry, val);
+ if (!d->type->no_value) dictSetVal(d, entry, val);
return DICT_OK;
}
@@ -358,33 +447,61 @@ int dictAdd(dict *d, void *key, void *val)
*/
dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
{
- long index;
- dictEntry *entry;
- int htidx;
+ /* Get the position for the new key or NULL if the key already exists. */
+ void *position = dictFindPositionForInsert(d, key, existing);
+ if (!position) return NULL;
- if (dictIsRehashing(d)) _dictRehashStep(d);
+ /* Dup the key if necessary. */
+ if (d->type->keyDup) key = d->type->keyDup(d, key);
- /* Get the index of the new element, or -1 if
- * the element already exists. */
- if ((index = _dictKeyIndex(d, key, dictHashKey(d,key), existing)) == -1)
- return NULL;
+ return dictInsertAtPosition(d, key, position);
+}
- /* Allocate the memory and store the new entry.
- * Insert the element in top, with the assumption that in a database
- * system it is more likely that recently added entries are accessed
- * more frequently. */
- htidx = dictIsRehashing(d) ? 1 : 0;
+/* Adds a key in the dict's hashtable at the position returned by a preceding
+ * call to dictFindPositionForInsert. This is a low level function which allows
+ * splitting dictAddRaw in two parts. Normally, dictAddRaw or dictAdd should be
+ * used instead. */
+dictEntry *dictInsertAtPosition(dict *d, void *key, void *position) {
+ dictEntry **bucket = position; /* It's a bucket, but the API hides that. */
+ dictEntry *entry;
+ /* If rehashing is ongoing, we insert in table 1, otherwise in table 0.
+ * Assert that the provided bucket is the right table. */
+ int htidx = dictIsRehashing(d) ? 1 : 0;
+ assert(bucket >= &d->ht_table[htidx][0] &&
+ bucket <= &d->ht_table[htidx][DICTHT_SIZE_MASK(d->ht_size_exp[htidx])]);
size_t metasize = dictEntryMetadataSize(d);
- entry = zmalloc(sizeof(*entry) + metasize);
- if (metasize > 0) {
- memset(dictEntryMetadata(entry), 0, metasize);
+ if (d->type->no_value) {
+ assert(!metasize); /* Entry metadata + no value not supported. */
+ if (d->type->keys_are_odd && !*bucket) {
+ /* We can store the key directly in the destination bucket without the
+ * allocated entry.
+ *
+ * TODO: Add a flag 'keys_are_even' and if set, we can use this
+ * optimization for these dicts too. We can set the LSB bit when
+ * stored as a dict entry and clear it again when we need the key
+ * back. */
+ entry = key;
+ assert(entryIsKey(entry));
+ } else {
+ /* Allocate an entry without value. */
+ entry = createEntryNoValue(key, *bucket);
+ }
+ } else {
+ /* Allocate the memory and store the new entry.
+ * Insert the element in top, with the assumption that in a database
+ * system it is more likely that recently added entries are accessed
+ * more frequently. */
+ entry = zmalloc(sizeof(*entry) + metasize);
+ assert(entryIsNormal(entry)); /* Check alignment of allocation */
+ if (metasize > 0) {
+ memset(dictEntryMetadata(entry), 0, metasize);
+ }
+ entry->key = key;
+ entry->next = *bucket;
}
- entry->next = d->ht_table[htidx][index];
- d->ht_table[htidx][index] = entry;
+ *bucket = entry;
d->ht_used[htidx]++;
- /* Set the hash entry fields. */
- dictSetKey(d, entry, key);
return entry;
}
@@ -395,7 +512,7 @@ dictEntry *dictAddRaw(dict *d, void *key, dictEntry **existing)
* operation. */
int dictReplace(dict *d, void *key, void *val)
{
- dictEntry *entry, *existing, auxentry;
+ dictEntry *entry, *existing;
/* Try to add the element. If the key
* does not exists dictAdd will succeed. */
@@ -410,9 +527,10 @@ int dictReplace(dict *d, void *key, void *val)
* as the previous one. In this context, think to reference counting,
* you want to increment (set), and then decrement (free), and not the
* reverse. */
- auxentry = *existing;
+ void *oldval = dictGetVal(existing);
dictSetVal(d, existing, val);
- dictFreeVal(d, &auxentry);
+ if (d->type->valDestructor)
+ d->type->valDestructor(d, oldval);
return 0;
}
@@ -448,12 +566,13 @@ static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
he = d->ht_table[table][idx];
prevHe = NULL;
while(he) {
- if (key==he->key || dictCompareKeys(d, key, he->key)) {
+ void *he_key = dictGetKey(he);
+ if (key == he_key || dictCompareKeys(d, key, he_key)) {
/* Unlink the element from the list */
if (prevHe)
- prevHe->next = he->next;
+ dictSetNext(prevHe, dictGetNext(he));
else
- d->ht_table[table][idx] = he->next;
+ d->ht_table[table][idx] = dictGetNext(he);
if (!nofree) {
dictFreeUnlinkedEntry(d, he);
}
@@ -461,7 +580,7 @@ static dictEntry *dictGenericDelete(dict *d, const void *key, int nofree) {
return he;
}
prevHe = he;
- he = he->next;
+ he = dictGetNext(he);
}
if (!dictIsRehashing(d)) break;
}
@@ -505,7 +624,7 @@ void dictFreeUnlinkedEntry(dict *d, dictEntry *he) {
if (he == NULL) return;
dictFreeKey(d, he);
dictFreeVal(d, he);
- zfree(he);
+ if (!entryIsKey(he)) zfree(decodeMaskedPtr(he));
}
/* Destroy an entire dictionary */
@@ -520,10 +639,10 @@ int _dictClear(dict *d, int htidx, void(callback)(dict*)) {
if ((he = d->ht_table[htidx][i]) == NULL) continue;
while(he) {
- nextHe = he->next;
+ nextHe = dictGetNext(he);
dictFreeKey(d, he);
dictFreeVal(d, he);
- zfree(he);
+ if (!entryIsKey(he)) zfree(decodeMaskedPtr(he));
d->ht_used[htidx]--;
he = nextHe;
}
@@ -555,9 +674,10 @@ dictEntry *dictFind(dict *d, const void *key)
idx = h & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
he = d->ht_table[table][idx];
while(he) {
- if (key==he->key || dictCompareKeys(d, key, he->key))
+ void *he_key = dictGetKey(he);
+ if (key == he_key || dictCompareKeys(d, key, he_key))
return he;
- he = he->next;
+ he = dictGetNext(he);
}
if (!dictIsRehashing(d)) return NULL;
}
@@ -597,14 +717,15 @@ dictEntry *dictTwoPhaseUnlinkFind(dict *d, const void *key, dictEntry ***plink,
for (table = 0; table <= 1; table++) {
idx = h & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
dictEntry **ref = &d->ht_table[table][idx];
- while(*ref) {
- if (key==(*ref)->key || dictCompareKeys(d, key, (*ref)->key)) {
+ while (ref && *ref) {
+ void *de_key = dictGetKey(*ref);
+ if (key == de_key || dictCompareKeys(d, key, de_key)) {
*table_index = table;
*plink = ref;
dictPauseRehashing(d);
return *ref;
}
- ref = &(*ref)->next;
+ ref = dictGetNextRef(*ref);
}
if (!dictIsRehashing(d)) return NULL;
}
@@ -614,14 +735,15 @@ dictEntry *dictTwoPhaseUnlinkFind(dict *d, const void *key, dictEntry ***plink,
void dictTwoPhaseUnlinkFree(dict *d, dictEntry *he, dictEntry **plink, int table_index) {
if (he == NULL) return;
d->ht_used[table_index]--;
- *plink = he->next;
+ *plink = dictGetNext(he);
dictFreeKey(d, he);
dictFreeVal(d, he);
- zfree(he);
+ if (!entryIsKey(he)) zfree(decodeMaskedPtr(he));
dictResumeRehashing(d);
}
void dictSetKey(dict *d, dictEntry* de, void *key) {
+ assert(!d->type->no_value);
if (d->type->keyDup)
de->key = d->type->keyDup(d, key);
else
@@ -629,63 +751,104 @@ void dictSetKey(dict *d, dictEntry* de, void *key) {
}
void dictSetVal(dict *d, dictEntry *de, void *val) {
+ assert(entryHasValue(de));
de->v.val = d->type->valDup ? d->type->valDup(d, val) : val;
}
void dictSetSignedIntegerVal(dictEntry *de, int64_t val) {
+ assert(entryHasValue(de));
de->v.s64 = val;
}
void dictSetUnsignedIntegerVal(dictEntry *de, uint64_t val) {
+ assert(entryHasValue(de));
de->v.u64 = val;
}
void dictSetDoubleVal(dictEntry *de, double val) {
+ assert(entryHasValue(de));
de->v.d = val;
}
int64_t dictIncrSignedIntegerVal(dictEntry *de, int64_t val) {
+ assert(entryHasValue(de));
return de->v.s64 += val;
}
uint64_t dictIncrUnsignedIntegerVal(dictEntry *de, uint64_t val) {
+ assert(entryHasValue(de));
return de->v.u64 += val;
}
double dictIncrDoubleVal(dictEntry *de, double val) {
+ assert(entryHasValue(de));
return de->v.d += val;
}
/* A pointer to the metadata section within the dict entry. */
void *dictEntryMetadata(dictEntry *de) {
+ assert(entryHasValue(de));
return &de->metadata;
}
void *dictGetKey(const dictEntry *de) {
+ if (entryIsKey(de)) return (void*)de;
+ if (entryIsNoValue(de)) return decodeEntryNoValue(de)->key;
return de->key;
}
void *dictGetVal(const dictEntry *de) {
+ assert(entryHasValue(de));
return de->v.val;
}
int64_t dictGetSignedIntegerVal(const dictEntry *de) {
+ assert(entryHasValue(de));
return de->v.s64;
}
uint64_t dictGetUnsignedIntegerVal(const dictEntry *de) {
+ assert(entryHasValue(de));
return de->v.u64;
}
double dictGetDoubleVal(const dictEntry *de) {
+ assert(entryHasValue(de));
return de->v.d;
}
/* Returns a mutable reference to the value as a double within the entry. */
double *dictGetDoubleValPtr(dictEntry *de) {
+ assert(entryHasValue(de));
return &de->v.d;
}
+/* Returns the 'next' field of the entry or NULL if the entry doesn't have a
+ * 'next' field. */
+static dictEntry *dictGetNext(const dictEntry *de) {
+ if (entryIsKey(de)) return NULL; /* there's no next */
+ if (entryIsNoValue(de)) return decodeEntryNoValue(de)->next;
+ return de->next;
+}
+
+/* Returns a pointer to the 'next' field in the entry or NULL if the entry
+ * doesn't have a next field. */
+static dictEntry **dictGetNextRef(dictEntry *de) {
+ if (entryIsKey(de)) return NULL;
+ if (entryIsNoValue(de)) return &decodeEntryNoValue(de)->next;
+ return &de->next;
+}
+
+static void dictSetNext(dictEntry *de, dictEntry *next) {
+ assert(!entryIsKey(de));
+ if (entryIsNoValue(de)) {
+ dictEntryNoValue *entry = decodeEntryNoValue(de);
+ entry->next = next;
+ } else {
+ de->next = next;
+ }
+}
+
/* Returns the memory usage in bytes of the dict, excluding the size of the keys
* and values. */
size_t dictMemUsage(const dict *d) {
@@ -801,7 +964,7 @@ dictEntry *dictNext(dictIterator *iter)
if (iter->entry) {
/* We need to save the 'next' here, the iterator user
* may delete the entry we are returning. */
- iter->nextEntry = iter->entry->next;
+ iter->nextEntry = dictGetNext(iter->entry);
return iter->entry;
}
}
@@ -847,12 +1010,12 @@ dictEntry *dictGetRandomKey(dict *d)
listlen = 0;
orighe = he;
while(he) {
- he = he->next;
+ he = dictGetNext(he);
listlen++;
}
listele = random() % listlen;
he = orighe;
- while(listele--) he = he->next;
+ while(listele--) he = dictGetNext(he);
return he;
}
@@ -936,7 +1099,7 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
* empty while iterating. */
*des = he;
des++;
- he = he->next;
+ he = dictGetNext(he);
stored++;
if (stored == count) return stored;
}
@@ -948,17 +1111,39 @@ unsigned int dictGetSomeKeys(dict *d, dictEntry **des, unsigned int count) {
}
-/* Reallocate the dictEntry allocations in a bucket using the provided
- * allocation function in order to defrag them. */
-static void dictDefragBucket(dict *d, dictEntry **bucketref, dictDefragAllocFunction *allocfn) {
+/* Reallocate the dictEntry, key and value allocations in a bucket using the
+ * provided allocation functions in order to defrag them. */
+static void dictDefragBucket(dict *d, dictEntry **bucketref, dictDefragFunctions *defragfns) {
+ dictDefragAllocFunction *defragalloc = defragfns->defragAlloc;
+ dictDefragAllocFunction *defragkey = defragfns->defragKey;
+ dictDefragAllocFunction *defragval = defragfns->defragVal;
while (bucketref && *bucketref) {
- dictEntry *de = *bucketref, *newde;
- if ((newde = allocfn(de))) {
+ dictEntry *de = *bucketref, *newde = NULL;
+ void *newkey = defragkey ? defragkey(dictGetKey(de)) : NULL;
+ void *newval = defragval ? defragval(dictGetVal(de)) : NULL;
+ if (entryIsKey(de)) {
+ if (newkey) *bucketref = newkey;
+ assert(entryIsKey(*bucketref));
+ } else if (entryIsNoValue(de)) {
+ dictEntryNoValue *entry = decodeEntryNoValue(de), *newentry;
+ if ((newentry = defragalloc(entry))) {
+ newde = encodeMaskedPtr(newentry, ENTRY_PTR_NO_VALUE);
+ entry = newentry;
+ }
+ if (newkey) entry->key = newkey;
+ } else {
+ assert(entryIsNormal(de));
+ newde = defragalloc(de);
+ if (newde) de = newde;
+ if (newkey) de->key = newkey;
+ if (newval) de->v.val = newval;
+ }
+ if (newde) {
*bucketref = newde;
if (d->type->afterReplaceEntry)
d->type->afterReplaceEntry(d, newde);
}
- bucketref = &(*bucketref)->next;
+ bucketref = dictGetNextRef(*bucketref);
}
}
@@ -1094,14 +1279,14 @@ unsigned long dictScan(dict *d,
* entries using the provided allocation function. This feature was added for
* the active defrag feature.
*
- * The 'defracallocfn' callback is called with a pointer to memory that callback
- * can reallocate. The callback should return a new memory address or NULL,
+ * The 'defragfns' callbacks are called with a pointer to memory that callback
+ * can reallocate. The callbacks should return a new memory address or NULL,
* where NULL means that no reallocation happened and the old memory is still
* valid. */
unsigned long dictScanDefrag(dict *d,
unsigned long v,
dictScanFunction *fn,
- dictDefragAllocFunction *defragallocfn,
+ dictDefragFunctions *defragfns,
void *privdata)
{
int htidx0, htidx1;
@@ -1118,12 +1303,12 @@ unsigned long dictScanDefrag(dict *d,
m0 = DICTHT_SIZE_MASK(d->ht_size_exp[htidx0]);
/* Emit entries at cursor */
- if (defragallocfn) {
- dictDefragBucket(d, &d->ht_table[htidx0][v & m0], defragallocfn);
+ if (defragfns) {
+ dictDefragBucket(d, &d->ht_table[htidx0][v & m0], defragfns);
}
de = d->ht_table[htidx0][v & m0];
while (de) {
- next = de->next;
+ next = dictGetNext(de);
fn(privdata, de);
de = next;
}
@@ -1151,12 +1336,12 @@ unsigned long dictScanDefrag(dict *d,
m1 = DICTHT_SIZE_MASK(d->ht_size_exp[htidx1]);
/* Emit entries at cursor */
- if (defragallocfn) {
- dictDefragBucket(d, &d->ht_table[htidx0][v & m0], defragallocfn);
+ if (defragfns) {
+ dictDefragBucket(d, &d->ht_table[htidx0][v & m0], defragfns);
}
de = d->ht_table[htidx0][v & m0];
while (de) {
- next = de->next;
+ next = dictGetNext(de);
fn(privdata, de);
de = next;
}
@@ -1165,12 +1350,12 @@ unsigned long dictScanDefrag(dict *d,
* of the index pointed to by the cursor in the smaller table */
do {
/* Emit entries at cursor */
- if (defragallocfn) {
- dictDefragBucket(d, &d->ht_table[htidx1][v & m1], defragallocfn);
+ if (defragfns) {
+ dictDefragBucket(d, &d->ht_table[htidx1][v & m1], defragfns);
}
de = d->ht_table[htidx1][v & m1];
while (de) {
- next = de->next;
+ next = dictGetNext(de);
fn(privdata, de);
de = next;
}
@@ -1241,36 +1426,39 @@ static signed char _dictNextExp(unsigned long size)
}
}
-/* Returns the index of a free slot that can be populated with
- * a hash entry for the given 'key'.
- * If the key already exists, -1 is returned
- * and the optional output parameter may be filled.
- *
- * Note that if we are in the process of rehashing the hash table, the
- * index is always returned in the context of the second (new) hash table. */
-static long _dictKeyIndex(dict *d, const void *key, uint64_t hash, dictEntry **existing)
-{
+/* Finds and returns the position within the dict where the provided key should
+ * be inserted using dictInsertAtPosition if the key does not already exist in
+ * the dict. If the key exists in the dict, NULL is returned and the optional
+ * 'existing' entry pointer is populated, if provided. */
+void *dictFindPositionForInsert(dict *d, const void *key, dictEntry **existing) {
unsigned long idx, table;
dictEntry *he;
+ uint64_t hash = dictHashKey(d, key);
if (existing) *existing = NULL;
+ if (dictIsRehashing(d)) _dictRehashStep(d);
/* Expand the hash table if needed */
if (_dictExpandIfNeeded(d) == DICT_ERR)
- return -1;
+ return NULL;
for (table = 0; table <= 1; table++) {
idx = hash & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
/* Search if this slot does not already contain the given key */
he = d->ht_table[table][idx];
while(he) {
- if (key==he->key || dictCompareKeys(d, key, he->key)) {
+ void *he_key = dictGetKey(he);
+ if (key == he_key || dictCompareKeys(d, key, he_key)) {
if (existing) *existing = he;
- return -1;
+ return NULL;
}
- he = he->next;
+ he = dictGetNext(he);
}
if (!dictIsRehashing(d)) break;
}
- return idx;
+
+ /* If we are in the process of rehashing the hash table, the bucket is
+ * always returned in the context of the second (new) hash table. */
+ dictEntry **bucket = &d->ht_table[dictIsRehashing(d) ? 1 : 0][idx];
+ return bucket;
}
void dictEmpty(dict *d, void(callback)(dict*)) {
@@ -1302,9 +1490,9 @@ dictEntry *dictFindEntryByPtrAndHash(dict *d, const void *oldptr, uint64_t hash)
idx = hash & DICTHT_SIZE_MASK(d->ht_size_exp[table]);
he = d->ht_table[table][idx];
while(he) {
- if (oldptr==he->key)
+ if (oldptr == dictGetKey(he))
return he;
- he = he->next;
+ he = dictGetNext(he);
}
if (!dictIsRehashing(d)) return NULL;
}
@@ -1340,7 +1528,7 @@ size_t _dictGetStatsHt(char *buf, size_t bufsize, dict *d, int htidx) {
he = d->ht_table[htidx][i];
while(he) {
chainlen++;
- he = he->next;
+ he = dictGetNext(he);
}
clvector[(chainlen < DICT_STATS_VECTLEN) ? chainlen : (DICT_STATS_VECTLEN-1)]++;
if (chainlen > maxchainlen) maxchainlen = chainlen;