
We have been using the term RelFileNode to refer to either (1) the integer that is used to name the sequence of files for a certain relation within the directory set aside for that tablespace/database combination; or (2) that value plus the OIDs of the tablespace and database; or occasionally (3) the whole series of files created for a relation based on those values. Using the same name for more than one thing is confusing. Replace RelFileNode with RelFileNumber when we're talking about just the single number, i.e. (1) from above, and with RelFileLocator when we're talking about all the things that are needed to locate a relation's files on disk, i.e. (2) from above. In the places where we refer to (3) as a relfilenode, instead refer to "relation storage". Since there is a ton of SQL code in the world that knows about pg_class.relfilenode, don't change the name of that column, or of other SQL-facing things that derive their name from it. On the other hand, do adjust closely-related internal terminology. For example, the structure member names dbNode and spcNode appear to be derived from the fact that the structure itself was called RelFileNode, so change those to dbOid and spcOid. Likewise, various variables with names like rnode and relnode get renamed appropriately, according to how they're being used in context. Hopefully, this is clearer than before. It is also preparation for future patches that intend to widen the relfilenumber fields from its current width of 32 bits. Variables that store a relfilenumber are now declared as type RelFileNumber rather than type Oid; right now, these are the same, but that can now more easily be changed. Dilip Kumar, per an idea from me. Reviewed also by Andres Freund. I fixed some whitespace issues, changed a couple of words in a comment, and made one other minor correction. Discussion: http://postgr.es/m/CA+TgmoamOtXbVAQf9hWFzonUo6bhhjS6toZQd7HZ-pmojtAmag@mail.gmail.com Discussion: http://postgr.es/m/CA+Tgmobp7+7kmi4gkq7Y+4AM9fTvL+O1oQ4-5gFTT+6Ng-dQ=g@mail.gmail.com Discussion: http://postgr.es/m/CAFiTN-vTe79M8uDH1yprOU64MNFE+R3ODRuA+JWf27JbhY4hJw@mail.gmail.com
708 lines
19 KiB
C
708 lines
19 KiB
C
/*-------------------------------------------------------------------------
|
|
*
|
|
* ginutil.c
|
|
* Utility routines for the Postgres inverted index access method.
|
|
*
|
|
*
|
|
* Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
|
|
* Portions Copyright (c) 1994, Regents of the University of California
|
|
*
|
|
* IDENTIFICATION
|
|
* src/backend/access/gin/ginutil.c
|
|
*-------------------------------------------------------------------------
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/gin_private.h"
|
|
#include "access/ginxlog.h"
|
|
#include "access/reloptions.h"
|
|
#include "access/xloginsert.h"
|
|
#include "catalog/pg_collation.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "commands/vacuum.h"
|
|
#include "miscadmin.h"
|
|
#include "storage/indexfsm.h"
|
|
#include "storage/lmgr.h"
|
|
#include "storage/predicate.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/index_selfuncs.h"
|
|
#include "utils/typcache.h"
|
|
|
|
|
|
/*
|
|
* GIN handler function: return IndexAmRoutine with access method parameters
|
|
* and callbacks.
|
|
*/
|
|
Datum
|
|
ginhandler(PG_FUNCTION_ARGS)
|
|
{
|
|
IndexAmRoutine *amroutine = makeNode(IndexAmRoutine);
|
|
|
|
amroutine->amstrategies = 0;
|
|
amroutine->amsupport = GINNProcs;
|
|
amroutine->amoptsprocnum = GIN_OPTIONS_PROC;
|
|
amroutine->amcanorder = false;
|
|
amroutine->amcanorderbyop = false;
|
|
amroutine->amcanbackward = false;
|
|
amroutine->amcanunique = false;
|
|
amroutine->amcanmulticol = true;
|
|
amroutine->amoptionalkey = true;
|
|
amroutine->amsearcharray = false;
|
|
amroutine->amsearchnulls = false;
|
|
amroutine->amstorage = true;
|
|
amroutine->amclusterable = false;
|
|
amroutine->ampredlocks = true;
|
|
amroutine->amcanparallel = false;
|
|
amroutine->amcaninclude = false;
|
|
amroutine->amusemaintenanceworkmem = true;
|
|
amroutine->amparallelvacuumoptions =
|
|
VACUUM_OPTION_PARALLEL_BULKDEL | VACUUM_OPTION_PARALLEL_CLEANUP;
|
|
amroutine->amkeytype = InvalidOid;
|
|
|
|
amroutine->ambuild = ginbuild;
|
|
amroutine->ambuildempty = ginbuildempty;
|
|
amroutine->aminsert = gininsert;
|
|
amroutine->ambulkdelete = ginbulkdelete;
|
|
amroutine->amvacuumcleanup = ginvacuumcleanup;
|
|
amroutine->amcanreturn = NULL;
|
|
amroutine->amcostestimate = gincostestimate;
|
|
amroutine->amoptions = ginoptions;
|
|
amroutine->amproperty = NULL;
|
|
amroutine->ambuildphasename = NULL;
|
|
amroutine->amvalidate = ginvalidate;
|
|
amroutine->amadjustmembers = ginadjustmembers;
|
|
amroutine->ambeginscan = ginbeginscan;
|
|
amroutine->amrescan = ginrescan;
|
|
amroutine->amgettuple = NULL;
|
|
amroutine->amgetbitmap = gingetbitmap;
|
|
amroutine->amendscan = ginendscan;
|
|
amroutine->ammarkpos = NULL;
|
|
amroutine->amrestrpos = NULL;
|
|
amroutine->amestimateparallelscan = NULL;
|
|
amroutine->aminitparallelscan = NULL;
|
|
amroutine->amparallelrescan = NULL;
|
|
|
|
PG_RETURN_POINTER(amroutine);
|
|
}
|
|
|
|
/*
|
|
* initGinState: fill in an empty GinState struct to describe the index
|
|
*
|
|
* Note: assorted subsidiary data is allocated in the CurrentMemoryContext.
|
|
*/
|
|
void
|
|
initGinState(GinState *state, Relation index)
|
|
{
|
|
TupleDesc origTupdesc = RelationGetDescr(index);
|
|
int i;
|
|
|
|
MemSet(state, 0, sizeof(GinState));
|
|
|
|
state->index = index;
|
|
state->oneCol = (origTupdesc->natts == 1);
|
|
state->origTupdesc = origTupdesc;
|
|
|
|
for (i = 0; i < origTupdesc->natts; i++)
|
|
{
|
|
Form_pg_attribute attr = TupleDescAttr(origTupdesc, i);
|
|
|
|
if (state->oneCol)
|
|
state->tupdesc[i] = state->origTupdesc;
|
|
else
|
|
{
|
|
state->tupdesc[i] = CreateTemplateTupleDesc(2);
|
|
|
|
TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 1, NULL,
|
|
INT2OID, -1, 0);
|
|
TupleDescInitEntry(state->tupdesc[i], (AttrNumber) 2, NULL,
|
|
attr->atttypid,
|
|
attr->atttypmod,
|
|
attr->attndims);
|
|
TupleDescInitEntryCollation(state->tupdesc[i], (AttrNumber) 2,
|
|
attr->attcollation);
|
|
}
|
|
|
|
/*
|
|
* If the compare proc isn't specified in the opclass definition, look
|
|
* up the index key type's default btree comparator.
|
|
*/
|
|
if (index_getprocid(index, i + 1, GIN_COMPARE_PROC) != InvalidOid)
|
|
{
|
|
fmgr_info_copy(&(state->compareFn[i]),
|
|
index_getprocinfo(index, i + 1, GIN_COMPARE_PROC),
|
|
CurrentMemoryContext);
|
|
}
|
|
else
|
|
{
|
|
TypeCacheEntry *typentry;
|
|
|
|
typentry = lookup_type_cache(attr->atttypid,
|
|
TYPECACHE_CMP_PROC_FINFO);
|
|
if (!OidIsValid(typentry->cmp_proc_finfo.fn_oid))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_UNDEFINED_FUNCTION),
|
|
errmsg("could not identify a comparison function for type %s",
|
|
format_type_be(attr->atttypid))));
|
|
fmgr_info_copy(&(state->compareFn[i]),
|
|
&(typentry->cmp_proc_finfo),
|
|
CurrentMemoryContext);
|
|
}
|
|
|
|
/* Opclass must always provide extract procs */
|
|
fmgr_info_copy(&(state->extractValueFn[i]),
|
|
index_getprocinfo(index, i + 1, GIN_EXTRACTVALUE_PROC),
|
|
CurrentMemoryContext);
|
|
fmgr_info_copy(&(state->extractQueryFn[i]),
|
|
index_getprocinfo(index, i + 1, GIN_EXTRACTQUERY_PROC),
|
|
CurrentMemoryContext);
|
|
|
|
/*
|
|
* Check opclass capability to do tri-state or binary logic consistent
|
|
* check.
|
|
*/
|
|
if (index_getprocid(index, i + 1, GIN_TRICONSISTENT_PROC) != InvalidOid)
|
|
{
|
|
fmgr_info_copy(&(state->triConsistentFn[i]),
|
|
index_getprocinfo(index, i + 1, GIN_TRICONSISTENT_PROC),
|
|
CurrentMemoryContext);
|
|
}
|
|
|
|
if (index_getprocid(index, i + 1, GIN_CONSISTENT_PROC) != InvalidOid)
|
|
{
|
|
fmgr_info_copy(&(state->consistentFn[i]),
|
|
index_getprocinfo(index, i + 1, GIN_CONSISTENT_PROC),
|
|
CurrentMemoryContext);
|
|
}
|
|
|
|
if (state->consistentFn[i].fn_oid == InvalidOid &&
|
|
state->triConsistentFn[i].fn_oid == InvalidOid)
|
|
{
|
|
elog(ERROR, "missing GIN support function (%d or %d) for attribute %d of index \"%s\"",
|
|
GIN_CONSISTENT_PROC, GIN_TRICONSISTENT_PROC,
|
|
i + 1, RelationGetRelationName(index));
|
|
}
|
|
|
|
/*
|
|
* Check opclass capability to do partial match.
|
|
*/
|
|
if (index_getprocid(index, i + 1, GIN_COMPARE_PARTIAL_PROC) != InvalidOid)
|
|
{
|
|
fmgr_info_copy(&(state->comparePartialFn[i]),
|
|
index_getprocinfo(index, i + 1, GIN_COMPARE_PARTIAL_PROC),
|
|
CurrentMemoryContext);
|
|
state->canPartialMatch[i] = true;
|
|
}
|
|
else
|
|
{
|
|
state->canPartialMatch[i] = false;
|
|
}
|
|
|
|
/*
|
|
* If the index column has a specified collation, we should honor that
|
|
* while doing comparisons. However, we may have a collatable storage
|
|
* type for a noncollatable indexed data type (for instance, hstore
|
|
* uses text index entries). If there's no index collation then
|
|
* specify default collation in case the support functions need
|
|
* collation. This is harmless if the support functions don't care
|
|
* about collation, so we just do it unconditionally. (We could
|
|
* alternatively call get_typcollation, but that seems like expensive
|
|
* overkill --- there aren't going to be any cases where a GIN storage
|
|
* type has a nondefault collation.)
|
|
*/
|
|
if (OidIsValid(index->rd_indcollation[i]))
|
|
state->supportCollation[i] = index->rd_indcollation[i];
|
|
else
|
|
state->supportCollation[i] = DEFAULT_COLLATION_OID;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Extract attribute (column) number of stored entry from GIN tuple
|
|
*/
|
|
OffsetNumber
|
|
gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple)
|
|
{
|
|
OffsetNumber colN;
|
|
|
|
if (ginstate->oneCol)
|
|
{
|
|
/* column number is not stored explicitly */
|
|
colN = FirstOffsetNumber;
|
|
}
|
|
else
|
|
{
|
|
Datum res;
|
|
bool isnull;
|
|
|
|
/*
|
|
* First attribute is always int16, so we can safely use any tuple
|
|
* descriptor to obtain first attribute of tuple
|
|
*/
|
|
res = index_getattr(tuple, FirstOffsetNumber, ginstate->tupdesc[0],
|
|
&isnull);
|
|
Assert(!isnull);
|
|
|
|
colN = DatumGetUInt16(res);
|
|
Assert(colN >= FirstOffsetNumber && colN <= ginstate->origTupdesc->natts);
|
|
}
|
|
|
|
return colN;
|
|
}
|
|
|
|
/*
|
|
* Extract stored datum (and possible null category) from GIN tuple
|
|
*/
|
|
Datum
|
|
gintuple_get_key(GinState *ginstate, IndexTuple tuple,
|
|
GinNullCategory *category)
|
|
{
|
|
Datum res;
|
|
bool isnull;
|
|
|
|
if (ginstate->oneCol)
|
|
{
|
|
/*
|
|
* Single column index doesn't store attribute numbers in tuples
|
|
*/
|
|
res = index_getattr(tuple, FirstOffsetNumber, ginstate->origTupdesc,
|
|
&isnull);
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* Since the datum type depends on which index column it's from, we
|
|
* must be careful to use the right tuple descriptor here.
|
|
*/
|
|
OffsetNumber colN = gintuple_get_attrnum(ginstate, tuple);
|
|
|
|
res = index_getattr(tuple, OffsetNumberNext(FirstOffsetNumber),
|
|
ginstate->tupdesc[colN - 1],
|
|
&isnull);
|
|
}
|
|
|
|
if (isnull)
|
|
*category = GinGetNullCategory(tuple, ginstate);
|
|
else
|
|
*category = GIN_CAT_NORM_KEY;
|
|
|
|
return res;
|
|
}
|
|
|
|
/*
|
|
* Allocate a new page (either by recycling, or by extending the index file)
|
|
* The returned buffer is already pinned and exclusive-locked
|
|
* Caller is responsible for initializing the page by calling GinInitBuffer
|
|
*/
|
|
Buffer
|
|
GinNewBuffer(Relation index)
|
|
{
|
|
Buffer buffer;
|
|
bool needLock;
|
|
|
|
/* First, try to get a page from FSM */
|
|
for (;;)
|
|
{
|
|
BlockNumber blkno = GetFreeIndexPage(index);
|
|
|
|
if (blkno == InvalidBlockNumber)
|
|
break;
|
|
|
|
buffer = ReadBuffer(index, blkno);
|
|
|
|
/*
|
|
* We have to guard against the possibility that someone else already
|
|
* recycled this page; the buffer may be locked if so.
|
|
*/
|
|
if (ConditionalLockBuffer(buffer))
|
|
{
|
|
if (GinPageIsRecyclable(BufferGetPage(buffer)))
|
|
return buffer; /* OK to use */
|
|
|
|
LockBuffer(buffer, GIN_UNLOCK);
|
|
}
|
|
|
|
/* Can't use it, so release buffer and try again */
|
|
ReleaseBuffer(buffer);
|
|
}
|
|
|
|
/* Must extend the file */
|
|
needLock = !RELATION_IS_LOCAL(index);
|
|
if (needLock)
|
|
LockRelationForExtension(index, ExclusiveLock);
|
|
|
|
buffer = ReadBuffer(index, P_NEW);
|
|
LockBuffer(buffer, GIN_EXCLUSIVE);
|
|
|
|
if (needLock)
|
|
UnlockRelationForExtension(index, ExclusiveLock);
|
|
|
|
return buffer;
|
|
}
|
|
|
|
void
|
|
GinInitPage(Page page, uint32 f, Size pageSize)
|
|
{
|
|
GinPageOpaque opaque;
|
|
|
|
PageInit(page, pageSize, sizeof(GinPageOpaqueData));
|
|
|
|
opaque = GinPageGetOpaque(page);
|
|
opaque->flags = f;
|
|
opaque->rightlink = InvalidBlockNumber;
|
|
}
|
|
|
|
void
|
|
GinInitBuffer(Buffer b, uint32 f)
|
|
{
|
|
GinInitPage(BufferGetPage(b), f, BufferGetPageSize(b));
|
|
}
|
|
|
|
void
|
|
GinInitMetabuffer(Buffer b)
|
|
{
|
|
GinMetaPageData *metadata;
|
|
Page page = BufferGetPage(b);
|
|
|
|
GinInitPage(page, GIN_META, BufferGetPageSize(b));
|
|
|
|
metadata = GinPageGetMeta(page);
|
|
|
|
metadata->head = metadata->tail = InvalidBlockNumber;
|
|
metadata->tailFreeSize = 0;
|
|
metadata->nPendingPages = 0;
|
|
metadata->nPendingHeapTuples = 0;
|
|
metadata->nTotalPages = 0;
|
|
metadata->nEntryPages = 0;
|
|
metadata->nDataPages = 0;
|
|
metadata->nEntries = 0;
|
|
metadata->ginVersion = GIN_CURRENT_VERSION;
|
|
|
|
/*
|
|
* Set pd_lower just past the end of the metadata. This is essential,
|
|
* because without doing so, metadata will be lost if xlog.c compresses
|
|
* the page.
|
|
*/
|
|
((PageHeader) page)->pd_lower =
|
|
((char *) metadata + sizeof(GinMetaPageData)) - (char *) page;
|
|
}
|
|
|
|
/*
|
|
* Compare two keys of the same index column
|
|
*/
|
|
int
|
|
ginCompareEntries(GinState *ginstate, OffsetNumber attnum,
|
|
Datum a, GinNullCategory categorya,
|
|
Datum b, GinNullCategory categoryb)
|
|
{
|
|
/* if not of same null category, sort by that first */
|
|
if (categorya != categoryb)
|
|
return (categorya < categoryb) ? -1 : 1;
|
|
|
|
/* all null items in same category are equal */
|
|
if (categorya != GIN_CAT_NORM_KEY)
|
|
return 0;
|
|
|
|
/* both not null, so safe to call the compareFn */
|
|
return DatumGetInt32(FunctionCall2Coll(&ginstate->compareFn[attnum - 1],
|
|
ginstate->supportCollation[attnum - 1],
|
|
a, b));
|
|
}
|
|
|
|
/*
|
|
* Compare two keys of possibly different index columns
|
|
*/
|
|
int
|
|
ginCompareAttEntries(GinState *ginstate,
|
|
OffsetNumber attnuma, Datum a, GinNullCategory categorya,
|
|
OffsetNumber attnumb, Datum b, GinNullCategory categoryb)
|
|
{
|
|
/* attribute number is the first sort key */
|
|
if (attnuma != attnumb)
|
|
return (attnuma < attnumb) ? -1 : 1;
|
|
|
|
return ginCompareEntries(ginstate, attnuma, a, categorya, b, categoryb);
|
|
}
|
|
|
|
|
|
/*
|
|
* Support for sorting key datums in ginExtractEntries
|
|
*
|
|
* Note: we only have to worry about null and not-null keys here;
|
|
* ginExtractEntries never generates more than one placeholder null,
|
|
* so it doesn't have to sort those.
|
|
*/
|
|
typedef struct
|
|
{
|
|
Datum datum;
|
|
bool isnull;
|
|
} keyEntryData;
|
|
|
|
typedef struct
|
|
{
|
|
FmgrInfo *cmpDatumFunc;
|
|
Oid collation;
|
|
bool haveDups;
|
|
} cmpEntriesArg;
|
|
|
|
static int
|
|
cmpEntries(const void *a, const void *b, void *arg)
|
|
{
|
|
const keyEntryData *aa = (const keyEntryData *) a;
|
|
const keyEntryData *bb = (const keyEntryData *) b;
|
|
cmpEntriesArg *data = (cmpEntriesArg *) arg;
|
|
int res;
|
|
|
|
if (aa->isnull)
|
|
{
|
|
if (bb->isnull)
|
|
res = 0; /* NULL "=" NULL */
|
|
else
|
|
res = 1; /* NULL ">" not-NULL */
|
|
}
|
|
else if (bb->isnull)
|
|
res = -1; /* not-NULL "<" NULL */
|
|
else
|
|
res = DatumGetInt32(FunctionCall2Coll(data->cmpDatumFunc,
|
|
data->collation,
|
|
aa->datum, bb->datum));
|
|
|
|
/*
|
|
* Detect if we have any duplicates. If there are equal keys, qsort must
|
|
* compare them at some point, else it wouldn't know whether one should go
|
|
* before or after the other.
|
|
*/
|
|
if (res == 0)
|
|
data->haveDups = true;
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/*
|
|
* Extract the index key values from an indexable item
|
|
*
|
|
* The resulting key values are sorted, and any duplicates are removed.
|
|
* This avoids generating redundant index entries.
|
|
*/
|
|
Datum *
|
|
ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
|
|
Datum value, bool isNull,
|
|
int32 *nentries, GinNullCategory **categories)
|
|
{
|
|
Datum *entries;
|
|
bool *nullFlags;
|
|
int32 i;
|
|
|
|
/*
|
|
* We don't call the extractValueFn on a null item. Instead generate a
|
|
* placeholder.
|
|
*/
|
|
if (isNull)
|
|
{
|
|
*nentries = 1;
|
|
entries = (Datum *) palloc(sizeof(Datum));
|
|
entries[0] = (Datum) 0;
|
|
*categories = (GinNullCategory *) palloc(sizeof(GinNullCategory));
|
|
(*categories)[0] = GIN_CAT_NULL_ITEM;
|
|
return entries;
|
|
}
|
|
|
|
/* OK, call the opclass's extractValueFn */
|
|
nullFlags = NULL; /* in case extractValue doesn't set it */
|
|
entries = (Datum *)
|
|
DatumGetPointer(FunctionCall3Coll(&ginstate->extractValueFn[attnum - 1],
|
|
ginstate->supportCollation[attnum - 1],
|
|
value,
|
|
PointerGetDatum(nentries),
|
|
PointerGetDatum(&nullFlags)));
|
|
|
|
/*
|
|
* Generate a placeholder if the item contained no keys.
|
|
*/
|
|
if (entries == NULL || *nentries <= 0)
|
|
{
|
|
*nentries = 1;
|
|
entries = (Datum *) palloc(sizeof(Datum));
|
|
entries[0] = (Datum) 0;
|
|
*categories = (GinNullCategory *) palloc(sizeof(GinNullCategory));
|
|
(*categories)[0] = GIN_CAT_EMPTY_ITEM;
|
|
return entries;
|
|
}
|
|
|
|
/*
|
|
* If the extractValueFn didn't create a nullFlags array, create one,
|
|
* assuming that everything's non-null.
|
|
*/
|
|
if (nullFlags == NULL)
|
|
nullFlags = (bool *) palloc0(*nentries * sizeof(bool));
|
|
|
|
/*
|
|
* If there's more than one key, sort and unique-ify.
|
|
*
|
|
* XXX Using qsort here is notationally painful, and the overhead is
|
|
* pretty bad too. For small numbers of keys it'd likely be better to use
|
|
* a simple insertion sort.
|
|
*/
|
|
if (*nentries > 1)
|
|
{
|
|
keyEntryData *keydata;
|
|
cmpEntriesArg arg;
|
|
|
|
keydata = (keyEntryData *) palloc(*nentries * sizeof(keyEntryData));
|
|
for (i = 0; i < *nentries; i++)
|
|
{
|
|
keydata[i].datum = entries[i];
|
|
keydata[i].isnull = nullFlags[i];
|
|
}
|
|
|
|
arg.cmpDatumFunc = &ginstate->compareFn[attnum - 1];
|
|
arg.collation = ginstate->supportCollation[attnum - 1];
|
|
arg.haveDups = false;
|
|
qsort_arg(keydata, *nentries, sizeof(keyEntryData),
|
|
cmpEntries, (void *) &arg);
|
|
|
|
if (arg.haveDups)
|
|
{
|
|
/* there are duplicates, must get rid of 'em */
|
|
int32 j;
|
|
|
|
entries[0] = keydata[0].datum;
|
|
nullFlags[0] = keydata[0].isnull;
|
|
j = 1;
|
|
for (i = 1; i < *nentries; i++)
|
|
{
|
|
if (cmpEntries(&keydata[i - 1], &keydata[i], &arg) != 0)
|
|
{
|
|
entries[j] = keydata[i].datum;
|
|
nullFlags[j] = keydata[i].isnull;
|
|
j++;
|
|
}
|
|
}
|
|
*nentries = j;
|
|
}
|
|
else
|
|
{
|
|
/* easy, no duplicates */
|
|
for (i = 0; i < *nentries; i++)
|
|
{
|
|
entries[i] = keydata[i].datum;
|
|
nullFlags[i] = keydata[i].isnull;
|
|
}
|
|
}
|
|
|
|
pfree(keydata);
|
|
}
|
|
|
|
/*
|
|
* Create GinNullCategory representation from nullFlags.
|
|
*/
|
|
*categories = (GinNullCategory *) palloc0(*nentries * sizeof(GinNullCategory));
|
|
for (i = 0; i < *nentries; i++)
|
|
(*categories)[i] = (nullFlags[i] ? GIN_CAT_NULL_KEY : GIN_CAT_NORM_KEY);
|
|
|
|
return entries;
|
|
}
|
|
|
|
bytea *
|
|
ginoptions(Datum reloptions, bool validate)
|
|
{
|
|
static const relopt_parse_elt tab[] = {
|
|
{"fastupdate", RELOPT_TYPE_BOOL, offsetof(GinOptions, useFastUpdate)},
|
|
{"gin_pending_list_limit", RELOPT_TYPE_INT, offsetof(GinOptions,
|
|
pendingListCleanupSize)}
|
|
};
|
|
|
|
return (bytea *) build_reloptions(reloptions, validate,
|
|
RELOPT_KIND_GIN,
|
|
sizeof(GinOptions),
|
|
tab, lengthof(tab));
|
|
}
|
|
|
|
/*
|
|
* Fetch index's statistical data into *stats
|
|
*
|
|
* Note: in the result, nPendingPages can be trusted to be up-to-date,
|
|
* as can ginVersion; but the other fields are as of the last VACUUM.
|
|
*/
|
|
void
|
|
ginGetStats(Relation index, GinStatsData *stats)
|
|
{
|
|
Buffer metabuffer;
|
|
Page metapage;
|
|
GinMetaPageData *metadata;
|
|
|
|
metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
|
|
LockBuffer(metabuffer, GIN_SHARE);
|
|
metapage = BufferGetPage(metabuffer);
|
|
metadata = GinPageGetMeta(metapage);
|
|
|
|
stats->nPendingPages = metadata->nPendingPages;
|
|
stats->nTotalPages = metadata->nTotalPages;
|
|
stats->nEntryPages = metadata->nEntryPages;
|
|
stats->nDataPages = metadata->nDataPages;
|
|
stats->nEntries = metadata->nEntries;
|
|
stats->ginVersion = metadata->ginVersion;
|
|
|
|
UnlockReleaseBuffer(metabuffer);
|
|
}
|
|
|
|
/*
|
|
* Write the given statistics to the index's metapage
|
|
*
|
|
* Note: nPendingPages and ginVersion are *not* copied over
|
|
*/
|
|
void
|
|
ginUpdateStats(Relation index, const GinStatsData *stats, bool is_build)
|
|
{
|
|
Buffer metabuffer;
|
|
Page metapage;
|
|
GinMetaPageData *metadata;
|
|
|
|
metabuffer = ReadBuffer(index, GIN_METAPAGE_BLKNO);
|
|
LockBuffer(metabuffer, GIN_EXCLUSIVE);
|
|
metapage = BufferGetPage(metabuffer);
|
|
metadata = GinPageGetMeta(metapage);
|
|
|
|
START_CRIT_SECTION();
|
|
|
|
metadata->nTotalPages = stats->nTotalPages;
|
|
metadata->nEntryPages = stats->nEntryPages;
|
|
metadata->nDataPages = stats->nDataPages;
|
|
metadata->nEntries = stats->nEntries;
|
|
|
|
/*
|
|
* Set pd_lower just past the end of the metadata. This is essential,
|
|
* because without doing so, metadata will be lost if xlog.c compresses
|
|
* the page. (We must do this here because pre-v11 versions of PG did not
|
|
* set the metapage's pd_lower correctly, so a pg_upgraded index might
|
|
* contain the wrong value.)
|
|
*/
|
|
((PageHeader) metapage)->pd_lower =
|
|
((char *) metadata + sizeof(GinMetaPageData)) - (char *) metapage;
|
|
|
|
MarkBufferDirty(metabuffer);
|
|
|
|
if (RelationNeedsWAL(index) && !is_build)
|
|
{
|
|
XLogRecPtr recptr;
|
|
ginxlogUpdateMeta data;
|
|
|
|
data.locator = index->rd_locator;
|
|
data.ntuples = 0;
|
|
data.newRightlink = data.prevTail = InvalidBlockNumber;
|
|
memcpy(&data.metadata, metadata, sizeof(GinMetaPageData));
|
|
|
|
XLogBeginInsert();
|
|
XLogRegisterData((char *) &data, sizeof(ginxlogUpdateMeta));
|
|
XLogRegisterBuffer(0, metabuffer, REGBUF_WILL_INIT | REGBUF_STANDARD);
|
|
|
|
recptr = XLogInsert(RM_GIN_ID, XLOG_GIN_UPDATE_META_PAGE);
|
|
PageSetLSN(metapage, recptr);
|
|
}
|
|
|
|
UnlockReleaseBuffer(metabuffer);
|
|
|
|
END_CRIT_SECTION();
|
|
}
|