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