| /* |
| * gistfuncs.c |
| * Functions to investigate the content of GiST indexes |
| * |
| * Copyright (c) 2014-2023, PostgreSQL Global Development Group |
| * |
| * IDENTIFICATION |
| * contrib/pageinspect/gistfuncs.c |
| */ |
| #include "postgres.h" |
| |
| #include "access/gist.h" |
| #include "access/gist_private.h" |
| #include "access/htup.h" |
| #include "access/relation.h" |
| #include "catalog/namespace.h" |
| #include "catalog/pg_am_d.h" |
| #include "funcapi.h" |
| #include "miscadmin.h" |
| #include "pageinspect.h" |
| #include "storage/itemptr.h" |
| #include "utils/array.h" |
| #include "utils/builtins.h" |
| #include "utils/pg_lsn.h" |
| #include "utils/lsyscache.h" |
| #include "utils/rel.h" |
| #include "utils/ruleutils.h" |
| #include "utils/varlena.h" |
| |
| PG_FUNCTION_INFO_V1(gist_page_opaque_info); |
| PG_FUNCTION_INFO_V1(gist_page_items); |
| PG_FUNCTION_INFO_V1(gist_page_items_bytea); |
| |
| #define IS_GIST(r) ((r)->rd_rel->relam == GIST_AM_OID) |
| |
| |
| static Page verify_gist_page(bytea *raw_page); |
| |
| /* |
| * Verify that the given bytea contains a GIST page or die in the attempt. |
| * A pointer to the page is returned. |
| */ |
| static Page |
| verify_gist_page(bytea *raw_page) |
| { |
| Page page = get_page_from_raw(raw_page); |
| GISTPageOpaque opaq; |
| |
| if (PageIsNew(page)) |
| return page; |
| |
| /* verify the special space has the expected size */ |
| if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("input page is not a valid %s page", "GiST"), |
| errdetail("Expected special size %d, got %d.", |
| (int) MAXALIGN(sizeof(GISTPageOpaqueData)), |
| (int) PageGetSpecialSize(page)))); |
| |
| opaq = GistPageGetOpaque(page); |
| if (opaq->gist_page_id != GIST_PAGE_ID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("input page is not a valid %s page", "GiST"), |
| errdetail("Expected %08x, got %08x.", |
| GIST_PAGE_ID, |
| opaq->gist_page_id))); |
| |
| return page; |
| } |
| |
| Datum |
| gist_page_opaque_info(PG_FUNCTION_ARGS) |
| { |
| bytea *raw_page = PG_GETARG_BYTEA_P(0); |
| TupleDesc tupdesc; |
| Page page; |
| GISTPageOpaque opaq; |
| HeapTuple resultTuple; |
| Datum values[4]; |
| bool nulls[4]; |
| Datum flags[16]; |
| int nflags = 0; |
| uint16 flagbits; |
| |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("must be superuser to use raw page functions"))); |
| |
| page = verify_gist_page(raw_page); |
| |
| if (PageIsNew(page)) |
| PG_RETURN_NULL(); |
| |
| /* verify the special space has the expected size */ |
| if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("input page is not a valid %s page", "GiST"), |
| errdetail("Expected special size %d, got %d.", |
| (int) MAXALIGN(sizeof(GISTPageOpaqueData)), |
| (int) PageGetSpecialSize(page)))); |
| |
| opaq = (GISTPageOpaque) PageGetSpecialPointer(page); |
| if (opaq->gist_page_id != GIST_PAGE_ID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("input page is not a valid %s page", "GiST"), |
| errdetail("Expected %08x, got %08x.", |
| GIST_PAGE_ID, |
| opaq->gist_page_id))); |
| |
| /* Build a tuple descriptor for our result type */ |
| if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE) |
| elog(ERROR, "return type must be a row type"); |
| |
| /* Convert the flags bitmask to an array of human-readable names */ |
| flagbits = GistPageGetOpaque(page)->flags; |
| if (flagbits & F_LEAF) |
| flags[nflags++] = CStringGetTextDatum("leaf"); |
| if (flagbits & F_DELETED) |
| flags[nflags++] = CStringGetTextDatum("deleted"); |
| if (flagbits & F_TUPLES_DELETED) |
| flags[nflags++] = CStringGetTextDatum("tuples_deleted"); |
| if (flagbits & F_FOLLOW_RIGHT) |
| flags[nflags++] = CStringGetTextDatum("follow_right"); |
| if (flagbits & F_HAS_GARBAGE) |
| flags[nflags++] = CStringGetTextDatum("has_garbage"); |
| flagbits &= ~(F_LEAF | F_DELETED | F_TUPLES_DELETED | F_FOLLOW_RIGHT | F_HAS_GARBAGE); |
| if (flagbits) |
| { |
| /* any flags we don't recognize are printed in hex */ |
| flags[nflags++] = DirectFunctionCall1(to_hex32, Int32GetDatum(flagbits)); |
| } |
| |
| memset(nulls, 0, sizeof(nulls)); |
| |
| values[0] = LSNGetDatum(PageGetLSN(page)); |
| values[1] = LSNGetDatum(GistPageGetNSN(page)); |
| values[2] = Int64GetDatum(GistPageGetOpaque(page)->rightlink); |
| values[3] = PointerGetDatum(construct_array_builtin(flags, nflags, TEXTOID)); |
| |
| /* Build and return the result tuple. */ |
| resultTuple = heap_form_tuple(tupdesc, values, nulls); |
| |
| return HeapTupleGetDatum(resultTuple); |
| } |
| |
| Datum |
| gist_page_items_bytea(PG_FUNCTION_ARGS) |
| { |
| bytea *raw_page = PG_GETARG_BYTEA_P(0); |
| ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
| Page page; |
| GISTPageOpaque opaq; |
| OffsetNumber offset; |
| OffsetNumber maxoff = InvalidOffsetNumber; |
| |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("must be superuser to use raw page functions"))); |
| |
| InitMaterializedSRF(fcinfo, 0); |
| |
| page = verify_gist_page(raw_page); |
| |
| if (PageIsNew(page)) |
| PG_RETURN_NULL(); |
| |
| if (PageIsNew(page)) |
| PG_RETURN_NULL(); |
| |
| /* verify the special space has the expected size */ |
| if (PageGetSpecialSize(page) != MAXALIGN(sizeof(GISTPageOpaqueData))) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("input page is not a valid %s page", "GiST"), |
| errdetail("Expected special size %d, got %d.", |
| (int) MAXALIGN(sizeof(GISTPageOpaqueData)), |
| (int) PageGetSpecialSize(page)))); |
| |
| opaq = (GISTPageOpaque) PageGetSpecialPointer(page); |
| if (opaq->gist_page_id != GIST_PAGE_ID) |
| ereport(ERROR, |
| (errcode(ERRCODE_INVALID_PARAMETER_VALUE), |
| errmsg("input page is not a valid %s page", "GiST"), |
| errdetail("Expected %08x, got %08x.", |
| GIST_PAGE_ID, |
| opaq->gist_page_id))); |
| |
| /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ |
| if (GistPageIsDeleted(page)) |
| elog(NOTICE, "page is deleted"); |
| else |
| maxoff = PageGetMaxOffsetNumber(page); |
| |
| for (offset = FirstOffsetNumber; |
| offset <= maxoff; |
| offset++) |
| { |
| Datum values[5]; |
| bool nulls[5]; |
| ItemId id; |
| IndexTuple itup; |
| bytea *tuple_bytea; |
| int tuple_len; |
| |
| id = PageGetItemId(page, offset); |
| |
| if (!ItemIdIsValid(id)) |
| elog(ERROR, "invalid ItemId"); |
| |
| itup = (IndexTuple) PageGetItem(page, id); |
| tuple_len = IndexTupleSize(itup); |
| |
| memset(nulls, 0, sizeof(nulls)); |
| |
| values[0] = DatumGetInt16(offset); |
| values[1] = ItemPointerGetDatum(&itup->t_tid); |
| values[2] = Int32GetDatum((int) IndexTupleSize(itup)); |
| |
| tuple_bytea = (bytea *) palloc(tuple_len + VARHDRSZ); |
| SET_VARSIZE(tuple_bytea, tuple_len + VARHDRSZ); |
| memcpy(VARDATA(tuple_bytea), itup, tuple_len); |
| values[3] = BoolGetDatum(ItemIdIsDead(id)); |
| values[4] = PointerGetDatum(tuple_bytea); |
| |
| tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); |
| } |
| |
| return (Datum) 0; |
| } |
| |
| Datum |
| gist_page_items(PG_FUNCTION_ARGS) |
| { |
| bytea *raw_page = PG_GETARG_BYTEA_P(0); |
| Oid indexRelid = PG_GETARG_OID(1); |
| ReturnSetInfo *rsinfo = (ReturnSetInfo *) fcinfo->resultinfo; |
| Relation indexRel; |
| TupleDesc tupdesc; |
| Page page; |
| uint16 flagbits; |
| bits16 printflags = 0; |
| OffsetNumber offset; |
| OffsetNumber maxoff = InvalidOffsetNumber; |
| char *index_columns; |
| |
| if (!superuser()) |
| ereport(ERROR, |
| (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), |
| errmsg("must be superuser to use raw page functions"))); |
| |
| InitMaterializedSRF(fcinfo, 0); |
| |
| /* Open the relation */ |
| indexRel = index_open(indexRelid, AccessShareLock); |
| |
| if (!IS_GIST(indexRel)) |
| ereport(ERROR, |
| (errcode(ERRCODE_WRONG_OBJECT_TYPE), |
| errmsg("\"%s\" is not a %s index", |
| RelationGetRelationName(indexRel), "GiST"))); |
| |
| page = get_page_from_raw(raw_page); |
| |
| if (PageIsNew(page)) |
| { |
| index_close(indexRel, AccessShareLock); |
| PG_RETURN_NULL(); |
| } |
| |
| flagbits = GistPageGetOpaque(page)->flags; |
| |
| /* |
| * Included attributes are added when dealing with leaf pages, discarded |
| * for non-leaf pages as these include only data for key attributes. |
| */ |
| printflags |= RULE_INDEXDEF_PRETTY; |
| if (flagbits & F_LEAF) |
| { |
| tupdesc = RelationGetDescr(indexRel); |
| } |
| else |
| { |
| tupdesc = CreateTupleDescCopy(RelationGetDescr(indexRel)); |
| tupdesc->natts = IndexRelationGetNumberOfKeyAttributes(indexRel); |
| printflags |= RULE_INDEXDEF_KEYS_ONLY; |
| } |
| |
| index_columns = pg_get_indexdef_columns_extended(indexRelid, |
| printflags); |
| |
| /* Avoid bogus PageGetMaxOffsetNumber() call with deleted pages */ |
| if (GistPageIsDeleted(page)) |
| elog(NOTICE, "page is deleted"); |
| else |
| maxoff = PageGetMaxOffsetNumber(page); |
| |
| for (offset = FirstOffsetNumber; |
| offset <= maxoff; |
| offset++) |
| { |
| Datum values[5]; |
| bool nulls[5]; |
| ItemId id; |
| IndexTuple itup; |
| Datum itup_values[INDEX_MAX_KEYS]; |
| bool itup_isnull[INDEX_MAX_KEYS]; |
| StringInfoData buf; |
| int i; |
| |
| id = PageGetItemId(page, offset); |
| |
| if (!ItemIdIsValid(id)) |
| elog(ERROR, "invalid ItemId"); |
| |
| itup = (IndexTuple) PageGetItem(page, id); |
| |
| index_deform_tuple(itup, RelationGetDescr(indexRel), |
| itup_values, itup_isnull); |
| |
| memset(nulls, 0, sizeof(nulls)); |
| |
| values[0] = DatumGetInt16(offset); |
| values[1] = ItemPointerGetDatum(&itup->t_tid); |
| values[2] = Int32GetDatum((int) IndexTupleSize(itup)); |
| values[3] = BoolGetDatum(ItemIdIsDead(id)); |
| |
| if (index_columns) |
| { |
| initStringInfo(&buf); |
| appendStringInfo(&buf, "(%s)=(", index_columns); |
| |
| /* Most of this is copied from record_out(). */ |
| for (i = 0; i < tupdesc->natts; i++) |
| { |
| char *value; |
| char *tmp; |
| bool nq = false; |
| |
| if (itup_isnull[i]) |
| value = "null"; |
| else |
| { |
| Oid foutoid; |
| bool typisvarlena; |
| Oid typoid; |
| |
| typoid = tupdesc->attrs[i].atttypid; |
| getTypeOutputInfo(typoid, &foutoid, &typisvarlena); |
| value = OidOutputFunctionCall(foutoid, itup_values[i]); |
| } |
| |
| if (i == IndexRelationGetNumberOfKeyAttributes(indexRel)) |
| appendStringInfoString(&buf, ") INCLUDE ("); |
| else if (i > 0) |
| appendStringInfoString(&buf, ", "); |
| |
| /* Check whether we need double quotes for this value */ |
| nq = (value[0] == '\0'); /* force quotes for empty string */ |
| for (tmp = value; *tmp; tmp++) |
| { |
| char ch = *tmp; |
| |
| if (ch == '"' || ch == '\\' || |
| ch == '(' || ch == ')' || ch == ',' || |
| isspace((unsigned char) ch)) |
| { |
| nq = true; |
| break; |
| } |
| } |
| |
| /* And emit the string */ |
| if (nq) |
| appendStringInfoCharMacro(&buf, '"'); |
| for (tmp = value; *tmp; tmp++) |
| { |
| char ch = *tmp; |
| |
| if (ch == '"' || ch == '\\') |
| appendStringInfoCharMacro(&buf, ch); |
| appendStringInfoCharMacro(&buf, ch); |
| } |
| if (nq) |
| appendStringInfoCharMacro(&buf, '"'); |
| } |
| |
| appendStringInfoChar(&buf, ')'); |
| |
| values[4] = CStringGetTextDatum(buf.data); |
| nulls[4] = false; |
| } |
| else |
| { |
| values[4] = (Datum) 0; |
| nulls[4] = true; |
| } |
| |
| tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, values, nulls); |
| } |
| |
| relation_close(indexRel, AccessShareLock); |
| |
| return (Datum) 0; |
| } |