0d85df6583a8c70fb35547fbb026b28b26a401db
[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 "catalog/namespace.h"
34 #include "commands/vacuum.h"
35 #include "executor/executor.h"
36 #include "nodes/nodes.h"
37 #include "nodes/parsenodes.h"
38 #include "storage/bufmgr.h"
39 #include "utils/builtins.h"
40 #include "utils/lsyscache.h"
41 #include "utils/guc.h"
42 #if PG_VERSION_NUM >= 90200
43 #include "catalog/pg_class.h"
44 #include "nodes/primnodes.h"
45 #include "tcop/utility.h"
46 #include "utils/rel.h"
47 #include "utils/relcache.h"
48 #include "utils/timestamp.h"
49 #if PG_VERSION_NUM >= 90500
50 #include "nodes/makefuncs.h"
51 #endif
52 #endif
53
54 #ifdef PG_MODULE_MAGIC
55 PG_MODULE_MAGIC;
56 #endif
57
58 static bool online_analyze_enable = true;
59 static bool online_analyze_verbose = true;
60 static double online_analyze_scale_factor = 0.1;
61 static int online_analyze_threshold = 50;
62 static double online_analyze_min_interval = 10000;
63
64 static ExecutorEnd_hook_type oldExecutorEndHook = NULL;
65 #if PG_VERSION_NUM >= 90200
66 static ProcessUtility_hook_type oldProcessUtilityHook = NULL;
67 #endif
68
69 typedef enum
70 {
71         OATT_ALL                = 0x03,
72         OATT_PERSISTENT = 0x01,
73         OATT_TEMPORARY  = 0x02,
74         OATT_NONE               = 0x00
75 } OnlineAnalyzeTableType;
76
77 static const struct config_enum_entry online_analyze_table_type_options[] =
78 {
79         {"all", OATT_ALL, false},
80         {"persistent", OATT_PERSISTENT, false},
81         {"temporary", OATT_TEMPORARY, false},
82         {"none", OATT_NONE, false},
83         {NULL, 0, false},
84 };
85
86 static int online_analyze_table_type = (int)OATT_ALL;
87
88 typedef struct TableList {
89         int             nTables;
90         Oid             *tables;
91         char    *tableStr;
92 } TableList;
93
94 static TableList excludeTables = {0, NULL, NULL};
95 static TableList includeTables = {0, NULL, NULL};
96
97 static int
98 oid_cmp(const void *a, const void *b)
99 {
100         if (*(Oid*)a == *(Oid*)b)
101                 return 0;
102         return (*(Oid*)a > *(Oid*)b) ? 1 : -1;
103 }
104
105 static const char *
106 tableListAssign(const char * newval, bool doit, TableList *tbl)
107 {
108         char            *rawname;
109         List            *namelist;
110         ListCell        *l;
111         Oid                     *newOids = NULL;
112         int                     nOids = 0,
113                                 i = 0;
114
115         rawname = pstrdup(newval);
116
117         if (!SplitIdentifierString(rawname, ',', &namelist))
118                 goto cleanup;
119
120         if (doit)
121         {
122                 nOids = list_length(namelist);
123                 newOids = malloc(sizeof(Oid) * (nOids+1));
124                 if (!newOids)
125                         elog(ERROR,"could not allocate %d bytes",
126                                  (int)(sizeof(Oid) * (nOids+1)));
127         }
128
129         foreach(l, namelist)
130         {
131                 char    *curname = (char *) lfirst(l);
132 #if PG_VERSION_NUM >= 90200
133                 Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
134                                                         stringToQualifiedNameList(curname)), NoLock, true);
135 #else
136                 Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
137                                                         stringToQualifiedNameList(curname)), true);
138 #endif
139
140                 if (relOid == InvalidOid)
141                 {
142 #if PG_VERSION_NUM >= 90100
143                         if (doit == false)
144 #endif
145                         elog(WARNING,"'%s' does not exist", curname);
146                         continue;
147                 }
148                 else if ( get_rel_relkind(relOid) != RELKIND_RELATION )
149                 {
150 #if PG_VERSION_NUM >= 90100
151                         if (doit == false)
152 #endif
153                                 elog(WARNING,"'%s' is not an table", curname);
154                         continue;
155                 }
156                 else if (doit)
157                 {
158                         newOids[i++] = relOid;
159                 }
160         }
161
162         if (doit)
163         {
164                 tbl->nTables = i;
165                 if (tbl->tables)
166                         free(tbl->tables);
167                 tbl->tables = newOids;
168                 if (tbl->nTables > 1)
169                         qsort(tbl->tables, tbl->nTables, sizeof(tbl->tables[0]), oid_cmp);
170         }
171
172         pfree(rawname);
173         list_free(namelist);
174
175         return newval;
176
177 cleanup:
178         if (newOids)
179                 free(newOids);
180         pfree(rawname);
181         list_free(namelist);
182         return NULL;
183 }
184
185 #if PG_VERSION_NUM >= 90100
186 static bool
187 excludeTablesCheck(char **newval, void **extra, GucSource source)
188 {
189         char *val;
190
191         val = (char*)tableListAssign(*newval, false, &excludeTables);
192
193         if (val)
194         {
195                 *newval = val;
196                 return true;
197         }
198
199         return false;
200 }
201
202 static void
203 excludeTablesAssign(const char *newval, void *extra)
204 {
205         tableListAssign(newval, true, &excludeTables);
206 }
207
208 static bool
209 includeTablesCheck(char **newval, void **extra, GucSource source)
210 {
211         char *val;
212
213         val = (char*)tableListAssign(*newval, false, &includeTables);
214
215         if (val)
216         {
217                 *newval = val;
218                 return true;
219         }
220
221         return false;
222 }
223
224 static void
225 includeTablesAssign(const char *newval, void *extra)
226 {
227         tableListAssign(newval, true, &excludeTables);
228 }
229
230 #else /* PG_VERSION_NUM < 90100 */
231
232 static const char *
233 excludeTablesAssign(const char * newval, bool doit, GucSource source)
234 {
235         return tableListAssign(newval, doit, &excludeTables);
236 }
237
238 static const char *
239 includeTablesAssign(const char * newval, bool doit, GucSource source)
240 {
241         return tableListAssign(newval, doit, &includeTables);
242 }
243
244 #endif
245
246 static const char*
247 tableListShow(TableList *tbl)
248 {
249         char    *val, *ptr;
250         int             i,
251                         len;
252
253         len = 1 /* \0 */ + tbl->nTables * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */);
254         ptr = val = palloc(len);
255         *ptr ='\0';
256         for(i=0; i<tbl->nTables; i++)
257         {
258                 char    *relname = get_rel_name(tbl->tables[i]);
259                 Oid             nspOid = get_rel_namespace(tbl->tables[i]);
260                 char    *nspname = get_namespace_name(nspOid);
261
262                 if ( relname == NULL || nspOid == InvalidOid || nspname == NULL )
263                         continue;
264
265                 ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s",
266                                                                                                         (i==0) ? "" : ", ",
267                                                                                                         nspname, relname);
268         }
269
270         return val;
271 }
272
273 static const char*
274 excludeTablesShow(void)
275 {
276         return tableListShow(&excludeTables);
277 }
278
279 static const char*
280 includeTablesShow(void)
281 {
282         return tableListShow(&includeTables);
283 }
284
285 static bool
286 matchOid(TableList *tbl, Oid oid)
287 {
288         Oid     *StopLow = tbl->tables,
289                 *StopHigh = tbl->tables + tbl->nTables,
290                 *StopMiddle;
291
292         /* Loop invariant: StopLow <= val < StopHigh */
293         while (StopLow < StopHigh)
294         {
295                 StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
296
297                 if (*StopMiddle == oid)
298                         return true;
299                 else  if (*StopMiddle < oid)
300                         StopLow = StopMiddle + 1;
301                 else
302                         StopHigh = StopMiddle;
303         }
304
305         return false;
306 }
307
308 #if PG_VERSION_NUM >= 90500
309 static RangeVar*
310 makeRangeVarFromOid(Oid relOid)
311 {
312         return makeRangeVar(
313                                 get_namespace_name(get_rel_namespace(relOid)),
314                                 get_rel_name(relOid),
315                                 -1
316                         );
317
318 }
319 #endif
320
321 static void
322 makeAnalyze(Oid relOid, CmdType operation, int32 naffected)
323 {
324         PgStat_StatTabEntry             *tabentry;
325         TimestampTz                             now = GetCurrentTimestamp();
326
327         if (relOid == InvalidOid)
328                 return;
329
330         if (naffected == 0)
331                 /* return if there is not changes */
332                 return;
333         else if (naffected < 0)
334                 /* number if affected rows is unknown */
335                 naffected = 0;
336
337         /*
338          * includeTables overwrites excludeTables
339          */
340         switch(online_analyze_table_type)
341         {
342                 case OATT_ALL:
343                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
344                                 (matchOid(&excludeTables, relOid) == true &&
345                                 matchOid(&includeTables, relOid) == false))
346                                 return;
347                         break;
348                 case OATT_NONE:
349                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
350                                 matchOid(&includeTables, relOid) == false)
351                                 return;
352                         break;
353                 case OATT_TEMPORARY:
354                 case OATT_PERSISTENT:
355                 default:
356                         {
357                                 Relation                                rel;
358                                 OnlineAnalyzeTableType  reltype;
359
360                                 rel = RelationIdGetRelation(relOid);
361
362                                 if (rel->rd_rel->relkind != RELKIND_RELATION)
363                                 {
364                                         RelationClose(rel);
365                                         return;
366                                 }
367
368                                 reltype =
369 #if PG_VERSION_NUM >= 90100
370                                         (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
371 #else
372                                         (rel->rd_istemp || rel->rd_islocaltemp)
373 #endif
374                                                 ? OATT_TEMPORARY : OATT_PERSISTENT;
375                                 RelationClose(rel);
376
377                                 /*
378                                  * skip analyze if relation's type doesn't not match
379                                  * online_analyze_table_type
380                                  */
381                                 if ((online_analyze_table_type & reltype) == 0 ||
382                                         matchOid(&excludeTables, relOid) == true)
383                                 {
384                                         if (matchOid(&includeTables, relOid) == false)
385                                                 return;
386                                 }
387                                 break;
388                         }
389         }
390
391         tabentry = pgstat_fetch_stat_tabentry(relOid);
392
393 #if PG_VERSION_NUM >= 90000
394 #define changes_since_analyze(t)        ((t)->changes_since_analyze)
395 #else
396 #define changes_since_analyze(t)        ((t)->n_live_tuples + (t)->n_dead_tuples - (t)->last_anl_tuples)
397 #endif
398
399         if (
400                 tabentry == NULL /* a new table */ ||
401                 (
402                         /* do not analyze too often, if both stamps are exceeded the go */
403                         TimestampDifferenceExceeds(tabentry->analyze_timestamp, now, online_analyze_min_interval) &&
404                         TimestampDifferenceExceeds(tabentry->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
405                         /* be in sync with relation_needs_vacanalyze */
406                         ((double)(changes_since_analyze(tabentry) + naffected)) >=
407                                 online_analyze_scale_factor * ((double)(tabentry->n_dead_tuples + tabentry->n_live_tuples)) +
408                                         (double)online_analyze_threshold
409                 )
410         )
411         {
412 #if PG_VERSION_NUM < 90500
413                 VacuumStmt                              vacstmt;
414 #else
415                 VacuumParams                    vacstmt;
416 #endif
417                 TimestampTz                             startStamp, endStamp;
418
419                 memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */
420
421                 memset(&vacstmt, 0, sizeof(vacstmt));
422
423                 vacstmt.freeze_min_age = -1;
424                 vacstmt.freeze_table_age = -1; /* ??? */
425
426 #if PG_VERSION_NUM < 90500
427                 vacstmt.type = T_VacuumStmt;
428                 vacstmt.relation = NULL;
429                 vacstmt.va_cols = NIL;
430 #if PG_VERSION_NUM >= 90000
431                 vacstmt.options = VACOPT_ANALYZE;
432                 if (online_analyze_verbose)
433                         vacstmt.options |= VACOPT_VERBOSE;
434 #else
435                 vacstmt.vacuum = vacstmt.full = false;
436                 vacstmt.analyze = true;
437                 vacstmt.verbose = online_analyze_verbose;
438 #endif
439 #else
440                 vacstmt.multixact_freeze_min_age = -1;
441                 vacstmt.multixact_freeze_table_age = -1;
442                 vacstmt.log_min_duration = -1;
443 #endif
444
445                 if (online_analyze_verbose)
446                         startStamp = GetCurrentTimestamp();
447
448                 analyze_rel(relOid,
449 #if PG_VERSION_NUM < 90500
450                         &vacstmt
451 #if PG_VERSION_NUM >= 90018
452                         , true
453 #endif
454                         , GetAccessStrategy(BAS_VACUUM)
455 #if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004)
456                         , true
457 #endif
458 #else
459                         makeRangeVarFromOid(relOid),
460                         VACOPT_ANALYZE | ((online_analyze_verbose) ? VACOPT_VERBOSE : 0),
461                         &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM)
462 #endif
463                 );
464
465                 if (online_analyze_verbose)
466                 {
467                         long    secs;
468                         int             microsecs;
469
470                         endStamp = GetCurrentTimestamp();
471                         TimestampDifference(startStamp, endStamp, &secs, &microsecs);
472                         elog(INFO, "analyze \"%s\" took %.02f seconds",
473                                 get_rel_name(relOid),
474                                 ((double)secs) + ((double)microsecs)/1.0e6);
475                 }
476
477                 if (tabentry == NULL)
478                 {
479                         /* new table */
480                         pgstat_clear_snapshot();
481                 }
482                 else
483                 {
484                         /* update last analyze timestamp in local memory of backend */
485                         tabentry->analyze_timestamp = now;
486                 }
487         }
488 #if PG_VERSION_NUM >= 90000
489         else if (tabentry != NULL)
490         {
491                 tabentry->changes_since_analyze += naffected;
492         }
493 #endif
494 }
495
496 extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
497 void
498 onlineAnalyzeHooker(QueryDesc *queryDesc)
499 {
500         uint32  naffected = -1;
501
502         if (queryDesc->estate)
503                 naffected = queryDesc->estate->es_processed;
504
505         if (online_analyze_enable && queryDesc->plannedstmt &&
506                         (queryDesc->operation == CMD_INSERT ||
507                          queryDesc->operation == CMD_UPDATE ||
508                          queryDesc->operation == CMD_DELETE
509 #if PG_VERSION_NUM < 90200
510                          || (queryDesc->operation == CMD_SELECT &&
511                                  queryDesc->plannedstmt->intoClause)
512 #endif
513                          ))
514         {
515 #if PG_VERSION_NUM < 90200
516                 if (queryDesc->operation == CMD_SELECT)
517                 {
518                         Oid     relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true);
519
520                         makeAnalyze(relOid, queryDesc->operation, naffected);
521                 }
522                 else
523 #endif
524                 if (queryDesc->plannedstmt->resultRelations &&
525                                  queryDesc->plannedstmt->rtable)
526                 {
527                         ListCell        *l;
528
529                         foreach(l, queryDesc->plannedstmt->resultRelations)
530                         {
531                                 int                             n = lfirst_int(l);
532                                 RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
533
534                                 if (rte->rtekind == RTE_RELATION)
535                                         makeAnalyze(rte->relid, queryDesc->operation, naffected);
536                         }
537                 }
538         }
539
540         if (oldExecutorEndHook)
541                 oldExecutorEndHook(queryDesc);
542         else
543                 standard_ExecutorEnd(queryDesc);
544 }
545
546 #if PG_VERSION_NUM >= 90200
547 static void
548 onlineAnalyzeHookerUtility(Node *parsetree, const char *queryString,
549 #if PG_VERSION_NUM >= 90300
550                                                         ProcessUtilityContext context, ParamListInfo params,
551 #else
552                                                         ParamListInfo params, bool isTopLevel,
553 #endif
554                                                         DestReceiver *dest, char *completionTag) {
555         RangeVar        *tblname = NULL;
556
557         if (IsA(parsetree, CreateTableAsStmt) && ((CreateTableAsStmt*)parsetree)->into)
558                 tblname = (RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel);
559
560         if (oldProcessUtilityHook)
561                 oldProcessUtilityHook(parsetree, queryString,
562 #if PG_VERSION_NUM >= 90300
563                                                           context, params,
564 #else
565                                                           params, isTopLevel,
566 #endif
567                                                           dest, completionTag);
568         else
569                 standard_ProcessUtility(parsetree, queryString,
570 #if PG_VERSION_NUM >= 90300
571                                                                 context, params,
572 #else
573                                                                 params, isTopLevel,
574 #endif
575                                                                 dest, completionTag);
576
577         if (tblname) {
578                 Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
579
580                 makeAnalyze(tblOid, CMD_INSERT, -1);
581         }
582 }
583 #endif
584
585 void _PG_init(void);
586 void
587 _PG_init(void)
588 {
589         oldExecutorEndHook = ExecutorEnd_hook;
590
591         ExecutorEnd_hook = onlineAnalyzeHooker;
592
593 #if PG_VERSION_NUM >= 90200
594         oldProcessUtilityHook = ProcessUtility_hook;
595
596         ProcessUtility_hook = onlineAnalyzeHookerUtility;
597 #endif
598
599
600         DefineCustomBoolVariable(
601                 "online_analyze.enable",
602                 "Enable on-line analyze",
603                 "Enables analyze of table directly after insert/update/delete/select into",
604                 &online_analyze_enable,
605 #if PG_VERSION_NUM >= 80400
606                 online_analyze_enable,
607 #endif
608                 PGC_USERSET,
609 #if PG_VERSION_NUM >= 80400
610                 GUC_NOT_IN_SAMPLE,
611 #if PG_VERSION_NUM >= 90100
612                 NULL,
613 #endif
614 #endif
615                 NULL,
616                 NULL
617         );
618
619         DefineCustomBoolVariable(
620                 "online_analyze.verbose",
621                 "Verbosity of on-line analyze",
622                 "Make ANALYZE VERBOSE after table's changes",
623                 &online_analyze_verbose,
624 #if PG_VERSION_NUM >= 80400
625                 online_analyze_verbose,
626 #endif
627                 PGC_USERSET,
628 #if PG_VERSION_NUM >= 80400
629                 GUC_NOT_IN_SAMPLE,
630 #if PG_VERSION_NUM >= 90100
631                 NULL,
632 #endif
633 #endif
634                 NULL,
635                 NULL
636         );
637
638         DefineCustomRealVariable(
639                 "online_analyze.scale_factor",
640                 "fraction of table size to start on-line analyze",
641                 "fraction of table size to start on-line analyze",
642                 &online_analyze_scale_factor,
643 #if PG_VERSION_NUM >= 80400
644                 online_analyze_scale_factor,
645 #endif
646                 0.0,
647                 1.0,
648                 PGC_USERSET,
649 #if PG_VERSION_NUM >= 80400
650                 GUC_NOT_IN_SAMPLE,
651 #if PG_VERSION_NUM >= 90100
652                 NULL,
653 #endif
654 #endif
655                 NULL,
656                 NULL
657         );
658
659         DefineCustomIntVariable(
660                 "online_analyze.threshold",
661                 "min number of row updates before on-line analyze",
662                 "min number of row updates before on-line analyze",
663                 &online_analyze_threshold,
664 #if PG_VERSION_NUM >= 80400
665                 online_analyze_threshold,
666 #endif
667                 0,
668                 0x7fffffff,
669                 PGC_USERSET,
670 #if PG_VERSION_NUM >= 80400
671                 GUC_NOT_IN_SAMPLE,
672 #if PG_VERSION_NUM >= 90100
673                 NULL,
674 #endif
675 #endif
676                 NULL,
677                 NULL
678         );
679
680         DefineCustomRealVariable(
681                 "online_analyze.min_interval",
682                 "minimum time interval between analyze call (in milliseconds)",
683                 "minimum time interval between analyze call (in milliseconds)",
684                 &online_analyze_min_interval,
685 #if PG_VERSION_NUM >= 80400
686                 online_analyze_min_interval,
687 #endif
688                 0.0,
689                 1e30,
690                 PGC_USERSET,
691 #if PG_VERSION_NUM >= 80400
692                 GUC_NOT_IN_SAMPLE,
693 #if PG_VERSION_NUM >= 90100
694                 NULL,
695 #endif
696 #endif
697                 NULL,
698                 NULL
699         );
700
701         DefineCustomEnumVariable(
702                 "online_analyze.table_type",
703                 "Type(s) of table for online analyze: all(default), persistent, temporary, none",
704                 NULL,
705                 &online_analyze_table_type,
706 #if PG_VERSION_NUM >= 80400
707                 online_analyze_table_type,
708 #endif
709                 online_analyze_table_type_options,
710                 PGC_USERSET,
711 #if PG_VERSION_NUM >= 80400
712                 GUC_NOT_IN_SAMPLE,
713 #if PG_VERSION_NUM >= 90100
714                 NULL,
715 #endif
716 #endif
717                 NULL,
718                 NULL
719         );
720
721         DefineCustomStringVariable(
722                 "online_analyze.exclude_tables",
723                 "List of tables which will not online analyze",
724                 NULL,
725                 &excludeTables.tableStr,
726 #if PG_VERSION_NUM >= 80400
727                 "",
728 #endif
729                 PGC_USERSET,
730                 0,
731 #if PG_VERSION_NUM >= 90100
732                 excludeTablesCheck,
733                 excludeTablesAssign,
734 #else
735                 excludeTablesAssign,
736 #endif
737                 excludeTablesShow
738         );
739
740         DefineCustomStringVariable(
741                 "online_analyze.include_tables",
742                 "List of tables which will online analyze",
743                 NULL,
744                 &includeTables.tableStr,
745 #if PG_VERSION_NUM >= 80400
746                 "",
747 #endif
748                 PGC_USERSET,
749                 0,
750 #if PG_VERSION_NUM >= 90100
751                 includeTablesCheck,
752                 includeTablesAssign,
753 #else
754                 includeTablesAssign,
755 #endif
756                 includeTablesShow
757         );
758 }
759
760 void _PG_fini(void);
761 void
762 _PG_fini(void)
763 {
764         ExecutorEnd_hook = oldExecutorEndHook;
765 #if PG_VERSION_NUM >= 90200
766         ProcessUtility_hook = oldProcessUtilityHook;
767 #endif
768
769         if (excludeTables.tables)
770                 free(excludeTables.tables);
771         if (includeTables.tables)
772                 free(includeTables.tables);
773
774         excludeTables.tables = includeTables.tables = NULL;
775         excludeTables.nTables = includeTables.nTables = 0;
776 }