Remove _PG_fini Mikhail Litsarev
[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                                 /* FALLTHROUGH */
606                         case CK_DELETE:
607                                 rstat->rereadStat = (reltype == OATT_PERSISTENT);
608                                 break;
609                         case CK_TRUNCATE:
610                         case CK_FASTTRUNCATE:
611                                 rstat->rereadStat = false;
612                                 rstat->n_tuples = 0;
613                                 break;
614                         default:
615                                 break;
616                 }
617
618                 /* update last analyze timestamp in local memory of backend */
619                 if (tabentry)
620                 {
621                         tabentry->analyze_timestamp = now;
622                         tabentry->changes_since_analyze = 0;
623                 }
624 #if 0
625                 /* force reload stat for new table */
626                 if (newTable)
627                         pgstat_clear_snapshot();
628 #endif
629         }
630         else
631         {
632 #if PG_VERSION_NUM >= 90000
633                 if (tabentry)
634                         tabentry->changes_since_analyze += naffected;
635 #endif
636                 switch(operation)
637                 {
638                         case CK_CREATE:
639                         case CK_INSERT:
640                                 rstat->changes_since_analyze += naffected;
641                                 rstat->n_tuples += naffected;
642                                 break;
643                         case CK_UPDATE:
644                                 rstat->changes_since_analyze += 2 * naffected;
645                                 rstat->n_tuples += naffected;
646                                 break;
647                         case CK_DELETE:
648                                 rstat->changes_since_analyze += naffected;
649                                 break;
650                         case CK_TRUNCATE:
651                         case CK_FASTTRUNCATE:
652                                 rstat->changes_since_analyze = 0;
653                                 rstat->n_tuples = 0;
654                                 break;
655                         default:
656                                 break;
657                 }
658         }
659
660         /* Reset local cache if we are over limit */
661         if (hash_get_num_entries(relstats) > online_analyze_capacity_threshold)
662                 relstatsInit();
663 }
664
665 static Const*
666 isFastTruncateCall(QueryDesc *queryDesc)
667 {
668         TargetEntry     *te;
669         FuncExpr        *fe;
670         Const           *constval;
671
672         if (!(
673                   queryDesc->plannedstmt &&
674                   queryDesc->operation == CMD_SELECT &&
675                   queryDesc->plannedstmt->planTree &&
676                   queryDesc->plannedstmt->planTree->targetlist &&
677                   list_length(queryDesc->plannedstmt->planTree->targetlist) == 1
678                  ))
679                 return NULL;
680
681         te = linitial(queryDesc->plannedstmt->planTree->targetlist);
682
683         if (!IsA(te, TargetEntry))
684                 return NULL;
685
686         fe = (FuncExpr*)te->expr;
687
688         if (!(
689                   fe && IsA(fe, FuncExpr) &&
690                   fe->funcid >= FirstNormalObjectId &&
691                   fe->funcretset == false &&
692                   fe->funcresulttype == VOIDOID &&
693                   fe->funcvariadic == false &&
694                   list_length(fe->args) == 1
695                  ))
696                 return NULL;
697
698         constval = linitial(fe->args);
699
700         if (!(
701                   IsA(constval,Const) &&
702                   constval->consttype == TEXTOID &&
703                   strcmp(get_func_name(fe->funcid), "fasttruncate") == 0
704                  ))
705                 return NULL;
706
707         return constval;
708 }
709
710
711 extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
712 void
713 onlineAnalyzeHooker(QueryDesc *queryDesc)
714 {
715         int64   naffected = -1;
716         Const   *constval;
717
718         if (queryDesc->estate)
719                 naffected = queryDesc->estate->es_processed;
720
721 #if PG_VERSION_NUM >= 90200
722         if (online_analyze_enable &&
723                 (constval = isFastTruncateCall(queryDesc)) != NULL)
724         {
725                 Datum           tblnamed = constval->constvalue;
726                 char            *tblname = text_to_cstring(DatumGetTextP(tblnamed));
727                 RangeVar        *tblvar =
728                         makeRangeVarFromNameList(stringToQualifiedNameList(tblname));
729
730                 makeAnalyze(RangeVarGetRelid(tblvar,
731                                                                          NoLock,
732                                                                          false),
733                                         CK_FASTTRUNCATE, -1);
734         }
735 #endif
736
737         if (online_analyze_enable && queryDesc->plannedstmt &&
738                         (queryDesc->operation == CMD_INSERT ||
739                          queryDesc->operation == CMD_UPDATE ||
740                          queryDesc->operation == CMD_DELETE
741 #if PG_VERSION_NUM < 90200
742                          || (queryDesc->operation == CMD_SELECT &&
743                                  queryDesc->plannedstmt->intoClause)
744 #endif
745                          ))
746         {
747 #if PG_VERSION_NUM < 90200
748                 if (queryDesc->operation == CMD_SELECT)
749                 {
750                         Oid     relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true);
751
752                         makeAnalyze(relOid, queryDesc->operation, naffected);
753                 }
754                 else
755 #endif
756                 if (queryDesc->plannedstmt->resultRelations &&
757                                  queryDesc->plannedstmt->rtable)
758                 {
759                         ListCell        *l;
760
761                         foreach(l, queryDesc->plannedstmt->resultRelations)
762                         {
763                                 int                             n = lfirst_int(l);
764                                 RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
765
766                                 if (rte->rtekind == RTE_RELATION)
767                                         makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected);
768                         }
769                 }
770         }
771
772         if (oldExecutorEndHook)
773                 oldExecutorEndHook(queryDesc);
774         else
775                 standard_ExecutorEnd(queryDesc);
776 }
777
778 static List             *toremove = NIL;
779
780 /*
781  * removeTable called on transaction end, see call RegisterXactCallback() below
782  */
783 static void
784 removeTable(XactEvent event, void *arg)
785 {
786         ListCell        *cell;
787
788         switch(event)
789         {
790                 case XACT_EVENT_COMMIT:
791                         break;
792                 case XACT_EVENT_ABORT:
793                         toremove = NIL;
794                 default:
795                         return;
796         }
797
798         foreach(cell, toremove)
799         {
800                 Oid     relOid = lfirst_oid(cell);
801
802                 hash_search(relstats, &relOid, HASH_REMOVE, NULL);
803         }
804
805         toremove = NIL;
806 }
807
808 #if PG_VERSION_NUM >= 120000
809 static int
810 parse_vacuum_opt(VacuumStmt *vacstmt)
811 {
812         int                     options = vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE;
813         ListCell        *lc;
814
815         foreach(lc, vacstmt->options)
816         {
817                 DefElem *opt = (DefElem *) lfirst(lc);
818
819                 /* Parse common options for VACUUM and ANALYZE */
820                 if (strcmp(opt->defname, "verbose") == 0)
821                         options |= VACOPT_VERBOSE;
822                 else if (strcmp(opt->defname, "skip_locked") == 0)
823                         options |= VACOPT_SKIP_LOCKED;
824                 else if (strcmp(opt->defname, "analyze") == 0)
825                         options |= VACOPT_ANALYZE;
826                 else if (strcmp(opt->defname, "freeze") == 0)
827                         options |= VACOPT_FREEZE;
828                 else if (strcmp(opt->defname, "full") == 0)
829                         options |= VACOPT_FULL;
830                 else if (strcmp(opt->defname, "disable_page_skipping") == 0)
831                         options |= VACOPT_DISABLE_PAGE_SKIPPING;
832         }
833
834         return options;
835 }
836 #endif
837
838
839 #if PG_VERSION_NUM >= 90200
840 static void
841 onlineAnalyzeHookerUtility(
842 #if PG_VERSION_NUM >= 100000
843                                                    PlannedStmt *pstmt,
844 #else
845                                                    Node *parsetree,
846 #endif
847                                                    const char *queryString,
848 #if PG_VERSION_NUM >= 90300
849                                                         ProcessUtilityContext context, ParamListInfo params,
850 #if PG_VERSION_NUM >= 100000
851                                                         QueryEnvironment *queryEnv,
852 #endif
853 #else
854                                                         ParamListInfo params, bool isTopLevel,
855 #endif
856                                                         DestReceiver *dest,
857 #if  PG_VERSION_NUM >= 130000
858                                                         QueryCompletion *completionTag
859 #else
860                                                         char *completionTag
861 #endif
862 ) {
863         List            *tblnames = NIL;
864         CmdKind         op = CK_INSERT;
865 #if PG_VERSION_NUM >= 100000
866         Node            *parsetree = NULL;
867
868         if (pstmt->commandType == CMD_UTILITY)
869                 parsetree = pstmt->utilityStmt;
870 #endif
871
872         if (parsetree && online_analyze_enable)
873         {
874                 if (IsA(parsetree, CreateTableAsStmt) &&
875                         ((CreateTableAsStmt*)parsetree)->into)
876                 {
877                         tblnames =
878                                 list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel));
879                         op = CK_CREATE;
880                 }
881                 else if (IsA(parsetree, TruncateStmt))
882                 {
883                         tblnames = list_copy(((TruncateStmt*)parsetree)->relations);
884                         op = CK_TRUNCATE;
885                 }
886                 else if (IsA(parsetree, DropStmt) &&
887                                  ((DropStmt*)parsetree)->removeType == OBJECT_TABLE)
888                 {
889                         ListCell        *cell;
890
891                         foreach(cell, ((DropStmt*)parsetree)->objects)
892                         {
893                                 List            *relname = (List *) lfirst(cell);
894                                 RangeVar        *rel = makeRangeVarFromNameList(relname);
895                                 Oid                     relOid = RangeVarGetRelid(rel, NoLock, true);
896
897                                 if (OidIsValid(relOid))
898                                 {
899                                         MemoryContext   ctx;
900
901                                         ctx = MemoryContextSwitchTo(TopTransactionContext);
902                                         toremove = lappend_oid(toremove, relOid);
903                                         MemoryContextSwitchTo(ctx);
904                                 }
905                         }
906                 }
907                 else if (IsA(parsetree, VacuumStmt))
908                 {
909                         VacuumStmt      *vac = (VacuumStmt*)parsetree;
910                         int                     options =
911 #if PG_VERSION_NUM >= 120000
912                                                         parse_vacuum_opt(vac)
913 #else
914                                                         vac->options
915 #endif
916                                                         ;
917
918
919 #if PG_VERSION_NUM >= 110000
920                         tblnames = vac->rels;
921 #else
922                         if (vac->relation)
923                                 tblnames = list_make1(vac->relation);
924 #endif
925
926                         if (options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE))
927                         {
928                                 /* optionally with analyze */
929                                 op = CK_VACUUM;
930
931                                 /* drop all collected stat */
932                                 if (tblnames == NIL)
933                                         relstatsInit();
934                         }
935                         else if (options & VACOPT_ANALYZE)
936                         {
937                                 op = CK_ANALYZE;
938
939                                 /* should reset all counters */
940                                 if (tblnames == NIL)
941                                 {
942                                         HASH_SEQ_STATUS                 hs;
943                                         OnlineAnalyzeTableStat  *rstat;
944                                         TimestampTz                             now = GetCurrentTimestamp();
945
946                                         hash_seq_init(&hs, relstats);
947
948                                         while((rstat = hash_seq_search(&hs)) != NULL)
949                                         {
950                                                 rstat->changes_since_analyze = 0;
951                                                 rstat->analyze_timestamp = now;
952                                         }
953                                 }
954                         }
955                         else
956                                 tblnames = NIL;
957                 }
958         }
959
960 #if PG_VERSION_NUM >= 100000
961 #define parsetree pstmt
962 #endif
963
964         if (oldProcessUtilityHook)
965                 oldProcessUtilityHook(parsetree, queryString,
966 #if PG_VERSION_NUM >= 90300
967                                                           context, params,
968 #if PG_VERSION_NUM >= 100000
969                                                           queryEnv,
970 #endif
971 #else
972                                                           params, isTopLevel,
973 #endif
974                                                           dest, completionTag);
975         else
976                 standard_ProcessUtility(parsetree, queryString,
977 #if PG_VERSION_NUM >= 90300
978                                                                 context, params,
979 #if PG_VERSION_NUM >= 100000
980                                                                 queryEnv,
981 #endif
982 #else
983                                                                 params, isTopLevel,
984 #endif
985                                                                 dest, completionTag);
986
987 #if PG_VERSION_NUM >= 100000
988 #undef parsetree
989 #endif
990
991         if (tblnames) {
992                 ListCell        *l;
993
994                 foreach(l, tblnames)
995                 {
996                         RangeVar        *tblname =
997 #if PG_VERSION_NUM >= 110000
998                                 (IsA(lfirst(l), VacuumRelation)) ?
999                                         ((VacuumRelation*)lfirst(l))->relation :
1000 #endif
1001                                         (RangeVar*)lfirst(l);
1002                         Oid     tblOid;
1003
1004                         Assert(IsA(tblname, RangeVar));
1005
1006                         tblOid = RangeVarGetRelid(tblname, NoLock, true);
1007                         makeAnalyze(tblOid, op, -1);
1008                 }
1009         }
1010 }
1011 #endif
1012
1013
1014 static void
1015 relstatsInit(void)
1016 {
1017         HASHCTL hash_ctl;
1018         int             flags = 0;
1019
1020         MemSet(&hash_ctl, 0, sizeof(hash_ctl));
1021
1022         hash_ctl.hash = oid_hash;
1023         flags |= HASH_FUNCTION;
1024
1025         if (onlineAnalyzeMemoryContext)
1026         {
1027                 Assert(relstats != NULL);
1028                 MemoryContextReset(onlineAnalyzeMemoryContext);
1029         }
1030         else
1031         {
1032                 Assert(relstats == NULL);
1033
1034 #if PG_VERSION_NUM < 90600
1035                 onlineAnalyzeMemoryContext =
1036                         AllocSetContextCreate(CacheMemoryContext,
1037                         "online_analyze storage context",
1038                         ALLOCSET_DEFAULT_MINSIZE,
1039                         ALLOCSET_DEFAULT_INITSIZE,
1040                         ALLOCSET_DEFAULT_MAXSIZE
1041                         );
1042 #else
1043                 onlineAnalyzeMemoryContext =
1044                         AllocSetContextCreate(CacheMemoryContext,
1045                         "online_analyze storage context", ALLOCSET_DEFAULT_SIZES);
1046 #endif
1047         }
1048
1049         hash_ctl.hcxt = onlineAnalyzeMemoryContext;
1050         flags |= HASH_CONTEXT;
1051
1052         hash_ctl.keysize = sizeof(Oid);
1053
1054         hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat);
1055         flags |= HASH_ELEM;
1056
1057         relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags);
1058 }
1059
1060 void _PG_init(void);
1061 void
1062 _PG_init(void)
1063 {
1064         relstatsInit();
1065
1066         oldExecutorEndHook = ExecutorEnd_hook;
1067
1068         ExecutorEnd_hook = onlineAnalyzeHooker;
1069
1070 #if PG_VERSION_NUM >= 90200
1071         oldProcessUtilityHook = ProcessUtility_hook;
1072
1073         ProcessUtility_hook = onlineAnalyzeHookerUtility;
1074 #endif
1075
1076
1077         DefineCustomBoolVariable(
1078                 "online_analyze.enable",
1079                 "Enable on-line analyze",
1080                 "Enables analyze of table directly after insert/update/delete/select into",
1081                 &online_analyze_enable,
1082 #if PG_VERSION_NUM >= 80400
1083                 online_analyze_enable,
1084 #endif
1085                 PGC_USERSET,
1086 #if PG_VERSION_NUM >= 80400
1087                 GUC_NOT_IN_SAMPLE,
1088 #if PG_VERSION_NUM >= 90100
1089                 NULL,
1090 #endif
1091 #endif
1092                 NULL,
1093                 NULL
1094         );
1095
1096         DefineCustomBoolVariable(
1097                 "online_analyze.local_tracking",
1098                 "Per backend tracking",
1099                 "Per backend tracking for temp tables (do not use system statistic)",
1100                 &online_analyze_local_tracking,
1101 #if PG_VERSION_NUM >= 80400
1102                 online_analyze_local_tracking,
1103 #endif
1104                 PGC_USERSET,
1105 #if PG_VERSION_NUM >= 80400
1106                 GUC_NOT_IN_SAMPLE,
1107 #if PG_VERSION_NUM >= 90100
1108                 NULL,
1109 #endif
1110 #endif
1111                 NULL,
1112                 NULL
1113         );
1114
1115         DefineCustomBoolVariable(
1116                 "online_analyze.verbose",
1117                 "Verbosity of on-line analyze",
1118                 "Make ANALYZE VERBOSE after table's changes",
1119                 &online_analyze_verbose,
1120 #if PG_VERSION_NUM >= 80400
1121                 online_analyze_verbose,
1122 #endif
1123                 PGC_USERSET,
1124 #if PG_VERSION_NUM >= 80400
1125                 GUC_NOT_IN_SAMPLE,
1126 #if PG_VERSION_NUM >= 90100
1127                 NULL,
1128 #endif
1129 #endif
1130                 NULL,
1131                 NULL
1132         );
1133
1134         DefineCustomRealVariable(
1135                 "online_analyze.scale_factor",
1136                 "fraction of table size to start on-line analyze",
1137                 "fraction of table size to start on-line analyze",
1138                 &online_analyze_scale_factor,
1139 #if PG_VERSION_NUM >= 80400
1140                 online_analyze_scale_factor,
1141 #endif
1142                 0.0,
1143                 1.0,
1144                 PGC_USERSET,
1145 #if PG_VERSION_NUM >= 80400
1146                 GUC_NOT_IN_SAMPLE,
1147 #if PG_VERSION_NUM >= 90100
1148                 NULL,
1149 #endif
1150 #endif
1151                 NULL,
1152                 NULL
1153         );
1154
1155         DefineCustomIntVariable(
1156                 "online_analyze.threshold",
1157                 "min number of row updates before on-line analyze",
1158                 "min number of row updates before on-line analyze",
1159                 &online_analyze_threshold,
1160 #if PG_VERSION_NUM >= 80400
1161                 online_analyze_threshold,
1162 #endif
1163                 0,
1164                 0x7fffffff,
1165                 PGC_USERSET,
1166 #if PG_VERSION_NUM >= 80400
1167                 GUC_NOT_IN_SAMPLE,
1168 #if PG_VERSION_NUM >= 90100
1169                 NULL,
1170 #endif
1171 #endif
1172                 NULL,
1173                 NULL
1174         );
1175
1176         DefineCustomIntVariable(
1177                 "online_analyze.capacity_threshold",
1178                 "Max local cache table capacity",
1179                 "Max local cache table capacity",
1180                 &online_analyze_capacity_threshold,
1181 #if PG_VERSION_NUM >= 80400
1182                 online_analyze_capacity_threshold,
1183 #endif
1184                 0,
1185                 0x7fffffff,
1186                 PGC_USERSET,
1187 #if PG_VERSION_NUM >= 80400
1188                 GUC_NOT_IN_SAMPLE,
1189 #if PG_VERSION_NUM >= 90100
1190                 NULL,
1191 #endif
1192 #endif
1193                 NULL,
1194                 NULL
1195         );
1196
1197         DefineCustomRealVariable(
1198                 "online_analyze.min_interval",
1199                 "minimum time interval between analyze call (in milliseconds)",
1200                 "minimum time interval between analyze call (in milliseconds)",
1201                 &online_analyze_min_interval,
1202 #if PG_VERSION_NUM >= 80400
1203                 online_analyze_min_interval,
1204 #endif
1205                 0.0,
1206                 1e30,
1207                 PGC_USERSET,
1208 #if PG_VERSION_NUM >= 80400
1209                 GUC_NOT_IN_SAMPLE,
1210 #if PG_VERSION_NUM >= 90100
1211                 NULL,
1212 #endif
1213 #endif
1214                 NULL,
1215                 NULL
1216         );
1217
1218         DefineCustomEnumVariable(
1219                 "online_analyze.table_type",
1220                 "Type(s) of table for online analyze: all(default), persistent, temporary, none",
1221                 NULL,
1222                 &online_analyze_table_type,
1223 #if PG_VERSION_NUM >= 80400
1224                 online_analyze_table_type,
1225 #endif
1226                 online_analyze_table_type_options,
1227                 PGC_USERSET,
1228 #if PG_VERSION_NUM >= 80400
1229                 GUC_NOT_IN_SAMPLE,
1230 #if PG_VERSION_NUM >= 90100
1231                 NULL,
1232 #endif
1233 #endif
1234                 NULL,
1235                 NULL
1236         );
1237
1238         DefineCustomStringVariable(
1239                 "online_analyze.exclude_tables",
1240                 "List of tables which will not online analyze",
1241                 NULL,
1242                 &excludeTables.tableStr,
1243 #if PG_VERSION_NUM >= 80400
1244                 "",
1245 #endif
1246                 PGC_USERSET,
1247                 0,
1248 #if PG_VERSION_NUM >= 90100
1249                 excludeTablesCheck,
1250                 excludeTablesAssign,
1251 #else
1252                 excludeTablesAssign,
1253 #endif
1254                 excludeTablesShow
1255         );
1256
1257         DefineCustomStringVariable(
1258                 "online_analyze.include_tables",
1259                 "List of tables which will online analyze",
1260                 NULL,
1261                 &includeTables.tableStr,
1262 #if PG_VERSION_NUM >= 80400
1263                 "",
1264 #endif
1265                 PGC_USERSET,
1266                 0,
1267 #if PG_VERSION_NUM >= 90100
1268                 includeTablesCheck,
1269                 includeTablesAssign,
1270 #else
1271                 includeTablesAssign,
1272 #endif
1273                 includeTablesShow
1274         );
1275
1276         DefineCustomIntVariable(
1277                 "online_analyze.lower_limit",
1278                 "min number of rows in table to analyze",
1279                 "min number of rows in table to analyze",
1280                 &online_analyze_lower_limit,
1281 #if PG_VERSION_NUM >= 80400
1282                 online_analyze_lower_limit,
1283 #endif
1284                 0,
1285                 0x7fffffff,
1286                 PGC_USERSET,
1287 #if PG_VERSION_NUM >= 80400
1288                 GUC_NOT_IN_SAMPLE,
1289 #if PG_VERSION_NUM >= 90100
1290                 NULL,
1291 #endif
1292 #endif
1293                 NULL,
1294                 NULL
1295         );
1296
1297         RegisterXactCallback(removeTable, NULL);
1298 }
1299
1300 void _PG_fini(void);
1301 void
1302 _PG_fini(void)
1303 {
1304         ExecutorEnd_hook = oldExecutorEndHook;
1305 #if PG_VERSION_NUM >= 90200
1306         ProcessUtility_hook = oldProcessUtilityHook;
1307 #endif
1308
1309         if (excludeTables.tables)
1310                 free(excludeTables.tables);
1311         if (includeTables.tables)
1312                 free(includeTables.tables);
1313
1314         excludeTables.tables = includeTables.tables = NULL;
1315         excludeTables.nTables = includeTables.nTables = 0;
1316 }