Do not miss relkind check
[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         Relation                                rel;
327         OnlineAnalyzeTableType  reltype;
328
329         if (relOid == InvalidOid)
330                 return;
331
332         if (naffected == 0)
333                 /* return if there is not changes */
334                 return;
335         else if (naffected < 0)
336                 /* number if affected rows is unknown */
337                 naffected = 0;
338
339         rel = RelationIdGetRelation(relOid);
340         if (rel->rd_rel->relkind != RELKIND_RELATION)
341         {
342                 RelationClose(rel);
343                 return;
344         }
345
346         reltype =
347 #if PG_VERSION_NUM >= 90100
348                 (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
349 #else
350                 (rel->rd_istemp || rel->rd_islocaltemp)
351 #endif
352                         ? OATT_TEMPORARY : OATT_PERSISTENT;
353
354         RelationClose(rel);
355
356         /*
357          * includeTables overwrites excludeTables
358          */
359         switch(online_analyze_table_type)
360         {
361                 case OATT_ALL:
362                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
363                                 (matchOid(&excludeTables, relOid) == true &&
364                                 matchOid(&includeTables, relOid) == false))
365                                 return;
366                         break;
367                 case OATT_NONE:
368                         if (get_rel_relkind(relOid) != RELKIND_RELATION ||
369                                 matchOid(&includeTables, relOid) == false)
370                                 return;
371                         break;
372                 case OATT_TEMPORARY:
373                 case OATT_PERSISTENT:
374                 default:
375                         /*
376                          * skip analyze if relation's type doesn't not match
377                          * online_analyze_table_type
378                          */
379                         if ((online_analyze_table_type & reltype) == 0 ||
380                                 matchOid(&excludeTables, relOid) == true)
381                         {
382                                 if (matchOid(&includeTables, relOid) == false)
383                                         return;
384                         }
385                         break;
386         }
387
388         tabentry = pgstat_fetch_stat_tabentry(relOid);
389
390 #if PG_VERSION_NUM >= 90000
391 #define changes_since_analyze(t)        ((t)->changes_since_analyze)
392 #else
393 #define changes_since_analyze(t)        ((t)->n_live_tuples + (t)->n_dead_tuples - (t)->last_anl_tuples)
394 #endif
395
396         if (
397                 tabentry == NULL /* a new table */ ||
398                 (
399                         /* do not analyze too often, if both stamps are exceeded the go */
400                         TimestampDifferenceExceeds(tabentry->analyze_timestamp, now, online_analyze_min_interval) &&
401                         TimestampDifferenceExceeds(tabentry->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
402                         /* be in sync with relation_needs_vacanalyze */
403                         ((double)(changes_since_analyze(tabentry) + naffected)) >=
404                                 online_analyze_scale_factor * ((double)(tabentry->n_dead_tuples + tabentry->n_live_tuples)) +
405                                         (double)online_analyze_threshold
406                 )
407         )
408         {
409 #if PG_VERSION_NUM < 90500
410                 VacuumStmt                              vacstmt;
411 #else
412                 VacuumParams                    vacstmt;
413 #endif
414                 TimestampTz                             startStamp, endStamp;
415
416                 memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */
417
418                 memset(&vacstmt, 0, sizeof(vacstmt));
419
420                 vacstmt.freeze_min_age = -1;
421                 vacstmt.freeze_table_age = -1; /* ??? */
422
423 #if PG_VERSION_NUM < 90500
424                 vacstmt.type = T_VacuumStmt;
425                 vacstmt.relation = NULL;
426                 vacstmt.va_cols = NIL;
427 #if PG_VERSION_NUM >= 90000
428                 vacstmt.options = VACOPT_ANALYZE;
429                 if (online_analyze_verbose)
430                         vacstmt.options |= VACOPT_VERBOSE;
431 #else
432                 vacstmt.vacuum = vacstmt.full = false;
433                 vacstmt.analyze = true;
434                 vacstmt.verbose = online_analyze_verbose;
435 #endif
436 #else
437                 vacstmt.multixact_freeze_min_age = -1;
438                 vacstmt.multixact_freeze_table_age = -1;
439                 vacstmt.log_min_duration = -1;
440 #endif
441
442                 if (online_analyze_verbose)
443                         startStamp = GetCurrentTimestamp();
444
445                 analyze_rel(relOid,
446 #if PG_VERSION_NUM < 90500
447                         &vacstmt
448 #if PG_VERSION_NUM >= 90018
449                         , true
450 #endif
451                         , GetAccessStrategy(BAS_VACUUM)
452 #if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004)
453                         , true
454 #endif
455 #else
456                         makeRangeVarFromOid(relOid),
457                         VACOPT_ANALYZE | ((online_analyze_verbose) ? VACOPT_VERBOSE : 0),
458                         &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM)
459 #endif
460                 );
461
462                 if (online_analyze_verbose)
463                 {
464                         long    secs;
465                         int             microsecs;
466
467                         endStamp = GetCurrentTimestamp();
468                         TimestampDifference(startStamp, endStamp, &secs, &microsecs);
469                         elog(INFO, "analyze \"%s\" took %.02f seconds",
470                                 get_rel_name(relOid),
471                                 ((double)secs) + ((double)microsecs)/1.0e6);
472                 }
473
474                 if (tabentry == NULL)
475                 {
476                         /* new table */
477                         pgstat_clear_snapshot();
478                 }
479                 else
480                 {
481                         /* update last analyze timestamp in local memory of backend */
482                         tabentry->analyze_timestamp = now;
483                 }
484         }
485 #if PG_VERSION_NUM >= 90000
486         else if (tabentry != NULL)
487         {
488                 tabentry->changes_since_analyze += naffected;
489         }
490 #endif
491 }
492
493 extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
494 void
495 onlineAnalyzeHooker(QueryDesc *queryDesc)
496 {
497         uint32  naffected = -1;
498
499         if (queryDesc->estate)
500                 naffected = queryDesc->estate->es_processed;
501
502         if (online_analyze_enable && queryDesc->plannedstmt &&
503                         (queryDesc->operation == CMD_INSERT ||
504                          queryDesc->operation == CMD_UPDATE ||
505                          queryDesc->operation == CMD_DELETE
506 #if PG_VERSION_NUM < 90200
507                          || (queryDesc->operation == CMD_SELECT &&
508                                  queryDesc->plannedstmt->intoClause)
509 #endif
510                          ))
511         {
512 #if PG_VERSION_NUM < 90200
513                 if (queryDesc->operation == CMD_SELECT)
514                 {
515                         Oid     relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true);
516
517                         makeAnalyze(relOid, queryDesc->operation, naffected);
518                 }
519                 else
520 #endif
521                 if (queryDesc->plannedstmt->resultRelations &&
522                                  queryDesc->plannedstmt->rtable)
523                 {
524                         ListCell        *l;
525
526                         foreach(l, queryDesc->plannedstmt->resultRelations)
527                         {
528                                 int                             n = lfirst_int(l);
529                                 RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
530
531                                 if (rte->rtekind == RTE_RELATION)
532                                         makeAnalyze(rte->relid, queryDesc->operation, naffected);
533                         }
534                 }
535         }
536
537         if (oldExecutorEndHook)
538                 oldExecutorEndHook(queryDesc);
539         else
540                 standard_ExecutorEnd(queryDesc);
541 }
542
543 #if PG_VERSION_NUM >= 90200
544 static void
545 onlineAnalyzeHookerUtility(Node *parsetree, const char *queryString,
546 #if PG_VERSION_NUM >= 90300
547                                                         ProcessUtilityContext context, ParamListInfo params,
548 #else
549                                                         ParamListInfo params, bool isTopLevel,
550 #endif
551                                                         DestReceiver *dest, char *completionTag) {
552         RangeVar        *tblname = NULL;
553
554         if (IsA(parsetree, CreateTableAsStmt) && ((CreateTableAsStmt*)parsetree)->into)
555                 tblname = (RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel);
556
557         if (oldProcessUtilityHook)
558                 oldProcessUtilityHook(parsetree, queryString,
559 #if PG_VERSION_NUM >= 90300
560                                                           context, params,
561 #else
562                                                           params, isTopLevel,
563 #endif
564                                                           dest, completionTag);
565         else
566                 standard_ProcessUtility(parsetree, queryString,
567 #if PG_VERSION_NUM >= 90300
568                                                                 context, params,
569 #else
570                                                                 params, isTopLevel,
571 #endif
572                                                                 dest, completionTag);
573
574         if (tblname) {
575                 Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
576
577                 makeAnalyze(tblOid, CMD_INSERT, -1);
578         }
579 }
580 #endif
581
582 void _PG_init(void);
583 void
584 _PG_init(void)
585 {
586         oldExecutorEndHook = ExecutorEnd_hook;
587
588         ExecutorEnd_hook = onlineAnalyzeHooker;
589
590 #if PG_VERSION_NUM >= 90200
591         oldProcessUtilityHook = ProcessUtility_hook;
592
593         ProcessUtility_hook = onlineAnalyzeHookerUtility;
594 #endif
595
596
597         DefineCustomBoolVariable(
598                 "online_analyze.enable",
599                 "Enable on-line analyze",
600                 "Enables analyze of table directly after insert/update/delete/select into",
601                 &online_analyze_enable,
602 #if PG_VERSION_NUM >= 80400
603                 online_analyze_enable,
604 #endif
605                 PGC_USERSET,
606 #if PG_VERSION_NUM >= 80400
607                 GUC_NOT_IN_SAMPLE,
608 #if PG_VERSION_NUM >= 90100
609                 NULL,
610 #endif
611 #endif
612                 NULL,
613                 NULL
614         );
615
616         DefineCustomBoolVariable(
617                 "online_analyze.verbose",
618                 "Verbosity of on-line analyze",
619                 "Make ANALYZE VERBOSE after table's changes",
620                 &online_analyze_verbose,
621 #if PG_VERSION_NUM >= 80400
622                 online_analyze_verbose,
623 #endif
624                 PGC_USERSET,
625 #if PG_VERSION_NUM >= 80400
626                 GUC_NOT_IN_SAMPLE,
627 #if PG_VERSION_NUM >= 90100
628                 NULL,
629 #endif
630 #endif
631                 NULL,
632                 NULL
633         );
634
635         DefineCustomRealVariable(
636                 "online_analyze.scale_factor",
637                 "fraction of table size to start on-line analyze",
638                 "fraction of table size to start on-line analyze",
639                 &online_analyze_scale_factor,
640 #if PG_VERSION_NUM >= 80400
641                 online_analyze_scale_factor,
642 #endif
643                 0.0,
644                 1.0,
645                 PGC_USERSET,
646 #if PG_VERSION_NUM >= 80400
647                 GUC_NOT_IN_SAMPLE,
648 #if PG_VERSION_NUM >= 90100
649                 NULL,
650 #endif
651 #endif
652                 NULL,
653                 NULL
654         );
655
656         DefineCustomIntVariable(
657                 "online_analyze.threshold",
658                 "min number of row updates before on-line analyze",
659                 "min number of row updates before on-line analyze",
660                 &online_analyze_threshold,
661 #if PG_VERSION_NUM >= 80400
662                 online_analyze_threshold,
663 #endif
664                 0,
665                 0x7fffffff,
666                 PGC_USERSET,
667 #if PG_VERSION_NUM >= 80400
668                 GUC_NOT_IN_SAMPLE,
669 #if PG_VERSION_NUM >= 90100
670                 NULL,
671 #endif
672 #endif
673                 NULL,
674                 NULL
675         );
676
677         DefineCustomRealVariable(
678                 "online_analyze.min_interval",
679                 "minimum time interval between analyze call (in milliseconds)",
680                 "minimum time interval between analyze call (in milliseconds)",
681                 &online_analyze_min_interval,
682 #if PG_VERSION_NUM >= 80400
683                 online_analyze_min_interval,
684 #endif
685                 0.0,
686                 1e30,
687                 PGC_USERSET,
688 #if PG_VERSION_NUM >= 80400
689                 GUC_NOT_IN_SAMPLE,
690 #if PG_VERSION_NUM >= 90100
691                 NULL,
692 #endif
693 #endif
694                 NULL,
695                 NULL
696         );
697
698         DefineCustomEnumVariable(
699                 "online_analyze.table_type",
700                 "Type(s) of table for online analyze: all(default), persistent, temporary, none",
701                 NULL,
702                 &online_analyze_table_type,
703 #if PG_VERSION_NUM >= 80400
704                 online_analyze_table_type,
705 #endif
706                 online_analyze_table_type_options,
707                 PGC_USERSET,
708 #if PG_VERSION_NUM >= 80400
709                 GUC_NOT_IN_SAMPLE,
710 #if PG_VERSION_NUM >= 90100
711                 NULL,
712 #endif
713 #endif
714                 NULL,
715                 NULL
716         );
717
718         DefineCustomStringVariable(
719                 "online_analyze.exclude_tables",
720                 "List of tables which will not online analyze",
721                 NULL,
722                 &excludeTables.tableStr,
723 #if PG_VERSION_NUM >= 80400
724                 "",
725 #endif
726                 PGC_USERSET,
727                 0,
728 #if PG_VERSION_NUM >= 90100
729                 excludeTablesCheck,
730                 excludeTablesAssign,
731 #else
732                 excludeTablesAssign,
733 #endif
734                 excludeTablesShow
735         );
736
737         DefineCustomStringVariable(
738                 "online_analyze.include_tables",
739                 "List of tables which will online analyze",
740                 NULL,
741                 &includeTables.tableStr,
742 #if PG_VERSION_NUM >= 80400
743                 "",
744 #endif
745                 PGC_USERSET,
746                 0,
747 #if PG_VERSION_NUM >= 90100
748                 includeTablesCheck,
749                 includeTablesAssign,
750 #else
751                 includeTablesAssign,
752 #endif
753                 includeTablesShow
754         );
755 }
756
757 void _PG_fini(void);
758 void
759 _PG_fini(void)
760 {
761         ExecutorEnd_hook = oldExecutorEndHook;
762 #if PG_VERSION_NUM >= 90200
763         ProcessUtility_hook = oldProcessUtilityHook;
764 #endif
765
766         if (excludeTables.tables)
767                 free(excludeTables.tables);
768         if (includeTables.tables)
769                 free(includeTables.tables);
770
771         excludeTables.tables = includeTables.tables = NULL;
772         excludeTables.nTables = includeTables.nTables = 0;
773 }