Remove _PG_fini Mikhail Litsarev
[online_analyze.git] / online_analyze.c
index 2519b5e..0021fc3 100644 (file)
@@ -6,13 +6,13 @@
  * modification, are permitted provided that the following conditions
  * are met:
  * 1. Redistributions of source code must retain the above copyright
- *        notice, this list of conditions and the following disclaimer.
+ *             notice, this list of conditions and the following disclaimer.
  * 2. Redistributions in binary form must reproduce the above copyright
- *        notice, this list of conditions and the following disclaimer in the
- *        documentation and/or other materials provided with the distribution.
+ *             notice, this list of conditions and the following disclaimer in the
+ *             documentation and/or other materials provided with the distribution.
  * 3. Neither the name of the author nor the names of any co-contributors
- *        may be used to endorse or promote products derived from this software
- *        without specific prior written permission.
+ *             may be used to endorse or promote products derived from this software
+ *             without specific prior written permission.
  *
  * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
  * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@@ -30,6 +30,9 @@
 #include "postgres.h"
 
 #include "pgstat.h"
+#include "miscadmin.h"
+#include "access/transam.h"
+#include "access/xact.h"
 #include "catalog/namespace.h"
 #include "commands/vacuum.h"
 #include "executor/executor.h"
@@ -37,6 +40,8 @@
 #include "nodes/parsenodes.h"
 #include "storage/bufmgr.h"
 #include "utils/builtins.h"
+#include "utils/hsearch.h"
+#include "utils/memutils.h"
 #include "utils/lsyscache.h"
 #include "utils/guc.h"
 #if PG_VERSION_NUM >= 90200
 #include "utils/rel.h"
 #include "utils/relcache.h"
 #include "utils/timestamp.h"
+#if PG_VERSION_NUM >= 90500
+#include "nodes/makefuncs.h"
+#if PG_VERSION_NUM >= 100000
+#include "utils/varlena.h"
+#include "utils/regproc.h"
+#if PG_VERSION_NUM >= 130000
+#include "common/hashfn.h"
+#endif
+#endif
+#endif
 #endif
 
 #ifdef PG_MODULE_MAGIC
@@ -53,17 +68,38 @@ PG_MODULE_MAGIC;
 #endif
 
 static bool online_analyze_enable = true;
+static bool online_analyze_local_tracking = false;
 static bool online_analyze_verbose = true;
 static double online_analyze_scale_factor = 0.1;
 static int online_analyze_threshold = 50;
+static int online_analyze_capacity_threshold = 100000;
 static double online_analyze_min_interval = 10000;
+static int online_analyze_lower_limit = 0;
 
 static ExecutorEnd_hook_type oldExecutorEndHook = NULL;
 #if PG_VERSION_NUM >= 90200
 static ProcessUtility_hook_type        oldProcessUtilityHook = NULL;
 #endif
 
-typedef enum 
+#if PG_VERSION_NUM >= 120000
+#define VACOPT_NOWAIT VACOPT_SKIP_LOCKED
+#endif
+
+typedef enum CmdKind
+{
+       CK_SELECT = CMD_SELECT,
+       CK_UPDATE = CMD_UPDATE,
+       CK_INSERT = CMD_INSERT,
+       CK_DELETE = CMD_DELETE,
+       CK_TRUNCATE,
+       CK_FASTTRUNCATE,
+       CK_CREATE,
+       CK_ANALYZE,
+       CK_VACUUM
+} CmdKind;
+
+
+typedef enum
 {
        OATT_ALL                = 0x03,
        OATT_PERSISTENT = 0x01,
@@ -71,7 +107,7 @@ typedef enum
        OATT_NONE               = 0x00
 } OnlineAnalyzeTableType;
 
