-pgstattuple README 2002/08/22 Tatsuo Ishii
+pgstattuple README 2002/08/29 Tatsuo Ishii
1. What is pgstattuple?
3. Using pgstattuple
- pgstattuple may be called as a SRF (set returning function) and is
+ pgstattuple may be called as a table function and is
defined as follows:
- CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS SETOF pgstattuple_view
+ CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
AS 'MODULE_PATHNAME', 'pgstattuple'
LANGUAGE 'c' WITH (isstrict);
- The argument is the table name. Note that pgstattuple never
- returns more than 1 tuple.
+ The argument is the table name. Note that pgstattuple only returns
+ one row.
4. Notes
/*
- * $Header: /cvsroot/pgsql/contrib/pgstattuple/pgstattuple.c,v 1.7 2002/08/23 08:19:49 ishii Exp $
+ * $Header: /cvsroot/pgsql/contrib/pgstattuple/pgstattuple.c,v 1.8 2002/08/29 17:14:31 tgl Exp $
*
* Copyright (c) 2001,2002 Tatsuo Ishii
*
#include "postgres.h"
#include "fmgr.h"
+#include "funcapi.h"
#include "access/heapam.h"
#include "access/transam.h"
#include "catalog/namespace.h"
-#include "funcapi.h"
#include "utils/builtins.h"
* returns live/dead tuples info
*
* C FUNCTION definition
- * pgstattuple(TEXT) returns setof pgstattuple_view
- * see pgstattuple.sql for pgstattuple_view
+ * pgstattuple(text) returns pgstattuple_type
+ * see pgstattuple.sql for pgstattuple_type
* ----------
*/
-#define DUMMY_TUPLE "pgstattuple_view"
+#define DUMMY_TUPLE "pgstattuple_type"
#define NCOLUMNS 9
#define NCHARS 32
Datum
pgstattuple(PG_FUNCTION_ARGS)
{
- text *relname;
+ text *relname = PG_GETARG_TEXT_P(0);
RangeVar *relrv;
Relation rel;
HeapScanDesc scan;
double dead_tuple_percent;
uint64 free_space = 0; /* free/reusable space in bytes */
double free_percent; /* free/reusable space in % */
-
- FuncCallContext *funcctx;
- int call_cntr;
- int max_calls;
TupleDesc tupdesc;
TupleTableSlot *slot;
AttInMetadata *attinmeta;
+ char **values;
+ int i;
+ Datum result;
- char **values;
- int i;
- Datum result;
-
- /* stuff done only on the first call of the function */
- if(SRF_IS_FIRSTCALL())
- {
- /* create a function context for cross-call persistence */
- funcctx = SRF_FIRSTCALL_INIT();
-
- /* total number of tuples to be returned */
- funcctx->max_calls = 1;
-
- /*
- * Build a tuple description for a pgstattupe_view tuple
- */
- tupdesc = RelationNameGetTupleDesc(DUMMY_TUPLE);
-
- /* allocate a slot for a tuple with this tupdesc */
- slot = TupleDescGetSlot(tupdesc);
-
- /* assign slot to function context */
- funcctx->slot = slot;
-
- /*
- * Generate attribute metadata needed later to produce tuples from raw
- * C strings
- */
- attinmeta = TupleDescGetAttInMetadata(tupdesc);
- funcctx->attinmeta = attinmeta;
- }
+ /*
+ * Build a tuple description for a pgstattupe_type tuple
+ */
+ tupdesc = RelationNameGetTupleDesc(DUMMY_TUPLE);
- /* stuff done on every call of the function */
- funcctx = SRF_PERCALL_SETUP();
- call_cntr = funcctx->call_cntr;
- max_calls = funcctx->max_calls;
- slot = funcctx->slot;
- attinmeta = funcctx->attinmeta;
+ /* allocate a slot for a tuple with this tupdesc */
+ slot = TupleDescGetSlot(tupdesc);
- /* Are we done? */
- if (call_cntr >= max_calls)
- {
- SRF_RETURN_DONE(funcctx);
- }
+ /*
+ * Generate attribute metadata needed later to produce tuples from raw
+ * C strings
+ */
+ attinmeta = TupleDescGetAttInMetadata(tupdesc);
/* open relation */
- relname = PG_GETARG_TEXT_P(0);
- relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname,"pgstattuple"));
+ relrv = makeRangeVarFromNameList(textToQualifiedNameList(relname,
+ "pgstattuple"));
rel = heap_openrv(relrv, AccessShareLock);
nblocks = RelationGetNumberOfBlocks(rel);
}
pfree(values);
- SRF_RETURN_NEXT(funcctx, result);
+ PG_RETURN_DATUM(result);
}
-DROP VIEW pgstattuple_view CASCADE;
-CREATE VIEW pgstattuple_view AS
- SELECT
- 0::BIGINT AS table_len, -- physical table length in bytes
- 0::BIGINT AS tuple_count, -- number of live tuples
- 0::BIGINT AS tuple_len, -- total tuples length in bytes
- 0.0::FLOAT AS tuple_percent, -- live tuples in %
- 0::BIGINT AS dead_tuple_count, -- number of dead tuples
- 0::BIGINT AS dead_tuple_len, -- total dead tuples length in bytes
- 0.0::FLOAT AS dead_tuple_percent, -- dead tuples in %
- 0::BIGINT AS free_space, -- free space in bytes
- 0.0::FLOAT AS free_percent; -- free space in %
+DROP TYPE pgstattuple_type CASCADE;
+CREATE TYPE pgstattuple_type AS (
+ table_len BIGINT, -- physical table length in bytes
+ tuple_count BIGINT, -- number of live tuples
+ tuple_len BIGINT, -- total tuples length in bytes
+ tuple_percent FLOAT, -- live tuples in %
+ dead_tuple_count BIGINT, -- number of dead tuples
+ dead_tuple_len BIGINT, -- total dead tuples length in bytes
+ dead_tuple_percent FLOAT, -- dead tuples in %
+ free_space BIGINT, -- free space in bytes
+ free_percent FLOAT -- free space in %
+);
-CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS SETOF pgstattuple_view
+CREATE OR REPLACE FUNCTION pgstattuple(text) RETURNS pgstattuple_type
AS 'MODULE_PATHNAME', 'pgstattuple'
LANGUAGE 'c' WITH (isstrict);
float8 stddev;
float8 carry_val;
bool use_carry;
+ MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
/* total number of tuples to be returned */
funcctx->max_calls = PG_GETARG_UINT32(0);
* purpose it doesn't matter, just cast it as an unsigned value
*/
srandom(PG_GETARG_UINT32(3));
+
+ MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
AttInMetadata *attinmeta;
SPITupleTable *spi_tuptable = NULL;
TupleDesc spi_tupdesc;
- char *lastrowid;
+ char *lastrowid = NULL;
crosstab_fctx *fctx;
int i;
int num_categories;
+ MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
TupleDesc tupdesc = NULL;
int ret;
int proc;
- MemoryContext oldcontext;
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
- /* SPI switches context on us, so save it first */
- oldcontext = CurrentMemoryContext;
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* Connect to SPI manager */
if ((ret = SPI_connect()) < 0)
SRF_RETURN_DONE(funcctx);
}
- /* back to the original memory context */
- MemoryContextSwitchTo(oldcontext);
+ /* SPI switches context on us, so reset it */
+ MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* get the typeid that represents our return type */
functypeid = get_func_rettype(funcid);
/* total number of tuples to be returned */
funcctx->max_calls = proc;
+
+ MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
for (i = 0; i < num_categories; i++)
{
HeapTuple spi_tuple;
- char *rowid;
+ char *rowid = NULL;
/* see if we've gone too far already */
if (call_cntr >= max_calls)
xpfree(fctx->lastrowid);
if (values[0] != NULL)
+ {
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
lastrowid = fctx->lastrowid = pstrdup(values[0]);
+ MemoryContextSwitchTo(oldcontext);
+ }
if (!allnulls)
{
AttInMetadata *attinmeta;
/*
- * memory context used to initialize structure
+ * memory context used for structures which must live for multiple calls
*
- * fmctx is set by SRF_FIRSTCALL_INIT() for you, and used by
- * SRF_RETURN_DONE() for cleanup. It is primarily for internal use
- * by the API.
+ * multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
+ * by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
+ * context for any memory that is to be re-used across multiple calls
+ * of the SRF.
*/
- MemoryContext fmctx;
+ MemoryContext multi_call_memory_ctx;
} FuncCallContext;
to clean up and end the SRF.
+ The palloc memory context that is current when the SRF is called is
+ a transient context that will be cleared between calls. This means
+ that you do not need to be careful about pfree'ing everything
+ you palloc; it will go away anyway. However, if you want to allocate
+ any data structures to live across calls, you need to put them somewhere
+ else. The memory context referenced by
+ multi_call_memory_ctx> is a suitable location for any
+ data that needs to survive until the SRF is finished running. In most
+ cases, this means that you should switch into
+ multi_call_memory_ctx> while doing the first-call setup.
+
+
A complete pseudo-code example looks like the following:
Datum
my_Set_Returning_Function(PG_FUNCTION_ARGS)
{
- FuncCallContext *funcctx;
- Datum result;
+ FuncCallContext *funcctx;
+ Datum result;
+ MemoryContext oldcontext;
[user defined declarations]
if (SRF_IS_FIRSTCALL())
{
+ funcctx = SRF_FIRSTCALL_INIT();
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* one-time setup code appears here: */
[user defined code]
- funcctx = SRF_FIRSTCALL_INIT();
[if returning composite]
[build TupleDesc, and perhaps AttInMetadata]
[obtain slot]
funcctx->slot = slot;
[endif returning composite]
[user defined code]
+ MemoryContextSwitchTo(oldcontext);
}
/* each-time setup code appears here: */
/* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL())
{
+ MemoryContext oldcontext;
+
/* create a function context for cross-call persistence */
- funcctx = SRF_FIRSTCALL_INIT();
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* total number of tuples to be returned */
funcctx->max_calls = PG_GETARG_UINT32(0);
*/
attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
+
+ MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
/* make the tuple into a datum */
result = TupleGetDatum(slot, tuple);
- /* Clean up */
+ /* Clean up (this is not actually necessary) */
pfree(values[0]);
pfree(values[1]);
pfree(values[2]);
*
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.6 2002/08/29 00:17:04 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/executor/nodeFunctionscan.c,v 1.7 2002/08/29 17:14:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
ExprContext *econtext = scanstate->csstate.cstate.cs_ExprContext;
TupleTableSlot *slot = scanstate->csstate.css_ScanTupleSlot;
+ /*
+ * reset per-tuple memory context before each call of the function.
+ * This cleans up any local memory the function may leak when called.
+ */
+ ResetExprContext(econtext);
+
/*
* get the next Datum from the function
*/
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/adt/lockfuncs.c,v 1.3 2002/08/29 00:17:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/adt/lockfuncs.c,v 1.4 2002/08/29 17:14:33 tgl Exp $
*/
#include "postgres.h"
Datum
pg_lock_status(PG_FUNCTION_ARGS)
{
- FuncCallContext *funccxt;
- LockData *lockData;
+ FuncCallContext *funcctx;
+ LockData *lockData;
+ MemoryContext oldcontext;
if (SRF_IS_FIRSTCALL())
{
- MemoryContext oldcxt;
TupleDesc tupdesc;
- funccxt = SRF_FIRSTCALL_INIT();
+ /* create a function context for cross-call persistence */
+ funcctx = SRF_FIRSTCALL_INIT();
+
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
tupdesc = CreateTemplateTupleDesc(5, WITHOUTOID);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "relation",
OIDOID, -1, 0, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 5, "isgranted",
BOOLOID, -1, 0, false);
- funccxt->slot = TupleDescGetSlot(tupdesc);
- funccxt->attinmeta = TupleDescGetAttInMetadata(tupdesc);
-
- oldcxt = MemoryContextSwitchTo(funccxt->fmctx);
+ funcctx->slot = TupleDescGetSlot(tupdesc);
+ funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
/*
* Preload all the locking information that we will eventually format
* MemoryContext is reset when the SRF finishes, we don't need to
* free it ourselves.
*/
- funccxt->user_fctx = (LockData *) palloc(sizeof(LockData));
+ funcctx->user_fctx = (LockData *) palloc(sizeof(LockData));
- GetLockStatusData(funccxt->user_fctx);
+ GetLockStatusData(funcctx->user_fctx);
- MemoryContextSwitchTo(oldcxt);
+ MemoryContextSwitchTo(oldcontext);
}
- funccxt = SRF_PERCALL_SETUP();
- lockData = (LockData *) funccxt->user_fctx;
+ funcctx = SRF_PERCALL_SETUP();
+ lockData = (LockData *) funcctx->user_fctx;
while (lockData->currIdx < lockData->nelements)
{
holder = &(lockData->holders[currIdx]);
lock = &(lockData->locks[currIdx]);
proc = &(lockData->procs[currIdx]);
- num_attrs = funccxt->attinmeta->tupdesc->natts;
+ num_attrs = funcctx->attinmeta->tupdesc->natts;
values = (char **) palloc(sizeof(*values) * num_attrs);
strncpy(values[3], GetLockmodeName(mode), 32);
- tuple = BuildTupleFromCStrings(funccxt->attinmeta, values);
- result = TupleGetDatum(funccxt->slot, tuple);
- SRF_RETURN_NEXT(funccxt, result);
+ tuple = BuildTupleFromCStrings(funcctx->attinmeta, values);
+ result = TupleGetDatum(funcctx->slot, tuple);
+ SRF_RETURN_NEXT(funcctx, result);
}
- SRF_RETURN_DONE(funccxt);
+ SRF_RETURN_DONE(funcctx);
}
static LOCKMODE
* Copyright (c) 2002, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $Header: /cvsroot/pgsql/src/backend/utils/fmgr/funcapi.c,v 1.3 2002/08/29 00:17:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/fmgr/funcapi.c,v 1.4 2002/08/29 17:14:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
{
/*
* First call
+ *
+ * Allocate suitably long-lived space and zero it
*/
- MemoryContext oldcontext;
-
- /* switch to the appropriate memory context */
- oldcontext = MemoryContextSwitchTo(fcinfo->flinfo->fn_mcxt);
-
- /*
- * allocate space and zero it
- */
- retval = (FuncCallContext *) palloc(sizeof(FuncCallContext));
+ retval = (FuncCallContext *)
+ MemoryContextAlloc(fcinfo->flinfo->fn_mcxt,
+ sizeof(FuncCallContext));
MemSet(retval, 0, sizeof(FuncCallContext));
/*
retval->slot = NULL;
retval->user_fctx = NULL;
retval->attinmeta = NULL;
- retval->fmctx = fcinfo->flinfo->fn_mcxt;
+ retval->multi_call_memory_ctx = fcinfo->flinfo->fn_mcxt;
/*
* save the pointer for cross-call use
*/
fcinfo->flinfo->fn_extra = retval;
-
- /* back to the original memory context */
- MemoryContextSwitchTo(oldcontext);
}
else /* second and subsequent calls */
{
* command, configuration file, and command line options.
* See src/backend/utils/misc/README for more information.
*
- * $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.85 2002/08/29 00:17:05 tgl Exp $
+ * $Header: /cvsroot/pgsql/src/backend/utils/misc/guc.c,v 1.86 2002/08/29 17:14:33 tgl Exp $
*
* Copyright 2000 by PostgreSQL Global Development Group
* Written by Peter Eisentraut
.
int max_calls;
TupleTableSlot *slot;
AttInMetadata *attinmeta;
+ MemoryContext oldcontext;
/* stuff done only on the first call of the function */
if(SRF_IS_FIRSTCALL())
/* create a function context for cross-call persistence */
funcctx = SRF_FIRSTCALL_INIT();
+ /* switch to memory context appropriate for multiple function calls */
+ oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+
/* need a tuple descriptor representing two TEXT columns */
tupdesc = CreateTemplateTupleDesc(2, WITHOUTOID);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "name",
/* total number of tuples to be returned */
funcctx->max_calls = GetNumConfigOptions();
+
+ MemoryContextSwitchTo(oldcontext);
}
/* stuff done on every call of the function */
*
* Copyright (c) 2002, PostgreSQL Global Development Group
*
- * $Id: funcapi.h,v 1.5 2002/08/29 00:17:06 tgl Exp $
+ * $Id: funcapi.h,v 1.6 2002/08/29 17:14:33 tgl Exp $
*
*-------------------------------------------------------------------------
*/
AttInMetadata *attinmeta;
/*
- * memory context used to initialize structure
+ * memory context used for structures which must live for multiple calls
*
- * fmctx is set by SRF_FIRSTCALL_INIT() for you, and used by
- * SRF_RETURN_DONE() for cleanup. It is primarily for internal use
- * by the API.
+ * multi_call_memory_ctx is set by SRF_FIRSTCALL_INIT() for you, and used
+ * by SRF_RETURN_DONE() for cleanup. It is the most appropriate memory
+ * context for any memory that is to be re-used across multiple calls
+ * of the SRF.
*/
- MemoryContext fmctx;
+ MemoryContext multi_call_memory_ctx;
} FuncCallContext;
* {
* FuncCallContext *funcctx;
* Datum result;
+ * MemoryContext oldcontext;
*
*
- * if(SRF_IS_FIRSTCALL())
+ * if (SRF_IS_FIRSTCALL())
* {
- *
* funcctx = SRF_FIRSTCALL_INIT();
+ * // switch context when allocating stuff to be used in later calls
+ * oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
+ *
*
*
* funcctx->slot = slot;
*
*
+ * // return to original context when allocating transient memory
+ * MemoryContextSwitchTo(oldcontext);
* }
*
* funcctx = SRF_PERCALL_SETUP();