4 * Copyright (c) 2009-2017, NIPPON TELEGRAPH AND TELEPHONE CORPORATION
5 * Portions Copyright (c) 1996-2013, PostgreSQL Global Development Group
6 * Portions Copyright (c) 1994, Regents of the University of California
10 #include "access/sysattr.h"
11 #include "access/transam.h"
12 #include "catalog/pg_index.h"
13 #include "catalog/pg_statistic.h"
14 #include "catalog/pg_type.h"
15 #include "catalog/namespace.h"
16 #include "catalog/pg_authid.h"
17 #include "commands/trigger.h"
18 #include "executor/spi.h"
20 #include "optimizer/plancat.h"
21 #include "optimizer/planner.h"
22 #include "parser/parse_oper.h"
23 #include "parser/parsetree.h"
24 #include "storage/bufmgr.h"
25 #include "utils/acl.h"
26 #include "utils/builtins.h"
27 #include "utils/elog.h"
28 #include "utils/fmgroids.h"
29 #include "utils/guc.h"
30 #include "utils/inval.h"
31 #include "utils/lsyscache.h"
32 #include "utils/selfuncs.h"
33 #include "utils/syscache.h"
34 #include "miscadmin.h"
35 #include "utils/rel.h"
36 #if PG_VERSION_NUM >= 90300
37 #include "access/htup_details.h"
38 #include "utils/catcache.h"
40 #if PG_VERSION_NUM >= 100000
44 #include "pg_dbms_stats.h"
48 /* Error levels used by pg_dbms_stats */
49 #define ELEVEL_DEBUG DEBUG3 /* log level for debug information */
50 #define ELEVEL_BADSTATS LOG /* log level for invalid statistics */
52 #define MAX_REL_CACHE 50 /* expected max # of rel stats entries */
54 /* acl_ok must be set after the fix for CVE-2017-7484 */
55 #if PG_VERSION_NUM >= 100000 || \
56 (PG_VERSION_NUM >= 90603 && PG_VERSION_NUM < 100000) || \
57 (PG_VERSION_NUM >= 90507 && PG_VERSION_NUM < 90600) || \
58 (PG_VERSION_NUM >= 90412 && PG_VERSION_NUM < 90500) || \
59 (PG_VERSION_NUM >= 90317 && PG_VERSION_NUM < 90400) || \
60 (PG_VERSION_NUM >= 90221 && PG_VERSION_NUM < 90300)
61 #define PGDS_HAVE_ACL_OK
63 * acl_ok of the returning VariableStatData must be set if set_acl_okk is
64 * true. The code is compiled only if the compile target PG version is match
65 * the above conditions. Conversely, acl_ok is added to the end of
66 * VariableStatData so we can safely omit setting it when the PG version
67 * pg_dbms_stats is loaded onto is out of the conditions.
69 static bool set_acl_ok = false;
72 /* FormData_pg_attribute is modifed at PG11 */
73 #if PG_VERSION_NUM >= 110000
74 #define get_attrs(pgatt_attrs) (&(pgatt_attrs))
76 #define get_attrs(pgatt_attrs) (pgatt_attrs)
79 /* Relation statistics cache entry */
80 typedef struct StatsRelationEntry
82 Oid relid; /* hash key must be at the head */
83 bool valid; /* T if the entry has valid stats */
84 bool invalidated; /* T if this relation has been
86 BlockNumber relpages; /* # of pages as of last ANALYZE */
87 double reltuples; /* # of tuples as of last ANALYZE */
88 BlockNumber relallvisible; /* # of all-visible pages as of last
90 BlockNumber curpages; /* # of pages as of lock/restore */
91 List *col_stats; /* list of StatsColumnEntry, each element
92 of which is pg_statistic record of this
97 * Column statistics cache entry. This is for list item for
98 * StatsRelationEntry.col_stats.
100 typedef struct StatsColumnEntry
108 /* Saved hook functions */
109 get_relation_info_hook_type prev_get_relation_info = NULL;
110 get_attavgwidth_hook_type prev_get_attavgwidth = NULL;
111 get_relation_stats_hook_type prev_get_relation_stats = NULL;
112 get_index_stats_hook_type prev_get_index_stats = NULL;
113 planner_hook_type prev_planner_hook = NULL;
116 #define NSPNAME "dbms_stats"
117 #define RELSTAT_TBLNAME "relation_stats_locked"
118 #define COLSTAT_TBLNAME "column_stats_locked"
120 /* rows_query(oid) RETURNS int4, float4, int4 */
121 static const char *rows_query =
122 "SELECT relpages, reltuples, curpages, relallvisible"
123 " FROM " NSPNAME "." RELSTAT_TBLNAME
125 static SPIPlanPtr rows_plan = NULL;
127 /* tuple_query(oid, int2, bool) RETURNS pg_statistic */
128 static const char *tuple_query =
130 " FROM " NSPNAME "." COLSTAT_TBLNAME
131 " WHERE starelid = $1 "
132 " AND staattnum = $2 "
133 " AND stainherit = $3";
134 static SPIPlanPtr tuple_plan = NULL;
137 static bool pg_dbms_stats_use_locked_stats = true;
139 /* Current nesting depth of SPI calls, used to prevent recursive calls */
140 static int nested_level = 0;
143 * The relation_stats_effective statistic cache is stored in hash table.
144 * rel_invalidated is set true if the hash has invalidated entries.
146 static HTAB *rel_stats;
147 static bool rel_invalidated = false;
150 * The owner of pg_dbms_stats statistic tables.
152 static Oid stats_table_owner = InvalidOid;
153 static char *stats_table_owner_name = "";
155 #define get_pg_statistic(tuple) ((Form_pg_statistic) GETSTRUCT(tuple))
157 PG_FUNCTION_INFO_V1(dbms_stats_merge);
158 PG_FUNCTION_INFO_V1(dbms_stats_invalidate_relation_cache);
159 PG_FUNCTION_INFO_V1(dbms_stats_invalidate_column_cache);
160 PG_FUNCTION_INFO_V1(dbms_stats_is_system_schema);
161 PG_FUNCTION_INFO_V1(dbms_stats_is_system_catalog);
162 PG_FUNCTION_INFO_V1(dbms_stats_anyary_anyary);
163 PG_FUNCTION_INFO_V1(dbms_stats_type_is_analyzable);
164 PG_FUNCTION_INFO_V1(dbms_stats_anyarray_basetype);
166 extern Datum dbms_stats_merge(PG_FUNCTION_ARGS);
167 extern Datum dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS);
168 extern Datum dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS);
169 extern Datum dbms_stats_is_system_schema(PG_FUNCTION_ARGS);
170 extern Datum dbms_stats_is_system_catalog(PG_FUNCTION_ARGS);
171 extern Datum dbms_stats_anyary_anyary(PG_FUNCTION_ARGS);
172 extern Datum dbms_stats_type_is_analyzable(PG_FUNCTION_ARGS);
173 extern Datum dbms_stats_anyarray_basetype(PG_FUNCTION_ARGS);
175 static HeapTuple dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs,
176 TupleDesc tupledesc);
177 static void dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
178 TriggerData *trigdata, HeapTuple *invtup, HeapTuple *rettup);
179 static void dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col);
181 /* Module callbacks */
186 static void dbms_stats_get_relation_info(PlannerInfo *root, Oid relid,
187 bool inhparent, RelOptInfo *rel);
188 static int32 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum);
189 static bool dbms_stats_get_relation_stats(PlannerInfo *root, RangeTblEntry *rte,
190 AttrNumber attnum, VariableStatData *vardata);
191 static bool dbms_stats_get_index_stats(PlannerInfo *root, Oid indexOid,
192 AttrNumber indexattnum, VariableStatData *vardata);
193 static PlannedStmt *dbms_stats_planner(Query *parse, int cursorOptions,
194 ParamListInfo boundParams);
196 /* internal functions */
197 static void get_merged_relation_stats(Oid relid, BlockNumber *pages,
198 double *tuples, double *allvisfrac, bool estimate);
199 static int32 get_merged_avgwidth(Oid relid, AttrNumber attnum);
200 static HeapTuple get_merged_column_stats(Oid relid, AttrNumber attnum,
202 static HeapTuple column_cache_search(Oid relid, AttrNumber attnum,
203 bool inh, bool*negative);
204 static HeapTuple column_cache_enter(Oid relid, int32 attnum, bool inh,
206 static bool execute_plan(SPIPlanPtr *plan, const char *query, Oid relid,
207 const AttrNumber *attnum, bool inh);
208 static void statscache_rel_callback(Datum arg, Oid relid);
209 static void cleanup_invalidated_cache(void);
210 static void init_rel_stats(void);
211 static void init_rel_stats_entry(StatsRelationEntry *entry, Oid relid);
213 /* copied from PG core source tree */
214 static void dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
215 BlockNumber *pages, double *tuples, double *allvisfrac,
216 BlockNumber curpages);
217 static int32 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths);
219 /* Unit test suit functions */
221 extern void test_import(int *passed, int *total);
222 extern void test_dump(int *passed, int *total);
223 extern void test_pg_dbms_stats(int *passed, int *total);
227 * Module load callback
232 /* Execute unit test cases */
238 test_import(&passed, &total);
239 test_dump(&passed, &total);
240 test_pg_dbms_stats(&passed, &total);
242 elog(WARNING, "TOTAL %d/%d passed", passed, total);
246 #ifdef PGDS_HAVE_ACL_OK
249 * Check the PG version this module loaded onto. This aid is required
250 * for binary backward compatibility within a major PG version.
252 int major_version = PG_VERSION_NUM / 100;
253 int minor_version = PG_VERSION_NUM % 100;
255 if (major_version >= 1000 ||
256 (major_version == 906 && minor_version >= 3) ||
257 (major_version == 905 && minor_version >= 7) ||
258 (major_version == 904 && minor_version >= 12) ||
259 (major_version == 903 && minor_version >= 17) ||
260 (major_version == 902 && minor_version >= 21))
265 /* Define custom GUC variables. */
266 DefineCustomBoolVariable("pg_dbms_stats.use_locked_stats",
267 "Enable user defined statistics.",
269 &pg_dbms_stats_use_locked_stats,
277 EmitWarningsOnPlaceholders("pg_dbms_stats");
279 /* Back up old hooks, and install ours. */
280 prev_get_relation_info = get_relation_info_hook;
281 get_relation_info_hook = dbms_stats_get_relation_info;
282 prev_get_attavgwidth = get_attavgwidth_hook;
283 get_attavgwidth_hook = dbms_stats_get_attavgwidth;
284 prev_get_relation_stats = get_relation_stats_hook;
285 get_relation_stats_hook = dbms_stats_get_relation_stats;
286 prev_get_index_stats = get_index_stats_hook;
287 get_index_stats_hook = dbms_stats_get_index_stats;
288 prev_planner_hook = planner_hook;
289 planner_hook = dbms_stats_planner;
291 /* Initialize hash table for statistics caching. */
294 /* Also set up a callback for relcache SI invalidations */
295 CacheRegisterRelcacheCallback(statscache_rel_callback, (Datum) 0);
299 * Module unload callback
304 /* Restore old hooks. */
305 get_relation_info_hook = prev_get_relation_info;
306 get_attavgwidth_hook = prev_get_attavgwidth;
307 get_relation_stats_hook = prev_get_relation_stats;
308 get_index_stats_hook = prev_get_index_stats;
309 planner_hook = prev_planner_hook;
311 /* A function to unregister callback for relcache is NOT provided. */
315 * Function to convert from any array from dbms_stats.anyarray.
318 dbms_stats_anyary_anyary(PG_FUNCTION_ARGS)
320 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
321 if (ARR_NDIM(arr) != 1)
322 elog(ERROR, "array must be one-dimentional.");
324 PG_RETURN_ARRAYTYPE_P(arr);
328 * Function to check if the type can have statistics.
331 dbms_stats_type_is_analyzable(PG_FUNCTION_ARGS)
333 Oid typid = PG_GETARG_OID(0);
336 if (!OidIsValid(typid))
337 PG_RETURN_BOOL(false);
339 get_sort_group_operators(typid, false, false, false,
342 PG_RETURN_BOOL(OidIsValid(eqopr));
346 * Function to get base type of the value of the type dbms_stats.anyarray.
349 dbms_stats_anyarray_basetype(PG_FUNCTION_ARGS)
351 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
352 Oid elemtype = arr->elemtype;
357 if (!OidIsValid(elemtype))
358 elog(ERROR, "invalid base type oid: %u", elemtype);
360 tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(elemtype));
361 if (!HeapTupleIsValid(tp)) /* I trust you. */
362 elog(ERROR, "invalid base type oid: %u", elemtype);
364 typtup = (Form_pg_type) GETSTRUCT(tp);
365 result = (Name) palloc0(NAMEDATALEN);
366 StrNCpy(NameStr(*result), NameStr(typtup->typname), NAMEDATALEN);
369 PG_RETURN_NAME(result);
373 * Find and store the owner of the dummy statistics table.
375 * We will access statistics tables using this owner
378 get_stats_table_owner(void)
382 if (!OidIsValid(stats_table_owner))
384 tp = SearchSysCache2(RELNAMENSP,
385 PointerGetDatum(RELSTAT_TBLNAME),
386 ObjectIdGetDatum(get_namespace_oid(NSPNAME, false)));
387 if (!HeapTupleIsValid(tp))
388 elog(ERROR, "table \"%s.%s\" not found in pg_class",
389 NSPNAME, RELSTAT_TBLNAME);
390 stats_table_owner = ((Form_pg_class) GETSTRUCT(tp))->relowner;
391 if (!OidIsValid(stats_table_owner))
392 elog(ERROR, "owner uid of table \"%s.%s\" is invalid",
393 NSPNAME, RELSTAT_TBLNAME);
396 tp = SearchSysCache1(AUTHOID, ObjectIdGetDatum(stats_table_owner));
397 if (!HeapTupleIsValid(tp))
400 "role id %u for the owner of the relation \"%s.%s\"is invalid",
401 stats_table_owner, NSPNAME, RELSTAT_TBLNAME);
403 /* This will be done once for the session, so not pstrdup. */
404 stats_table_owner_name =
405 strdup(NameStr(((Form_pg_authid) GETSTRUCT(tp))->rolname));
408 return stats_table_owner;
412 * Store heap tuple header into given heap tuple.
415 AssignHeapTuple(HeapTuple htup, HeapTupleHeader header)
417 htup->t_len = HeapTupleHeaderGetDatumLength(header);
418 ItemPointerSetInvalid(&htup->t_self);
419 htup->t_tableOid = InvalidOid;
420 htup->t_data = header;
425 * called by sql function 'dbms_stats.merge', and return the execution result
426 * of the function 'dbms_stats_merge_internal'.
429 dbms_stats_merge(PG_FUNCTION_ARGS)
434 HeapTuple ret = NULL;
436 /* assign HeapTuple of the left statistics data unless null. */
440 AssignHeapTuple(&lhs, PG_GETARG_HEAPTUPLEHEADER(0));
442 /* assign HeapTuple of the right statistics data unless null. */
446 AssignHeapTuple(&rhs, PG_GETARG_HEAPTUPLEHEADER(1));
448 /* fast path for one-side is null */
449 if (lhs.t_data == NULL && rhs.t_data == NULL)
452 /* build a tuple descriptor for our result type */
453 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
454 elog(ERROR, "return type must be a row type");
456 /* merge two statistics tuples into one, and return it */
457 ret = dbms_stats_merge_internal(&lhs, &rhs, tupdesc);
460 PG_RETURN_DATUM(HeapTupleGetDatum(ret));
466 * dbms_stats_merge_internal
467 * merge the dummy statistic (lhs) and the true statistic (rhs), on the basis
468 * of given TupleDesc.
470 * this function doesn't become an error level of ERROR to meet that the
471 * result of the SQL is not affected by the query plan.
474 dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs, TupleDesc tupdesc)
476 Datum values[Natts_pg_statistic];
477 bool nulls[Natts_pg_statistic];
479 Oid atttype = InvalidOid;
483 /* fast path for both-sides are null */
484 if ((lhs == NULL || lhs->t_data == NULL) &&
485 (rhs == NULL || rhs->t_data == NULL))
488 /* fast path for one-side is null */
489 if (lhs == NULL || lhs->t_data == NULL)
491 /* use right tuple */
492 heap_deform_tuple(rhs, tupdesc, values, nulls);
493 for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
495 return NULL; /* check null constraints */
497 else if (rhs == NULL || rhs->t_data == NULL)
500 heap_deform_tuple(lhs, tupdesc, values, nulls);
501 for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
503 return NULL; /* check null constraints */
508 * If the column value of the dummy statistic is not NULL, in the
509 * statistics except the slot, use it. Otherwise we use the column
510 * value of the true statistic.
512 heap_deform_tuple(lhs, tupdesc, values, nulls);
513 for (i = 0; i < Anum_pg_statistic_stakind1 - 1; i++)
517 values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
520 ereport(ELEVEL_BADSTATS,
521 (errmsg("pg_dbms_stats: bad statistics"),
522 errdetail("column \"%s\" should not be null",
523 get_attname(StatisticRelationId,
524 get_attrs(tupdesc->attrs[i])->attnum))));
525 return NULL; /* should not be null */
531 * If the column value of the dummy statistic is not all NULL, in the
532 * statistics the slot, use it. Otherwise we use the column
533 * value of the true statistic.
535 for (; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
539 for (i = Anum_pg_statistic_stakind1 - 1;
540 i < Anum_pg_statistic_stavalues1 + STATISTIC_NUM_SLOTS - 1;
543 values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
544 if (i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1 &&
547 ereport(ELEVEL_BADSTATS,
548 (errmsg("pg_dbms_stats: bad statistics"),
549 errdetail("column \"%s\" should not be null",
550 get_attname(StatisticRelationId,
551 get_attrs(tupdesc->attrs[i])->attnum))));
552 return NULL; /* should not be null */
562 * Verify types to work around for ALTER COLUMN TYPE.
564 * Note: We don't need to retrieve atttype when the attribute doesn't have
565 * neither Most-Common-Value nor Histogram, but we retrieve it always
566 * because it's not usual.
568 relid = DatumGetObjectId(values[0]);
569 attnum = DatumGetInt16(values[1]);
570 atttype = get_atttype(relid, attnum);
571 if (atttype == InvalidOid)
574 (errmsg("pg_dbms_stats: no-longer-existent column"),
575 errdetail("relid \"%d\" or its column whose attnum is \"%d\" might be deleted",
577 errhint("dbms_stats.clean_up_stats() would fix this.")));
580 for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
582 if ((i + 1 == STATISTIC_KIND_MCV ||
583 i + 1 == STATISTIC_KIND_HISTOGRAM) &&
584 !nulls[Anum_pg_statistic_stavalues1 + i - 1])
588 arr = DatumGetArrayTypeP(
589 values[Anum_pg_statistic_stavalues1 + i - 1]);
590 if (arr == NULL || arr->elemtype != atttype)
592 const char *attname = get_attname(relid, attnum);
595 * relid and attnum must be valid here because valid atttype
596 * has been gotten already.
599 ereport(ELEVEL_BADSTATS,
600 (errmsg("pg_dbms_stats: bad column type"),
601 errdetail("type of column \"%s\" has been changed",
603 errhint("need to execute dbms_stats.unlock('%s', '%s')",
604 get_rel_name(relid), attname)));
610 return heap_form_tuple(tupdesc, values, nulls);
614 * dbms_stats_invalidate_relation_cache
615 * Register invalidation of the specified relation's relcache.
617 * CREATE TRIGGER dbms_stats.relation_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
621 dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS)
623 TriggerData *trigdata = (TriggerData *) fcinfo->context;
624 HeapTuple invtup; /* tuple to be invalidated */
625 HeapTuple rettup; /* tuple to be returned */
629 /* make sure it's called as a before/after trigger */
630 dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
633 * assume that position of dbms_stats.relation_stats_locked.relid is head value of
636 value = fastgetattr(invtup, 1, trigdata->tg_relation->rd_att, &isnull);
639 * invalidate prepared statements and force re-planning with pg_dbms_stats.
641 dbms_stats_invalidate_cache_internal((Oid)value, false);
643 PG_RETURN_POINTER(rettup);
647 * dbms_stats_invalidate_column_cache
648 * Register invalidation of the specified relation's relcache.
650 * CREATE TRIGGER dbms_stats.column_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
654 dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS)
656 TriggerData *trigdata = (TriggerData *) fcinfo->context;
657 Form_pg_statistic form;
658 HeapTuple invtup; /* tuple to be invalidated */
659 HeapTuple rettup; /* tuple to be returned */
661 /* make sure it's called as a before/after trigger */
662 dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
665 * assume that both pg_statistic and dbms_stats.column_stats_locked have the same
668 form = get_pg_statistic(invtup);
671 * invalidate prepared statements and force re-planning with pg_dbms_stats.
673 dbms_stats_invalidate_cache_internal(form->starelid, true);
675 PG_RETURN_POINTER(rettup);
679 dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
680 TriggerData *trigdata,
684 /* make sure it's called as a before/after trigger */
685 if (!CALLED_AS_TRIGGER(fcinfo) ||
686 !TRIGGER_FIRED_BEFORE(trigdata->tg_event) ||
687 !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
688 elog(ERROR, "pg_dbms_stats: invalid trigger call");
690 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
693 *rettup = *invtup = trigdata->tg_trigtuple;
695 else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
698 *rettup = *invtup = trigdata->tg_trigtuple;
703 *invtup = trigdata->tg_trigtuple;
704 *rettup = trigdata->tg_newtuple;
709 dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col)
714 * invalidate prepared statements and force re-planning with pg_dbms_stats.
716 rel = try_relation_open(relid, NoLock);
720 rel->rd_rel->relkind == RELKIND_INDEX &&
721 (rel->rd_indextuple == NULL ||
722 heap_attisnull(rel->rd_indextuple, Anum_pg_index_indexprs)))
724 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
725 errmsg("\"%s\" is an index except an index expression",
726 RelationGetRelationName(rel))));
727 if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
729 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
730 errmsg("\"%s\" is a composite type",
731 RelationGetRelationName(rel))));
734 * We need to invalidate relcache of underlying table too, because
735 * CachedPlan mechanism decides to do re-planning when any relcache of
736 * used tables was invalid at EXECUTE.
738 if (rel->rd_rel->relkind == RELKIND_INDEX &&
739 rel->rd_index && OidIsValid(rel->rd_index->indrelid))
740 CacheInvalidateRelcacheByRelid(rel->rd_index->indrelid);
742 CacheInvalidateRelcache(rel);
743 relation_close(rel, NoLock);
748 * dbms_stats_is_system_schema
749 * called by sql function 'dbms_stats.is_system_schema', and return the
750 * result of the function 'dbms_stats_is_system_internal'.
753 dbms_stats_is_system_schema(PG_FUNCTION_ARGS)
759 arg0 = PG_GETARG_TEXT_PP(0);
760 schema_name = text_to_cstring(arg0);
761 result = dbms_stats_is_system_schema_internal(schema_name);
763 PG_FREE_IF_COPY(arg0, 0);
765 PG_RETURN_BOOL(result);
769 * dbms_stats_is_system_schema_internal
770 * return whether the given schema contains any system catalog. Here we
771 * treat dbms_stats objects as system catalogs to avoid infinite loop.
774 dbms_stats_is_system_schema_internal(char *schema_name)
776 Assert(schema_name != NULL);
778 /* if the schema is system_schema, return true */
779 if (strcmp(schema_name, "pg_catalog") == 0 ||
780 strcmp(schema_name, "pg_toast") == 0 ||
781 strcmp(schema_name, "information_schema") == 0 ||
782 strcmp(schema_name, NSPNAME) == 0)
789 * dbms_stats_is_system_catalog
790 * called by sql function 'dbms_stats.is_system_catalog', and return the
791 * result of the function 'dbms_stats_is_system_catalog_internal'.
794 dbms_stats_is_system_catalog(PG_FUNCTION_ARGS)
800 PG_RETURN_BOOL(true);
802 relid = PG_GETARG_OID(0);
803 result = dbms_stats_is_system_catalog_internal(relid);
805 PG_RETURN_BOOL(result);
809 * dbms_stats_is_system_catalog_internal
810 * Check whether the given relation is one of system catalogs.
813 dbms_stats_is_system_catalog_internal(Oid relid)
819 /* relid is InvalidOid */
820 if (!OidIsValid(relid))
823 /* no such relation */
824 rel = try_relation_open(relid, NoLock);
828 /* check by namespace name. */
829 schema_name = get_namespace_name(rel->rd_rel->relnamespace);
830 result = dbms_stats_is_system_schema_internal(schema_name);
831 relation_close(rel, NoLock);
837 * dbms_stats_get_relation_info
838 * Hook function for get_relation_info_hook, which implements post-process of
839 * get_relation_info().
841 * This function is designed on the basis of the fact that only expression
842 * indexes have statistics.
845 dbms_stats_get_relation_info(PlannerInfo *root,
851 double allvisfrac; /* dummy */
854 * Call previously installed hook function regardless to whether
855 * pg_dbms_stats is enabled or not.
857 if (prev_get_relation_info)
858 prev_get_relation_info(root, relid, inhparent, rel);
860 /* If pg_dbms_stats is disabled, there is no more thing to do. */
861 if (!pg_dbms_stats_use_locked_stats)
865 * Adjust stats of table itself, and stats of index
866 * relation_stats_effective as well
870 * Estimate relation size --- unless it's an inheritance parent, in which
871 * case the size will be computed later in set_append_rel_pathlist, and we
872 * must leave it zero for now to avoid bollixing the total_table_pages
876 get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
877 &rel->allvisfrac, true);
881 foreach(lc, rel->indexlist)
884 * Estimate the index size. If it's not a partial index, we lock
885 * the number-of-tuples estimate to equal the parent table; if it
886 * is partial then we have to use the same methods as we would for
887 * a table, except we can be sure that the index is not larger
890 IndexOptInfo *info = (IndexOptInfo *) lfirst(lc);
891 bool estimate = info->indpred != NIL;
893 get_merged_relation_stats(info->indexoid, &info->pages, &info->tuples,
894 &allvisfrac, estimate);
896 if (!estimate || (estimate && info->tuples > rel->tuples))
897 info->tuples = rel->tuples;
902 * dbms_stats_get_attavgwidth
903 * Hook function for get_attavgwidth_hook which replaces get_attavgwidth().
904 * Returning 0 tells caller to use standard routine.
907 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum)
909 if (pg_dbms_stats_use_locked_stats)
911 int32 width = get_merged_avgwidth(relid, attnum);
916 if (prev_get_attavgwidth)
917 return prev_get_attavgwidth(relid, attnum);
923 * We do nothing here, to keep the tuple valid even after examination.
926 FreeHeapTuple(HeapTuple tuple)
932 * dbms_stats_get_relation_stats
933 * Hook function for get_relation_stats_hook which provides custom
934 * per-relation statistics.
935 * Returning false tells caller to use standard (true) statistics.
938 dbms_stats_get_relation_stats(PlannerInfo *root,
941 VariableStatData *vardata)
943 if (pg_dbms_stats_use_locked_stats)
947 tuple = get_merged_column_stats(rte->relid, attnum, rte->inh);
948 vardata->statsTuple = tuple;
949 if (HeapTupleIsValid(tuple))
951 vardata->freefunc = FreeHeapTuple;
953 #ifdef PGDS_HAVE_ACL_OK
955 * set acl_ok if required. See the definition of set_acl_ok for
961 (pg_class_aclcheck(rte->relid, GetUserId(),
962 ACL_SELECT) == ACLCHECK_OK) ||
963 (pg_attribute_aclcheck(rte->relid, attnum, GetUserId(),
964 ACL_SELECT) == ACLCHECK_OK);
971 if (prev_get_relation_stats)
972 return prev_get_relation_stats(root, rte, attnum, vardata);
978 * dbms_stats_get_index_stats
979 * Hook function for get_index_stats_hook which provides custom per-relation
981 * Returning false tells caller to use standard (true) statistics.
984 dbms_stats_get_index_stats(PlannerInfo *root,
986 AttrNumber indexattnum,
987 VariableStatData *vardata)
991 if (!pg_dbms_stats_use_locked_stats)
994 tuple = get_merged_column_stats(indexOid, indexattnum, false);
995 vardata->statsTuple = tuple;
999 vardata->freefunc = FreeHeapTuple;
1001 #ifdef PGDS_HAVE_ACL_OK
1003 * set acl_ok if required. See the definition of set_acl_ok for details.
1008 * XXX: we had to scan the whole the rel array since we got
1009 * only the oid of the index.
1013 /* don't stop by this misassumption */
1014 if (root->simple_rel_array == NULL)
1016 elog(WARNING, "pg_dbms_stats internal error. relation has not been set up. index %d ignored", indexOid);
1021 * scan over simple_rel_array_size to find the owner relation of the
1022 * index with the oid
1024 for (i = 1 ; i < root->simple_rel_array_size ; i++)
1028 foreach (lc, root->simple_rel_array[i]->indexlist)
1030 IndexOptInfo *index = (IndexOptInfo *) lfirst(lc);
1033 if (index->indexoid != indexOid)
1036 /* This relation is the owner of the given index, go ahead */
1037 rte = planner_rt_fetch(index->rel->relid, root);
1039 /* don't stop by this error */
1040 if (rte->rtekind != RTE_RELATION)
1042 elog(WARNING, "pg_dbms_stats internal error. index %d is owned by a non-relation", indexOid);
1047 (pg_class_aclcheck(rte->relid, GetUserId(),
1048 ACL_SELECT) == ACLCHECK_OK);
1057 if (prev_get_index_stats)
1058 return prev_get_index_stats(root, indexOid, indexattnum, vardata);
1065 * dbms_stats_planner
1066 * Hook function for planner_hook which cleans up invalidated statistics.
1068 static PlannedStmt *
1069 dbms_stats_planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
1073 cleanup_invalidated_cache();
1075 if (prev_planner_hook)
1076 ret = (*prev_planner_hook) (parse, cursorOptions, boundParams);
1078 ret = standard_planner(parse, cursorOptions, boundParams);
1080 cleanup_invalidated_cache();
1087 * Extract binary value from given column.
1090 get_binary_datum(int column, bool *isnull)
1092 return SPI_getbinval(SPI_tuptable->vals[0],
1093 SPI_tuptable->tupdesc, column, isnull);
1097 * get_merged_relation_stats
1098 * get the statistics of the table, # of pages and # of rows, by executing
1099 * SELECT against dbms_stats.relation_stats_locked view.
1102 get_merged_relation_stats(Oid relid, BlockNumber *pages, double *tuples,
1103 double *allvisfrac, bool estimate)
1105 StatsRelationEntry *entry;
1109 /* avoid recursive call and system objects */
1110 if (nested_level > 0 || relid < FirstNormalObjectId)
1114 * pg_dbms_stats doesn't handle system catalogs and its internal relation_stats_effective
1116 if (dbms_stats_is_system_catalog_internal(relid))
1120 * First, search from cache. If we have not cached stats for given relid
1121 * yet, initialize newly created entry.
1123 entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
1125 init_rel_stats_entry(entry, relid);
1130 * Valid entry with invalid relpage is a negative cache, which
1131 * eliminates excessive SPI calls below. Negative caches will be
1132 * invalidated again on invalidation of system relation cache, which
1133 * occur on modification of the dummy stats tables
1134 * dbms_stats.relation_stats_locked and column_stats_locked.
1136 if (entry->relpages == InvalidBlockNumber)
1142 * If we don't have valid cache entry, retrieve system stats and dummy
1143 * stats in dbms_stats.relation_stats_locked, then merge them for
1144 * planner use. We also cache curpages value to make plans stable.
1154 * Retrieve per-relation dummy statistics from
1155 * relation_stats_locked table via SPI.
1157 has_dummy = execute_plan(&rows_plan, rows_query, relid, NULL, true);
1160 /* If dummy stats is not found, store negative cache. */
1161 entry->relpages = InvalidBlockNumber;
1166 * Retrieve per-relation system stats from pg_class. We use
1167 * syscache to support indexes
1174 tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
1175 if (!HeapTupleIsValid(tuple))
1176 elog(ERROR, "cache lookup failed for relation %u", relid);
1177 form = (Form_pg_class) GETSTRUCT(tuple);
1179 /* Choose dummy or authentic */
1180 val = get_binary_datum(1, &isnull);
1181 entry->relpages = isnull ? form->relpages :
1182 (BlockNumber) DatumGetInt32(val);
1183 val = get_binary_datum(2, &isnull);
1184 entry->reltuples = isnull ? form->reltuples :
1185 (double) DatumGetFloat4(val);
1186 val = get_binary_datum(3, &isnull);
1187 entry->curpages = isnull ? InvalidBlockNumber :
1188 (BlockNumber) DatumGetInt32(val);
1189 val = get_binary_datum(4, &isnull);
1190 entry->relallvisible = isnull ? form->relallvisible :
1191 (BlockNumber) DatumGetInt32(val);
1193 ReleaseSysCache(tuple);
1195 entry->valid = true;
1207 * If no dummy statistics available for this relation, do nothing then
1208 * return immediately.
1214 /* Tweaking statistics using merged statistics */
1217 *pages = entry->relpages;
1218 *tuples = entry->reltuples;
1223 * Get current number of pages to estimate current number of tuples, based
1224 * on tuple density at the last ANALYZE and current number of pages.
1226 rel = relation_open(relid, NoLock);
1227 rel->rd_rel->relpages = entry->relpages;
1228 rel->rd_rel->reltuples = entry->reltuples;
1229 rel->rd_rel->relallvisible = entry->relallvisible;
1230 dbms_stats_estimate_rel_size(rel, NULL, pages, tuples, allvisfrac,
1232 relation_close(rel, NoLock);
1236 * get_merged_avgwidth
1237 * get average width of the given column by merging dummy and authentic
1241 get_merged_avgwidth(Oid relid, AttrNumber attnum)
1245 if (nested_level > 0 || relid < FirstNormalObjectId)
1246 return 0; /* avoid recursive call and system objects */
1248 if ((tuple = get_merged_column_stats(relid, attnum, false)) == NULL)
1251 return get_pg_statistic(tuple)->stawidth;
1255 * get_merged_column_stats
1256 * returns per-column statistics for given column
1258 * This caches the result to avoid excessive SPI calls for repetitive
1259 * request for every columns many time.
1262 get_merged_column_stats(Oid relid, AttrNumber attnum, bool inh)
1265 HeapTuple statsTuple;
1266 bool negative = false;
1268 if (nested_level > 0 || relid < FirstNormalObjectId)
1269 return NULL; /* avoid recursive call and system objects */
1272 * Return NULL for system catalog, directing the caller to use system
1275 if (dbms_stats_is_system_catalog_internal(relid))
1278 /* Return cached statistics, if any. */
1279 if ((tuple = column_cache_search(relid, attnum, inh, &negative)) != NULL)
1282 /* Obtain system statistics from syscache. */
1283 statsTuple = SearchSysCache3(STATRELATTINH,
1284 ObjectIdGetDatum(relid),
1285 Int16GetDatum(attnum),
1290 * Return system statistics whatever it is if negative cache for this
1291 * column is returned
1293 tuple = heap_copytuple(statsTuple);
1298 * Search for dummy statistics and try merge with system stats.
1303 * Save current context in order to use during SPI is
1306 MemoryContext outer_cxt = CurrentMemoryContext;
1312 /* Obtain dummy statistics for the column using SPI call. */
1314 execute_plan(&tuple_plan, tuple_query, relid, &attnum, inh);
1316 /* Reset to the outer memory context for following steps. */
1317 MemoryContextSwitchTo(outer_cxt);
1321 /* merge the dummy statistics with the system statistics */
1322 tuple = dbms_stats_merge_internal(SPI_tuptable->vals[0],
1324 SPI_tuptable->tupdesc);
1329 /* Cache merged result for subsequent calls. */
1330 tuple = column_cache_enter(relid, attnum, inh, tuple);
1332 /* Return system stats if the merging results in failure. */
1333 if (!HeapTupleIsValid(tuple))
1334 tuple = heap_copytuple(statsTuple);
1347 if (HeapTupleIsValid(statsTuple))
1348 ReleaseSysCache(statsTuple);
1354 * column_cache_search
1355 * Search statistic of the given column from the cache.
1358 column_cache_search(Oid relid, AttrNumber attnum, bool inh, bool *negative)
1360 StatsRelationEntry *entry;
1366 * First, get cached relation stats. If we have not cached relation stats,
1367 * we don't have column stats too.
1369 entry = hash_search(rel_stats, &relid, HASH_FIND, &found);
1374 * We assume that not so many column_stats_effective are defined on one
1375 * relation, so we use simple linear-search here. Hash table would be an
1376 * alternative, but it seems overkill so far.
1378 foreach(lc, entry->col_stats)
1380 StatsColumnEntry *ent = (StatsColumnEntry*) lfirst (lc);
1382 if (ent->attnum != attnum || ent->inh != inh) continue;
1386 /* Retrun NULL for negative cache, with noticing of that.*/
1394 return NULL; /* Not yet registered. */
1398 * Cache a per-column statistics. Storing in CacheMemoryContext, the cached
1399 * statistics will live through the current session, unless dummy statistics or
1400 * table definition have been changed.
1403 column_cache_enter(Oid relid, int32 attnum, bool inh, HeapTuple tuple)
1405 MemoryContext oldcontext;
1406 StatsColumnEntry *newcolent;
1407 StatsRelationEntry *entry;
1410 Assert(tuple == NULL || !heap_attisnull(tuple, 1));
1412 entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
1414 init_rel_stats_entry(entry, relid);
1417 * Adding this column stats to the column stats list of the relation stats
1418 * cache just obtained.
1420 oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
1421 newcolent = (StatsColumnEntry*)palloc(sizeof(StatsColumnEntry));
1422 newcolent->attnum = attnum;
1423 newcolent->inh = inh;
1425 if (HeapTupleIsValid(tuple))
1427 newcolent->negative = false;
1428 newcolent->tuple = heap_copytuple(tuple);
1432 /* Invalid tuple makes a negative cache. */
1433 newcolent->negative = true;
1434 newcolent->tuple = NULL;
1437 entry->col_stats = lappend(entry->col_stats, newcolent);
1438 MemoryContextSwitchTo(oldcontext);
1440 return newcolent->tuple;
1444 * Execute given plan. When given plan is NULL, create new plan from given
1445 * query string, and execute it. This function can be used only for retrieving
1446 * statistics of column_stats_effective and relation_stats_effective, because we assume #, types, and order
1447 * of parameters here.
1450 execute_plan(SPIPlanPtr *plan,
1453 const AttrNumber *attnum,
1457 Oid argtypes[3] = { OIDOID, INT2OID, BOOLOID };
1460 bool nulls[3] = { false, false, false };
1462 int save_sec_context;
1464 /* XXXX: this works for now but should be fixed later.. */
1465 nargs = (attnum ? 3 : 1);
1468 * The dummy statistics table allows access from no one other than its
1469 * owner or superuser.
1471 GetUserIdAndSecContext(&save_userid, &save_sec_context);
1472 SetUserIdAndSecContext(get_stats_table_owner(),
1473 save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
1477 /* Create plan from the query if not yet. */
1480 *plan = SPI_prepare(query, nargs, argtypes);
1483 "pg_dbms_stats: SPI_prepare() failed. result = %d",
1486 SPI_keepplan(*plan);
1489 values[0] = ObjectIdGetDatum(relid);
1490 values[1] = Int16GetDatum(attnum ? *attnum : 0);
1491 values[2] = BoolGetDatum(inh);
1493 ret = SPI_execute_plan(*plan, values, nulls, true, 1);
1497 SetUserIdAndSecContext(save_userid, save_sec_context);
1498 if (geterrcode() == ERRCODE_INSUFFICIENT_PRIVILEGE)
1499 errdetail("dbms_stats could not access the object as the role \"%s\".",
1500 stats_table_owner_name);
1501 errhint("Check your settings of pg_dbms_stats.");
1506 SetUserIdAndSecContext(save_userid, save_sec_context);
1507 if (ret != SPI_OK_SELECT)
1508 elog(ERROR, "pg_dbms_stats: SPI_execute_plan() returned %d", ret);
1510 return SPI_processed > 0;
1514 * statscache_rel_callback
1515 * Relcache inval callback function
1517 * Invalidates cached statistics of the given relid, or all cached statistics
1518 * if relid == InvalidOid. The statsTuple in the hash entries are directly
1519 * passed to planner so we cannot remove them until planner ends. Just mark
1520 * here then cleanup after planner finishes work.
1523 statscache_rel_callback(Datum arg, Oid relid)
1525 StatsRelationEntry *entry;
1527 if (relid != InvalidOid)
1532 * invalidate the entry for the specfied relation. Don't mind if found.
1534 entry = hash_search(rel_stats, &relid, HASH_FIND, &found);
1537 entry->invalidated = true;
1538 rel_invalidated = true;
1543 /* invalidate all the entries of the hash */
1544 HASH_SEQ_STATUS status;
1546 hash_seq_init(&status, rel_stats);
1547 while ((entry = hash_seq_search(&status)) != NULL)
1549 entry->invalidated = true;
1550 rel_invalidated = true;
1556 * cleanup_invalidated_cache()
1557 * Cleanup invalidated stats cache
1559 * removes invalidated cache entries.
1562 cleanup_invalidated_cache(void)
1564 HASH_SEQ_STATUS status;
1565 StatsRelationEntry *entry;
1567 /* Return immediately if nothing to do */
1568 if (!rel_invalidated)
1572 * Reset rel_invalidated first so that we don't lose invalidations that
1573 * happens during this round of cleanup.
1575 rel_invalidated = false;
1577 hash_seq_init(&status, rel_stats);
1578 while ((entry = hash_seq_search(&status)) != NULL)
1582 if (!entry->invalidated)
1585 /* Discard every column statistics */
1586 foreach (lc, entry->col_stats)
1588 StatsColumnEntry *ent = (StatsColumnEntry*) lfirst(lc);
1594 list_free(entry->col_stats);
1596 /* Finally remove the hash entry. */
1597 hash_search(rel_stats, &entry->relid, HASH_REMOVE, NULL);
1602 * Initialize hash table for per-relation statistics.
1605 init_rel_stats(void)
1610 /* Prevent double initialization. */
1611 if (rel_stats != NULL)
1614 MemSet(&ctl, 0, sizeof(ctl));
1615 ctl.keysize = sizeof(Oid);
1616 ctl.entrysize = sizeof(StatsRelationEntry);
1617 ctl.hash = oid_hash;
1618 ctl.hcxt = CacheMemoryContext;
1619 hash = hash_create("dbms_stats relation statistics cache",
1621 &ctl, HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION);
1627 * Initialize newly added cache entry so that it represents an invalid cache
1628 * entry for given relid.
1631 init_rel_stats_entry(StatsRelationEntry *entry, Oid relid)
1633 entry->relid = relid;
1634 entry->valid = false;
1635 entry->invalidated = false;
1636 entry->relpages = InvalidBlockNumber;
1637 entry->reltuples = 0.0;
1638 entry->relallvisible = InvalidBlockNumber;
1639 entry->curpages = InvalidBlockNumber;
1640 entry->col_stats = NIL;
1644 * dbms_stats_estimate_rel_size - estimate # pages and # tuples in a table or
1647 * We also estimate the fraction of the pages that are marked all-visible in
1648 * the visibility map, for use in estimation of index-only scans.
1650 * If attr_widths isn't NULL, it points to the zero-index entry of the
1651 * relation's attr_widths[] cache; we fill this in if we have need to compute
1652 * the attribute widths for estimation purposes.
1654 * Note: This function is copied from plancat.c in core source tree of version
1655 * 9.2, and customized for pg_dbms_stats. Changes from original one are:
1656 * - rename by prefixing dbms_stats_
1657 * - add 3 parameters (relpages, reltuples, curpage) to pass dummy curpage
1659 * - Get current # of pages only when supplied curpages is InvalidBlockNumber
1660 * - get fraction of all-visible-pages
1663 dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
1664 BlockNumber *pages, double *tuples,
1665 double *allvisfrac, BlockNumber curpages)
1667 BlockNumber relpages;
1669 BlockNumber relallvisible;
1672 switch (rel->rd_rel->relkind)
1674 case RELKIND_RELATION:
1676 #if PG_VERSION_NUM >= 90300
1677 case RELKIND_MATVIEW:
1679 case RELKIND_TOASTVALUE:
1680 /* it has storage, ok to call the smgr */
1681 if (curpages == InvalidBlockNumber)
1682 curpages = RelationGetNumberOfBlocks(rel);
1685 * HACK: if the relation has never yet been vacuumed, use a
1686 * minimum size estimate of 10 pages. The idea here is to avoid
1687 * assuming a newly-created table is really small, even if it
1688 * currently is, because that may not be true once some data gets
1689 * loaded into it. Once a vacuum or analyze cycle has been done
1690 * on it, it's more reasonable to believe the size is somewhat
1693 * (Note that this is only an issue if the plan gets cached and
1694 * used again after the table has been filled. What we're trying
1695 * to avoid is using a nestloop-type plan on a table that has
1696 * grown substantially since the plan was made. Normally,
1697 * autovacuum/autoanalyze will occur once enough inserts have
1698 * happened and cause cached-plan invalidation; but that doesn't
1699 * happen instantaneously, and it won't happen at all for cases
1700 * such as temporary tables.)
1702 * We approximate "never vacuumed" by "has relpages = 0", which
1703 * means this will also fire on genuinely empty relation_stats_effective. Not
1704 * great, but fortunately that's a seldom-seen case in the real
1705 * world, and it shouldn't degrade the quality of the plan too
1706 * much anyway to err in this direction.
1708 * There are two exceptions wherein we don't apply this heuristic.
1709 * One is if the table has inheritance children. Totally empty
1710 * parent tables are quite common, so we should be willing to
1711 * believe that they are empty. Also, we don't apply the 10-page
1712 * minimum to indexes.
1714 if (curpages < 10 &&
1715 rel->rd_rel->relpages == 0 &&
1716 !rel->rd_rel->relhassubclass &&
1717 rel->rd_rel->relkind != RELKIND_INDEX)
1720 /* report estimated # pages */
1722 /* quick exit if rel is clearly empty */
1729 /* coerce values in pg_class to more desirable types */
1730 relpages = (BlockNumber) rel->rd_rel->relpages;
1731 reltuples = (double) rel->rd_rel->reltuples;
1732 relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
1735 * If it's an index, discount the metapage while estimating the
1736 * number of tuples. This is a kluge because it assumes more than
1737 * it ought to about index structure. Currently it's OK for
1738 * btree, hash, and GIN indexes but suspect for GiST indexes.
1740 if (rel->rd_rel->relkind == RELKIND_INDEX &&
1747 /* estimate number of tuples from previous tuple density */
1749 density = reltuples / (double) relpages;
1753 * When we have no data because the relation was truncated,
1754 * estimate tuple width from attribute datatypes. We assume
1755 * here that the pages are completely full, which is OK for
1756 * tables (since they've presumably not been VACUUMed yet) but
1757 * is probably an overestimate for indexes. Fortunately
1758 * get_relation_info() can clamp the overestimate to the
1759 * parent table's size.
1761 * Note: this code intentionally disregards alignment
1762 * considerations, because (a) that would be gilding the lily
1763 * considering how crude the estimate is, and (b) it creates
1764 * platform dependencies in the default plans which are kind
1765 * of a headache for regression testing.
1769 tuple_width = dbms_stats_get_rel_data_width(rel, attr_widths);
1770 tuple_width += sizeof(HeapTupleHeaderData);
1771 tuple_width += sizeof(ItemPointerData);
1772 /* note: integer division is intentional here */
1773 density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width;
1775 *tuples = rint(density * (double) curpages);
1778 * We use relallvisible as-is, rather than scaling it up like we
1779 * do for the pages and tuples counts, on the theory that any
1780 * pages added since the last VACUUM are most likely not marked
1781 * all-visible. But costsize.c wants it converted to a fraction.
1783 if (relallvisible == 0 || curpages <= 0)
1785 else if ((double) relallvisible >= curpages)
1788 *allvisfrac = (double) relallvisible / curpages;
1790 case RELKIND_SEQUENCE:
1791 /* Sequences always have a known size */
1796 case RELKIND_FOREIGN_TABLE:
1797 /* Just use whatever's in pg_class */
1798 *pages = rel->rd_rel->relpages;
1799 *tuples = rel->rd_rel->reltuples;
1803 /* else it has no disk storage; probably shouldn't get here? */
1812 * dbms_stats_get_rel_data_width
1814 * Estimate the average width of (the data part of) the relation's tuples.
1816 * If attr_widths isn't NULL, it points to the zero-index entry of the
1817 * relation's attr_widths[] cache; use and update that cache as appropriate.
1819 * Currently we ignore dropped column_stats_effective. Ideally those should be included
1820 * in the result, but we haven't got any way to get info about them; and
1821 * since they might be mostly NULLs, treating them as zero-width is not
1822 * necessarily the wrong thing anyway.
1824 * Note: This function is copied from plancat.c in core source tree of version
1825 * 9.2, and just renamed.
1828 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths)
1830 int32 tuple_width = 0;
1833 for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
1835 Form_pg_attribute att = get_attrs(rel->rd_att->attrs[i - 1]);
1838 if (att->attisdropped)
1841 /* use previously cached data, if any */
1842 if (attr_widths != NULL && attr_widths[i] > 0)
1844 tuple_width += attr_widths[i];
1848 /* This should match set_rel_width() in costsize.c */
1849 item_width = get_attavgwidth(RelationGetRelid(rel), i);
1850 if (item_width <= 0)
1852 item_width = get_typavgwidth(att->atttypid, att->atttypmod);
1853 Assert(item_width > 0);
1855 if (attr_widths != NULL)
1856 attr_widths[i] = item_width;
1857 tuple_width += item_width;
1864 void test_pg_dbms_stats(int *passed, int *total);
1865 static void test_init_rel_stats(int *passed, int *total);
1866 static void test_init_rel_stats_entry(int *passed, int *total);
1869 test_pg_dbms_stats(int *passed, int *total)
1871 int local_passed = 0;
1872 int local_total = 0;
1874 elog(WARNING, "==========");
1877 test_init_rel_stats(&local_passed, &local_total);
1878 test_init_rel_stats_entry(&local_passed, &local_total);
1880 elog(WARNING, "%s %d/%d passed", __FUNCTION__, local_passed, local_total);
1881 *passed += local_passed;
1882 *total += local_total;
1886 test_init_rel_stats_entry(int *passed, int *total)
1889 StatsRelationEntry entry;
1895 init_rel_stats_entry(&entry, 1234);
1896 if (entry.relid == 1234 &&
1897 entry.valid == false &&
1898 entry.relpages == InvalidBlockNumber &&
1899 entry.reltuples == 0 &&
1900 entry.relallvisible == InvalidBlockNumber &&
1901 entry.curpages == InvalidBlockNumber &&
1902 entry.col_stats == NIL)
1904 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1908 elog(WARNING, "%s-%d failed: initialized", __FUNCTION__, caseno);
1914 test_init_rel_stats(int *passed, int *total)
1917 static HTAB *org_rel_stats;
1925 if (rel_stats != NULL)
1927 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1931 elog(WARNING, "%s-%d failed: rel_stats is NULL", __FUNCTION__, caseno);
1938 org_rel_stats = rel_stats;
1940 if (org_rel_stats == rel_stats)
1942 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1946 elog(WARNING, "%s-%d failed: rel_stats changed from %p to %p",
1947 __FUNCTION__, caseno, org_rel_stats, rel_stats);