b710d47f5b98952c7f550144682cee63beeb4463
[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         if (naffected == 0)
374                 /* return if there is no changes */
375                 return;
376         else if (naffected < 0)
377                 /* number if affected rows is unknown */
378                 naffected = 0;
379
380         rel = RelationIdGetRelation(relOid);
381         if (rel->rd_rel->relkind != RELKIND_RELATION)
382         {
383                 RelationClose(rel);
384                 return;
385         }
386
387         reltype =
388 #if PG_VERSION_NUM >= 90100
389                 (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
390 #else
391                 (rel->rd_istemp || rel->rd_islocaltemp)
392 #endif
393                         ? OATT_TEMPORARY : OATT_PERSISTENT;
394
395         RelationClose(rel);
396
397         /*
398          * includeTables overwrites excludeTables
399          */
400         switch(online_analyze_table_type)
401         {
402                 case OATT_ALL:
403                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
404                                 (matchOid(&excludeTables, relOid) == true &&
405                                 matchOid(&includeTables, relOid) == false))
406                                 return;
407                         break;
408                 case OATT_NONE:
409                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
410                                 matchOid(&includeTables, relOid) == false)
411                                 return;
412                         break;
413                 case OATT_TEMPORARY:
414                 case OATT_PERSISTENT:
415                 default:
416                         /*
417                          * skip analyze if relation's type doesn't not match
418                          * online_analyze_table_type
419                          */
420                         if ((online_analyze_table_type & reltype) == 0 ||
421                                 matchOid(&excludeTables, relOid) == true)
422                         {
423                                 if (matchOid(&includeTables, relOid) == false)
424                                         return;
425                         }
426                         break;
427         }
428
429         /*
430          * Do not store data about persistent table in local memory because we
431          * could not track changes of them: they could be changed by another
432          * backends. So always get a pgstat table entry.
433          */
434         if (reltype == OATT_TEMPORARY)
435                 rstat = hash_search(relstats, &relOid, HASH_ENTER, &found);
436         else
437                 rstat = &dummyrstat; /* found == false for following if */
438
439         if (!found)
440         {
441                 MemSet(rstat, 0, sizeof(*rstat));
442                 rstat->tableid = relOid;
443                 newTable = true;
444         }
445
446         Assert(rstat->tableid == relOid);
447
448         /* do not rered data if it was a truncation */
449         if (operation != CK_TRUNCATE && operation != CK_FASTTRUNCATE &&
450                 (newTable == true || rstat->rereadStat == true))
451         {
452                 rstat->rereadStat = false;
453
454                 tabentry = pgstat_fetch_stat_tabentry(relOid);
455
456                 if (tabentry)
457                 {
458                         rstat->n_tuples = tabentry->n_dead_tuples + tabentry->n_live_tuples;
459                         rstat->changes_since_analyze =
460 #if PG_VERSION_NUM >= 90000
461                                 tabentry->changes_since_analyze;
462 #else
463                                 tabentry->n_live_tuples + tabentry->n_dead_tuples -
464                                         tabentry->last_anl_tuples;
465 #endif
466                         rstat->autovac_analyze_timestamp =
467                                 tabentry->autovac_analyze_timestamp;
468                         rstat->analyze_timestamp = tabentry->analyze_timestamp;
469                 }
470         }
471
472         if (newTable ||
473                 /* force analyze after truncate, fasttruncate already did analyze */
474                 operation == CK_TRUNCATE || (
475                 /* do not analyze too often, if both stamps are exceeded the go */
476                 TimestampDifferenceExceeds(rstat->analyze_timestamp, now, online_analyze_min_interval) &&
477                 TimestampDifferenceExceeds(rstat->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
478                 /* do not analyze too small tables */
479                 rstat->n_tuples + rstat->changes_since_analyze + naffected > online_analyze_lower_limit &&
480                 /* be in sync with relation_needs_vacanalyze */
481                 ((double)(rstat->changes_since_analyze + naffected)) >=
482                          online_analyze_scale_factor * ((double)rstat->n_tuples) +
483                          (double)online_analyze_threshold))
484         {
485 #if PG_VERSION_NUM < 90500
486                 VacuumStmt                              vacstmt;
487 #else
488                 VacuumParams                    vacstmt;
489 #endif
490                 TimestampTz                             startStamp, endStamp;
491
492
493                 memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */
494
495                 memset(&vacstmt, 0, sizeof(vacstmt));
496
497                 vacstmt.freeze_min_age = -1;
498                 vacstmt.freeze_table_age = -1; /* ??? */
499
500 #if PG_VERSION_NUM < 90500
501                 vacstmt.type = T_VacuumStmt;
502                 vacstmt.relation = NULL;
503                 vacstmt.va_cols = NIL;
504 #if PG_VERSION_NUM >= 90000
505                 vacstmt.options = VACOPT_ANALYZE;
506                 if (online_analyze_verbose)
507                         vacstmt.options |= VACOPT_VERBOSE;
508 #else
509                 vacstmt.vacuum = vacstmt.full = false;
510                 vacstmt.analyze = true;
511                 vacstmt.verbose = online_analyze_verbose;
512 #endif
513 #else
514                 vacstmt.multixact_freeze_min_age = -1;
515                 vacstmt.multixact_freeze_table_age = -1;
516                 vacstmt.log_min_duration = -1;
517 #endif
518
519                 if (online_analyze_verbose)
520                         startStamp = GetCurrentTimestamp();
521
522                 analyze_rel(relOid,
523 #if PG_VERSION_NUM < 90500
524                         &vacstmt
525 #if PG_VERSION_NUM >= 90018
526                         , true
527 #endif
528                         , GetAccessStrategy(BAS_VACUUM)
529 #if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004)
530                         , true
531 #endif
532 #else
533                         makeRangeVarFromOid(relOid),
534                         VACOPT_ANALYZE | ((online_analyze_verbose) ? VACOPT_VERBOSE : 0),
535                         &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM)
536 #endif
537                 );
538
539                 if (online_analyze_verbose)
540                 {
541                         long    secs;
542                         int             microsecs;
543
544                         endStamp = GetCurrentTimestamp();
545                         TimestampDifference(startStamp, endStamp, &secs, &microsecs);
546                         elog(INFO, "analyze \"%s\" took %.02f seconds",
547                                 get_rel_name(relOid),
548                                 ((double)secs) + ((double)microsecs)/1.0e6);
549                 }
550
551                 rstat->autovac_analyze_timestamp = now;
552                 rstat->changes_since_analyze = 0;
553
554                 switch(operation)
555                 {
556                         case CK_CREATE:
557                         case CK_INSERT:
558                         case CK_UPDATE:
559                                 rstat->n_tuples += naffected;
560                         case CK_DELETE:
561                                 rstat->rereadStat = (reltype == OATT_PERSISTENT);
562                                 break;
563                         case CK_TRUNCATE:
564                         case CK_FASTTRUNCATE:
565                                 rstat->rereadStat = false;
566                                 rstat->n_tuples = 0;
567                                 break;
568                         default:
569                                 break;
570                 }
571
572                 /* update last analyze timestamp in local memory of backend */
573                 if (tabentry)
574                 {
575                         tabentry->analyze_timestamp = now;
576                         tabentry->changes_since_analyze = 0;
577                 }
578 #if 0
579                 /* force reload stat for new table */
580                 if (newTable)
581                         pgstat_clear_snapshot();
582 #endif
583         }
584         else
585         {
586 #if PG_VERSION_NUM >= 90000
587                 if (tabentry)
588                         tabentry->changes_since_analyze += naffected;
589 #endif
590                 switch(operation)
591                 {
592                         case CK_CREATE:
593                         case CK_INSERT:
594                                 rstat->changes_since_analyze += naffected;
595                                 rstat->n_tuples += naffected;
596                                 break;
597                         case CK_UPDATE:
598                                 rstat->changes_since_analyze += 2 * naffected;
599                                 rstat->n_tuples += naffected;
600                         case CK_DELETE:
601                                 rstat->changes_since_analyze += naffected;
602                                 break;
603                         case CK_TRUNCATE:
604                         case CK_FASTTRUNCATE:
605                                 rstat->changes_since_analyze = 0;
606                                 rstat->n_tuples = 0;
607                                 break;
608                         default:
609                                 break;
610                 }
611         }
612
613         /* Reset local cache if we are over limit */
614         if (hash_get_num_entries(relstats) > online_analyze_capacity_threshold)
615                 relstatsInit();
616 }
617
618 static Const*
619 isFastTruncateCall(QueryDesc *queryDesc)
620 {
621         TargetEntry     *te;
622         FuncExpr        *fe;
623         Const           *constval;
624
625         if (!(
626                   queryDesc->plannedstmt &&
627                   queryDesc->operation == CMD_SELECT &&
628                   queryDesc->plannedstmt->planTree &&
629                   queryDesc->plannedstmt->planTree->targetlist &&
630                   list_length(queryDesc->plannedstmt->planTree->targetlist) == 1 &&
631                   IsA(linitial(queryDesc->plannedstmt->planTree->targetlist), TargetEntry)
632                  ))
633                 return NULL;
634
635         te = linitial(queryDesc->plannedstmt->planTree->targetlist);
636
637         if (!IsA(te, TargetEntry))
638                 return NULL;
639
640         fe = (FuncExpr*)te->expr;
641
642         if (!(
643                   fe && IsA(fe, FuncExpr) &&
644                   fe->funcid >= FirstNormalObjectId &&
645                   fe->funcretset == false &&
646                   fe->funcresulttype == VOIDOID &&
647                   fe->funcvariadic == false &&
648                   list_length(fe->args) == 1 &&
649                   IsA(linitial(fe->args), Const)
650                  ))
651                 return NULL;
652
653         constval = linitial(fe->args);
654
655         if (!(
656                   IsA(constval,Const) &&
657                   constval->consttype == TEXTOID &&
658                   strcmp(get_func_name(fe->funcid), "fasttruncate") == 0
659                  ))
660                 return NULL;
661
662         return constval;
663 }
664
665
666
667 extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
668 void
669 onlineAnalyzeHooker(QueryDesc *queryDesc)
670 {
671         int64   naffected = -1;
672         Const   *constval;
673
674         if (queryDesc->estate)
675                 naffected = queryDesc->estate->es_processed;
676
677 #if PG_VERSION_NUM >= 90200
678         if (online_analyze_enable &&
679                 (constval = isFastTruncateCall(queryDesc)) != NULL)
680         {
681                 Datum           tblnamed = constval->constvalue;
682                 char            *tblname = text_to_cstring(DatumGetTextP(tblnamed));
683                 RangeVar        *tblvar =
684                         makeRangeVarFromNameList(stringToQualifiedNameList(tblname));
685
686                 makeAnalyze(RangeVarGetRelid(tblvar,
687                                                                          NoLock,
688                                                                          false),
689                                         CK_FASTTRUNCATE, -1);
690         }
691 #endif
692
693         if (online_analyze_enable && queryDesc->plannedstmt &&
694                         (queryDesc->operation == CMD_INSERT ||
695                          queryDesc->operation == CMD_UPDATE ||
696                          queryDesc->operation == CMD_DELETE
697 #if PG_VERSION_NUM < 90200
698                          || (queryDesc->operation == CMD_SELECT &&
699                                  queryDesc->plannedstmt->intoClause)
700 #endif
701                          ))
702         {
703 #if PG_VERSION_NUM < 90200
704                 if (queryDesc->operation == CMD_SELECT)
705                 {
706                         Oid     relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true);
707
708                         makeAnalyze(relOid, queryDesc->operation, naffected);
709                 }
710                 else
711 #endif
712                 if (queryDesc->plannedstmt->resultRelations &&
713                                  queryDesc->plannedstmt->rtable)
714                 {
715                         ListCell        *l;
716
717                         foreach(l, queryDesc->plannedstmt->resultRelations)
718                         {
719                                 int                             n = lfirst_int(l);
720                                 RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
721
722                                 if (rte->rtekind == RTE_RELATION)
723                                         makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected);
724                         }
725                 }
726         }
727
728         if (oldExecutorEndHook)
729                 oldExecutorEndHook(queryDesc);
730         else
731                 standard_ExecutorEnd(queryDesc);
732 }
733
734 #if PG_VERSION_NUM >= 90200
735 static void
736 onlineAnalyzeHookerUtility(
737 #if PG_VERSION_NUM >= 100000
738                                                    PlannedStmt *pstmt,
739 #else
740                                                    Node *parsetree,
741 #endif
742                                                    const char *queryString,
743 #if PG_VERSION_NUM >= 90300
744                                                         ProcessUtilityContext context, ParamListInfo params,
745 #if PG_VERSION_NUM >= 100000
746                                                         QueryEnvironment *queryEnv,
747 #endif
748 #else
749                                                         ParamListInfo params, bool isTopLevel,
750 #endif
751                                                         DestReceiver *dest, char *completionTag) {
752         List            *tblnames = NIL;
753         CmdKind         op = CK_INSERT;
754 #if PG_VERSION_NUM >= 100000
755         Node            *parsetree = NULL;
756
757         if (pstmt->commandType == CMD_UTILITY)
758                 parsetree = pstmt->utilityStmt;
759 #endif
760
761         if (parsetree && online_analyze_enable)
762         {
763                 if (IsA(parsetree, CreateTableAsStmt) &&
764                         ((CreateTableAsStmt*)parsetree)->into)
765                 {
766                         tblnames =
767                                 list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel));
768                         op = CK_CREATE;
769                 }
770                 else if (IsA(parsetree, TruncateStmt))
771                 {
772                         tblnames = list_copy(((TruncateStmt*)parsetree)->relations);
773                         op = CK_TRUNCATE;
774                 }
775         }
776
777 #if PG_VERSION_NUM >= 100000
778 #define parsetree pstmt
779 #endif
780
781         if (oldProcessUtilityHook)
782                 oldProcessUtilityHook(parsetree, queryString,
783 #if PG_VERSION_NUM >= 90300
784                                                           context, params,
785 #if PG_VERSION_NUM >= 100000
786                                                           queryEnv,
787 #endif
788 #else
789                                                           params, isTopLevel,
790 #endif
791                                                           dest, completionTag);
792         else
793                 standard_ProcessUtility(parsetree, queryString,
794 #if PG_VERSION_NUM >= 90300
795                                                                 context, params,
796 #if PG_VERSION_NUM >= 100000
797                                                                 queryEnv,
798 #endif
799 #else
800                                                                 params, isTopLevel,
801 #endif
802                                                                 dest, completionTag);
803
804 #if PG_VERSION_NUM >= 100000
805 #undef parsetree
806 #endif
807
808         if (tblnames) {
809                 ListCell        *l;
810
811                 foreach(l, tblnames)
812                 {
813                         RangeVar        *tblname = (RangeVar*)lfirst(l);
814                         Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
815
816                         makeAnalyze(tblOid, op, -1);
817                 }
818         }
819 }
820 #endif
821
822 static void
823 relstatsInit(void)
824 {
825         HASHCTL hash_ctl;
826         int             flags = 0;
827
828         MemSet(&hash_ctl, 0, sizeof(hash_ctl));
829
830         hash_ctl.hash = oid_hash;
831         flags |= HASH_FUNCTION;
832
833         if (onlineAnalyzeMemoryContext)
834         {
835                 Assert(relstats != NULL);
836                 MemoryContextReset(onlineAnalyzeMemoryContext);
837         }
838         else
839         {
840                 Assert(relstats == NULL);
841                 onlineAnalyzeMemoryContext =
842                         AllocSetContextCreate(CacheMemoryContext,
843                                                                   "online_analyze storage context",
844 #if PG_VERSION_NUM < 90600
845                                                                   ALLOCSET_DEFAULT_MINSIZE,
846                                                                   ALLOCSET_DEFAULT_INITSIZE,
847                                                                   ALLOCSET_DEFAULT_MAXSIZE
848 #else
849                                                                   ALLOCSET_DEFAULT_SIZES
850 #endif
851                                                                  );
852         }
853
854         hash_ctl.hcxt = onlineAnalyzeMemoryContext;
855         flags |= HASH_CONTEXT;
856
857         hash_ctl.keysize = sizeof(Oid);
858
859         hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat);
860         flags |= HASH_ELEM;
861
862         relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags);
863 }
864
865 void _PG_init(void);
866 void
867 _PG_init(void)
868 {
869         relstatsInit();
870
871         oldExecutorEndHook = ExecutorEnd_hook;
872
873         ExecutorEnd_hook = onlineAnalyzeHooker;
874
875 #if PG_VERSION_NUM >= 90200
876         oldProcessUtilityHook = ProcessUtility_hook;
877
878         ProcessUtility_hook = onlineAnalyzeHookerUtility;
879 #endif
880
881
882         DefineCustomBoolVariable(
883                 "online_analyze.enable",
884                 "Enable on-line analyze",
885                 "Enables analyze of table directly after insert/update/delete/select into",
886                 &online_analyze_enable,
887 #if PG_VERSION_NUM >= 80400
888                 online_analyze_enable,
889 #endif
890                 PGC_USERSET,
891 #if PG_VERSION_NUM >= 80400
892                 GUC_NOT_IN_SAMPLE,
893 #if PG_VERSION_NUM >= 90100
894                 NULL,
895 #endif
896 #endif
897                 NULL,
898                 NULL
899         );
900
901         DefineCustomBoolVariable(
902                 "online_analyze.verbose",
903                 "Verbosity of on-line analyze",
904                 "Make ANALYZE VERBOSE after table's changes",
905                 &online_analyze_verbose,
906 #if PG_VERSION_NUM >= 80400
907                 online_analyze_verbose,
908 #endif
909                 PGC_USERSET,
910 #if PG_VERSION_NUM >= 80400
911                 GUC_NOT_IN_SAMPLE,
912 #if PG_VERSION_NUM >= 90100
913                 NULL,
914 #endif
915 #endif
916                 NULL,
917                 NULL
918         );
919
920         DefineCustomRealVariable(
921                 "online_analyze.scale_factor",
922                 "fraction of table size to start on-line analyze",
923                 "fraction of table size to start on-line analyze",
924                 &online_analyze_scale_factor,
925 #if PG_VERSION_NUM >= 80400
926                 online_analyze_scale_factor,
927 #endif
928                 0.0,
929                 1.0,
930                 PGC_USERSET,
931 #if PG_VERSION_NUM >= 80400
932                 GUC_NOT_IN_SAMPLE,
933 #if PG_VERSION_NUM >= 90100
934                 NULL,
935 #endif
936 #endif
937                 NULL,
938                 NULL
939         );
940
941         DefineCustomIntVariable(
942                 "online_analyze.threshold",
943                 "min number of row updates before on-line analyze",
944                 "min number of row updates before on-line analyze",
945                 &online_analyze_threshold,
946 #if PG_VERSION_NUM >= 80400
947                 online_analyze_threshold,
948 #endif
949                 0,
950                 0x7fffffff,
951                 PGC_USERSET,
952 #if PG_VERSION_NUM >= 80400
953                 GUC_NOT_IN_SAMPLE,
954 #if PG_VERSION_NUM >= 90100
955                 NULL,
956 #endif
957 #endif
958                 NULL,
959                 NULL
960         );
961
962         DefineCustomIntVariable(
963                 "online_analyze.capacity_threshold",
964                 "Max local cache table capacity",
965                 "Max local cache table capacity",
966                 &online_analyze_capacity_threshold,
967 #if PG_VERSION_NUM >= 80400
968                 online_analyze_capacity_threshold,
969 #endif
970                 0,
971                 0x7fffffff,
972                 PGC_USERSET,
973 #if PG_VERSION_NUM >= 80400
974                 GUC_NOT_IN_SAMPLE,
975 #if PG_VERSION_NUM >= 90100
976                 NULL,
977 #endif
978 #endif
979                 NULL,
980                 NULL
981         );
982
983         DefineCustomRealVariable(
984                 "online_analyze.min_interval",
985                 "minimum time interval between analyze call (in milliseconds)",
986                 "minimum time interval between analyze call (in milliseconds)",
987                 &online_analyze_min_interval,
988 #if PG_VERSION_NUM >= 80400
989                 online_analyze_min_interval,
990 #endif
991                 0.0,
992                 1e30,
993                 PGC_USERSET,
994 #if PG_VERSION_NUM >= 80400
995                 GUC_NOT_IN_SAMPLE,
996 #if PG_VERSION_NUM >= 90100
997                 NULL,
998 #endif
999 #endif
1000                 NULL,
1001                 NULL
1002         );
1003
1004         DefineCustomEnumVariable(
1005                 "online_analyze.table_type",
1006                 "Type(s) of table for online analyze: all(default), persistent, temporary, none",
1007                 NULL,
1008                 &online_analyze_table_type,
1009 #if PG_VERSION_NUM >= 80400
1010                 online_analyze_table_type,
1011 #endif
1012                 online_analyze_table_type_options,
1013                 PGC_USERSET,
1014 #if PG_VERSION_NUM >= 80400
1015                 GUC_NOT_IN_SAMPLE,
1016 #if PG_VERSION_NUM >= 90100
1017                 NULL,
1018 #endif
1019 #endif
1020                 NULL,
1021                 NULL
1022         );
1023
1024         DefineCustomStringVariable(
1025                 "online_analyze.exclude_tables",
1026                 "List of tables which will not online analyze",
1027                 NULL,
1028                 &excludeTables.tableStr,
1029 #if PG_VERSION_NUM >= 80400
1030                 "",
1031 #endif
1032                 PGC_USERSET,
1033                 0,
1034 #if PG_VERSION_NUM >= 90100
1035                 excludeTablesCheck,
1036                 excludeTablesAssign,
1037 #else
1038                 excludeTablesAssign,
1039 #endif
1040                 excludeTablesShow
1041         );
1042
1043         DefineCustomStringVariable(
1044                 "online_analyze.include_tables",
1045                 "List of tables which will online analyze",
1046                 NULL,
1047                 &includeTables.tableStr,
1048 #if PG_VERSION_NUM >= 80400
1049                 "",
1050 #endif
1051                 PGC_USERSET,
1052                 0,
1053 #if PG_VERSION_NUM >= 90100
1054                 includeTablesCheck,
1055                 includeTablesAssign,
1056 #else
1057                 includeTablesAssign,
1058 #endif
1059                 includeTablesShow
1060         );
1061
1062         DefineCustomIntVariable(
1063                 "online_analyze.lower_limit",
1064                 "min number of rows in table to analyze",
1065                 "min number of rows in table to analyze",
1066                 &online_analyze_lower_limit,
1067 #if PG_VERSION_NUM >= 80400
1068                 online_analyze_lower_limit,
1069 #endif
1070                 0,
1071                 0x7fffffff,
1072                 PGC_USERSET,
1073 #if PG_VERSION_NUM >= 80400
1074                 GUC_NOT_IN_SAMPLE,
1075 #if PG_VERSION_NUM >= 90100
1076                 NULL,
1077 #endif
1078 #endif
1079                 NULL,
1080                 NULL
1081         );
1082
1083 }
1084
1085 void _PG_fini(void);
1086 void
1087 _PG_fini(void)
1088 {
1089         ExecutorEnd_hook = oldExecutorEndHook;
1090 #if PG_VERSION_NUM >= 90200
1091         ProcessUtility_hook = oldProcessUtilityHook;
1092 #endif
1093
1094         if (excludeTables.tables)
1095                 free(excludeTables.tables);
1096         if (includeTables.tables)
1097                 free(includeTables.tables);
1098
1099         excludeTables.tables = includeTables.tables = NULL;
1100         excludeTables.nTables = includeTables.nTables = 0;
1101 }