4 * Copyright (c) 2009-2014, 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/transam.h"
11 #include "catalog/pg_statistic.h"
12 #include "catalog/pg_type.h"
13 #include "commands/trigger.h"
14 #include "executor/spi.h"
16 #include "optimizer/plancat.h"
17 #include "storage/bufmgr.h"
18 #include "utils/builtins.h"
19 #include "utils/guc.h"
20 #include "utils/inval.h"
21 #include "utils/lsyscache.h"
22 #include "utils/selfuncs.h"
23 #include "utils/syscache.h"
24 #if PG_VERSION_NUM >= 90200
25 #include "utils/rel.h"
27 #if PG_VERSION_NUM >= 90300
28 #include "access/htup_details.h"
29 #include "utils/catcache.h"
32 #include "pg_dbms_stats.h"
36 /* Error levels used by pg_dbms_stats */
37 #define ELEVEL_DEBUG DEBUG3 /* log level for debug information */
38 #define ELEVEL_BADSTATS LOG /* log level for invalid statistics */
40 #define MAX_REL_CACHE 50 /* expected max # of rel stats entries */
42 /* Relation statistics cache entry */
43 typedef struct StatsRelationEntry
45 Oid relid; /* hash key must be at the head */
47 bool valid; /* T if the entry has valid stats */
49 BlockNumber relpages; /* # of pages as of last ANALYZE */
50 double reltuples; /* # of tuples as of last ANALYZE */
51 BlockNumber relallvisible; /* # of all-visible pages as of last
53 BlockNumber curpages; /* # of pages as of lock/restore */
55 List *col_stats; /* list of StatsColumnEntry, each element
56 of which is pg_statistic record of this
61 * Column statistics cache entry. This is for list item for
62 * StatsRelationEntry.col_stats.
64 typedef struct StatsColumnEntry
71 /* Saved hook functions */
72 get_relation_info_hook_type prev_get_relation_info = NULL;
73 get_attavgwidth_hook_type prev_get_attavgwidth = NULL;
74 get_relation_stats_hook_type prev_get_relation_stats = NULL;
75 get_index_stats_hook_type prev_get_index_stats = NULL;
77 /* rows_query(oid) RETURNS int4, float4, int4 */
78 static const char *rows_query =
79 "SELECT relpages, reltuples, curpages"
80 #if PG_VERSION_NUM >= 90200
83 " FROM dbms_stats.relation_stats_locked"
85 static SPIPlanPtr rows_plan = NULL;
87 /* tuple_query(oid, int2, bool) RETURNS pg_statistic */
88 static const char *tuple_query =
90 " FROM dbms_stats.column_stats_locked "
91 " WHERE starelid = $1 "
92 " AND staattnum = $2 "
93 " AND stainherit = $3";
94 static SPIPlanPtr tuple_plan = NULL;
97 static bool pg_dbms_stats_use_locked_stats = true;
99 /* Current nesting depth of SPI calls, used to prevent recursive calls */
100 static int nested_level = 0;
103 * The relation_stats_effective statistic cache is stored in hash table.
105 static HTAB *rel_stats;
107 #define get_pg_statistic(tuple) ((Form_pg_statistic) GETSTRUCT(tuple))
109 PG_FUNCTION_INFO_V1(dbms_stats_merge);
110 PG_FUNCTION_INFO_V1(dbms_stats_invalidate_relation_cache);
111 PG_FUNCTION_INFO_V1(dbms_stats_invalidate_column_cache);
112 PG_FUNCTION_INFO_V1(dbms_stats_is_system_schema);
113 PG_FUNCTION_INFO_V1(dbms_stats_is_system_catalog);
115 extern Datum dbms_stats_merge(PG_FUNCTION_ARGS);
116 extern Datum dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS);
117 extern Datum dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS);
118 extern Datum dbms_stats_is_system_schema(PG_FUNCTION_ARGS);
119 extern Datum dbms_stats_is_system_catalog(PG_FUNCTION_ARGS);
121 static HeapTuple dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs,
122 TupleDesc tupledesc);
123 static void dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
124 TriggerData *trigdata, HeapTuple *invtup, HeapTuple *rettup);
125 static void dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col);
127 /* Module callbacks */
131 static void dbms_stats_get_relation_info(PlannerInfo *root, Oid relid,
132 bool inhparent, RelOptInfo *rel);
133 static int32 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum);
134 static bool dbms_stats_get_relation_stats(PlannerInfo *root, RangeTblEntry *rte,
135 AttrNumber attnum, VariableStatData *vardata);
136 static bool dbms_stats_get_index_stats(PlannerInfo *root, Oid indexOid,
137 AttrNumber indexattnum, VariableStatData *vardata);
139 static void get_merged_relation_stats(Oid relid, BlockNumber *pages,
140 double *tuples, double *allvisfrac, bool estimate);
141 static int32 get_merged_avgwidth(Oid relid, AttrNumber attnum);
142 static HeapTuple get_merged_column_stats(Oid relid, AttrNumber attnum,
144 static HeapTuple column_cache_search(Oid relid, AttrNumber attnum,
145 bool inh, bool*negative);
146 static HeapTuple column_cache_enter(Oid relid, int32 attnum, HeapTuple tuple);
147 static bool execute_plan(SPIPlanPtr *plan, const char *query, Oid relid,
148 const AttrNumber *attnum, bool inh);
149 static void StatsCacheRelCallback(Datum arg, Oid relid);
150 static void init_rel_stats(void);
151 static void init_rel_stats_entry(StatsRelationEntry *entry, Oid relid);
152 /* copied from PG core source tree */
153 static void dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
154 BlockNumber *pages, double *tuples, double *allvisfrac,
155 BlockNumber curpages);
156 static int32 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths);
158 /* Unit test suit functions */
160 extern void test_import(int *passed, int *total);
161 extern void test_dump(int *passed, int *total);
162 extern void test_pg_dbms_stats(int *passed, int *total);
166 * Module load callback
171 /* Execute unit test cases */
177 test_import(&passed, &total);
178 test_dump(&passed, &total);
179 test_pg_dbms_stats(&passed, &total);
181 elog(WARNING, "TOTAL %d/%d passed", passed, total);
185 /* Define custom GUC variables. */
186 DefineCustomBoolVariable("pg_dbms_stats.use_locked_stats",
187 "Enable user defined statistics.",
189 &pg_dbms_stats_use_locked_stats,
197 EmitWarningsOnPlaceholders("pg_dbms_stats");
199 /* Back up old hooks, and install ours. */
200 prev_get_relation_info = get_relation_info_hook;
201 get_relation_info_hook = dbms_stats_get_relation_info;
202 prev_get_attavgwidth = get_attavgwidth_hook;
203 get_attavgwidth_hook = dbms_stats_get_attavgwidth;
204 prev_get_relation_stats = get_relation_stats_hook;
205 get_relation_stats_hook = dbms_stats_get_relation_stats;
206 prev_get_index_stats = get_index_stats_hook;
207 get_index_stats_hook = dbms_stats_get_index_stats;
209 /* Initialize hash table for statistics caching. */
212 /* Also set up a callback for relcache SI invalidations */
213 CacheRegisterRelcacheCallback(StatsCacheRelCallback, (Datum) 0);
217 * Module unload callback
222 /* Restore old hooks. */
223 get_relation_info_hook = prev_get_relation_info;
224 get_attavgwidth_hook = prev_get_attavgwidth;
225 get_relation_stats_hook = prev_get_relation_stats;
226 get_index_stats_hook = prev_get_index_stats;
228 /* A function to unregister callback for relcache is NOT provided. */
232 * Store heap tuple header into given heap tuple.
235 AssignHeapTuple(HeapTuple htup, HeapTupleHeader header)
237 htup->t_len = HeapTupleHeaderGetDatumLength(header);
238 ItemPointerSetInvalid(&htup->t_self);
239 htup->t_tableOid = InvalidOid;
240 htup->t_data = header;
245 * called by sql function 'dbms_stats.merge', and return the execution result
246 * of the function 'dbms_stats_merge_internal'.
249 dbms_stats_merge(PG_FUNCTION_ARGS)
254 HeapTuple ret = NULL;
256 /* assign HeapTuple of the left statistics data unless null. */
260 AssignHeapTuple(&lhs, PG_GETARG_HEAPTUPLEHEADER(0));
262 /* assign HeapTuple of the right statistics data unless null. */
266 AssignHeapTuple(&rhs, PG_GETARG_HEAPTUPLEHEADER(1));
268 /* fast path for one-side is null */
269 if (lhs.t_data == NULL && rhs.t_data == NULL)
272 /* build a tuple descriptor for our result type */
273 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
274 elog(ERROR, "return type must be a row type");
276 /* merge two statistics tuples into one, and return it */
277 ret = dbms_stats_merge_internal(&lhs, &rhs, tupdesc);
280 PG_RETURN_DATUM(HeapTupleGetDatum(ret));
286 * dbms_stats_merge_internal
287 * merge the dummy statistic (lhs) and the true statistic (rhs), on the basis
288 * of given TupleDesc.
290 * this function doesn't become an error level of ERROR to meet that the
291 * result of the SQL is not affected by the query plan.
294 dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs, TupleDesc tupdesc)
296 Datum values[Natts_pg_statistic];
297 bool nulls[Natts_pg_statistic];
299 Oid atttype = InvalidOid;
303 /* fast path for both-sides are null */
304 if ((lhs == NULL || lhs->t_data == NULL) &&
305 (rhs == NULL || rhs->t_data == NULL))
308 /* fast path for one-side is null */
309 if (lhs == NULL || lhs->t_data == NULL)
311 /* use right tuple */
312 heap_deform_tuple(rhs, tupdesc, values, nulls);
313 for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
315 return NULL; /* check null constraints */
317 else if (rhs == NULL || rhs->t_data == NULL)
320 heap_deform_tuple(lhs, tupdesc, values, nulls);
321 for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
323 return NULL; /* check null constraints */
328 * If the column value of the dummy statistic is not NULL, in the
329 * statistics except the slot, use it. Otherwise we use the column
330 * value of the true statistic.
332 heap_deform_tuple(lhs, tupdesc, values, nulls);
333 for (i = 0; i < Anum_pg_statistic_stakind1 - 1; i++)
337 values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
340 ereport(ELEVEL_BADSTATS,
341 (errmsg("pg_dbms_stats: bad statistics"),
342 errdetail("column \"%s\" should not be null",
343 get_attname(StatisticRelationId,
344 tupdesc->attrs[i]->attnum))));
345 return NULL; /* should not be null */
351 * If the column value of the dummy statistic is not all NULL, in the
352 * statistics the slot, use it. Otherwise we use the column
353 * value of the true statistic.
355 for (; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
359 for (i = Anum_pg_statistic_stakind1 - 1;
360 i < Anum_pg_statistic_stavalues1 + STATISTIC_NUM_SLOTS - 1;
363 values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
364 if (i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1 &&
367 ereport(ELEVEL_BADSTATS,
368 (errmsg("pg_dbms_stats: bad statistics"),
369 errdetail("column \"%s\" should not be null",
370 get_attname(StatisticRelationId,
371 tupdesc->attrs[i]->attnum))));
372 return NULL; /* should not be null */
382 * Verify types to work around for ALTER COLUMN TYPE.
384 * Note: We don't need to retrieve atttype when the attribute doesn't have
385 * neither Most-Common-Value nor Histogram, but we retrieve it always
386 * because it's not usual.
388 relid = DatumGetObjectId(values[0]);
389 attnum = DatumGetInt16(values[1]);
390 atttype = get_atttype(relid, attnum);
391 if (atttype == InvalidOid)
394 (errmsg("pg_dbms_stats: not exist column"),
395 errdetail("relid \"%d\" or it's column number \"%d\" does not exist",
397 errhint("need to execute clean_up_stats()")));
400 for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
402 if ((i + 1 == STATISTIC_KIND_MCV ||
403 i + 1 == STATISTIC_KIND_HISTOGRAM) &&
404 !nulls[Anum_pg_statistic_stavalues1 + i - 1])
408 arr = DatumGetArrayTypeP(
409 values[Anum_pg_statistic_stavalues1 + i - 1]);
410 if (arr == NULL || arr->elemtype != atttype)
412 const char *attname = get_attname(relid, attnum);
415 * relid and attnum must be valid here because valid atttype
416 * has been gotten already.
419 ereport(ELEVEL_BADSTATS,
420 (errmsg("pg_dbms_stats: bad column type"),
421 errdetail("type of column \"%s\" has been changed",
423 errhint("need to execute dbms_stats.unlock('%s', '%s')",
424 get_rel_name(relid), attname)));
430 return heap_form_tuple(tupdesc, values, nulls);
434 * dbms_stats_invalidate_relation_cache
435 * Register invalidation of the specified relation's relcache.
437 * CREATE TRIGGER dbms_stats.relation_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
441 dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS)
443 TriggerData *trigdata = (TriggerData *) fcinfo->context;
444 HeapTuple invtup; /* tuple to be invalidated */
445 HeapTuple rettup; /* tuple to be returned */
449 /* make sure it's called as a before/after trigger */
450 dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
453 * assume that position of dbms_stats.relation_stats_locked.relid is head value of
456 value = fastgetattr(invtup, 1, trigdata->tg_relation->rd_att, &isnull);
459 * invalidate prepared statements and force re-planning with pg_dbms_stats.
461 dbms_stats_invalidate_cache_internal((Oid)value, false);
463 PG_RETURN_POINTER(rettup);
467 * dbms_stats_invalidate_column_cache
468 * Register invalidation of the specified relation's relcache.
470 * CREATE TRIGGER dbms_stats.column_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
474 dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS)
476 TriggerData *trigdata = (TriggerData *) fcinfo->context;
477 Form_pg_statistic form;
478 HeapTuple invtup; /* tuple to be invalidated */
479 HeapTuple rettup; /* tuple to be returned */
481 /* make sure it's called as a before/after trigger */
482 dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
485 * assume that both pg_statistic and dbms_stats.column_stats_locked have the same
488 form = get_pg_statistic(invtup);
491 * invalidate prepared statements and force re-planning with pg_dbms_stats.
493 dbms_stats_invalidate_cache_internal(form->starelid, true);
495 PG_RETURN_POINTER(rettup);
499 dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
500 TriggerData *trigdata,
504 /* make sure it's called as a before/after trigger */
505 if (!CALLED_AS_TRIGGER(fcinfo) ||
506 !TRIGGER_FIRED_BEFORE(trigdata->tg_event) ||
507 !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
508 elog(ERROR, "pg_dbms_stats: invalid trigger call");
510 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
513 *rettup = *invtup = trigdata->tg_trigtuple;
515 else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
518 *rettup = *invtup = trigdata->tg_trigtuple;
523 *invtup = trigdata->tg_trigtuple;
524 *rettup = trigdata->tg_newtuple;
529 dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col)
534 * invalidate prepared statements and force re-planning with pg_dbms_stats.
536 rel = try_relation_open(relid, NoLock);
540 rel->rd_rel->relkind == RELKIND_INDEX &&
541 (rel->rd_indextuple == NULL ||
542 heap_attisnull(rel->rd_indextuple, Anum_pg_index_indexprs)))
544 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
545 errmsg("\"%s\" is an index except an index expression",
546 RelationGetRelationName(rel))));
547 if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
549 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
550 errmsg("\"%s\" is a composite type",
551 RelationGetRelationName(rel))));
554 * We need to invalidate relcache of underlying table too, because
555 * CachedPlan mechanism decides to do re-planning when any relcache of
556 * used tables was invalid at EXECUTE.
558 if (rel->rd_rel->relkind == RELKIND_INDEX &&
559 rel->rd_index && OidIsValid(rel->rd_index->indrelid))
560 CacheInvalidateRelcacheByRelid(rel->rd_index->indrelid);
562 CacheInvalidateRelcache(rel);
563 relation_close(rel, NoLock);
568 * dbms_stats_is_system_schema
569 * called by sql function 'dbms_stats.is_system_schema', and return the
570 * result of the function 'dbms_stats_is_system_internal'.
573 dbms_stats_is_system_schema(PG_FUNCTION_ARGS)
579 arg0 = PG_GETARG_TEXT_PP(0);
580 schema_name = text_to_cstring(arg0);
581 result = dbms_stats_is_system_schema_internal(schema_name);
583 PG_FREE_IF_COPY(arg0, 0);
585 PG_RETURN_BOOL(result);
589 * dbms_stats_is_system_schema_internal
590 * return whether the given schema contains any system catalog. Here we
591 * treat dbms_stats objects as system catalogs to avoid infinite loop.
594 dbms_stats_is_system_schema_internal(char *schema_name)
596 Assert(schema_name != NULL);
598 /* if the schema is system_schema, return true */
599 if (strcmp(schema_name, "pg_catalog") == 0 ||
600 strcmp(schema_name, "pg_toast") == 0 ||
601 strcmp(schema_name, "information_schema") == 0 ||
602 strcmp(schema_name, "dbms_stats") == 0)
609 * dbms_stats_is_system_catalog
610 * called by sql function 'dbms_stats.is_system_catalog', and return the
611 * result of the function 'dbms_stats_is_system_catalog_internal'.
614 dbms_stats_is_system_catalog(PG_FUNCTION_ARGS)
620 PG_RETURN_BOOL(true);
622 relid = PG_GETARG_OID(0);
623 result = dbms_stats_is_system_catalog_internal(relid);
625 PG_RETURN_BOOL(result);
629 * dbms_stats_is_system_catalog_internal
630 * Check whether the given relation is one of system catalogs.
633 dbms_stats_is_system_catalog_internal(Oid relid)
639 /* relid is InvalidOid */
640 if (!OidIsValid(relid))
643 /* no such relation */
644 rel = try_relation_open(relid, NoLock);
648 /* check by namespace name. */
649 schema_name = get_namespace_name(rel->rd_rel->relnamespace);
650 result = dbms_stats_is_system_schema_internal(schema_name);
651 relation_close(rel, NoLock);
657 * dbms_stats_get_relation_info
658 * Hook function for get_relation_info_hook, which implements post-process of
659 * get_relation_info().
661 * This function is designed on the basis of the fact that only expression
662 * indexes have statistics.
665 dbms_stats_get_relation_info(PlannerInfo *root,
671 double allvisfrac; /* dummy */
674 * Call previously installed hook function regardless to whether
675 * pg_dbms_stats is enabled or not.
677 if (prev_get_relation_info)
678 prev_get_relation_info(root, relid, inhparent, rel);
680 /* If pg_dbms_stats is disabled, there is no more thing to do. */
681 if (!pg_dbms_stats_use_locked_stats)
685 * Adjust stats of table itself, and stats of index
686 * relation_stats_effective as well
690 * Estimate relation size --- unless it's an inheritance parent, in which
691 * case the size will be computed later in set_append_rel_pathlist, and we
692 * must leave it zero for now to avoid bollixing the total_table_pages
697 #if PG_VERSION_NUM >= 90200
698 get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
699 &rel->allvisfrac, true);
701 get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
708 foreach(lc, rel->indexlist)
711 * Estimate the index size. If it's not a partial index, we lock
712 * the number-of-tuples estimate to equal the parent table; if it
713 * is partial then we have to use the same methods as we would for
714 * a table, except we can be sure that the index is not larger
717 IndexOptInfo *info = (IndexOptInfo *) lfirst(lc);
718 bool estimate = info->indpred != NIL;
720 get_merged_relation_stats(info->indexoid, &info->pages, &info->tuples,
721 &allvisfrac, estimate);
723 if (!estimate || (estimate && info->tuples > rel->tuples))
724 info->tuples = rel->tuples;
729 * dbms_stats_get_attavgwidth
730 * Hook function for get_attavgwidth_hook which replaces get_attavgwidth().
731 * Returning 0 tells caller to use standard routine.
734 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum)
736 if (pg_dbms_stats_use_locked_stats)
738 int32 width = get_merged_avgwidth(relid, attnum);
743 if (prev_get_attavgwidth)
744 return prev_get_attavgwidth(relid, attnum);
750 * We do nothing here, to keep the tuple valid even after examination.
753 FreeHeapTuple(HeapTuple tuple)
759 * dbms_stats_get_relation_stats
760 * Hook function for get_relation_stats_hook which provides custom
761 * per-relation statistics.
762 * Returning false tells caller to use standard (true) statistics.
765 dbms_stats_get_relation_stats(PlannerInfo *root,
768 VariableStatData *vardata)
770 if (pg_dbms_stats_use_locked_stats)
774 tuple = get_merged_column_stats(rte->relid, attnum, rte->inh);
777 vardata->statsTuple = tuple;
778 vardata->freefunc = FreeHeapTuple;
783 if (prev_get_relation_stats)
784 return prev_get_relation_stats(root, rte, attnum, vardata);
790 * dbms_stats_get_index_stats
791 * Hook function for get_index_stats_hook which provides custom per-relation
793 * Returning false tells caller to use standard (true) statistics.
796 dbms_stats_get_index_stats(PlannerInfo *root,
798 AttrNumber indexattnum,
799 VariableStatData *vardata)
801 if (pg_dbms_stats_use_locked_stats)
805 tuple = get_merged_column_stats(indexOid, indexattnum, false);
808 vardata->statsTuple = tuple;
809 vardata->freefunc = FreeHeapTuple;
814 if (prev_get_index_stats)
815 return prev_get_index_stats(root, indexOid, indexattnum, vardata);
821 * Extract binary value from given column.
824 get_binary_datum(int column, bool *isnull)
826 return SPI_getbinval(SPI_tuptable->vals[0],
827 SPI_tuptable->tupdesc, column, isnull);
831 * get_merged_relation_stats
832 * get the statistics of the table, # of pages and # of rows, by executing
833 * SELECT against dbms_stats.relation_stats_locked view.
836 get_merged_relation_stats(Oid relid, BlockNumber *pages, double *tuples,
837 double *allvisfrac, bool estimate)
839 StatsRelationEntry *entry;
843 /* avoid recursive call and system objects */
844 if (nested_level > 0 || relid < FirstNormalObjectId)
848 * pg_dbms_stats doesn't handle system catalogs and its internal relation_stats_effective
850 if (dbms_stats_is_system_catalog_internal(relid))
854 * First, search from cache. If we have not cached stats for given relid
855 * yet, initialize newly created entry.
857 entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
859 init_rel_stats_entry(entry, relid);
864 * Valid entry with invalid relpage is a negative cache, which
865 * eliminates excessive SPI calls below. Negative caches will be
866 * invalidated again on invalidation of system relation cache, which
867 * occur on modification of the dummy stats tables
868 * dbms_stats._relation_stats_locked and _column_stats_locked.
870 if (entry->relpages == InvalidBlockNumber)
876 * If we don't have valid cache entry, retrieve system stats and dummy
877 * stats in dbms_stats.relation_stats_locked, then merge them for
878 * planner use. We also cache curpages value to make plans stable.
889 * Retrieve per-relation dummy statistics from
890 * relation_stats_locked table via SPI.
892 has_dummy = execute_plan(&rows_plan, rows_query, relid, NULL, true);
895 /* If dummy stats is not found, store negative cache. */
896 entry->relpages = InvalidBlockNumber;
901 * Retrieve per-relation system stats from pg_class. We use
902 * syscache to support indexes
909 tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
910 if (!HeapTupleIsValid(tuple))
911 elog(ERROR, "cache lookup failed for relation %u", relid);
912 form = (Form_pg_class) GETSTRUCT(tuple);
914 /* Choose dummy or authentic */
915 val = get_binary_datum(1, &isnull);
916 entry->relpages = isnull ? form->relpages :
917 (BlockNumber) DatumGetInt32(val);
918 val = get_binary_datum(2, &isnull);
919 entry->reltuples = isnull ? form->reltuples :
920 (double) DatumGetFloat4(val);
921 val = get_binary_datum(3, &isnull);
922 entry->curpages = isnull ? InvalidBlockNumber :
923 (BlockNumber) DatumGetInt32(val);
924 #if PG_VERSION_NUM >= 90200
925 val = get_binary_datum(4, &isnull);
926 entry->relallvisible = isnull ? form->relallvisible :
927 (BlockNumber) DatumGetInt32(val);
930 ReleaseSysCache(tuple);
944 * If no dummy statistics available for this relation, do nothing then
945 * return immediately.
951 /* Tweaking statistics using merged statistics */
954 *pages = entry->relpages;
955 *tuples = entry->reltuples;
960 * Get current number of pages to estimate current number of tuples, based
961 * on tuple density at the last ANALYZE and current number of pages.
963 rel = relation_open(relid, NoLock);
964 rel->rd_rel->relpages = entry->relpages;
965 rel->rd_rel->reltuples = entry->reltuples;
966 #if PG_VERSION_NUM >= 90200
967 rel->rd_rel->relallvisible = entry->relallvisible;
969 dbms_stats_estimate_rel_size(rel, NULL, pages, tuples, allvisfrac,
971 relation_close(rel, NoLock);
975 * get_merged_avgwidth
976 * get average width of the given column by merging dummy and authentic
980 get_merged_avgwidth(Oid relid, AttrNumber attnum)
984 if (nested_level > 0 || relid < FirstNormalObjectId)
985 return 0; /* avoid recursive call and system objects */
987 if ((tuple = get_merged_column_stats(relid, attnum, false)) == NULL)
990 return get_pg_statistic(tuple)->stawidth;
994 * get_merged_column_stats
995 * returns per-column statistics for given column
997 * This caches the result to avoid excessive SPI calls for repetitive
998 * request for every columns many time.
1001 get_merged_column_stats(Oid relid, AttrNumber attnum, bool inh)
1004 HeapTuple statsTuple;
1005 bool negative = false;
1007 if (nested_level > 0 || relid < FirstNormalObjectId)
1008 return NULL; /* avoid recursive call and system objects */
1011 * Return NULL for system catalog, directing the caller to use system
1014 if (dbms_stats_is_system_catalog_internal(relid))
1017 /* Return cached statistics, if any. */
1018 if ((tuple = column_cache_search(relid, attnum, inh, &negative)) != NULL)
1021 /* Obtain system statistics from syscache. */
1022 statsTuple = SearchSysCache3(STATRELATTINH,
1023 ObjectIdGetDatum(relid),
1024 Int16GetDatum(attnum),
1029 * Return system statistics whatever it is if negative cache for this
1030 * column is returned
1032 tuple = heap_copytuple(statsTuple);
1037 * Search for dummy statistics and try merge with system stats.
1042 * Save current context in order to use during SPI is
1045 MemoryContext outer_cxt = CurrentMemoryContext;
1051 /* Obtain dummy statistics for the column using SPI call. */
1053 execute_plan(&tuple_plan, tuple_query, relid, &attnum, inh);
1055 /* Reset to the outer memory context for following steps. */
1056 MemoryContextSwitchTo(outer_cxt);
1060 /* merge the dummy statistics with the system statistics */
1061 tuple = dbms_stats_merge_internal(SPI_tuptable->vals[0],
1063 SPI_tuptable->tupdesc);
1068 /* Cache merged result for subsequent calls. */
1069 tuple = column_cache_enter(relid, attnum, tuple);
1071 /* Return system stats if the merging results in failure. */
1072 if (!HeapTupleIsValid(tuple))
1073 tuple = heap_copytuple(statsTuple);
1086 if (HeapTupleIsValid(statsTuple))
1087 ReleaseSysCache(statsTuple);
1093 * column_cache_search
1094 * Search statistic of the given column from the cache.
1097 column_cache_search(Oid relid, AttrNumber attnum, bool inh, bool *negative)
1099 StatsRelationEntry *entry;
1105 * First, get cached relation stats. If we have not cached relation stats,
1106 * we don't have column stats too.
1108 entry = hash_search(rel_stats, &relid, HASH_FIND, &found);
1113 * We assume that not so many column_stats_effective are defined on one
1114 * relation, so we use simple linear-search here. Hash table would be an
1115 * alternative, but it seems overkill so far.
1117 foreach(lc, entry->col_stats)
1119 StatsColumnEntry *ent = (StatsColumnEntry*) lfirst (lc);
1121 if (ent->attnum != attnum) continue;
1125 /* Retrun NULL for negative cache, with noticing of that.*/
1131 HeapTuple tuple = (HeapTuple) ent->tuple;
1132 Form_pg_statistic form = get_pg_statistic(tuple);
1134 /* Find statistic of the given column from the cache. */
1135 if (form->stainherit == inh)
1140 return NULL; /* Not yet registered. */
1144 * Cache a per-column statistics. Storing in CacheMemoryContext, the cached
1145 * statistics will live through the current session, unless dummy statistics or
1146 * table definition have been changed.
1149 column_cache_enter(Oid relid, int32 attnum, HeapTuple tuple)
1151 MemoryContext oldcontext;
1153 StatsColumnEntry *newcolent;
1154 Form_pg_statistic form;
1155 StatsRelationEntry *entry;
1158 Assert(tuple != NULL);
1159 Assert(!heap_attisnull(tuple, 1));
1161 entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
1163 init_rel_stats_entry(entry, relid);
1166 * Adding this column stats to the column stats list of the relation stats
1167 * cache just obtained.
1169 oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
1170 newcolent = (StatsColumnEntry*)palloc(sizeof(StatsColumnEntry));
1171 newcolent->attnum = attnum;
1172 if (HeapTupleIsValid(tuple))
1174 newcolent->negative = false;
1175 newcolent->tuple = heap_copytuple(tuple);
1179 /* Invalid tuple makes a negative cache. */
1180 newcolent->negative = true;
1181 newcolent->tuple = NULL;
1184 entry->col_stats = lappend(entry->col_stats, newcolent);
1185 MemoryContextSwitchTo(oldcontext);
1187 return newcolent->tuple;
1191 * Execute given plan. When given plan is NULL, create new plan from given
1192 * query string, and execute it. This function can be used only for retrieving
1193 * statistics of column_stats_effective and relation_stats_effective, because we assume #, types, and order
1194 * of parameters here.
1197 execute_plan(SPIPlanPtr *plan,
1200 const AttrNumber *attnum,
1204 Oid argtypes[3] = { OIDOID, INT2OID, BOOLOID };
1207 bool nulls[3] = { false, false, false };
1209 nargs = (attnum ? 3 : 1);
1211 /* When plan is not given, create plan from query string at first. */
1215 p = SPI_prepare(query, nargs, argtypes);
1217 elog(ERROR, "pg_dbms_stats: SPI_prepare => %d", SPI_result);
1218 *plan = SPI_saveplan(p);
1222 values[0] = ObjectIdGetDatum(relid);
1223 values[1] = Int16GetDatum(attnum ? *attnum : 0);
1224 values[2] = BoolGetDatum(inh);
1226 ret = SPI_execute_plan(*plan, values, nulls, true, 1);
1227 if (ret != SPI_OK_SELECT)
1228 elog(ERROR, "pg_dbms_stats: SPI_execute_plan => %d", ret);
1230 return SPI_processed > 0;
1234 * StatsCacheRelCallback
1235 * Relcache inval callback function
1237 * Invalidate cached statistic info of the given relid, or all cached statistic
1238 * info if relid == InvalidOid. We don't complain even when we don't have such
1241 * Note: arg is not used.
1244 StatsCacheRelCallback(Datum arg, Oid relid)
1246 HASH_SEQ_STATUS status;
1247 StatsRelationEntry *entry;
1249 hash_seq_init(&status, rel_stats);
1250 while ((entry = hash_seq_search(&status)) != NULL)
1252 if (relid == InvalidOid || relid == entry->relid)
1256 /* Mark the relation entry as INVALID */
1257 entry->valid = false;
1259 /* Discard every column statistics */
1260 foreach (lc, entry->col_stats)
1262 StatsColumnEntry *ent = (StatsColumnEntry*) lfirst(lc);
1268 list_free(entry->col_stats);
1269 entry->col_stats = NIL;
1273 /* We always check throughout the list, so hash_seq_term is not necessary */
1277 * Initialize hash table for per-relation statistics.
1280 init_rel_stats(void)
1285 /* Prevent double initialization. */
1286 if (rel_stats != NULL)
1289 MemSet(&ctl, 0, sizeof(ctl));
1290 ctl.keysize = sizeof(Oid);
1291 ctl.entrysize = sizeof(StatsRelationEntry);
1292 ctl.hash = oid_hash;
1293 ctl.hcxt = CacheMemoryContext;
1294 hash = hash_create("dbms_stats relation statistics cache",
1296 &ctl, HASH_ELEM | HASH_CONTEXT);
1302 * Initialize newly added cache entry so that it represents an invalid cache
1303 * entry for given relid.
1306 init_rel_stats_entry(StatsRelationEntry *entry, Oid relid)
1308 entry->relid = relid;
1309 entry->valid = false;
1310 entry->relpages = InvalidBlockNumber;
1311 entry->reltuples = 0.0;
1312 entry->relallvisible = InvalidBlockNumber;
1313 entry->curpages = InvalidBlockNumber;
1314 entry->col_stats = NIL;
1318 * dbms_stats_estimate_rel_size - estimate # pages and # tuples in a table or
1321 * We also estimate the fraction of the pages that are marked all-visible in
1322 * the visibility map, for use in estimation of index-only scans.
1324 * If attr_widths isn't NULL, it points to the zero-index entry of the
1325 * relation's attr_widths[] cache; we fill this in if we have need to compute
1326 * the attribute widths for estimation purposes.
1328 * Note: This function is copied from plancat.c in core source tree of version
1329 * 9.2, and customized for pg_dbms_stats. Changes from orignal one are:
1330 * - rename by prefixing dbms_stats_
1331 * - add 3 parameters (relpages, reltuples, curpage) to pass dummy curpage
1333 * - Get current # of pages only when supplied curpages is InvalidBlockNumber
1334 * - get franction of all-visible-pages
1337 dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
1338 BlockNumber *pages, double *tuples,
1339 double *allvisfrac, BlockNumber curpages)
1341 BlockNumber relpages;
1343 BlockNumber relallvisible;
1346 switch (rel->rd_rel->relkind)
1348 case RELKIND_RELATION:
1350 #if PG_VERSION_NUM >= 90300
1351 case RELKIND_MATVIEW:
1353 case RELKIND_TOASTVALUE:
1354 /* it has storage, ok to call the smgr */
1355 if (curpages == InvalidBlockNumber)
1356 curpages = RelationGetNumberOfBlocks(rel);
1359 * HACK: if the relation has never yet been vacuumed, use a
1360 * minimum size estimate of 10 pages. The idea here is to avoid
1361 * assuming a newly-created table is really small, even if it
1362 * currently is, because that may not be true once some data gets
1363 * loaded into it. Once a vacuum or analyze cycle has been done
1364 * on it, it's more reasonable to believe the size is somewhat
1367 * (Note that this is only an issue if the plan gets cached and
1368 * used again after the table has been filled. What we're trying
1369 * to avoid is using a nestloop-type plan on a table that has
1370 * grown substantially since the plan was made. Normally,
1371 * autovacuum/autoanalyze will occur once enough inserts have
1372 * happened and cause cached-plan invalidation; but that doesn't
1373 * happen instantaneously, and it won't happen at all for cases
1374 * such as temporary tables.)
1376 * We approximate "never vacuumed" by "has relpages = 0", which
1377 * means this will also fire on genuinely empty relation_stats_effective. Not
1378 * great, but fortunately that's a seldom-seen case in the real
1379 * world, and it shouldn't degrade the quality of the plan too
1380 * much anyway to err in this direction.
1382 * There are two exceptions wherein we don't apply this heuristic.
1383 * One is if the table has inheritance children. Totally empty
1384 * parent tables are quite common, so we should be willing to
1385 * believe that they are empty. Also, we don't apply the 10-page
1386 * minimum to indexes.
1388 if (curpages < 10 &&
1389 rel->rd_rel->relpages == 0 &&
1390 !rel->rd_rel->relhassubclass &&
1391 rel->rd_rel->relkind != RELKIND_INDEX)
1394 /* report estimated # pages */
1396 /* quick exit if rel is clearly empty */
1403 /* coerce values in pg_class to more desirable types */
1404 relpages = (BlockNumber) rel->rd_rel->relpages;
1405 reltuples = (double) rel->rd_rel->reltuples;
1406 #if PG_VERSION_NUM >= 90200
1407 relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
1412 * If it's an index, discount the metapage while estimating the
1413 * number of tuples. This is a kluge because it assumes more than
1414 * it ought to about index structure. Currently it's OK for
1415 * btree, hash, and GIN indexes but suspect for GiST indexes.
1417 if (rel->rd_rel->relkind == RELKIND_INDEX &&
1424 /* estimate number of tuples from previous tuple density */
1426 density = reltuples / (double) relpages;
1430 * When we have no data because the relation was truncated,
1431 * estimate tuple width from attribute datatypes. We assume
1432 * here that the pages are completely full, which is OK for
1433 * tables (since they've presumably not been VACUUMed yet) but
1434 * is probably an overestimate for indexes. Fortunately
1435 * get_relation_info() can clamp the overestimate to the
1436 * parent table's size.
1438 * Note: this code intentionally disregards alignment
1439 * considerations, because (a) that would be gilding the lily
1440 * considering how crude the estimate is, and (b) it creates
1441 * platform dependencies in the default plans which are kind
1442 * of a headache for regression testing.
1446 tuple_width = dbms_stats_get_rel_data_width(rel, attr_widths);
1447 tuple_width += sizeof(HeapTupleHeaderData);
1448 tuple_width += sizeof(ItemPointerData);
1449 /* note: integer division is intentional here */
1450 density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width;
1452 *tuples = rint(density * (double) curpages);
1455 * We use relallvisible as-is, rather than scaling it up like we
1456 * do for the pages and tuples counts, on the theory that any
1457 * pages added since the last VACUUM are most likely not marked
1458 * all-visible. But costsize.c wants it converted to a fraction.
1460 if (relallvisible == 0 || curpages <= 0)
1462 else if ((double) relallvisible >= curpages)
1465 *allvisfrac = (double) relallvisible / curpages;
1467 case RELKIND_SEQUENCE:
1468 /* Sequences always have a known size */
1473 case RELKIND_FOREIGN_TABLE:
1474 /* Just use whatever's in pg_class */
1475 *pages = rel->rd_rel->relpages;
1476 *tuples = rel->rd_rel->reltuples;
1480 /* else it has no disk storage; probably shouldn't get here? */
1489 * dbms_stats_get_rel_data_width
1491 * Estimate the average width of (the data part of) the relation's tuples.
1493 * If attr_widths isn't NULL, it points to the zero-index entry of the
1494 * relation's attr_widths[] cache; use and update that cache as appropriate.
1496 * Currently we ignore dropped column_stats_effective. Ideally those should be included
1497 * in the result, but we haven't got any way to get info about them; and
1498 * since they might be mostly NULLs, treating them as zero-width is not
1499 * necessarily the wrong thing anyway.
1501 * Note: This function is copied from plancat.c in core source tree of version
1502 * 9.2, and just renamed.
1505 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths)
1507 int32 tuple_width = 0;
1510 for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
1512 Form_pg_attribute att = rel->rd_att->attrs[i - 1];
1515 if (att->attisdropped)
1518 /* use previously cached data, if any */
1519 if (attr_widths != NULL && attr_widths[i] > 0)
1521 tuple_width += attr_widths[i];
1525 /* This should match set_rel_width() in costsize.c */
1526 item_width = get_attavgwidth(RelationGetRelid(rel), i);
1527 if (item_width <= 0)
1529 item_width = get_typavgwidth(att->atttypid, att->atttypmod);
1530 Assert(item_width > 0);
1532 if (attr_widths != NULL)
1533 attr_widths[i] = item_width;
1534 tuple_width += item_width;
1541 void test_pg_dbms_stats(int *passed, int *total);
1542 static void test_init_rel_stats(int *passed, int *total);
1543 static void test_init_rel_stats_entry(int *passed, int *total);
1546 test_pg_dbms_stats(int *passed, int *total)
1548 int local_passed = 0;
1549 int local_total = 0;
1551 elog(WARNING, "==========");
1554 test_init_rel_stats(&local_passed, &local_total);
1555 test_init_rel_stats_entry(&local_passed, &local_total);
1557 elog(WARNING, "%s %d/%d passed", __FUNCTION__, local_passed, local_total);
1558 *passed += local_passed;
1559 *total += local_total;
1563 test_init_rel_stats_entry(int *passed, int *total)
1566 StatsRelationEntry entry;
1572 init_rel_stats_entry(&entry, 1234);
1573 if (entry.relid == 1234 &&
1574 entry.valid == false &&
1575 entry.relpages == InvalidBlockNumber &&
1576 entry.reltuples == 0 &&
1577 entry.relallvisible == InvalidBlockNumber &&
1578 entry.curpages == InvalidBlockNumber &&
1579 entry.col_stats == NIL)
1581 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1585 elog(WARNING, "%s-%d failed: initialized", __FUNCTION__, caseno);
1591 test_init_rel_stats(int *passed, int *total)
1594 static HTAB *org_rel_stats;
1602 if (rel_stats != NULL)
1604 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1608 elog(WARNING, "%s-%d failed: rel_stats is NULL", __FUNCTION__, caseno);
1615 org_rel_stats = rel_stats;
1617 if (org_rel_stats == rel_stats)
1619 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1623 elog(WARNING, "%s-%d failed: rel_stats changed from %p to %p",
1624 __FUNCTION__, caseno, org_rel_stats, rel_stats);