add support for vacuum/analyze
[online_analyze.git] / online_analyze.c
1 /*
2  * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru>
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *              notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *              notice, this list of conditions and the following disclaimer in the
12  *              documentation and/or other materials provided with the distribution.
13  * 3. Neither the name of the author nor the names of any co-contributors
14  *              may be used to endorse or promote products derived from this software
15  *              without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
18  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20  * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
22  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
23  * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
24  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
25  * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
26  * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
27  * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 #include "postgres.h"
31
32 #include "pgstat.h"
33 #include "access/transam.h"
34 #include "catalog/namespace.h"
35 #include "commands/vacuum.h"
36 #include "executor/executor.h"
37 #include "nodes/nodes.h"
38 #include "nodes/parsenodes.h"
39 #include "storage/bufmgr.h"
40 #include "utils/builtins.h"
41 #include "utils/hsearch.h"
42 #include "utils/memutils.h"
43 #include "utils/lsyscache.h"
44 #include "utils/guc.h"
45 #if PG_VERSION_NUM >= 90200
46 #include "catalog/pg_class.h"
47 #include "nodes/primnodes.h"
48 #include "tcop/utility.h"
49 #include "utils/rel.h"
50 #include "utils/relcache.h"
51 #include "utils/timestamp.h"
52 #if PG_VERSION_NUM >= 90500
53 #include "nodes/makefuncs.h"
54 #if PG_VERSION_NUM >= 100000
55 #include "utils/varlena.h"
56 #include "utils/regproc.h"
57 #endif
58 #endif
59 #endif
60
61 #ifdef PG_MODULE_MAGIC
62 PG_MODULE_MAGIC;
63 #endif
64
65 static bool online_analyze_enable = true;
66 static bool online_analyze_verbose = true;
67 static double online_analyze_scale_factor = 0.1;
68 static int online_analyze_threshold = 50;
69 static int online_analyze_capacity_threshold = 100000;
70 static double online_analyze_min_interval = 10000;
71 static int online_analyze_lower_limit = 0;
72
73 static ExecutorEnd_hook_type oldExecutorEndHook = NULL;
74 #if PG_VERSION_NUM >= 90200
75 static ProcessUtility_hook_type oldProcessUtilityHook = NULL;
76 #endif
77
78 typedef enum CmdKind
79 {
80         CK_SELECT = CMD_SELECT,
81         CK_UPDATE = CMD_UPDATE,
82         CK_INSERT = CMD_INSERT,
83         CK_DELETE = CMD_DELETE,
84         CK_TRUNCATE,
85         CK_FASTTRUNCATE,
86         CK_CREATE,
87         CK_ANALYZE,
88         CK_VACUUM
89 } CmdKind;
90
91
92 typedef enum
93 {
94         OATT_ALL                = 0x03,
95         OATT_PERSISTENT = 0x01,
96         OATT_TEMPORARY  = 0x02,
97         OATT_NONE               = 0x00
98 } OnlineAnalyzeTableType;
99
100 static const struct config_enum_entry online_analyze_table_type_options[] =
101 {
102         {"all", OATT_ALL, false},
103         {"persistent", OATT_PERSISTENT, false},
104         {"temporary", OATT_TEMPORARY, false},
105         {"none", OATT_NONE, false},
106         {NULL, 0, false},
107 };
108
109 static int online_analyze_table_type = (int)OATT_ALL;
110
111 typedef struct TableList {
112         int             nTables;
113         Oid             *tables;
114         char    *tableStr;
115 } TableList;
116
117 static TableList excludeTables = {0, NULL, NULL};
118 static TableList includeTables = {0, NULL, NULL};
119
120 typedef struct OnlineAnalyzeTableStat {
121         Oid                             tableid;
122         bool                    rereadStat;
123         PgStat_Counter  n_tuples;
124         PgStat_Counter  changes_since_analyze;
125         TimestampTz             autovac_analyze_timestamp;
126         TimestampTz             analyze_timestamp;
127 } OnlineAnalyzeTableStat;
128
129 static  MemoryContext   onlineAnalyzeMemoryContext = NULL;
130 static  HTAB    *relstats = NULL;
131
132 static void relstatsInit(void);
133
134 #if PG_VERSION_NUM < 100000
135 static int
136 oid_cmp(const void *a, const void *b)
137 {
138         if (*(Oid*)a == *(Oid*)b)
139                 return 0;
140         return (*(Oid*)a > *(Oid*)b) ? 1 : -1;
141 }
142 #endif
143
144 static const char *
145 tableListAssign(const char * newval, bool doit, TableList *tbl)
146 {
147         char            *rawname;
148         List            *namelist;
149         ListCell        *l;
150         Oid                     *newOids = NULL;
151         int                     nOids = 0,
152                                 i = 0;
153
154         rawname = pstrdup(newval);
155
156         if (!SplitIdentifierString(rawname, ',', &namelist))
157                 goto cleanup;
158
159         if (doit)
160         {
161                 nOids = list_length(namelist);
162                 newOids = malloc(sizeof(Oid) * (nOids+1));
163                 if (!newOids)
164                         elog(ERROR,"could not allocate %d bytes",
165                                  (int)(sizeof(Oid) * (nOids+1)));
166         }
167
168         foreach(l, namelist)
169         {
170                 char    *curname = (char *) lfirst(l);
171 #if PG_VERSION_NUM >= 90200
172                 Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
173                                                         stringToQualifiedNameList(curname)), NoLock, true);
174 #else
175                 Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
176                                                         stringToQualifiedNameList(curname)), true);
177 #endif
178
179                 if (relOid == InvalidOid)
180                 {
181 #if PG_VERSION_NUM >= 90100
182                         if (doit == false)
183 #endif
184                         elog(WARNING,"'%s' does not exist", curname);
185                         continue;
186                 }
187                 else if ( get_rel_relkind(relOid) != RELKIND_RELATION )
188                 {
189 #if PG_VERSION_NUM >= 90100
190                         if (doit == false)
191 #endif
192                                 elog(WARNING,"'%s' is not an table", curname);
193                         continue;
194                 }
195                 else if (doit)
196                 {
197                         newOids[i++] = relOid;
198                 }
199         }
200
201         if (doit)
202         {
203                 tbl->nTables = i;
204                 if (tbl->tables)
205                         free(tbl->tables);
206                 tbl->tables = newOids;
207                 if (tbl->nTables > 1)
208                         qsort(tbl->tables, tbl->nTables, sizeof(tbl->tables[0]), oid_cmp);
209         }
210
211         pfree(rawname);
212         list_free(namelist);
213
214         return newval;
215
216 cleanup:
217         if (newOids)
218                 free(newOids);
219         pfree(rawname);
220         list_free(namelist);
221         return NULL;
222 }
223
224 #if PG_VERSION_NUM >= 90100
225 static bool
226 excludeTablesCheck(char **newval, void **extra, GucSource source)
227 {
228         char *val;
229
230         val = (char*)tableListAssign(*newval, false, &excludeTables);
231
232         if (val)
233         {
234                 *newval = val;
235                 return true;
236         }
237
238         return false;
239 }
240
241 static void
242 excludeTablesAssign(const char *newval, void *extra)
243 {
244         tableListAssign(newval, true, &excludeTables);
245 }
246
247 static bool
248 includeTablesCheck(char **newval, void **extra, GucSource source)
249 {
250         char *val;
251
252         val = (char*)tableListAssign(*newval, false, &includeTables);
253
254         if (val)
255         {
256                 *newval = val;
257                 return true;
258         }
259
260         return false;
261 }
262
263 static void
264 includeTablesAssign(const char *newval, void *extra)
265 {
266         tableListAssign(newval, true, &excludeTables);
267 }
268
269 #else /* PG_VERSION_NUM < 90100 */
270
271 static const char *
272 excludeTablesAssign(const char * newval, bool doit, GucSource source)
273 {
274         return tableListAssign(newval, doit, &excludeTables);
275 }
276
277 static const char *
278 includeTablesAssign(const char * newval, bool doit, GucSource source)
279 {
280         return tableListAssign(newval, doit, &includeTables);
281 }
282
283 #endif
284
285 static const char*
286 tableListShow(TableList *tbl)
287 {
288         char    *val, *ptr;
289         int             i,
290                         len;
291
292         len = 1 /* \0 */ + tbl->nTables * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */);
293         ptr = val = palloc(len);
294         *ptr ='\0';
295         for(i=0; i<tbl->nTables; i++)
296         {
297                 char    *relname = get_rel_name(tbl->tables[i]);
298                 Oid             nspOid = get_rel_namespace(tbl->tables[i]);
299                 char    *nspname = get_namespace_name(nspOid);
300
301                 if ( relname == NULL || nspOid == InvalidOid || nspname == NULL )
302                         continue;
303
304                 ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s",
305                                                                                                         (i==0) ? "" : ", ",
306                                                                                                         nspname, relname);
307         }
308
309         return val;
310 }
311
312 static const char*
313 excludeTablesShow(void)
314 {
315         return tableListShow(&excludeTables);
316 }
317
318 static const char*
319 includeTablesShow(void)
320 {
321         return tableListShow(&includeTables);
322 }
323
324 static bool
325 matchOid(TableList *tbl, Oid oid)
326 {
327         Oid     *StopLow = tbl->tables,
328                 *StopHigh = tbl->tables + tbl->nTables,
329                 *StopMiddle;
330
331         /* Loop invariant: StopLow <= val < StopHigh */
332         while (StopLow < StopHigh)
333         {
334                 StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
335
336                 if (*StopMiddle == oid)
337                         return true;
338                 else  if (*StopMiddle < oid)
339                         StopLow = StopMiddle + 1;
340                 else
341                         StopHigh = StopMiddle;
342         }
343
344         return false;
345 }
346
347 #if PG_VERSION_NUM >= 90500
348 static RangeVar*
349 makeRangeVarFromOid(Oid relOid)
350 {
351         return makeRangeVar(
352                                 get_namespace_name(get_rel_namespace(relOid)),
353                                 get_rel_name(relOid),
354                                 -1
355                         );
356
357 }
358 #endif
359
360 static void
361 makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
362 {
363         TimestampTz                             now = GetCurrentTimestamp();
364         Relation                                rel;
365         OnlineAnalyzeTableType  reltype;
366         bool                                    found = false,
367                                                         newTable = false;
368         OnlineAnalyzeTableStat  *rstat,
369                                                         dummyrstat;
370         PgStat_StatTabEntry             *tabentry = NULL;
371
372         if (relOid == InvalidOid)
373                 return;
374
375         if (naffected == 0)
376                 /* return if there is no changes */
377                 return;
378         else if (naffected < 0)
379                 /* number if affected rows is unknown */
380                 naffected = 0;
381
382         rel = RelationIdGetRelation(relOid);
383         if (rel->rd_rel->relkind != RELKIND_RELATION)
384         {
385                 RelationClose(rel);
386                 return;
387         }
388
389         reltype =
390 #if PG_VERSION_NUM >= 90100
391                 (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
392 #else
393                 (rel->rd_istemp || rel->rd_islocaltemp)
394 #endif
395                         ? OATT_TEMPORARY : OATT_PERSISTENT;
396
397         RelationClose(rel);
398
399         /*
400          * includeTables overwrites excludeTables
401          */
402         switch(online_analyze_table_type)
403         {
404                 case OATT_ALL:
405                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
406                                 (matchOid(&excludeTables, relOid) == true &&
407                                 matchOid(&includeTables, relOid) == false))
408                                 return;
409                         break;
410                 case OATT_NONE:
411                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
412                                 matchOid(&includeTables, relOid) == false)
413                                 return;
414                         break;
415                 case OATT_TEMPORARY:
416                 case OATT_PERSISTENT:
417                 default:
418                         /*
419                          * skip analyze if relation's type doesn't not match
420                          * online_analyze_table_type
421                          */
422                         if ((online_analyze_table_type & reltype) == 0 ||
423                                 matchOid(&excludeTables, relOid) == true)
424                         {
425                                 if (matchOid(&includeTables, relOid) == false)
426                                         return;
427                         }
428                         break;
429         }
430
431         /*
432          * Do not store data about persistent table in local memory because we
433          * could not track changes of them: they could be changed by another
434          * backends. So always get a pgstat table entry.
435          */
436         if (reltype == OATT_TEMPORARY)
437                 rstat = hash_search(relstats, &relOid, HASH_ENTER, &found);
438         else
439                 rstat = &dummyrstat; /* found == false for following if */
440
441         if (!found)
442         {
443                 MemSet(rstat, 0, sizeof(*rstat));
444                 rstat->tableid = relOid;
445                 newTable = true;
446         }
447         else if (operation == CK_VACUUM)
448         {
449                 /* force reread becouse vacuum could change n_tuples */
450                 rstat->rereadStat = true;
451                 return;
452         }
453         else if (operation == CK_ANALYZE)
454         {
455                 /* only analyze */
456                 rstat->changes_since_analyze = 0;
457                 return;
458         }
459
460         Assert(rstat->tableid == relOid);
461
462         /* do not rered data if it was a truncation */
463         if (operation != CK_TRUNCATE && operation != CK_FASTTRUNCATE &&
464                 (newTable == true || rstat->rereadStat == true))
465         {
466                 rstat->rereadStat = false;
467
468                 tabentry = pgstat_fetch_stat_tabentry(relOid);
469
470                 if (tabentry)
471                 {
472                         rstat->n_tuples = tabentry->n_dead_tuples + tabentry->n_live_tuples;
473                         rstat->changes_since_analyze =
474 #if PG_VERSION_NUM >= 90000
475                                 tabentry->changes_since_analyze;
476 #else
477                                 tabentry->n_live_tuples + tabentry->n_dead_tuples -
478                                         tabentry->last_anl_tuples;
479 #endif
480                         rstat->autovac_analyze_timestamp =
481                                 tabentry->autovac_analyze_timestamp;
482                         rstat->analyze_timestamp = tabentry->analyze_timestamp;
483                 }
484         }
485
486         if (newTable ||
487                 /* force analyze after truncate, fasttruncate already did analyze */
488                 operation == CK_TRUNCATE || (
489                 /* do not analyze too often, if both stamps are exceeded the go */
490                 TimestampDifferenceExceeds(rstat->analyze_timestamp, now, online_analyze_min_interval) &&
491                 TimestampDifferenceExceeds(rstat->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
492                 /* do not analyze too small tables */
493                 rstat->n_tuples + rstat->changes_since_analyze + naffected > online_analyze_lower_limit &&
494                 /* be in sync with relation_needs_vacanalyze */
495                 ((double)(rstat->changes_since_analyze + naffected)) >=
496                          online_analyze_scale_factor * ((double)rstat->n_tuples) +
497                          (double)online_analyze_threshold))
498         {
499 #if PG_VERSION_NUM < 90500
500                 VacuumStmt                              vacstmt;
501 #else
502                 VacuumParams                    vacstmt;
503 #endif
504                 TimestampTz                             startStamp, endStamp;
505
506
507                 memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */
508
509                 memset(&vacstmt, 0, sizeof(vacstmt));
510
511                 vacstmt.freeze_min_age = -1;
512                 vacstmt.freeze_table_age = -1; /* ??? */
513
514 #if PG_VERSION_NUM < 90500
515                 vacstmt.type = T_VacuumStmt;
516                 vacstmt.relation = NULL;
517                 vacstmt.va_cols = NIL;
518 #if PG_VERSION_NUM >= 90000
519                 vacstmt.options = VACOPT_ANALYZE;
520                 if (online_analyze_verbose)
521                         vacstmt.options |= VACOPT_VERBOSE;
522 #else
523                 vacstmt.vacuum = vacstmt.full = false;
524                 vacstmt.analyze = true;
525                 vacstmt.verbose = online_analyze_verbose;
526 #endif
527 #else
528                 vacstmt.multixact_freeze_min_age = -1;
529                 vacstmt.multixact_freeze_table_age = -1;
530                 vacstmt.log_min_duration = -1;
531 #endif
532
533                 if (online_analyze_verbose)
534                         startStamp = GetCurrentTimestamp();
535
536                 analyze_rel(relOid,
537 #if PG_VERSION_NUM < 90500
538                         &vacstmt
539 #if PG_VERSION_NUM >= 90018
540                         , true
541 #endif
542                         , GetAccessStrategy(BAS_VACUUM)
543 #if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004)
544                         , true
545 #endif
546 #else
547                         makeRangeVarFromOid(relOid),
548                         VACOPT_ANALYZE | ((online_analyze_verbose) ? VACOPT_VERBOSE : 0),
549                         &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM)
550 #endif
551                 );
552
553                 if (online_analyze_verbose)
554                 {
555                         long    secs;
556                         int             microsecs;
557
558                         endStamp = GetCurrentTimestamp();
559                         TimestampDifference(startStamp, endStamp, &secs, &microsecs);
560                         elog(INFO, "analyze \"%s\" took %.02f seconds",
561                                 get_rel_name(relOid),
562                                 ((double)secs) + ((double)microsecs)/1.0e6);
563                 }
564
565                 rstat->autovac_analyze_timestamp = now;
566                 rstat->changes_since_analyze = 0;
567
568                 switch(operation)
569                 {
570                         case CK_CREATE:
571                         case CK_INSERT:
572                         case CK_UPDATE:
573                                 rstat->n_tuples += naffected;
574                         case CK_DELETE:
575                                 rstat->rereadStat = (reltype == OATT_PERSISTENT);
576                                 break;
577                         case CK_TRUNCATE:
578                         case CK_FASTTRUNCATE:
579                                 rstat->rereadStat = false;
580                                 rstat->n_tuples = 0;
581                                 break;
582                         default:
583                                 break;
584                 }
585
586                 /* update last analyze timestamp in local memory of backend */
587                 if (tabentry)
588                 {
589                         tabentry->analyze_timestamp = now;
590                         tabentry->changes_since_analyze = 0;
591                 }
592 #if 0
593                 /* force reload stat for new table */
594                 if (newTable)
595                         pgstat_clear_snapshot();
596 #endif
597         }
598         else
599         {
600 #if PG_VERSION_NUM >= 90000
601                 if (tabentry)
602                         tabentry->changes_since_analyze += naffected;
603 #endif
604                 switch(operation)
605                 {
606                         case CK_CREATE:
607                         case CK_INSERT:
608                                 rstat->changes_since_analyze += naffected;
609                                 rstat->n_tuples += naffected;
610                                 break;
611                         case CK_UPDATE:
612                                 rstat->changes_since_analyze += 2 * naffected;
613                                 rstat->n_tuples += naffected;
614                         case CK_DELETE:
615                                 rstat->changes_since_analyze += naffected;
616                                 break;
617                         case CK_TRUNCATE:
618                         case CK_FASTTRUNCATE:
619                                 rstat->changes_since_analyze = 0;
620                                 rstat->n_tuples = 0;
621                                 break;
622                         default:
623                                 break;
624                 }
625         }
626
627         /* Reset local cache if we are over limit */
628         if (hash_get_num_entries(relstats) > online_analyze_capacity_threshold)
629                 relstatsInit();
630 }
631
632 static Const*
633 isFastTruncateCall(QueryDesc *queryDesc)
634 {
635         TargetEntry     *te;
636         FuncExpr        *fe;
637         Const           *constval;
638
639         if (!(
640                   queryDesc->plannedstmt &&
641                   queryDesc->operation == CMD_SELECT &&
642                   queryDesc->plannedstmt->planTree &&
643                   queryDesc->plannedstmt->planTree->targetlist &&
644                   list_length(queryDesc->plannedstmt->planTree->targetlist) == 1 &&
645                   IsA(linitial(queryDesc->plannedstmt->planTree->targetlist), TargetEntry)
646                  ))
647                 return NULL;
648
649         te = linitial(queryDesc->plannedstmt->planTree->targetlist);
650
651         if (!IsA(te, TargetEntry))
652                 return NULL;
653
654         fe = (FuncExpr*)te->expr;
655
656         if (!(
657                   fe && IsA(fe, FuncExpr) &&
658                   fe->funcid >= FirstNormalObjectId &&
659                   fe->funcretset == false &&
660                   fe->funcresulttype == VOIDOID &&
661                   fe->funcvariadic == false &&
662                   list_length(fe->args) == 1 &&
663                   IsA(linitial(fe->args), Const)
664                  ))
665                 return NULL;
666
667         constval = linitial(fe->args);
668
669         if (!(
670                   IsA(constval,Const) &&
671                   constval->consttype == TEXTOID &&
672                   strcmp(get_func_name(fe->funcid), "fasttruncate") == 0
673                  ))
674                 return NULL;
675
676         return constval;
677 }
678
679
680
681 extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
682 void
683 onlineAnalyzeHooker(QueryDesc *queryDesc)
684 {
685         int64   naffected = -1;
686         Const   *constval;
687
688         if (queryDesc->estate)
689                 naffected = queryDesc->estate->es_processed;
690
691 #if PG_VERSION_NUM >= 90200
692         if (online_analyze_enable &&
693                 (constval = isFastTruncateCall(queryDesc)) != NULL)
694         {
695                 Datum           tblnamed = constval->constvalue;
696                 char            *tblname = text_to_cstring(DatumGetTextP(tblnamed));
697                 RangeVar        *tblvar =
698                         makeRangeVarFromNameList(stringToQualifiedNameList(tblname));
699
700                 makeAnalyze(RangeVarGetRelid(tblvar,
701                                                                          NoLock,
702                                                                          false),
703                                         CK_FASTTRUNCATE, -1);
704         }
705 #endif
706
707         if (online_analyze_enable && queryDesc->plannedstmt &&
708                         (queryDesc->operation == CMD_INSERT ||
709                          queryDesc->operation == CMD_UPDATE ||
710                          queryDesc->operation == CMD_DELETE
711 #if PG_VERSION_NUM < 90200
712                          || (queryDesc->operation == CMD_SELECT &&
713                                  queryDesc->plannedstmt->intoClause)
714 #endif
715                          ))
716         {
717 #if PG_VERSION_NUM < 90200
718                 if (queryDesc->operation == CMD_SELECT)
719                 {
720                         Oid     relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true);
721
722                         makeAnalyze(relOid, queryDesc->operation, naffected);
723                 }
724                 else
725 #endif
726                 if (queryDesc->plannedstmt->resultRelations &&
727                                  queryDesc->plannedstmt->rtable)
728                 {
729                         ListCell        *l;
730
731                         foreach(l, queryDesc->plannedstmt->resultRelations)
732                         {
733                                 int                             n = lfirst_int(l);
734                                 RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
735
736                                 if (rte->rtekind == RTE_RELATION)
737                                         makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected);
738                         }
739                 }
740         }
741
742         if (oldExecutorEndHook)
743                 oldExecutorEndHook(queryDesc);
744         else
745                 standard_ExecutorEnd(queryDesc);
746 }
747
748 #if PG_VERSION_NUM >= 90200
749 static void
750 onlineAnalyzeHookerUtility(
751 #if PG_VERSION_NUM >= 100000
752                                                    PlannedStmt *pstmt,
753 #else
754                                                    Node *parsetree,
755 #endif
756                                                    const char *queryString,
757 #if PG_VERSION_NUM >= 90300
758                                                         ProcessUtilityContext context, ParamListInfo params,
759 #if PG_VERSION_NUM >= 100000
760                                                         QueryEnvironment *queryEnv,
761 #endif
762 #else
763                                                         ParamListInfo params, bool isTopLevel,
764 #endif
765                                                         DestReceiver *dest, char *completionTag) {
766         List            *tblnames = NIL;
767         CmdKind         op = CK_INSERT;
768 #if PG_VERSION_NUM >= 100000
769         Node            *parsetree = NULL;
770
771         if (pstmt->commandType == CMD_UTILITY)
772                 parsetree = pstmt->utilityStmt;
773 #endif
774
775         if (parsetree && online_analyze_enable)
776         {
777                 if (IsA(parsetree, CreateTableAsStmt) &&
778                         ((CreateTableAsStmt*)parsetree)->into)
779                 {
780                         tblnames =
781                                 list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel));
782                         op = CK_CREATE;
783                 }
784                 else if (IsA(parsetree, TruncateStmt))
785                 {
786                         tblnames = list_copy(((TruncateStmt*)parsetree)->relations);
787                         op = CK_TRUNCATE;
788                 }
789                 else if (IsA(parsetree, VacuumStmt))
790                 {
791                         VacuumStmt      *vac = (VacuumStmt*)parsetree;
792
793                         tblnames = list_make1(vac->relation);
794
795                         if (vac->options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE))
796                                 /* optionally with analyze */
797                                 op = CK_VACUUM;
798                         else if (vac->options & VACOPT_ANALYZE)
799                                 op = CK_ANALYZE;
800                         else
801                                 tblnames = NIL;
802                 }
803         }
804
805 #if PG_VERSION_NUM >= 100000
806 #define parsetree pstmt
807 #endif
808
809         if (oldProcessUtilityHook)
810                 oldProcessUtilityHook(parsetree, queryString,
811 #if PG_VERSION_NUM >= 90300
812                                                           context, params,
813 #if PG_VERSION_NUM >= 100000
814                                                           queryEnv,
815 #endif
816 #else
817                                                           params, isTopLevel,
818 #endif
819                                                           dest, completionTag);
820         else
821                 standard_ProcessUtility(parsetree, queryString,
822 #if PG_VERSION_NUM >= 90300
823                                                                 context, params,
824 #if PG_VERSION_NUM >= 100000
825                                                                 queryEnv,
826 #endif
827 #else
828                                                                 params, isTopLevel,
829 #endif
830                                                                 dest, completionTag);
831
832 #if PG_VERSION_NUM >= 100000
833 #undef parsetree
834 #endif
835
836         if (tblnames) {
837                 ListCell        *l;
838
839                 foreach(l, tblnames)
840                 {
841                         RangeVar        *tblname = (RangeVar*)lfirst(l);
842                         Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
843
844                         makeAnalyze(tblOid, op, -1);
845                 }
846         }
847 }
848 #endif
849
850 static void
851 relstatsInit(void)
852 {
853         HASHCTL hash_ctl;
854         int             flags = 0;
855
856         MemSet(&hash_ctl, 0, sizeof(hash_ctl));
857
858         hash_ctl.hash = oid_hash;
859         flags |= HASH_FUNCTION;
860
861         if (onlineAnalyzeMemoryContext)
862         {
863                 Assert(relstats != NULL);
864                 MemoryContextReset(onlineAnalyzeMemoryContext);
865         }
866         else
867         {
868                 Assert(relstats == NULL);
869                 onlineAnalyzeMemoryContext =
870                         AllocSetContextCreate(CacheMemoryContext,
871                                                                   "online_analyze storage context",
872 #if PG_VERSION_NUM < 90600
873                                                                   ALLOCSET_DEFAULT_MINSIZE,
874                                                                   ALLOCSET_DEFAULT_INITSIZE,
875                                                                   ALLOCSET_DEFAULT_MAXSIZE
876 #else
877                                                                   ALLOCSET_DEFAULT_SIZES
878 #endif
879                                                                  );
880         }
881
882         hash_ctl.hcxt = onlineAnalyzeMemoryContext;
883         flags |= HASH_CONTEXT;
884
885         hash_ctl.keysize = sizeof(Oid);
886
887         hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat);
888         flags |= HASH_ELEM;
889
890         relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags);
891 }
892
893 void _PG_init(void);
894 void
895 _PG_init(void)
896 {
897         relstatsInit();
898
899         oldExecutorEndHook = ExecutorEnd_hook;
900
901         ExecutorEnd_hook = onlineAnalyzeHooker;
902
903 #if PG_VERSION_NUM >= 90200
904         oldProcessUtilityHook = ProcessUtility_hook;
905
906         ProcessUtility_hook = onlineAnalyzeHookerUtility;
907 #endif
908
909
910         DefineCustomBoolVariable(
911                 "online_analyze.enable",
912                 "Enable on-line analyze",
913                 "Enables analyze of table directly after insert/update/delete/select into",
914                 &online_analyze_enable,
915 #if PG_VERSION_NUM >= 80400
916                 online_analyze_enable,
917 #endif
918                 PGC_USERSET,
919 #if PG_VERSION_NUM >= 80400
920                 GUC_NOT_IN_SAMPLE,
921 #if PG_VERSION_NUM >= 90100
922                 NULL,
923 #endif
924 #endif
925                 NULL,
926                 NULL
927         );
928
929         DefineCustomBoolVariable(
930                 "online_analyze.verbose",
931                 "Verbosity of on-line analyze",
932                 "Make ANALYZE VERBOSE after table's changes",
933                 &online_analyze_verbose,
934 #if PG_VERSION_NUM >= 80400
935                 online_analyze_verbose,
936 #endif
937                 PGC_USERSET,
938 #if PG_VERSION_NUM >= 80400
939                 GUC_NOT_IN_SAMPLE,
940 #if PG_VERSION_NUM >= 90100
941                 NULL,
942 #endif
943 #endif
944                 NULL,
945                 NULL
946         );
947
948         DefineCustomRealVariable(
949                 "online_analyze.scale_factor",
950                 "fraction of table size to start on-line analyze",
951                 "fraction of table size to start on-line analyze",
952                 &online_analyze_scale_factor,
953 #if PG_VERSION_NUM >= 80400
954                 online_analyze_scale_factor,
955 #endif
956                 0.0,
957                 1.0,
958                 PGC_USERSET,
959 #if PG_VERSION_NUM >= 80400
960                 GUC_NOT_IN_SAMPLE,
961 #if PG_VERSION_NUM >= 90100
962                 NULL,
963 #endif
964 #endif
965                 NULL,
966                 NULL
967         );
968
969         DefineCustomIntVariable(
970                 "online_analyze.threshold",
971                 "min number of row updates before on-line analyze",
972                 "min number of row updates before on-line analyze",
973                 &online_analyze_threshold,
974 #if PG_VERSION_NUM >= 80400
975                 online_analyze_threshold,
976 #endif
977                 0,
978                 0x7fffffff,
979                 PGC_USERSET,
980 #if PG_VERSION_NUM >= 80400
981                 GUC_NOT_IN_SAMPLE,
982 #if PG_VERSION_NUM >= 90100
983                 NULL,
984 #endif
985 #endif
986                 NULL,
987                 NULL
988         );
989
990         DefineCustomIntVariable(
991                 "online_analyze.capacity_threshold",
992                 "Max local cache table capacity",
993                 "Max local cache table capacity",
994                 &online_analyze_capacity_threshold,
995 #if PG_VERSION_NUM >= 80400
996                 online_analyze_capacity_threshold,
997 #endif
998                 0,
999                 0x7fffffff,
1000                 PGC_USERSET,
1001 #if PG_VERSION_NUM >= 80400
1002                 GUC_NOT_IN_SAMPLE,
1003 #if PG_VERSION_NUM >= 90100
1004                 NULL,
1005 #endif
1006 #endif
1007                 NULL,
1008                 NULL
1009         );
1010
1011         DefineCustomRealVariable(
1012                 "online_analyze.min_interval",
1013                 "minimum time interval between analyze call (in milliseconds)",
1014                 "minimum time interval between analyze call (in milliseconds)",
1015                 &online_analyze_min_interval,
1016 #if PG_VERSION_NUM >= 80400
1017                 online_analyze_min_interval,
1018 #endif
1019                 0.0,
1020                 1e30,
1021                 PGC_USERSET,
1022 #if PG_VERSION_NUM >= 80400
1023                 GUC_NOT_IN_SAMPLE,
1024 #if PG_VERSION_NUM >= 90100
1025                 NULL,
1026 #endif
1027 #endif
1028                 NULL,
1029                 NULL
1030         );
1031
1032         DefineCustomEnumVariable(
1033                 "online_analyze.table_type",
1034                 "Type(s) of table for online analyze: all(default), persistent, temporary, none",
1035                 NULL,
1036                 &online_analyze_table_type,
1037 #if PG_VERSION_NUM >= 80400
1038                 online_analyze_table_type,
1039 #endif
1040                 online_analyze_table_type_options,
1041                 PGC_USERSET,
1042 #if PG_VERSION_NUM >= 80400
1043                 GUC_NOT_IN_SAMPLE,
1044 #if PG_VERSION_NUM >= 90100
1045                 NULL,
1046 #endif
1047 #endif
1048                 NULL,
1049                 NULL
1050         );
1051
1052         DefineCustomStringVariable(
1053                 "online_analyze.exclude_tables",
1054                 "List of tables which will not online analyze",
1055                 NULL,
1056                 &excludeTables.tableStr,
1057 #if PG_VERSION_NUM >= 80400
1058                 "",
1059 #endif
1060                 PGC_USERSET,
1061                 0,
1062 #if PG_VERSION_NUM >= 90100
1063                 excludeTablesCheck,
1064                 excludeTablesAssign,
1065 #else
1066                 excludeTablesAssign,
1067 #endif
1068                 excludeTablesShow
1069         );
1070
1071         DefineCustomStringVariable(
1072                 "online_analyze.include_tables",
1073                 "List of tables which will online analyze",
1074                 NULL,
1075                 &includeTables.tableStr,
1076 #if PG_VERSION_NUM >= 80400
1077                 "",
1078 #endif
1079                 PGC_USERSET,
1080                 0,
1081 #if PG_VERSION_NUM >= 90100
1082                 includeTablesCheck,
1083                 includeTablesAssign,
1084 #else
1085                 includeTablesAssign,
1086 #endif
1087                 includeTablesShow
1088         );
1089
1090         DefineCustomIntVariable(
1091                 "online_analyze.lower_limit",
1092                 "min number of rows in table to analyze",
1093                 "min number of rows in table to analyze",
1094                 &online_analyze_lower_limit,
1095 #if PG_VERSION_NUM >= 80400
1096                 online_analyze_lower_limit,
1097 #endif
1098                 0,
1099                 0x7fffffff,
1100                 PGC_USERSET,
1101 #if PG_VERSION_NUM >= 80400
1102                 GUC_NOT_IN_SAMPLE,
1103 #if PG_VERSION_NUM >= 90100
1104                 NULL,
1105 #endif
1106 #endif
1107                 NULL,
1108                 NULL
1109         );
1110
1111 }
1112
1113 void _PG_fini(void);
1114 void
1115 _PG_fini(void)
1116 {
1117         ExecutorEnd_hook = oldExecutorEndHook;
1118 #if PG_VERSION_NUM >= 90200
1119         ProcessUtility_hook = oldProcessUtilityHook;
1120 #endif
1121
1122         if (excludeTables.tables)
1123                 free(excludeTables.tables);
1124         if (includeTables.tables)
1125                 free(includeTables.tables);
1126
1127         excludeTables.tables = includeTables.tables = NULL;
1128         excludeTables.nTables = includeTables.nTables = 0;
1129 }