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