4 * Copyright (c) 2009-2016, 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 "catalog/namespace.h"
14 #include "catalog/pg_authid.h"
15 #include "commands/trigger.h"
16 #include "executor/spi.h"
18 #include "optimizer/plancat.h"
19 #include "parser/parse_oper.h"
20 #include "storage/bufmgr.h"
21 #include "utils/builtins.h"
22 #include "utils/elog.h"
23 #include "utils/guc.h"
24 #include "utils/inval.h"
25 #include "utils/lsyscache.h"
26 #include "utils/selfuncs.h"
27 #include "utils/syscache.h"
28 #include "miscadmin.h"
29 #if PG_VERSION_NUM >= 90200
30 #include "utils/rel.h"
32 #if PG_VERSION_NUM >= 90300
33 #include "access/htup_details.h"
34 #include "utils/catcache.h"
36 #if PG_VERSION_NUM >= 100000
40 #include "pg_dbms_stats.h"
44 /* Error levels used by pg_dbms_stats */
45 #define ELEVEL_DEBUG DEBUG3 /* log level for debug information */
46 #define ELEVEL_BADSTATS LOG /* log level for invalid statistics */
48 #define MAX_REL_CACHE 50 /* expected max # of rel stats entries */
50 /* Relation statistics cache entry */
51 typedef struct StatsRelationEntry
53 Oid relid; /* hash key must be at the head */
55 bool valid; /* T if the entry has valid stats */
57 BlockNumber relpages; /* # of pages as of last ANALYZE */
58 double reltuples; /* # of tuples as of last ANALYZE */
59 BlockNumber relallvisible; /* # of all-visible pages as of last
61 BlockNumber curpages; /* # of pages as of lock/restore */
63 List *col_stats; /* list of StatsColumnEntry, each element
64 of which is pg_statistic record of this
69 * Column statistics cache entry. This is for list item for
70 * StatsRelationEntry.col_stats.
72 typedef struct StatsColumnEntry
80 /* Saved hook functions */
81 get_relation_info_hook_type prev_get_relation_info = NULL;
82 get_attavgwidth_hook_type prev_get_attavgwidth = NULL;
83 get_relation_stats_hook_type prev_get_relation_stats = NULL;
84 get_index_stats_hook_type prev_get_index_stats = NULL;
87 #define NSPNAME "dbms_stats"
88 #define RELSTAT_TBLNAME "relation_stats_locked"
89 #define COLSTAT_TBLNAME "column_stats_locked"
91 /* rows_query(oid) RETURNS int4, float4, int4 */
92 static const char *rows_query =
93 "SELECT relpages, reltuples, curpages"
94 #if PG_VERSION_NUM >= 90200
97 " FROM " NSPNAME "." RELSTAT_TBLNAME
99 static SPIPlanPtr rows_plan = NULL;
101 /* tuple_query(oid, int2, bool) RETURNS pg_statistic */
102 static const char *tuple_query =
104 " FROM " NSPNAME "." COLSTAT_TBLNAME
105 " WHERE starelid = $1 "
106 " AND staattnum = $2 "
107 " AND stainherit = $3";
108 static SPIPlanPtr tuple_plan = NULL;
111 static bool pg_dbms_stats_use_locked_stats = true;
113 /* Current nesting depth of SPI calls, used to prevent recursive calls */
114 static int nested_level = 0;
117 * The relation_stats_effective statistic cache is stored in hash table.
119 static HTAB *rel_stats;
122 * The owner of pg_dbms_stats statistic tables.
124 static Oid stats_table_owner = InvalidOid;
125 static char *stats_table_owner_name = "";
127 #define get_pg_statistic(tuple) ((Form_pg_statistic) GETSTRUCT(tuple))
129 PG_FUNCTION_INFO_V1(dbms_stats_merge);
130 PG_FUNCTION_INFO_V1(dbms_stats_invalidate_relation_cache);
131 PG_FUNCTION_INFO_V1(dbms_stats_invalidate_column_cache);
132 PG_FUNCTION_INFO_V1(dbms_stats_is_system_schema);
133 PG_FUNCTION_INFO_V1(dbms_stats_is_system_catalog);
134 PG_FUNCTION_INFO_V1(dbms_stats_anyary_anyary);
135 PG_FUNCTION_INFO_V1(dbms_stats_type_is_analyzable);
136 PG_FUNCTION_INFO_V1(dbms_stats_anyarray_basetype);
138 extern Datum dbms_stats_merge(PG_FUNCTION_ARGS);
139 extern Datum dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS);
140 extern Datum dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS);
141 extern Datum dbms_stats_is_system_schema(PG_FUNCTION_ARGS);
142 extern Datum dbms_stats_is_system_catalog(PG_FUNCTION_ARGS);
143 extern Datum dbms_stats_anyary_anyary(PG_FUNCTION_ARGS);
144 extern Datum dbms_stats_type_is_analyzable(PG_FUNCTION_ARGS);
145 extern Datum dbms_stats_anyarray_basetype(PG_FUNCTION_ARGS);
147 static HeapTuple dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs,
148 TupleDesc tupledesc);
149 static void dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
150 TriggerData *trigdata, HeapTuple *invtup, HeapTuple *rettup);
151 static void dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col);
153 /* Module callbacks */
157 static void dbms_stats_get_relation_info(PlannerInfo *root, Oid relid,
158 bool inhparent, RelOptInfo *rel);
159 static int32 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum);
160 static bool dbms_stats_get_relation_stats(PlannerInfo *root, RangeTblEntry *rte,
161 AttrNumber attnum, VariableStatData *vardata);
162 static bool dbms_stats_get_index_stats(PlannerInfo *root, Oid indexOid,
163 AttrNumber indexattnum, VariableStatData *vardata);
165 static void get_merged_relation_stats(Oid relid, BlockNumber *pages,
166 double *tuples, double *allvisfrac, bool estimate);
167 static int32 get_merged_avgwidth(Oid relid, AttrNumber attnum);
168 static HeapTuple get_merged_column_stats(Oid relid, AttrNumber attnum,
170 static HeapTuple column_cache_search(Oid relid, AttrNumber attnum,
171 bool inh, bool*negative);
172 static HeapTuple column_cache_enter(Oid relid, int32 attnum, bool inh,
174 static bool execute_plan(SPIPlanPtr *plan, const char *query, Oid relid,
175 const AttrNumber *attnum, bool inh);
176 static void StatsCacheRelCallback(Datum arg, Oid relid);
177 static void init_rel_stats(void);
178 static void init_rel_stats_entry(StatsRelationEntry *entry, Oid relid);
179 /* copied from PG core source tree */
180 static void dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
181 BlockNumber *pages, double *tuples, double *allvisfrac,
182 BlockNumber curpages);
183 static int32 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths);
185 /* Unit test suit functions */
187 extern void test_import(int *passed, int *total);
188 extern void test_dump(int *passed, int *total);
189 extern void test_pg_dbms_stats(int *passed, int *total);
192 /* SPI_keepplan() is since 9.2 */
193 #if PG_VERSION_NUM < 90200
194 #define SPI_keepplan(pplan) {\
195 SPIPlanPtr tp = *plan;\
196 *plan = SPI_saveplan(tp);\
202 * Module load callback
207 /* Execute unit test cases */
213 test_import(&passed, &total);
214 test_dump(&passed, &total);
215 test_pg_dbms_stats(&passed, &total);
217 elog(WARNING, "TOTAL %d/%d passed", passed, total);
221 /* Define custom GUC variables. */
222 DefineCustomBoolVariable("pg_dbms_stats.use_locked_stats",
223 "Enable user defined statistics.",
225 &pg_dbms_stats_use_locked_stats,
233 EmitWarningsOnPlaceholders("pg_dbms_stats");
235 /* Back up old hooks, and install ours. */
236 prev_get_relation_info = get_relation_info_hook;
237 get_relation_info_hook = dbms_stats_get_relation_info;
238 prev_get_attavgwidth = get_attavgwidth_hook;
239 get_attavgwidth_hook = dbms_stats_get_attavgwidth;
240 prev_get_relation_stats = get_relation_stats_hook;
241 get_relation_stats_hook = dbms_stats_get_relation_stats;
242 prev_get_index_stats = get_index_stats_hook;
243 get_index_stats_hook = dbms_stats_get_index_stats;
245 /* Initialize hash table for statistics caching. */
248 /* Also set up a callback for relcache SI invalidations */
249 CacheRegisterRelcacheCallback(StatsCacheRelCallback, (Datum) 0);
253 * Module unload callback
258 /* Restore old hooks. */
259 get_relation_info_hook = prev_get_relation_info;
260 get_attavgwidth_hook = prev_get_attavgwidth;
261 get_relation_stats_hook = prev_get_relation_stats;
262 get_index_stats_hook = prev_get_index_stats;
264 /* A function to unregister callback for relcache is NOT provided. */
268 * Function to convert from any array from dbms_stats.anyarray.
271 dbms_stats_anyary_anyary(PG_FUNCTION_ARGS)
273 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
274 if (ARR_NDIM(arr) != 1)
275 elog(ERROR, "array must be one-dimentional.");
277 PG_RETURN_ARRAYTYPE_P(arr);
281 * Function to check if the type can have statistics.
284 dbms_stats_type_is_analyzable(PG_FUNCTION_ARGS)
286 Oid typid = PG_GETARG_OID(0);
289 if (!OidIsValid(typid))
290 PG_RETURN_BOOL(false);
292 get_sort_group_operators(typid, false, false, false,
295 PG_RETURN_BOOL(OidIsValid(eqopr));
299 * Function to get base type of the value of the type dbms_stats.anyarray.
302 dbms_stats_anyarray_basetype(PG_FUNCTION_ARGS)
304 ArrayType *arr = PG_GETARG_ARRAYTYPE_P(0);
305 Oid elemtype = arr->elemtype;
310 if (!OidIsValid(elemtype))
311 elog(ERROR, "invalid base type oid: %u", elemtype);
313 tp = SearchSysCache1(TYPEOID, ObjectIdGetDatum(elemtype));
314 if (!HeapTupleIsValid(tp)) /* I trust you. */
315 elog(ERROR, "invalid base type oid: %u", elemtype);
317 typtup = (Form_pg_type) GETSTRUCT(tp);
318 result = (Name) palloc0(NAMEDATALEN);
319 StrNCpy(NameStr(*result), NameStr(typtup->typname), NAMEDATALEN);
322 PG_RETURN_NAME(result);
326 * Find and store the owner of the dummy statistics table.
328 * We will access statistics tables using this owner
331 get_stats_table_owner(void)
335 if (!OidIsValid(stats_table_owner))
337 tp = SearchSysCache2(RELNAMENSP,
338 PointerGetDatum(RELSTAT_TBLNAME),
339 ObjectIdGetDatum(get_namespace_oid(NSPNAME, false)));
340 if (!HeapTupleIsValid(tp))
341 elog(ERROR, "table \"%s.%s\" not found in pg_class",
342 NSPNAME, RELSTAT_TBLNAME);
343 stats_table_owner = ((Form_pg_class) GETSTRUCT(tp))->relowner;
344 if (!OidIsValid(stats_table_owner))
345 elog(ERROR, "owner uid of table \"%s.%s\" is invalid",
346 NSPNAME, RELSTAT_TBLNAME);
349 tp = SearchSysCache1(AUTHOID, ObjectIdGetDatum(stats_table_owner));
350 if (!HeapTupleIsValid(tp))
353 "role id %u for the owner of the relation \"%s.%s\"is invalid",
354 stats_table_owner, NSPNAME, RELSTAT_TBLNAME);
356 /* This will be done once for the session, so not pstrdup. */
357 stats_table_owner_name =
358 strdup(NameStr(((Form_pg_authid) GETSTRUCT(tp))->rolname));
361 return stats_table_owner;
365 * Store heap tuple header into given heap tuple.
368 AssignHeapTuple(HeapTuple htup, HeapTupleHeader header)
370 htup->t_len = HeapTupleHeaderGetDatumLength(header);
371 ItemPointerSetInvalid(&htup->t_self);
372 htup->t_tableOid = InvalidOid;
373 htup->t_data = header;
378 * called by sql function 'dbms_stats.merge', and return the execution result
379 * of the function 'dbms_stats_merge_internal'.
382 dbms_stats_merge(PG_FUNCTION_ARGS)
387 HeapTuple ret = NULL;
389 /* assign HeapTuple of the left statistics data unless null. */
393 AssignHeapTuple(&lhs, PG_GETARG_HEAPTUPLEHEADER(0));
395 /* assign HeapTuple of the right statistics data unless null. */
399 AssignHeapTuple(&rhs, PG_GETARG_HEAPTUPLEHEADER(1));
401 /* fast path for one-side is null */
402 if (lhs.t_data == NULL && rhs.t_data == NULL)
405 /* build a tuple descriptor for our result type */
406 if (get_call_result_type(fcinfo, NULL, &tupdesc) != TYPEFUNC_COMPOSITE)
407 elog(ERROR, "return type must be a row type");
409 /* merge two statistics tuples into one, and return it */
410 ret = dbms_stats_merge_internal(&lhs, &rhs, tupdesc);
413 PG_RETURN_DATUM(HeapTupleGetDatum(ret));
419 * dbms_stats_merge_internal
420 * merge the dummy statistic (lhs) and the true statistic (rhs), on the basis
421 * of given TupleDesc.
423 * this function doesn't become an error level of ERROR to meet that the
424 * result of the SQL is not affected by the query plan.
427 dbms_stats_merge_internal(HeapTuple lhs, HeapTuple rhs, TupleDesc tupdesc)
429 Datum values[Natts_pg_statistic];
430 bool nulls[Natts_pg_statistic];
432 Oid atttype = InvalidOid;
436 /* fast path for both-sides are null */
437 if ((lhs == NULL || lhs->t_data == NULL) &&
438 (rhs == NULL || rhs->t_data == NULL))
441 /* fast path for one-side is null */
442 if (lhs == NULL || lhs->t_data == NULL)
444 /* use right tuple */
445 heap_deform_tuple(rhs, tupdesc, values, nulls);
446 for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
448 return NULL; /* check null constraints */
450 else if (rhs == NULL || rhs->t_data == NULL)
453 heap_deform_tuple(lhs, tupdesc, values, nulls);
454 for (i = 0; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
456 return NULL; /* check null constraints */
461 * If the column value of the dummy statistic is not NULL, in the
462 * statistics except the slot, use it. Otherwise we use the column
463 * value of the true statistic.
465 heap_deform_tuple(lhs, tupdesc, values, nulls);
466 for (i = 0; i < Anum_pg_statistic_stakind1 - 1; i++)
470 values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
473 ereport(ELEVEL_BADSTATS,
474 (errmsg("pg_dbms_stats: bad statistics"),
475 errdetail("column \"%s\" should not be null",
476 get_attname(StatisticRelationId,
477 tupdesc->attrs[i]->attnum))));
478 return NULL; /* should not be null */
484 * If the column value of the dummy statistic is not all NULL, in the
485 * statistics the slot, use it. Otherwise we use the column
486 * value of the true statistic.
488 for (; i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1; i++)
492 for (i = Anum_pg_statistic_stakind1 - 1;
493 i < Anum_pg_statistic_stavalues1 + STATISTIC_NUM_SLOTS - 1;
496 values[i] = fastgetattr(rhs, i + 1, tupdesc, &nulls[i]);
497 if (i < Anum_pg_statistic_staop1 + STATISTIC_NUM_SLOTS - 1 &&
500 ereport(ELEVEL_BADSTATS,
501 (errmsg("pg_dbms_stats: bad statistics"),
502 errdetail("column \"%s\" should not be null",
503 get_attname(StatisticRelationId,
504 tupdesc->attrs[i]->attnum))));
505 return NULL; /* should not be null */
515 * Verify types to work around for ALTER COLUMN TYPE.
517 * Note: We don't need to retrieve atttype when the attribute doesn't have
518 * neither Most-Common-Value nor Histogram, but we retrieve it always
519 * because it's not usual.
521 relid = DatumGetObjectId(values[0]);
522 attnum = DatumGetInt16(values[1]);
523 atttype = get_atttype(relid, attnum);
524 if (atttype == InvalidOid)
527 (errmsg("pg_dbms_stats: no-longer-existent column"),
528 errdetail("relid \"%d\" or its column whose attnum is \"%d\" might be deleted",
530 errhint("dbms_stats.clean_up_stats() would fix this.")));
533 for (i = 0; i < STATISTIC_NUM_SLOTS; i++)
535 if ((i + 1 == STATISTIC_KIND_MCV ||
536 i + 1 == STATISTIC_KIND_HISTOGRAM) &&
537 !nulls[Anum_pg_statistic_stavalues1 + i - 1])
541 arr = DatumGetArrayTypeP(
542 values[Anum_pg_statistic_stavalues1 + i - 1]);
543 if (arr == NULL || arr->elemtype != atttype)
545 const char *attname = get_attname(relid, attnum);
548 * relid and attnum must be valid here because valid atttype
549 * has been gotten already.
552 ereport(ELEVEL_BADSTATS,
553 (errmsg("pg_dbms_stats: bad column type"),
554 errdetail("type of column \"%s\" has been changed",
556 errhint("need to execute dbms_stats.unlock('%s', '%s')",
557 get_rel_name(relid), attname)));
563 return heap_form_tuple(tupdesc, values, nulls);
567 * dbms_stats_invalidate_relation_cache
568 * Register invalidation of the specified relation's relcache.
570 * CREATE TRIGGER dbms_stats.relation_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
574 dbms_stats_invalidate_relation_cache(PG_FUNCTION_ARGS)
576 TriggerData *trigdata = (TriggerData *) fcinfo->context;
577 HeapTuple invtup; /* tuple to be invalidated */
578 HeapTuple rettup; /* tuple to be returned */
582 /* make sure it's called as a before/after trigger */
583 dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
586 * assume that position of dbms_stats.relation_stats_locked.relid is head value of
589 value = fastgetattr(invtup, 1, trigdata->tg_relation->rd_att, &isnull);
592 * invalidate prepared statements and force re-planning with pg_dbms_stats.
594 dbms_stats_invalidate_cache_internal((Oid)value, false);
596 PG_RETURN_POINTER(rettup);
600 * dbms_stats_invalidate_column_cache
601 * Register invalidation of the specified relation's relcache.
603 * CREATE TRIGGER dbms_stats.column_stats_locked FOR INSERT, UPDATE, DELETE FOR EACH
607 dbms_stats_invalidate_column_cache(PG_FUNCTION_ARGS)
609 TriggerData *trigdata = (TriggerData *) fcinfo->context;
610 Form_pg_statistic form;
611 HeapTuple invtup; /* tuple to be invalidated */
612 HeapTuple rettup; /* tuple to be returned */
614 /* make sure it's called as a before/after trigger */
615 dbms_stats_check_tg_event(fcinfo, trigdata, &invtup, &rettup);
618 * assume that both pg_statistic and dbms_stats.column_stats_locked have the same
621 form = get_pg_statistic(invtup);
624 * invalidate prepared statements and force re-planning with pg_dbms_stats.
626 dbms_stats_invalidate_cache_internal(form->starelid, true);
628 PG_RETURN_POINTER(rettup);
632 dbms_stats_check_tg_event(FunctionCallInfo fcinfo,
633 TriggerData *trigdata,
637 /* make sure it's called as a before/after trigger */
638 if (!CALLED_AS_TRIGGER(fcinfo) ||
639 !TRIGGER_FIRED_BEFORE(trigdata->tg_event) ||
640 !TRIGGER_FIRED_FOR_ROW(trigdata->tg_event))
641 elog(ERROR, "pg_dbms_stats: invalid trigger call");
643 if (TRIGGER_FIRED_BY_INSERT(trigdata->tg_event))
646 *rettup = *invtup = trigdata->tg_trigtuple;
648 else if (TRIGGER_FIRED_BY_DELETE(trigdata->tg_event))
651 *rettup = *invtup = trigdata->tg_trigtuple;
656 *invtup = trigdata->tg_trigtuple;
657 *rettup = trigdata->tg_newtuple;
662 dbms_stats_invalidate_cache_internal(Oid relid, bool sta_col)
667 * invalidate prepared statements and force re-planning with pg_dbms_stats.
669 rel = try_relation_open(relid, NoLock);
673 rel->rd_rel->relkind == RELKIND_INDEX &&
674 (rel->rd_indextuple == NULL ||
675 heap_attisnull(rel->rd_indextuple, Anum_pg_index_indexprs)))
677 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
678 errmsg("\"%s\" is an index except an index expression",
679 RelationGetRelationName(rel))));
680 if (rel->rd_rel->relkind == RELKIND_COMPOSITE_TYPE)
682 (errcode(ERRCODE_WRONG_OBJECT_TYPE),
683 errmsg("\"%s\" is a composite type",
684 RelationGetRelationName(rel))));
687 * We need to invalidate relcache of underlying table too, because
688 * CachedPlan mechanism decides to do re-planning when any relcache of
689 * used tables was invalid at EXECUTE.
691 if (rel->rd_rel->relkind == RELKIND_INDEX &&
692 rel->rd_index && OidIsValid(rel->rd_index->indrelid))
693 CacheInvalidateRelcacheByRelid(rel->rd_index->indrelid);
695 CacheInvalidateRelcache(rel);
696 relation_close(rel, NoLock);
701 * dbms_stats_is_system_schema
702 * called by sql function 'dbms_stats.is_system_schema', and return the
703 * result of the function 'dbms_stats_is_system_internal'.
706 dbms_stats_is_system_schema(PG_FUNCTION_ARGS)
712 arg0 = PG_GETARG_TEXT_PP(0);
713 schema_name = text_to_cstring(arg0);
714 result = dbms_stats_is_system_schema_internal(schema_name);
716 PG_FREE_IF_COPY(arg0, 0);
718 PG_RETURN_BOOL(result);
722 * dbms_stats_is_system_schema_internal
723 * return whether the given schema contains any system catalog. Here we
724 * treat dbms_stats objects as system catalogs to avoid infinite loop.
727 dbms_stats_is_system_schema_internal(char *schema_name)
729 Assert(schema_name != NULL);
731 /* if the schema is system_schema, return true */
732 if (strcmp(schema_name, "pg_catalog") == 0 ||
733 strcmp(schema_name, "pg_toast") == 0 ||
734 strcmp(schema_name, "information_schema") == 0 ||
735 strcmp(schema_name, NSPNAME) == 0)
742 * dbms_stats_is_system_catalog
743 * called by sql function 'dbms_stats.is_system_catalog', and return the
744 * result of the function 'dbms_stats_is_system_catalog_internal'.
747 dbms_stats_is_system_catalog(PG_FUNCTION_ARGS)
753 PG_RETURN_BOOL(true);
755 relid = PG_GETARG_OID(0);
756 result = dbms_stats_is_system_catalog_internal(relid);
758 PG_RETURN_BOOL(result);
762 * dbms_stats_is_system_catalog_internal
763 * Check whether the given relation is one of system catalogs.
766 dbms_stats_is_system_catalog_internal(Oid relid)
772 /* relid is InvalidOid */
773 if (!OidIsValid(relid))
776 /* no such relation */
777 rel = try_relation_open(relid, NoLock);
781 /* check by namespace name. */
782 schema_name = get_namespace_name(rel->rd_rel->relnamespace);
783 result = dbms_stats_is_system_schema_internal(schema_name);
784 relation_close(rel, NoLock);
790 * dbms_stats_get_relation_info
791 * Hook function for get_relation_info_hook, which implements post-process of
792 * get_relation_info().
794 * This function is designed on the basis of the fact that only expression
795 * indexes have statistics.
798 dbms_stats_get_relation_info(PlannerInfo *root,
804 double allvisfrac; /* dummy */
807 * Call previously installed hook function regardless to whether
808 * pg_dbms_stats is enabled or not.
810 if (prev_get_relation_info)
811 prev_get_relation_info(root, relid, inhparent, rel);
813 /* If pg_dbms_stats is disabled, there is no more thing to do. */
814 if (!pg_dbms_stats_use_locked_stats)
818 * Adjust stats of table itself, and stats of index
819 * relation_stats_effective as well
823 * Estimate relation size --- unless it's an inheritance parent, in which
824 * case the size will be computed later in set_append_rel_pathlist, and we
825 * must leave it zero for now to avoid bollixing the total_table_pages
830 #if PG_VERSION_NUM >= 90200
831 get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
832 &rel->allvisfrac, true);
834 get_merged_relation_stats(relid, &rel->pages, &rel->tuples,
841 foreach(lc, rel->indexlist)
844 * Estimate the index size. If it's not a partial index, we lock
845 * the number-of-tuples estimate to equal the parent table; if it
846 * is partial then we have to use the same methods as we would for
847 * a table, except we can be sure that the index is not larger
850 IndexOptInfo *info = (IndexOptInfo *) lfirst(lc);
851 bool estimate = info->indpred != NIL;
853 get_merged_relation_stats(info->indexoid, &info->pages, &info->tuples,
854 &allvisfrac, estimate);
856 if (!estimate || (estimate && info->tuples > rel->tuples))
857 info->tuples = rel->tuples;
862 * dbms_stats_get_attavgwidth
863 * Hook function for get_attavgwidth_hook which replaces get_attavgwidth().
864 * Returning 0 tells caller to use standard routine.
867 dbms_stats_get_attavgwidth(Oid relid, AttrNumber attnum)
869 if (pg_dbms_stats_use_locked_stats)
871 int32 width = get_merged_avgwidth(relid, attnum);
876 if (prev_get_attavgwidth)
877 return prev_get_attavgwidth(relid, attnum);
883 * We do nothing here, to keep the tuple valid even after examination.
886 FreeHeapTuple(HeapTuple tuple)
892 * dbms_stats_get_relation_stats
893 * Hook function for get_relation_stats_hook which provides custom
894 * per-relation statistics.
895 * Returning false tells caller to use standard (true) statistics.
898 dbms_stats_get_relation_stats(PlannerInfo *root,
901 VariableStatData *vardata)
903 if (pg_dbms_stats_use_locked_stats)
907 tuple = get_merged_column_stats(rte->relid, attnum, rte->inh);
908 vardata->statsTuple = tuple;
911 vardata->freefunc = FreeHeapTuple;
916 if (prev_get_relation_stats)
917 return prev_get_relation_stats(root, rte, attnum, vardata);
923 * dbms_stats_get_index_stats
924 * Hook function for get_index_stats_hook which provides custom per-relation
926 * Returning false tells caller to use standard (true) statistics.
929 dbms_stats_get_index_stats(PlannerInfo *root,
931 AttrNumber indexattnum,
932 VariableStatData *vardata)
934 if (pg_dbms_stats_use_locked_stats)
938 tuple = get_merged_column_stats(indexOid, indexattnum, false);
939 vardata->statsTuple = tuple;
942 vardata->freefunc = FreeHeapTuple;
947 if (prev_get_index_stats)
948 return prev_get_index_stats(root, indexOid, indexattnum, vardata);
954 * Extract binary value from given column.
957 get_binary_datum(int column, bool *isnull)
959 return SPI_getbinval(SPI_tuptable->vals[0],
960 SPI_tuptable->tupdesc, column, isnull);
964 * get_merged_relation_stats
965 * get the statistics of the table, # of pages and # of rows, by executing
966 * SELECT against dbms_stats.relation_stats_locked view.
969 get_merged_relation_stats(Oid relid, BlockNumber *pages, double *tuples,
970 double *allvisfrac, bool estimate)
972 StatsRelationEntry *entry;
976 /* avoid recursive call and system objects */
977 if (nested_level > 0 || relid < FirstNormalObjectId)
981 * pg_dbms_stats doesn't handle system catalogs and its internal relation_stats_effective
983 if (dbms_stats_is_system_catalog_internal(relid))
987 * First, search from cache. If we have not cached stats for given relid
988 * yet, initialize newly created entry.
990 entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
992 init_rel_stats_entry(entry, relid);
997 * Valid entry with invalid relpage is a negative cache, which
998 * eliminates excessive SPI calls below. Negative caches will be
999 * invalidated again on invalidation of system relation cache, which
1000 * occur on modification of the dummy stats tables
1001 * dbms_stats.relation_stats_locked and column_stats_locked.
1003 if (entry->relpages == InvalidBlockNumber)
1009 * If we don't have valid cache entry, retrieve system stats and dummy
1010 * stats in dbms_stats.relation_stats_locked, then merge them for
1011 * planner use. We also cache curpages value to make plans stable.
1021 * Retrieve per-relation dummy statistics from
1022 * relation_stats_locked table via SPI.
1024 has_dummy = execute_plan(&rows_plan, rows_query, relid, NULL, true);
1027 /* If dummy stats is not found, store negative cache. */
1028 entry->relpages = InvalidBlockNumber;
1033 * Retrieve per-relation system stats from pg_class. We use
1034 * syscache to support indexes
1041 tuple = SearchSysCache1(RELOID, ObjectIdGetDatum(relid));
1042 if (!HeapTupleIsValid(tuple))
1043 elog(ERROR, "cache lookup failed for relation %u", relid);
1044 form = (Form_pg_class) GETSTRUCT(tuple);
1046 /* Choose dummy or authentic */
1047 val = get_binary_datum(1, &isnull);
1048 entry->relpages = isnull ? form->relpages :
1049 (BlockNumber) DatumGetInt32(val);
1050 val = get_binary_datum(2, &isnull);
1051 entry->reltuples = isnull ? form->reltuples :
1052 (double) DatumGetFloat4(val);
1053 val = get_binary_datum(3, &isnull);
1054 entry->curpages = isnull ? InvalidBlockNumber :
1055 (BlockNumber) DatumGetInt32(val);
1056 #if PG_VERSION_NUM >= 90200
1057 val = get_binary_datum(4, &isnull);
1058 entry->relallvisible = isnull ? form->relallvisible :
1059 (BlockNumber) DatumGetInt32(val);
1062 ReleaseSysCache(tuple);
1064 entry->valid = true;
1076 * If no dummy statistics available for this relation, do nothing then
1077 * return immediately.
1083 /* Tweaking statistics using merged statistics */
1086 *pages = entry->relpages;
1087 *tuples = entry->reltuples;
1092 * Get current number of pages to estimate current number of tuples, based
1093 * on tuple density at the last ANALYZE and current number of pages.
1095 rel = relation_open(relid, NoLock);
1096 rel->rd_rel->relpages = entry->relpages;
1097 rel->rd_rel->reltuples = entry->reltuples;
1098 #if PG_VERSION_NUM >= 90200
1099 rel->rd_rel->relallvisible = entry->relallvisible;
1101 dbms_stats_estimate_rel_size(rel, NULL, pages, tuples, allvisfrac,
1103 relation_close(rel, NoLock);
1107 * get_merged_avgwidth
1108 * get average width of the given column by merging dummy and authentic
1112 get_merged_avgwidth(Oid relid, AttrNumber attnum)
1116 if (nested_level > 0 || relid < FirstNormalObjectId)
1117 return 0; /* avoid recursive call and system objects */
1119 if ((tuple = get_merged_column_stats(relid, attnum, false)) == NULL)
1122 return get_pg_statistic(tuple)->stawidth;
1126 * get_merged_column_stats
1127 * returns per-column statistics for given column
1129 * This caches the result to avoid excessive SPI calls for repetitive
1130 * request for every columns many time.
1133 get_merged_column_stats(Oid relid, AttrNumber attnum, bool inh)
1136 HeapTuple statsTuple;
1137 bool negative = false;
1139 if (nested_level > 0 || relid < FirstNormalObjectId)
1140 return NULL; /* avoid recursive call and system objects */
1143 * Return NULL for system catalog, directing the caller to use system
1146 if (dbms_stats_is_system_catalog_internal(relid))
1149 /* Return cached statistics, if any. */
1150 if ((tuple = column_cache_search(relid, attnum, inh, &negative)) != NULL)
1153 /* Obtain system statistics from syscache. */
1154 statsTuple = SearchSysCache3(STATRELATTINH,
1155 ObjectIdGetDatum(relid),
1156 Int16GetDatum(attnum),
1161 * Return system statistics whatever it is if negative cache for this
1162 * column is returned
1164 tuple = heap_copytuple(statsTuple);
1169 * Search for dummy statistics and try merge with system stats.
1174 * Save current context in order to use during SPI is
1177 MemoryContext outer_cxt = CurrentMemoryContext;
1183 /* Obtain dummy statistics for the column using SPI call. */
1185 execute_plan(&tuple_plan, tuple_query, relid, &attnum, inh);
1187 /* Reset to the outer memory context for following steps. */
1188 MemoryContextSwitchTo(outer_cxt);
1192 /* merge the dummy statistics with the system statistics */
1193 tuple = dbms_stats_merge_internal(SPI_tuptable->vals[0],
1195 SPI_tuptable->tupdesc);
1200 /* Cache merged result for subsequent calls. */
1201 tuple = column_cache_enter(relid, attnum, inh, tuple);
1203 /* Return system stats if the merging results in failure. */
1204 if (!HeapTupleIsValid(tuple))
1205 tuple = heap_copytuple(statsTuple);
1218 if (HeapTupleIsValid(statsTuple))
1219 ReleaseSysCache(statsTuple);
1225 * column_cache_search
1226 * Search statistic of the given column from the cache.
1229 column_cache_search(Oid relid, AttrNumber attnum, bool inh, bool *negative)
1231 StatsRelationEntry *entry;
1237 * First, get cached relation stats. If we have not cached relation stats,
1238 * we don't have column stats too.
1240 entry = hash_search(rel_stats, &relid, HASH_FIND, &found);
1245 * We assume that not so many column_stats_effective are defined on one
1246 * relation, so we use simple linear-search here. Hash table would be an
1247 * alternative, but it seems overkill so far.
1249 foreach(lc, entry->col_stats)
1251 StatsColumnEntry *ent = (StatsColumnEntry*) lfirst (lc);
1253 if (ent->attnum != attnum || ent->inh != inh) continue;
1257 /* Retrun NULL for negative cache, with noticing of that.*/
1265 return NULL; /* Not yet registered. */
1269 * Cache a per-column statistics. Storing in CacheMemoryContext, the cached
1270 * statistics will live through the current session, unless dummy statistics or
1271 * table definition have been changed.
1274 column_cache_enter(Oid relid, int32 attnum, bool inh, HeapTuple tuple)
1276 MemoryContext oldcontext;
1277 StatsColumnEntry *newcolent;
1278 StatsRelationEntry *entry;
1281 Assert(tuple == NULL || !heap_attisnull(tuple, 1));
1283 entry = hash_search(rel_stats, &relid, HASH_ENTER, &found);
1285 init_rel_stats_entry(entry, relid);
1288 * Adding this column stats to the column stats list of the relation stats
1289 * cache just obtained.
1291 oldcontext = MemoryContextSwitchTo(CacheMemoryContext);
1292 newcolent = (StatsColumnEntry*)palloc(sizeof(StatsColumnEntry));
1293 newcolent->attnum = attnum;
1294 newcolent->inh = inh;
1296 if (HeapTupleIsValid(tuple))
1298 newcolent->negative = false;
1299 newcolent->tuple = heap_copytuple(tuple);
1303 /* Invalid tuple makes a negative cache. */
1304 newcolent->negative = true;
1305 newcolent->tuple = NULL;
1308 entry->col_stats = lappend(entry->col_stats, newcolent);
1309 MemoryContextSwitchTo(oldcontext);
1311 return newcolent->tuple;
1315 * Execute given plan. When given plan is NULL, create new plan from given
1316 * query string, and execute it. This function can be used only for retrieving
1317 * statistics of column_stats_effective and relation_stats_effective, because we assume #, types, and order
1318 * of parameters here.
1321 execute_plan(SPIPlanPtr *plan,
1324 const AttrNumber *attnum,
1328 Oid argtypes[3] = { OIDOID, INT2OID, BOOLOID };
1331 bool nulls[3] = { false, false, false };
1333 int save_sec_context;
1335 /* XXXX: this works for now but should be fixed later.. */
1336 nargs = (attnum ? 3 : 1);
1339 * The dummy statistics table allows access from no one other than its
1340 * owner or superuser.
1342 GetUserIdAndSecContext(&save_userid, &save_sec_context);
1343 SetUserIdAndSecContext(get_stats_table_owner(),
1344 save_sec_context | SECURITY_LOCAL_USERID_CHANGE);
1348 /* Create plan from the query if not yet. */
1351 *plan = SPI_prepare(query, nargs, argtypes);
1354 "pg_dbms_stats: SPI_prepare() failed. result = %d",
1357 SPI_keepplan(*plan);
1360 values[0] = ObjectIdGetDatum(relid);
1361 values[1] = Int16GetDatum(attnum ? *attnum : 0);
1362 values[2] = BoolGetDatum(inh);
1364 ret = SPI_execute_plan(*plan, values, nulls, true, 1);
1368 SetUserIdAndSecContext(save_userid, save_sec_context);
1369 if (geterrcode() == ERRCODE_INSUFFICIENT_PRIVILEGE)
1370 errdetail("dbms_stats could not access the object as the role \"%s\".",
1371 stats_table_owner_name);
1372 errhint("Check your settings of pg_dbms_stats.");
1377 SetUserIdAndSecContext(save_userid, save_sec_context);
1378 if (ret != SPI_OK_SELECT)
1379 elog(ERROR, "pg_dbms_stats: SPI_execute_plan() returned %d", ret);
1381 return SPI_processed > 0;
1385 * StatsCacheRelCallback
1386 * Relcache inval callback function
1388 * Invalidate cached statistic info of the given relid, or all cached statistic
1389 * info if relid == InvalidOid. We don't complain even when we don't have such
1392 * Note: arg is not used.
1395 StatsCacheRelCallback(Datum arg, Oid relid)
1397 HASH_SEQ_STATUS status;
1398 StatsRelationEntry *entry;
1400 hash_seq_init(&status, rel_stats);
1401 while ((entry = hash_seq_search(&status)) != NULL)
1403 if (relid == InvalidOid || relid == entry->relid)
1407 /* Mark the relation entry as INVALID */
1408 entry->valid = false;
1410 /* Discard every column statistics */
1411 foreach (lc, entry->col_stats)
1413 StatsColumnEntry *ent = (StatsColumnEntry*) lfirst(lc);
1419 list_free(entry->col_stats);
1420 entry->col_stats = NIL;
1424 /* We always check throughout the list, so hash_seq_term is not necessary */
1428 * Initialize hash table for per-relation statistics.
1431 init_rel_stats(void)
1436 /* Prevent double initialization. */
1437 if (rel_stats != NULL)
1440 MemSet(&ctl, 0, sizeof(ctl));
1441 ctl.keysize = sizeof(Oid);
1442 ctl.entrysize = sizeof(StatsRelationEntry);
1443 ctl.hash = oid_hash;
1444 ctl.hcxt = CacheMemoryContext;
1445 hash = hash_create("dbms_stats relation statistics cache",
1447 &ctl, HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION);
1453 * Initialize newly added cache entry so that it represents an invalid cache
1454 * entry for given relid.
1457 init_rel_stats_entry(StatsRelationEntry *entry, Oid relid)
1459 entry->relid = relid;
1460 entry->valid = false;
1461 entry->relpages = InvalidBlockNumber;
1462 entry->reltuples = 0.0;
1463 entry->relallvisible = InvalidBlockNumber;
1464 entry->curpages = InvalidBlockNumber;
1465 entry->col_stats = NIL;
1469 * dbms_stats_estimate_rel_size - estimate # pages and # tuples in a table or
1472 * We also estimate the fraction of the pages that are marked all-visible in
1473 * the visibility map, for use in estimation of index-only scans.
1475 * If attr_widths isn't NULL, it points to the zero-index entry of the
1476 * relation's attr_widths[] cache; we fill this in if we have need to compute
1477 * the attribute widths for estimation purposes.
1479 * Note: This function is copied from plancat.c in core source tree of version
1480 * 9.2, and customized for pg_dbms_stats. Changes from original one are:
1481 * - rename by prefixing dbms_stats_
1482 * - add 3 parameters (relpages, reltuples, curpage) to pass dummy curpage
1484 * - Get current # of pages only when supplied curpages is InvalidBlockNumber
1485 * - get fraction of all-visible-pages
1488 dbms_stats_estimate_rel_size(Relation rel, int32 *attr_widths,
1489 BlockNumber *pages, double *tuples,
1490 double *allvisfrac, BlockNumber curpages)
1492 BlockNumber relpages;
1494 BlockNumber relallvisible;
1497 switch (rel->rd_rel->relkind)
1499 case RELKIND_RELATION:
1501 #if PG_VERSION_NUM >= 90300
1502 case RELKIND_MATVIEW:
1504 case RELKIND_TOASTVALUE:
1505 /* it has storage, ok to call the smgr */
1506 if (curpages == InvalidBlockNumber)
1507 curpages = RelationGetNumberOfBlocks(rel);
1510 * HACK: if the relation has never yet been vacuumed, use a
1511 * minimum size estimate of 10 pages. The idea here is to avoid
1512 * assuming a newly-created table is really small, even if it
1513 * currently is, because that may not be true once some data gets
1514 * loaded into it. Once a vacuum or analyze cycle has been done
1515 * on it, it's more reasonable to believe the size is somewhat
1518 * (Note that this is only an issue if the plan gets cached and
1519 * used again after the table has been filled. What we're trying
1520 * to avoid is using a nestloop-type plan on a table that has
1521 * grown substantially since the plan was made. Normally,
1522 * autovacuum/autoanalyze will occur once enough inserts have
1523 * happened and cause cached-plan invalidation; but that doesn't
1524 * happen instantaneously, and it won't happen at all for cases
1525 * such as temporary tables.)
1527 * We approximate "never vacuumed" by "has relpages = 0", which
1528 * means this will also fire on genuinely empty relation_stats_effective. Not
1529 * great, but fortunately that's a seldom-seen case in the real
1530 * world, and it shouldn't degrade the quality of the plan too
1531 * much anyway to err in this direction.
1533 * There are two exceptions wherein we don't apply this heuristic.
1534 * One is if the table has inheritance children. Totally empty
1535 * parent tables are quite common, so we should be willing to
1536 * believe that they are empty. Also, we don't apply the 10-page
1537 * minimum to indexes.
1539 if (curpages < 10 &&
1540 rel->rd_rel->relpages == 0 &&
1541 !rel->rd_rel->relhassubclass &&
1542 rel->rd_rel->relkind != RELKIND_INDEX)
1545 /* report estimated # pages */
1547 /* quick exit if rel is clearly empty */
1554 /* coerce values in pg_class to more desirable types */
1555 relpages = (BlockNumber) rel->rd_rel->relpages;
1556 reltuples = (double) rel->rd_rel->reltuples;
1557 #if PG_VERSION_NUM >= 90200
1558 relallvisible = (BlockNumber) rel->rd_rel->relallvisible;
1563 * If it's an index, discount the metapage while estimating the
1564 * number of tuples. This is a kluge because it assumes more than
1565 * it ought to about index structure. Currently it's OK for
1566 * btree, hash, and GIN indexes but suspect for GiST indexes.
1568 if (rel->rd_rel->relkind == RELKIND_INDEX &&
1575 /* estimate number of tuples from previous tuple density */
1577 density = reltuples / (double) relpages;
1581 * When we have no data because the relation was truncated,
1582 * estimate tuple width from attribute datatypes. We assume
1583 * here that the pages are completely full, which is OK for
1584 * tables (since they've presumably not been VACUUMed yet) but
1585 * is probably an overestimate for indexes. Fortunately
1586 * get_relation_info() can clamp the overestimate to the
1587 * parent table's size.
1589 * Note: this code intentionally disregards alignment
1590 * considerations, because (a) that would be gilding the lily
1591 * considering how crude the estimate is, and (b) it creates
1592 * platform dependencies in the default plans which are kind
1593 * of a headache for regression testing.
1597 tuple_width = dbms_stats_get_rel_data_width(rel, attr_widths);
1598 tuple_width += sizeof(HeapTupleHeaderData);
1599 tuple_width += sizeof(ItemPointerData);
1600 /* note: integer division is intentional here */
1601 density = (BLCKSZ - SizeOfPageHeaderData) / tuple_width;
1603 *tuples = rint(density * (double) curpages);
1606 * We use relallvisible as-is, rather than scaling it up like we
1607 * do for the pages and tuples counts, on the theory that any
1608 * pages added since the last VACUUM are most likely not marked
1609 * all-visible. But costsize.c wants it converted to a fraction.
1611 if (relallvisible == 0 || curpages <= 0)
1613 else if ((double) relallvisible >= curpages)
1616 *allvisfrac = (double) relallvisible / curpages;
1618 case RELKIND_SEQUENCE:
1619 /* Sequences always have a known size */
1624 case RELKIND_FOREIGN_TABLE:
1625 /* Just use whatever's in pg_class */
1626 *pages = rel->rd_rel->relpages;
1627 *tuples = rel->rd_rel->reltuples;
1631 /* else it has no disk storage; probably shouldn't get here? */
1640 * dbms_stats_get_rel_data_width
1642 * Estimate the average width of (the data part of) the relation's tuples.
1644 * If attr_widths isn't NULL, it points to the zero-index entry of the
1645 * relation's attr_widths[] cache; use and update that cache as appropriate.
1647 * Currently we ignore dropped column_stats_effective. Ideally those should be included
1648 * in the result, but we haven't got any way to get info about them; and
1649 * since they might be mostly NULLs, treating them as zero-width is not
1650 * necessarily the wrong thing anyway.
1652 * Note: This function is copied from plancat.c in core source tree of version
1653 * 9.2, and just renamed.
1656 dbms_stats_get_rel_data_width(Relation rel, int32 *attr_widths)
1658 int32 tuple_width = 0;
1661 for (i = 1; i <= RelationGetNumberOfAttributes(rel); i++)
1663 Form_pg_attribute att = rel->rd_att->attrs[i - 1];
1666 if (att->attisdropped)
1669 /* use previously cached data, if any */
1670 if (attr_widths != NULL && attr_widths[i] > 0)
1672 tuple_width += attr_widths[i];
1676 /* This should match set_rel_width() in costsize.c */
1677 item_width = get_attavgwidth(RelationGetRelid(rel), i);
1678 if (item_width <= 0)
1680 item_width = get_typavgwidth(att->atttypid, att->atttypmod);
1681 Assert(item_width > 0);
1683 if (attr_widths != NULL)
1684 attr_widths[i] = item_width;
1685 tuple_width += item_width;
1692 void test_pg_dbms_stats(int *passed, int *total);
1693 static void test_init_rel_stats(int *passed, int *total);
1694 static void test_init_rel_stats_entry(int *passed, int *total);
1697 test_pg_dbms_stats(int *passed, int *total)
1699 int local_passed = 0;
1700 int local_total = 0;
1702 elog(WARNING, "==========");
1705 test_init_rel_stats(&local_passed, &local_total);
1706 test_init_rel_stats_entry(&local_passed, &local_total);
1708 elog(WARNING, "%s %d/%d passed", __FUNCTION__, local_passed, local_total);
1709 *passed += local_passed;
1710 *total += local_total;
1714 test_init_rel_stats_entry(int *passed, int *total)
1717 StatsRelationEntry entry;
1723 init_rel_stats_entry(&entry, 1234);
1724 if (entry.relid == 1234 &&
1725 entry.valid == false &&
1726 entry.relpages == InvalidBlockNumber &&
1727 entry.reltuples == 0 &&
1728 entry.relallvisible == InvalidBlockNumber &&
1729 entry.curpages == InvalidBlockNumber &&
1730 entry.col_stats == NIL)
1732 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1736 elog(WARNING, "%s-%d failed: initialized", __FUNCTION__, caseno);
1742 test_init_rel_stats(int *passed, int *total)
1745 static HTAB *org_rel_stats;
1753 if (rel_stats != NULL)
1755 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1759 elog(WARNING, "%s-%d failed: rel_stats is NULL", __FUNCTION__, caseno);
1766 org_rel_stats = rel_stats;
1768 if (org_rel_stats == rel_stats)
1770 elog(WARNING, "%s-%d ok", __FUNCTION__, caseno);
1774 elog(WARNING, "%s-%d failed: rel_stats changed from %p to %p",
1775 __FUNCTION__, caseno, org_rel_stats, rel_stats);