init
This commit is contained in:
500
db_include/access/gin_private.h
Executable file
500
db_include/access/gin_private.h
Executable file
@@ -0,0 +1,500 @@
|
||||
/*--------------------------------------------------------------------------
|
||||
* gin_private.h
|
||||
* header file for postgres inverted index access method implementation.
|
||||
*
|
||||
* Copyright (c) 2006-2021, PostgreSQL Global Development Group
|
||||
*
|
||||
* src/include/access/gin_private.h
|
||||
*--------------------------------------------------------------------------
|
||||
*/
|
||||
#ifndef GIN_PRIVATE_H
|
||||
#define GIN_PRIVATE_H
|
||||
|
||||
#include "access/amapi.h"
|
||||
#include "access/gin.h"
|
||||
#include "access/ginblock.h"
|
||||
#include "access/itup.h"
|
||||
#include "catalog/pg_am_d.h"
|
||||
#include "fmgr.h"
|
||||
#include "lib/rbtree.h"
|
||||
#include "storage/bufmgr.h"
|
||||
|
||||
/*
|
||||
* Storage type for GIN's reloptions
|
||||
*/
|
||||
typedef struct GinOptions
|
||||
{
|
||||
int32 vl_len_; /* varlena header (do not touch directly!) */
|
||||
bool useFastUpdate; /* use fast updates? */
|
||||
int pendingListCleanupSize; /* maximum size of pending list */
|
||||
} GinOptions;
|
||||
|
||||
#define GIN_DEFAULT_USE_FASTUPDATE true
|
||||
#define GinGetUseFastUpdate(relation) \
|
||||
(AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \
|
||||
relation->rd_rel->relam == GIN_AM_OID), \
|
||||
(relation)->rd_options ? \
|
||||
((GinOptions *) (relation)->rd_options)->useFastUpdate : GIN_DEFAULT_USE_FASTUPDATE)
|
||||
#define GinGetPendingListCleanupSize(relation) \
|
||||
(AssertMacro(relation->rd_rel->relkind == RELKIND_INDEX && \
|
||||
relation->rd_rel->relam == GIN_AM_OID), \
|
||||
(relation)->rd_options && \
|
||||
((GinOptions *) (relation)->rd_options)->pendingListCleanupSize != -1 ? \
|
||||
((GinOptions *) (relation)->rd_options)->pendingListCleanupSize : \
|
||||
gin_pending_list_limit)
|
||||
|
||||
|
||||
/* Macros for buffer lock/unlock operations */
|
||||
#define GIN_UNLOCK BUFFER_LOCK_UNLOCK
|
||||
#define GIN_SHARE BUFFER_LOCK_SHARE
|
||||
#define GIN_EXCLUSIVE BUFFER_LOCK_EXCLUSIVE
|
||||
|
||||
|
||||
/*
|
||||
* GinState: working data structure describing the index being worked on
|
||||
*/
|
||||
typedef struct GinState
|
||||
{
|
||||
Relation index;
|
||||
bool oneCol; /* true if single-column index */
|
||||
|
||||
/*
|
||||
* origTupdesc is the nominal tuple descriptor of the index, ie, the i'th
|
||||
* attribute shows the key type (not the input data type!) of the i'th
|
||||
* index column. In a single-column index this describes the actual leaf
|
||||
* index tuples. In a multi-column index, the actual leaf tuples contain
|
||||
* a smallint column number followed by a key datum of the appropriate
|
||||
* type for that column. We set up tupdesc[i] to describe the actual
|
||||
* rowtype of the index tuples for the i'th column, ie, (int2, keytype).
|
||||
* Note that in any case, leaf tuples contain more data than is known to
|
||||
* the TupleDesc; see access/gin/README for details.
|
||||
*/
|
||||
TupleDesc origTupdesc;
|
||||
TupleDesc tupdesc[INDEX_MAX_KEYS];
|
||||
|
||||
/*
|
||||
* Per-index-column opclass support functions
|
||||
*/
|
||||
FmgrInfo compareFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo extractValueFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo extractQueryFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo consistentFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo triConsistentFn[INDEX_MAX_KEYS];
|
||||
FmgrInfo comparePartialFn[INDEX_MAX_KEYS]; /* optional method */
|
||||
/* canPartialMatch[i] is true if comparePartialFn[i] is valid */
|
||||
bool canPartialMatch[INDEX_MAX_KEYS];
|
||||
/* Collations to pass to the support functions */
|
||||
Oid supportCollation[INDEX_MAX_KEYS];
|
||||
} GinState;
|
||||
|
||||
|
||||
/* ginutil.c */
|
||||
extern bytea *ginoptions(Datum reloptions, bool validate);
|
||||
extern void initGinState(GinState *state, Relation index);
|
||||
extern Buffer GinNewBuffer(Relation index);
|
||||
extern void GinInitBuffer(Buffer b, uint32 f);
|
||||
extern void GinInitPage(Page page, uint32 f, Size pageSize);
|
||||
extern void GinInitMetabuffer(Buffer b);
|
||||
extern int ginCompareEntries(GinState *ginstate, OffsetNumber attnum,
|
||||
Datum a, GinNullCategory categorya,
|
||||
Datum b, GinNullCategory categoryb);
|
||||
extern int ginCompareAttEntries(GinState *ginstate,
|
||||
OffsetNumber attnuma, Datum a, GinNullCategory categorya,
|
||||
OffsetNumber attnumb, Datum b, GinNullCategory categoryb);
|
||||
extern Datum *ginExtractEntries(GinState *ginstate, OffsetNumber attnum,
|
||||
Datum value, bool isNull,
|
||||
int32 *nentries, GinNullCategory **categories);
|
||||
|
||||
extern OffsetNumber gintuple_get_attrnum(GinState *ginstate, IndexTuple tuple);
|
||||
extern Datum gintuple_get_key(GinState *ginstate, IndexTuple tuple,
|
||||
GinNullCategory *category);
|
||||
|
||||
/* gininsert.c */
|
||||
extern IndexBuildResult *ginbuild(Relation heap, Relation index,
|
||||
struct IndexInfo *indexInfo);
|
||||
extern void ginbuildempty(Relation index);
|
||||
extern bool gininsert(Relation index, Datum *values, bool *isnull,
|
||||
ItemPointer ht_ctid, Relation heapRel,
|
||||
IndexUniqueCheck checkUnique,
|
||||
bool indexUnchanged,
|
||||
struct IndexInfo *indexInfo);
|
||||
extern void ginEntryInsert(GinState *ginstate,
|
||||
OffsetNumber attnum, Datum key, GinNullCategory category,
|
||||
ItemPointerData *items, uint32 nitem,
|
||||
GinStatsData *buildStats);
|
||||
|
||||
/* ginbtree.c */
|
||||
|
||||
typedef struct GinBtreeStack
|
||||
{
|
||||
BlockNumber blkno;
|
||||
Buffer buffer;
|
||||
OffsetNumber off;
|
||||
ItemPointerData iptr;
|
||||
/* predictNumber contains predicted number of pages on current level */
|
||||
uint32 predictNumber;
|
||||
struct GinBtreeStack *parent;
|
||||
} GinBtreeStack;
|
||||
|
||||
typedef struct GinBtreeData *GinBtree;
|
||||
|
||||
/* Return codes for GinBtreeData.beginPlaceToPage method */
|
||||
typedef enum
|
||||
{
|
||||
GPTP_NO_WORK,
|
||||
GPTP_INSERT,
|
||||
GPTP_SPLIT
|
||||
} GinPlaceToPageRC;
|
||||
|
||||
typedef struct GinBtreeData
|
||||
{
|
||||
/* search methods */
|
||||
BlockNumber (*findChildPage) (GinBtree, GinBtreeStack *);
|
||||
BlockNumber (*getLeftMostChild) (GinBtree, Page);
|
||||
bool (*isMoveRight) (GinBtree, Page);
|
||||
bool (*findItem) (GinBtree, GinBtreeStack *);
|
||||
|
||||
/* insert methods */
|
||||
OffsetNumber (*findChildPtr) (GinBtree, Page, BlockNumber, OffsetNumber);
|
||||
GinPlaceToPageRC (*beginPlaceToPage) (GinBtree, Buffer, GinBtreeStack *, void *, BlockNumber, void **, Page *, Page *);
|
||||
void (*execPlaceToPage) (GinBtree, Buffer, GinBtreeStack *, void *, BlockNumber, void *);
|
||||
void *(*prepareDownlink) (GinBtree, Buffer);
|
||||
void (*fillRoot) (GinBtree, Page, BlockNumber, Page, BlockNumber, Page);
|
||||
|
||||
bool isData;
|
||||
|
||||
Relation index;
|
||||
BlockNumber rootBlkno;
|
||||
GinState *ginstate; /* not valid in a data scan */
|
||||
bool fullScan;
|
||||
bool isBuild;
|
||||
|
||||
/* Search key for Entry tree */
|
||||
OffsetNumber entryAttnum;
|
||||
Datum entryKey;
|
||||
GinNullCategory entryCategory;
|
||||
|
||||
/* Search key for data tree (posting tree) */
|
||||
ItemPointerData itemptr;
|
||||
} GinBtreeData;
|
||||
|
||||
/* This represents a tuple to be inserted to entry tree. */
|
||||
typedef struct
|
||||
{
|
||||
IndexTuple entry; /* tuple to insert */
|
||||
bool isDelete; /* delete old tuple at same offset? */
|
||||
} GinBtreeEntryInsertData;
|
||||
|
||||
/*
|
||||
* This represents an itempointer, or many itempointers, to be inserted to
|
||||
* a data (posting tree) leaf page
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
ItemPointerData *items;
|
||||
uint32 nitem;
|
||||
uint32 curitem;
|
||||
} GinBtreeDataLeafInsertData;
|
||||
|
||||
/*
|
||||
* For internal data (posting tree) pages, the insertion payload is a
|
||||
* PostingItem
|
||||
*/
|
||||
|
||||
extern GinBtreeStack *ginFindLeafPage(GinBtree btree, bool searchMode,
|
||||
bool rootConflictCheck, Snapshot snapshot);
|
||||
extern Buffer ginStepRight(Buffer buffer, Relation index, int lockmode);
|
||||
extern void freeGinBtreeStack(GinBtreeStack *stack);
|
||||
extern void ginInsertValue(GinBtree btree, GinBtreeStack *stack,
|
||||
void *insertdata, GinStatsData *buildStats);
|
||||
|
||||
/* ginentrypage.c */
|
||||
extern IndexTuple GinFormTuple(GinState *ginstate,
|
||||
OffsetNumber attnum, Datum key, GinNullCategory category,
|
||||
Pointer data, Size dataSize, int nipd, bool errorTooBig);
|
||||
extern void ginPrepareEntryScan(GinBtree btree, OffsetNumber attnum,
|
||||
Datum key, GinNullCategory category,
|
||||
GinState *ginstate);
|
||||
extern void ginEntryFillRoot(GinBtree btree, Page root, BlockNumber lblkno, Page lpage, BlockNumber rblkno, Page rpage);
|
||||
extern ItemPointer ginReadTuple(GinState *ginstate, OffsetNumber attnum,
|
||||
IndexTuple itup, int *nitems);
|
||||
|
||||
/* gindatapage.c */
|
||||
extern ItemPointer GinDataLeafPageGetItems(Page page, int *nitems, ItemPointerData advancePast);
|
||||
extern int GinDataLeafPageGetItemsToTbm(Page page, TIDBitmap *tbm);
|
||||
extern BlockNumber createPostingTree(Relation index,
|
||||
ItemPointerData *items, uint32 nitems,
|
||||
GinStatsData *buildStats, Buffer entrybuffer);
|
||||
extern void GinDataPageAddPostingItem(Page page, PostingItem *data, OffsetNumber offset);
|
||||
extern void GinPageDeletePostingItem(Page page, OffsetNumber offset);
|
||||
extern void ginInsertItemPointers(Relation index, BlockNumber rootBlkno,
|
||||
ItemPointerData *items, uint32 nitem,
|
||||
GinStatsData *buildStats);
|
||||
extern GinBtreeStack *ginScanBeginPostingTree(GinBtree btree, Relation index, BlockNumber rootBlkno, Snapshot snapshot);
|
||||
extern void ginDataFillRoot(GinBtree btree, Page root, BlockNumber lblkno, Page lpage, BlockNumber rblkno, Page rpage);
|
||||
|
||||
/*
|
||||
* This is declared in ginvacuum.c, but is passed between ginVacuumItemPointers
|
||||
* and ginVacuumPostingTreeLeaf and as an opaque struct, so we need a forward
|
||||
* declaration for it.
|
||||
*/
|
||||
typedef struct GinVacuumState GinVacuumState;
|
||||
|
||||
extern void ginVacuumPostingTreeLeaf(Relation rel, Buffer buf, GinVacuumState *gvs);
|
||||
|
||||
/* ginscan.c */
|
||||
|
||||
/*
|
||||
* GinScanKeyData describes a single GIN index qualifier expression.
|
||||
*
|
||||
* From each qual expression, we extract one or more specific index search
|
||||
* conditions, which are represented by GinScanEntryData. It's quite
|
||||
* possible for identical search conditions to be requested by more than
|
||||
* one qual expression, in which case we merge such conditions to have just
|
||||
* one unique GinScanEntry --- this is particularly important for efficiency
|
||||
* when dealing with full-index-scan entries. So there can be multiple
|
||||
* GinScanKeyData.scanEntry pointers to the same GinScanEntryData.
|
||||
*
|
||||
* In each GinScanKeyData, nentries is the true number of entries, while
|
||||
* nuserentries is the number that extractQueryFn returned (which is what
|
||||
* we report to consistentFn). The "user" entries must come first.
|
||||
*/
|
||||
typedef struct GinScanKeyData *GinScanKey;
|
||||
|
||||
typedef struct GinScanEntryData *GinScanEntry;
|
||||
|
||||
typedef struct GinScanKeyData
|
||||
{
|
||||
/* Real number of entries in scanEntry[] (always > 0) */
|
||||
uint32 nentries;
|
||||
/* Number of entries that extractQueryFn and consistentFn know about */
|
||||
uint32 nuserentries;
|
||||
|
||||
/* array of GinScanEntry pointers, one per extracted search condition */
|
||||
GinScanEntry *scanEntry;
|
||||
|
||||
/*
|
||||
* At least one of the entries in requiredEntries must be present for a
|
||||
* tuple to match the overall qual.
|
||||
*
|
||||
* additionalEntries contains entries that are needed by the consistent
|
||||
* function to decide if an item matches, but are not sufficient to
|
||||
* satisfy the qual without entries from requiredEntries.
|
||||
*/
|
||||
GinScanEntry *requiredEntries;
|
||||
int nrequired;
|
||||
GinScanEntry *additionalEntries;
|
||||
int nadditional;
|
||||
|
||||
/* array of check flags, reported to consistentFn */
|
||||
GinTernaryValue *entryRes;
|
||||
bool (*boolConsistentFn) (GinScanKey key);
|
||||
GinTernaryValue (*triConsistentFn) (GinScanKey key);
|
||||
FmgrInfo *consistentFmgrInfo;
|
||||
FmgrInfo *triConsistentFmgrInfo;
|
||||
Oid collation;
|
||||
|
||||
/* other data needed for calling consistentFn */
|
||||
Datum query;
|
||||
/* NB: these three arrays have only nuserentries elements! */
|
||||
Datum *queryValues;
|
||||
GinNullCategory *queryCategories;
|
||||
Pointer *extra_data;
|
||||
StrategyNumber strategy;
|
||||
int32 searchMode;
|
||||
OffsetNumber attnum;
|
||||
|
||||
/*
|
||||
* An excludeOnly scan key is not able to enumerate all matching tuples.
|
||||
* That is, to be semantically correct on its own, it would need to have a
|
||||
* GIN_CAT_EMPTY_QUERY scanEntry, but it doesn't. Such a key can still be
|
||||
* used to filter tuples returned by other scan keys, so we will get the
|
||||
* right answers as long as there's at least one non-excludeOnly scan key
|
||||
* for each index attribute considered by the search. For efficiency
|
||||
* reasons we don't want to have unnecessary GIN_CAT_EMPTY_QUERY entries,
|
||||
* so we will convert an excludeOnly scan key to non-excludeOnly (by
|
||||
* adding a GIN_CAT_EMPTY_QUERY scanEntry) only if there are no other
|
||||
* non-excludeOnly scan keys.
|
||||
*/
|
||||
bool excludeOnly;
|
||||
|
||||
/*
|
||||
* Match status data. curItem is the TID most recently tested (could be a
|
||||
* lossy-page pointer). curItemMatches is true if it passes the
|
||||
* consistentFn test; if so, recheckCurItem is the recheck flag.
|
||||
* isFinished means that all the input entry streams are finished, so this
|
||||
* key cannot succeed for any later TIDs.
|
||||
*/
|
||||
ItemPointerData curItem;
|
||||
bool curItemMatches;
|
||||
bool recheckCurItem;
|
||||
bool isFinished;
|
||||
} GinScanKeyData;
|
||||
|
||||
typedef struct GinScanEntryData
|
||||
{
|
||||
/* query key and other information from extractQueryFn */
|
||||
Datum queryKey;
|
||||
GinNullCategory queryCategory;
|
||||
bool isPartialMatch;
|
||||
Pointer extra_data;
|
||||
StrategyNumber strategy;
|
||||
int32 searchMode;
|
||||
OffsetNumber attnum;
|
||||
|
||||
/* Current page in posting tree */
|
||||
Buffer buffer;
|
||||
|
||||
/* current ItemPointer to heap */
|
||||
ItemPointerData curItem;
|
||||
|
||||
/* for a partial-match or full-scan query, we accumulate all TIDs here */
|
||||
TIDBitmap *matchBitmap;
|
||||
TBMIterator *matchIterator;
|
||||
TBMIterateResult *matchResult;
|
||||
|
||||
/* used for Posting list and one page in Posting tree */
|
||||
ItemPointerData *list;
|
||||
int nlist;
|
||||
OffsetNumber offset;
|
||||
|
||||
bool isFinished;
|
||||
bool reduceResult;
|
||||
uint32 predictNumberResult;
|
||||
GinBtreeData btree;
|
||||
} GinScanEntryData;
|
||||
|
||||
typedef struct GinScanOpaqueData
|
||||
{
|
||||
MemoryContext tempCtx;
|
||||
GinState ginstate;
|
||||
|
||||
GinScanKey keys; /* one per scan qualifier expr */
|
||||
uint32 nkeys;
|
||||
|
||||
GinScanEntry *entries; /* one per index search condition */
|
||||
uint32 totalentries;
|
||||
uint32 allocentries; /* allocated length of entries[] */
|
||||
|
||||
MemoryContext keyCtx; /* used to hold key and entry data */
|
||||
|
||||
bool isVoidRes; /* true if query is unsatisfiable */
|
||||
} GinScanOpaqueData;
|
||||
|
||||
typedef GinScanOpaqueData *GinScanOpaque;
|
||||
|
||||
extern IndexScanDesc ginbeginscan(Relation rel, int nkeys, int norderbys);
|
||||
extern void ginendscan(IndexScanDesc scan);
|
||||
extern void ginrescan(IndexScanDesc scan, ScanKey key, int nscankeys,
|
||||
ScanKey orderbys, int norderbys);
|
||||
extern void ginNewScanKey(IndexScanDesc scan);
|
||||
extern void ginFreeScanKeys(GinScanOpaque so);
|
||||
|
||||
/* ginget.c */
|
||||
extern int64 gingetbitmap(IndexScanDesc scan, TIDBitmap *tbm);
|
||||
|
||||
/* ginlogic.c */
|
||||
extern void ginInitConsistentFunction(GinState *ginstate, GinScanKey key);
|
||||
|
||||
/* ginvacuum.c */
|
||||
extern IndexBulkDeleteResult *ginbulkdelete(IndexVacuumInfo *info,
|
||||
IndexBulkDeleteResult *stats,
|
||||
IndexBulkDeleteCallback callback,
|
||||
void *callback_state);
|
||||
extern IndexBulkDeleteResult *ginvacuumcleanup(IndexVacuumInfo *info,
|
||||
IndexBulkDeleteResult *stats);
|
||||
extern ItemPointer ginVacuumItemPointers(GinVacuumState *gvs,
|
||||
ItemPointerData *items, int nitem, int *nremaining);
|
||||
|
||||
/* ginvalidate.c */
|
||||
extern bool ginvalidate(Oid opclassoid);
|
||||
extern void ginadjustmembers(Oid opfamilyoid,
|
||||
Oid opclassoid,
|
||||
List *operators,
|
||||
List *functions);
|
||||
|
||||
/* ginbulk.c */
|
||||
typedef struct GinEntryAccumulator
|
||||
{
|
||||
RBTNode rbtnode;
|
||||
Datum key;
|
||||
GinNullCategory category;
|
||||
OffsetNumber attnum;
|
||||
bool shouldSort;
|
||||
ItemPointerData *list;
|
||||
uint32 maxcount; /* allocated size of list[] */
|
||||
uint32 count; /* current number of list[] entries */
|
||||
} GinEntryAccumulator;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
GinState *ginstate;
|
||||
Size allocatedMemory;
|
||||
GinEntryAccumulator *entryallocator;
|
||||
uint32 eas_used;
|
||||
RBTree *tree;
|
||||
RBTreeIterator tree_walk;
|
||||
} BuildAccumulator;
|
||||
|
||||
extern void ginInitBA(BuildAccumulator *accum);
|
||||
extern void ginInsertBAEntries(BuildAccumulator *accum,
|
||||
ItemPointer heapptr, OffsetNumber attnum,
|
||||
Datum *entries, GinNullCategory *categories,
|
||||
int32 nentries);
|
||||
extern void ginBeginBAScan(BuildAccumulator *accum);
|
||||
extern ItemPointerData *ginGetBAEntry(BuildAccumulator *accum,
|
||||
OffsetNumber *attnum, Datum *key, GinNullCategory *category,
|
||||
uint32 *n);
|
||||
|
||||
/* ginfast.c */
|
||||
|
||||
typedef struct GinTupleCollector
|
||||
{
|
||||
IndexTuple *tuples;
|
||||
uint32 ntuples;
|
||||
uint32 lentuples;
|
||||
uint32 sumsize;
|
||||
} GinTupleCollector;
|
||||
|
||||
extern void ginHeapTupleFastInsert(GinState *ginstate,
|
||||
GinTupleCollector *collector);
|
||||
extern void ginHeapTupleFastCollect(GinState *ginstate,
|
||||
GinTupleCollector *collector,
|
||||
OffsetNumber attnum, Datum value, bool isNull,
|
||||
ItemPointer ht_ctid);
|
||||
extern void ginInsertCleanup(GinState *ginstate, bool full_clean,
|
||||
bool fill_fsm, bool forceCleanup, IndexBulkDeleteResult *stats);
|
||||
|
||||
/* ginpostinglist.c */
|
||||
|
||||
extern GinPostingList *ginCompressPostingList(const ItemPointer ipd, int nipd,
|
||||
int maxsize, int *nwritten);
|
||||
extern int ginPostingListDecodeAllSegmentsToTbm(GinPostingList *ptr, int totalsize, TIDBitmap *tbm);
|
||||
|
||||
extern ItemPointer ginPostingListDecodeAllSegments(GinPostingList *ptr, int len, int *ndecoded);
|
||||
extern ItemPointer ginPostingListDecode(GinPostingList *ptr, int *ndecoded);
|
||||
extern ItemPointer ginMergeItemPointers(ItemPointerData *a, uint32 na,
|
||||
ItemPointerData *b, uint32 nb,
|
||||
int *nmerged);
|
||||
|
||||
/*
|
||||
* Merging the results of several gin scans compares item pointers a lot,
|
||||
* so we want this to be inlined.
|
||||
*/
|
||||
static inline int
|
||||
ginCompareItemPointers(ItemPointer a, ItemPointer b)
|
||||
{
|
||||
uint64 ia = (uint64) GinItemPointerGetBlockNumber(a) << 32 | GinItemPointerGetOffsetNumber(a);
|
||||
uint64 ib = (uint64) GinItemPointerGetBlockNumber(b) << 32 | GinItemPointerGetOffsetNumber(b);
|
||||
|
||||
if (ia == ib)
|
||||
return 0;
|
||||
else if (ia > ib)
|
||||
return 1;
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
|
||||
extern int ginTraverseLock(Buffer buffer, bool searchMode);
|
||||
|
||||
#endif /* GIN_PRIVATE_H */
|
||||
Reference in New Issue
Block a user