Rev. | 45db08b313c3f129dd0b037b087a161a98246c0a |
---|---|
Tamaño | 48,673 octetos |
Tiempo | 2016-04-25 14:19:47 |
Autor | MasaoFujii |
Log Message | Add .gitignore entries for files generated by build and regression test.
|
/*
* pg_dbms_stats.c
*
* Copyright (c) 2009-2014, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
* Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California
*/
#include "postgres.h"
#include "access/transam.h"
#include "catalog/pg_statistic.h"
#include "catalog/pg_type.h"
#include "catalog/namespace.h"
#include "catalog/pg_authid.h"
#include "commands/trigger.h"
#include "executor/spi.h"
#include "funcapi.h"
#include "optimizer/plancat.h"
#include "parser/parse_oper.h"
#include "storage/bufmgr.h"
#include "utils/builtins.h"
#include "utils/elog.h"
#include "utils/guc.h"
#include "utils/inval.h"
#include "utils/lsyscache.h"
#include "utils/selfuncs.h"
#include "utils/syscache.h"
#include "miscadmin.h"
#if PG_VERSION_NUM >= 90200
#include "utils/rel.h"
#endif
#if PG_VERSION_NUM >= 90300
#include "access/htup_details.h"
#include "utils/catcache.h"
#endif
#include "pg_dbms_stats.h"
PG_MODULE_MAGIC;
/* Error levels used by pg_dbms_stats */
#define ELEVEL_DEBUG DEBUG3 /* log level for debug information */
#define ELEVEL_BADSTATS LOG /* log level for invalid statistics */
#define MAX_REL_CACHE 50 /* expected max # of rel stats entries */
/* Relation statistics cache entry */
typedef struct StatsRelationEntry
{
Oid relid; /* hash key must be at the head */
bool valid; /* T if the entry has valid stats */
BlockNumber relpages; /* # of pages as of last ANALYZE */
double reltuples; /* # of tuples as of last ANALYZE */
BlockNumber relallvisible; /* # of all-visible pages as of last
* ANALYZE */
BlockNumber curpages; /* # of pages as of lock/restore */
List *col_stats; /* list of StatsColumnEntry, each element
of which is pg_statistic record of this
relation. */
} StatsRelationEntry;
/*
* Column statistics cache entry. This is for list item for
* StatsRelationEntry.col_stats.
*/
typedef struct StatsColumnEntry
{
bool negative;
int32 attnum;
bool inh;
HeapTuple tuple;
} StatsColumnEntry;
/* Saved hook functions */
get_relation_info_hook_type prev_get_relation_info = NULL;
get_attavgwidth_hook_type prev_get_attavgwidth = NULL;
get_relation_stats_hook_type prev_get_relation_stats = NULL;
get_index_stats_hook_type prev_get_index_stats = NULL;
/* namings */
#define NSPNAME "dbms_stats"
#define RELSTAT_TBLNAME "relation_stats_locked"
#define COLSTAT_TBLNAME "column_stats_locked"
/* rows_query(oid) RETURNS int4, float4, int4 */
static const char *rows_query =
"SELECT relpages, reltuples, curpages"
#if PG_VERSION_NUM >= 90200
", relallvisible"
#endif
" FROM " NSPNAME "." RELSTAT_TBLNAME
" WHERE relid = $1";
static SPIPlanPtr rows_plan = NULL;
/* tuple_query(oid, int2, bool) RETURNS pg_statistic */
static const char *tuple_query =
"SELECT * "
" FROM " NSPNAME "." COLSTAT_TBLNAME
" WHERE starelid = $1 "
" AND staattnum = $2 "
" AND stainherit = $3";
static SPIPlanPtr tuple_plan = NULL;
/* GUC variables */
static bool pg_dbms_stats_use_locked_stats = true;
/* Current nesting depth of SPI calls, used to prevent recursive calls */
static int nested_level = 0;
/*
* The relation_stats_effective statistic cache is stored in hash table.
*/
static HTAB *rel_stats;
/*
* The owner of pg_dbms_stats statistic tables.
*/
static Oid stats_table_owner = InvalidOid;
static char *stats_table_owner_name = "";
#define get_pg_statistic(tuple) ((Form_pg_statistic) GETSTRUCT(tuple))
PG_FUNCTION_INFO_V1(dbms_stats_merge);
PG_FUNCTION_INFO_V1(dbms_stats_invalidate_relation_cache);
PG_FUNCTION_INFO_V1(dbms_stats_invalidate_column_cache);
PG_FUNCTION_INFO_V1(dbms_stats_is_system_schema);
PG_FUNCTION_INFO_V1(dbms_stats_is_system_catalog);
PG_FUNCTION_INFO_V1(dbms_stats_anyary_anyary);
PG_FUNCTION_INFO_V1(dbms_stats_type_is_analyzable);
PG_FUNCTION_INFO_V1(dbms_stats_anyarray_basetype);
extern Datum dbms_stats_merge(PG_FUNCTION_ARGS);
extern Datum dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS);
extern Datum dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS);
extern Datum dbms_stats_is_system_schema(PG_FUNCTION_ARGS);
extern Datum dbms_stats_is_system_catalog(PG_FUNCTION_ARGS);
extern Datum dbms_stats_anyary_anyary(PG_FUNCTION_ARGS);
extern Datum dbms_stats_type_is_analyzable(PG_FUNCTION_ARGS);
extern Datum dbms_stats_anyarray_basetype(PG_FUNCTION_ARGS);
static HeapTuple dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs,
TupleDesc tupledesc);
static void dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
TriggerData *trigdata, HeapTuple *invtup, HeapTuple *rettup);
static void dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col);
/* Module callbacks */
void _PG_init(void);
void _PG_fini(void);
static void dbms_stats_get_relation_info(PlannerInfo *root, Oid relid,
bool inhparent, RelOptInfo *rel);
static int32 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum);
static bool dbms_stats_get_relation_stats(PlannerInfo *root, RangeTblEntry *rte,
AttrNumber attnum, VariableStatData *vardata);
static bool dbms_stats_get_index_stats(PlannerInfo *root, Oid indexOid,
AttrNumber indexattnum, VariableStatData *vardata);
static void get_merged_relation_stats(Oid relid, BlockNumber *pages,
double *tuples, double *allvisfrac, bool estimate);
static int32 get_merged_avgwidth(Oid relid, AttrNumber attnum);
static HeapTuple get_merged_column_stats(Oid relid, AttrNumber attnum,
bool inh);
static HeapTuple column_cache_search(Oid relid, AttrNumber attnum,
bool inh, bool*negative);
static HeapTuple column_cache_enter(Oid relid, int32 attnum, bool inh,
HeapTuple tuple);
static bool execute_plan(SPIPlanPtr *plan, const char *query, Oid relid,
const AttrNumber *attnum, bool inh);
static void StatsCacheRelCallback(Datum arg, Oid relid);
static void init_rel_stats(void);
static void init_rel_stats_entry(StatsRelationEntry *entry, Oid relid);
/* copied from PG core source tree */
static void dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples, double *allvisfrac,
BlockNumber curpages);
static int32 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths);
/* Unit test suit functions */
#ifdef UNIT_TEST
extern void test_import(int *passed, int *total);
extern void test_dump(int *passed, int *total);
extern void test_pg_dbms_stats(int *passed, int *total);
#endif
/* SPI_keepplan() is since 9.2 */
#if PG_VERSION_NUM < 90200
#define SPI_keepplan(pplan) {\
SPIPlanPtr tp = *plan;\
*plan = SPI_saveplan(tp);\
SPI_freeplan(tp);\
}
#endif
/*
* Module load callback
*/
void
_PG_init(void)
{
/* Execute unit test cases */
#ifdef UNIT_TEST
{
int passed = 0;
int total = 0;
test_import(&passed, &total);
test_dump(&passed, &total);
test_pg_dbms_stats(&passed, &total);
elog(WARNING, "TOTAL %d/%d passed", passed, total);
}
#endif
/* Define custom GUC variables. */
DefineCustomBoolVariable("pg_dbms_stats.use_locked_stats",
"Enable user defined statistics.",
NULL,
&pg_dbms_stats_use_locked_stats,
true,
PGC_USERSET,
0,
NULL,
NULL,
NULL);
EmitWarningsOnPlaceholders("pg_dbms_stats");
/* Back up old hooks, and install ours. */
prev_get_relation_info = get_relation_info_hook;
get_relation_info_hook = dbms_stats_get_relation_info;
prev_get_attavgwidth = get_attavgwidth_hook;
get_attavgwidth_hook = dbms_stats_get_attavgwidth;
prev_get_relation_stats = get_relation_stats_hook;
get_relation_stats_hook = dbms_stats_get_relation_stats;
prev_get_index_stats = get_index_stats_hook;
get_index_stats_hook = dbms_stats_get_index_stats;
/* Initialize hash table for statistics caching. */
init_rel_stats();
/* Also set up a callback for relcache SI invalidations */
CacheRegisterRelcacheCallback(StatsCacheRelCallback, (Datum) 0);
}
/*
* Module unload callback
*/
void
_PG_fini(void)
{
/* Restore old hooks. */
get_relation_info_hook = prev_get_relation_info;
get_attavgwidth_hook = prev_get_attavgwidth;
get_relation_stats_hook = prev_get_relation_stats;
get_index_stats_hook = prev_get_index_stats;
/* A function to unregister callback for relcache is NOT provided. */
}
/*
* Function to convert from any array from dbms_stats.anyarray.
*/
Datum
dbms_stats_anyary_anyary(PG_FUNCTION_ARGS)
{
ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
if (ARR_NDIM(arr) != 1)
elog(ERROR, "array must be one-dimentional.");
PG_RETURN_ARRAYTYPE_P(arr);
}
/*
* Function to check if the type can have statistics.
*/
Datum
dbms_stats_type_is_analyzable(PG_FUNCTION_ARGS)
{
Oid typid = PG_GETARG_OID(0);
Oid eqopr;
if (!OidIsValid(typid))
PG_RETURN_BOOL(false);
get_sort_group_operators(typid, false, false, false,
NULL, &eqopr, NULL,
NULL);
PG_RETURN_BOOL(OidIsValid(eqopr));
}
/*
* Function to get base type of the value of the type dbms_stats.anyarray.
*/
Datum
dbms_stats_anyarray_basetype(PG_FUNCTION_ARGS)
{
ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
Oid elemtype = arr->elemtype;
HeapTuple tp;
Form_pg_type typtup;
Name result;
if (!OidIsValid(elemtype))
elog(ERROR, "invalid base type oid: %u", elemtype);
tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(elemtype));
if (!HeapTupleIsValid(tp)) /* I trust you. */
elog(ERROR, "invalid base type oid: %u", elemtype);
typtup = (Form_pg_type) GETSTRUCT(tp);
result = (Name) palloc0(NAMEDATALEN);
StrNCpy(NameStr(*result), NameStr(typtup->typname), NAMEDATALEN);
ReleaseSysCache(tp);
PG_RETURN_NAME(result);
}
/*
* Find and store the owner of the dummy statistics table.
*
* We will access statistics tables using this owner
*/
static Oid
get_stats_table_owner(void)
{
HeapTuple tp;
if (!OidIsValid(stats_table_owner))
{
tp = SearchSysCache2(RELNAMENSP,
PointerGetDatum(RELSTAT_TBLNAME),
ObjectIdGetDatum(get_namespace_oid(NSPNAME, false)));
if (!HeapTupleIsValid(tp))
elog(ERROR, "table \"%s.%s\" not found in pg_class",
NSPNAME, RELSTAT_TBLNAME);
stats_table_owner = ((Form_pg_class) GETSTRUCT(tp))->relowner;
if (!OidIsValid(stats_table_owner))
elog(ERROR, "owner uid of table \"%s.%s\" is invalid",
NSPNAME, RELSTAT_TBLNAME);
ReleaseSysCache(tp);
tp = SearchSysCache1(AUTHOID, ObjectIdGetDatum(stats_table_owner));
if (!HeapTupleIsValid(tp))
{
elog(ERROR,
"role id %u for the owner of the relation \"%s.%s\"is invalid",
stats_table_owner, NSPNAME, RELSTAT_TBLNAME);
}
/* This will be done once for the session, so not pstrdup. */
stats_table_owner_name =
strdup(NameStr(((Form_pg_authid) GETSTRUCT(tp))->rolname));
ReleaseSysCache(tp);
}
return stats_table_owner;
}
/*
* Store heap tuple header into given heap tuple.
*/
static void
AssignHeapTuple(HeapTuple htup, HeapTupleHeader header)
{
htup->t_len = HeapTupleHeaderGetDatumLength(header);
ItemPointerSetInvalid(&htup->t_self);
htup->t_tableOid = InvalidOid;
htup->t_data = header;
}
/*
* dbms_stats_merge
* called by sql function 'dbms_stats.merge', and return the execution result
* of the function 'dbms_stats_merge_internal'.
*/
Datum
dbms_stats_merge(PG_FUNCTION_ARGS)
{
HeapTupleData lhs;
HeapTupleData rhs;
TupleDesc tupdesc;
HeapTuple ret = NULL;
/* assign HeapTuple of the left statistics data unless null. */
if (PG_ARGISNULL(0))
lhs.t_data = NULL;
else
AssignHeapTuple(&lhs, PG_GETARG_HEAPTUPLEHEADER(0));
/* assign HeapTuple of the right statistics data unless null. */
if (PG_ARGISNULL(1))
rhs.t_data = NULL;
else
AssignHeapTuple(&rhs, PG_GETARG_HEAPTUPLEHEADER(1));
/* fast path for one-side is null */
if (lhs.t_data == NULL && rhs.t_data == NULL)
PG_RETURN_NULL();
/* 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");
/* merge two statistics tuples into one, and return it */
ret = dbms_stats_merge_internal(&lhs, &rhs, tupdesc);
if (ret)
PG_RETURN_DATUM(HeapTupleGetDatum(ret));
else
PG_RETURN_NULL();
}
/*
* dbms_stats_merge_internal
* merge the dummy statistic (lhs) and the true statistic (rhs), on the basis
* of given TupleDesc.
*
* this function doesn't become an error level of ERROR to meet that the
* result of the SQL is not affected by the query plan.
*/
static HeapTuple
dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs, TupleDesc tupdesc)
{
Datum values[Natts_pg_statistic];
bool nulls[Natts_pg_statistic];
int i;
Oid atttype = InvalidOid;
Oid relid;
AttrNumber attnum;
/* fast path for both-sides are null */
if ((lhs == NULL || lhs->t_data == NULL) &&
(rhs == NULL || rhs->t_data == NULL))
return NULL;
/* fast path for one-side is null */
if (lhs == NULL || lhs->t_data == NULL)
{
/* use right tuple */
heap_deform_tuple(rhs, tupdesc, values, nulls);
for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
if (nulls[i])
return NULL; /* check null constraints */
}
else if (rhs == NULL || rhs->t_data == NULL)
{
/* use left tuple */
heap_deform_tuple(lhs, tupdesc, values, nulls);
for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
if (nulls[i])
return NULL; /* check null constraints */
}
else
{
/*
* If the column value of the dummy statistic is not NULL, in the
* statistics except the slot, use it. Otherwise we use the column
* value of the true statistic.
*/
heap_deform_tuple(lhs, tupdesc, values, nulls);
for (i = 0; i < Anum_pg_statistic_stakind1 - 1; i++)
{
if (nulls[i])
{
values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
if (nulls[i])
{
ereport(ELEVEL_BADSTATS,
(errmsg("pg_dbms_stats: bad statistics"),
errdetail("column \"%s\" should not be null",
get_attname(StatisticRelationId,
tupdesc->attrs[i]->attnum))));
return NULL; /* should not be null */
}
}
}
/*
* If the column value of the dummy statistic is not all NULL, in the
* statistics the slot, use it. Otherwise we use the column
* value of the true statistic.
*/
for (; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
{
if (nulls[i])
{
for (i = Anum_pg_statistic_stakind1 - 1;
i < Anum_pg_statistic_stavalues1 + STATISTIC_NUM_SLOTS - 1;
i++)
{
values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
if (i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1 &&
nulls[i])
{
ereport(ELEVEL_BADSTATS,
(errmsg("pg_dbms_stats: bad statistics"),
errdetail("column \"%s\" should not be null",
get_attname(StatisticRelationId,
tupdesc->attrs[i]->attnum))));
return NULL; /* should not be null */
}
}
break;
}
}
}
/*
* Verify types to work around for ALTER COLUMN TYPE.
*
* Note: We don't need to retrieve atttype when the attribute doesn't have
* neither Most-Common-Value nor Histogram, but we retrieve it always
* because it's not usual.
*/
relid = DatumGetObjectId(values[0]);
attnum = DatumGetInt16(values[1]);
atttype = get_atttype(relid, attnum);
if (atttype == InvalidOid)
{
ereport(WARNING,
(errmsg("pg_dbms_stats: no-longer-existent column"),
errdetail("relid \"%d\" or its column whose attnum is \"%d\" might be deleted",
relid, attnum),
errhint("dbms_stats.clean_up_stats() would fix this.")));
return NULL;
}
for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
{
if ((i + 1 == STATISTIC_KIND_MCV ||
i + 1 == STATISTIC_KIND_HISTOGRAM) &&
!nulls[Anum_pg_statistic_stavalues1 + i - 1])
{
ArrayType *arr;
arr = DatumGetArrayTypeP(
values[Anum_pg_statistic_stavalues1 + i - 1]);
if (arr == NULL || arr->elemtype != atttype)
{
const char *attname = get_attname(relid, attnum);
/*
* relid and attnum must be valid here because valid atttype
* has been gotten already.
*/
Assert(attname);
ereport(ELEVEL_BADSTATS,
(errmsg("pg_dbms_stats: bad column type"),
errdetail("type of column \"%s\" has been changed",
attname),
errhint("need to execute dbms_stats.unlock('%s', '%s')",
get_rel_name(relid), attname)));
return NULL;
}
}
}
return heap_form_tuple(tupdesc, values, nulls);
}
/*
* dbms_stats_invalidate_relation_cache
* Register invalidation of the specified relation's relcache.
*
* CREATE TRIGGER dbms_stats.relation_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
* ROWS EXECUTE ...
*/
Datum
dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
HeapTuple invtup; /* tuple to be invalidated */
HeapTuple rettup; /* tuple to be returned */
Datum value;
bool isnull;
/* make sure it's called as a before/after trigger */
dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
/*
* assume that position of dbms_stats.relation_stats_locked.relid is head value of
* tuple.
*/
value = fastgetattr(invtup, 1, trigdata->tg_relation->rd_att, &isnull);
/*
* invalidate prepared statements and force re-planning with pg_dbms_stats.
*/
dbms_stats_invalidate_cache_internal((Oid)value, false);
PG_RETURN_POINTER(rettup);
}
/*
* dbms_stats_invalidate_column_cache
* Register invalidation of the specified relation's relcache.
*
* CREATE TRIGGER dbms_stats.column_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
* ROWS EXECUTE ...
*/
Datum
dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS)
{
TriggerData *trigdata = (TriggerData *) fcinfo->context;
Form_pg_statistic form;
HeapTuple invtup; /* tuple to be invalidated */
HeapTuple rettup; /* tuple to be returned */
/* make sure it's called as a before/after trigger */
dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
/*
* assume that both pg_statistic and dbms_stats.column_stats_locked have the same
* definition.
*/
form = get_pg_statistic(invtup);
/*
* invalidate prepared statements and force re-planning with pg_dbms_stats.
*/
dbms_stats_invalidate_cache_internal(form->starelid, true);
PG_RETURN_POINTER(rettup);
}
static void
dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
TriggerData *trigdata,
HeapTuple *invtup,
HeapTuple *rettup)
{
/* make sure it's called as a before/after trigger */
if (!CALLED_AS_TRIGGER(fcinfo) ||
!TRIGGER_FIRED_BEFORE(trigdata->tg_event) ||
!TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
elog(ERROR, "pg_dbms_stats: invalid trigger call");
if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
{
/* INSERT */
*rettup = *invtup = trigdata->tg_trigtuple;
}
else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
{
/* DELETE */
*rettup = *invtup = trigdata->tg_trigtuple;
}
else
{
/* UPDATE */
*invtup = trigdata->tg_trigtuple;
*rettup = trigdata->tg_newtuple;
}
}
static void
dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col)
{
Relation rel;
/*
* invalidate prepared statements and force re-planning with pg_dbms_stats.
*/
rel = try_relation_open(relid, NoLock);
if (rel != NULL)
{
if (sta_col &&
rel->rd_rel->relkind == RELKIND_INDEX &&
(rel->rd_indextuple == NULL ||
heap_attisnull(rel->rd_indextuple, Anum_pg_index_indexprs)))
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is an index except an index expression",
RelationGetRelationName(rel))));
if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
ereport(ERROR,
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
errmsg("\"%s\" is a composite type",
RelationGetRelationName(rel))));
/*
* We need to invalidate relcache of underlying table too, because
* CachedPlan mechanism decides to do re-planning when any relcache of
* used tables was invalid at EXECUTE.
*/
if (rel->rd_rel->relkind == RELKIND_INDEX &&
rel->rd_index && OidIsValid(rel->rd_index->indrelid))
CacheInvalidateRelcacheByRelid(rel->rd_index->indrelid);
CacheInvalidateRelcache(rel);
relation_close(rel, NoLock);
}
}
/*
* dbms_stats_is_system_schema
* called by sql function 'dbms_stats.is_system_schema', and return the
* result of the function 'dbms_stats_is_system_internal'.
*/
Datum
dbms_stats_is_system_schema(PG_FUNCTION_ARGS)
{
text *arg0;
char *schema_name;
bool result;
arg0 = PG_GETARG_TEXT_PP(0);
schema_name = text_to_cstring(arg0);
result = dbms_stats_is_system_schema_internal(schema_name);
PG_FREE_IF_COPY(arg0, 0);
PG_RETURN_BOOL(result);
}
/*
* dbms_stats_is_system_schema_internal
* return whether the given schema contains any system catalog. Here we
* treat dbms_stats objects as system catalogs to avoid infinite loop.
*/
bool
dbms_stats_is_system_schema_internal(char *schema_name)
{
Assert(schema_name != NULL);
/* if the schema is system_schema, return true */
if (strcmp(schema_name, "pg_catalog") == 0 ||
strcmp(schema_name, "pg_toast") == 0 ||
strcmp(schema_name, "information_schema") == 0 ||
strcmp(schema_name, NSPNAME) == 0)
return true;
return false;
}
/*
* dbms_stats_is_system_catalog
* called by sql function 'dbms_stats.is_system_catalog', and return the
* result of the function 'dbms_stats_is_system_catalog_internal'.
*/
Datum
dbms_stats_is_system_catalog(PG_FUNCTION_ARGS)
{
Oid relid;
bool result;
if (PG_ARGISNULL(0))
PG_RETURN_BOOL(true);
relid = PG_GETARG_OID(0);
result = dbms_stats_is_system_catalog_internal(relid);
PG_RETURN_BOOL(result);
}
/*
* dbms_stats_is_system_catalog_internal
* Check whether the given relation is one of system catalogs.
*/
bool
dbms_stats_is_system_catalog_internal(Oid relid)
{
Relation rel;
char *schema_name;
bool result;
/* relid is InvalidOid */
if (!OidIsValid(relid))
return false;
/* no such relation */
rel = try_relation_open(relid, NoLock);
if (rel == NULL)
return false;
/* check by namespace name. */
schema_name = get_namespace_name(rel->rd_rel->relnamespace);
result = dbms_stats_is_system_schema_internal(schema_name);
relation_close(rel, NoLock);
return result;
}
/*
* dbms_stats_get_relation_info
* Hook function for get_relation_info_hook, which implements post-process of
* get_relation_info().
*
* This function is designed on the basis of the fact that only expression
* indexes have statistics.
*/
static void
dbms_stats_get_relation_info(PlannerInfo *root,
Oid relid,
bool inhparent,
RelOptInfo *rel)
{
ListCell *lc;
double allvisfrac; /* dummy */
/*
* Call previously installed hook function regardless to whether
* pg_dbms_stats is enabled or not.
*/
if (prev_get_relation_info)
prev_get_relation_info(root, relid, inhparent, rel);
/* If pg_dbms_stats is disabled, there is no more thing to do. */
if (!pg_dbms_stats_use_locked_stats)
return;
/*
* Adjust stats of table itself, and stats of index
* relation_stats_effective as well
*/
/*
* Estimate relation size --- unless it's an inheritance parent, in which
* case the size will be computed later in set_append_rel_pathlist, and we
* must leave it zero for now to avoid bollixing the total_table_pages
* calculation.
*/
if (!inhparent)
{
#if PG_VERSION_NUM >= 90200
get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
&rel->allvisfrac, true);
#else
get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
&allvisfrac, true);
#endif
}
else
return;
foreach(lc, rel->indexlist)
{
/*
* Estimate the index size. If it's not a partial index, we lock
* the number-of-tuples estimate to equal the parent table; if it
* is partial then we have to use the same methods as we would for
* a table, except we can be sure that the index is not larger
* than the table.
*/
IndexOptInfo *info = (IndexOptInfo *) lfirst(lc);
bool estimate = info->indpred != NIL;
get_merged_relation_stats(info->indexoid, &info->pages, &info->tuples,
&allvisfrac, estimate);
if (!estimate || (estimate && info->tuples > rel->tuples))
info->tuples = rel->tuples;
}
}
/*
* dbms_stats_get_attavgwidth
* Hook function for get_attavgwidth_hook which replaces get_attavgwidth().
* Returning 0 tells caller to use standard routine.
*/
static int32
dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum)
{
if (pg_dbms_stats_use_locked_stats)
{
int32 width = get_merged_avgwidth(relid, attnum);
if (width > 0)
return width;
}
if (prev_get_attavgwidth)
return prev_get_attavgwidth(relid, attnum);
else
return 0;
}
/*
* We do nothing here, to keep the tuple valid even after examination.
*/
static void
FreeHeapTuple(HeapTuple tuple)
{
/* noop */
}
/*
* dbms_stats_get_relation_stats
* Hook function for get_relation_stats_hook which provides custom
* per-relation statistics.
* Returning false tells caller to use standard (true) statistics.
*/
static bool
dbms_stats_get_relation_stats(PlannerInfo *root,
RangeTblEntry *rte,
AttrNumber attnum,
VariableStatData *vardata)
{
if (pg_dbms_stats_use_locked_stats)
{
HeapTuple tuple;
tuple = get_merged_column_stats(rte->relid, attnum, rte->inh);
vardata->statsTuple = tuple;
if (tuple != NULL)
{
vardata->freefunc = FreeHeapTuple;
return true;
}
}
if (prev_get_relation_stats)
return prev_get_relation_stats(root, rte, attnum, vardata);
else
return false;
}
/*
* dbms_stats_get_index_stats
* Hook function for get_index_stats_hook which provides custom per-relation
* statistics.
* Returning false tells caller to use standard (true) statistics.
*/
static bool
dbms_stats_get_index_stats(PlannerInfo *root,
Oid indexOid,
AttrNumber indexattnum,
VariableStatData *vardata)
{
if (pg_dbms_stats_use_locked_stats)
{
HeapTuple tuple;
tuple = get_merged_column_stats(indexOid, indexattnum, false);
vardata->statsTuple = tuple;
if (tuple != NULL)
{
vardata->freefunc = FreeHeapTuple;
return true;
}
}
if (prev_get_index_stats)
return prev_get_index_stats(root, indexOid, indexattnum, vardata);
else
return false;
}
/*
* Extract binary value from given column.
*/
static Datum
get_binary_datum(int column, bool *isnull)
{
return SPI_getbinval(SPI_tuptable->vals[0],
SPI_tuptable->tupdesc, column, isnull);
}
/*
* get_merged_relation_stats
* get the statistics of the table, # of pages and # of rows, by executing
* SELECT against dbms_stats.relation_stats_locked view.
*/
static void
get_merged_relation_stats(Oid relid, BlockNumber *pages, double *tuples,
double *allvisfrac, bool estimate)
{
StatsRelationEntry *entry;
bool found;
Relation rel;
/* avoid recursive call and system objects */
if (nested_level > 0 || relid < FirstNormalObjectId)
return;
/*
* pg_dbms_stats doesn't handle system catalogs and its internal relation_stats_effective
*/
if (dbms_stats_is_system_catalog_internal(relid))
return;
/*
* First, search from cache. If we have not cached stats for given relid
* yet, initialize newly created entry.
*/
entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
if (!found)
init_rel_stats_entry(entry, relid);
if (entry->valid)
{
/*
* Valid entry with invalid relpage is a negative cache, which
* eliminates excessive SPI calls below. Negative caches will be
* invalidated again on invalidation of system relation cache, which
* occur on modification of the dummy stats tables
* dbms_stats.relation_stats_locked and column_stats_locked.
*/
if (entry->relpages == InvalidBlockNumber)
return;
}
if (!entry->valid)
{
/*
* If we don't have valid cache entry, retrieve system stats and dummy
* stats in dbms_stats.relation_stats_locked, then merge them for
* planner use. We also cache curpages value to make plans stable.
*/
bool has_dummy;
PG_TRY();
{
++nested_level;
SPI_connect();
/*
* Retrieve per-relation dummy statistics from
* relation_stats_locked table via SPI.
*/
has_dummy = execute_plan(&rows_plan, rows_query, relid, NULL, true);
if (!has_dummy)
{
/* If dummy stats is not found, store negative cache. */
entry->relpages = InvalidBlockNumber;
}
else
{
/*
* Retrieve per-relation system stats from pg_class. We use
* syscache to support indexes
*/
HeapTuple tuple;
Form_pg_class form;
bool isnull;
Datum val;
tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
if (!HeapTupleIsValid(tuple))
elog(ERROR, "cache lookup failed for relation %u", relid);
form = (Form_pg_class) GETSTRUCT(tuple);
/* Choose dummy or authentic */
val = get_binary_datum(1, &isnull);
entry->relpages = isnull ? form->relpages :
(BlockNumber) DatumGetInt32(val);
val = get_binary_datum(2, &isnull);
entry->reltuples = isnull ? form->reltuples :
(double) DatumGetFloat4(val);
val = get_binary_datum(3, &isnull);
entry->curpages = isnull ? InvalidBlockNumber :
(BlockNumber) DatumGetInt32(val);
#if PG_VERSION_NUM >= 90200
val = get_binary_datum(4, &isnull);
entry->relallvisible = isnull ? form->relallvisible :
(BlockNumber) DatumGetInt32(val);
#endif
ReleaseSysCache(tuple);
}
entry->valid = true;
SPI_finish();
--nested_level;
}
PG_CATCH();
{
--nested_level;
PG_RE_THROW();
}
PG_END_TRY();
/*
* If no dummy statistics available for this relation, do nothing then
* return immediately.
*/
if (!has_dummy)
return;
}
/* Tweaking statistics using merged statistics */
if (!estimate)
{
*pages = entry->relpages;
*tuples = entry->reltuples;
return;
}
/*
* Get current number of pages to estimate current number of tuples, based
* on tuple density at the last ANALYZE and current number of pages.
*/
rel = relation_open(relid, NoLock);
rel->rd_rel->relpages = entry->relpages;
rel->rd_rel->reltuples = entry->reltuples;
#if PG_VERSION_NUM >= 90200
rel->rd_rel->relallvisible = entry->relallvisible;
#endif
dbms_stats_estimate_rel_size(rel, NULL, pages, tuples, allvisfrac,
entry->curpages);
relation_close(rel, NoLock);
}
/*
* get_merged_avgwidth
* get average width of the given column by merging dummy and authentic
* statistics
*/
static int32
get_merged_avgwidth(Oid relid, AttrNumber attnum)
{
HeapTuple tuple;
if (nested_level > 0 || relid < FirstNormalObjectId)
return 0; /* avoid recursive call and system objects */
if ((tuple = get_merged_column_stats(relid, attnum, false)) == NULL)
return 0;
return get_pg_statistic(tuple)->stawidth;
}
/*
* get_merged_column_stats
* returns per-column statistics for given column
*
* This caches the result to avoid excessive SPI calls for repetitive
* request for every columns many time.
*/
static HeapTuple
get_merged_column_stats(Oid relid, AttrNumber attnum, bool inh)
{
HeapTuple tuple;
HeapTuple statsTuple;
bool negative = false;
if (nested_level > 0 || relid < FirstNormalObjectId)
return NULL; /* avoid recursive call and system objects */
/*
* Return NULL for system catalog, directing the caller to use system
* statistics.
*/
if (dbms_stats_is_system_catalog_internal(relid))
return NULL;
/* Return cached statistics, if any. */
if ((tuple = column_cache_search(relid, attnum, inh, &negative)) != NULL)
return tuple;
/* Obtain system statistics from syscache. */
statsTuple = SearchSysCache3(STATRELATTINH,
ObjectIdGetDatum(relid),
Int16GetDatum(attnum),
BoolGetDatum(inh));
if (negative)
{
/*
* Return system statistics whatever it is if negative cache for this
* column is returned
*/
tuple = heap_copytuple(statsTuple);
}
else
{
/*
* Search for dummy statistics and try merge with system stats.
*/
PG_TRY();
{
/*
* Save current context in order to use during SPI is
* connected.
*/
MemoryContext outer_cxt = CurrentMemoryContext;
bool exec_success;
++nested_level;
SPI_connect();
/* Obtain dummy statistics for the column using SPI call. */
exec_success =
execute_plan(&tuple_plan, tuple_query, relid, &attnum, inh);
/* Reset to the outer memory context for following steps. */
MemoryContextSwitchTo(outer_cxt);
if (exec_success)
{
/* merge the dummy statistics with the system statistics */
tuple = dbms_stats_merge_internal(SPI_tuptable->vals[0],
statsTuple,
SPI_tuptable->tupdesc);
}
else
tuple = NULL;
/* Cache merged result for subsequent calls. */
tuple = column_cache_enter(relid, attnum, inh, tuple);
/* Return system stats if the merging results in failure. */
if (!HeapTupleIsValid(tuple))
tuple = heap_copytuple(statsTuple);
SPI_finish();
--nested_level;
}
PG_CATCH();
{
--nested_level;
PG_RE_THROW();
}
PG_END_TRY();
}
if (HeapTupleIsValid(statsTuple))
ReleaseSysCache(statsTuple);
return tuple;
}
/*
* column_cache_search
* Search statistic of the given column from the cache.
*/
static HeapTuple
column_cache_search(Oid relid, AttrNumber attnum, bool inh, bool *negative)
{
StatsRelationEntry *entry;
bool found;
ListCell *lc;
*negative = false;
/*
* First, get cached relation stats. If we have not cached relation stats,
* we don't have column stats too.
*/
entry = hash_search(rel_stats, &relid, HASH_FIND, &found);
if (!found)
return NULL;
/*
* We assume that not so many column_stats_effective are defined on one
* relation, so we use simple linear-search here. Hash table would be an
* alternative, but it seems overkill so far.
*/
foreach(lc, entry->col_stats)
{
StatsColumnEntry *ent = (StatsColumnEntry*) lfirst (lc);
if (ent->attnum != attnum || ent->inh != inh) continue;
if (ent->negative)
{
/* Retrun NULL for negative cache, with noticing of that.*/
*negative = true;
return NULL;
}
return ent->tuple;
}
return NULL; /* Not yet registered. */
}
/*
* Cache a per-column statistics. Storing in CacheMemoryContext, the cached
* statistics will live through the current session, unless dummy statistics or
* table definition have been changed.
*/
static HeapTuple
column_cache_enter(Oid relid, int32 attnum, bool inh, HeapTuple tuple)
{
MemoryContext oldcontext;
StatsColumnEntry *newcolent;
StatsRelationEntry *entry;
bool found;
Assert(tuple == NULL || !heap_attisnull(tuple, 1));
entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
if (!found)
init_rel_stats_entry(entry, relid);
/*
* Adding this column stats to the column stats list of the relation stats
* cache just obtained.
*/
oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
newcolent = (StatsColumnEntry*)palloc(sizeof(StatsColumnEntry));
newcolent->attnum = attnum;
newcolent->inh = inh;
if (HeapTupleIsValid(tuple))
{
newcolent->negative = false;
newcolent->tuple = heap_copytuple(tuple);
}
else
{
/* Invalid tuple makes a negative cache. */
newcolent->negative = true;
newcolent->tuple = NULL;
}
entry->col_stats = lappend(entry->col_stats, newcolent);
MemoryContextSwitchTo(oldcontext);
return newcolent->tuple;
}
/*
* Execute given plan. When given plan is NULL, create new plan from given
* query string, and execute it. This function can be used only for retrieving
* statistics of column_stats_effective and relation_stats_effective, because we assume #, types, and order
* of parameters here.
*/
static bool
execute_plan(SPIPlanPtr *plan,
const char *query,
Oid relid,
const AttrNumber *attnum,
bool inh)
{
int ret;
Oid argtypes[3] = { OIDOID, INT2OID, BOOLOID };
int nargs;
Datum values[3];
bool nulls[3] = { false, false, false };
Oid save_userid;
int save_sec_context;
/* XXXX: this works for now but should be fixed later.. */
nargs = (attnum ? 3 : 1);
/*
* The dummy statistics table allows access from no one other than its
* owner or superuser.
*/
GetUserIdAndSecContext(&save_userid, &save_sec_context);
SetUserIdAndSecContext(get_stats_table_owner(),
save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
PG_TRY();
{
/* Create plan from the query if not yet. */
if (*plan == NULL)
{
*plan = SPI_prepare(query, nargs, argtypes);
if (*plan == NULL)
elog(ERROR,
"pg_dbms_stats: SPI_prepare() failed. result = %d",
SPI_result);
SPI_keepplan(*plan);
}
values[0] = ObjectIdGetDatum(relid);
values[1] = Int16GetDatum(attnum ? *attnum : 0);
values[2] = BoolGetDatum(inh);
ret = SPI_execute_plan(*plan, values, nulls, true, 1);
}
PG_CATCH();
{
SetUserIdAndSecContext(save_userid, save_sec_context);
if (geterrcode() == ERRCODE_INSUFFICIENT_PRIVILEGE)
errdetail("dbms_stats could not access the object as the role \"%s\".",
stats_table_owner_name);
errhint("Check your settings of pg_dbms_stats.");
PG_RE_THROW();
}
PG_END_TRY();
SetUserIdAndSecContext(save_userid, save_sec_context);
if (ret != SPI_OK_SELECT)
elog(ERROR, "pg_dbms_stats: SPI_execute_plan() returned %d", ret);
return SPI_processed > 0;
}
/*
* StatsCacheRelCallback
* Relcache inval callback function
*
* Invalidate cached statistic info of the given relid, or all cached statistic
* info if relid == InvalidOid. We don't complain even when we don't have such
* statistics.
*
* Note: arg is not used.
*/
static void
StatsCacheRelCallback(Datum arg, Oid relid)
{
HASH_SEQ_STATUS status;
StatsRelationEntry *entry;
hash_seq_init(&status, rel_stats);
while ((entry = hash_seq_search(&status)) != NULL)
{
if (relid == InvalidOid || relid == entry->relid)
{
ListCell *lc;
/* Mark the relation entry as INVALID */
entry->valid = false;
/* Discard every column statistics */
foreach (lc, entry->col_stats)
{
StatsColumnEntry *ent = (StatsColumnEntry*) lfirst(lc);
if (!ent->negative)
pfree(ent->tuple);
pfree(ent);
}
list_free(entry->col_stats);
entry->col_stats = NIL;
}
}
/* We always check throughout the list, so hash_seq_term is not necessary */
}
/*
* Initialize hash table for per-relation statistics.
*/
static void
init_rel_stats(void)
{
HTAB *hash;
HASHCTL ctl;
/* Prevent double initialization. */
if (rel_stats != NULL)
return;
MemSet(&ctl, 0, sizeof(ctl));
ctl.keysize = sizeof(Oid);
ctl.entrysize = sizeof(StatsRelationEntry);
ctl.hash = oid_hash;
ctl.hcxt = CacheMemoryContext;
hash = hash_create("dbms_stats relation statistics cache",
MAX_REL_CACHE,
&ctl, HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION);
rel_stats = hash;
}
/*
* Initialize newly added cache entry so that it represents an invalid cache
* entry for given relid.
*/
static void
init_rel_stats_entry(StatsRelationEntry *entry, Oid relid)
{
entry->relid = relid;
entry->valid = false;
entry->relpages = InvalidBlockNumber;
entry->reltuples = 0.0;
entry->relallvisible = InvalidBlockNumber;
entry->curpages = InvalidBlockNumber;
entry->col_stats = NIL;
}
/*
* dbms_stats_estimate_rel_size - estimate # pages and # tuples in a table or
* index
*
* We also estimate the fraction of the pages that are marked all-visible in
* the visibility map, for use in estimation of index-only scans.
*
* If attr_widths isn't NULL, it points to the zero-index entry of the
* relation's attr_widths[] cache; we fill this in if we have need to compute
* the attribute widths for estimation purposes.
*
* Note: This function is copied from plancat.c in core source tree of version
* 9.2, and customized for pg_dbms_stats. Changes from original one are:
* - rename by prefixing dbms_stats_
* - add 3 parameters (relpages, reltuples, curpage) to pass dummy curpage
* values.
* - Get current # of pages only when supplied curpages is InvalidBlockNumber
* - get fraction of all-visible-pages
*/
static void
dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
BlockNumber *pages, double *tuples,
double *allvisfrac, BlockNumber curpages)
{
BlockNumber relpages;
double reltuples;
BlockNumber relallvisible;
double density;
switch (rel->rd_rel->relkind)
{
case RELKIND_RELATION:
case RELKIND_INDEX:
#if PG_VERSION_NUM >= 90300
case RELKIND_MATVIEW:
#endif
case RELKIND_TOASTVALUE:
/* it has storage, ok to call the smgr */
if (curpages == InvalidBlockNumber)
curpages = RelationGetNumberOfBlocks(rel);
/*
* HACK: if the relation has never yet been vacuumed, use a
* minimum size estimate of 10 pages. The idea here is to avoid
* assuming a newly-created table is really small, even if it
* currently is, because that may not be true once some data gets
* loaded into it. Once a vacuum or analyze cycle has been done
* on it, it's more reasonable to believe the size is somewhat
* stable.
*
* (Note that this is only an issue if the plan gets cached and
* used again after the table has been filled. What we're trying
* to avoid is using a nestloop-type plan on a table that has
* grown substantially since the plan was made. Normally,
* autovacuum/autoanalyze will occur once enough inserts have
* happened and cause cached-plan invalidation; but that doesn't
* happen instantaneously, and it won't happen at all for cases
* such as temporary tables.)
*
* We approximate "never vacuumed" by "has relpages = 0", which
* means this will also fire on genuinely empty relation_stats_effective. Not
* great, but fortunately that's a seldom-seen case in the real
* world, and it shouldn't degrade the quality of the plan too
* much anyway to err in this direction.
*
* There are two exceptions wherein we don't apply this heuristic.
* One is if the table has inheritance children. Totally empty
* parent tables are quite common, so we should be willing to
* believe that they are empty. Also, we don't apply the 10-page
* minimum to indexes.
*/
if (curpages < 10 &&
rel->rd_rel->relpages == 0 &&
!rel->rd_rel->relhassubclass &&
rel->rd_rel->relkind != RELKIND_INDEX)
curpages = 10;
/* report estimated # pages */
*pages = curpages;
/* quick exit if rel is clearly empty */
if (curpages == 0)
{
*tuples = 0;
*allvisfrac = 0;
break;
}
/* coerce values in pg_class to more desirable types */
relpages = (BlockNumber) rel->rd_rel->relpages;
reltuples = (double) rel->rd_rel->reltuples;
#if PG_VERSION_NUM >= 90200
relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
#else
relallvisible = 0;
#endif
/*
* If it's an index, discount the metapage while estimating the
* number of tuples. This is a kluge because it assumes more than
* it ought to about index structure. Currently it's OK for
* btree, hash, and GIN indexes but suspect for GiST indexes.
*/
if (rel->rd_rel->relkind == RELKIND_INDEX &&
relpages > 0)
{
curpages--;
relpages--;
}
/* estimate number of tuples from previous tuple density */
if (relpages > 0)
density = reltuples / (double) relpages;
else
{
/*
* When we have no data because the relation was truncated,
* estimate tuple width from attribute datatypes. We assume
* here that the pages are completely full, which is OK for
* tables (since they've presumably not been VACUUMed yet) but
* is probably an overestimate for indexes. Fortunately
* get_relation_info() can clamp the overestimate to the
* parent table's size.
*
* Note: this code intentionally disregards alignment
* considerations, because (a) that would be gilding the lily
* considering how crude the estimate is, and (b) it creates
* platform dependencies in the default plans which are kind
* of a headache for regression testing.
*/
int32 tuple_width;
tuple_width = dbms_stats_get_rel_data_width(rel, attr_widths);
tuple_width += sizeof(HeapTupleHeaderData);
tuple_width += sizeof(ItemPointerData);
/* note: integer division is intentional here */
density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width;
}
*tuples = rint(density * (double) curpages);
/*
* We use relallvisible as-is, rather than scaling it up like we
* do for the pages and tuples counts, on the theory that any
* pages added since the last VACUUM are most likely not marked
* all-visible. But costsize.c wants it converted to a fraction.
*/
if (relallvisible == 0 || curpages <= 0)
*allvisfrac = 0;
else if ((double) relallvisible >= curpages)
*allvisfrac = 1;
else
*allvisfrac = (double) relallvisible / curpages;
break;
case RELKIND_SEQUENCE:
/* Sequences always have a known size */
*pages = 1;
*tuples = 1;
*allvisfrac = 0;
break;
case RELKIND_FOREIGN_TABLE:
/* Just use whatever's in pg_class */
*pages = rel->rd_rel->relpages;
*tuples = rel->rd_rel->reltuples;
*allvisfrac = 0;
break;
default:
/* else it has no disk storage; probably shouldn't get here? */
*pages = 0;
*tuples = 0;
*allvisfrac = 0;
break;
}
}
/*
* dbms_stats_get_rel_data_width
*
* Estimate the average width of (the data part of) the relation's tuples.
*
* If attr_widths isn't NULL, it points to the zero-index entry of the
* relation's attr_widths[] cache; use and update that cache as appropriate.
*
* Currently we ignore dropped column_stats_effective. Ideally those should be included
* in the result, but we haven't got any way to get info about them; and
* since they might be mostly NULLs, treating them as zero-width is not
* necessarily the wrong thing anyway.
*
* Note: This function is copied from plancat.c in core source tree of version
* 9.2, and just renamed.
*/
static int32
dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths)
{
int32 tuple_width = 0;
int i;
for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
{
Form_pg_attribute att = rel->rd_att->attrs[i - 1];
int32 item_width;
if (att->attisdropped)
continue;
/* use previously cached data, if any */
if (attr_widths != NULL && attr_widths[i] > 0)
{
tuple_width += attr_widths[i];
continue;
}
/* This should match set_rel_width() in costsize.c */
item_width = get_attavgwidth(RelationGetRelid(rel), i);
if (item_width <= 0)
{
item_width = get_typavgwidth(att->atttypid, att->atttypmod);
Assert(item_width > 0);
}
if (attr_widths != NULL)
attr_widths[i] = item_width;
tuple_width += item_width;
}
return tuple_width;
}
#ifdef UNIT_TEST
void test_pg_dbms_stats(int *passed, int *total);
static void test_init_rel_stats(int *passed, int *total);
static void test_init_rel_stats_entry(int *passed, int *total);
void
test_pg_dbms_stats(int *passed, int *total)
{
int local_passed = 0;
int local_total = 0;
elog(WARNING, "==========");
/* Do tests here */
test_init_rel_stats(&local_passed, &local_total);
test_init_rel_stats_entry(&local_passed, &local_total);
elog(WARNING, "%s %d/%d passed", __FUNCTION__, local_passed, local_total);
*passed += local_passed;
*total += local_total;
}
static void
test_init_rel_stats_entry(int *passed, int *total)
{
int caseno = 0;
StatsRelationEntry entry;
/*
* *-*-1
*/
caseno++;
init_rel_stats_entry(&entry, 1234);
if (entry.relid == 1234 &&
entry.valid == false &&
entry.relpages == InvalidBlockNumber &&
entry.reltuples == 0 &&
entry.relallvisible == InvalidBlockNumber &&
entry.curpages == InvalidBlockNumber &&
entry.col_stats == NIL)
{
elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
(*passed)++;
}
else
elog(WARNING, "%s-%d failed: initialized", __FUNCTION__, caseno);
(*total) += caseno;
}
static void
test_init_rel_stats(int *passed, int *total)
{
int caseno = 0;
static HTAB *org_rel_stats;
/*
* *-*-1
* - first call
*/
caseno++;
init_rel_stats();
if (rel_stats != NULL)
{
elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
(*passed)++;
}
else
elog(WARNING, "%s-%d failed: rel_stats is NULL", __FUNCTION__, caseno);
/*
* *-*-2
* - second call
*/
caseno++;
org_rel_stats = rel_stats;
init_rel_stats();
if (org_rel_stats == rel_stats)
{
elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
(*passed)++;
}
else
elog(WARNING, "%s-%d failed: rel_stats changed from %p to %p",
__FUNCTION__, caseno, org_rel_stats, rel_stats);
*total += caseno;
}
#endif