-static const struct config_enum_entry online_analyze_table_type_options[] = 
+static const struct config_enum_entry online_analyze_table_type_options[] =
 {
        {"all", OATT_ALL, false},
        {"persistent", OATT_PERSISTENT, false},
@@ -86,11 +122,27 @@ typedef struct TableList {
        int             nTables;
        Oid             *tables;
        char    *tableStr;
+       bool    inited;
 } TableList;
 
-static TableList excludeTables = {0, NULL, NULL};
-static TableList includeTables = {0, NULL, NULL};
+static TableList excludeTables = {0, NULL, NULL, false};
+static TableList includeTables = {0, NULL, NULL, false};
+
+typedef struct OnlineAnalyzeTableStat {
+       Oid                             tableid;
+       bool                    rereadStat;
+       PgStat_Counter  n_tuples;
+       PgStat_Counter  mod_since_analyze;
+       TimestampTz             last_autoanalyze_time;
+       TimestampTz             last_analyze_time;
+} OnlineAnalyzeTableStat;
+
+static MemoryContext   onlineAnalyzeMemoryContext = NULL;
+static HTAB    *relstats = NULL;
 
+static void relstatsInit(void);
+
+#if PG_VERSION_NUM < 100000
 static int
 oid_cmp(const void *a, const void *b)
 {
@@ -98,15 +150,16 @@ oid_cmp(const void *a, const void *b)
                return 0;
        return (*(Oid*)a > *(Oid*)b) ? 1 : -1;
 }
+#endif
 
 static const char *
 tableListAssign(const char * newval, bool doit, TableList *tbl)
 {
-       char       *rawname;
-       List       *namelist;
-       ListCell   *l;
-       Oid         *newOids = NULL;
-       int         nOids = 0,
+       char            *rawname;
+       List            *namelist;
+       ListCell        *l;
+       Oid                     *newOids = NULL;
+       int                     nOids = 0,
                                i = 0;
 
        rawname = pstrdup(newval);
@@ -114,22 +167,45 @@ tableListAssign(const char * newval, bool doit, TableList *tbl)
        if (!SplitIdentifierString(rawname, ',', &namelist))
                goto cleanup;
 
+       /*
+       * follow work could be done only in normal processing because of
+       * accsess to system catalog
+       */
+#if PG_VERSION_NUM >= 170000
+       if (MyProcNumber == INVALID_PROC_NUMBER ||
+#else
+       if (MyBackendId == InvalidBackendId ||
+#endif
+               !IsUnderPostmaster ||
+               !IsTransactionState())
+       {
+               includeTables.inited = false;
+               excludeTables.inited = false;
+               return newval;
+       }
+
        if (doit)
        {
                nOids = list_length(namelist);
                newOids = malloc(sizeof(Oid) * (nOids+1));
                if (!newOids)
-                       elog(ERROR,"could not allocate %d bytes", (int)(sizeof(Oid) * (nOids+1)));
+                       elog(ERROR,"could not allocate %d bytes",
+                                (int)(sizeof(Oid) * (nOids+1)));
        }
 
        foreach(l, namelist)
        {
-               char        *curname = (char *) lfirst(l);
-               Oid         relOid = RangeVarGetRelid(makeRangeVarFromNameList(stringToQualifiedNameList(curname)), 
-#if PG_VERSION_NUM >= 90200
-                                                                                               NoLock,
+               char    *curname = (char *) lfirst(l);
+#if PG_VERSION_NUM >= 160000
+               Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
+                                                       stringToQualifiedNameList(curname, NULL)), NoLock, true);
+#elif PG_VERSION_NUM >= 90200
+               Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
+                                                       stringToQualifiedNameList(curname)), NoLock, true);
+#else
+               Oid             relOid = RangeVarGetRelid(makeRangeVarFromNameList(
+                                                       stringToQualifiedNameList(curname)), true);
 #endif
-                                                                                               true);
 
                if (relOid == InvalidOid)
                {
@@ -218,10 +294,10 @@ includeTablesCheck(char **newval, void **extra, GucSource source)
 static void
 includeTablesAssign(const char *newval, void *extra)
 {
-       tableListAssign(newval, true, &excludeTables);
+       tableListAssign(newval, true, &includeTables);
 }
 
-#else /* PG_VERSION_NUM < 90100 */ 
+#else /* PG_VERSION_NUM < 90100 */
 
 static const char *
 excludeTablesAssign(const char * newval, bool doit, GucSource source)
@@ -237,21 +313,48 @@ includeTablesAssign(const char * newval, bool doit, GucSource source)
 
 #endif
 
+static void
+lateInit()
+{
+       TableList       *tl[] = {&includeTables, &excludeTables};
+       int i;
+
+#if PG_VERSION_NUM >= 170000
+       if (MyProcNumber == INVALID_PROC_NUMBER ||
+#else
+       if (MyBackendId == InvalidBackendId ||
+#endif
+               !IsUnderPostmaster ||
+               !IsTransactionState())
+               return; /* we aren't in connected state */
+
+       for(i=0; i<lengthof(tl); i++)
+       {
+               TableList       *tbl = tl[i];
+
+               if (tbl->inited == false)
+                       tableListAssign(tbl->tableStr, true, tbl);
+               tbl->inited = true;
+       }
+}
+
 static const char*
 tableListShow(TableList *tbl)
 {
-       char    *val, *ptr;
-       int     i,
+       char    *val, *ptr;
+       int             i,
                        len;
 
+       lateInit();
+
        len = 1 /* \0 */ + tbl->nTables * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */);
        ptr = val = palloc(len);
        *ptr ='\0';
        for(i=0; i<tbl->nTables; i++)
        {
-               char    *relname = get_rel_name(tbl->tables[i]);
-               Oid     nspOid = get_rel_namespace(tbl->tables[i]);
-               char    *nspname = get_namespace_name(nspOid);
+               char    *relname = get_rel_name(tbl->tables[i]);
+               Oid             nspOid = get_rel_namespace(tbl->tables[i]);
+               char    *nspname = get_namespace_name(nspOid);
 
                if ( relname == NULL || nspOid == InvalidOid || nspname == NULL )
                        continue;
@@ -299,87 +402,211 @@ matchOid(TableList *tbl, Oid oid)
        return false;
 }
 
+#if PG_VERSION_NUM >= 90500
+static RangeVar*
+makeRangeVarFromOid(Oid relOid)
+{
+       return makeRangeVar(
+                               get_namespace_name(get_rel_namespace(relOid)),
+                               get_rel_name(relOid),
+                               -1
+                       );
+
+}
+#endif
+
 static void
-makeAnalyze(Oid relOid, CmdType operation, uint32 naffected)
+makeAnalyze(Oid relOid, CmdKind operation, int64 naffected)
 {
-       PgStat_StatTabEntry             *tabentry;
-       TimestampTz                     now = GetCurrentTimestamp();
+       TimestampTz                             now = GetCurrentTimestamp();
+       Relation                                rel;
+       OnlineAnalyzeTableType  reltype;
+       bool                                    found = false,
+                                                       newTable = false;
+       OnlineAnalyzeTableStat  *rstat,
+                                                       dummyrstat;
+       PgStat_StatTabEntry             *tabentry = NULL;
 
        if (relOid == InvalidOid)
                return;
 
-       tabentry = pgstat_fetch_stat_tabentry(relOid);
+       if (naffected == 0)
+               /* return if there is no changes */
+               return;
+       else if (naffected < 0)
+               /* number if affected rows is unknown */
+               naffected = 0;
 
-#if PG_VERSION_NUM >= 90000
-#define changes_since_analyze(t)       ((t)->changes_since_analyze)
-#else
-#define changes_since_analyze(t)       ((t)->n_live_tuples + (t)->n_dead_tuples - (t)->last_anl_tuples)
-#endif
-
-       if (    
-               tabentry == NULL /* a new table */ ||
-               (
-                       /* do not analyze too often, if both stamps are exceeded the go */
-                       TimestampDifferenceExceeds(tabentry->analyze_timestamp, now, online_analyze_min_interval) && 
-                       TimestampDifferenceExceeds(tabentry->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
-                       /* be in sync with relation_needs_vacanalyze */
-                       ((double)(changes_since_analyze(tabentry) + naffected)) >=
-                               online_analyze_scale_factor * ((double)(tabentry->n_dead_tuples + tabentry->n_live_tuples)) + 
-                                       (double)online_analyze_threshold
-               )
-       )
+       rel = RelationIdGetRelation(relOid);
+       if (rel->rd_rel->relkind != RELKIND_RELATION)
        {
-               VacuumStmt                              vacstmt;
-               TimestampTz                             startStamp, endStamp;
+               RelationClose(rel);
+               return;
+       }
 
-               /*
-                * includeTables overwrites excludeTables
-                */
-               switch(online_analyze_table_type)
-               {
-                       case OATT_ALL:
-                               if (matchOid(&excludeTables, relOid) == true && matchOid(&includeTables, relOid) == false)
-                                       return;
-                               break;
-                       case OATT_NONE:
+       reltype =
+#if PG_VERSION_NUM >= 90100
+               (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+#else
+               (rel->rd_istemp || rel->rd_islocaltemp)
+#endif
+                       ? OATT_TEMPORARY : OATT_PERSISTENT;
+
+       RelationClose(rel);
+
+       /*
+        * includeTables overwrites excludeTables
+        */
+       switch(online_analyze_table_type)
+       {
+               case OATT_ALL:
+                       if (get_rel_relkind(relOid) != RELKIND_RELATION ||
+                               (matchOid(&excludeTables, relOid) == true &&
+                               matchOid(&includeTables, relOid) == false))
+                               return;
+                       break;
+               case OATT_NONE:
+                       if (get_rel_relkind(relOid) != RELKIND_RELATION ||
+                               matchOid(&includeTables, relOid) == false)
+                               return;
+                       break;
+               case OATT_TEMPORARY:
+               case OATT_PERSISTENT:
+               default:
+                       /*
+                        * skip analyze if relation's type doesn't not match
+                        * online_analyze_table_type
+                        */
+                       if ((online_analyze_table_type & reltype) == 0 ||
+                               matchOid(&excludeTables, relOid) == true)
+                       {
                                if (matchOid(&includeTables, relOid) == false)
                                        return;
-                               break;
-                       case OATT_TEMPORARY:
-                       case OATT_PERSISTENT:
-                       default:
-                               {
-                                       Relation                                rel;
-                                       OnlineAnalyzeTableType  reltype;
+                       }
+                       break;
+       }
 
-                                       rel = RelationIdGetRelation(relOid);
-                                       reltype = 
-#if PG_VERSION_NUM >= 90100
-                                               (rel->rd_rel->relpersistence == RELPERSISTENCE_TEMP)
+       /*
+        * Do not store data about persistent table in local memory because we
+        * could not track changes of them: they could be changed by another
+        * backends. So always get a pgstat table entry.
+        */
+       if (reltype == OATT_TEMPORARY)
+               rstat = hash_search(relstats, &relOid, HASH_ENTER, &found);
+       else
+               rstat = &dummyrstat; /* found == false for following if */
+
+       if (!found)
+       {
+               MemSet(rstat, 0, sizeof(*rstat));
+               rstat->tableid = relOid;
+               newTable = true;
+       }
+
+       if (operation == CK_VACUUM)
+       {
+               /* force reread because vacuum could change n_tuples */
+               rstat->rereadStat = true;
+               return;
+       }
+       else if (operation == CK_ANALYZE)
+       {
+               /* only analyze */
+               rstat->mod_since_analyze = 0;
+               rstat->last_analyze_time = now;
+               if (newTable)
+                       rstat->rereadStat = true;
+               return;
+       }
+
+       Assert(rstat->tableid == relOid);
+
+       if (
+               /* do not reread data if it was a truncation */
+               operation != CK_TRUNCATE && operation != CK_FASTTRUNCATE &&
+               /* read  for persistent table and for temp teble if it allowed */
+               (reltype == OATT_PERSISTENT || online_analyze_local_tracking == false) &&
+               /* read only for new table or we know that it's needed */
+               (newTable == true || rstat->rereadStat == true)
+          )
+       {
+               rstat->rereadStat = false;
+
+               tabentry = pgstat_fetch_stat_tabentry(relOid);
+
+               if (tabentry)
+               {
+                       rstat->n_tuples =
+#if PG_VERSION_NUM >= 160000
+                               tabentry->dead_tuples + tabentry->live_tuples;
 #else
-                                               (rel->rd_istemp || rel->rd_islocaltemp)
+                               tabentry->n_dead_tuples + tabentry->n_live_tuples;
 #endif
-                                                       ? OATT_TEMPORARY : OATT_PERSISTENT;
-                                       RelationClose(rel);
 
-                                       /*
-                                        * skip analyze if relation's type doesn't not match online_analyze_table_type
-                                        */
-                                       if ((online_analyze_table_type & reltype) == 0 || matchOid(&excludeTables, relOid) == true)
-                                       {
-                                               if (matchOid(&includeTables, relOid) == false)
-                                                       return;
-                                       }
-                               }
-                               break;
+                       rstat->mod_since_analyze =
+#if PG_VERSION_NUM >= 160000
+                               tabentry->mod_since_analyze;
+#elif PG_VERSION_NUM >= 90000
+                               tabentry->changes_since_analyze;
+#else
+                               tabentry->n_live_tuples + tabentry->n_dead_tuples -
+                                       tabentry->last_anl_tuples;
+#endif
+
+                       rstat->last_autoanalyze_time =
+#if PG_VERSION_NUM >= 160000
+                               tabentry->last_autoanalyze_time;
+#else
+                               tabentry->autovac_analyze_timestamp;
+#endif
+
+                       rstat->last_analyze_time =
+#if PG_VERSION_NUM >= 160000
+                               tabentry->last_analyze_time;
+#else
+                               tabentry->analyze_timestamp;
+#endif
                }
+       }
+
+       if (newTable ||
+               /* force analyze after truncate, fasttruncate already did analyze */
+               operation == CK_TRUNCATE || (
+               /* do not analyze too often, if both stamps are exceeded the go */
+               TimestampDifferenceExceeds(rstat->last_analyze_time, now, online_analyze_min_interval) &&
+               TimestampDifferenceExceeds(rstat->last_autoanalyze_time, now, online_analyze_min_interval) &&
+               /* do not analyze too small tables */
+               rstat->n_tuples + rstat->mod_since_analyze + naffected > online_analyze_lower_limit &&
+               /* be in sync with relation_needs_vacanalyze */
+               ((double)(rstat->mod_since_analyze + naffected)) >=
+                        online_analyze_scale_factor * ((double)rstat->n_tuples) +
+                        (double)online_analyze_threshold))
+       {
+#if PG_VERSION_NUM < 90500
+               VacuumStmt                              vacstmt;
+#else
+               VacuumParams                    vacstmt;
+#endif
+               TimestampTz                             startStamp, endStamp;
+               int                                             flags;
+
+#ifdef PGPRO_EE
+               /* ATX is not compatible with online_analyze */
+               if (getNestLevelATX() != 0)
+                       return;
+#endif
+
+               memset(&startStamp, 0, sizeof(startStamp)); /* keep compiler quiet */
+
+               memset(&vacstmt, 0, sizeof(vacstmt));
 
-               vacstmt.type = T_VacuumStmt;
                vacstmt.freeze_min_age = -1;
                vacstmt.freeze_table_age = -1; /* ??? */
+
+#if PG_VERSION_NUM < 90500
+               vacstmt.type = T_VacuumStmt;
                vacstmt.relation = NULL;
                vacstmt.va_cols = NIL;
-
 #if PG_VERSION_NUM >= 90000
                vacstmt.options = VACOPT_ANALYZE;
                if (online_analyze_verbose)
@@ -389,16 +616,44 @@ makeAnalyze(Oid relOid, CmdType operation, uint32 naffected)
                vacstmt.analyze = true;
                vacstmt.verbose = online_analyze_verbose;
 #endif
+#else
+               vacstmt.multixact_freeze_min_age = -1;
+               vacstmt.multixact_freeze_table_age = -1;
+               vacstmt.log_min_duration = -1;
+#endif
+
 
                if (online_analyze_verbose)
                        startStamp = GetCurrentTimestamp();
 
-               analyze_rel(relOid, &vacstmt, GetAccessStrategy(BAS_VACUUM)
-#if (PG_VERSION_NUM < 90004) && (PG_VERSION_NUM >= 90000)
+               flags = VACOPT_ANALYZE | VACOPT_NOWAIT |
+                                       ((online_analyze_verbose) ?  VACOPT_VERBOSE : 0);
+
+#if PG_VERSION_NUM >= 120000
+               vacstmt.options = flags;
+#endif
+               analyze_rel(relOid,
+#if PG_VERSION_NUM < 90500
+                       &vacstmt
+#if PG_VERSION_NUM >= 90018
                        , true
+#endif
+                       , GetAccessStrategy(BAS_VACUUM)
+#if (PG_VERSION_NUM >= 90000) && (PG_VERSION_NUM < 90004)
+                       , true
+#endif
+#else
+                       makeRangeVarFromOid(relOid),
+#if PG_VERSION_NUM < 120000
+                       flags,
+#endif
+                       &vacstmt, NULL, true, GetAccessStrategy(BAS_VACUUM)
 #endif
                );
 
+               /* Make changes visible to subsequent calls */
+               CommandCounterIncrement();
+
                if (online_analyze_verbose)
                {
                        long    secs;
@@ -406,45 +661,175 @@ makeAnalyze(Oid relOid, CmdType operation, uint32 naffected)
 
                        endStamp = GetCurrentTimestamp();
                        TimestampDifference(startStamp, endStamp, &secs, &microsecs);
-                       elog(INFO, "analyze \"%s\" took %.02f seconds", 
-                               get_rel_name(relOid), ((double)secs) + ((double)microsecs)/1.0e6);
+                       elog(INFO, "analyze \"%s\" took %.02f seconds",
+                               get_rel_name(relOid),
+                               ((double)secs) + ((double)microsecs)/1.0e6);
                }
 
+               rstat->last_autoanalyze_time = now;
+               rstat->mod_since_analyze = 0;
 
-               if (tabentry == NULL)
+               switch(operation)
                {
-                       /* new table */
-                       pgstat_clear_snapshot();
+                       case CK_CREATE:
+                       case CK_INSERT:
+                       case CK_UPDATE:
+                               rstat->n_tuples += naffected;
+                               /* FALLTHROUGH */
+                       case CK_DELETE:
+                               rstat->rereadStat = (reltype == OATT_PERSISTENT);
+                               break;
+                       case CK_TRUNCATE:
+                       case CK_FASTTRUNCATE:
+                               rstat->rereadStat = false;
+                               rstat->n_tuples = 0;
+                               break;
+                       default:
+                               break;
                }
-               else
+
+               /* update last analyze timestamp in local memory of backend */
+               if (tabentry)
                {
-                       /* update last analyze timestamp in local memory of backend */
+#if PG_VERSION_NUM >= 160000
+                       tabentry->last_analyze_time = now;
+                       tabentry->mod_since_analyze = 0;
+#else
                        tabentry->analyze_timestamp = now;
+                       tabentry->changes_since_analyze = 0;
+#endif
                }
+#if 0
+               /* force reload stat for new table */
+               if (newTable)
+                       pgstat_clear_snapshot();
+#endif
        }
-#if PG_VERSION_NUM >= 90000
-       else if (tabentry != NULL)
+       else
        {
-               tabentry->changes_since_analyze += naffected;
-       }
+#if PG_VERSION_NUM >= 90000
+               if (tabentry)
+#if PG_VERSION_NUM >= 160000
+                       tabentry->mod_since_analyze += naffected;
+#else
+                       tabentry->changes_since_analyze += naffected;
+#endif
 #endif
+               switch(operation)
+               {
+                       case CK_CREATE:
+                       case CK_INSERT:
+                               rstat->mod_since_analyze += naffected;
+                               rstat->n_tuples += naffected;
+                               break;
+                       case CK_UPDATE:
+                               rstat->mod_since_analyze += 2 * naffected;
+                               rstat->n_tuples += naffected;
+                               break;
+                       case CK_DELETE:
+                               rstat->mod_since_analyze += naffected;
+                               break;
+                       case CK_TRUNCATE:
+                       case CK_FASTTRUNCATE:
+                               rstat->mod_since_analyze = 0;
+                               rstat->n_tuples = 0;
+                               break;
+                       default:
+                               break;
+               }
+       }
+
+       /* Reset local cache if we are over limit */
+       if (hash_get_num_entries(relstats) > online_analyze_capacity_threshold)
+               relstatsInit();
+}
+
+static Const*
+isFastTruncateCall(QueryDesc *queryDesc)
+{
+       TargetEntry     *te;
+       FuncExpr        *fe;
+       Const           *constval;
+
+       if (!(
+                 queryDesc->plannedstmt &&
+                 queryDesc->operation == CMD_SELECT &&
+                 queryDesc->plannedstmt->planTree &&
+                 queryDesc->plannedstmt->planTree->targetlist &&
+                 list_length(queryDesc->plannedstmt->planTree->targetlist) == 1
+                ))
+               return NULL;
+
+       te = linitial(queryDesc->plannedstmt->planTree->targetlist);
+
+       if (!IsA(te, TargetEntry))
+               return NULL;
+
+       fe = (FuncExpr*)te->expr;
+
+       if (!(
+                 fe && IsA(fe, FuncExpr) &&
+                 fe->funcid >= FirstNormalObjectId &&
+                 fe->funcretset == false &&
+                 fe->funcresulttype == VOIDOID &&
+                 fe->funcvariadic == false &&
+                 list_length(fe->args) == 1
+                ))
+               return NULL;
+
+       constval = linitial(fe->args);
+
+       if (!(
+                 IsA(constval,Const) &&
+                 constval->consttype == TEXTOID &&
+                 strcmp(get_func_name(fe->funcid), "fasttruncate") == 0
+                ))
+               return NULL;
+
+       return constval;
 }
 
+
 extern PGDLLIMPORT void onlineAnalyzeHooker(QueryDesc *queryDesc);
 void
-onlineAnalyzeHooker(QueryDesc *queryDesc) 
+onlineAnalyzeHooker(QueryDesc *queryDesc)
 {
-       uint32  naffected = 0;
+       int64   naffected = -1;
+       Const   *constval;
 
        if (queryDesc->estate)
-               naffected = queryDesc->estate->es_processed;    
+               naffected = queryDesc->estate->es_processed;
+
+       lateInit();
+
+#if PG_VERSION_NUM >= 90200
+       if (online_analyze_enable &&
+               (constval = isFastTruncateCall(queryDesc)) != NULL)
+       {
+               Datum           tblnamed = constval->constvalue;
+               char            *tblname = text_to_cstring(DatumGetTextP(tblnamed));
+#if PG_VERSION_NUM >= 160000
+               RangeVar        *tblvar =
+                       makeRangeVarFromNameList(stringToQualifiedNameList(tblname, NULL));
+#else
+               RangeVar        *tblvar =
+                       makeRangeVarFromNameList(stringToQualifiedNameList(tblname));
+#endif
+
+               makeAnalyze(RangeVarGetRelid(tblvar,
+                                                                        NoLock,
+                                                                        false),
+                                       CK_FASTTRUNCATE, -1);
+       }
+#endif
 
        if (online_analyze_enable && queryDesc->plannedstmt &&
-                       (queryDesc->operation == CMD_INSERT || 
+                       (queryDesc->operation == CMD_INSERT ||
                         queryDesc->operation == CMD_UPDATE ||
                         queryDesc->operation == CMD_DELETE
 #if PG_VERSION_NUM < 90200
-                        || (queryDesc->operation == CMD_SELECT && queryDesc->plannedstmt->intoClause)
+                        || (queryDesc->operation == CMD_SELECT &&
+                                queryDesc->plannedstmt->intoClause)
 #endif
                         ))
        {
@@ -455,7 +840,7 @@ onlineAnalyzeHooker(QueryDesc *queryDesc)
 
                        makeAnalyze(relOid, queryDesc->operation, naffected);
                }
-               else 
+               else
 #endif
                if (queryDesc->plannedstmt->resultRelations &&
                                 queryDesc->plannedstmt->rtable)
@@ -464,11 +849,11 @@ onlineAnalyzeHooker(QueryDesc *queryDesc)
 
                        foreach(l, queryDesc->plannedstmt->resultRelations)
                        {
-                               int                     n = lfirst_int(l);
+                               int                             n = lfirst_int(l);
                                RangeTblEntry   *rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
-               
+
                                if (rte->rtekind == RTE_RELATION)
-                                       makeAnalyze(rte->relid, queryDesc->operation, naffected);
+                                       makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected);
                        }
                }
        }
@@ -479,33 +864,305 @@ onlineAnalyzeHooker(QueryDesc *queryDesc)
                standard_ExecutorEnd(queryDesc);
 }
 
+static List            *toremove = NIL;
+
+/*
+ * removeTable called on transaction end, see call RegisterXactCallback() below
+ */
+static void
+removeTable(XactEvent event, void *arg)
+{
+       ListCell        *cell;
+
+       switch(event)
+       {
+               case XACT_EVENT_COMMIT:
+                       break;
+               case XACT_EVENT_ABORT:
+                       toremove = NIL;
+               default:
+                       return;
+       }
+
+       foreach(cell, toremove)
+       {
+               Oid     relOid = lfirst_oid(cell);
+
+               hash_search(relstats, &relOid, HASH_REMOVE, NULL);
+       }
+
+       toremove = NIL;
+}
+
+#if PG_VERSION_NUM >= 120000
+static int
+parse_vacuum_opt(VacuumStmt *vacstmt)
+{
+       int                     options = vacstmt->is_vacuumcmd ? VACOPT_VACUUM : VACOPT_ANALYZE;
+       ListCell        *lc;
+
+       foreach(lc, vacstmt->options)
+       {
+               DefElem *opt = (DefElem *) lfirst(lc);
+
+               /* Parse common options for VACUUM and ANALYZE */
+               if (strcmp(opt->defname, "verbose") == 0)
+                       options |= VACOPT_VERBOSE;
+               else if (strcmp(opt->defname, "skip_locked") == 0)
+                       options |= VACOPT_SKIP_LOCKED;
+               else if (strcmp(opt->defname, "analyze") == 0)
+                       options |= VACOPT_ANALYZE;
+               else if (strcmp(opt->defname, "freeze") == 0)
+                       options |= VACOPT_FREEZE;
+               else if (strcmp(opt->defname, "full") == 0)
+                       options |= VACOPT_FULL;
+               else if (strcmp(opt->defname, "disable_page_skipping") == 0)
+                       options |= VACOPT_DISABLE_PAGE_SKIPPING;
+       }
+
+       return options;
+}
+#endif
+
+
 #if PG_VERSION_NUM >= 90200
 static void
-onlineAnalyzeHookerUtility(Node *parsetree, const char *queryString, 
+onlineAnalyzeHookerUtility(
+#if PG_VERSION_NUM >= 100000
+                                                  PlannedStmt *pstmt,
+#else
+                                                  Node *parsetree,
+#endif
+                                                  const char *queryString,
+#if PG_VERSION_NUM >= 140000
+                                                  bool readOnlyTree,
+#endif
+#if PG_VERSION_NUM >= 90300
+                                                       ProcessUtilityContext context, ParamListInfo params,
+#if PG_VERSION_NUM >= 100000
+                                                       QueryEnvironment *queryEnv,
+#endif
+#else
                                                        ParamListInfo params, bool isTopLevel,
-                                                       DestReceiver *dest, char *completionTag) {
-       RangeVar        *tblname = NULL;
+#endif
+                                                       DestReceiver *dest,
+#if  PG_VERSION_NUM >= 130000
+                                                       QueryCompletion *completionTag
+#else
+                                                       char *completionTag
+#endif
+) {
+       List            *tblnames = NIL;
+       CmdKind         op = CK_INSERT;
+#if PG_VERSION_NUM >= 100000
+       Node            *parsetree = NULL;
+
+       if (pstmt->commandType == CMD_UTILITY)
+               parsetree = pstmt->utilityStmt;
+#endif
+
+       lateInit();
+
+       if (parsetree && online_analyze_enable)
+       {
+               if (IsA(parsetree, CreateTableAsStmt) &&
+                       ((CreateTableAsStmt*)parsetree)->into)
+               {
+                       tblnames =
+                               list_make1((RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel));
+                       op = CK_CREATE;
+               }
+               else if (IsA(parsetree, TruncateStmt))
+               {
+                       tblnames = list_copy(((TruncateStmt*)parsetree)->relations);
+                       op = CK_TRUNCATE;
+               }
+               else if (IsA(parsetree, DropStmt) &&
+                                ((DropStmt*)parsetree)->removeType == OBJECT_TABLE)
+               {
+                       ListCell        *cell;
+
+                       foreach(cell, ((DropStmt*)parsetree)->objects)
+                       {
+                               List            *relname = (List *) lfirst(cell);
+                               RangeVar        *rel = makeRangeVarFromNameList(relname);
+                               Oid                     relOid = RangeVarGetRelid(rel, NoLock, true);
+
+                               if (OidIsValid(relOid))
+                               {
+                                       MemoryContext   ctx;
+
+                                       ctx = MemoryContextSwitchTo(TopTransactionContext);
+                                       toremove = lappend_oid(toremove, relOid);
+                                       MemoryContextSwitchTo(ctx);
+                               }
+                       }
+               }
+               else if (IsA(parsetree, VacuumStmt))
+               {
+                       VacuumStmt      *vac = (VacuumStmt*)parsetree;
+                       int                     options =
+#if PG_VERSION_NUM >= 120000
+                                                       parse_vacuum_opt(vac)
+#else
+                                                       vac->options
+#endif
+                                                       ;
+
+
+#if PG_VERSION_NUM >= 110000
+                       tblnames = vac->rels;
+#else
+                       if (vac->relation)
+                               tblnames = list_make1(vac->relation);
+#endif
 
-       if (IsA(parsetree, CreateTableAsStmt) && ((CreateTableAsStmt*)parsetree)->into)
-               tblname = (RangeVar*)copyObject(((CreateTableAsStmt*)parsetree)->into->rel);
+                       if (options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE))
+                       {
+                               /* optionally with analyze */
+                               op = CK_VACUUM;
+
+                               /* drop all collected stat */
+                               if (tblnames == NIL)
+                                       relstatsInit();
+                       }
+                       else if (options & VACOPT_ANALYZE)
+                       {
+                               op = CK_ANALYZE;
+
+                               /* should reset all counters */
+                               if (tblnames == NIL)
+                               {
+                                       HASH_SEQ_STATUS                 hs;
+                                       OnlineAnalyzeTableStat  *rstat;
+                                       TimestampTz                             now = GetCurrentTimestamp();
+
+                                       hash_seq_init(&hs, relstats);
+
+                                       while((rstat = hash_seq_search(&hs)) != NULL)
+                                       {
+                                               rstat->mod_since_analyze = 0;
+                                               rstat->last_analyze_time = now;
+                                       }
+                               }
+                       }
+                       else
+                               tblnames = NIL;
+               }
+       }
+
+#if PG_VERSION_NUM >= 100000
+#define parsetree pstmt
+#endif
 
        if (oldProcessUtilityHook)
-               oldProcessUtilityHook(parsetree, queryString, params, isTopLevel, dest, completionTag);
+               oldProcessUtilityHook(parsetree, queryString,
+#if PG_VERSION_NUM >= 140000
+                                                         readOnlyTree,
+#endif
+#if PG_VERSION_NUM >= 90300
+                                                         context, params,
+#if PG_VERSION_NUM >= 100000
+                                                         queryEnv,
+#endif
+#else
+                                                         params, isTopLevel,
+#endif
+                                                         dest, completionTag);
        else
-               standard_ProcessUtility(parsetree, queryString, params, isTopLevel, dest, completionTag);
+               standard_ProcessUtility(parsetree, queryString,
+#if PG_VERSION_NUM >= 140000
+                                                               readOnlyTree,
+#endif
+#if PG_VERSION_NUM >= 90300
+                                                               context, params,
+#if PG_VERSION_NUM >= 100000
+                                                               queryEnv,
+#endif
+#else
+                                                               params, isTopLevel,
+#endif
+                                                               dest, completionTag);
+
+#if PG_VERSION_NUM >= 100000
+#undef parsetree
+#endif
+
+       if (tblnames) {
+               ListCell        *l;
 
-       if (tblname) {
-               Oid     tblOid = RangeVarGetRelid(tblname, NoLock, true);
+               foreach(l, tblnames)
+               {
+                       RangeVar        *tblname =
+#if PG_VERSION_NUM >= 110000
+                               (IsA(lfirst(l), VacuumRelation)) ?
+                                       ((VacuumRelation*)lfirst(l))->relation :
+#endif
+                                       (RangeVar*)lfirst(l);
+                       Oid     tblOid;
+
+                       Assert(IsA(tblname, RangeVar));
 
-               makeAnalyze(tblOid, CMD_INSERT, 0); 
+                       tblOid = RangeVarGetRelid(tblname, NoLock, true);
+                       makeAnalyze(tblOid, op, -1);
+               }
        }
 }
 #endif
 
+
+static void
+relstatsInit(void)
+{
+       HASHCTL hash_ctl;
+       int             flags = 0;
+
+       MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+
+       hash_ctl.hash = oid_hash;
+       flags |= HASH_FUNCTION;
+
+       if (onlineAnalyzeMemoryContext)
+       {
+               Assert(relstats != NULL);
+               MemoryContextReset(onlineAnalyzeMemoryContext);
+       }
+       else
+       {
+               Assert(relstats == NULL);
+
+#if PG_VERSION_NUM < 90600
+               onlineAnalyzeMemoryContext =
+                       AllocSetContextCreate(CacheMemoryContext,
+                       "online_analyze storage context",
+                       ALLOCSET_DEFAULT_MINSIZE,
+                       ALLOCSET_DEFAULT_INITSIZE,
+                       ALLOCSET_DEFAULT_MAXSIZE
+                       );
+#else
+               onlineAnalyzeMemoryContext =
+                       AllocSetContextCreate(CacheMemoryContext,
+                       "online_analyze storage context", ALLOCSET_DEFAULT_SIZES);
+#endif
+       }
+
+       hash_ctl.hcxt = onlineAnalyzeMemoryContext;
+       flags |= HASH_CONTEXT;
+
+       hash_ctl.keysize = sizeof(Oid);
+
+       hash_ctl.entrysize = sizeof(OnlineAnalyzeTableStat);
+       flags |= HASH_ELEM;
+
+       relstats = hash_create("online_analyze storage", 1024, &hash_ctl, flags);
+}
+
 void _PG_init(void);
 void
 _PG_init(void)
 {
+       relstatsInit();
+
        oldExecutorEndHook = ExecutorEnd_hook;
 
        ExecutorEnd_hook = onlineAnalyzeHooker;
@@ -536,6 +1193,25 @@ _PG_init(void)
                NULL
        );
 
+       DefineCustomBoolVariable(
+               "online_analyze.local_tracking",
+               "Per backend tracking",
+               "Per backend tracking for temp tables (do not use system statistic)",
+               &online_analyze_local_tracking,
+#if PG_VERSION_NUM >= 80400
+               online_analyze_local_tracking,
+#endif
+               PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+               GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+               NULL,
+#endif
+#endif
+               NULL,
+               NULL
+       );
+
        DefineCustomBoolVariable(
                "online_analyze.verbose",
                "Verbosity of on-line analyze",
@@ -555,7 +1231,7 @@ _PG_init(void)
                NULL
        );
 
-    DefineCustomRealVariable(
+       DefineCustomRealVariable(
                "online_analyze.scale_factor",
                "fraction of table size to start on-line analyze",
                "fraction of table size to start on-line analyze",
@@ -576,7 +1252,7 @@ _PG_init(void)
                NULL
        );
 
-    DefineCustomIntVariable(
+       DefineCustomIntVariable(
                "online_analyze.threshold",
                "min number of row updates before on-line analyze",
                "min number of row updates before on-line analyze",
@@ -597,11 +1273,32 @@ _PG_init(void)
                NULL
        );
 
-    DefineCustomRealVariable(
+       DefineCustomIntVariable(
+               "online_analyze.capacity_threshold",
+               "Max local cache table capacity",
+               "Max local cache table capacity",
+               &online_analyze_capacity_threshold,
+#if PG_VERSION_NUM >= 80400
+               online_analyze_capacity_threshold,
+#endif
+               0,
+               0x7fffffff,
+               PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+               GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+               NULL,
+#endif
+#endif
+               NULL,
+               NULL
+       );
+
+       DefineCustomRealVariable(
                "online_analyze.min_interval",
                "minimum time interval between analyze call (in milliseconds)",
                "minimum time interval between analyze call (in milliseconds)",
-               &online_analyze_scale_factor,
+               &online_analyze_min_interval,
 #if PG_VERSION_NUM >= 80400
                online_analyze_min_interval,
 #endif
@@ -629,7 +1326,7 @@ _PG_init(void)
                online_analyze_table_type_options,
                PGC_USERSET,
 #if PG_VERSION_NUM >= 80400
-        GUC_NOT_IN_SAMPLE,
+               GUC_NOT_IN_SAMPLE,
 #if PG_VERSION_NUM >= 90100
                NULL,
 #endif
@@ -638,7 +1335,7 @@ _PG_init(void)
                NULL
        );
 
-    DefineCustomStringVariable(
+       DefineCustomStringVariable(
                "online_analyze.exclude_tables",
                "List of tables which will not online analyze",
                NULL,
@@ -657,7 +1354,7 @@ _PG_init(void)
                excludeTablesShow
        );
 
-    DefineCustomStringVariable(
+       DefineCustomStringVariable(
                "online_analyze.include_tables",
                "List of tables which will online analyze",
                NULL,
@@ -675,22 +1372,27 @@ _PG_init(void)
 #endif
                includeTablesShow
        );
-}
 
-void _PG_fini(void);
-void
-_PG_fini(void)
-{
-       ExecutorEnd_hook = oldExecutorEndHook;
-#if PG_VERSION_NUM >= 90200
-       ProcessUtility_hook = oldProcessUtilityHook;
+       DefineCustomIntVariable(
+               "online_analyze.lower_limit",
+               "min number of rows in table to analyze",
+               "min number of rows in table to analyze",
+               &online_analyze_lower_limit,
+#if PG_VERSION_NUM >= 80400
+               online_analyze_lower_limit,
 #endif
+               0,
+               0x7fffffff,
+               PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+               GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+               NULL,
+#endif
+#endif
+               NULL,
+               NULL
+       );
 
-       if (excludeTables.tables)
-               free(excludeTables.tables);
-       if (includeTables.tables)
-               free(includeTables.tables);
-
-       excludeTables.tables = includeTables.tables = NULL;
-       excludeTables.nTables = includeTables.nTables = 0;
+       RegisterXactCallback(removeTable, NULL);
 }