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