587 lines
16 KiB
C
587 lines
16 KiB
C
/*
|
|
* hashfuncs.c
|
|
* Functions to investigate the content of HASH indexes
|
|
*
|
|
* Copyright (c) 2017-2022, PostgreSQL Global Development Group
|
|
*
|
|
* IDENTIFICATION
|
|
* contrib/pageinspect/hashfuncs.c
|
|
*/
|
|
|
|
#include "postgres.h"
|
|
|
|
#include "access/hash.h"
|
|
#include "access/htup_details.h"
|
|
#include "catalog/pg_am.h"
|
|
#include "catalog/pg_type.h"
|
|
#include "funcapi.h"
|
|
#include "miscadmin.h"
|
|
#include "pageinspect.h"
|
|
#include "utils/array.h"
|
|
#include "utils/builtins.h"
|
|
#include "utils/rel.h"
|
|
|
|
PG_FUNCTION_INFO_V1(hash_page_type);
|
|
PG_FUNCTION_INFO_V1(hash_page_stats);
|
|
PG_FUNCTION_INFO_V1(hash_page_items);
|
|
PG_FUNCTION_INFO_V1(hash_bitmap_info);
|
|
PG_FUNCTION_INFO_V1(hash_metapage_info);
|
|
|
|
#define IS_HASH(r) ((r)->rd_rel->relam == HASH_AM_OID)
|
|
|
|
/* ------------------------------------------------
|
|
* structure for single hash page statistics
|
|
* ------------------------------------------------
|
|
*/
|
|
typedef struct HashPageStat
|
|
{
|
|
int live_items;
|
|
int dead_items;
|
|
int page_size;
|
|
int free_size;
|
|
|
|
/* opaque data */
|
|
BlockNumber hasho_prevblkno;
|
|
BlockNumber hasho_nextblkno;
|
|
Bucket hasho_bucket;
|
|
uint16 hasho_flag;
|
|
uint16 hasho_page_id;
|
|
} HashPageStat;
|
|
|
|
|
|
/*
|
|
* Verify that the given bytea contains a HASH page, or die in the attempt.
|
|
* A pointer to a palloc'd, properly aligned copy of the page is returned.
|
|
*/
|
|
static Page
|
|
verify_hash_page(bytea *raw_page, int flags)
|
|
{
|
|
Page page = get_page_from_raw(raw_page);
|
|
int pagetype = LH_UNUSED_PAGE;
|
|
|
|
/* Treat new pages as unused. */
|
|
if (!PageIsNew(page))
|
|
{
|
|
HashPageOpaque pageopaque;
|
|
|
|
if (PageGetSpecialSize(page) != MAXALIGN(sizeof(HashPageOpaqueData)))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("input page is not a valid %s page", "hash"),
|
|
errdetail("Expected special size %d, got %d.",
|
|
(int) MAXALIGN(sizeof(HashPageOpaqueData)),
|
|
(int) PageGetSpecialSize(page))));
|
|
|
|
pageopaque = HashPageGetOpaque(page);
|
|
if (pageopaque->hasho_page_id != HASHO_PAGE_ID)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("input page is not a valid %s page", "hash"),
|
|
errdetail("Expected %08x, got %08x.",
|
|
HASHO_PAGE_ID, pageopaque->hasho_page_id)));
|
|
|
|
pagetype = pageopaque->hasho_flag & LH_PAGE_TYPE;
|
|
}
|
|
|
|
/* Check that page type is sane. */
|
|
if (pagetype != LH_OVERFLOW_PAGE && pagetype != LH_BUCKET_PAGE &&
|
|
pagetype != LH_BITMAP_PAGE && pagetype != LH_META_PAGE &&
|
|
pagetype != LH_UNUSED_PAGE)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid hash page type %08x", pagetype)));
|
|
|
|
/* If requested, verify page type. */
|
|
if (flags != 0 && (pagetype & flags) == 0)
|
|
{
|
|
switch (flags)
|
|
{
|
|
case LH_META_PAGE:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("page is not a hash meta page")));
|
|
break;
|
|
case LH_BUCKET_PAGE | LH_OVERFLOW_PAGE:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("page is not a hash bucket or overflow page")));
|
|
break;
|
|
case LH_OVERFLOW_PAGE:
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("page is not a hash overflow page")));
|
|
break;
|
|
default:
|
|
elog(ERROR,
|
|
"hash page of type %08x not in mask %08x",
|
|
pagetype, flags);
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* If it is the metapage, also verify magic number and version.
|
|
*/
|
|
if (pagetype == LH_META_PAGE)
|
|
{
|
|
HashMetaPage metap = HashPageGetMeta(page);
|
|
|
|
if (metap->hashm_magic != HASH_MAGIC)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("invalid magic number for metadata"),
|
|
errdetail("Expected 0x%08x, got 0x%08x.",
|
|
HASH_MAGIC, metap->hashm_magic)));
|
|
|
|
if (metap->hashm_version != HASH_VERSION)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INDEX_CORRUPTED),
|
|
errmsg("invalid version for metadata"),
|
|
errdetail("Expected %d, got %d.",
|
|
HASH_VERSION, metap->hashm_version)));
|
|
}
|
|
|
|
return page;
|
|
}
|
|
|
|
/* -------------------------------------------------
|
|
* GetHashPageStatistics()
|
|
*
|
|
* Collect statistics of single hash page
|
|
* -------------------------------------------------
|
|
*/
|
|
static void
|
|
GetHashPageStatistics(Page page, HashPageStat *stat)
|
|
{
|
|
OffsetNumber maxoff = PageGetMaxOffsetNumber(page);
|
|
HashPageOpaque opaque = HashPageGetOpaque(page);
|
|
int off;
|
|
|
|
stat->dead_items = stat->live_items = 0;
|
|
stat->page_size = PageGetPageSize(page);
|
|
|
|
/* hash page opaque data */
|
|
stat->hasho_prevblkno = opaque->hasho_prevblkno;
|
|
stat->hasho_nextblkno = opaque->hasho_nextblkno;
|
|
stat->hasho_bucket = opaque->hasho_bucket;
|
|
stat->hasho_flag = opaque->hasho_flag;
|
|
stat->hasho_page_id = opaque->hasho_page_id;
|
|
|
|
/* count live and dead tuples, and free space */
|
|
for (off = FirstOffsetNumber; off <= maxoff; off++)
|
|
{
|
|
ItemId id = PageGetItemId(page, off);
|
|
|
|
if (!ItemIdIsDead(id))
|
|
stat->live_items++;
|
|
else
|
|
stat->dead_items++;
|
|
}
|
|
stat->free_size = PageGetFreeSpace(page);
|
|
}
|
|
|
|
/* ---------------------------------------------------
|
|
* hash_page_type()
|
|
*
|
|
* Usage: SELECT hash_page_type(get_raw_page('con_hash_index', 1));
|
|
* ---------------------------------------------------
|
|
*/
|
|
Datum
|
|
hash_page_type(PG_FUNCTION_ARGS)
|
|
{
|
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
|
Page page;
|
|
HashPageOpaque opaque;
|
|
int pagetype;
|
|
const char *type;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to use raw page functions")));
|
|
|
|
page = verify_hash_page(raw_page, 0);
|
|
|
|
if (PageIsNew(page))
|
|
type = "unused";
|
|
else
|
|
{
|
|
opaque = HashPageGetOpaque(page);
|
|
|
|
/* page type (flags) */
|
|
pagetype = opaque->hasho_flag & LH_PAGE_TYPE;
|
|
if (pagetype == LH_META_PAGE)
|
|
type = "metapage";
|
|
else if (pagetype == LH_OVERFLOW_PAGE)
|
|
type = "overflow";
|
|
else if (pagetype == LH_BUCKET_PAGE)
|
|
type = "bucket";
|
|
else if (pagetype == LH_BITMAP_PAGE)
|
|
type = "bitmap";
|
|
else
|
|
type = "unused";
|
|
}
|
|
|
|
PG_RETURN_TEXT_P(cstring_to_text(type));
|
|
}
|
|
|
|
/* ---------------------------------------------------
|
|
* hash_page_stats()
|
|
*
|
|
* Usage: SELECT * FROM hash_page_stats(get_raw_page('con_hash_index', 1));
|
|
* ---------------------------------------------------
|
|
*/
|
|
Datum
|
|
hash_page_stats(PG_FUNCTION_ARGS)
|
|
{
|
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
|
Page page;
|
|
int j;
|
|
Datum values[9];
|
|
bool nulls[9];
|
|
HashPageStat stat;
|
|
HeapTuple tuple;
|
|
TupleDesc tupleDesc;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to use raw page functions")));
|
|
|
|
page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
|
|
|
|
/* keep compiler quiet */
|
|
stat.hasho_prevblkno = stat.hasho_nextblkno = InvalidBlockNumber;
|
|
stat.hasho_flag = stat.hasho_page_id = stat.free_size = 0;
|
|
|
|
GetHashPageStatistics(page, &stat);
|
|
|
|
/* Build a tuple descriptor for our result type */
|
|
if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
|
|
elog(ERROR, "return type must be a row type");
|
|
tupleDesc = BlessTupleDesc(tupleDesc);
|
|
|
|
MemSet(nulls, 0, sizeof(nulls));
|
|
|
|
j = 0;
|
|
values[j++] = Int32GetDatum(stat.live_items);
|
|
values[j++] = Int32GetDatum(stat.dead_items);
|
|
values[j++] = Int32GetDatum(stat.page_size);
|
|
values[j++] = Int32GetDatum(stat.free_size);
|
|
values[j++] = Int64GetDatum((int64) stat.hasho_prevblkno);
|
|
values[j++] = Int64GetDatum((int64) stat.hasho_nextblkno);
|
|
values[j++] = Int64GetDatum((int64) stat.hasho_bucket);
|
|
values[j++] = Int32GetDatum((int32) stat.hasho_flag);
|
|
values[j++] = Int32GetDatum((int32) stat.hasho_page_id);
|
|
|
|
tuple = heap_form_tuple(tupleDesc, values, nulls);
|
|
|
|
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
|
|
}
|
|
|
|
/*
|
|
* cross-call data structure for SRF
|
|
*/
|
|
struct user_args
|
|
{
|
|
Page page;
|
|
OffsetNumber offset;
|
|
};
|
|
|
|
/*-------------------------------------------------------
|
|
* hash_page_items()
|
|
*
|
|
* Get IndexTupleData set in a hash page
|
|
*
|
|
* Usage: SELECT * FROM hash_page_items(get_raw_page('con_hash_index', 1));
|
|
*-------------------------------------------------------
|
|
*/
|
|
Datum
|
|
hash_page_items(PG_FUNCTION_ARGS)
|
|
{
|
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
|
Page page;
|
|
Datum result;
|
|
Datum values[3];
|
|
bool nulls[3];
|
|
uint32 hashkey;
|
|
HeapTuple tuple;
|
|
FuncCallContext *fctx;
|
|
MemoryContext mctx;
|
|
struct user_args *uargs;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to use raw page functions")));
|
|
|
|
if (SRF_IS_FIRSTCALL())
|
|
{
|
|
TupleDesc tupleDesc;
|
|
|
|
fctx = SRF_FIRSTCALL_INIT();
|
|
|
|
mctx = MemoryContextSwitchTo(fctx->multi_call_memory_ctx);
|
|
|
|
page = verify_hash_page(raw_page, LH_BUCKET_PAGE | LH_OVERFLOW_PAGE);
|
|
|
|
uargs = palloc(sizeof(struct user_args));
|
|
|
|
uargs->page = page;
|
|
|
|
uargs->offset = FirstOffsetNumber;
|
|
|
|
fctx->max_calls = PageGetMaxOffsetNumber(uargs->page);
|
|
|
|
/* Build a tuple descriptor for our result type */
|
|
if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
|
|
elog(ERROR, "return type must be a row type");
|
|
tupleDesc = BlessTupleDesc(tupleDesc);
|
|
|
|
fctx->attinmeta = TupleDescGetAttInMetadata(tupleDesc);
|
|
|
|
fctx->user_fctx = uargs;
|
|
|
|
MemoryContextSwitchTo(mctx);
|
|
}
|
|
|
|
fctx = SRF_PERCALL_SETUP();
|
|
uargs = fctx->user_fctx;
|
|
|
|
if (fctx->call_cntr < fctx->max_calls)
|
|
{
|
|
ItemId id;
|
|
IndexTuple itup;
|
|
int j;
|
|
|
|
id = PageGetItemId(uargs->page, uargs->offset);
|
|
|
|
if (!ItemIdIsValid(id))
|
|
elog(ERROR, "invalid ItemId");
|
|
|
|
itup = (IndexTuple) PageGetItem(uargs->page, id);
|
|
|
|
MemSet(nulls, 0, sizeof(nulls));
|
|
|
|
j = 0;
|
|
values[j++] = Int32GetDatum((int32) uargs->offset);
|
|
values[j++] = PointerGetDatum(&itup->t_tid);
|
|
|
|
hashkey = _hash_get_indextuple_hashkey(itup);
|
|
values[j] = Int64GetDatum((int64) hashkey);
|
|
|
|
tuple = heap_form_tuple(fctx->attinmeta->tupdesc, values, nulls);
|
|
result = HeapTupleGetDatum(tuple);
|
|
|
|
uargs->offset = uargs->offset + 1;
|
|
|
|
SRF_RETURN_NEXT(fctx, result);
|
|
}
|
|
|
|
SRF_RETURN_DONE(fctx);
|
|
}
|
|
|
|
/* ------------------------------------------------
|
|
* hash_bitmap_info()
|
|
*
|
|
* Get bitmap information for a particular overflow page
|
|
*
|
|
* Usage: SELECT * FROM hash_bitmap_info('con_hash_index'::regclass, 5);
|
|
* ------------------------------------------------
|
|
*/
|
|
Datum
|
|
hash_bitmap_info(PG_FUNCTION_ARGS)
|
|
{
|
|
Oid indexRelid = PG_GETARG_OID(0);
|
|
int64 ovflblkno = PG_GETARG_INT64(1);
|
|
HashMetaPage metap;
|
|
Buffer metabuf,
|
|
mapbuf;
|
|
BlockNumber bitmapblkno;
|
|
Page mappage;
|
|
bool bit = false;
|
|
TupleDesc tupleDesc;
|
|
Relation indexRel;
|
|
uint32 ovflbitno;
|
|
int32 bitmappage,
|
|
bitmapbit;
|
|
HeapTuple tuple;
|
|
int i,
|
|
j;
|
|
Datum values[3];
|
|
bool nulls[3];
|
|
uint32 *freep;
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to use raw page functions")));
|
|
|
|
indexRel = index_open(indexRelid, AccessShareLock);
|
|
|
|
if (!IS_HASH(indexRel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"%s\" is not a %s index",
|
|
RelationGetRelationName(indexRel), "hash")));
|
|
|
|
if (RELATION_IS_OTHER_TEMP(indexRel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("cannot access temporary tables of other sessions")));
|
|
|
|
if (ovflblkno < 0 || ovflblkno > MaxBlockNumber)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid block number")));
|
|
|
|
if (ovflblkno >= RelationGetNumberOfBlocks(indexRel))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("block number %lld is out of range for relation \"%s\"",
|
|
(long long int) ovflblkno, RelationGetRelationName(indexRel))));
|
|
|
|
/* Read the metapage so we can determine which bitmap page to use */
|
|
metabuf = _hash_getbuf(indexRel, HASH_METAPAGE, HASH_READ, LH_META_PAGE);
|
|
metap = HashPageGetMeta(BufferGetPage(metabuf));
|
|
|
|
/*
|
|
* Reject attempt to read the bit for a metapage or bitmap page; this is
|
|
* only meaningful for overflow pages.
|
|
*/
|
|
if (ovflblkno == 0)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid overflow block number %u",
|
|
(BlockNumber) ovflblkno)));
|
|
for (i = 0; i < metap->hashm_nmaps; i++)
|
|
if (metap->hashm_mapp[i] == ovflblkno)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid overflow block number %u",
|
|
(BlockNumber) ovflblkno)));
|
|
|
|
/*
|
|
* Identify overflow bit number. This will error out for primary bucket
|
|
* pages, and we've already rejected the metapage and bitmap pages above.
|
|
*/
|
|
ovflbitno = _hash_ovflblkno_to_bitno(metap, (BlockNumber) ovflblkno);
|
|
|
|
bitmappage = ovflbitno >> BMPG_SHIFT(metap);
|
|
bitmapbit = ovflbitno & BMPG_MASK(metap);
|
|
|
|
if (bitmappage >= metap->hashm_nmaps)
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
|
errmsg("invalid overflow block number %u",
|
|
(BlockNumber) ovflblkno)));
|
|
|
|
bitmapblkno = metap->hashm_mapp[bitmappage];
|
|
|
|
_hash_relbuf(indexRel, metabuf);
|
|
|
|
/* Check the status of bitmap bit for overflow page */
|
|
mapbuf = _hash_getbuf(indexRel, bitmapblkno, HASH_READ, LH_BITMAP_PAGE);
|
|
mappage = BufferGetPage(mapbuf);
|
|
freep = HashPageGetBitmap(mappage);
|
|
|
|
bit = ISSET(freep, bitmapbit) != 0;
|
|
|
|
_hash_relbuf(indexRel, mapbuf);
|
|
index_close(indexRel, AccessShareLock);
|
|
|
|
/* Build a tuple descriptor for our result type */
|
|
if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
|
|
elog(ERROR, "return type must be a row type");
|
|
tupleDesc = BlessTupleDesc(tupleDesc);
|
|
|
|
MemSet(nulls, 0, sizeof(nulls));
|
|
|
|
j = 0;
|
|
values[j++] = Int64GetDatum((int64) bitmapblkno);
|
|
values[j++] = Int32GetDatum(bitmapbit);
|
|
values[j++] = BoolGetDatum(bit);
|
|
|
|
tuple = heap_form_tuple(tupleDesc, values, nulls);
|
|
|
|
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
|
|
}
|
|
|
|
/* ------------------------------------------------
|
|
* hash_metapage_info()
|
|
*
|
|
* Get the meta-page information for a hash index
|
|
*
|
|
* Usage: SELECT * FROM hash_metapage_info(get_raw_page('con_hash_index', 0))
|
|
* ------------------------------------------------
|
|
*/
|
|
Datum
|
|
hash_metapage_info(PG_FUNCTION_ARGS)
|
|
{
|
|
bytea *raw_page = PG_GETARG_BYTEA_P(0);
|
|
Page page;
|
|
HashMetaPageData *metad;
|
|
TupleDesc tupleDesc;
|
|
HeapTuple tuple;
|
|
int i,
|
|
j;
|
|
Datum values[16];
|
|
bool nulls[16];
|
|
Datum spares[HASH_MAX_SPLITPOINTS];
|
|
Datum mapp[HASH_MAX_BITMAPS];
|
|
|
|
if (!superuser())
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
|
|
errmsg("must be superuser to use raw page functions")));
|
|
|
|
page = verify_hash_page(raw_page, LH_META_PAGE);
|
|
|
|
/* Build a tuple descriptor for our result type */
|
|
if (get_call_result_type(fcinfo, NULL, &tupleDesc) != TYPEFUNC_COMPOSITE)
|
|
elog(ERROR, "return type must be a row type");
|
|
tupleDesc = BlessTupleDesc(tupleDesc);
|
|
|
|
metad = HashPageGetMeta(page);
|
|
|
|
MemSet(nulls, 0, sizeof(nulls));
|
|
|
|
j = 0;
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_magic);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_version);
|
|
values[j++] = Float8GetDatum(metad->hashm_ntuples);
|
|
values[j++] = Int32GetDatum((int32) metad->hashm_ffactor);
|
|
values[j++] = Int32GetDatum((int32) metad->hashm_bsize);
|
|
values[j++] = Int32GetDatum((int32) metad->hashm_bmsize);
|
|
values[j++] = Int32GetDatum((int32) metad->hashm_bmshift);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_maxbucket);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_highmask);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_lowmask);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_ovflpoint);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_firstfree);
|
|
values[j++] = Int64GetDatum((int64) metad->hashm_nmaps);
|
|
values[j++] = ObjectIdGetDatum((Oid) metad->hashm_procid);
|
|
|
|
for (i = 0; i < HASH_MAX_SPLITPOINTS; i++)
|
|
spares[i] = Int64GetDatum((int64) metad->hashm_spares[i]);
|
|
values[j++] = PointerGetDatum(construct_array(spares,
|
|
HASH_MAX_SPLITPOINTS,
|
|
INT8OID,
|
|
sizeof(int64),
|
|
FLOAT8PASSBYVAL,
|
|
TYPALIGN_DOUBLE));
|
|
|
|
for (i = 0; i < HASH_MAX_BITMAPS; i++)
|
|
mapp[i] = Int64GetDatum((int64) metad->hashm_mapp[i]);
|
|
values[j++] = PointerGetDatum(construct_array(mapp,
|
|
HASH_MAX_BITMAPS,
|
|
INT8OID,
|
|
sizeof(int64),
|
|
FLOAT8PASSBYVAL,
|
|
TYPALIGN_DOUBLE));
|
|
|
|
tuple = heap_form_tuple(tupleDesc, values, nulls);
|
|
|
|
PG_RETURN_DATUM(HeapTupleGetDatum(tuple));
|
|
}
|