diff --git a/contrib/auto_explain/Readme.md b/contrib/auto_explain/Readme.md
new file mode 100644
index 0000000..64a1983
--- /dev/null
+++ b/contrib/auto_explain/Readme.md
@@ -0,0 +1,62 @@
+Auto_explain module can be use to log plans of long queries.
+Using "auto_explain.log_min_duration" parameter it is possible to specify logging threshold.
+Performing explain with analyze provides us information not only about query plan and execution time,
+but also actual and estimated number of rows for each plan node.
+This information can be used for query optimizer tuning: adaptive query optimization (AQO).
+
+Auto_explain provides two AQO modes:
+1. Generation multicolumn statistics for clauses with large estimation error.
+2. Adjust selectivity for such clauses.
+
+Estimation errors in Postgres are mostly caused by correlation between columns which is not taken in account by
+Postgres optimizer, because by default only single-column statistics is collected.
+Multicolumn statistics can be only explicitly created by DBA.
+Auto_explain makes it possible to automatically create multicolumn statistics for
+columns causes with huge estimation errors.
+Generation of multicolumn statistics can be ttoggled by setting "auto_explain.add_statistics_threshold" option
+which specifies minimal actual/estimated #rows ratio for multicolumn statistics generation.
+Once multicolumn statistics is added, you should perform ANALYZE command and then this updated statistics
+will be used by query optimizer in all backends.
+
+Unfortunately right now multicolumn statistics
+is used only for restriction clauses, not for joins and for joins estimation errors
+are most critical. This is why AQO makes it possible to directly adjust estimation of number of rows for particular nodes.
+
+Option "auto_explain.aqo_threshold" specifies minimal actual/estimated #rows ratio
+for node selectivity adjustment. When values of this option is greater than zero, AQO stores
+correction coefficient for nodes with equal or greater estimation error.
+Correction coefficient is stored in small array in element with index floor(log10(estimation)).
+When optimizer assigns estimated number of rows, it searches in AQO hash if correction coefficient
+is available for this node and if so, adjusts (multiplies) estimation by this coefficient.
+
+As far as we get actual values only for one particular plan, AQO is able to store correction coefficient only for
+clauses used in nodes of this plan. After such adjustment this plan most like becomes non-optimal which cause optimizer
+to choose other plan. But there may be no information about actually selectivity of nodes of new winner plan.
+And actually it can be even less efficient than original plan. So it can take several iterations before
+best plan will be actually selected. The more joins query has, the more alternative plans exist (factorial of
+number of joins). This why many iteration may be needed and some of them can cause choosing bad execution plan.
+
+Once optimal plan is constructed, you can disable further learning by setting -1 value to "auto_explain.aqo_threshold".
+In this case optimizer will still use stored AQO data for estimations adjustment, but doesn't update this
+information. Assigning zero value to "auto_explain.aqo_threshold" completely disable AQO optimization.
+
+AQO is collecting information and performs estimation adjustment only within current backend.
+Other backends can perform independent query tuning. Once session is closed, all collected AQO information is lost.
+But it is possible to save collected AQO information in the file, specified by "auto_explain.aqo_file" option.
+AQO information will be saved on backend exit. This file is always overwritten, so if several backends
+tries to save AQO data, only data of last one will be kept. So the intended way of AQO usage is that
+you perform learning in one backend (specifying positive "auto_explain.aqo_threshold") and then store this result
+in some file. All other backends should use "auto_explain.aqo_threshold"=-1 and specify "auto_explain.aqo_file",
+which cause them to load this AQO information from this file.
+
+By default AQO takes in account actual constant values in clauses when perform clauses matching.
+It increase estimation quality because of data skews: frequency of different values can significantly vary.
+But it cause fast growth of AQO hash, because each new constant value produces new entry in the hash.
+AQO provides two ways to address this problems.
+
+First of all you can ignore constant values. It can be achieved by setting "on" value to
+"auto_explain.aqo_disregard_constants" option. In this case all literals will be treated as parameters with unknown
+value.
+
+Also you can limit amount of collected AQO data by setting non zero value to "auto_explain.aqo_limit".
+In this case LRU replacement algorithm will be used to restrict size of AQO hash.
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index a9536c2..48d2365 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -13,12 +13,25 @@
 #include "postgres.h"
 
 #include <limits.h>
+#include <math.h>
 
 #include "access/parallel.h"
 #include "commands/explain.h"
+#include "commands/defrem.h"
 #include "executor/instrument.h"
 #include "jit/jit.h"
+#include "nodes/makefuncs.h"
+#include "nodes/nodeFuncs.h"
+#include "optimizer/cost.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/planmain.h"
+#include "parser/parsetree.h"
+#include "storage/ipc.h"
+#include "statistics/statistics.h"
 #include "utils/guc.h"
+#include "utils/syscache.h"
+#include "utils/lsyscache.h"
+#include "utils/ruleutils.h"
 
 PG_MODULE_MAGIC;
 
@@ -34,6 +47,29 @@ static int	auto_explain_log_format = EXPLAIN_FORMAT_TEXT;
 static int	auto_explain_log_level = LOG;
 static bool auto_explain_log_nested_statements = false;
 static double auto_explain_sample_rate = 1;
+static double auto_explain_add_statistics_threshold = 0.0;
+static double auto_explain_aqo_threshold = 0.0;
+static int  auto_explain_aqo_limit = 0;
+static bool auto_explain_aqo_disregard_constants = false;
+static char* auto_explain_aqo_file;
+
+static void AQO_set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
+										   RelOptInfo *outer_rel,
+										   RelOptInfo *inner_rel,
+										   SpecialJoinInfo *sjinfo,
+										   List *restrict_clauses);
+static void AQO_set_baserel_rows_estimate(PlannerInfo *root, RelOptInfo *rel);
+static double AQO_get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
+											 Path *outer_path,
+											 Path *inner_path,
+											 SpecialJoinInfo *sjinfo,
+											 List *restrict_clauses);
+static double AQO_get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
+												 List *param_clauses);
+static void AQO_copy_generic_path_info(Plan *dest, Path *src);
+static void LoadAQOHash(char const* file);
+static void SaveAQOHash(char const* file);
+
 
 static const struct config_enum_entry format_options[] = {
 	{"text", EXPLAIN_FORMAT_TEXT, false},
@@ -73,6 +109,11 @@ static ExecutorStart_hook_type prev_ExecutorStart = NULL;
 static ExecutorRun_hook_type prev_ExecutorRun = NULL;
 static ExecutorFinish_hook_type prev_ExecutorFinish = NULL;
 static ExecutorEnd_hook_type prev_ExecutorEnd = NULL;
+static set_joinrel_size_estimates_hook_type prev_set_joinrel_size_estimates_hook;
+static set_baserel_rows_estimate_hook_type prev_set_baserel_rows_estimate_hook;
+static get_parameterized_joinrel_size_hook_type prev_get_parameterized_joinrel_size_hook;
+static get_parameterized_baserel_size_hook_type prev_get_parameterized_baserel_size_hook;
+static copy_generic_path_info_hook_type prev_copy_generic_path_info_hook;
 
 void		_PG_init(void);
 void		_PG_fini(void);
@@ -218,6 +259,69 @@ _PG_init(void)
 							 NULL,
 							 NULL);
 
