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