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