Commit 4d66285a authored by Tomas Vondra's avatar Tomas Vondra

Fix pg_mcv_list_items() to produce text[]

The function pg_mcv_list_items() returns values stored in MCV items. The
items may contain columns with different data types, so the function was
generating text array-like representation, but in an ad-hoc way without
properly escaping various characters etc.

Fixed by simply building a text[] array, which also makes it easier to
use from queries etc.

Requires changes to pg_proc entry, so bump catversion.

Backpatch to 12, where multi-column MCV lists were introduced.

Author: Tomas Vondra
Reviewed-by: Dean Rasheed
Discussion: https://postgr.es/m/20190618205920.qtlzcu73whfpfqne@development
parent e365a581
...@@ -1248,9 +1248,6 @@ Datum ...@@ -1248,9 +1248,6 @@ Datum
pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS) pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS)
{ {
FuncCallContext *funcctx; FuncCallContext *funcctx;
int call_cntr;
int max_calls;
AttInMetadata *attinmeta;
/* stuff done only on the first call of the function */ /* stuff done only on the first call of the function */
if (SRF_IS_FIRSTCALL()) if (SRF_IS_FIRSTCALL())
...@@ -1280,13 +1277,13 @@ pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS) ...@@ -1280,13 +1277,13 @@ pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS)
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("function returning record called in context " errmsg("function returning record called in context "
"that cannot accept type record"))); "that cannot accept type record")));
tupdesc = BlessTupleDesc(tupdesc);
/* /*
* generate attribute metadata needed later to produce tuples from raw * generate attribute metadata needed later to produce tuples from raw
* C strings * C strings
*/ */
attinmeta = TupleDescGetAttInMetadata(tupdesc); funcctx->attinmeta = TupleDescGetAttInMetadata(tupdesc);
funcctx->attinmeta = attinmeta;
MemoryContextSwitchTo(oldcontext); MemoryContextSwitchTo(oldcontext);
} }
...@@ -1294,111 +1291,78 @@ pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS) ...@@ -1294,111 +1291,78 @@ pg_stats_ext_mcvlist_items(PG_FUNCTION_ARGS)
/* stuff done on every call of the function */ /* stuff done on every call of the function */
funcctx = SRF_PERCALL_SETUP(); funcctx = SRF_PERCALL_SETUP();
call_cntr = funcctx->call_cntr; if (funcctx->call_cntr < funcctx->max_calls) /* do when there is more left to send */
max_calls = funcctx->max_calls;
attinmeta = funcctx->attinmeta;
if (call_cntr < max_calls) /* do when there is more left to send */
{ {
char **values; Datum values[5];
bool nulls[5];
HeapTuple tuple; HeapTuple tuple;
Datum result; Datum result;
ArrayBuildState *astate_values = NULL;
StringInfoData itemValues; ArrayBuildState *astate_nulls = NULL;
StringInfoData itemNulls;
int i; int i;
Oid *outfuncs;
FmgrInfo *fmgrinfo;
MCVList *mcvlist; MCVList *mcvlist;
MCVItem *item; MCVItem *item;
mcvlist = (MCVList *) funcctx->user_fctx; mcvlist = (MCVList *) funcctx->user_fctx;
Assert(call_cntr < mcvlist->nitems); Assert(funcctx->call_cntr < mcvlist->nitems);
item = &mcvlist->items[call_cntr];
/*
* Prepare a values array for building the returned tuple. This should
* be an array of C strings which will be processed later by the type
* input functions.
*/
values = (char **) palloc0(5 * sizeof(char *));
values[0] = (char *) palloc(64 * sizeof(char)); /* item index */
values[3] = (char *) palloc(64 * sizeof(char)); /* frequency */
values[4] = (char *) palloc(64 * sizeof(char)); /* base frequency */
outfuncs = (Oid *) palloc0(sizeof(Oid) * mcvlist->ndimensions); item = &mcvlist->items[funcctx->call_cntr];
fmgrinfo = (FmgrInfo *) palloc0(sizeof(FmgrInfo) * mcvlist->ndimensions);
for (i = 0; i < mcvlist->ndimensions; i++) for (i = 0; i < mcvlist->ndimensions; i++)
{ {
bool isvarlena;
getTypeOutputInfo(mcvlist->types[i], &outfuncs[i], &isvarlena);
fmgr_info(outfuncs[i], &fmgrinfo[i]);
}
/* build the arrays of values / nulls */ astate_nulls = accumArrayResult(astate_nulls,
initStringInfo(&itemValues); BoolGetDatum(item->isnull[i]),
initStringInfo(&itemNulls); false,
BOOLOID,
CurrentMemoryContext);
appendStringInfoChar(&itemValues, '{'); if (!item->isnull[i])
appendStringInfoChar(&itemNulls, '{');
for (i = 0; i < mcvlist->ndimensions; i++)
{
Datum val,
valout;
if (i > 0)
{ {
appendStringInfoString(&itemValues, ", "); bool isvarlena;
appendStringInfoString(&itemNulls, ", "); Oid outfunc;
FmgrInfo fmgrinfo;
Datum val;
text *txt;
/* lookup output func for the type */
getTypeOutputInfo(mcvlist->types[i], &outfunc, &isvarlena);
fmgr_info(outfunc, &fmgrinfo);
val = FunctionCall1(&fmgrinfo, item->values[i]);
txt = cstring_to_text(DatumGetPointer(val));
astate_values = accumArrayResult(astate_values,
PointerGetDatum(txt),
false,
TEXTOID,
CurrentMemoryContext);
} }
if (item->isnull[i])
valout = CStringGetDatum("NULL");
else else
{ astate_values = accumArrayResult(astate_values,
val = item->values[i]; (Datum) 0,
valout = FunctionCall1(&fmgrinfo[i], val); true,
} TEXTOID,
CurrentMemoryContext);
appendStringInfoString(&itemValues, DatumGetCString(valout));
appendStringInfoString(&itemNulls, item->isnull[i] ? "t" : "f");
} }
appendStringInfoChar(&itemValues, '}'); values[0] = Int32GetDatum(funcctx->call_cntr);
appendStringInfoChar(&itemNulls, '}'); values[1] = PointerGetDatum(makeArrayResult(astate_values, CurrentMemoryContext));
values[2] = PointerGetDatum(makeArrayResult(astate_nulls, CurrentMemoryContext));
snprintf(values[0], 64, "%d", call_cntr); values[3] = Float8GetDatum(item->frequency);
snprintf(values[3], 64, "%f", item->frequency); values[4] = Float8GetDatum(item->base_frequency);
snprintf(values[4], 64, "%f", item->base_frequency);
values[1] = itemValues.data; /* no NULLs in the tuple */
values[2] = itemNulls.data; memset(nulls, 0, sizeof(nulls));
/* build a tuple */ /* build a tuple */
tuple = BuildTupleFromCStrings(attinmeta, values); tuple = heap_form_tuple(funcctx->attinmeta->tupdesc, values, nulls);
/* make the tuple into a datum */ /* make the tuple into a datum */
result = HeapTupleGetDatum(tuple); result = HeapTupleGetDatum(tuple);
/* clean up (this is not really necessary) */
pfree(itemValues.data);
pfree(itemNulls.data);
pfree(values[0]);
pfree(values[3]);
pfree(values[4]);
pfree(values);
SRF_RETURN_NEXT(funcctx, result); SRF_RETURN_NEXT(funcctx, result);
} }
else /* do when there is no more left */ else /* do when there is no more left */
......
...@@ -53,6 +53,6 @@ ...@@ -53,6 +53,6 @@
*/ */
/* yyyymmddN */ /* yyyymmddN */
#define CATALOG_VERSION_NO 201907041 #define CATALOG_VERSION_NO 201907051
#endif #endif
...@@ -5019,7 +5019,7 @@ ...@@ -5019,7 +5019,7 @@
{ oid => '3427', descr => 'details about MCV list items', { oid => '3427', descr => 'details about MCV list items',
proname => 'pg_mcv_list_items', prorows => '1000', proretset => 't', proname => 'pg_mcv_list_items', prorows => '1000', proretset => 't',
provolatile => 's', prorettype => 'record', proargtypes => 'pg_mcv_list', provolatile => 's', prorettype => 'record', proargtypes => 'pg_mcv_list',
proallargtypes => '{pg_mcv_list,int4,text,_bool,float8,float8}', proallargtypes => '{pg_mcv_list,int4,_text,_bool,float8,float8}',
proargmodes => '{i,o,o,o,o,o}', proargmodes => '{i,o,o,o,o,o}',
proargnames => '{mcv_list,index,values,nulls,frequency,base_frequency}', proargnames => '{mcv_list,index,values,nulls,frequency,base_frequency}',
prosrc => 'pg_stats_ext_mcvlist_items' }, prosrc => 'pg_stats_ext_mcvlist_items' },
......
...@@ -614,9 +614,9 @@ SELECT m.* ...@@ -614,9 +614,9 @@ SELECT m.*
pg_mcv_list_items(d.stxdmcv) m pg_mcv_list_items(d.stxdmcv) m
WHERE s.stxname = 'mcv_lists_stats' WHERE s.stxname = 'mcv_lists_stats'
AND d.stxoid = s.oid; AND d.stxoid = s.oid;
index | values | nulls | frequency | base_frequency index | values | nulls | frequency | base_frequency
-------+-----------+---------+-----------+---------------- -------+---------+---------+-----------+----------------
0 | {1, 2, 3} | {f,f,f} | 1 | 1 0 | {1,2,3} | {f,f,f} | 1 | 1
(1 row) (1 row)
-- mcv with arrays -- mcv with arrays
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment