mirror of
https://github.com/zebrajr/postgres.git
synced 2025-12-07 00:20:24 +01:00
This patch is a no-op patch which is intended to reduce the chances of failures of omission once the functional part of the "snapshot too old" patch goes in. It adds parameters for snapshot, relation, and an enum to specify whether the snapshot age check needs to be done for the page at this point. This initial patch passes NULL for the first two new parameters and BGP_NO_SNAPSHOT_TEST for the third. The follow-on patch will change the places where the test needs to be made.
345 lines
8.4 KiB
C
345 lines
8.4 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* hashutil.c
|
|
* Utility code for Postgres hash implementation.
|
|
*
|
|
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/access/hash/hashutil.c
|
|
*
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
|
|
#include "access/hash.h"
|
|
#include "access/reloptions.h"
|
|
#include "access/relscan.h"
|
|
#include "utils/lsyscache.h"
|
|
#include "utils/rel.h"
|
|
|
|
|
|
/*
|
|
* _hash_checkqual -- does the index tuple satisfy the scan conditions?
|
|
*/
|
|
bool
|
|
_hash_checkqual(IndexScanDesc scan, IndexTuple itup)
|
|
{
|
|
/*
|
|
* Currently, we can't check any of the scan conditions since we do not
|
|
* have the original index entry value to supply to the sk_func. Always
|
|
* return true; we expect that hashgettuple already set the recheck flag
|
|
* to make the main indexscan code do it.
|
|
*/
|
|
#ifdef NOT_USED
|
|
TupleDesc tupdesc = RelationGetDescr(scan->indexRelation);
|
|
ScanKey key = scan->keyData;
|
|
int scanKeySize = scan->numberOfKeys;
|
|
|
|
while (scanKeySize > 0)
|
|
{
|
|
Datum datum;
|
|
bool isNull;
|
|
Datum test;
|
|
|
|
datum = index_getattr(itup,
|
|
key->sk_attno,
|
|
tupdesc,
|
|
&isNull);
|
|
|
|
/* assume sk_func is strict */
|
|
if (isNull)
|
|
return false;
|
|
if (key->sk_flags & SK_ISNULL)
|
|
return false;
|
|
|
|
test = FunctionCall2Coll(&key->sk_func, key->sk_collation,
|
|
datum, key->sk_argument);
|
|
|
|
if (!DatumGetBool(test))
|
|
return false;
|
|
|
|
key++;
|
|
scanKeySize--;
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* _hash_datum2hashkey -- given a Datum, call the index's hash procedure
|
|
*
|
|
* The Datum is assumed to be of the index's column type, so we can use the
|
|
* "primary" hash procedure that's tracked for us by the generic index code.
|
|
*/
|
|
uint32
|
|
_hash_datum2hashkey(Relation rel, Datum key)
|
|
{
|
|
FmgrInfo *procinfo;
|
|
Oid collation;
|
|
|
|
/* XXX assumes index has only one attribute */
|
|
procinfo = index_getprocinfo(rel, 1, HASHPROC);
|
|
collation = rel->rd_indcollation[0];
|
|
|
|
return DatumGetUInt32(FunctionCall1Coll(procinfo, collation, key));
|
|
}
|
|
|
|
/*
|
|
* _hash_datum2hashkey_type -- given a Datum of a specified type,
|
|
* hash it in a fashion compatible with this index
|
|
*
|
|
* This is much more expensive than _hash_datum2hashkey, so use it only in
|
|
* cross-type situations.
|
|
*/
|
|
uint32
|
|
_hash_datum2hashkey_type(Relation rel, Datum key, Oid keytype)
|
|
{
|
|
RegProcedure hash_proc;
|
|
Oid collation;
|
|
|
|
/* XXX assumes index has only one attribute */
|
|
hash_proc = get_opfamily_proc(rel->rd_opfamily[0],
|
|
keytype,
|
|
keytype,
|
|
HASHPROC);
|
|
if (!RegProcedureIsValid(hash_proc))
|
|
elog(ERROR, "missing support function %d(%u,%u) for index \"%s\"",
|
|
HASHPROC, keytype, keytype,
|
|
RelationGetRelationName(rel));
|
|
collation = rel->rd_indcollation[0];
|
|
|
|
return DatumGetUInt32(OidFunctionCall1Coll(hash_proc, collation, key));
|
|
}
|
|
|
|
/*
|
|
* _hash_hashkey2bucket -- determine which bucket the hashkey maps to.
|
|
*/
|
|
Bucket
|
|
_hash_hashkey2bucket(uint32 hashkey, uint32 maxbucket,
|
|
uint32 highmask, uint32 lowmask)
|
|
{
|
|
Bucket bucket;
|
|
|
|
bucket = hashkey & highmask;
|
|
if (bucket > maxbucket)
|
|
bucket = bucket & lowmask;
|
|
|
|
return bucket;
|
|
}
|
|
|
|
/*
|
|
* _hash_log2 -- returns ceil(lg2(num))
|
|
*/
|
|
uint32
|
|
_hash_log2(uint32 num)
|
|
{
|
|
uint32 i,
|
|
limit;
|
|
|
|
limit = 1;
|
|
for (i = 0; limit < num; limit <<= 1, i++)
|
|
;
|
|
return i;
|
|
}
|
|
|
|
/*
|
|
* _hash_checkpage -- sanity checks on the format of all hash pages
|
|
*
|
|
* If flags is not zero, it is a bitwise OR of the acceptable values of
|
|
* hasho_flag.
|
|
*/
|
|
void
|
|
_hash_checkpage(Relation rel, Buffer buf, int flags)
|
|
{
|
|
Page page = BufferGetPage(buf, NULL, NULL, BGP_NO_SNAPSHOT_TEST);
|
|
|
|
/*
|
|
* ReadBuffer verifies that every newly-read page passes
|
|
* PageHeaderIsValid, which means it either contains a reasonably sane
|
|
* page header or is all-zero. We have to defend against the all-zero
|
|
* case, however.
|
|
*/
|
|
if (PageIsNew(page))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("index \"%s\" contains unexpected zero page at block %u",
|
|
RelationGetRelationName(rel),
|
|
BufferGetBlockNumber(buf)),
|
|
errhint("Please REINDEX it.")));
|
|
|
|
/*
|
|
* Additionally check that the special area looks sane.
|
|
*/
|
|
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("index \"%s\" contains corrupted page at block %u",
|
|
RelationGetRelationName(rel),
|
|
BufferGetBlockNumber(buf)),
|
|
errhint("Please REINDEX it.")));
|
|
|
|
if (flags)
|
|
{
|
|
HashPageOpaque opaque = (HashPageOpaque) PageGetSpecialPointer(page);
|
|
|
|
if ((opaque->hasho_flag & flags) == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("index \"%s\" contains corrupted page at block %u",
|
|
RelationGetRelationName(rel),
|
|
BufferGetBlockNumber(buf)),
|
|
errhint("Please REINDEX it.")));
|
|
}
|
|
|
|
/*
|
|
* When checking the metapage, also verify magic number and version.
|
|
*/
|
|
if (flags == LH_META_PAGE)
|
|
{
|
|
HashMetaPage metap = HashPageGetMeta(page);
|
|
|
|
if (metap->hashm_magic != HASH_MAGIC)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("index \"%s\" is not a hash index",
|
|
RelationGetRelationName(rel))));
|
|
|
|
if (metap->hashm_version != HASH_VERSION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("index \"%s\" has wrong hash version",
|
|
RelationGetRelationName(rel)),
|
|
errhint("Please REINDEX it.")));
|
|
}
|
|
}
|
|
|
|
bytea *
|
|
hashoptions(Datum reloptions, bool validate)
|
|
{
|
|
return default_reloptions(reloptions, validate, RELOPT_KIND_HASH);
|
|
}
|
|
|
|
/*
|
|
* _hash_get_indextuple_hashkey - get the hash index tuple's hash key value
|
|
*/
|
|
uint32
|
|
_hash_get_indextuple_hashkey(IndexTuple itup)
|
|
{
|
|
char *attp;
|
|
|
|
/*
|
|
* We assume the hash key is the first attribute and can't be null, so
|
|
* this can be done crudely but very very cheaply ...
|
|
*/
|
|
attp = (char *) itup + IndexInfoFindDataOffset(itup->t_info);
|
|
return *((uint32 *) attp);
|
|
}
|
|
|
|
/*
|
|
* _hash_form_tuple - form an index tuple containing hash code only
|
|
*/
|
|
IndexTuple
|
|
_hash_form_tuple(Relation index, Datum *values, bool *isnull)
|
|
{
|
|
IndexTuple itup;
|
|
uint32 hashkey;
|
|
Datum hashkeydatum;
|
|
TupleDesc hashdesc;
|
|
|
|
if (isnull[0])
|
|
hashkeydatum = (Datum) 0;
|
|
else
|
|
{
|
|
hashkey = _hash_datum2hashkey(index, values[0]);
|
|
hashkeydatum = UInt32GetDatum(hashkey);
|
|
}
|
|
hashdesc = RelationGetDescr(index);
|
|
Assert(hashdesc->natts == 1);
|
|
itup = index_form_tuple(hashdesc, &hashkeydatum, isnull);
|
|
return itup;
|
|
}
|
|
|
|
/*
|
|
* _hash_binsearch - Return the offset number in the page where the
|
|
* specified hash value should be sought or inserted.
|
|
*
|
|
* We use binary search, relying on the assumption that the existing entries
|
|
* are ordered by hash key.
|
|
*
|
|
* Returns the offset of the first index entry having hashkey >= hash_value,
|
|
* or the page's max offset plus one if hash_value is greater than all
|
|
* existing hash keys in the page. This is the appropriate place to start
|
|
* a search, or to insert a new item.
|
|
*/
|
|
OffsetNumber
|
|
_hash_binsearch(Page page, uint32 hash_value)
|
|
{
|
|
OffsetNumber upper;
|
|
OffsetNumber lower;
|
|
|
|
/* Loop invariant: lower <= desired place <= upper */
|
|
upper = PageGetMaxOffsetNumber(page) + 1;
|
|
lower = FirstOffsetNumber;
|
|
|
|
while (upper > lower)
|
|
{
|
|
OffsetNumber off;
|
|
IndexTuple itup;
|
|
uint32 hashkey;
|
|
|
|
off = (upper + lower) / 2;
|
|
Assert(OffsetNumberIsValid(off));
|
|
|
|
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
|
|
hashkey = _hash_get_indextuple_hashkey(itup);
|
|
if (hashkey < hash_value)
|
|
lower = off + 1;
|
|
else
|
|
upper = off;
|
|
}
|
|
|
|
return lower;
|
|
}
|
|
|
|
/*
|
|
* _hash_binsearch_last
|
|
*
|
|
* Same as above, except that if there are multiple matching items in the
|
|
* page, we return the offset of the last one instead of the first one,
|
|
* and the possible range of outputs is 0..maxoffset not 1..maxoffset+1.
|
|
* This is handy for starting a new page in a backwards scan.
|
|
*/
|
|
OffsetNumber
|
|
_hash_binsearch_last(Page page, uint32 hash_value)
|
|
{
|
|
OffsetNumber upper;
|
|
OffsetNumber lower;
|
|
|
|
/* Loop invariant: lower <= desired place <= upper */
|
|
upper = PageGetMaxOffsetNumber(page);
|
|
lower = FirstOffsetNumber - 1;
|
|
|
|
while (upper > lower)
|
|
{
|
|
IndexTuple itup;
|
|
OffsetNumber off;
|
|
uint32 hashkey;
|
|
|
|
off = (upper + lower + 1) / 2;
|
|
Assert(OffsetNumberIsValid(off));
|
|
|
|
itup = (IndexTuple) PageGetItem(page, PageGetItemId(page, off));
|
|
hashkey = _hash_get_indextuple_hashkey(itup);
|
|
if (hashkey > hash_value)
|
|
upper = off - 1;
|
|
else
|
|
lower = off;
|
|
}
|
|
|
|
return lower;
|
|
}
|