void * hash_seq_search(HASH_SEQ_STATUS *status) { HTAB *hashp = status->hashp; HASHHDR *hctl = hashp->hctl; while (status->curBucket <= hctl->max_bucket) { long segment_num; long segment_ndx; HASHSEGMENT segp; if (status->curEntry != NULL) { /* Continuing scan of curBucket... */ HASHELEMENT *curElem; curElem = status->curEntry; status->curEntry = curElem->link; if (status->curEntry == NULL) /* end of this bucket */ ++status->curBucket; return (void *) ELEMENTKEY(curElem); } /* * initialize the search within this bucket. */ segment_num = status->curBucket >> hctl->sshift; segment_ndx = MOD(status->curBucket, hctl->ssize); /* * first find the right segment in the table directory. */ segp = hashp->dir[segment_num]; if (segp == NULL) hash_corrupted(hashp); /* * now find the right index into the segment for the first item in * this bucket's chain. if the bucket is not empty (its entry in * the dir is valid), we know this must correspond to a valid * element and not a freed element because it came out of the * directory of valid stuff. if there are elements in the bucket * chains that point to the freelist we're in big trouble. */ status->curEntry = segp[segment_ndx]; if (status->curEntry == NULL) /* empty bucket */ ++status->curBucket; } return NULL; /* out of buckets */ }
void * hash_search_with_hash_value(HTAB *hashp, const void *keyPtr, uint32 hashvalue, HASHACTION action, bool *foundPtr) { HASHHDR *hctl = hashp->hctl; Size keysize; uint32 bucket; long segment_num; long segment_ndx; HASHSEGMENT segp; HASHBUCKET currBucket; HASHBUCKET *prevBucketPtr; HashCompareFunc match; #if HASH_STATISTICS hash_accesses++; hctl->accesses++; #endif /* * If inserting, check if it is time to split a bucket. * * NOTE: failure to expand table is not a fatal error, it just means we * have to run at higher fill factor than we wanted. However, if we're * using the palloc allocator then it will throw error anyway on * out-of-memory, so we must do this before modifying the table. */ if (action == HASH_ENTER || action == HASH_ENTER_NULL) { /* * Can't split if running in partitioned mode, nor if frozen, nor if * table is the subject of any active hash_seq_search scans. Strange * order of these tests is to try to check cheaper conditions first. */ if (!IS_PARTITIONED(hctl) && !hashp->frozen && hctl->nentries / (long) (hctl->max_bucket + 1) >= hctl->ffactor && !has_seq_scans(hashp)) (void) expand_table(hashp); } /* * Do the initial lookup */ bucket = calc_bucket(hctl, hashvalue); segment_num = bucket >> hashp->sshift; segment_ndx = MOD(bucket, hashp->ssize); segp = hashp->dir[segment_num]; if (segp == NULL) hash_corrupted(hashp); prevBucketPtr = &segp[segment_ndx]; currBucket = *prevBucketPtr; /* * Follow collision chain looking for matching key */ match = hashp->match; /* save one fetch in inner loop */ keysize = hashp->keysize; /* ditto */ while (currBucket != NULL) { if (currBucket->hashvalue == hashvalue && match(ELEMENTKEY(currBucket), keyPtr, keysize) == 0) break; prevBucketPtr = &(currBucket->link); currBucket = *prevBucketPtr; #if HASH_STATISTICS hash_collisions++; hctl->collisions++; #endif } if (foundPtr) *foundPtr = (bool) (currBucket != NULL); /* * OK, now what? */ switch (action) { case HASH_FIND: if (currBucket != NULL) return (void *) ELEMENTKEY(currBucket); return NULL; case HASH_REMOVE: if (currBucket != NULL) { /* use volatile pointer to prevent code rearrangement */ volatile HASHHDR *hctlv = hctl; /* if partitioned, must lock to touch nentries and freeList */ if (IS_PARTITIONED(hctlv)) SpinLockAcquire(&hctlv->mutex); Assert(hctlv->nentries > 0); hctlv->nentries--; /* remove record from hash bucket's chain. */ *prevBucketPtr = currBucket->link; /* add the record to the freelist for this table. */ currBucket->link = hctlv->freeList; hctlv->freeList = currBucket; if (IS_PARTITIONED(hctlv)) SpinLockRelease(&hctlv->mutex); /* * better hope the caller is synchronizing access to this * element, because someone else is going to reuse it the next * time something is added to the table */ return (void *) ELEMENTKEY(currBucket); } return NULL; case HASH_ENTER_NULL: /* ENTER_NULL does not work with palloc-based allocator */ Assert(hashp->alloc != DynaHashAlloc); /* FALL THRU */ case HASH_ENTER: /* Return existing element if found, else create one */ if (currBucket != NULL) return (void *) ELEMENTKEY(currBucket); /* disallow inserts if frozen */ if (hashp->frozen) elog(ERROR, "cannot insert into frozen hashtable \"%s\"", hashp->tabname); currBucket = get_hash_entry(hashp); if (currBucket == NULL) { /* out of memory */ if (action == HASH_ENTER_NULL) return NULL; /* report a generic message */ if (hashp->isshared) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of shared memory"))); else ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); } /* link into hashbucket chain */ *prevBucketPtr = currBucket; currBucket->link = NULL; /* copy key into record */ currBucket->hashvalue = hashvalue; hashp->keycopy(ELEMENTKEY(currBucket), keyPtr, keysize); /* * Caller is expected to fill the data field on return. DO NOT * insert any code that could possibly throw error here, as doing * so would leave the table entry incomplete and hence corrupt the * caller's data structure. */ return (void *) ELEMENTKEY(currBucket); } elog(ERROR, "unrecognized hash action code: %d", (int) action); return NULL; /* keep compiler quiet */ }
/* * hash_update_hash_key -- change the hash key of an existing table entry * * This is equivalent to removing the entry, making a new entry, and copying * over its data, except that the entry never goes to the table's freelist. * Therefore this cannot suffer an out-of-memory failure, even if there are * other processes operating in other partitions of the hashtable. * * Returns TRUE if successful, FALSE if the requested new hash key is already * present. Throws error if the specified entry pointer isn't actually a * table member. * * NB: currently, there is no special case for old and new hash keys being * identical, which means we'll report FALSE for that situation. This is * preferable for existing uses. * * NB: for a partitioned hashtable, caller must hold lock on both relevant * partitions, if the new hash key would belong to a different partition. */ bool hash_update_hash_key(HTAB *hashp, void *existingEntry, const void *newKeyPtr) { HASHELEMENT *existingElement = ELEMENT_FROM_KEY(existingEntry); HASHHDR *hctl = hashp->hctl; uint32 newhashvalue; Size keysize; uint32 bucket; uint32 newbucket; long segment_num; long segment_ndx; HASHSEGMENT segp; HASHBUCKET currBucket; HASHBUCKET *prevBucketPtr; HASHBUCKET *oldPrevPtr; HashCompareFunc match; #if HASH_STATISTICS hash_accesses++; hctl->accesses++; #endif /* disallow updates if frozen */ if (hashp->frozen) elog(ERROR, "cannot update in frozen hashtable \"%s\"", hashp->tabname); /* * Lookup the existing element using its saved hash value. We need to * do this to be able to unlink it from its hash chain, but as a side * benefit we can verify the validity of the passed existingEntry pointer. */ bucket = calc_bucket(hctl, existingElement->hashvalue); segment_num = bucket >> hashp->sshift; segment_ndx = MOD(bucket, hashp->ssize); segp = hashp->dir[segment_num]; if (segp == NULL) hash_corrupted(hashp); prevBucketPtr = &segp[segment_ndx]; currBucket = *prevBucketPtr; while (currBucket != NULL) { if (currBucket == existingElement) break; prevBucketPtr = &(currBucket->link); currBucket = *prevBucketPtr; } if (currBucket == NULL) elog(ERROR, "hash_update_hash_key argument is not in hashtable \"%s\"", hashp->tabname); oldPrevPtr = prevBucketPtr; /* * Now perform the equivalent of a HASH_ENTER operation to locate the * hash chain we want to put the entry into. */ newhashvalue = hashp->hash(newKeyPtr, hashp->keysize); newbucket = calc_bucket(hctl, newhashvalue); segment_num = newbucket >> hashp->sshift; segment_ndx = MOD(newbucket, hashp->ssize); segp = hashp->dir[segment_num]; if (segp == NULL) hash_corrupted(hashp); prevBucketPtr = &segp[segment_ndx]; currBucket = *prevBucketPtr; /* * Follow collision chain looking for matching key */ match = hashp->match; /* save one fetch in inner loop */ keysize = hashp->keysize; /* ditto */ while (currBucket != NULL) { if (currBucket->hashvalue == newhashvalue && match(ELEMENTKEY(currBucket), newKeyPtr, keysize) == 0) break; prevBucketPtr = &(currBucket->link); currBucket = *prevBucketPtr; #if HASH_STATISTICS hash_collisions++; hctl->collisions++; #endif } if (currBucket != NULL) return false; /* collision with an existing entry */ currBucket = existingElement; /* * If old and new hash values belong to the same bucket, we need not * change any chain links, and indeed should not since this simplistic * update will corrupt the list if currBucket is the last element. (We * cannot fall out earlier, however, since we need to scan the bucket to * check for duplicate keys.) */ if (bucket != newbucket) { /* OK to remove record from old hash bucket's chain. */ *oldPrevPtr = currBucket->link; /* link into new hashbucket chain */ *prevBucketPtr = currBucket; currBucket->link = NULL; } /* copy new key into record */ currBucket->hashvalue = newhashvalue; hashp->keycopy(ELEMENTKEY(currBucket), newKeyPtr, keysize); /* rest of record is untouched */ return true; }
/*---------- * hash_search -- look up key in table and perform action * * action is one of: * HASH_FIND: look up key in table * HASH_ENTER: look up key in table, creating entry if not present * HASH_REMOVE: look up key in table, remove entry if present * HASH_FIND_SAVE: look up key in table, also save in static var * HASH_REMOVE_SAVED: remove entry saved by HASH_FIND_SAVE * * Return value is a pointer to the element found/entered/removed if any, * or NULL if no match was found. (NB: in the case of the REMOVE actions, * the result is a dangling pointer that shouldn't be dereferenced!) * A NULL result for HASH_ENTER implies we ran out of memory. * * If foundPtr isn't NULL, then *foundPtr is set TRUE if we found an * existing entry in the table, FALSE otherwise. This is needed in the * HASH_ENTER case, but is redundant with the return value otherwise. * * The HASH_FIND_SAVE/HASH_REMOVE_SAVED interface is a hack to save one * table lookup in a find/process/remove scenario. Note that no other * addition or removal in the table can safely happen in between. *---------- */ void * hash_search(HTAB *hashp, const void *keyPtr, HASHACTION action, bool *foundPtr) { HASHHDR *hctl = hashp->hctl; uint32 hashvalue = 0; uint32 bucket; long segment_num; long segment_ndx; HASHSEGMENT segp; HASHBUCKET currBucket; HASHBUCKET *prevBucketPtr; static struct State { HASHBUCKET currBucket; HASHBUCKET *prevBucketPtr; } saveState; #if HASH_STATISTICS hash_accesses++; hctl->accesses++; #endif /* * Do the initial lookup (or recall result of prior lookup) */ if (action == HASH_REMOVE_SAVED) { currBucket = saveState.currBucket; prevBucketPtr = saveState.prevBucketPtr; /* * Try to catch subsequent errors */ Assert(currBucket); saveState.currBucket = NULL; } else { HashCompareFunc match; Size keysize = hctl->keysize; hashvalue = hashp->hash(keyPtr, keysize); bucket = calc_bucket(hctl, hashvalue); segment_num = bucket >> hctl->sshift; segment_ndx = MOD(bucket, hctl->ssize); segp = hashp->dir[segment_num]; if (segp == NULL) hash_corrupted(hashp); prevBucketPtr = &segp[segment_ndx]; currBucket = *prevBucketPtr; /* * Follow collision chain looking for matching key */ match = hashp->match; /* save one fetch in inner loop */ while (currBucket != NULL) { if (currBucket->hashvalue == hashvalue && match(ELEMENTKEY(currBucket), keyPtr, keysize) == 0) break; prevBucketPtr = &(currBucket->link); currBucket = *prevBucketPtr; #if HASH_STATISTICS hash_collisions++; hctl->collisions++; #endif } } if (foundPtr) *foundPtr = (bool) (currBucket != NULL); /* * OK, now what? */ switch (action) { case HASH_FIND: if (currBucket != NULL) return (void *) ELEMENTKEY(currBucket); return NULL; case HASH_FIND_SAVE: if (currBucket != NULL) { saveState.currBucket = currBucket; saveState.prevBucketPtr = prevBucketPtr; return (void *) ELEMENTKEY(currBucket); } return NULL; case HASH_REMOVE: case HASH_REMOVE_SAVED: if (currBucket != NULL) { Assert(hctl->nentries > 0); hctl->nentries--; /* remove record from hash bucket's chain. */ *prevBucketPtr = currBucket->link; /* add the record to the freelist for this table. */ currBucket->link = hctl->freeList; hctl->freeList = currBucket; /* * better hope the caller is synchronizing access to this * element, because someone else is going to reuse it the * next time something is added to the table */ return (void *) ELEMENTKEY(currBucket); } return NULL; case HASH_ENTER: /* Return existing element if found, else create one */ if (currBucket != NULL) return (void *) ELEMENTKEY(currBucket); /* get the next free element */ currBucket = hctl->freeList; if (currBucket == NULL) { /* no free elements. allocate another chunk of buckets */ if (!element_alloc(hashp)) return NULL; /* out of memory */ currBucket = hctl->freeList; Assert(currBucket != NULL); } hctl->freeList = currBucket->link; /* link into hashbucket chain */ *prevBucketPtr = currBucket; currBucket->link = NULL; /* copy key into record */ currBucket->hashvalue = hashvalue; hashp->keycopy(ELEMENTKEY(currBucket), keyPtr, hctl->keysize); /* caller is expected to fill the data field on return */ /* Check if it is time to split the segment */ if (++hctl->nentries / (long) (hctl->max_bucket + 1) > hctl->ffactor) { /* * NOTE: failure to expand table is not a fatal error, it * just means we have to run at higher fill factor than we * wanted. */ expand_table(hashp); } return (void *) ELEMENTKEY(currBucket); } elog(ERROR, "unrecognized hash action code: %d", (int) action); return NULL; /* keep compiler quiet */ }