+	DefineCustomRealVariable("auto_explain.add_statistics_threshold",
+							 "Sets the threshold for actual/estimated #rows ratio triggering creation of multicolumn statistic for the related columns.",
+							 "Zero disables implicit creation of multicolumn statistic.",
+							 &auto_explain_add_statistics_threshold,
+							 0.0,
+							 0.0,
+							 INT_MAX,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	DefineCustomRealVariable("auto_explain.aqo_threshold",
+							 "Sets the threshold for actual/estimated #rows ratio for storing AQO correction coefficient for plan nodes",
+							 "Non-zero value of this parameter enables AQO learning mode when it collects information and uses it for plan adjustment. "
+							 "Negative value of this parameter disable update of AQO statistic but enables using it for query adjustment. "
+							 "Zero value completely disables AQO",
+							 &auto_explain_aqo_threshold,
+							 0.0,
+							 -1,
+							 INT_MAX,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	DefineCustomStringVariable("auto_explain.aqo_file",
+							   "File for saving/loading AQO information",
+							   NULL,
+							   &auto_explain_aqo_file,
+							   "",
+							   PGC_SUSET,
+							   0,
+							   NULL,
+							   NULL,
+							   NULL);
+	DefineCustomIntVariable("auto_explain.aqo_limit",
+							"Limit number of clauses for which AQO information is stored.",
+							"Non-zero value of this parameter limits about of information stored by AQO (to avoid memory overflow). "
+							"LRU replacement algorithm is used to maintain AQO hash.",
+							 &auto_explain_aqo_limit,
+							 0,
+							 0,
+							 INT_MAX,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+
+	DefineCustomBoolVariable("auto_explain.aqo_disregard_constants",
+							 "Do not take in account constant values when matching plans.",
+							 NULL,
+							 &auto_explain_aqo_disregard_constants,
+							 false,
+							 PGC_SUSET,
+							 0,
+							 NULL,
+							 NULL,
+							 NULL);
+
 	EmitWarningsOnPlaceholders("auto_explain");
 
 	/* Install hooks. */
@@ -229,6 +333,21 @@ _PG_init(void)
 	ExecutorFinish_hook = explain_ExecutorFinish;
 	prev_ExecutorEnd = ExecutorEnd_hook;
 	ExecutorEnd_hook = explain_ExecutorEnd;
+
+	prev_set_baserel_rows_estimate_hook = set_baserel_rows_estimate_hook;
+	set_baserel_rows_estimate_hook = AQO_set_baserel_rows_estimate;
+
+	prev_set_joinrel_size_estimates_hook = set_joinrel_size_estimates_hook;
+	set_joinrel_size_estimates_hook = AQO_set_joinrel_size_estimates;
+
+	prev_get_parameterized_joinrel_size_hook = get_parameterized_joinrel_size_hook;
+	get_parameterized_joinrel_size_hook = AQO_get_parameterized_joinrel_size;
+
+	prev_get_parameterized_baserel_size_hook = get_parameterized_baserel_size_hook;
+	get_parameterized_baserel_size_hook = AQO_get_parameterized_baserel_size;
+
+	prev_copy_generic_path_info_hook = copy_generic_path_info_hook;
+	copy_generic_path_info_hook = AQO_copy_generic_path_info;
 }
 
 /*
@@ -242,6 +361,11 @@ _PG_fini(void)
 	ExecutorRun_hook = prev_ExecutorRun;
 	ExecutorFinish_hook = prev_ExecutorFinish;
 	ExecutorEnd_hook = prev_ExecutorEnd;
+	set_joinrel_size_estimates_hook = prev_set_joinrel_size_estimates_hook;
+	set_baserel_rows_estimate_hook = prev_set_baserel_rows_estimate_hook;
+	get_parameterized_joinrel_size_hook = prev_get_parameterized_joinrel_size_hook;
+	get_parameterized_baserel_size_hook = prev_get_parameterized_baserel_size_hook;
+	copy_generic_path_info_hook = prev_copy_generic_path_info_hook;
 }
 
 /*
@@ -353,6 +477,871 @@ explain_ExecutorFinish(QueryDesc *queryDesc)
 	PG_END_TRY();
 }
 
+static void
+AddMultiColumnStatisticsForNode(PlanState *planstate, ExplainState *es);
+
+static void
+AddMultiColumnStatisticsForSubPlans(List *plans, ExplainState *es)
+{
+	ListCell   *lst;
+
+	foreach(lst, plans)
+	{
+		SubPlanState *sps = (SubPlanState *) lfirst(lst);
+
+		AddMultiColumnStatisticsForNode(sps->planstate, es);
+	}
+}
+
+static void
+AddMultiColumnStatisticsForMemberNodes(PlanState **planstates, int nsubnodes,
+									ExplainState *es)
+{
+	int			j;
+
+	for (j = 0; j < nsubnodes; j++)
+		AddMultiColumnStatisticsForNode(planstates[j], es);
+}
+
+static void
+AddMultiColumnStatisticsForQual(void* qual, ExplainState *es)
+{
+	List *vars = NULL;
+	ListCell* lc;
+	foreach (lc, qual)
+	{
+		Node* node = (Node*)lfirst(lc);
+		if (IsA(node, RestrictInfo))
+			node = (Node*)((RestrictInfo*)node)->clause;
+		vars = list_concat(vars, pull_vars_of_level(node, 0));
+	}
+	while (vars != NULL)
+	{
+		ListCell *cell, *next, *prev = NULL;
+		List *cols = NULL;
+		Index varno = 0;
+		Bitmapset* colmap = NULL;
+
+		for (cell = list_head(vars); cell != NULL; cell = next)
+		{
+			Node* node = (Node *) lfirst(cell);
+			next = lnext(cell);
+			if (IsA(node, Var))
+			{
+				Var *var = (Var *) node;
+				if (cols == NULL || var->varno == varno)
+				{
+					varno = var->varno;
+					if (var->varattno > 0 &&
+						!bms_is_member(var->varattno, colmap) &&
+						varno >= 1 &&
+						varno <= list_length(es->rtable) &&
+						list_length(cols) < STATS_MAX_DIMENSIONS)
+					{
+						RangeTblEntry *rte = rt_fetch(varno, es->rtable);
+						if (rte->rtekind == RTE_RELATION)
+						{
+							ColumnRef  *col = makeNode(ColumnRef);
+							char *colname = get_rte_attribute_name(rte, var->varattno);
+							col->fields = list_make1(makeString(colname));
+							cols = lappend(cols, col);
+							colmap = bms_add_member(colmap, var->varattno);
+						}
+					}
+				}
+				else
+				{
+					prev = cell;
+					continue;
+				}
+			}
+			vars = list_delete_cell(vars, cell, prev);
+		}
+		if (list_length(cols) >= 2)
+		{
+			CreateStatsStmt* stats = makeNode(CreateStatsStmt);
+			RangeTblEntry *rte = rt_fetch(varno, es->rtable);
+			char *rel_namespace = get_namespace_name(get_rel_namespace(rte->relid));
+			char *rel_name = get_rel_name(rte->relid);
+			RangeVar* rel = makeRangeVar(rel_namespace, rel_name, 0);
+			char* stat_name = rel_name;
+
+			/* Construct name for statistic by concatenating relation name with all columns */
+			foreach (cell, cols)
+				stat_name = psprintf("%s_%s", stat_name, strVal((Value *) linitial(((ColumnRef *)lfirst(cell))->fields)));
+
+			/*
+			 * Check if multicolumn if multicolumn statistic object with such name already exists
+			 * (most likely if was already created by auto_explain, but either ANALYZE was not performed since
+			 * this time, either presence of this multicolumn statistic doesn't help to provide more precise estimation.
+			 * Despite to the fact that we create statistics with "if_not_exist" option, presence of such check
+			 * allows to eliminate notice message that statistics object already exists.
+			 */
+			if (!SearchSysCacheExists2(STATEXTNAMENSP,
+									   CStringGetDatum(stat_name),
+									   ObjectIdGetDatum(get_rel_namespace(rte->relid))))
+			{
+				stats->defnames = list_make2(makeString(rel_namespace), makeString(stat_name));
+				stats->if_not_exists = true;
+				stats->relations = list_make1(rel);
+				stats->exprs = cols;
+				CreateStatistics(stats);
+			}
+		}
+	}
+}
+
+static void
+AddMultiColumnStatisticsForNode(PlanState *planstate, ExplainState *es)
+{
+	Plan	   *plan = planstate->plan;
+
+	if (planstate->instrument && plan->plan_rows != 0)
+	{
+		if (auto_explain_add_statistics_threshold != 0
+			&& planstate->instrument->ntuples / plan->plan_rows >= auto_explain_add_statistics_threshold)
+		{
+			AddMultiColumnStatisticsForQual(plan->path_clauses, es);
+		}
+	}
+
+	/* initPlan-s */
+	if (planstate->initPlan)
+		AddMultiColumnStatisticsForSubPlans(planstate->initPlan, es);
+
+	/* lefttree */
+	if (outerPlanState(planstate))
+		AddMultiColumnStatisticsForNode(outerPlanState(planstate), es);
+
+	/* righttree */
+	if (innerPlanState(planstate))
+		AddMultiColumnStatisticsForNode(innerPlanState(planstate), es);
+
+	/* special child plans */
+	switch (nodeTag(plan))
+	{
+		case T_ModifyTable:
+			AddMultiColumnStatisticsForMemberNodes(((ModifyTableState *) planstate)->mt_plans,
+												   ((ModifyTableState *) planstate)->mt_nplans,
+												   es);
+			break;
+		case T_Append:
+			AddMultiColumnStatisticsForMemberNodes(((AppendState *) planstate)->appendplans,
+												   ((AppendState *) planstate)->as_nplans,
+												   es);
+			break;
+		case T_MergeAppend:
+			AddMultiColumnStatisticsForMemberNodes(((MergeAppendState *) planstate)->mergeplans,
+												   ((MergeAppendState *) planstate)->ms_nplans,
+												   es);
+			break;
+		case T_BitmapAnd:
+			AddMultiColumnStatisticsForMemberNodes(((BitmapAndState *) planstate)->bitmapplans,
+												   ((BitmapAndState *) planstate)->nplans,
+												   es);
+			break;
+		case T_BitmapOr:
+			AddMultiColumnStatisticsForMemberNodes(((BitmapOrState *) planstate)->bitmapplans,
+												   ((BitmapOrState *) planstate)->nplans,
+												   es);
+			break;
+		case T_SubqueryScan:
+			AddMultiColumnStatisticsForNode(((SubqueryScanState *) planstate)->subplan, es);
+			break;
+		default:
+			break;
+	}
+}
+
+/*
+ * Adaptive query optimization
+ */
+#ifndef AQO_DEBUG
+#define AQO_DEBUG 0
+#endif
+
+#if AQO_DEBUG
+#define AQO_LOG LOG
+#define AQO_PRETTY_KEY(key) key
+#else
+#define AQO_LOG DEBUG1
+#define AQO_PRETTY_KEY(key) ""
+#endif
+
+static void
+StoreAQOInfoForNode(PlanState *planstate, ExplainState *es);
+
+static void
+StoreAQOInfoForSubPlans(List *plans, ExplainState *es)
+{
+	ListCell   *lst;
+
+	foreach(lst, plans)
+	{
+		SubPlanState *sps = (SubPlanState *) lfirst(lst);
+
+		StoreAQOInfoForNode(sps->planstate, es);
+	}
+}
+
+static void
+StoreAQOInfoForMemberNodes(PlanState **planstates, int nsubnodes,
+						   ExplainState *es)
+{
+	int			j;
+
+	for (j = 0; j < nsubnodes; j++)
+		StoreAQOInfoForNode(planstates[j], es);
+}
+
+/*
+ * Replace location in plan nodes with -1 to make it possible to match nodes from different queries.
+ */
+static Node*
+ReplaceLocation(Node* expr, int location)
+{
+	if (expr == NULL)
+		return NULL;
+	switch (nodeTag(expr))
+	{
+		case T_RangeVar:
+			((RangeVar *) expr)->location = location;
+			break;
+		case T_TableFunc:
+			((TableFunc *) expr)->location = location;
+			break;
+		case T_Var:
+		    ((Var *) expr)->location = location;
+			break;
+		case T_Const:
+		    ((Const *) expr)->location = location;
+			break;
+		case T_Param:
+			((Param *) expr)->location = location;
+			break;
+		case T_OpExpr:
+		case T_NullIfExpr:		/* struct-equivalent to OpExpr */
+		    ((OpExpr *) expr)->location = location;
+			break;
+		case T_ScalarArrayOpExpr:
+		    ((ScalarArrayOpExpr *) expr)->location = location;
+			break;
+		case T_BoolExpr:
+		  ((BoolExpr *) expr)->location = location;
+			break;
+		case T_RelabelType:
+			((RelabelType *) expr)->location = location;
+			break;
+		case T_CaseExpr:
+			((CaseExpr *) expr)->location = location;
+			break;
+		case T_CaseWhen:
+		    ((CaseWhen *) expr)->location = location;
+			break;
+		case T_ArrayExpr:
+			((ArrayExpr *) expr)->location = location;
+			break;
+		case T_NullTest:
+			((NullTest *) expr)->location = location;
+			break;
+		case T_BooleanTest:
+			((BooleanTest *) expr)->location = location;
+			break;
+		case T_CoerceToDomain:
+			((CoerceToDomain *) expr)->location = location;
+			break;
+		case T_CoerceToDomainValue:
+			((CoerceToDomainValue *) expr)->location = location;
+			break;
+		case T_ColumnRef:
+			((ColumnRef *) expr)->location = location;
+			break;
+		case T_ParamRef:
+			((ParamRef *) expr)->location = location;
+			break;
+		case T_A_Const:
+			((A_Const*) expr)->location = location;
+			break;
+		case T_A_Expr:
+			((A_Expr*)expr)->location = location;
+			break;
+		case T_FuncCall:
+			((FuncCall *) expr)->location = location;
+			break;
+		case T_A_ArrayExpr:
+			((ArrayExpr *) expr)->location = location;
+			break;
+		case T_TypeCast:
+			((TypeCast *) expr)->location = location;
+			break;
+		default:
+			break;
+	 }
+	return expr;
+}
+
+/*
+ * Clause trasformation required to use expression tree as hash key.
+ * We do two unconditional tranformation:
+ * 1. Replace varno with OID in Var nodes
+ * 2. Replace location with -1
+ * and one conditional transformation of constant to parameters when auto_explain_aqo_disregard_constants is on.
+ */
+static Node*
+TransformClause(Node* node, void* context)
+{
+	if (node == NULL)
+		return NULL;
+	if (auto_explain_aqo_disregard_constants && IsA(node, Const))
+	{
+		Const* constant = (Const*)node;
+		Param* param = makeNode(Param);
+		param->paramkind = PARAM_EXEC;
+		param->paramtype = constant->consttype;
+		param->paramtypmod = constant->consttypmod;
+		param->paramcollid = constant->constcollid;
+		param->location = -1;
+		return (Node*)param;
+	}
+	else if (IsA(node, Var))
+	{
+		Var* oldvar = (Var*)node;
+		if (oldvar->varno > 0)
+		{
+			List* rtable = (List*)context;
+			Var* newvar = makeNode(Var);
+			RangeTblEntry *rte = rt_fetch(oldvar->varno, rtable);
+			*newvar = *oldvar;
+			newvar->varno = newvar->varnoold = rte->relid;
+			newvar->location = -1;
+			return (Node*)newvar;
+		}
+	}
+	return ReplaceLocation(expression_tree_mutator(node, TransformClause, context), -1);
+}
+
+/*
+ * Convert quals list to string used as hash key.
+ * expression_tree_mutator is not able to handle ReistrictClause nodes, so we have to perform loop here.
+ */
+static char*
+QualsListToString(List* quals, List* rtable)
+{
+	ListCell *lc;
+	List* clauses = NULL;
+	foreach(lc, quals)
+	{
+		Node* node = (Node*)lfirst(lc);
+		if (IsA(node, RestrictInfo))
+			node = (Node*)((RestrictInfo*)node)->clause;
+		node = TransformClause(node, (void*)rtable);
+		clauses = lappend(clauses, node);
+	}
+	return nodeToString(clauses);
+}
+
+typedef struct AQOHashKey
+{
+	char*  predicate; /* text represantation of path clause */
+} AQOHashKey;
+
+/*
+ * Number of bins (correction coeficients) stored for each hash entry.
+ * Each bin corresponds to log10 of estimated nubmer fo rows.
+ */
+#define AQO_MAX_BINS 6
+
+#define MAX_AQO_KEY_LEN (16*1024)
+
+typedef struct AQOHashEntry
+{
+	AQOHashKey           key;
+	float                correction[AQO_MAX_BINS];
+	struct AQOHashEntry* next; /* LRU list */
+	struct AQOHashEntry* prev;
+} AQOHashEntry;
+
+static uint32
+AQOEntryHashFunc(const void *key, Size keysize)
+{
+	AQOHashKey* entry = (AQOHashKey*)key;
+	return string_hash(entry->predicate, INT_MAX);
+}
+
+static int
+AQOEntryMatchFunc(const void *key1, const void *key2, Size keysize)
+{
+	AQOHashKey* e1 = (AQOHashKey*)key1;
+	AQOHashKey* e2 = (AQOHashKey*)key2;
+    return strcmp(e1->predicate, e2->predicate);
+}
+
+static void*
+AQOEntryCopyFunc(void *dst_entry, const void *src_entry, Size keysize)
+{
+	AQOHashKey* dst = (AQOHashKey*)dst_entry;
+	AQOHashKey* src = (AQOHashKey*)src_entry;
+	dst->predicate = MemoryContextStrdup(TopMemoryContext, src->predicate);
+	return dst;
+}
+
+static HTAB* AQOHash;
+#define AQO_HASH_INIT_SIZE 1013  /* start small and extend */
+
+static AQOHashEntry AQOHashLRU;
+static bool AQOHashUpdated;
+
+static void
+AQO_on_exit_callback(int code, Datum arg)
+{
+	if (auto_explain_aqo_file && *auto_explain_aqo_file && AQOHashUpdated)
+		SaveAQOHash(auto_explain_aqo_file);
+}
+
+static void
+InitAQOHash(void)
+{
+	if (AQOHash == NULL)
+	{
+		HASHCTL		hash_ctl;
+
+		/* Create the hashtable proper */
+		MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+		hash_ctl.keysize = sizeof(AQOHashKey);
+		hash_ctl.entrysize = sizeof(AQOHashEntry);
+		hash_ctl.hash = AQOEntryHashFunc;
+		hash_ctl.match = AQOEntryMatchFunc;
+		hash_ctl.keycopy = AQOEntryCopyFunc;
+		hash_ctl.hcxt = TopMemoryContext;
+		AQOHash = hash_create("aqo_hash",
+							  auto_explain_aqo_limit != 0 ? auto_explain_aqo_limit : AQO_HASH_INIT_SIZE,
+							  &hash_ctl,
+							  HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_KEYCOPY | HASH_CONTEXT);
+		AQOHashLRU.next = AQOHashLRU.prev = &AQOHashLRU;
+
+		on_proc_exit(AQO_on_exit_callback, 0);
+	}
+}
+
+static void SaveAQOHash(char const* file)
+{
+	char tmp_file[MAXPGPATH];
+	int fd;
+	FILE* f;
+	sprintf(tmp_file, "%s.XXXXXX", file);
+	fd = mkstemp(tmp_file);
+	f = fdopen(fd, "w");
+	if (f == NULL)
+	{
+		elog(WARNING, "Failed to save AQO information in file %s: %m", tmp_file);
+		return;
+	}
+	if (AQOHash)
+	{
+		HASH_SEQ_STATUS status;
+        AQOHashEntry* entry;
+		int i;
+		hash_seq_init(&status, AQOHash);
+
+        while ((entry = hash_seq_search(&status)) != NULL)
+		{
+			char sep = '\n';
+			fputs(entry->key.predicate, f);
+			for (i = 0; i < AQO_MAX_BINS; i++)
+			{
+				fputc(sep, f);
+				fprintf(f, "%f", entry->correction[i]);
+				sep = ' ';
+			}
+			fputc('\n', f);
+		}
+	}
+	if (fclose(f) < 0)
+		elog(WARNING, "Failed to write AQO file %s: %m", tmp_file);
+	if (rename(tmp_file, file) < 0)
+		elog(WARNING, "Failed to save AQO file %s: %m", file);
+}
+
+static void LoadAQOHash(char const* file)
+{
+	FILE* f = fopen(file, "r");
+	char buf[MAX_AQO_KEY_LEN];
+	AQOHashKey key;
+	key.predicate = buf;
+	if (f == NULL)
+	{
+		elog(WARNING, "Failed to load AQO information from file %s: %m", file);
+		return;
+	}
+	InitAQOHash();
+	while (fgets(buf, sizeof buf, f))
+	{
+		AQOHashEntry* entry;
+		int i;
+		buf[strlen(buf)-1] = '\0'; /* remove new line */
+		entry = hash_search(AQOHash, &key, HASH_ENTER, NULL);
+		for (i = 0; i < AQO_MAX_BINS; i++)
+			fscanf(f, "%f", &entry->correction[i]);
+		fgets(buf, sizeof buf, f); /* read end of line */
+	}
+}
+
+static bool HasAQOData(void)
+{
+	if (!AQOHash && auto_explain_aqo_threshold
+		&& auto_explain_aqo_file && *auto_explain_aqo_file)
+	{
+		LoadAQOHash(auto_explain_aqo_file);
+	}
+	return AQOHash != NULL;
+}
+
+static void
+LRUListUnlink(AQOHashEntry* entry)
+{
+	entry->prev->next = entry->next;
+	entry->next->prev = entry->prev;
+}
+
+static void
+LRUListInsert(AQOHashEntry* list, AQOHashEntry* entry)
+{
+	entry->next = list->next;
+	entry->prev = list;
+	list->next = list->next->prev = entry;
+}
+
+#if AQO_DEBUG
+/*
+ * Print user-friendly representation of quals list. Used only for debugging purposes.
+ */
+static char*
+PrintPlanNode(PlanState *planstate, List* quals, ExplainState *es, List* rtable)
+{
+	/* Set up deparsing context */
+	List *ancestors = list_make1(planstate);
+	List* context = planstate
+		? set_deparse_context_planstate(es->deparse_cxt, (Node *) planstate, ancestors)
+		: deparse_context_for_plan_rtable(rtable, select_rtable_names_for_explain(rtable, NULL));
+	ListCell* lc;
+	StringInfoData buf;
+	char const* sep = "";
+  	initStringInfo(&buf);
+
+	foreach (lc, quals)
+	{
+		Node* node = (Node*)lfirst(lc);
+		if (IsA(node, RestrictInfo))
+			node = (Node*)((RestrictInfo*)node)->clause;
+		appendStringInfoString(&buf, sep);
+		appendStringInfoString(&buf, deparse_expression(node, context, true, false));
+		sep = " and ";
+	}
+	return buf.data;
+}
+
+static char*
+PrintExpr(List* quals, List* rtable)
+{
+	return PrintPlanNode(NULL, quals, NULL, rtable);
+}
+#endif
+
+static int floor_log(double x)
+{
+	return (int)log10(x);
+}
+
+
+static void
+StoreAQOInfoForNode(PlanState *planstate, ExplainState *es)
+{
+	Plan	   *plan = planstate->plan;
+
+	/* Do we have actual rows information for this node? */
+	if (plan->path_clauses != NULL && planstate->instrument && plan->plan_rows != 0)
+	{
+		double estimation_error = planstate->instrument->ntuples / plan->plan_rows;
+		if (estimation_error >= auto_explain_aqo_threshold) /* this function is called only when auto_explain_aqo_threshold is non-zero */
+		{
+			AQOHashKey key;
+			AQOHashEntry* entry;
+			bool found;
+			int bin;
+			key.predicate = QualsListToString(plan->path_clauses, es->rtable);
+			InitAQOHash(); /* Init hash if needed */
+
+			/* LRU */
+			if (auto_explain_aqo_limit != 0 && hash_get_num_entries(AQOHash) >= auto_explain_aqo_limit)
+			{
+				entry = AQOHashLRU.prev;
+				LRUListUnlink(entry);
+				hash_search(AQOHash, entry, HASH_REMOVE, NULL);
+			}
+			entry = hash_search(AQOHash, &key, HASH_ENTER, &found);
+			bin = Min(floor_log(plan->plan_rows), AQO_MAX_BINS-1);
+			Assert(bin >= 0);
+			if (found)
+			{
+				LRUListUnlink(entry);
+				ereport(AQO_LOG, (errmsg("AQO: update correction coeffictient for %f from %f to %f for %s %s",
+										 plan->plan_rows, entry->correction[bin], estimation_error,
+										 AQO_PRETTY_KEY(PrintPlanNode(planstate, plan->path_clauses, es, es->rtable)), key.predicate),
+								  errhidestmt(true), errhidecontext(true)));
+			}
+			else
+			{
+				ereport(AQO_LOG, (errmsg("AQO: set correction coefficient for %f to %f for %s %s",
+										 plan->plan_rows,  estimation_error,
+										 AQO_PRETTY_KEY(PrintPlanNode(planstate, plan->path_clauses, es, es->rtable)), key.predicate),
+								  errhidestmt(true), errhidecontext(true)));
+				memset(entry->correction, 0, sizeof entry->correction);
+			}
+			entry->correction[bin] = estimation_error;
+			LRUListInsert(&AQOHashLRU, entry);
+			AQOHashUpdated = true;
+		}
+	}
+
+	/* lefttree */
+	if (outerPlanState(planstate))
+		StoreAQOInfoForNode(outerPlanState(planstate), es);
+
+	/* righttree */
+	if (innerPlanState(planstate))
+		StoreAQOInfoForNode(innerPlanState(planstate), es);
+
+    /* initPlan-s */
+	if (planstate->initPlan)
+		StoreAQOInfoForSubPlans(planstate->initPlan, es);
+
+	/* special child plans */
+	switch (nodeTag(plan))
+	{
+		case T_ModifyTable:
+			StoreAQOInfoForMemberNodes(((ModifyTableState *) planstate)->mt_plans,
+									   ((ModifyTableState *) planstate)->mt_nplans,
+									   es);
+			break;
+		case T_Append:
+			StoreAQOInfoForMemberNodes(((AppendState *) planstate)->appendplans,
+									   ((AppendState *) planstate)->as_nplans,
+									   es);
+			break;
+		case T_MergeAppend:
+			StoreAQOInfoForMemberNodes(((MergeAppendState *) planstate)->mergeplans,
+									   ((MergeAppendState *) planstate)->ms_nplans,
+									   es);
+			break;
+		case T_BitmapAnd:
+			StoreAQOInfoForMemberNodes(((BitmapAndState *) planstate)->bitmapplans,
+									   ((BitmapAndState *) planstate)->nplans,
+									   es);
+			break;
+		case T_BitmapOr:
+			StoreAQOInfoForMemberNodes(((BitmapOrState *) planstate)->bitmapplans,
+									   ((BitmapOrState *) planstate)->nplans,
+									   es);
+			break;
+		case T_SubqueryScan:
+			StoreAQOInfoForNode(((SubqueryScanState *) planstate)->subplan, es);
+			break;
+		default:
+			break;
+	}
+}
+
+static float
+AQOGetCorrection(AQOHashEntry* entry, double estimation)
+{
+	int bin = Min(floor_log(estimation), AQO_MAX_BINS-1);
+	float correction = entry->correction[bin];
+	if (correction == 0.0 && bin > 0)
+		correction = entry->correction[bin-1];
+	if (correction == 0.0 && bin < AQO_MAX_BINS-1)
+		correction = entry->correction[bin+1];
+	return correction == 0.0 ? 1.0 : correction;
+}
+
+/*
+ * AQO relation size estimation hooks.
+ */
+static void
+AQO_set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
+							   RelOptInfo *outer_rel,
+							   RelOptInfo *inner_rel,
+							   SpecialJoinInfo *sjinfo,
+							   List *restrict_clauses)
+{
+	Assert(rel->joininfo == NULL);
+	Assert(rel->baserestrictinfo == NULL);
+
+	if (prev_set_joinrel_size_estimates_hook)
+		prev_set_joinrel_size_estimates_hook(root, rel,
+											outer_rel,
+											inner_rel,
+											sjinfo,
+											restrict_clauses);
+	else
+		set_joinrel_size_estimates_standard(root, rel,
+											outer_rel,
+											inner_rel,
+											sjinfo,
+											restrict_clauses);
+
+	if (HasAQOData() && restrict_clauses && rel->rows >= 1.0)
+	{
+		AQOHashKey key;
+		AQOHashEntry* entry;
+		key.predicate = QualsListToString(restrict_clauses, root->parse->rtable);
+		entry = (AQOHashEntry*)hash_search(AQOHash, &key, HASH_FIND, NULL);
+		if (entry != NULL)
+		{
+			float correction = AQOGetCorrection(entry, rel->rows);
+			double rows = Min(outer_rel->rows * inner_rel->rows, rel->rows * correction);
+			Assert(rows >= 1.0);
+			ereport(AQO_LOG, (errmsg("AQO: Adjust joinrel estimation %f to %f for %s %s",
+									 rel->rows, rows,
+									 AQO_PRETTY_KEY(PrintExpr(restrict_clauses, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+			rel->rows = rows;
+		}
+		else
+			ereport(AQO_LOG, (errmsg("AQO: No runtime info for %s %s",
+									 AQO_PRETTY_KEY(PrintExpr(restrict_clauses, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+	}
+}
+
+static void
+AQO_set_baserel_rows_estimate(PlannerInfo *root, RelOptInfo *rel)
+{
+	Assert(rel->joininfo == NULL);
+	if (prev_set_baserel_rows_estimate_hook)
+		prev_set_baserel_rows_estimate_hook(root, rel);
+	else
+		set_baserel_rows_estimate_standard(root, rel);
+
+	if (HasAQOData() && rel->baserestrictinfo && rel->rows >= 1.0)
+	{
+		AQOHashKey key;
+		AQOHashEntry* entry;
+		key.predicate = QualsListToString(rel->baserestrictinfo, root->parse->rtable);
+		entry = (AQOHashEntry*)hash_search(AQOHash, &key, HASH_FIND, NULL);
+		if (entry != NULL)
+		{
+			float correction = AQOGetCorrection(entry, rel->rows);
+			double rows = Min(rel->tuples, rel->rows * correction);
+			Assert(rows >= 1.0);
+			ereport(AQO_LOG, (errmsg("AQO: Adjust baserel estimation %f to %f for %s %s",
+									 rel->rows, rows,
+									 AQO_PRETTY_KEY(PrintExpr(rel->baserestrictinfo, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+			rel->rows = rows;
+		}
+		else
+			ereport(AQO_LOG, (errmsg("AQO: No runtime info for %s %s",
+									 AQO_PRETTY_KEY(PrintExpr(rel->baserestrictinfo, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+	}
+}
+
+static double
+AQO_get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
+											 Path *outer_path,
+											 Path *inner_path,
+											 SpecialJoinInfo *sjinfo,
+											 List *restrict_clauses)
+{
+	double rows = prev_get_parameterized_joinrel_size_hook
+		? prev_get_parameterized_joinrel_size_hook(root, rel,
+												   outer_path,
+												   inner_path,
+												   sjinfo,
+												   restrict_clauses)
+		: get_parameterized_joinrel_size_standard(root, rel,
+												  outer_path,
+												  inner_path,
+												  sjinfo,
+												  restrict_clauses);
+	if (HasAQOData() && restrict_clauses && rows >= 1.0)
+	{
+		AQOHashKey key;
+		AQOHashEntry* entry;
+		key.predicate = QualsListToString(restrict_clauses, root->parse->rtable);
+		entry = (AQOHashEntry*)hash_search(AQOHash, &key, HASH_FIND, NULL);
+		if (entry != NULL)
+		{
+			float correction = AQOGetCorrection(entry, rows);
+			double fix_rows = Min(outer_path->rows * inner_path->rows, rows * correction);
+			Assert(fix_rows >= 1.0);
+			ereport(AQO_LOG, (errmsg("AQO: Adjust parameterized joinrel estimation %f to %f for %s %s",
+									 rows, fix_rows,
+									 AQO_PRETTY_KEY(PrintExpr(restrict_clauses, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+			rows = fix_rows;
+		}
+		else
+			ereport(AQO_LOG, (errmsg("AQO: No runtime info for %s %s",
+									 AQO_PRETTY_KEY(PrintExpr(restrict_clauses, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+	}
+	return rows;
+}
+
+static double AQO_get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
+												 List *param_clauses)
+{
+	double rows = prev_get_parameterized_baserel_size_hook
+		? prev_get_parameterized_baserel_size_hook(root, rel, param_clauses)
+		: get_parameterized_baserel_size_standard(root, rel, param_clauses);
+	if (HasAQOData() && rows >= 1.0)
+	{
+		AQOHashKey key;
+		AQOHashEntry* entry;
+
+		if (rel->baserestrictinfo)
+			param_clauses = list_concat(list_copy(rel->baserestrictinfo), param_clauses);
+
+		key.predicate = QualsListToString(param_clauses, root->parse->rtable);
+		entry = (AQOHashEntry*)hash_search(AQOHash, &key, HASH_FIND, NULL);
+		if (entry != NULL)
+		{
+			float correction = AQOGetCorrection(entry, rows);
+			double fix_rows = Min(rel->tuples, rows * correction);
+			Assert(fix_rows >= 1.0);
+			ereport(AQO_LOG, (errmsg("AQO: Adjust parameterized baserel estimation %f to %f for %s %s",
+									 rows, fix_rows,
+									 AQO_PRETTY_KEY(PrintExpr(param_clauses, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+			rows = fix_rows;
+		}
+		else
+			ereport(AQO_LOG, (errmsg("AQO: No runtime info for %s %s",
+									 AQO_PRETTY_KEY(PrintExpr(param_clauses, root->parse->rtable)), key.predicate),
+							  errhidestmt(true), errhidecontext(true)));
+	}
+	return rows;
+}
+
+
+void
+AQO_copy_generic_path_info(Plan *dest, Path *src)
+{
+	bool		is_join_path =
+		(src->type == T_NestPath || src->type == T_MergePath ||	src->type == T_HashPath);
+
+	dest->path_clauses = is_join_path
+		? ((JoinPath *) src)->joinrestrictinfo
+		: src->param_info
+		    ? list_concat(list_copy(src->parent->baserestrictinfo), src->param_info->ppi_clauses)
+		    : src->parent->baserestrictinfo;
+
+	if (prev_copy_generic_path_info_hook)
+		prev_copy_generic_path_info_hook(dest, src);
+}
+
+
 /*
  * ExecutorEnd hook: log results if needed
  */
@@ -392,6 +1381,14 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
 				ExplainPrintJITSummary(es, queryDesc);
 			ExplainEndOutput(es);
 
+			/* Add multicolumn statistic if requested */
+			if (auto_explain_add_statistics_threshold && !IsParallelWorker())
+				AddMultiColumnStatisticsForNode(queryDesc->planstate, es);
+
+			/* Store AQO information is enabled */
+			if (auto_explain_aqo_threshold > 0)
+				StoreAQOInfoForNode(queryDesc->planstate, es);
+
 			/* Remove last line break */
 			if (es->str->len > 0 && es->str->data[es->str->len - 1] == '\n')
 				es->str->data[--es->str->len] = '\0';
diff --git a/src/backend/nodes/copyfuncs.c b/src/backend/nodes/copyfuncs.c
index 78deade..0a60118 100644
--- a/src/backend/nodes/copyfuncs.c
+++ b/src/backend/nodes/copyfuncs.c
@@ -123,6 +123,7 @@ CopyPlanFields(const Plan *from, Plan *newnode)
 	COPY_SCALAR_FIELD(plan_node_id);
 	COPY_NODE_FIELD(targetlist);
 	COPY_NODE_FIELD(qual);
+	COPY_NODE_FIELD(path_clauses);
 	COPY_NODE_FIELD(lefttree);
 	COPY_NODE_FIELD(righttree);
 	COPY_NODE_FIELD(initPlan);
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index a2a9b1f..72815c0 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -96,6 +96,10 @@
 #include "utils/spccache.h"
 #include "utils/tuplesort.h"
 
+set_baserel_rows_estimate_hook_type set_baserel_rows_estimate_hook = NULL;
+get_parameterized_baserel_size_hook_type get_parameterized_baserel_size_hook = NULL;
+get_parameterized_joinrel_size_hook_type get_parameterized_joinrel_size_hook = NULL;
+set_joinrel_size_estimates_hook_type set_joinrel_size_estimates_hook = NULL;
 
 #define LOG2(x)  (log(x) / 0.693147180559945)
 
@@ -4388,6 +4392,49 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals)
 
 
 /*
+ * set_baserel_rows_estimate
+ *		Set the rows estimate for the given base relation.
+ *
+ * Rows is the estimated number of output tuples after applying
+ * restriction clauses.
+ *
+ * To support loadable plugins that monitor or modify cardinality estimation,
+ * we provide a hook variable that lets a plugin get control before and
+ * after the cardinality estimation.
+ * The hook must set rel->rows.
+ */
+void
+set_baserel_rows_estimate(PlannerInfo *root, RelOptInfo *rel)
+{
+	if (set_baserel_rows_estimate_hook)
+		(*set_baserel_rows_estimate_hook) (root, rel);
+	else
+		set_baserel_rows_estimate_standard(root, rel);
+}
+
+/*
+ * set_baserel_rows_estimate
+ *		Set the rows estimate for the given base relation.
+ *
+ * Rows is the estimated number of output tuples after applying
+ * restriction clauses.
+ */
+void
+set_baserel_rows_estimate_standard(PlannerInfo *root, RelOptInfo *rel)
+{
+	double		nrows;
+
+	nrows = rel->tuples *
+		clauselist_selectivity(root,
+							   rel->baserestrictinfo,
+							   0,
+							   JOIN_INNER,
+							   NULL);
+
+	rel->rows = clamp_row_est(nrows);
+}
+
+/*
  * set_baserel_size_estimates
  *		Set the size estimates for the given base relation.
  *
@@ -4403,19 +4450,10 @@ approx_tuple_count(PlannerInfo *root, JoinPath *path, List *quals)
 void
 set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
 {
-	double		nrows;
-
 	/* Should only be applied to base relations */
 	Assert(rel->relid > 0);
 
-	nrows = rel->tuples *
-		clauselist_selectivity(root,
-							   rel->baserestrictinfo,
-							   0,
-							   JOIN_INNER,
-							   NULL);
-
-	rel->rows = clamp_row_est(nrows);
+	set_baserel_rows_estimate(root, rel);
 
 	cost_qual_eval(&rel->baserestrictcost, rel->baserestrictinfo, root);
 
@@ -4426,13 +4464,33 @@ set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel)
  * get_parameterized_baserel_size
  *		Make a size estimate for a parameterized scan of a base relation.
  *
+ * To support loadable plugins that monitor or modify cardinality estimation,
+ * we provide a hook variable that lets a plugin get control before and
+ * after the cardinality estimation.
+ */
+double
+get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
+							   List *param_clauses)
+{
+	if (get_parameterized_baserel_size_hook)
+		return (*get_parameterized_baserel_size_hook) (root, rel,
+													   param_clauses);
+	else
+		return get_parameterized_baserel_size_standard(root, rel,
+													   param_clauses);
+}
+
+/*
+ * get_parameterized_baserel_size_standard
+ *		Make a size estimate for a parameterized scan of a base relation.
+ *
  * 'param_clauses' lists the additional join clauses to be used.
  *
  * set_baserel_size_estimates must have been applied already.
  */
 double
-get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
-							   List *param_clauses)
+get_parameterized_baserel_size_standard(PlannerInfo *root, RelOptInfo *rel,
+										List *param_clauses)
 {
 	List	   *allclauses;
 	double		nrows;
@@ -4462,6 +4520,36 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
  * set_joinrel_size_estimates
  *		Set the size estimates for the given join relation.
  *
+ * To support loadable plugins that monitor or modify cardinality estimation,
+ * we provide a hook variable that lets a plugin get control before and
+ * after the cardinality estimation.
+ * The hook must set rel->rows value.
+ */
+void
+set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
+						   RelOptInfo *outer_rel,
+						   RelOptInfo *inner_rel,
+						   SpecialJoinInfo *sjinfo,
+						   List *restrictlist)
+{
+	if (set_joinrel_size_estimates_hook)
+		(*set_joinrel_size_estimates_hook) (root, rel,
+											outer_rel,
+											inner_rel,
+											sjinfo,
+											restrictlist);
+	else
+		set_joinrel_size_estimates_standard(root, rel,
+											outer_rel,
+											inner_rel,
+											sjinfo,
+											restrictlist);
+}
+
+/*
+ * set_joinrel_size_estimates_standard
+ *		Set the size estimates for the given join relation.
+ *
  * The rel's targetlist must have been constructed already, and a
  * restriction clause list that matches the given component rels must
  * be provided.
@@ -4481,11 +4569,11 @@ get_parameterized_baserel_size(PlannerInfo *root, RelOptInfo *rel,
  * build_joinrel_tlist, and baserestrictcost is not used for join rels.
  */
 void
-set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
-						   RelOptInfo *outer_rel,
-						   RelOptInfo *inner_rel,
-						   SpecialJoinInfo *sjinfo,
-						   List *restrictlist)
+set_joinrel_size_estimates_standard(PlannerInfo *root, RelOptInfo *rel,
+									RelOptInfo *outer_rel,
+									RelOptInfo *inner_rel,
+									SpecialJoinInfo *sjinfo,
+									List *restrictlist)
 {
 	rel->rows = calc_joinrel_size_estimate(root,
 										   rel,
@@ -4501,6 +4589,35 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
  * get_parameterized_joinrel_size
  *		Make a size estimate for a parameterized scan of a join relation.
  *
+ * To support loadable plugins that monitor or modify cardinality estimation,
+ * we provide a hook variable that lets a plugin get control before and
+ * after the cardinality estimation.
+ */
+double
+get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
+							   Path *outer_path,
+							   Path *inner_path,
+							   SpecialJoinInfo *sjinfo,
+							   List *restrict_clauses)
+{
+	if (get_parameterized_joinrel_size_hook)
+		return (*get_parameterized_joinrel_size_hook) (root, rel,
+													   outer_path,
+													   inner_path,
+													   sjinfo,
+													   restrict_clauses);
+	else
+		return get_parameterized_joinrel_size_standard(root, rel,
+													   outer_path,
+													   inner_path,
+													   sjinfo,
+													   restrict_clauses);
+}
+
+/*
+ * get_parameterized_joinrel_size_standard
+ *		Make a size estimate for a parameterized scan of a join relation.
+ *
  * 'rel' is the joinrel under consideration.
  * 'outer_path', 'inner_path' are (probably also parameterized) Paths that
  *		produce the relations being joined.
@@ -4513,11 +4630,11 @@ set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
  * set_joinrel_size_estimates must have been applied already.
  */
 double
-get_parameterized_joinrel_size(PlannerInfo *root, RelOptInfo *rel,
-							   Path *outer_path,
-							   Path *inner_path,
-							   SpecialJoinInfo *sjinfo,
-							   List *restrict_clauses)
+get_parameterized_joinrel_size_standard(PlannerInfo *root, RelOptInfo *rel,
+										Path *outer_path,
+										Path *inner_path,
+										SpecialJoinInfo *sjinfo,
+										List *restrict_clauses)
 {
 	double		nrows;
 
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 608d5ad..3313617 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -70,6 +70,9 @@
 #define CP_LABEL_TLIST		0x0004	/* tlist must contain sortgrouprefs */
 #define CP_IGNORE_TLIST		0x0008	/* caller will replace tlist */
 
+/* Hook for plugins to get control in creating plan from path */
+copy_generic_path_info_hook_type copy_generic_path_info_hook = NULL;
+ 
 
 static Plan *create_plan_recurse(PlannerInfo *root, Path *best_path,
 								 int flags);
@@ -5018,6 +5021,9 @@ copy_generic_path_info(Plan *dest, Path *src)
 	dest->plan_width = src->pathtarget->width;
 	dest->parallel_aware = src->parallel_aware;
 	dest->parallel_safe = src->parallel_safe;
+
+	if (copy_generic_path_info_hook)
+		(*copy_generic_path_info_hook) (dest, src);
 }
 
 /*
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index 70f8b8e..35c66b1 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -139,6 +139,7 @@ typedef struct Plan
 	int			plan_node_id;	/* unique across entire final plan tree */
 	List	   *targetlist;		/* target list to be computed at this node */
 	List	   *qual;			/* implicitly-ANDed qual conditions */
+	List	   *path_clauses;	/* original quals copied from best path */
 	struct Plan *lefttree;		/* input plan tree(s) */
 	struct Plan *righttree;
 	List	   *initPlan;		/* Init Plan nodes (un-correlated expr
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 9b6bdbc..f35b908 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -39,6 +39,34 @@ typedef enum
 }			ConstraintExclusionType;
 
 
+/* Hook for plugins to get control of cardinality estimation */
+typedef void (*set_baserel_rows_estimate_hook_type) (PlannerInfo *root,
+															RelOptInfo *rel);
+extern PGDLLIMPORT set_baserel_rows_estimate_hook_type
+			set_baserel_rows_estimate_hook;
+typedef double (*get_parameterized_baserel_size_hook_type) (PlannerInfo *root,
+															 RelOptInfo *rel,
+														List *param_clauses);
+extern PGDLLIMPORT get_parameterized_baserel_size_hook_type
+			get_parameterized_baserel_size_hook;
+typedef double (*get_parameterized_joinrel_size_hook_type) (PlannerInfo *root,
+															 RelOptInfo *rel,
+															Path *outer_path,
+															Path *inner_path,
+													 SpecialJoinInfo *sjinfo,
+													 List *restrict_clauses);
+extern PGDLLIMPORT get_parameterized_joinrel_size_hook_type
+			get_parameterized_joinrel_size_hook;
+typedef void (*set_joinrel_size_estimates_hook_type) (PlannerInfo *root,
+															 RelOptInfo *rel,
+													   RelOptInfo *outer_rel,
+													   RelOptInfo *inner_rel,
+													 SpecialJoinInfo *sjinfo,
+														 List *restrictlist);
+extern PGDLLIMPORT set_joinrel_size_estimates_hook_type
+			set_joinrel_size_estimates_hook;
+
+
 /*
  * prototypes for costsize.c
  *	  routines to compute costs and sizes
@@ -171,21 +199,37 @@ extern void compute_semi_anti_join_factors(PlannerInfo *root,
 										   SpecialJoinInfo *sjinfo,
 										   List *restrictlist,
 										   SemiAntiJoinFactors *semifactors);
+extern void set_baserel_rows_estimate(PlannerInfo *root, RelOptInfo *rel);
+extern void set_baserel_rows_estimate_standard(PlannerInfo *root, RelOptInfo *rel);
 extern void set_baserel_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern double get_parameterized_baserel_size(PlannerInfo *root,
 											 RelOptInfo *rel,
 											 List *param_clauses);
+extern double get_parameterized_baserel_size_standard(PlannerInfo *root,
+													  RelOptInfo *rel,
+													  List *param_clauses);
 extern double get_parameterized_joinrel_size(PlannerInfo *root,
 											 RelOptInfo *rel,
 											 Path *outer_path,
 											 Path *inner_path,
 											 SpecialJoinInfo *sjinfo,
 											 List *restrict_clauses);
+extern double get_parameterized_joinrel_size_standard(PlannerInfo *root,
+													  RelOptInfo *rel,
+													  Path *outer_path,
+													  Path *inner_path,
+													  SpecialJoinInfo *sjinfo,
+													  List *restrict_clauses);
 extern void set_joinrel_size_estimates(PlannerInfo *root, RelOptInfo *rel,
 									   RelOptInfo *outer_rel,
 									   RelOptInfo *inner_rel,
 									   SpecialJoinInfo *sjinfo,
 									   List *restrictlist);
+extern void set_joinrel_size_estimates_standard(PlannerInfo *root, RelOptInfo *rel,
+												RelOptInfo *outer_rel,
+												RelOptInfo *inner_rel,
+												SpecialJoinInfo *sjinfo,
+												List *restrictlist);
 extern void set_subquery_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_function_size_estimates(PlannerInfo *root, RelOptInfo *rel);
 extern void set_values_size_estimates(PlannerInfo *root, RelOptInfo *rel);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index e7aaddd..8452f8e 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -24,6 +24,11 @@ extern double cursor_tuple_fraction;
 /* query_planner callback to compute query_pathkeys */
 typedef void (*query_pathkeys_callback) (PlannerInfo *root, void *extra);
 
+/* hook for plugins to get control in creating plan from path */
+typedef void (*copy_generic_path_info_hook_type)(Plan *dest, Path *src);
+
+extern PGDLLIMPORT copy_generic_path_info_hook_type copy_generic_path_info_hook;
+
 /*
  * prototypes for plan/planmain.c
  */
