File 0001-1C_FULL.patch of Package postgresql15

diff --git a/contrib/Makefile b/contrib/Makefile
index bbf220407b0..e52fbcd133d 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -20,18 +20,23 @@ SUBDIRS = \
 		dict_int	\
 		dict_xsyn	\
 		earthdistance	\
+		fasttrun	\
 		file_fdw	\
 		fuzzystrmatch	\
+		fulleq	\
 		hstore		\
 		intagg		\
 		intarray	\
 		isn		\
 		lo		\
 		ltree		\
+		mchar		\
 		oid2name	\
 		old_snapshot	\
+		online_analyze	\
 		pageinspect	\
 		passwordcheck	\
+		plantuner	\
 		pg_buffercache	\
 		pg_freespacemap \
 		pg_prewarm	\
@@ -51,7 +56,8 @@ SUBDIRS = \
 		tsm_system_rows \
 		tsm_system_time \
 		unaccent	\
-		vacuumlo
+		vacuumlo \
+        dbcopies_decoding
 
 ifeq ($(with_ssl),openssl)
 SUBDIRS += pgcrypto sslinfo
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index c9a0d947c83..9b6f28bea98 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -19,6 +19,7 @@
 #include "common/pg_prng.h"
 #include "executor/instrument.h"
 #include "jit/jit.h"
+#include "optimizer/planner.h"
 #include "utils/guc.h"
 
 PG_MODULE_MAGIC;
@@ -377,6 +378,7 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
 		if (msec >= auto_explain_log_min_duration)
 		{
 			ExplainState *es = NewExplainState();
+			instr_time planduration = queryDesc->plannedstmt->planDuration;
 
 			es->analyze = (queryDesc->instrument_options && auto_explain_log_analyze);
 			es->verbose = auto_explain_log_verbose;
@@ -414,8 +416,9 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
 			 * often result in duplication.
 			 */
 			ereport(auto_explain_log_level,
-					(errmsg("duration: %.3f ms  plan:\n%s",
-							msec, es->str->data),
+					(errmsg("duration: %.3f ms planning: %.3f ms plan:\n%s",
+							msec, 1000 * INSTR_TIME_GET_DOUBLE(planduration),
+							es->str->data),
 					 errhidestmt(true)));
 		}
 
diff --git a/contrib/dbcopies_decoding/Makefile b/contrib/dbcopies_decoding/Makefile
new file mode 100644
index 00000000000..aa6fbc538c6
--- /dev/null
+++ b/contrib/dbcopies_decoding/Makefile
@@ -0,0 +1,36 @@
+MODULE_big = dbcopies_decoding
+OBJS=dbcopies_decoding.o mchar_recode.o
+REGRESS = simple
+
+REGRESS_OPTS = --temp-config $(top_srcdir)/contrib/dbcopies_decoding/logical.conf
+
+# Disabled because these tests require "wal_level=logical", which
+# typical installcheck users do not have (e.g. buildfarm clients).
+NO_INSTALLCHECK = 1
+
+PG_CPPFLAGS += -I/usr/local/include -I$(top_srcdir)/contrib/mchar
+
+ifdef USE_PGXS
+PG_CONFIG = pg_config
+PGXS = $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/dbcopies_decoding
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+ifeq ($(PORTNAME),win32)
+ICUNAME=icuin
+else
+ICUNAME=icui18n
+endif
+
+SHLIB_LINK += -L/usr/local/lib -licuuc -l$(ICUNAME) -Wl,-rpath,'$$ORIGIN'
+
+installcheck-force:
+	$(pg_regress_installcheck) $(REGRESS)
+
+mchar_recode.c: $(top_srcdir)/contrib/mchar/mchar_recode.c
+	cp -f $(top_srcdir)/contrib/mchar/mchar_recode.c ./
diff --git a/contrib/dbcopies_decoding/dbcopies_decoding.c b/contrib/dbcopies_decoding/dbcopies_decoding.c
new file mode 100644
index 00000000000..132553cfc27
--- /dev/null
+++ b/contrib/dbcopies_decoding/dbcopies_decoding.c
@@ -0,0 +1,898 @@
+#include "postgres.h"
+
+#include "catalog/pg_type.h"
+
+#include "replication/logical.h"
+#include "replication/origin.h"
+
+#include "utils/builtins.h"
+#include "utils/lsyscache.h"
+#include "utils/memutils.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/catcache.h"
+#include "utils/timestamp.h"
+#include "utils/cash.h"
+#include "utils/pg_locale.h"
+#include "utils/date.h"
+#include "utils/datetime.h"
+
+#include "mchar.h"
+
+PG_MODULE_MAGIC;
+
+
+Oid MCHAROID = InvalidOid;
+Oid MVARCHAROID = InvalidOid;
+const char cQuoteChar = '\'';
+const char cContinueChar = '!';
+
+extern PGDLLEXPORT void _PG_init(void);
+extern PGDLLEXPORT void _PG_output_plugin_init(OutputPluginCallbacks* cb);
+
+typedef struct
+{
+	MemoryContext context;
+	int record_buf_size;
+	//Заказанный размер записи
+	int prepare_header_size;
+	//Размер заголовка, который записывает OutputPluginPrepareWrite в ctx->out
+	bool include_xids;
+	//флаг Записывать идентификатор транзакции
+	bool skip_change;
+	//флаг пропустить все, ничего не выводить
+	bool xact_wrote_changes;
+	//Признак того, что старт транзакции уже выведен.
+} DecodingData;
+
+static void decode_startup(LogicalDecodingContext* ctx,
+	OutputPluginOptions* opt, bool is_init);
+static void decode_shutdown(LogicalDecodingContext* ctx
+	);
+static void decode_begin_txn(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn);
+static void decode_commit_txn(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn, XLogRecPtr commit_lsn);
+static void decode_change(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn, Relation rel, ReorderBufferChange* change);
+static void decode_truncate(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn, int nrelations, Relation relations[],
+	ReorderBufferChange* change);
+static bool filter_by_origin(LogicalDecodingContext *ctx,
+	RepOriginId origin_id);
+
+#ifndef U8_TRUNCATE_IF_INCOMPLETE
+// © 2016 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+/*
+*******************************************************************************
+*
+*   Copyright (C) 1999-2015, International Business Machines
+*   Corporation and others.  All Rights Reserved.
+*
+*******************************************************************************
+*   file name:  utf8.h
+*   encoding:   UTF-8
+*   tab size:   8 (not used)
+*   indentation:4
+*
+*   created on: 1999sep13
+*   created by: Markus W. Scherer
+*/
+#define U8_LEAD4_T1_BITS \
+"\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00"
+#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) \
+(U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7)))
+#define U8_LEAD3_T1_BITS \
+"\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30"
+#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) \
+(U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5)))
+#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) { \
+	if((length)>(start)) { \
+		uint8_t __b1=s[(length)-1]; \
+		if(U8_IS_SINGLE(__b1)) { \
+			/* common ASCII character */ \
+		} else if(U8_IS_LEAD(__b1)) { \
+			--(length); \
+		} else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \
+			uint8_t __b2=s[(length)-2]; \
+			if(0xe0<=__b2 && __b2<=0xf4) { \
+				if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \
+						U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \
+					(length)-=2; \
+				} \
+			} else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \
+				uint8_t __b3=s[(length)-3]; \
+				if(0xf0<=__b3 && __b3<=0xf4 && \
+					U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \
+					(length)-=3; \
+				} \
+			} \
+		} \
+	} \
+}
+
+#endif
+
+void _PG_init(void)
+{
+}
+
+void _PG_output_plugin_init(OutputPluginCallbacks* cb)
+{
+	AssertVariableIsOfType(&_PG_output_plugin_init, LogicalOutputPluginInit);
+
+	cb->startup_cb = decode_startup;
+	cb->begin_cb = decode_begin_txn;
+	cb->change_cb = decode_change;
+	cb->truncate_cb = decode_truncate;
+	cb->commit_cb = decode_commit_txn;
+	cb->shutdown_cb = decode_shutdown;
+	cb->filter_by_origin_cb = filter_by_origin;
+}
+
+static bool tryExtractBoolOption(DefElem* elem, const char* name, bool* dest)
+{
+	if (strcmp(elem->defname, name) == 0)
+	{
+		if (elem->arg != NULL)
+		{
+			if (!parse_bool(strVal(elem->arg), dest))
+				ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("could not parse value \"%s\" for parameter \"%s\"",
+						strVal(elem->arg), elem->defname)));
+		}
+		return true;
+	}
+	else
+		return false;
+}
+
+static bool tryExtractIntOption(DefElem* elem, const char* name, int32* dest)
+{
+	if (strcmp(elem->defname, name) == 0)
+	{
+		if (elem->arg != NULL)
+			*dest = pg_strtoint32(strVal(elem->arg));
+		return true;
+	}
+	else
+		return false;
+}
+
+static void readTypeOID(char* typeName, Oid* typeOid)
+{
+	if (*typeOid == InvalidOid)
+	{
+		CatCList* catlist = SearchSysCacheList(TYPENAMENSP,
+			1, CStringGetDatum(typeName), 0, 0);
+		if (catlist->n_members == 1)
+			*typeOid = ((Form_pg_type)GETSTRUCT(
+				&catlist->members[0]->tuple))->oid;
+		ReleaseSysCacheList(catlist);
+
+		if (*typeOid == InvalidOid)
+			elog(WARNING, "OID of type %s not defined!", typeName);
+	}
+}
+
+static void decode_startup(LogicalDecodingContext* ctx,
+	OutputPluginOptions* opt, bool is_init)
+{
+	ListCell* option;
+	DecodingData* data = palloc0(sizeof(DecodingData));
+
+	data->include_xids = true;
+	data->skip_change = false;
+	data->record_buf_size = ALLOCSET_DEFAULT_MAXSIZE / 4;
+	foreach(option, ctx->output_plugin_options)
+	{
+		DefElem* elem = lfirst(option);
+		Assert(elem->arg == NULL || IsA(elem->arg, String));
+
+		if (!tryExtractBoolOption(elem, "include-xids",
+			&data->include_xids))
+			if (!tryExtractBoolOption(elem, "skip-change",
+				&data->skip_change))
+				if (!tryExtractIntOption(elem, "slice_size",
+					&data->record_buf_size))
+				{
+					ereport(ERROR,
+						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+							errmsg("option \"%s\" = \"%s\" is unknown",
+							elem->defname,
+							elem->arg ? strVal(elem->arg) : "(null)")
+						)
+					);
+				}
+	}
+	data->context = AllocSetContextCreate(ctx->context,
+		"text conversion context",
+		ALLOCSET_DEFAULT_SIZES);
+	ctx->output_plugin_private = data;
+
+	opt->output_type = OUTPUT_PLUGIN_TEXTUAL_OUTPUT;
+	opt->receive_rewrites = false;
+}
+
+static void decode_shutdown(LogicalDecodingContext* ctx)
+{
+	DecodingData* data = ctx->output_plugin_private;
+
+	MemoryContextDelete(data->context);
+}
+
+static bool filter_by_origin(LogicalDecodingContext *ctx,
+	RepOriginId origin_id)
+{
+	DecodingData* data = ctx->output_plugin_private;
+	return data && data->skip_change;
+}
+
+static void decode_begin_txn(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn)
+{
+	DecodingData* data = ctx->output_plugin_private;
+
+	data->xact_wrote_changes = false;
+}
+
+static void decode_commit_txn(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn, XLogRecPtr commit_lsn)
+{
+	DecodingData* data = ctx->output_plugin_private;
+
+	if (!data->xact_wrote_changes || data->skip_change)
+		return;
+
+	OutputPluginPrepareWrite(ctx, true);
+	if (data->include_xids)
+		appendStringInfo(ctx->out, "C %u", txn->xid);
+	else
+		appendStringInfo(ctx->out, "C");
+	OutputPluginWrite(ctx, true);
+}
+
+static int record_buf_size(LogicalDecodingContext* ctx) {
+	return ((DecodingData*)(ctx->output_plugin_private))->record_buf_size;
+}
+
+static void prepareFlushedCtx(LogicalDecodingContext* ctx)
+{
+	ctx->out->len = 0;
+	OutputPluginPrepareWrite(ctx, true);
+	((DecodingData*)(ctx->output_plugin_private)
+	)->prepare_header_size = ctx->out->len;
+}
+
+static int checkFlushCtx(LogicalDecodingContext* ctx, int toWriteSize)
+{	//возвращает максимальное число байт,
+	//которое можно записать до превышения лимита длинны
+	int overflowRemain = record_buf_size(ctx) - ctx->out->len - 1;
+	if (overflowRemain <= toWriteSize)
+	{
+		appendStringInfoChar(ctx->out, cContinueChar);
+		switch (((DecodingData*)(ctx->output_plugin_private)
+				)->prepare_header_size)
+		{
+		case 0:
+			break;
+		case 1 + sizeof(int64) * 3:
+			memset(&ctx->out->data[1], 0, sizeof(int64) * 2);
+			break;
+		default:
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("Unsupported ctx->prepare_write function!")));
+
+		}
+		OutputPluginWrite(ctx, false);
+		prepareFlushedCtx(ctx);
+		return record_buf_size(ctx) - ctx->out->len - 1;
+	}
+	else
+		return overflowRemain;
+}
+
+static void printByts(LogicalDecodingContext* ctx, Datum val)
+{
+	const char n[] = { "0123456789abcdef" };
+	const int cDig = 16;
+	char* bytsData = VARDATA(val);
+	int32 bytsLen = VARSIZE_ANY_EXHDR(val);
+	int resultSize = 3 + bytsLen * 2 + 1; //остаток, который требуется записать
+	int overflowRemain = checkFlushCtx(ctx, 3);
+	if (resultSize > overflowRemain)
+		enlargeStringInfo(ctx->out, overflowRemain);
+	else
+		enlargeStringInfo(ctx->out, resultSize);
+
+	appendStringInfoString(ctx->out, "\'\\x");
+	overflowRemain -= 3;
+	resultSize -= 3;
+
+	{
+		int32 i;
+		for (i = 0; i < bytsLen; ++i)
+		{
+			int x;
+			if (overflowRemain < 2)
+			{
+				overflowRemain = checkFlushCtx(ctx, 2);
+				if (resultSize > overflowRemain)
+					enlargeStringInfo(ctx->out, overflowRemain);
+				else
+					enlargeStringInfo(ctx->out, resultSize);
+			}
+			x = bytsData[i] & 255;
+			ctx->out->data[ctx->out->len] = n[x / cDig];
+			ctx->out->data[ctx->out->len + 1] = n[x % cDig];
+			ctx->out->len += 2;
+			overflowRemain -= 2;
+			resultSize -= 2;
+		}
+	}
+	if (overflowRemain < 1)
+		checkFlushCtx(ctx, 1);
+	appendStringInfoChar(ctx->out, cQuoteChar);
+}
+
+static bool truncateIfIncmoplete(const int maxCharSize, const char* str, int* len)
+{
+	if (maxCharSize == 1)
+		return true;
+	else
+	{
+		int dbEnc = GetDatabaseEncoding();
+		if (dbEnc == PG_UTF8)
+		{
+			U8_TRUNCATE_IF_INCOMPLETE(str, 0, *len);
+			return (len > 0);
+		}
+		else
+		{	//медленный экзотичный вариант
+			int truncCount;
+			for (truncCount = 1; truncCount < maxCharSize; ++truncCount)
+			{
+				int charLen;
+				for (charLen = 1;
+					charLen <= maxCharSize && *len >= charLen;
+					++charLen)
+					if (pg_verify_mbstr(dbEnc, &str[*len - charLen],
+										charLen, true))
+						return true;
+
+				--(*len);
+			}
+		}
+	}
+
+	return false;
+}
+
+static void printCharVarchar(LogicalDecodingContext* ctx, Datum val)
+{
+	const int maxCharSize = pg_database_encoding_max_length();
+	char* bytsData = VARDATA(val);
+	int32 bytsLen = VARSIZE_ANY_EXHDR(val);
+	int overflowRemain = checkFlushCtx(ctx, 1 + maxCharSize);
+
+	appendStringInfoChar(ctx->out, cQuoteChar);
+	--overflowRemain;
+	{
+		char* pBegin = bytsData;
+		int L = 0;
+		int i;
+		for (i = 0; i < bytsLen; ++i)
+		{
+			bool overflow;
+			++L;
+			overflow = (L >= overflowRemain);
+			if (bytsData[i] == cQuoteChar || overflow || i + 1 == bytsLen)
+			{
+				if (overflow &&
+					!(bytsData[i] == cQuoteChar || i + 1 == bytsLen))
+				{
+					if (!truncateIfIncmoplete(maxCharSize, pBegin, &L))
+
+						ereport(ERROR,
+							(errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST),
+								errmsg("invalid string value")
+							)
+						);
+				}
+				appendBinaryStringInfo(ctx->out, pBegin, L);
+				pBegin += L;
+				if (bytsData[i] == cQuoteChar)
+				{
+					overflowRemain = checkFlushCtx(ctx, maxCharSize+1);
+					appendStringInfoChar(ctx->out, bytsData[i]);
+					--overflowRemain;
+				}
+				else if (overflow) //гарантированный сброс буфера
+					overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx));
+				else
+					overflowRemain = checkFlushCtx(ctx, maxCharSize);
+				L = 0;
+			}
+
+		}
+	}
+	if (overflowRemain < 1)
+		checkFlushCtx(ctx, 1);
+	appendStringInfoChar(ctx->out, cQuoteChar);
+}
+
+static int printM(const UChar* wordsData,
+	int wordsLen, LogicalDecodingContext* ctx)
+{
+	const int maxCharSize = pg_database_encoding_max_length();
+	const UChar cQuoteUChar = L'\'';
+	int overflowRemain = checkFlushCtx(ctx, 1 + maxCharSize);
+
+	appendStringInfoChar(ctx->out, cQuoteChar);
+	--overflowRemain;
+	{
+		const UChar* pBegin = wordsData;
+		int L = 0;
+		int i;
+		for (i = 0; i < wordsLen; ++i)
+		{
+			bool overflow;
+			++L;
+			overflow = (L*maxCharSize >= overflowRemain);
+			if (wordsData[i] == cQuoteUChar ||
+				overflow ||
+				i + 1 == wordsLen)
+			{
+				if (overflow &&
+					!(wordsData[i] == cQuoteUChar || i + 1 == wordsLen))
+				{
+					if (U16_IS_LEAD(wordsData[i]))
+						--L;
+
+					if (L == 0 || (i > 0 && U16_IS_LEAD(wordsData[i - 1])))
+						ereport(ERROR,
+							(errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST),
+								errmsg("invalid utf16 string value")
+							)
+						);
+				}
+				enlargeStringInfo(ctx->out, L * maxCharSize);
+				ctx->out->len += UChar2Char(pBegin, L,
+					&ctx->out->data[ctx->out->len]);
+				pBegin += L;
+
+				if (wordsData[i] == cQuoteUChar)
+				{
+					overflowRemain = checkFlushCtx(ctx, maxCharSize+1);
+					appendStringInfoChar(ctx->out, cQuoteChar);
+					--overflowRemain;
+				}
+				else if (overflow)
+					overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx));
+				else
+					overflowRemain = checkFlushCtx(ctx, maxCharSize);
+				L = 0;
+			}
+		}
+	}
+	return overflowRemain;
+}
+
+static void printMVarchar(LogicalDecodingContext* ctx, Datum val)
+{
+	const UChar* pBegin = (UChar*)(DatumGetPointer(val) + MVARCHARHDRSZ);
+	if (printM(pBegin, UVARCHARLENGTH(val), ctx) < 1)
+		checkFlushCtx(ctx, 1);
+	appendStringInfoChar(ctx->out, cQuoteChar);
+}
+
+static void printMChar(LogicalDecodingContext* ctx, Datum val)
+{
+	const UChar* pBegin = (UChar*)(DatumGetPointer(val) + MCHARHDRSZ);
+	int32  trailBlanksCount =
+		DatumGetMChar(val)->typmod - u_countChar32(pBegin, UCHARLENGTH(val));
+	int overflowRemain = printM(pBegin, UCHARLENGTH(val), ctx);
+	while (trailBlanksCount > 0)
+	{
+
+		if (trailBlanksCount > overflowRemain)
+		{
+			appendStringInfoSpaces(ctx->out, overflowRemain);
+			trailBlanksCount -= overflowRemain;
+			overflowRemain = checkFlushCtx(ctx, 1);
+		}
+		else
+		{
+			appendStringInfoSpaces(ctx->out, trailBlanksCount);
+			overflowRemain -= trailBlanksCount;
+			trailBlanksCount = 0;
+		}
+	}
+	if (overflowRemain < 1)
+		checkFlushCtx(ctx, 1);
+	appendStringInfoChar(ctx->out, cQuoteChar);
+}
+
+static void appendCtxString(LogicalDecodingContext* ctx, char* str)
+{
+	int l = strlen(str);
+	checkFlushCtx(ctx, l);
+	appendBinaryStringInfo(ctx->out, str, l);
+}
+
+static void printTimestamp(LogicalDecodingContext* ctx, Datum val)
+{
+	Timestamp ts = DatumGetTimestamp(val);
+	if (!TIMESTAMP_NOT_FINITE(ts))
+	{
+		struct pg_tm tm;
+		fsec_t		fsec;
+		if (timestamp2tm(ts, NULL, &tm, &fsec, NULL, NULL) == 0)
+		{   //отсутствие в параметрах указателя на tz
+			//приводит к конвертации часов (ts with timezone)
+			//например было 10:23:54.123+02 получим 08:23:54
+			char* str;
+			checkFlushCtx(ctx, 14);
+			enlargeStringInfo(ctx->out, 14);
+			str = ctx->out->data + ctx->out->len;
+			ctx->out->len += 14;
+			str = pg_ultostr_zeropad(str,
+				(tm.tm_year > 0) ? tm.tm_year : -(tm.tm_year - 1), 4);
+			str = pg_ultostr_zeropad(str, tm.tm_mon, 2);
+			str = pg_ultostr_zeropad(str, tm.tm_mday, 2);
+			str = pg_ultostr_zeropad(str, tm.tm_hour, 2);
+			str = pg_ultostr_zeropad(str, tm.tm_min, 2);
+			str = pg_ultostr_zeropad(str, Abs(tm.tm_sec), 2);
+			return;
+		}
+	}
+	appendCtxString(ctx, "'invalid timestamp'");
+}
+
+static void printDate(LogicalDecodingContext* ctx, Datum val)
+{
+	DateADT d = DatumGetDateADT(val);
+	if (!DATE_NOT_FINITE(d))
+	{
+		char* str;
+		int year, mon, day;
+		j2date(d + POSTGRES_EPOCH_JDATE, &year, &mon, &day);
+		checkFlushCtx(ctx, 8);
+		enlargeStringInfo(ctx->out, 8);
+		str = ctx->out->data + ctx->out->len;
+		ctx->out->len += 8;
+		str = pg_ultostr_zeropad(str, (year > 0) ? year : -(year - 1), 4);
+		str = pg_ultostr_zeropad(str, mon, 2);
+		str = pg_ultostr_zeropad(str, day, 2);
+		return;
+	}
+	appendCtxString(ctx, "'invalid date'");
+}
+
+static void printTime(LogicalDecodingContext* ctx, Datum val)
+{
+	TimeADT t = DatumGetTimeADT(val);
+	char* str;
+	struct pg_tm tm;
+	fsec_t		fsec;
+	time2tm(t, &tm, &fsec);
+	checkFlushCtx(ctx, 14);
+	enlargeStringInfo(ctx->out, 14);
+	str = ctx->out->data + ctx->out->len;
+	ctx->out->len += 14;
+	str = pg_ultostr_zeropad(str, 0, 4);
+	str = pg_ultostr_zeropad(str, 0, 2);
+	str = pg_ultostr_zeropad(str, 0, 2);
+	str = pg_ultostr_zeropad(str, tm.tm_hour, 2);
+	str = pg_ultostr_zeropad(str, tm.tm_min, 2);
+	str = pg_ultostr_zeropad(str, Abs(tm.tm_sec), 2);
+}
+
+static void printMoney(LogicalDecodingContext* ctx, Datum val)
+{
+	Cash v = DatumGetCash(val);
+	char buf[128];
+	char* pBuf = &buf[127];
+	bool minus = (v < 0);
+	struct lconv *lconvert = PGLC_localeconv();
+	int points = lconvert->frac_digits;
+
+	if (points < 0 || points > 10)
+		points = 2;
+
+	buf[127] = 0;
+	if (minus)
+		v = -v;
+
+	do {
+		*(--pBuf) = ((uint64)v % 10) + '0';
+		--points;
+
+		if (points == 0)
+			*(--pBuf) = '.';
+
+		if (v)
+			v = ((uint64)v) / 10;
+	} while (v || points >= 0);
+	if (minus)
+		*(--pBuf) = '-';
+
+	appendCtxString(ctx, pBuf);
+}
+
+static void printBool(LogicalDecodingContext* ctx, Datum val)
+{
+	appendCtxString(ctx, DatumGetBool(val) ? "true" : "false");
+}
+
+static void printDefault(LogicalDecodingContext* ctx,
+							Datum val, Oid typid, Oid	typoutput)
+{   // Вывод с помощью стандартной OUTPUT функции ..._out
+	char* dataAsChar = OidOutputFunctionCall(typoutput, val);
+	switch (typid)
+	{
+	case INT2OID:
+	case INT4OID:
+	case INT8OID:
+	case OIDOID:
+	case FLOAT4OID:
+	case FLOAT8OID:
+	case NUMERICOID:
+		appendCtxString(ctx, dataAsChar);
+		break;
+
+	case BITOID:
+	case VARBITOID:
+		checkFlushCtx(ctx, (int)strlen(dataAsChar) + 3);
+		appendStringInfo(ctx->out, "B'%s'", dataAsChar);
+		break;
+
+	default:
+		{
+			const int maxCharSize = pg_database_encoding_max_length();
+			const char* pBegin;
+			const char* pEnd = dataAsChar;
+			int overflowRemain = checkFlushCtx(ctx, maxCharSize + 1);
+
+			appendStringInfoChar(ctx->out, cQuoteChar);
+			--overflowRemain;
+			//в отличие от printCharVarchar,
+			//здесь я не знаю длинну, но точно знаю, что на конце ноль
+			for (pBegin = dataAsChar; *pBegin; pBegin = pEnd)
+			{
+				bool overflow;
+				while (*pEnd &&
+						*pEnd != cQuoteChar &&
+						(int)(pEnd - pBegin) < overflowRemain)
+					++pEnd;
+				overflow = (int)(pEnd - pBegin) >= overflowRemain;
+				if (pEnd != pBegin)
+				{
+					if (overflow && *pEnd && *pEnd != cQuoteChar)
+					{
+						int32 L = (int32)(pEnd - pBegin);
+						if (!truncateIfIncmoplete(maxCharSize, pBegin, &L))
+							ereport(ERROR,
+							(errcode(ERRCODE_INVALID_CHARACTER_VALUE_FOR_CAST),
+								errmsg("invalid string value")
+								));
+						pEnd = pBegin + L;
+					}
+					appendBinaryStringInfo(ctx->out,
+						pBegin, (int)(pEnd - pBegin));
+				}
+
+				if (*pEnd == cQuoteChar)
+				{
+					overflowRemain = checkFlushCtx(ctx, maxCharSize + 2);
+					appendStringInfoChar(ctx->out, *pEnd);
+					appendStringInfoChar(ctx->out, *pEnd);
+					++pEnd;
+					overflowRemain -= 2;
+				}
+				else if (overflow)
+					overflowRemain = checkFlushCtx(ctx, record_buf_size(ctx));
+				else
+					overflowRemain = checkFlushCtx(ctx, maxCharSize);
+			}
+			if (overflowRemain < 1)
+				checkFlushCtx(ctx, 1);
+			appendStringInfoChar(ctx->out, cQuoteChar);
+		}
+		break;
+	}
+	pfree(dataAsChar);
+}
+
+static void printTuple(LogicalDecodingContext* ctx,
+	TupleDesc tupdesc, ReorderBufferTupleBuf* tuple,
+	bool skip_nulls, char* tableName)
+{
+
+	if (tuple == NULL)
+		appendCtxString(ctx, " (no-tuple-data)");
+	else
+	{
+		int natt;
+		for (natt = 0; natt < tupdesc->natts; natt++)
+		{
+			bool typisvarlena;
+			Oid	typoutput;
+			Form_pg_attribute attr = TupleDescAttr(tupdesc, natt);
+			Oid typid = attr->atttypid;
+			bool isnull;
+			Datum origval;
+
+			if (attr->attisdropped)
+				continue;
+			if (attr->attnum < 0) // Don't print system columns,
+				continue;//oid will already have been printed if present.
+
+			origval = heap_getattr(&tuple->tuple, natt + 1, tupdesc, &isnull);
+			if (isnull)
+			{
+				if (!skip_nulls)
+					appendCtxString(ctx, " null");
+				continue;
+			}
+
+			checkFlushCtx(ctx, 1);
+			appendStringInfoChar(ctx->out, ' ');
+
+			getTypeOutputInfo(typid, &typoutput, &typisvarlena);
+
+			if (typisvarlena)
+			{
+				if (VARATT_IS_EXTERNAL_ONDISK(origval))
+					appendCtxString(ctx, "unchanged-toast-datum");
+				else
+				{
+					Datum val = PointerGetDatum(PG_DETOAST_DATUM(origval));
+
+					if (typid == BPCHAROID ||
+						typid == VARCHAROID ||
+						typid == TEXTOID)
+						printCharVarchar(ctx, val);
+					else if (typid == BYTEAOID)
+						printByts(ctx, val);
+					else if (typid > FirstNormalObjectId)  {
+						readTypeOID("mchar", &MCHAROID);
+						readTypeOID("mvarchar", &MVARCHAROID);
+
+						if (typid == MCHAROID)
+							printMChar(ctx, val);
+						else if (typid == MVARCHAROID)
+							printMVarchar(ctx, val);
+						else
+							printDefault(ctx, val, typid, typoutput);
+					} else
+						printDefault(ctx, val, typid, typoutput);
+
+					if (DatumGetPointer(val) != DatumGetPointer(origval))
+						pfree(DatumGetPointer(val));
+				}
+
+			}
+			else
+			{
+				switch (typid)
+				{
+				case MONEYOID:
+					printMoney(ctx, origval);
+					break;
+				case TIMESTAMPOID:
+				case TIMESTAMPTZOID:
+					printTimestamp(ctx, origval);
+					break;
+				case DATEOID:
+					printDate(ctx, origval);
+					break;
+				case TIMEOID:
+					printTime(ctx, origval);
+					break;
+				case BOOLOID:
+					printBool(ctx, origval);
+					break;
+				default:
+					printDefault(ctx, origval, typid, typoutput);
+					break;
+				}
+			}
+		}
+	}
+}
+
+static void printTransaction(DecodingData* data,
+	LogicalDecodingContext* ctx, ReorderBufferTXN* txn)
+{
+	if (data->xact_wrote_changes)
+		return;
+
+	OutputPluginPrepareWrite(ctx, false);
+	if (data->include_xids)
+		appendStringInfo(ctx->out, "B %u", txn->xid);
+	else
+		appendStringInfoString(ctx->out, "B");
+	OutputPluginWrite(ctx, false);
+	data->xact_wrote_changes = true;
+}
+
+static void decode_change(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn, Relation relation, ReorderBufferChange* change)
+{
+	DecodingData* data = ctx->output_plugin_private;
+	if (data->skip_change)
+		return;
+	{
+		MemoryContext old = MemoryContextSwitchTo(data->context);
+		TupleDesc tupdesc = RelationGetDescr(relation);
+		char* tableName = RelationGetRelationName(relation);
+
+		printTransaction(data, ctx, txn);
+		prepareFlushedCtx(ctx);
+		switch (change->action)
+		{
+		case REORDER_BUFFER_CHANGE_INSERT:
+		{
+			appendStringInfoString(ctx->out, "I ");
+			appendStringInfoString(ctx->out, tableName);
+			printTuple(ctx, tupdesc, change->data.tp.newtuple, false, tableName);
+		}
+		break;
+		case REORDER_BUFFER_CHANGE_UPDATE:
+		{
+			appendStringInfoString(ctx->out, "U ");
+			appendStringInfoString(ctx->out, tableName);
+			printTuple(ctx, tupdesc, change->data.tp.newtuple, false, tableName);
+		}
+		break;
+		case REORDER_BUFFER_CHANGE_DELETE:
+		{
+			appendStringInfoString(ctx->out, "D ");
+			appendStringInfoString(ctx->out, tableName);
+			printTuple(ctx, tupdesc, change->data.tp.oldtuple, true, tableName);
+		}
+		break;
+		default:
+			Assert(false);
+		}
+		MemoryContextSwitchTo(old);
+	}
+	OutputPluginWrite(ctx, true);
+	MemoryContextReset(data->context);
+}
+
+static void decode_truncate(LogicalDecodingContext* ctx,
+	ReorderBufferTXN* txn, int nrelations,
+	Relation relations[], ReorderBufferChange* change)
+{
+	int i;
+	DecodingData* data = ctx->output_plugin_private;
+	if (data->skip_change)
+		return;
+
+	printTransaction(data, ctx, txn);
+	{
+		MemoryContext old = MemoryContextSwitchTo(data->context);
+
+		OutputPluginPrepareWrite(ctx, true);
+
+		appendStringInfoString(ctx->out, "T ");
+
+		for (i = 0; i < nrelations; i++)
+		{
+			if (i > 0)
+				appendStringInfoString(ctx->out, ", ");
+
+			appendStringInfoString(ctx->out,
+				RelationGetRelationName(relations[i]));
+		}
+
+		MemoryContextSwitchTo(old);
+	}
+	OutputPluginWrite(ctx, true);
+	MemoryContextReset(data->context);
+}
diff --git a/contrib/dbcopies_decoding/expected/simple.out b/contrib/dbcopies_decoding/expected/simple.out
new file mode 100644
index 00000000000..4053ec1935a
--- /dev/null
+++ b/contrib/dbcopies_decoding/expected/simple.out
@@ -0,0 +1,28 @@
+-- predictability
+SET synchronous_commit = on;
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+SELECT 'init' FROM pg_create_logical_replication_slot('dbcopies_slot', 'dbcopies_decoding');
+ ?column? 
+----------
+ init
+(1 row)
+
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+COMMIT;
+SELECT data FROM pg_logical_slot_get_changes('dbcopies_slot', NULL, NULL, 'include-xids', '0');
+             data              
+-------------------------------
+ B
+ I replication_example 1 1 '1'
+ I replication_example 2 1 '2'
+ C
+(4 rows)
+
+SELECT pg_drop_replication_slot('dbcopies_slot');
+ pg_drop_replication_slot 
+--------------------------
+ 
+(1 row)
+
diff --git a/contrib/dbcopies_decoding/logical.conf b/contrib/dbcopies_decoding/logical.conf
new file mode 100644
index 00000000000..367f7066514
--- /dev/null
+++ b/contrib/dbcopies_decoding/logical.conf
@@ -0,0 +1,2 @@
+wal_level = logical
+max_replication_slots = 4
diff --git a/contrib/dbcopies_decoding/sql/simple.sql b/contrib/dbcopies_decoding/sql/simple.sql
new file mode 100644
index 00000000000..1e9d2f72323
--- /dev/null
+++ b/contrib/dbcopies_decoding/sql/simple.sql
@@ -0,0 +1,15 @@
+-- predictability
+SET synchronous_commit = on;
+
+CREATE TABLE replication_example(id SERIAL PRIMARY KEY, somedata int, text varchar(120));
+
+SELECT 'init' FROM pg_create_logical_replication_slot('dbcopies_slot', 'dbcopies_decoding');
+
+BEGIN;
+INSERT INTO replication_example(somedata, text) VALUES (1, 1);
+INSERT INTO replication_example(somedata, text) VALUES (1, 2);
+COMMIT;
+
+SELECT data FROM pg_logical_slot_get_changes('dbcopies_slot', NULL, NULL, 'include-xids', '0');
+
+SELECT pg_drop_replication_slot('dbcopies_slot');
diff --git a/contrib/fasttrun/Makefile b/contrib/fasttrun/Makefile
new file mode 100644
index 00000000000..78e92b86cbe
--- /dev/null
+++ b/contrib/fasttrun/Makefile
@@ -0,0 +1,17 @@
+MODULE_big = fasttrun
+OBJS = fasttrun.o	
+DATA = fasttrun--2.0.sql fasttrun--unpackaged--2.0.sql
+DOCS = README.fasttrun
+REGRESS = fasttrun
+EXTENSION=fasttrun
+
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/fasttrun
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/fasttrun/README.fasttrun b/contrib/fasttrun/README.fasttrun
new file mode 100644
index 00000000000..b6d1b41a6d2
--- /dev/null
+++ b/contrib/fasttrun/README.fasttrun
@@ -0,0 +1,16 @@
+select fasttruncate('TABLE_NAME');
+
+Function truncates the temporary table and doesn't grow
+pg_class size.
+
+Warning: function isn't transaction safe!
+
+For tests:
+create or replace function f() returns void as $$
+begin
+for i in 1..1000
+loop
+         PERFORM fasttruncate('tt1');
+end loop;
+end;
+$$ language plpgsql;
diff --git a/contrib/fasttrun/expected/fasttrun.out b/contrib/fasttrun/expected/fasttrun.out
new file mode 100644
index 00000000000..ef64fa6400e
--- /dev/null
+++ b/contrib/fasttrun/expected/fasttrun.out
@@ -0,0 +1,115 @@
+CREATE EXTENSION fasttrun;
+create table persist ( a int );
+insert into persist values (1);
+select fasttruncate('persist');
+ERROR:  Relation isn't a temporary table
+insert into persist values (2);
+select * from persist order by a;
+ a 
+---
+ 1
+ 2
+(2 rows)
+
+create temp table temp1 (a int);
+insert into temp1 values (1);
+BEGIN;
+create temp table temp2 (a int);
+insert into temp2 values (1);
+select * from temp1 order by a;
+ a 
+---
+ 1
+(1 row)
+
+select * from temp2 order by a;
+ a 
+---
+ 1
+(1 row)
+
+insert into temp1 (select * from generate_series(1,10000));
+insert into temp2 (select * from generate_series(1,11000));
+analyze temp2;
+select relname,  relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+ relname | ?column? | ?column? 
+---------+----------+----------
+ temp1   | f        | f
+ temp2   | t        | t
+(2 rows)
+
+select fasttruncate('temp1');
+ fasttruncate 
+--------------
+ 
+(1 row)
+
+select fasttruncate('temp2');
+ fasttruncate 
+--------------
+ 
+(1 row)
+
+insert into temp1 values (-2);
+insert into temp2 values (-2);
+select * from temp1 order by a;
+ a  
+----
+ -2
+(1 row)
+
+select * from temp2 order by a;
+ a  
+----
+ -2
+(1 row)
+
+COMMIT;
+select * from temp1 order by a;
+ a  
+----
+ -2
+(1 row)
+
+select * from temp2 order by a;
+ a  
+----
+ -2
+(1 row)
+
+select relname,  relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+ relname | ?column? | ?column? 
+---------+----------+----------
+ temp1   | f        | f
+ temp2   | f        | f
+(2 rows)
+
+select fasttruncate('temp1');
+ fasttruncate 
+--------------
+ 
+(1 row)
+
+select fasttruncate('temp2');
+ fasttruncate 
+--------------
+ 
+(1 row)
+
+select * from temp1 order by a;
+ a 
+---
+(0 rows)
+
+select * from temp2 order by a;
+ a 
+---
+(0 rows)
+
+select relname,  relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+ relname | ?column? | ?column? 
+---------+----------+----------
+ temp1   | f        | f
+ temp2   | f        | f
+(2 rows)
+
diff --git a/contrib/fasttrun/fasttrun--2.0.sql b/contrib/fasttrun/fasttrun--2.0.sql
new file mode 100644
index 00000000000..708c2753151
--- /dev/null
+++ b/contrib/fasttrun/fasttrun--2.0.sql
@@ -0,0 +1,6 @@
+\echo Use "CREATE EXTENSION fasttrun" to load this file. \quit
+
+
+CREATE OR REPLACE FUNCTION fasttruncate(text)
+RETURNS void AS 'MODULE_PATHNAME'
+LANGUAGE C RETURNS NULL ON NULL INPUT VOLATILE;
diff --git a/contrib/fasttrun/fasttrun--unpackaged--2.0.sql b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql
new file mode 100644
index 00000000000..3a071f077e1
--- /dev/null
+++ b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql
@@ -0,0 +1,3 @@
+\echo Use "CREATE EXTENSION fasttrun FROM unpackaged" to load this file. \quit
+
+ALTER EXTENSION fasttrun ADD function fasttruncate(text);
diff --git a/contrib/fasttrun/fasttrun.c b/contrib/fasttrun/fasttrun.c
new file mode 100644
index 00000000000..0a92851c0fc
--- /dev/null
+++ b/contrib/fasttrun/fasttrun.c
@@ -0,0 +1,85 @@
+#include "postgres.h"
+
+#include "access/genam.h"
+#include "access/heapam.h"
+#include "miscadmin.h"
+#include "storage/lmgr.h"
+#include "storage/bufmgr.h"
+#include "catalog/namespace.h"
+#include "utils/lsyscache.h"
+#include "utils/builtins.h"
+#include <fmgr.h>
+#include <funcapi.h>
+#include <access/heapam.h>
+#include <catalog/pg_type.h>
+#include <catalog/heap.h>
+#include <commands/vacuum.h>
+#include <utils/regproc.h>
+#include <utils/varlena.h>
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+PG_FUNCTION_INFO_V1(fasttruncate);
+Datum	fasttruncate(PG_FUNCTION_ARGS);
+Datum
+fasttruncate(PG_FUNCTION_ARGS) {
+	text	*name=PG_GETARG_TEXT_P(0);
+	char	*relname;
+	List	*relname_list;
+	RangeVar	*relvar;
+	Oid		relOid;
+	Relation	rel;
+	bool	makeanalyze = false;
+
+	relname = palloc( VARSIZE(name) + 1);
+	memcpy(relname, VARDATA(name), VARSIZE(name)-VARHDRSZ);
+	relname[ VARSIZE(name)-VARHDRSZ ] = '\0';
+
+	relname_list = stringToQualifiedNameList(relname);
+	relvar = makeRangeVarFromNameList(relname_list);
+	relOid = RangeVarGetRelid(relvar, AccessExclusiveLock, false);
+
+	if ( get_rel_relkind(relOid) != RELKIND_RELATION )
+		elog(ERROR,"Relation isn't a ordinary table");
+
+	rel = table_open(relOid, NoLock);
+
+	if ( !isTempNamespace(get_rel_namespace(relOid)) )
+		elog(ERROR,"Relation isn't a temporary table");
+
+	heap_truncate(list_make1_oid(relOid));
+
+	if ( rel->rd_rel->relpages > 0 || rel->rd_rel->reltuples > 0 )
+		makeanalyze = true;
+
+	/*
+	 * heap_truncate doesn't unlock the table,
+	 * so we should unlock it.
+	 */
+
+	table_close(rel, AccessExclusiveLock);
+
+	if ( makeanalyze ) {
+		VacuumParams	params;
+		VacuumRelation	*rel;
+
+		params.options = VACOPT_ANALYZE;
+		params.freeze_min_age = -1;
+		params.freeze_table_age = -1;
+		params.multixact_freeze_min_age = -1;
+		params.multixact_freeze_table_age = -1;
+		params.is_wraparound = false;
+		params.log_min_duration = -1;
+
+		rel = makeNode(VacuumRelation);
+		rel->relation = relvar;
+		rel->oid = relOid;
+		rel->va_cols = NULL;
+		vacuum(list_make1(rel), &params,
+			   GetAccessStrategy(BAS_VACUUM), false);
+	}
+
+	PG_RETURN_VOID();
+}
diff --git a/contrib/fasttrun/fasttrun.control b/contrib/fasttrun/fasttrun.control
new file mode 100644
index 00000000000..7862c0bf8ad
--- /dev/null
+++ b/contrib/fasttrun/fasttrun.control
@@ -0,0 +1,5 @@
+comment = 'fast transaction-unsafe truncate'
+default_version = '2.0'
+module_pathname = '$libdir/fasttrun'
+relocatable = true
+trusted = true
diff --git a/contrib/fasttrun/sql/fasttrun.sql b/contrib/fasttrun/sql/fasttrun.sql
new file mode 100644
index 00000000000..0e3cb6c9beb
--- /dev/null
+++ b/contrib/fasttrun/sql/fasttrun.sql
@@ -0,0 +1,48 @@
+CREATE EXTENSION fasttrun;
+
+create table persist ( a int );
+insert into persist values (1);
+select fasttruncate('persist');
+insert into persist values (2);
+select * from persist order by a;
+
+create temp table temp1 (a int);
+insert into temp1 values (1);
+
+BEGIN;
+
+create temp table temp2 (a int);
+insert into temp2 values (1);
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+insert into temp1 (select * from generate_series(1,10000));
+insert into temp2 (select * from generate_series(1,11000));
+
+analyze temp2;
+select relname,  relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+
+select fasttruncate('temp1');
+select fasttruncate('temp2');
+
+insert into temp1 values (-2);
+insert into temp2 values (-2);
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+COMMIT;
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+select relname,  relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+
+select fasttruncate('temp1');
+select fasttruncate('temp2');
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+select relname,  relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
diff --git a/contrib/fulleq/Makefile b/contrib/fulleq/Makefile
new file mode 100644
index 00000000000..de8fbfcec41
--- /dev/null
+++ b/contrib/fulleq/Makefile
@@ -0,0 +1,45 @@
+MODULE_big = fulleq
+OBJS = fulleq.o	
+DOCS = README.fulleq
+REGRESS = fulleq
+DATA_built = fulleq--2.0.sql fulleq--unpackaged--2.0.sql
+EXTENSION=fulleq
+
+ARGTYPE = bool bytea char name int8 int2 int4 text \
+	oid xid cid oidvector float4 float8 macaddr \
+	inet cidr varchar date time timestamp timestamptz \
+	interval timetz
+
+EXTRA_CLEAN = fulleq--2.0.sql fulleq--unpackaged--2.0.sql
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/fulleq
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+all: fulleq--2.0.sql fulleq--unpackaged--2.0.sql
+
+fulleq--2.0.sql:	fulleq.sql.in
+	/bin/echo '\echo Use "CREATE EXTENSION fulleq" to load this file. \quit' > $@
+	for type in	$(ARGTYPE);	\
+	do	\
+		sed -e "s/ARGTYPE/$$type/g" < $< >> $@;	\
+	done
+
+fulleq--unpackaged--2.0.sql:	fulleq-unpackaged.sql.in
+	/bin/echo '\echo Use "CREATE EXTENSION fulleq FROM unpackaged" to load this file. \quit' > $@
+	/bin/echo 'DROP OPERATOR CLASS IF EXISTS int2vector_fill_ops USING hash;' >> $@
+	/bin/echo 'DROP OPERATOR FAMILY IF EXISTS int2vector_fill_ops USING hash;' >> $@
+	/bin/echo 'DROP FUNCTION IF EXISTS fullhash_int2vector(int2vector);' >> $@
+	/bin/echo 'DROP OPERATOR IF EXISTS == (int2vector, int2vector);' >> $@
+	/bin/echo 'DROP FUNCTION IF EXISTS isfulleq_int2vector(int2vector, int2vector);' >> $@
+	for type in	$(ARGTYPE);	\
+	do	\
+		sed -e "s/ARGTYPE/$$type/g" < $< >> $@;	\
+	done
+
diff --git a/contrib/fulleq/README.fulleq b/contrib/fulleq/README.fulleq
new file mode 100644
index 00000000000..93bf0cad20e
--- /dev/null
+++ b/contrib/fulleq/README.fulleq
@@ -0,0 +1,2 @@
+Introduce operator == which returns true when
+operands are equal or both are nulls.
diff --git a/contrib/fulleq/expected/fulleq.out b/contrib/fulleq/expected/fulleq.out
new file mode 100644
index 00000000000..452f8593432
--- /dev/null
+++ b/contrib/fulleq/expected/fulleq.out
@@ -0,0 +1,61 @@
+CREATE EXTENSION fulleq;
+select 4::int == 4;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 4::int == 5;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 4::int == NULL;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL::int == 5;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL::int == NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+select '4'::text == '4';
+ ?column? 
+----------
+ t
+(1 row)
+
+select '4'::text == '5';
+ ?column? 
+----------
+ f
+(1 row)
+
+select '4'::text == NULL;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL::text == '5';
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL::text == NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
diff --git a/contrib/fulleq/fulleq-unpackaged.sql.in b/contrib/fulleq/fulleq-unpackaged.sql.in
new file mode 100644
index 00000000000..8d759d8221f
--- /dev/null
+++ b/contrib/fulleq/fulleq-unpackaged.sql.in
@@ -0,0 +1,10 @@
+-- For ARGTYPE
+
+ALTER EXTENSION fulleq ADD FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE);
+
+ALTER EXTENSION fulleq ADD FUNCTION fullhash_ARGTYPE(ARGTYPE);
+
+ALTER EXTENSION fulleq ADD OPERATOR == (ARGTYPE, ARGTYPE);
+
+ALTER EXTENSION fulleq ADD OPERATOR CLASS ARGTYPE_fill_ops USING hash;
+
diff --git a/contrib/fulleq/fulleq.c b/contrib/fulleq/fulleq.c
new file mode 100644
index 00000000000..e435be4b93a
--- /dev/null
+++ b/contrib/fulleq/fulleq.c
@@ -0,0 +1,112 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "access/hash.h"
+#include "catalog/pg_collation.h"
+#include "utils/builtins.h"
+#include "utils/bytea.h"
+#include "utils/timestamp.h"
+#include "utils/date.h"
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+#define	NULLHASHVALUE		(-2147483647)
+
+#define	FULLEQ_FUNC(type, cmpfunc, hashfunc)			\
+PG_FUNCTION_INFO_V1( isfulleq_##type );					\
+Datum	isfulleq_##type(PG_FUNCTION_ARGS);				\
+Datum													\
+isfulleq_##type(PG_FUNCTION_ARGS) {						\
+	if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) )			\
+		PG_RETURN_BOOL(true);							\
+	else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )		\
+		PG_RETURN_BOOL(false);							\
+														\
+	PG_RETURN_DATUM( DirectFunctionCall2Coll( cmpfunc,	\
+			DEFAULT_COLLATION_OID,						\
+			PG_GETARG_DATUM(0),							\
+			PG_GETARG_DATUM(1)							\
+	) );												\
+}														\
+														\
+PG_FUNCTION_INFO_V1( fullhash_##type );					\
+Datum	fullhash_##type(PG_FUNCTION_ARGS);				\
+Datum													\
+fullhash_##type(PG_FUNCTION_ARGS) {						\
+	if ( PG_ARGISNULL(0) )								\
+		PG_RETURN_INT32(NULLHASHVALUE);					\
+														\
+	PG_RETURN_DATUM( DirectFunctionCall1( hashfunc,		\
+			PG_GETARG_DATUM(0)							\
+	) );												\
+}
+
+
+static Datum
+hashint2vector(PG_FUNCTION_ARGS)
+{
+	int2vector *key = (int2vector *) PG_GETARG_POINTER(0);
+
+	return hash_any((unsigned char *) key->values, key->dim1 * sizeof(int16));
+}
+
+/*
+ * We don't have a complete set of int2vector support routines,
+ * but we need int2vectoreq for catcache indexing.
+ */
+static Datum
+int2vectoreq(PG_FUNCTION_ARGS)
+{
+	int2vector *a = (int2vector *) PG_GETARG_POINTER(0);
+	int2vector *b = (int2vector *) PG_GETARG_POINTER(1);
+
+	if (a->dim1 != b->dim1)
+		PG_RETURN_BOOL(false);
+	PG_RETURN_BOOL(memcmp(a->values, b->values, a->dim1 * sizeof(int16)) == 0);
+}
+
+
+FULLEQ_FUNC( bool        , booleq         , hashchar       );
+FULLEQ_FUNC( bytea       , byteaeq        , hashvarlena    );
+FULLEQ_FUNC( char        , chareq         , hashchar       );
+FULLEQ_FUNC( name        , nameeq         , hashname       );
+FULLEQ_FUNC( int8        , int8eq         , hashint8       );
+FULLEQ_FUNC( int2        , int2eq         , hashint2       );
+FULLEQ_FUNC( int4        , int4eq         , hashint4       );
+FULLEQ_FUNC( text        , texteq         , hashtext       );
+FULLEQ_FUNC( oid         , oideq          , hashoid        );
+FULLEQ_FUNC( xid         , xideq          , hashint4       );
+FULLEQ_FUNC( cid         , cideq          , hashint4       );
+FULLEQ_FUNC( oidvector   , oidvectoreq    , hashoidvector  );
+FULLEQ_FUNC( float4      , float4eq       , hashfloat4     );
+FULLEQ_FUNC( float8      , float8eq       , hashfloat8     );
+/*FULLEQ_FUNC( abstime     , abstimeeq      , hashint4       );*/
+/*FULLEQ_FUNC( reltime     , reltimeeq      , hashint4       );*/
+FULLEQ_FUNC( macaddr     , macaddr_eq     , hashmacaddr    );
+FULLEQ_FUNC( inet        , network_eq     , hashinet       );
+FULLEQ_FUNC( cidr        , network_eq     , hashinet       );
+FULLEQ_FUNC( varchar     , texteq         , hashtext       );
+FULLEQ_FUNC( date        , date_eq        , hashint4       );
+FULLEQ_FUNC( time        , time_eq        , hashfloat8     );
+FULLEQ_FUNC( timestamp   , timestamp_eq   , hashfloat8     );
+FULLEQ_FUNC( timestamptz , timestamp_eq   , hashfloat8     );
+FULLEQ_FUNC( interval    , interval_eq    , interval_hash  );
+FULLEQ_FUNC( timetz      , timetz_eq      , timetz_hash    );
+
+/*
+ * v10 drop * support for int2vector equality and hash operator in commit
+ * 5c80642aa8de8393b08cd3cbf612b325cedd98dc, but for compatibility
+ * we still add this operators
+ */
+FULLEQ_FUNC( int2vector  , int2vectoreq   , hashint2vector );
+
+static Datum
+dummy_eq(PG_FUNCTION_ARGS)
+{
+	 elog(ERROR, "unimplemented");
+	 PG_RETURN_DATUM(0); //keep compiler quiet
+}
+
+FULLEQ_FUNC( abstime     , dummy_eq      , hashint4       );
+FULLEQ_FUNC( reltime     , dummy_eq      , hashint4       );
diff --git a/contrib/fulleq/fulleq.control b/contrib/fulleq/fulleq.control
new file mode 100644
index 00000000000..30a26c65fff
--- /dev/null
+++ b/contrib/fulleq/fulleq.control
@@ -0,0 +1,5 @@
+comment = 'exact equal operation'
+default_version = '2.0'
+module_pathname = '$libdir/fulleq'
+relocatable = true
+trusted = true
diff --git a/contrib/fulleq/fulleq.sql.in b/contrib/fulleq/fulleq.sql.in
new file mode 100644
index 00000000000..c270647c720
--- /dev/null
+++ b/contrib/fulleq/fulleq.sql.in
@@ -0,0 +1,25 @@
+-- For ARGTYPE
+
+CREATE OR REPLACE FUNCTION isfulleq_ARGTYPE(ARGTYPE, ARGTYPE) 
+RETURNS bool AS 'MODULE_PATHNAME'
+LANGUAGE C CALLED ON NULL INPUT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION fullhash_ARGTYPE(ARGTYPE)
+RETURNS int4 AS 'MODULE_PATHNAME'
+LANGUAGE C CALLED ON NULL INPUT IMMUTABLE;
+
+
+CREATE OPERATOR == (
+	LEFTARG		= ARGTYPE,
+	RIGHTARG	= ARGTYPE,
+	PROCEDURE	= isfulleq_ARGTYPE,
+	COMMUTATOR	= '==',
+	RESTRICT	= eqsel,
+	JOIN		= eqjoinsel,
+	HASHES
+);
+
+CREATE OPERATOR CLASS ARGTYPE_fill_ops
+ FOR TYPE ARGTYPE USING hash AS
+	OPERATOR	1	==,
+	FUNCTION	1	fullhash_ARGTYPE(ARGTYPE);
diff --git a/contrib/fulleq/sql/fulleq.sql b/contrib/fulleq/sql/fulleq.sql
new file mode 100644
index 00000000000..d43abeb34b7
--- /dev/null
+++ b/contrib/fulleq/sql/fulleq.sql
@@ -0,0 +1,13 @@
+CREATE EXTENSION fulleq;
+
+select 4::int == 4;
+select 4::int == 5;
+select 4::int == NULL;
+select NULL::int == 5;
+select NULL::int == NULL;
+
+select '4'::text == '4';
+select '4'::text == '5';
+select '4'::text == NULL;
+select NULL::text == '5';
+select NULL::text == NULL;
diff --git a/contrib/mchar/Changes b/contrib/mchar/Changes
new file mode 100644
index 00000000000..b7f6e0c5718
--- /dev/null
+++ b/contrib/mchar/Changes
@@ -0,0 +1,20 @@
+2.0  make an extension
+0.17  add == operation:
+		a == b   =>   ( a = b or a is null and b is null )
+0.16  fix pg_dump - now mchar in pg_catalog scheme, not public
+ 	  fix bug in mvarchar_substr()
+0.15  add upper()/lower()
+0.14  Add ESCAPE for LIKE, SIMILAR TO [ESCAPE], POSIX regexp 
+0.13  Outer binary format is now different from
+	  inner: it's just a UTF-16 string
+0.12  Fix copy binary
+0.11  Force UTF-8 convertor if server_encoding='UTF8'
+0.10  add (mchar|mvarchar)_(send|recv) functions to
+      allow binary copying. Note: that functions
+	  don't recode values.
+0.9	index support for like, improve recoding functions
+0.8 initial suport for like optimizioation with index:
+    still thres no algo to find the nearest greater string
+0.7	hash indexes and enable a hash joins
+0.6	implicit casting mchar-mvarchar
+	cross type comparison operations
diff --git a/contrib/mchar/Makefile b/contrib/mchar/Makefile
new file mode 100644
index 00000000000..81826afd296
--- /dev/null
+++ b/contrib/mchar/Makefile
@@ -0,0 +1,31 @@
+MODULE_big = mchar
+OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o mchar_like.o
+EXTENSION=mchar
+DATA = mchar--2.2.1.sql mchar--2.0.1--2.1.sql mchar--2.0--2.1.sql \
+	    mchar--2.1.1--2.2.sql mchar--2.1--2.2.sql \
+		mchar--2.2--2.2.1.sql \
+		mchar--unpackaged--2.0.sql
+DOCS = README.mchar
+REGRESS = init mchar mvarchar mm like compat
+ENCODING = UTF8
+
+PG_CPPFLAGS=-I/usr/local/include
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/mchar
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
+ifeq ($(PORTNAME),win32)
+ICUNAME=icuin
+else
+ICUNAME=icui18n
+endif
+
+SHLIB_LINK += -L/usr/local/lib -licuuc -l$(ICUNAME) -Wl,-rpath,'$$ORIGIN'
+
diff --git a/contrib/mchar/README.mchar b/contrib/mchar/README.mchar
new file mode 100644
index 00000000000..479a7d1f40a
--- /dev/null
+++ b/contrib/mchar/README.mchar
@@ -0,0 +1,20 @@
+MCHAR & VARCHAR
+	type modifier
+	length()
+	substr(str, pos[, length])
+	|| - concatenation with any (mchar,mvarchar) arguments
+	< <= = >= >  - case-insensitive comparisons (libICU)
+	&< &<= &= &>= &>  - case-sensitive comparisons (libICU)
+	implicit casting  mchar<->mvarchar
+	B-tree and hash index
+	LIKE [ESCAPE]
+	SIMILAR TO [ESCAPE]
+	~ (POSIX regexp)
+	index support for LIKE
+
+
+Authors:
+	Oleg Bartunov <oleg@sai.msu.ru>
+	Teodor Sigaev <teodor@sigaev.ru>
+
+
diff --git a/contrib/mchar/expected/compat.out b/contrib/mchar/expected/compat.out
new file mode 100644
index 00000000000..480a286e8f6
--- /dev/null
+++ b/contrib/mchar/expected/compat.out
@@ -0,0 +1,66 @@
+--- table based checks
+select '<' || ch || '>', '<' || vch || '>' from chvch;
+    ?column?    |   ?column?   
+----------------+--------------
+ <No spaces   > | <No spaces>
+ <One space   > | <One space >
+ <1 space     > | <1 space >
+(3 rows)
+
+select * from chvch where vch = 'One space';
+      ch      |    vch     
+--------------+------------
+ One space    | One space 
+(1 row)
+
+select * from chvch where vch = 'One space ';
+      ch      |    vch     
+--------------+------------
+ One space    | One space 
+(1 row)
+
+select * from ch where chcol = 'abcd' order by chcol;
+              chcol               
+----------------------------------
+ abcd                            
+ AbcD                            
+(2 rows)
+
+select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol;
+              chcol               |              chcol               
+----------------------------------+----------------------------------
+ abcd                             | AbcD                            
+ abcd                             | abcd                            
+ AbcD                             | AbcD                            
+ AbcD                             | abcd                            
+ abcz                             | abcz                            
+ defg                             | dEfg                            
+ defg                             | defg                            
+ dEfg                             | dEfg                            
+ dEfg                             | defg                            
+ ee                               | Ee                              
+ ee                               | ee                              
+ Ee                               | Ee                              
+ Ee                               | ee                              
+(13 rows)
+
+select * from ch where chcol > 'abcd' and chcol<'ee';
+              chcol               
+----------------------------------
+ abcz                            
+ defg                            
+ dEfg                            
+(3 rows)
+
+select * from ch order by chcol;
+              chcol               
+----------------------------------
+ abcd                            
+ AbcD                            
+ abcz                            
+ defg                            
+ dEfg                            
+ ee                              
+ Ee                              
+(7 rows)
+
diff --git a/contrib/mchar/expected/init.out b/contrib/mchar/expected/init.out
new file mode 100644
index 00000000000..7bae978ec35
--- /dev/null
+++ b/contrib/mchar/expected/init.out
@@ -0,0 +1,18 @@
+CREATE EXTENSION mchar;
+create table ch (
+	chcol mchar(32)
+) without oids;
+insert into ch values('abcd');
+insert into ch values('AbcD');
+insert into ch values('abcz');
+insert into ch values('defg');
+insert into ch values('dEfg');
+insert into ch values('ee');
+insert into ch values('Ee');
+create table chvch (
+    ch      mchar(12),
+	vch     mvarchar(12)
+) without oids;
+insert into chvch values('No spaces', 'No spaces');
+insert into chvch values('One space ', 'One space ');
+insert into chvch values('1 space', '1 space ');
diff --git a/contrib/mchar/expected/like.out b/contrib/mchar/expected/like.out
new file mode 100644
index 00000000000..a3f47f8c710
--- /dev/null
+++ b/contrib/mchar/expected/like.out
@@ -0,0 +1,841 @@
+-- simplest examples
+-- E061-04 like predicate
+set standard_conforming_strings=off;
+SELECT 'hawkeye'::mchar LIKE 'h%' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'hawkeye'::mchar LIKE 'H%' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mchar LIKE '_ndio' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mchar LIKE 'in__o' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mchar LIKE 'in_o' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mvarchar LIKE '_ndio' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mvarchar LIKE 'in__o' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mvarchar LIKE 'in_o' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true";
+ true 
+------
+ t
+(1 row)
+
+-- unused escape character
+SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+-- escape character
+-- E061-05 like predicate with escape clause
+SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+-- escape character same as pattern character
+SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true";
+ true 
+------
+ t
+(1 row)
+
+-- unused escape character
+SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+-- escape character
+-- E061-05 like predicate with escape clause
+SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+-- escape character same as pattern character
+SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true";
+ true 
+------
+ t
+(1 row)
+
+-- similar to
+SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar   AS   "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'abc'::mchar SIMILAR TO 'a'::mchar      AS  "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS  "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar   AS   "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar      AS  "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true";
+ true 
+------
+ t
+(1 row)
+
+SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS  "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false";
+ false 
+-------
+ f
+(1 row)
+
+SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true";
+ true 
+------
+ t
+(1 row)
+
+-- index support
+SELECT * from ch where chcol like 'aB_d' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+(2 rows)
+
+SELECT * from ch where chcol like 'aB%d' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+(2 rows)
+
+SELECT * from ch where chcol like 'aB%' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+ abcz                            
+(3 rows)
+
+SELECT * from ch where chcol like '%BC%' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+ abcz                            
+(3 rows)
+
+set enable_seqscan = off;
+explain (costs off)
+SELECT * from ch where chcol like 'aB_d' order by chcol using &<;
+                                  QUERY PLAN                                  
+------------------------------------------------------------------------------
+ Sort
+   Sort Key: chcol USING &<
+   ->  Index Only Scan using qq on ch
+         Index Cond: ((chcol >= 'aB'::mvarchar) AND (chcol < 'aC'::mvarchar))
+         Filter: (chcol ~~ 'aB_d'::mvarchar)
+(5 rows)
+
+SELECT * from ch where chcol like 'aB_d' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+(2 rows)
+
+SELECT * from ch where chcol like 'aB%d' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+(2 rows)
+
+SELECT * from ch where chcol like 'aB%' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+ abcz                            
+(3 rows)
+
+SELECT * from ch where chcol like '%BC%' order by chcol using &<;
+              chcol               
+----------------------------------
+ AbcD                            
+ abcd                            
+ abcz                            
+(3 rows)
+
+set enable_seqscan = on;
+create table testt (f1 mchar(10));
+insert into testt values ('Abc-000001');
+insert into testt values ('Abc-000002');
+insert into testt values ('0000000001');
+insert into testt values ('0000000002');
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+create index testindex on testt(f1);
+set enable_seqscan=off;
+explain (costs off)
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+                    QUERY PLAN                     
+---------------------------------------------------
+ Index Only Scan using testindex on testt
+   Filter: ((f1)::mvarchar ~~ 'Abc\\-%'::mvarchar)
+(2 rows)
+
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+set enable_seqscan = on;
+drop table testt;
+create table testt (f1 mvarchar(10));
+insert into testt values ('Abc-000001');
+insert into testt values ('Abc-000002');
+insert into testt values ('0000000001');
+insert into testt values ('0000000002');
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'Abc\\-  %'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'   %'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+ 0000000001
+ 0000000002
+(4 rows)
+
+create index testindex on testt(f1);
+set enable_seqscan=off;
+explain (costs off)
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+                              QUERY PLAN                              
+----------------------------------------------------------------------
+ Index Only Scan using testindex on testt
+   Index Cond: ((f1 >= 'Abc-'::mvarchar) AND (f1 < 'Abc.'::mvarchar))
+   Filter: ((f1)::mvarchar ~~ 'Abc\\-%'::mvarchar)
+(3 rows)
+
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'Abc\\-   %'::mchar;
+     f1     
+------------
+ Abc-000001
+ Abc-000002
+(2 rows)
+
+select * from testt where f1::mchar like E'   %'::mchar;
+     f1     
+------------
+ 0000000001
+ 0000000002
+ Abc-000001
+ Abc-000002
+(4 rows)
+
+set enable_seqscan = on;
+drop table testt;
+CREATE TABLE test ( code mchar(5) NOT NULL );
+insert into test values('1111 ');
+insert into test values('111  ');
+insert into test values('11   ');
+insert into test values('1    ');
+SELECT * FROM test WHERE code LIKE ('%    ');
+ code  
+-------
+ 1    
+(1 row)
+
+set escape_string_warning = off;
+SELECT CASE WHEN ('_'::text SIMILAR TO '[\\_]'::text ESCAPE '\\'::text) THEN TRUE ELSE FALSE END ;
+ case 
+------
+ t
+(1 row)
+
+SELECT CASE WHEN ('_'::mchar SIMILAR TO '[\\_]'::mchar ESCAPE '\\'::mchar) THEN TRUE ELSE FALSE END ;
+ case 
+------
+ t
+(1 row)
+
+SELECT CASE WHEN ('_'::mvarchar SIMILAR TO '[\\_]'::mvarchar ESCAPE '\\'::mvarchar) THEN TRUE ELSE FALSE END ;
+ case 
+------
+ t
+(1 row)
+
+reset escape_string_warning;
+reset standard_conforming_strings;
diff --git a/contrib/mchar/expected/mchar.out b/contrib/mchar/expected/mchar.out
new file mode 100644
index 00000000000..f6c592fd16f
--- /dev/null
+++ b/contrib/mchar/expected/mchar.out
@@ -0,0 +1,382 @@
+-- I/O tests
+select '1'::mchar;
+ mchar 
+-------
+ 1
+(1 row)
+
+select '2  '::mchar;
+ mchar 
+-------
+ 2
+(1 row)
+
+select '10          '::mchar;
+ mchar 
+-------
+ 10
+(1 row)
+
+select '1'::mchar(2);
+ mchar 
+-------
+ 1 
+(1 row)
+
+select '2 '::mchar(2);
+ mchar 
+-------
+ 2 
+(1 row)
+
+select '3  '::mchar(2);
+ mchar 
+-------
+ 3 
+(1 row)
+
+select '10          '::mchar(2);
+ mchar 
+-------
+ 10
+(1 row)
+
+select '                  '::mchar(10); 
+   mchar    
+------------
+           
+(1 row)
+
+select '                  '::mchar; 
+ mchar 
+-------
+ 
+(1 row)
+
+-- operations & functions
+select length('1'::mchar);
+ length 
+--------
+      1
+(1 row)
+
+select length('2  '::mchar);
+ length 
+--------
+      1
+(1 row)
+
+select length('10          '::mchar);
+ length 
+--------
+      2
+(1 row)
+
+select length('1'::mchar(2));
+ length 
+--------
+      1
+(1 row)
+
+select length('2 '::mchar(2));
+ length 
+--------
+      1
+(1 row)
+
+select length('3  '::mchar(2));
+ length 
+--------
+      1
+(1 row)
+
+select length('10          '::mchar(2));
+ length 
+--------
+      2
+(1 row)
+
+select length('                  '::mchar(10)); 
+ length 
+--------
+      0
+(1 row)
+
+select length('                  '::mchar); 
+ length 
+--------
+      0
+(1 row)
+
+select 'asd'::mchar(10) || '>'::mchar(10);
+       ?column?       
+----------------------
+ asd       >         
+(1 row)
+
+select length('asd'::mchar(10) || '>'::mchar(10));
+ length 
+--------
+     11
+(1 row)
+
+select 'asd'::mchar(2)  || '>'::mchar(10);
+   ?column?   
+--------------
+ as>         
+(1 row)
+
+select length('asd'::mchar(2) || '>'::mchar(10));
+ length 
+--------
+      3
+(1 row)
+
+-- Comparisons
+select 'asdf'::mchar = 'aSdf'::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar = 'aSdf '::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar = 'aSdf 1'::mchar(4);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar = 'aSdf 1'::mchar(5);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar = 'aSdf 1'::mchar(6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar < 'aSdf'::mchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar < 'aSdf '::mchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar < 'aSdf 1'::mchar(4);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar < 'aSdf 1'::mchar(5);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar < 'aSdf 1'::mchar(6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar <= 'aSdf'::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar <= 'aSdf '::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar <= 'aSdf 1'::mchar(4);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar <= 'aSdf 1'::mchar(5);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar <= 'aSdf 1'::mchar(6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar >= 'aSdf'::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar >= 'aSdf '::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar >= 'aSdf 1'::mchar(4);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar >= 'aSdf 1'::mchar(5);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mchar >= 'aSdf 1'::mchar(6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar > 'aSdf'::mchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar > 'aSdf '::mchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar > 'aSdf 1'::mchar(4);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar > 'aSdf 1'::mchar(5);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mchar > 'aSdf 1'::mchar(6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select max(ch) from chvch;
+     max      
+--------------
+ One space   
+(1 row)
+
+select min(ch) from chvch;
+     min      
+--------------
+ 1 space     
+(1 row)
+
+select substr('1234567890'::mchar, 3) = '34567890' as "34567890";
+ 34567890 
+----------
+ f
+(1 row)
+
+select substr('1234567890'::mchar, 4, 3) = '456' as "456";
+ 456 
+-----
+ t
+(1 row)
+
+select lower('asdfASDF'::mchar);
+  lower   
+----------
+ asdfasdf
+(1 row)
+
+select upper('asdfASDF'::mchar);
+  upper   
+----------
+ ASDFASDF
+(1 row)
+
+select 'asd'::mchar == 'aSd'::mchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asd'::mchar == 'aCd'::mchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asd'::mchar == NULL;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL == 'aCd'::mchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL::mchar == NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
+--Note: here we use different space symbols, be carefull to copy it!
+select v, count(*) from
+(values  (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+   v   | count 
+-------+-------
+ aSDF  |     2
+ 4 242 |     2
+(2 rows)
+
+set enable_hashagg=off;
+select v, count(*) from
+(values  (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+   v   | count 
+-------+-------
+ 4 242 |     2
+ aSDF  |     2
+(2 rows)
+
+reset enable_hashagg;
diff --git a/contrib/mchar/expected/mm.out b/contrib/mchar/expected/mm.out
new file mode 100644
index 00000000000..c5b36c21611
--- /dev/null
+++ b/contrib/mchar/expected/mm.out
@@ -0,0 +1,855 @@
+select 'asd'::mchar::mvarchar;
+ mvarchar 
+----------
+ asd
+(1 row)
+
+select 'asd '::mchar::mvarchar;
+ mvarchar 
+----------
+ asd
+(1 row)
+
+select 'asd'::mchar(2)::mvarchar;
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd '::mchar(2)::mvarchar;
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd'::mchar(5)::mvarchar;
+ mvarchar 
+----------
+ asd  
+(1 row)
+
+select 'asd '::mchar(5)::mvarchar;
+ mvarchar 
+----------
+ asd  
+(1 row)
+
+select 'asd'::mchar::mvarchar(2);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd '::mchar::mvarchar(2);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd'::mchar(2)::mvarchar(2);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd '::mchar(2)::mvarchar(2);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd'::mchar(5)::mvarchar(2);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd '::mchar(5)::mvarchar(2);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd'::mchar::mvarchar(5);
+ mvarchar 
+----------
+ asd
+(1 row)
+
+select 'asd '::mchar::mvarchar(5);
+ mvarchar 
+----------
+ asd
+(1 row)
+
+select 'asd'::mchar(2)::mvarchar(5);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd '::mchar(2)::mvarchar(5);
+ mvarchar 
+----------
+ as
+(1 row)
+
+select 'asd'::mchar(5)::mvarchar(5);
+ mvarchar 
+----------
+ asd  
+(1 row)
+
+select 'asd '::mchar(5)::mvarchar(5);
+ mvarchar 
+----------
+ asd  
+(1 row)
+
+select 'asd'::mvarchar::mchar;
+ mchar 
+-------
+ asd
+(1 row)
+
+select 'asd '::mvarchar::mchar;
+ mchar 
+-------
+ asd
+(1 row)
+
+select 'asd'::mvarchar(2)::mchar;
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd '::mvarchar(2)::mchar;
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd'::mvarchar(5)::mchar;
+ mchar 
+-------
+ asd
+(1 row)
+
+select 'asd '::mvarchar(5)::mchar;
+ mchar 
+-------
+ asd
+(1 row)
+
+select 'asd'::mvarchar::mchar(2);
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd '::mvarchar::mchar(2);
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd'::mvarchar(2)::mchar(2);
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd '::mvarchar(2)::mchar(2);
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd'::mvarchar(5)::mchar(2);
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd '::mvarchar(5)::mchar(2);
+ mchar 
+-------
+ as
+(1 row)
+
+select 'asd'::mvarchar::mchar(5);
+ mchar 
+-------
+ asd  
+(1 row)
+
+select 'asd '::mvarchar::mchar(5);
+ mchar 
+-------
+ asd  
+(1 row)
+
+select 'asd'::mvarchar(2)::mchar(5);
+ mchar 
+-------
+ as   
+(1 row)
+
+select 'asd '::mvarchar(2)::mchar(5);
+ mchar 
+-------
+ as   
+(1 row)
+
+select 'asd'::mvarchar(5)::mchar(5);
+ mchar 
+-------
+ asd  
+(1 row)
+
+select 'asd '::mvarchar(5)::mchar(5);
+ mchar 
+-------
+ asd  
+(1 row)
+
+select 'asd'::mchar || '123';
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd'::mchar || '123'::mchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd'::mchar || '123'::mvarchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mchar || '123';
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mchar || '123'::mchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mchar || '123'::mvarchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mchar || '123 ';
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mchar || '123 '::mchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mchar || '123 '::mvarchar;
+ ?column? 
+----------
+ asd123 
+(1 row)
+
+select 'asd'::mvarchar || '123';
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd'::mvarchar || '123'::mchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd'::mvarchar || '123'::mvarchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mvarchar || '123';
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar || '123'::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar || '123'::mvarchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar || '123 ';
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd '::mvarchar || '123 '::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar || '123 '::mvarchar;
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd'::mchar(2) || '123';
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd'::mchar(2) || '123'::mchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd'::mchar(2) || '123'::mvarchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mchar(2) || '123';
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mchar(2) || '123'::mchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mchar(2) || '123'::mvarchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mchar(2) || '123 ';
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mchar(2) || '123 '::mchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mchar(2) || '123 '::mvarchar;
+ ?column? 
+----------
+ as123 
+(1 row)
+
+select 'asd'::mvarchar(2) || '123';
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd'::mvarchar(2) || '123'::mchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd'::mvarchar(2) || '123'::mvarchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mvarchar(2) || '123';
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mvarchar(2) || '123'::mchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mvarchar(2) || '123'::mvarchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mvarchar(2) || '123 ';
+ ?column? 
+----------
+ as123 
+(1 row)
+
+select 'asd '::mvarchar(2) || '123 '::mchar;
+ ?column? 
+----------
+ as123
+(1 row)
+
+select 'asd '::mvarchar(2) || '123 '::mvarchar;
+ ?column? 
+----------
+ as123 
+(1 row)
+
+select 'asd'::mchar(4) || '143';
+ ?column? 
+----------
+ asd 143
+(1 row)
+
+select 'asd'::mchar(4) || '123'::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd'::mchar(4) || '123'::mvarchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mchar(4) || '123';
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mchar(4) || '123'::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mchar(4) || '123'::mvarchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mchar(4) || '123 ';
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mchar(4) || '123 '::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mchar(4) || '123 '::mvarchar;
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd'::mvarchar(4) || '123';
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd'::mvarchar(4) || '123'::mchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd'::mvarchar(4) || '123'::mvarchar;
+ ?column? 
+----------
+ asd123
+(1 row)
+
+select 'asd '::mvarchar(4) || '123';
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar(4) || '123'::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar(4) || '123'::mvarchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar(4) || '123 ';
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd '::mvarchar(4) || '123 '::mchar;
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 'asd '::mvarchar(4) || '123 '::mvarchar;
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd '::mvarchar(4) || '123 '::mchar(4);
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd '::mvarchar(4) || '123 '::mvarchar(4);
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd '::mvarchar(4) || '123'::mchar(4);
+ ?column? 
+----------
+ asd 123 
+(1 row)
+
+select 'asd '::mvarchar(4) || '123'::mvarchar(4);
+ ?column? 
+----------
+ asd 123
+(1 row)
+
+select 1 where 'f'::mchar='F'::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar='F '::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar='F'::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar='F '::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar='F'::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar='F '::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar='F'::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar='F '::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar(2)='F'::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar(2)='F '::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar(2)='F'::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar(2)='F '::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar(2)='F'::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f'::mchar(2)='F '::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar(2)='F'::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'f '::mchar(2)='F '::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo'::mchar='FOO'::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo'::mchar='FOO '::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo '::mchar='FOO'::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo '::mchar='FOO '::mvarchar;
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo'::mchar='FOO'::mvarchar(2);
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo'::mchar='FOO '::mvarchar(2);
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo '::mchar='FOO'::mvarchar(2);
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo '::mchar='FOO '::mvarchar(2);
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo'::mchar(2)='FOO'::mvarchar;
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo'::mchar(2)='FOO '::mvarchar;
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo '::mchar(2)='FOO'::mvarchar;
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo '::mchar(2)='FOO '::mvarchar;
+ ?column? 
+----------
+(0 rows)
+
+select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2);
+ ?column? 
+----------
+        1
+(1 row)
+
+Select 'f'::mchar(1) Union Select 'o'::mvarchar(1);
+ mchar 
+-------
+ f
+ o
+(2 rows)
+
+Select 'f'::mvarchar(1) Union Select 'o'::mchar(1);
+ mvarchar 
+----------
+ f
+ o
+(2 rows)
+
+select * from chvch where ch=vch;
+      ch      |    vch     
+--------------+------------
+ No spaces    | No spaces
+ One space    | One space 
+ 1 space      | 1 space 
+(3 rows)
+
+select ch.* from ch, (select 'dEfg'::mvarchar as q) as p  where  chcol > p.q;
+              chcol               
+----------------------------------
+ ee                              
+ Ee                              
+(2 rows)
+
+create index qq on ch (chcol);
+set enable_seqscan=off;
+select ch.* from ch, (select 'dEfg'::mvarchar as q) as p  where  chcol > p.q;
+              chcol               
+----------------------------------
+ ee                              
+ Ee                              
+(2 rows)
+
+set enable_seqscan=on;
+--\copy chvch to 'results/chvch.dump' binary
+--truncate table chvch;
+--\copy chvch from 'results/chvch.dump' binary
+--test joins
+CREATE TABLE a (mchar2 MCHAR(2) NOT NULL);
+CREATE TABLE c (mvarchar255 mvarchar NOT NULL);
+SELECT * FROM a, c WHERE mchar2 = mvarchar255;
+ mchar2 | mvarchar255 
+--------+-------------
+(0 rows)
+
+SELECT * FROM a, c WHERE mvarchar255 = mchar2;
+ mchar2 | mvarchar255 
+--------+-------------
+(0 rows)
+
+DROP TABLE a;
+DROP TABLE c;
+select * from (values
+    ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её'))
+    z order by 1;
+ column1 
+---------
+ е
+ ё
+ еа
+ ёа
+ еб
+ ёб
+ ее
+ её
+ ёе
+ ёё
+ еж
+ ёж
+(12 rows)
+
+select 'ё'::mchar = 'е';
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'Ё'::mchar = 'Е';
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'й'::mchar = 'и';
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'Й'::mchar = 'И';
+ ?column? 
+----------
+ f
+(1 row)
+
+select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'),
+    mvarchar_icase_cmp('ё', 'ёа');
+ mvarchar_icase_cmp | mvarchar_icase_cmp | mvarchar_icase_cmp 
+--------------------+--------------------+--------------------
+                 -1 |                  1 |                 -1
+(1 row)
+
diff --git a/contrib/mchar/expected/mvarchar.out b/contrib/mchar/expected/mvarchar.out
new file mode 100644
index 00000000000..5c866b43e71
--- /dev/null
+++ b/contrib/mchar/expected/mvarchar.out
@@ -0,0 +1,363 @@
+-- I/O tests
+select '1'::mvarchar;
+ mvarchar 
+----------
+ 1
+(1 row)
+
+select '2  '::mvarchar;
+ mvarchar 
+----------
+ 2  
+(1 row)
+
+select '10          '::mvarchar;
+   mvarchar   
+--------------
+ 10          
+(1 row)
+
+select '1'::mvarchar(2);
+ mvarchar 
+----------
+ 1
+(1 row)
+
+select '2 '::mvarchar(2);
+ mvarchar 
+----------
+ 2 
+(1 row)
+
+select '3  '::mvarchar(2);
+ mvarchar 
+----------
+ 3 
+(1 row)
+
+select '10          '::mvarchar(2);
+ mvarchar 
+----------
+ 10
+(1 row)
+
+select '                  '::mvarchar(10); 
+  mvarchar  
+------------
+           
+(1 row)
+
+select '                  '::mvarchar; 
+      mvarchar      
+--------------------
+                   
+(1 row)
+
+-- operations & functions
+select length('1'::mvarchar);
+ length 
+--------
+      1
+(1 row)
+
+select length('2  '::mvarchar);
+ length 
+--------
+      1
+(1 row)
+
+select length('10          '::mvarchar);
+ length 
+--------
+      2
+(1 row)
+
+select length('1'::mvarchar(2));
+ length 
+--------
+      1
+(1 row)
+
+select length('2 '::mvarchar(2));
+ length 
+--------
+      1
+(1 row)
+
+select length('3  '::mvarchar(2));
+ length 
+--------
+      1
+(1 row)
+
+select length('10          '::mvarchar(2));
+ length 
+--------
+      2
+(1 row)
+
+select length('                  '::mvarchar(10)); 
+ length 
+--------
+      0
+(1 row)
+
+select length('                  '::mvarchar); 
+ length 
+--------
+      0
+(1 row)
+
+select 'asd'::mvarchar(10) || '>'::mvarchar(10);
+ ?column? 
+----------
+ asd>
+(1 row)
+
+select length('asd'::mvarchar(10) || '>'::mvarchar(10));
+ length 
+--------
+      4
+(1 row)
+
+select 'asd'::mvarchar(2)  || '>'::mvarchar(10);
+ ?column? 
+----------
+ as>
+(1 row)
+
+select length('asd'::mvarchar(2) || '>'::mvarchar(10));
+ length 
+--------
+      3
+(1 row)
+
+-- Comparisons
+select 'asdf'::mvarchar = 'aSdf'::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar = 'aSdf '::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar < 'aSdf'::mvarchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar < 'aSdf '::mvarchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar <= 'aSdf'::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar <= 'aSdf '::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar >= 'aSdf'::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar >= 'aSdf '::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5);
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar > 'aSdf'::mvarchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar > 'aSdf '::mvarchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5);
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6);
+ ?column? 
+----------
+ f
+(1 row)
+
+select max(vch) from chvch;
+    max     
+------------
+ One space 
+(1 row)
+
+select min(vch) from chvch;
+   min    
+----------
+ 1 space 
+(1 row)
+
+select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890";
+ 34567890 
+----------
+ f
+(1 row)
+
+select substr('1234567890'::mvarchar, 4, 3) = '456' as "456";
+ 456 
+-----
+ t
+(1 row)
+
+select lower('asdfASDF'::mvarchar);
+  lower   
+----------
+ asdfasdf
+(1 row)
+
+select upper('asdfASDF'::mvarchar);
+  upper   
+----------
+ ASDFASDF
+(1 row)
+
+select 'asd'::mvarchar == 'aSd'::mvarchar;
+ ?column? 
+----------
+ t
+(1 row)
+
+select 'asd'::mvarchar == 'aCd'::mvarchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select 'asd'::mvarchar == NULL;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL == 'aCd'::mvarchar;
+ ?column? 
+----------
+ f
+(1 row)
+
+select NULL::mvarchar == NULL;
+ ?column? 
+----------
+ t
+(1 row)
+
diff --git a/contrib/mchar/mchar--2.0--2.1.sql b/contrib/mchar/mchar--2.0--2.1.sql
new file mode 100644
index 00000000000..a794772f376
--- /dev/null
+++ b/contrib/mchar/mchar--2.0--2.1.sql
@@ -0,0 +1,2 @@
+ALTER FUNCTION  mchar_like(mchar, mvarchar) SUPPORT textlike_support;
+ALTER FUNCTION  mvarchar_like(mvarchar, mvarchar) SUPPORT textlike_support;
diff --git a/contrib/mchar/mchar--2.0.1--2.1.sql b/contrib/mchar/mchar--2.0.1--2.1.sql
new file mode 100644
index 00000000000..a794772f376
--- /dev/null
+++ b/contrib/mchar/mchar--2.0.1--2.1.sql
@@ -0,0 +1,2 @@
+ALTER FUNCTION  mchar_like(mchar, mvarchar) SUPPORT textlike_support;
+ALTER FUNCTION  mvarchar_like(mvarchar, mvarchar) SUPPORT textlike_support;
diff --git a/contrib/mchar/mchar--2.1--2.2.sql b/contrib/mchar/mchar--2.1--2.2.sql
new file mode 100644
index 00000000000..98689671499
--- /dev/null
+++ b/contrib/mchar/mchar--2.1--2.2.sql
@@ -0,0 +1,20 @@
+CREATE FUNCTION similar_to_escape(mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mchar, mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mvarchar, mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
diff --git a/contrib/mchar/mchar--2.1.1--2.2.sql b/contrib/mchar/mchar--2.1.1--2.2.sql
new file mode 100644
index 00000000000..98689671499
--- /dev/null
+++ b/contrib/mchar/mchar--2.1.1--2.2.sql
@@ -0,0 +1,20 @@
+CREATE FUNCTION similar_to_escape(mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mchar, mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mvarchar, mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
diff --git a/contrib/mchar/mchar--2.2--2.2.1.sql b/contrib/mchar/mchar--2.2--2.2.1.sql
new file mode 100644
index 00000000000..e663aa24a5d
--- /dev/null
+++ b/contrib/mchar/mchar--2.2--2.2.1.sql
@@ -0,0 +1,10 @@
+CREATE OR REPLACE FUNCTION mvarchar_support(internal)
+	RETURNS internal
+	AS 'MODULE_PATHNAME'
+	LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT
+	PARALLEL SAFE;
+	
+ALTER FUNCTION mvarchar(mvarchar, integer, boolean)
+	SUPPORT mvarchar_support;
+
+
diff --git a/contrib/mchar/mchar--2.2.1.sql b/contrib/mchar/mchar--2.2.1.sql
new file mode 100644
index 00000000000..2f975b64edd
--- /dev/null
+++ b/contrib/mchar/mchar--2.2.1.sql
@@ -0,0 +1,1352 @@
+\echo Use "CREATE EXTENSION mchar" to load this file. \quit
+
+-- I/O functions
+
+CREATE FUNCTION mchartypmod_in(cstring[])
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchartypmod_out(int4)
+RETURNS cstring
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_in(cstring)
+RETURNS mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_out(mchar)
+RETURNS cstring
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_send(mchar)
+RETURNS bytea
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_recv(internal)
+RETURNS mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE TYPE mchar (
+    INTERNALLENGTH = -1,
+	INPUT = mchar_in,
+	OUTPUT = mchar_out,
+	TYPMOD_IN	= mchartypmod_in,
+	TYPMOD_OUT	= mchartypmod_out,
+	RECEIVE	= mchar_recv,
+	SEND = mchar_send,
+	STORAGE = extended
+);
+
+CREATE FUNCTION mchar(mchar, integer, boolean)
+RETURNS mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE CAST (mchar as mchar)
+WITH FUNCTION mchar(mchar, integer, boolean) as IMPLICIT;
+
+CREATE FUNCTION mvarchar_in(cstring)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_out(mvarchar)
+RETURNS cstring
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_send(mvarchar)
+RETURNS bytea
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_recv(internal)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE TYPE mvarchar (
+    INTERNALLENGTH = -1,
+	INPUT = mvarchar_in,
+	OUTPUT = mvarchar_out,
+	TYPMOD_IN	= mchartypmod_in,
+	TYPMOD_OUT	= mchartypmod_out,
+	RECEIVE	= mvarchar_recv,
+	SEND = mvarchar_send,
+	STORAGE = extended
+);
+
+CREATE FUNCTION mvarchar_support(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT
+PARALLEL SAFE;
+
+CREATE FUNCTION mvarchar(mvarchar, integer, boolean)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT
+SUPPORT mvarchar_support;
+
+CREATE CAST (mvarchar as mvarchar)
+WITH FUNCTION mvarchar(mvarchar, integer, boolean) as IMPLICIT;
+
+--Operations and functions
+
+CREATE FUNCTION length(mchar)
+RETURNS int4
+AS 'MODULE_PATHNAME', 'mchar_length'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION upper(mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_upper'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION lower(mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_lower'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_hash(mchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_concat(mchar, mchar)
+RETURNS	mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR || (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	= 	mchar_concat
+);
+
+CREATE FUNCTION mchar_like(mchar, mvarchar)
+RETURNS bool
+SUPPORT textlike_support
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_notlike(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR ~~ (
+	LEFTARG     =   mchar,
+	RIGHTARG	= 	mvarchar,
+	PROCEDURE	= 	mchar_like,
+	RESTRICT	= 	likesel,
+	JOIN		= 	likejoinsel,
+	NEGATOR		= 	'!~~'
+);
+
+CREATE OPERATOR !~~ (
+	LEFTARG     =   mchar,
+	RIGHTARG	= 	mvarchar,
+	PROCEDURE	= 	mchar_notlike,
+	RESTRICT	= 	nlikesel,
+	JOIN		= 	nlikejoinsel,
+	NEGATOR		= 	'~~'
+);
+
+CREATE FUNCTION mchar_regexeq(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_regexne(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR ~ (
+	LEFTARG     =   mchar,
+	RIGHTARG	= 	mchar,
+	PROCEDURE	= 	mchar_regexeq,
+	RESTRICT	= 	regexeqsel,
+	JOIN		= 	regexeqjoinsel,
+	NEGATOR		= 	'!~'
+);
+
+CREATE OPERATOR !~ (
+	LEFTARG     =   mchar,
+	RIGHTARG	= 	mchar,
+	PROCEDURE	= 	mchar_regexne,
+	RESTRICT	= 	regexnesel,
+	JOIN		= 	regexnejoinsel,
+	NEGATOR		= 	'~'
+);
+
+CREATE FUNCTION similar_escape(mchar, mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION length(mvarchar)
+RETURNS int4
+AS 'MODULE_PATHNAME', 'mvarchar_length'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION upper(mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_upper'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION lower(mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_lower'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_hash(mvarchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_concat(mvarchar, mvarchar)
+RETURNS	mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR || (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	= 	mvarchar_concat
+);
+
+CREATE FUNCTION mvarchar_like(mvarchar, mvarchar)
+RETURNS bool
+SUPPORT textlike_support
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION like_escape(mvarchar, mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_like_escape'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_notlike(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR ~~ (
+	LEFTARG     =   mvarchar,
+	RIGHTARG	= 	mvarchar,
+	PROCEDURE	= 	mvarchar_like,
+	RESTRICT	= 	likesel,
+	JOIN		= 	likejoinsel,
+	NEGATOR		= 	'!~~'
+);
+
+CREATE OPERATOR !~~ (
+	LEFTARG     =   mvarchar,
+	RIGHTARG	= 	mvarchar,
+	PROCEDURE	= 	mvarchar_notlike,
+	RESTRICT	= 	nlikesel,
+	JOIN		= 	nlikejoinsel,
+	NEGATOR		= 	'~~'
+);
+
+CREATE FUNCTION mvarchar_regexeq(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_regexne(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR ~ (
+	LEFTARG     =   mvarchar,
+	RIGHTARG	= 	mvarchar,
+	PROCEDURE	= 	mvarchar_regexeq,
+	RESTRICT	= 	regexeqsel,
+	JOIN		= 	regexeqjoinsel,
+	NEGATOR		= 	'!~'
+);
+
+CREATE OPERATOR !~ (
+	LEFTARG     =   mvarchar,
+	RIGHTARG	= 	mvarchar,
+	PROCEDURE	= 	mvarchar_regexne,
+	RESTRICT	= 	regexnesel,
+	JOIN		= 	regexnejoinsel,
+	NEGATOR		= 	'~'
+);
+
+CREATE FUNCTION similar_escape(mvarchar, mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION substr (mchar, int4)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_substring_no_len'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION substr (mchar, int4, int4)
+RETURNS mchar
+AS 'MODULE_PATHNAME', 'mchar_substring'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION substr (mvarchar, int4)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_substring_no_len'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION substr (mvarchar, int4, int4)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME', 'mvarchar_substring'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+-- Comparing
+--    MCHAR
+
+CREATE FUNCTION mchar_icase_cmp(mchar, mchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_icase_eq(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_icase_ne(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_icase_lt(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_icase_le(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_icase_gt(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_icase_ge(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR < (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_icase_lt,
+	COMMUTATOR	= 	'>',
+	NEGATOR		= 	'>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR > (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_icase_gt,
+	COMMUTATOR	= 	'<',
+	NEGATOR		= 	'<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_icase_le,
+	COMMUTATOR	= 	'>=',
+	NEGATOR		= 	'>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_icase_ge,
+	COMMUTATOR	= 	'<=',
+	NEGATOR		= 	'<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_icase_eq,
+	COMMUTATOR	= 	'=',
+	NEGATOR		= 	'<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'<',
+	SORT2 		= 	'<',
+	HASHES
+);
+
+CREATE OPERATOR <> (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_icase_ne,
+	COMMUTATOR	= 	'<>',
+	NEGATOR		= 	'=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+CREATE FUNCTION mchar_case_cmp(mchar, mchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_case_eq(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_case_ne(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_case_lt(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_case_le(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_case_gt(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_case_ge(mchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR &< (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_case_lt,
+	COMMUTATOR	= 	'&>',
+	NEGATOR		= 	'&>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &> (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_case_gt,
+	COMMUTATOR	= 	'&<',
+	NEGATOR		= 	'&<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &<= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_case_le,
+	COMMUTATOR	= 	'&>=',
+	NEGATOR		= 	'&>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &>= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_case_ge,
+	COMMUTATOR	= 	'&<=',
+	NEGATOR		= 	'&<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_case_eq,
+	COMMUTATOR	= 	'&=',
+	NEGATOR		= 	'&<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'&<',
+	SORT2 		= 	'&<'
+);
+
+CREATE OPERATOR &<> (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mchar_case_ne,
+	COMMUTATOR	= 	'&<>',
+	NEGATOR		= 	'&=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+--MVARCHAR
+
+CREATE FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_icase_eq(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_icase_ne(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_icase_lt(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_icase_le(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_icase_gt(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_icase_ge(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR < (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_icase_lt,
+	COMMUTATOR	= 	'>',
+	NEGATOR		= 	'>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR > (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_icase_gt,
+	COMMUTATOR	= 	'<',
+	NEGATOR		= 	'<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_icase_le,
+	COMMUTATOR	= 	'>=',
+	NEGATOR		= 	'>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_icase_ge,
+	COMMUTATOR	= 	'<=',
+	NEGATOR		= 	'<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_icase_eq,
+	COMMUTATOR	= 	'=',
+	NEGATOR		= 	'<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'<',
+	SORT2 		= 	'<',
+	HASHES
+);
+
+CREATE OPERATOR <> (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_icase_ne,
+	COMMUTATOR	= 	'<>',
+	NEGATOR		= 	'=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+CREATE FUNCTION mvarchar_case_cmp(mvarchar, mvarchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_case_eq(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_case_ne(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_case_lt(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_case_le(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_case_gt(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mvarchar_case_ge(mvarchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR &< (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_case_lt,
+	COMMUTATOR	= 	'&>',
+	NEGATOR		= 	'&>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &> (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_case_gt,
+	COMMUTATOR	= 	'&<',
+	NEGATOR		= 	'&<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &<= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_case_le,
+	COMMUTATOR	= 	'&>=',
+	NEGATOR		= 	'&>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &>= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_case_ge,
+	COMMUTATOR	= 	'&<=',
+	NEGATOR		= 	'&<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_case_eq,
+	COMMUTATOR	= 	'&=',
+	NEGATOR		= 	'&<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'&<',
+	SORT2 		= 	'&<'
+);
+
+CREATE OPERATOR &<> (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mvarchar_case_ne,
+	COMMUTATOR	= 	'&<>',
+	NEGATOR		= 	'&=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+--    MCHAR <> MVARCHAR
+
+CREATE FUNCTION mc_mv_icase_cmp(mchar, mvarchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_icase_eq(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_icase_ne(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_icase_lt(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_icase_le(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_icase_gt(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_icase_ge(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR < (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_icase_lt,
+	COMMUTATOR	= 	'>',
+	NEGATOR		= 	'>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR > (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_icase_gt,
+	COMMUTATOR	= 	'<',
+	NEGATOR		= 	'<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_icase_le,
+	COMMUTATOR	= 	'>=',
+	NEGATOR		= 	'>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_icase_ge,
+	COMMUTATOR	= 	'<=',
+	NEGATOR		= 	'<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_icase_eq,
+	COMMUTATOR	= 	'=',
+	NEGATOR		= 	'<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'<',
+	SORT2 		= 	'<'
+);
+
+CREATE OPERATOR <> (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_icase_ne,
+	COMMUTATOR	= 	'<>',
+	NEGATOR		= 	'=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+CREATE FUNCTION mc_mv_case_cmp(mchar, mvarchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_case_eq(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_case_ne(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_case_lt(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_case_le(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_case_gt(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mc_mv_case_ge(mchar, mvarchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR &< (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_case_lt,
+	COMMUTATOR	= 	'&>',
+	NEGATOR		= 	'&>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &> (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_case_gt,
+	COMMUTATOR	= 	'&<',
+	NEGATOR		= 	'&<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &<= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_case_le,
+	COMMUTATOR	= 	'&>=',
+	NEGATOR		= 	'&>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &>= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_case_ge,
+	COMMUTATOR	= 	'&<=',
+	NEGATOR		= 	'&<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &= (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_case_eq,
+	COMMUTATOR	= 	'&=',
+	NEGATOR		= 	'&<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'&<',
+	SORT2 		= 	'&<'
+);
+
+CREATE OPERATOR &<> (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	=	mc_mv_case_ne,
+	COMMUTATOR	= 	'&<>',
+	NEGATOR		= 	'&=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+--    MVARCHAR <> MCHAR
+
+CREATE FUNCTION mv_mc_icase_cmp(mvarchar, mchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_icase_eq(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_icase_ne(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_icase_lt(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_icase_le(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_icase_gt(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_icase_ge(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR < (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_icase_lt,
+	COMMUTATOR	= 	'>',
+	NEGATOR		= 	'>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR > (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_icase_gt,
+	COMMUTATOR	= 	'<',
+	NEGATOR		= 	'<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR <= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_icase_le,
+	COMMUTATOR	= 	'>=',
+	NEGATOR		= 	'>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR >= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_icase_ge,
+	COMMUTATOR	= 	'<=',
+	NEGATOR		= 	'<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR = (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_icase_eq,
+	COMMUTATOR	= 	'=',
+	NEGATOR		= 	'<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'<',
+	SORT2 		= 	'<'
+);
+
+CREATE OPERATOR <> (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_icase_ne,
+	COMMUTATOR	= 	'<>',
+	NEGATOR		= 	'=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+CREATE FUNCTION mv_mc_case_cmp(mvarchar, mchar)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_case_eq(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_case_ne(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_case_lt(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_case_le(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_case_gt(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mv_mc_case_ge(mvarchar, mchar)
+RETURNS bool
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+
+CREATE OPERATOR &< (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_case_lt,
+	COMMUTATOR	= 	'&>',
+	NEGATOR		= 	'&>=',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &> (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_case_gt,
+	COMMUTATOR	= 	'&<',
+	NEGATOR		= 	'&<=',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &<= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_case_le,
+	COMMUTATOR	= 	'&>=',
+	NEGATOR		= 	'&>',
+	RESTRICT	= 	scalarltsel,
+	JOIN		= 	scalarltjoinsel
+);
+
+CREATE OPERATOR &>= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_case_ge,
+	COMMUTATOR	= 	'&<=',
+	NEGATOR		= 	'&<',
+	RESTRICT	= 	scalargtsel,
+	JOIN		= 	scalargtjoinsel
+);
+
+CREATE OPERATOR &= (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_case_eq,
+	COMMUTATOR	= 	'&=',
+	NEGATOR		= 	'&<>',
+	RESTRICT	= 	eqsel,
+	JOIN		= 	eqjoinsel,
+	SORT1 		= 	'&<',
+	SORT2 		= 	'&<'
+);
+
+CREATE OPERATOR &<> (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	=	mv_mc_case_ne,
+	COMMUTATOR	= 	'&<>',
+	NEGATOR		= 	'&=',
+	RESTRICT	= 	neqsel,
+	JOIN		= 	neqjoinsel
+);
+
+-- MCHAR - VARCHAR operations
+
+CREATE FUNCTION mchar_mvarchar_concat(mchar, mvarchar)
+RETURNS	mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR || (
+	LEFTARG		=	mchar,
+	RIGHTARG	=	mvarchar,
+	PROCEDURE	= 	mchar_mvarchar_concat
+);
+
+CREATE FUNCTION mvarchar_mchar_concat(mvarchar, mchar)
+RETURNS	mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OPERATOR || (
+	LEFTARG		=	mvarchar,
+	RIGHTARG	=	mchar,
+	PROCEDURE	= 	mvarchar_mchar_concat
+);
+
+CREATE FUNCTION mvarchar_mchar(mvarchar, integer, boolean)
+RETURNS mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE CAST (mvarchar as mchar)
+WITH FUNCTION mvarchar_mchar(mvarchar, integer, boolean) as IMPLICIT;
+
+CREATE FUNCTION mchar_mvarchar(mchar, integer, boolean)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE CAST (mchar as mvarchar)
+WITH FUNCTION mchar_mvarchar(mchar, integer, boolean) as IMPLICIT;
+
+-- Aggregates
+
+CREATE FUNCTION mchar_larger(mchar, mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE AGGREGATE max (
+	BASETYPE	= 	mchar,
+	SFUNC 		= 	mchar_larger,
+	STYPE		= 	mchar,
+	SORTOP		= 	'>'
+);
+
+CREATE FUNCTION mchar_smaller(mchar, mchar)
+RETURNS mchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE AGGREGATE min (
+	BASETYPE	= 	mchar,
+	SFUNC 		= 	mchar_smaller,
+	STYPE		= 	mchar,
+	SORTOP		= 	'<'
+);
+
+CREATE FUNCTION mvarchar_larger(mvarchar, mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE AGGREGATE max (
+	BASETYPE	= 	mvarchar,
+	SFUNC 		= 	mvarchar_larger,
+	STYPE		= 	mvarchar,
+	SORTOP		= 	'>'
+);
+
+CREATE FUNCTION mvarchar_smaller(mvarchar, mvarchar)
+RETURNS mvarchar
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE AGGREGATE min (
+	BASETYPE	= 	mvarchar,
+	SFUNC 		= 	mvarchar_smaller,
+	STYPE		= 	mvarchar,
+	SORTOP		= 	'<'
+);
+
+-- B-tree support
+CREATE OPERATOR FAMILY icase_ops USING btree;
+CREATE OPERATOR FAMILY case_ops USING btree;
+
+CREATE OPERATOR CLASS mchar_icase_ops
+DEFAULT FOR TYPE mchar USING btree FAMILY icase_ops AS
+        OPERATOR        1       <  ,
+		OPERATOR        2       <= ,
+		OPERATOR        3       =  ,
+		OPERATOR        4       >= ,
+		OPERATOR        5       >  ,
+		FUNCTION        1       mchar_icase_cmp(mchar, mchar),
+        OPERATOR        1       <  (mchar, mvarchar),
+		OPERATOR        2       <= (mchar, mvarchar),
+		OPERATOR        3       =  (mchar, mvarchar),
+		OPERATOR        4       >= (mchar, mvarchar),
+		OPERATOR        5       >  (mchar, mvarchar),
+		FUNCTION        1       mc_mv_icase_cmp(mchar, mvarchar);
+
+CREATE OPERATOR CLASS mchar_case_ops
+FOR TYPE mchar USING btree FAMILY case_ops AS
+        OPERATOR        1       &<  ,
+		OPERATOR        2       &<= ,
+		OPERATOR        3       &=  ,
+		OPERATOR        4       &>= ,
+		OPERATOR        5       &>  ,
+		FUNCTION        1       mchar_case_cmp(mchar, mchar),
+        OPERATOR        1       &<  (mchar, mvarchar),
+		OPERATOR        2       &<= (mchar, mvarchar),
+		OPERATOR        3       &=  (mchar, mvarchar),
+		OPERATOR        4       &>= (mchar, mvarchar),
+		OPERATOR        5       &>  (mchar, mvarchar),
+		FUNCTION        1       mc_mv_case_cmp(mchar, mvarchar);
+
+CREATE OPERATOR CLASS mchar_icase_ops
+DEFAULT FOR TYPE mchar USING hash AS
+		OPERATOR        1       =  ,
+		FUNCTION        1       mchar_hash(mchar);
+
+CREATE OPERATOR CLASS mvarchar_icase_ops
+DEFAULT FOR TYPE mvarchar USING btree FAMILY icase_ops AS
+        OPERATOR        1       <  ,
+		OPERATOR        2       <= ,
+		OPERATOR        3       =  ,
+		OPERATOR        4       >= ,
+		OPERATOR        5       >  ,
+		FUNCTION        1       mvarchar_icase_cmp(mvarchar, mvarchar),
+        OPERATOR        1       <  (mvarchar, mchar),
+		OPERATOR        2       <= (mvarchar, mchar),
+		OPERATOR        3       =  (mvarchar, mchar),
+		OPERATOR        4       >= (mvarchar, mchar),
+		OPERATOR        5       >  (mvarchar, mchar),
+		FUNCTION        1       mv_mc_icase_cmp(mvarchar, mchar);
+
+CREATE OPERATOR CLASS mvarchar_case_ops
+FOR TYPE mvarchar USING btree FAMILY case_ops AS
+        OPERATOR        1       &<  ,
+		OPERATOR        2       &<= ,
+		OPERATOR        3       &=  ,
+		OPERATOR        4       &>= ,
+		OPERATOR        5       &>  ,
+		FUNCTION        1       mvarchar_case_cmp(mvarchar, mvarchar),
+        OPERATOR        1       &<  (mvarchar, mchar),
+		OPERATOR        2       &<= (mvarchar, mchar),
+		OPERATOR        3       &=  (mvarchar, mchar),
+		OPERATOR        4       &>= (mvarchar, mchar),
+		OPERATOR        5       &>  (mvarchar, mchar),
+		FUNCTION        1       mv_mc_case_cmp(mvarchar, mchar);
+
+CREATE OPERATOR CLASS mvarchar_icase_ops
+DEFAULT FOR TYPE mvarchar USING hash AS
+		OPERATOR        1       =  ,
+		FUNCTION        1       mvarchar_hash(mvarchar);
+
+
+-- Index support for LIKE
+
+CREATE FUNCTION mchar_pattern_fixed_prefix(internal, internal, internal)
+RETURNS int4
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE FUNCTION mchar_greaterstring(internal)
+RETURNS internal
+AS 'MODULE_PATHNAME'
+LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT;
+
+CREATE OR REPLACE FUNCTION isfulleq_mchar(mchar, mchar)
+RETURNS bool AS 'MODULE_PATHNAME'
+LANGUAGE C CALLED ON NULL INPUT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION fullhash_mchar(mchar)
+RETURNS int4 AS 'MODULE_PATHNAME'
+LANGUAGE C CALLED ON NULL INPUT IMMUTABLE;
+
+
+CREATE OPERATOR == (
+    LEFTARG     = mchar,
+    RIGHTARG    = mchar,
+    PROCEDURE   = isfulleq_mchar,
+    COMMUTATOR  = '==',
+    RESTRICT    = eqsel,
+    JOIN        = eqjoinsel,
+    HASHES
+);
+
+CREATE OPERATOR CLASS mchar_fill_ops
+ FOR TYPE mchar USING hash AS
+    OPERATOR    1   ==,
+    FUNCTION    1   fullhash_mchar(mchar);
+
+CREATE OR REPLACE FUNCTION isfulleq_mvarchar(mvarchar, mvarchar)
+RETURNS bool AS 'MODULE_PATHNAME'
+LANGUAGE C CALLED ON NULL INPUT IMMUTABLE;
+
+CREATE OR REPLACE FUNCTION fullhash_mvarchar(mvarchar)
+RETURNS int4 AS 'MODULE_PATHNAME'
+LANGUAGE C CALLED ON NULL INPUT IMMUTABLE;
+
+
+CREATE OPERATOR == (
+    LEFTARG     = mvarchar,
+    RIGHTARG    = mvarchar,
+    PROCEDURE   = isfulleq_mvarchar,
+    COMMUTATOR  = '==',
+    RESTRICT    = eqsel,
+    JOIN        = eqjoinsel,
+    HASHES
+);
+
+CREATE OPERATOR CLASS mvarchar_fill_ops
+ FOR TYPE mvarchar USING hash AS
+    OPERATOR    1   ==,
+    FUNCTION    1   fullhash_mvarchar(mvarchar);
+
+CREATE FUNCTION similar_to_escape(mchar)
+	RETURNS mchar
+	AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+	LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mchar, mchar)
+	RETURNS mchar
+	AS 'MODULE_PATHNAME', 'mchar_similar_escape'
+	LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mvarchar)
+	RETURNS mvarchar
+	AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+	LANGUAGE C IMMUTABLE;
+
+CREATE FUNCTION similar_to_escape(mvarchar, mvarchar)
+	RETURNS mvarchar
+	AS 'MODULE_PATHNAME', 'mvarchar_similar_escape'
+	LANGUAGE C IMMUTABLE;
+
diff --git a/contrib/mchar/mchar--unpackaged--2.0.sql b/contrib/mchar/mchar--unpackaged--2.0.sql
new file mode 100644
index 00000000000..1acc4ccec1e
--- /dev/null
+++ b/contrib/mchar/mchar--unpackaged--2.0.sql
@@ -0,0 +1,404 @@
+\echo Use "CREATE EXTENSION mchar FROM unpackaged" to load this file. \quit
+
+-- I/O functions
+
+ALTER EXTENSION mchar ADD FUNCTION mchartypmod_in(cstring[]);
+
+ALTER EXTENSION mchar ADD FUNCTION mchartypmod_out(int4);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_in(cstring);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_out(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_send(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_recv(internal);
+
+ALTER EXTENSION mchar ADD TYPE mchar;
+
+ALTER EXTENSION mchar ADD FUNCTION mchar(mchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mchar as mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_in(cstring);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_out(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_send(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_recv(internal);
+
+ALTER EXTENSION mchar ADD TYPE mvarchar;
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar(mvarchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mvarchar as mvarchar);
+
+--Operations and functions
+
+ALTER EXTENSION mchar ADD FUNCTION length(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION upper(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION lower(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_hash(mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_concat(mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_like(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_notlike(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~~ (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~~ (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_regexeq(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_regexne(mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~ (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~ (mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION similar_escape(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION length(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION upper(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION lower(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_hash(mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_concat(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_like(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION like_escape(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_notlike(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexeq(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_regexne(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR ~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR !~ (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION similar_escape(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mchar, int4, int4);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4);
+
+ALTER EXTENSION mchar ADD FUNCTION substr (mvarchar, int4, int4);
+
+-- Comparing
+--    MCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_cmp(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_eq(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ne(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_lt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_le(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_gt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_icase_ge(mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mchar); 
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_cmp(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_eq(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_ne(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_lt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_le(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_gt(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_case_ge(mchar, mchar);
+
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mchar);
+
+--MVARCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_cmp(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_eq(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ne(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_lt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_le(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_gt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_icase_ge(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_cmp(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_eq(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ne(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_lt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_le(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_gt(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_case_ge(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mvarchar);
+
+--    MCHAR <> MVARCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_cmp(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_eq(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ne(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_lt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_le(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_gt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_icase_ge(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_cmp(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_eq(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ne(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_lt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_le(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_gt(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mc_mv_case_ge(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mchar, mvarchar);
+
+--    MVARCHAR <> MCHAR
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_cmp(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_eq(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ne(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_lt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_le(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_gt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_icase_ge(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR < (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR > (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR >= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR = (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR <> (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_cmp(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_eq(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ne(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_lt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_le(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_gt(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mv_mc_case_ge(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &< (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &> (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &>= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &= (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR &<> (mvarchar, mchar);
+
+-- MCHAR - VARCHAR operations
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar_concat(mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar_concat(mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR || (mvarchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_mchar(mvarchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mvarchar as mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_mvarchar(mchar, integer, boolean);
+
+ALTER EXTENSION mchar ADD CAST (mchar as mvarchar);
+
+-- Aggregates
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_larger(mchar, mchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE max (mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_smaller(mchar, mchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE min (mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_larger(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE max (mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION mvarchar_smaller(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD AGGREGATE min (mvarchar);
+
+-- B-tree support
+ALTER EXTENSION mchar ADD OPERATOR FAMILY icase_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR FAMILY case_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_case_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_icase_ops USING hash;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_case_ops USING btree;
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_icase_ops USING hash;
+
+
+-- Index support for LIKE
+
+--mchar_pattern_fixed_prefix could be with wrong number of arguments
+ALTER EXTENSION mchar ADD FUNCTION mchar_pattern_fixed_prefix;
+
+ALTER EXTENSION mchar ADD FUNCTION mchar_greaterstring(internal);
+
+ALTER EXTENSION mchar ADD FUNCTION isfulleq_mchar(mchar, mchar);
+
+ALTER EXTENSION mchar ADD FUNCTION fullhash_mchar(mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR == (mchar, mchar);
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mchar_fill_ops USING hash;
+
+ALTER EXTENSION mchar ADD FUNCTION isfulleq_mvarchar(mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD FUNCTION fullhash_mvarchar(mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR == (mvarchar, mvarchar);
+
+ALTER EXTENSION mchar ADD OPERATOR CLASS mvarchar_fill_ops USING hash;
+
+
diff --git a/contrib/mchar/mchar.control b/contrib/mchar/mchar.control
new file mode 100644
index 00000000000..02668a5d617
--- /dev/null
+++ b/contrib/mchar/mchar.control
@@ -0,0 +1,6 @@
+# mchar extension
+comment = 'SQL Server text type'
+default_version = '2.2.1'
+module_pathname = '$libdir/mchar'
+relocatable = true
+trusted = true
diff --git a/contrib/mchar/mchar.h b/contrib/mchar/mchar.h
new file mode 100644
index 00000000000..a88a0e1eb76
--- /dev/null
+++ b/contrib/mchar/mchar.h
@@ -0,0 +1,63 @@
+#ifndef __MCHAR_H__
+#define __MCHAR_H__
+
+#include "postgres.h"
+#include "mb/pg_wchar.h"
+#include "utils/builtins.h"
+#include "unicode/uchar.h"
+#include "unicode/ustring.h"
+
+typedef struct {
+	int32	len;
+	int32 	typmod;
+	UChar	data[1];
+} MChar;
+
+#define MCHARHDRSZ	offsetof(MChar, data)	
+#define MCHARLENGTH(m)	( VARSIZE(m)-MCHARHDRSZ )
+#define UCHARLENGTH(m)  ( MCHARLENGTH(m)/sizeof(UChar) )	
+
+#define DatumGetMChar(m)	((MChar*)DatumGetPointer(m))
+#define MCharGetDatum(m)	PointerGetDatum(m)
+
+#define	PG_GETARG_MCHAR(n)	DatumGetMChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n)))
+#define	PG_GETARG_MCHAR_COPY(n)	DatumGetMChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n)))
+
+#define PG_RETURN_MCHAR(m)	PG_RETURN_POINTER(m)
+
+typedef struct {
+	int32	len;
+	UChar	data[1];
+} MVarChar;
+
+#define MVARCHARHDRSZ 	offsetof(MVarChar, data)
+#define MVARCHARLENGTH(m)  ( VARSIZE(m)-MVARCHARHDRSZ )
+#define UVARCHARLENGTH(m)  ( MVARCHARLENGTH(m)/sizeof(UChar) )
+
+#define DatumGetMVarChar(m)	((MVarChar*)DatumGetPointer(m))
+#define MVarCharGetDatum(m)	PointerGetDatum(m)
+
+#define	PG_GETARG_MVARCHAR(n)	DatumGetMVarChar(PG_DETOAST_DATUM(PG_GETARG_DATUM(n)))
+#define	PG_GETARG_MVARCHAR_COPY(n)	DatumGetMVarChar(PG_DETOAST_DATUM_COPY(PG_GETARG_DATUM(n)))
+
+#define PG_RETURN_MVARCHAR(m)	PG_RETURN_POINTER(m)
+
+
+int Char2UChar(const char * src, int srclen, UChar *dst); 
+int UChar2Char(const UChar * src, int srclen, char *dst); 
+int UChar2Wchar(UChar * src, int srclen, pg_wchar *dst);
+int UCharCompare(UChar * a, int alen, UChar *b, int blen); 
+int UCharCaseCompare(UChar * a, int alen, UChar *b, int blen); 
+
+void FillWhiteSpace( UChar *dst, int n );
+
+int lengthWithoutSpaceVarChar(MVarChar *m);
+int lengthWithoutSpaceChar(MChar *m);
+
+extern Datum       mchar_hash(PG_FUNCTION_ARGS);
+extern Datum       mvarchar_hash(PG_FUNCTION_ARGS);
+
+int m_isspace(UChar c); /* is == ' ' */
+
+Datum hash_uchar( UChar *s, int len );
+#endif
diff --git a/contrib/mchar/mchar_io.c b/contrib/mchar/mchar_io.c
new file mode 100644
index 00000000000..d6c2ac7d393
--- /dev/null
+++ b/contrib/mchar/mchar_io.c
@@ -0,0 +1,403 @@
+#include "mchar.h"
+#include "mb/pg_wchar.h"
+#include "fmgr.h"
+#include "libpq/pqformat.h"
+#include "nodes/nodeFuncs.h"
+#include "nodes/supportnodes.h"
+#include <utils/array.h>
+
+#ifdef PG_MODULE_MAGIC
+PG_MODULE_MAGIC;
+#endif
+
+PG_FUNCTION_INFO_V1(mchar_in);
+Datum       mchar_in(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(mchar_out);
+Datum       mchar_out(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(mchar);
+Datum       mchar(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(mvarchar_in);
+Datum       mvarchar_in(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(mvarchar_out);
+Datum       mvarchar_out(PG_FUNCTION_ARGS);
+PG_FUNCTION_INFO_V1(mvarchar);
+Datum       mvarchar(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(mvarchar_support);
+Datum		varchar_support(PG_FUNCTION_ARGS);
+
+PG_FUNCTION_INFO_V1(mchartypmod_in);
+Datum mchartypmod_in(PG_FUNCTION_ARGS);
+Datum 
+mchartypmod_in(PG_FUNCTION_ARGS) {
+	ArrayType  *ta = PG_GETARG_ARRAYTYPE_P(0);
+	int32      *tl;
+	int         n;
+
+	tl = ArrayGetIntegerTypmods(ta, &n);
+
+	if (n != 1)
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+				errmsg("invalid type modifier")));
+	if (*tl < 1)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					errmsg("length for type mchar/mvarchar must be at least 1")));
+
+	return *tl;
+}
+
+PG_FUNCTION_INFO_V1(mchartypmod_out);
+Datum mchartypmod_out(PG_FUNCTION_ARGS);
+Datum 
+mchartypmod_out(PG_FUNCTION_ARGS) {
+	int32       typmod = PG_GETARG_INT32(0);
+	char       *res = (char *) palloc(64);
+
+	if (typmod >0) 
+		snprintf(res, 64, "(%d)", (int) (typmod));
+	else
+		*res = '\0';
+
+	PG_RETURN_CSTRING( res );
+}
+
+static void
+mchar_strip( MChar * m, int atttypmod ) {
+	int maxlen;
+	
+	if ( atttypmod<=0 ) {
+		atttypmod =-1;
+	} else {
+		int	charlen = u_countChar32( m->data, UCHARLENGTH(m) );
+
+		if ( charlen > atttypmod ) {
+			int i=0;
+			U16_FWD_N( m->data, i, UCHARLENGTH(m), atttypmod);
+			SET_VARSIZE( m, sizeof(UChar) * i + MCHARHDRSZ );
+		}
+	}
+
+	m->typmod = atttypmod;
+
+	maxlen = UCHARLENGTH(m);
+	while( maxlen>0 && m_isspace( m->data[ maxlen-1 ] ) )
+		maxlen--;
+
+	SET_VARSIZE(m, sizeof(UChar) * maxlen + MCHARHDRSZ);
+}
+
+
+Datum
+mchar_in(PG_FUNCTION_ARGS) {
+	char       *s = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+	Oid         typelem = PG_GETARG_OID(1);
+#endif
+	int32       atttypmod = PG_GETARG_INT32(2);
+	MChar	*result;
+	int32	slen = strlen(s), rlen;
+
+	pg_verifymbstr(s, slen, false);
+
+	result = (MChar*)palloc( MCHARHDRSZ + slen * sizeof(UChar) * 4 /* upper limit of length */ );
+	rlen = Char2UChar( s, slen, result->data );
+	SET_VARSIZE(result, sizeof(UChar) * rlen + MCHARHDRSZ);
+
+	mchar_strip(result, atttypmod);
+
+	PG_RETURN_MCHAR(result);
+}
+
+Datum
+mchar_out(PG_FUNCTION_ARGS) {
+	MChar	*in = PG_GETARG_MCHAR(0);
+	char	*out;
+	size_t	size, inlen = UCHARLENGTH(in);
+	size_t  charlen = u_countChar32(in->data, inlen);
+
+	Assert( in->typmod < 0 || charlen<=in->typmod );
+	size = ( in->typmod < 0 ) ? inlen : in->typmod;
+	size *= pg_database_encoding_max_length();
+
+	out = (char*)palloc( size+1 );
+	size = UChar2Char( in->data, inlen, out );
+
+	if ( in->typmod>0 && charlen < in->typmod ) {
+		memset( out+size, ' ', in->typmod - charlen);
+		size += in->typmod - charlen;
+	}
+
+	out[size] = '\0';
+
+	PG_FREE_IF_COPY(in,0);
+
+	PG_RETURN_CSTRING(out);
+}
+
+Datum
+mchar(PG_FUNCTION_ARGS) {
+	MChar	*source = PG_GETARG_MCHAR(0);
+	MChar	*result;
+	int32    typmod = PG_GETARG_INT32(1);
+#ifdef NOT_USED
+	bool     isExplicit = PG_GETARG_BOOL(2);
+#endif
+
+	result = palloc( VARSIZE(source) );
+	memcpy( result, source, VARSIZE(source) );
+	PG_FREE_IF_COPY(source,0);
+
+	mchar_strip(result, typmod);
+
+	PG_RETURN_MCHAR(result);
+}
+
+Datum
+mvarchar_in(PG_FUNCTION_ARGS) {
+	char       *s = PG_GETARG_CSTRING(0);
+#ifdef NOT_USED
+	Oid         typelem = PG_GETARG_OID(1);
+#endif
+	int32       atttypmod = PG_GETARG_INT32(2);
+	MVarChar	*result;
+	int32		slen = strlen(s), rlen;
+
+	pg_verifymbstr(s, slen, false);
+
+	result = (MVarChar*)palloc( MVARCHARHDRSZ + slen * sizeof(UChar) * 2 /* upper limit of length */ );
+	rlen = Char2UChar( s, slen, result->data );
+	SET_VARSIZE(result, sizeof(UChar) * rlen + MVARCHARHDRSZ);
+
+	if ( atttypmod > 0 && atttypmod < u_countChar32(result->data, UVARCHARLENGTH(result)) )
+		elog(ERROR,"value too long for type mvarchar(%d)", atttypmod);
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+Datum
+mvarchar_out(PG_FUNCTION_ARGS) {
+	MVarChar	*in = PG_GETARG_MVARCHAR(0);
+	char	*out;
+	size_t	size = UVARCHARLENGTH(in);
+
+	size *= pg_database_encoding_max_length();
+
+	out = (char*)palloc( size+1 );
+	size = UChar2Char( in->data, UVARCHARLENGTH(in), out );
+
+	out[size] = '\0';
+
+	PG_FREE_IF_COPY(in,0);
+
+	PG_RETURN_CSTRING(out);
+}
+
+static void
+mvarchar_strip(MVarChar *m, int atttypmod) {
+	int     charlen = u_countChar32(m->data, UVARCHARLENGTH(m));
+
+	if ( atttypmod>=0 && atttypmod < charlen ) {
+		int i=0;
+		U16_FWD_N( m->data, i, charlen, atttypmod);
+		SET_VARSIZE(m, sizeof(UChar) * i + MVARCHARHDRSZ);
+	}
+}
+
+Datum
+mvarchar(PG_FUNCTION_ARGS) {
+	MVarChar	*source = PG_GETARG_MVARCHAR(0);
+	MVarChar	*result;
+	int32    typmod = PG_GETARG_INT32(1);
+	bool     isExplicit = PG_GETARG_BOOL(2);
+	int		charlen = u_countChar32(source->data, UVARCHARLENGTH(source)); 
+
+	result = palloc( VARSIZE(source) );
+	memcpy( result, source, VARSIZE(source) );
+	PG_FREE_IF_COPY(source,0);
+
+	if ( typmod>=0 && typmod < charlen ) {
+		if ( isExplicit )
+			mvarchar_strip(result, typmod);
+		else
+			elog(ERROR,"value too long for type mvarchar(%d)", typmod);
+	}
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_mchar);
+Datum       mvarchar_mchar(PG_FUNCTION_ARGS);
+Datum       
+mvarchar_mchar(PG_FUNCTION_ARGS) {
+	MVarChar	*source = PG_GETARG_MVARCHAR(0);
+	MChar	*result;
+	int32    typmod = PG_GETARG_INT32(1);
+#ifdef NOT_USED
+	bool     isExplicit = PG_GETARG_BOOL(2);
+#endif
+
+	result = palloc( MVARCHARLENGTH(source) +  MCHARHDRSZ );
+	SET_VARSIZE(result, MVARCHARLENGTH(source) +  MCHARHDRSZ);
+	memcpy( result->data, source->data, MVARCHARLENGTH(source));
+
+	PG_FREE_IF_COPY(source,0);
+	
+	mchar_strip( result, typmod );
+
+	PG_RETURN_MCHAR(result);
+}
+
+PG_FUNCTION_INFO_V1(mchar_mvarchar);
+Datum       mchar_mvarchar(PG_FUNCTION_ARGS);
+Datum       
+mchar_mvarchar(PG_FUNCTION_ARGS) {
+	MChar	*source = PG_GETARG_MCHAR(0);
+	MVarChar	*result;
+	int32    typmod = PG_GETARG_INT32(1);
+	int32	 scharlen = u_countChar32(source->data, UCHARLENGTH(source));
+	int32	 curlen = 0, maxcharlen;
+#ifdef NOT_USED
+	bool     isExplicit = PG_GETARG_BOOL(2);
+#endif
+
+	maxcharlen = (source->typmod > 0) ? source->typmod : scharlen;
+
+	result = palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen );
+
+	curlen = UCHARLENGTH( source );
+	if ( curlen > 0 )
+		memcpy( result->data, source->data, MCHARLENGTH(source) );
+	if ( source->typmod > 0 && scharlen < source->typmod  ) {
+		FillWhiteSpace( result->data + curlen, source->typmod-scharlen );
+		curlen += source->typmod-scharlen;
+	}
+	SET_VARSIZE(result, MVARCHARHDRSZ + curlen *sizeof(UChar));
+
+	PG_FREE_IF_COPY(source,0);
+	
+	mvarchar_strip( result, typmod );
+
+	PG_RETURN_MCHAR(result);
+}
+
+PG_FUNCTION_INFO_V1(mchar_send);
+Datum       mchar_send(PG_FUNCTION_ARGS);
+Datum       
+mchar_send(PG_FUNCTION_ARGS) {
+	MChar	*in = PG_GETARG_MCHAR(0);
+	size_t	inlen = UCHARLENGTH(in);
+	size_t  charlen = u_countChar32(in->data, inlen);
+	StringInfoData buf;
+
+	Assert( in->typmod < 0 || charlen<=in->typmod );
+
+	pq_begintypsend(&buf);
+	pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) );
+
+	if ( in->typmod>0 && charlen < in->typmod ) {
+		int		nw = in->typmod - charlen;
+		UChar	*white = palloc( sizeof(UChar) * nw );
+
+		FillWhiteSpace( white, nw );
+		pq_sendbytes(&buf, (char*)white, sizeof(UChar) * nw);
+		pfree(white);
+	}
+
+	PG_FREE_IF_COPY(in,0);
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));	
+}
+
+PG_FUNCTION_INFO_V1(mchar_recv);
+Datum       mchar_recv(PG_FUNCTION_ARGS);
+Datum       
+mchar_recv(PG_FUNCTION_ARGS) {
+	StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
+	MChar		*res; 
+	int			nbytes;
+#ifdef NOT_USED
+	Oid         typelem = PG_GETARG_OID(1);
+#endif
+	int32       atttypmod = PG_GETARG_INT32(2);
+
+	nbytes = buf->len - buf->cursor;
+	res = (MChar*)palloc( nbytes + MCHARHDRSZ );
+	res->len = nbytes + MCHARHDRSZ;
+	res->typmod = -1;
+	SET_VARSIZE(res, res->len);
+	pq_copymsgbytes(buf, (char*)res->data, nbytes);
+
+	mchar_strip( res, atttypmod );
+
+	PG_RETURN_MCHAR(res);	
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_send);
+Datum       mvarchar_send(PG_FUNCTION_ARGS);
+Datum       
+mvarchar_send(PG_FUNCTION_ARGS) {
+	MVarChar	*in = PG_GETARG_MVARCHAR(0);
+	size_t	inlen = UVARCHARLENGTH(in);
+	StringInfoData buf;
+
+	pq_begintypsend(&buf);
+	pq_sendbytes(&buf, (char*)in->data, inlen * sizeof(UChar) );
+
+	PG_FREE_IF_COPY(in,0);
+
+	PG_RETURN_BYTEA_P(pq_endtypsend(&buf));	
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_recv);
+Datum       mvarchar_recv(PG_FUNCTION_ARGS);
+Datum       
+mvarchar_recv(PG_FUNCTION_ARGS) {
+	StringInfo  buf = (StringInfo) PG_GETARG_POINTER(0);
+	MVarChar	*res; 
+	int			nbytes;
+#ifdef NOT_USED
+	Oid         typelem = PG_GETARG_OID(1);
+#endif
+	int32       atttypmod = PG_GETARG_INT32(2);
+
+	nbytes = buf->len - buf->cursor;
+	res = (MVarChar*)palloc( nbytes + MVARCHARHDRSZ );
+	res->len = nbytes + MVARCHARHDRSZ;
+	SET_VARSIZE(res, res->len);
+	pq_copymsgbytes(buf, (char*)res->data, nbytes);
+
+	mvarchar_strip( res, atttypmod );
+
+	PG_RETURN_MVARCHAR(res);	
+}
+
+Datum
+mvarchar_support(PG_FUNCTION_ARGS)
+{
+	Node	   *node = (Node *) PG_GETARG_POINTER(0);
+	Node	   *ret = NULL;
+
+	if (IsA(node, SupportRequestSimplify))
+	{
+		SupportRequestSimplify *req = (SupportRequestSimplify *) node;
+		FuncExpr   *expr = req->fcall;
+		Node	   *typmodnode;
+
+		typmodnode = (Node *) lsecond(expr->args);
+
+		if (IsA(typmodnode, Const) && !((Const *) typmodnode)->constisnull)
+		{
+			Node	   *source = (Node *) linitial(expr->args);
+			int32		source_typmod = exprTypmod(source);
+			int32		req_typemod = DatumGetInt32(((Const *) typmodnode)->constvalue);
+
+			if (req_typemod < 0 || (source_typmod >= 0 && source_typmod <= req_typemod))
+				ret = relabel_to_typmod(source, req_typemod);
+		}
+	}
+
+	PG_RETURN_POINTER(ret);
+}
diff --git a/contrib/mchar/mchar_like.c b/contrib/mchar/mchar_like.c
new file mode 100644
index 00000000000..1f83fb37864
--- /dev/null
+++ b/contrib/mchar/mchar_like.c
@@ -0,0 +1,929 @@
+#include "mchar.h"
+#include "mb/pg_wchar.h"
+
+#include "catalog/pg_collation.h"
+#include "utils/selfuncs.h"
+#include "nodes/primnodes.h"
+#include "nodes/makefuncs.h"
+#include "nodes/supportnodes.h"
+#include "regex/regex.h"
+
+/*
+**  Originally written by Rich $alz, mirror!rs, Wed Nov 26 19:03:17 EST 1986.
+**  Rich $alz is now <rsalz@bbn.com>.
+**  Special thanks to Lars Mathiesen <thorinn@diku.dk> for the LABORT code.
+**
+**  This code was shamelessly stolen from the "pql" code by myself and
+**  slightly modified :)
+**
+**  All references to the word "star" were replaced by "percent"
+**  All references to the word "wild" were replaced by "like"
+**
+**  All the nice shell RE matching stuff was replaced by just "_" and "%"
+**
+**  As I don't have a copy of the SQL standard handy I wasn't sure whether
+**  to leave in the '\' escape character handling.
+**
+**  Keith Parks. <keith@mtcc.demon.co.uk>
+**
+**  SQL92 lets you specify the escape character by saying
+**  LIKE <pattern> ESCAPE <escape character>. We are a small operation
+**  so we force you to use '\'. - ay 7/95
+**
+**  Now we have the like_escape() function that converts patterns with
+**  any specified escape character (or none at all) to the internal
+**  default escape character, which is still '\'. - tgl 9/2000
+**
+** The code is rewritten to avoid requiring null-terminated strings,
+** which in turn allows us to leave out some memcpy() operations.
+** This code should be faster and take less memory, but no promises...
+** - thomas 2000-08-06
+**
+** Adopted for UTF-16 by teodor
+*/
+
+#define LIKE_TRUE                       1
+#define LIKE_FALSE                      0
+#define LIKE_ABORT                      (-1)
+
+
+static int
+uchareq(UChar *p1, UChar *p2) {
+	int l1=0, l2=0;
+	/*
+	 * Count length of char:
+	 * We suppose that string is correct!!
+	 */
+	U16_FWD_1(p1, l1, 2);
+	U16_FWD_1(p2, l2, 2);
+
+	return (UCharCaseCompare(p1, l1, p2, l2)==0) ? 1 : 0;
+}
+
+#define NextChar(p, plen) 			\
+	do {							\
+		int __l = 0;				\
+		U16_FWD_1((p), __l, (plen));\
+		(p) +=__l;					\
+		(plen) -=__l;				\
+	} while(0)
+
+#define CopyAdvChar(dst, src, srclen) 	\
+	do { 								\
+		int __l = 0;					\
+		U16_FWD_1((src), __l, (srclen));\
+		(srclen) -= __l;				\
+		while (__l-- > 0)				\
+			*(dst)++ = *(src)++;		\
+	} while (0)
+
+
+static UChar	UCharPercent = 0;
+static UChar	UCharBackSlesh = 0;
+static UChar	UCharUnderLine = 0;
+static UChar	UCharStar = 0;
+static UChar	UCharDotDot = 0;
+static UChar	UCharUp = 0;
+static UChar	UCharLBracket = 0;
+static UChar	UCharQ = 0;
+static UChar	UCharRBracket = 0;
+static UChar	UCharDollar = 0;
+static UChar	UCharDot = 0;
+static UChar	UCharLFBracket = 0;
+static UChar	UCharRFBracket = 0;
+static UChar	UCharQuote = 0;
+static UChar	UCharSpace = 0;
+static UChar	UCharOne = 0;
+static UChar	UCharComma = 0;
+static UChar	UCharLQBracket = 0;
+static UChar	UCharRQBracket = 0;
+
+#define MkUChar(uc, c) 	do {			\
+	char __c = (c); 					\
+	u_charsToUChars( &__c, &(uc), 1 );	\
+} while(0)
+
+#define	SET_UCHAR	if ( UCharPercent == 0 ) {	\
+		MkUChar( UCharPercent,		'%' );		\
+		MkUChar( UCharBackSlesh,	'\\' );		\
+		MkUChar( UCharUnderLine,	'_' );		\
+		MkUChar( UCharStar,			'*' );		\
+		MkUChar( UCharDotDot,		':' );		\
+		MkUChar( UCharUp,			'^' );		\
+		MkUChar( UCharLBracket,		'(' );		\
+		MkUChar( UCharQ,			'?' );		\
+		MkUChar( UCharRBracket,		')' );		\
+		MkUChar( UCharDollar,		'$' );		\
+		MkUChar( UCharDot,			'.' );		\
+		MkUChar( UCharLFBracket,	'{' );		\
+		MkUChar( UCharRFBracket,	'}' );		\
+		MkUChar( UCharQuote,		'"' );		\
+		MkUChar( UCharSpace,		' ' );		\
+		MkUChar( UCharOne,			'1' );		\
+		MkUChar( UCharComma,		',' );		\
+		MkUChar( UCharLQBracket,	'[' );		\
+		MkUChar( UCharRQBracket,	']' );		\
+	}
+
+int
+m_isspace(UChar c) {
+	SET_UCHAR;
+
+	return (c == UCharSpace);
+}
+
+static int
+MatchUChar(UChar *t, int tlen, UChar *p, int plen) {
+	SET_UCHAR;
+
+	/* Fast path for match-everything pattern */
+	if ((plen == 1) && (*p == UCharPercent))
+		return LIKE_TRUE;
+
+	while ((tlen > 0) && (plen > 0)) {
+		if (*p == UCharBackSlesh) {
+			/* Next pattern char must match literally, whatever it is */
+			NextChar(p, plen);
+			if ((plen <= 0) || !uchareq(t, p))
+				return LIKE_FALSE;
+		} else if (*p == UCharPercent) {
+			/* %% is the same as % according to the SQL standard */
+			/* Advance past all %'s */
+			while ((plen > 0) && (*p == UCharPercent))
+				NextChar(p, plen);
+			/* Trailing percent matches everything. */
+			if (plen <= 0)
+				return LIKE_TRUE;
+
+			/*
+			 * Otherwise, scan for a text position at which we can match the
+			 * rest of the pattern.
+			 */
+			while (tlen > 0) {
+				/*
+				 * Optimization to prevent most recursion: don't recurse
+				 * unless first pattern char might match this text char.
+				 */
+				if (uchareq(t, p) || (*p == UCharBackSlesh) || (*p == UCharUnderLine)) {
+					int         matched = MatchUChar(t, tlen, p, plen);
+
+					if (matched != LIKE_FALSE)
+						return matched; /* TRUE or ABORT */
+				}
+
+				NextChar(t, tlen);
+			}
+
+			/*
+			 * End of text with no match, so no point in trying later places
+			 * to start matching this pattern.
+			 */
+			return LIKE_ABORT;
+		} if ((*p != UCharUnderLine) && !uchareq(t, p)) {
+			/*
+			 * Not the single-character wildcard and no explicit match? Then
+			 * time to quit...
+			 */
+			return LIKE_FALSE;
+		}
+
+		NextChar(t, tlen);
+		NextChar(p, plen);
+	}
+
+	if (tlen > 0)
+		return LIKE_FALSE;      /* end of pattern, but not of text */
+
+	/* End of input string.  Do we have matching pattern remaining? */
+	while ((plen > 0) && (*p == UCharPercent))   /* allow multiple %'s at end of
+										 		  * pattern */
+		NextChar(p, plen);
+	if (plen <= 0)
+		return LIKE_TRUE;
+
+	/*
+	 * End of text with no match, so no point in trying later places to start
+	 * matching this pattern.
+	 */
+
+	return LIKE_ABORT;
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_like );
+Datum mvarchar_like( PG_FUNCTION_ARGS );
+Datum
+mvarchar_like( PG_FUNCTION_ARGS ) {
+	MVarChar *str = PG_GETARG_MVARCHAR(0);
+	MVarChar *pat = PG_GETARG_MVARCHAR(1);
+	int		result;
+
+	result = MatchUChar( str->data, UVARCHARLENGTH(str), pat->data, UVARCHARLENGTH(pat) );
+
+	PG_FREE_IF_COPY(str,0);
+	PG_FREE_IF_COPY(pat,1);
+
+	PG_RETURN_BOOL(result == LIKE_TRUE);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_notlike );
+Datum mvarchar_notlike( PG_FUNCTION_ARGS );
+Datum
+mvarchar_notlike( PG_FUNCTION_ARGS ) {
+	bool res = DatumGetBool( DirectFunctionCall2(
+			mvarchar_like, 
+			PG_GETARG_DATUM(0), 
+			PG_GETARG_DATUM(1)
+		));
+	PG_RETURN_BOOL( !res );
+}
+
+/*
+ * Removes trailing spaces in '111 %' pattern
+ */
+static UChar *
+removeTrailingSpaces( UChar *src, int srclen, int *dstlen, bool *isSpecialLast) {
+	UChar* dst = src;
+	UChar *ptr, *dptr, *markptr;
+
+	*dstlen = srclen;
+	ptr = src + srclen-1;
+	SET_UCHAR;
+
+	*isSpecialLast = ( srclen > 0 && (u_isspace(*ptr) || *ptr == UCharPercent || *ptr == UCharUnderLine ) ) ? true : false; 
+	while( ptr>=src ) {
+		if ( *ptr == UCharPercent || *ptr == UCharUnderLine ) {
+			if ( ptr==src )
+				return dst; /* first character */
+
+			if ( *(ptr-1) == UCharBackSlesh )
+				return dst; /* use src as is */
+
+			if ( u_isspace( *(ptr-1) ) ) {
+				ptr--;
+				break; /* % or _ is after space which should be removed */
+			}
+		} else {
+			return dst;
+		}
+		ptr--;
+	}
+
+	markptr = ptr+1;
+	dst = (UChar*)palloc( sizeof(UChar) * srclen );
+
+	/* find last non-space character */
+	while( ptr>=src && u_isspace(*ptr) )
+		ptr--;
+
+	dptr = dst + (ptr-src+1);
+
+	if ( ptr>=src ) 
+		memcpy( dst, src, sizeof(UChar) * (ptr-src+1) );
+
+	while( markptr - src < srclen ) {
+		*dptr = *markptr;
+		dptr++;
+		markptr++;
+	}
+
+	*dstlen = dptr - dst;
+	return dst;
+}
+
+static UChar*
+addTrailingSpace( MChar *src, int *newlen ) {
+	int	scharlen = u_countChar32(src->data, UCHARLENGTH(src));
+
+	if ( src->typmod > scharlen ) {
+		UChar	*res = (UChar*) palloc( sizeof(UChar) * (UCHARLENGTH(src) + src->typmod) );
+
+		memcpy( res, src->data, sizeof(UChar) * UCHARLENGTH(src));
+		FillWhiteSpace( res+UCHARLENGTH(src), src->typmod - scharlen );
+
+		*newlen = src->typmod;
+
+		return res;
+	} else {
+		*newlen = UCHARLENGTH(src);
+		return src->data;
+	}
+}
+
+PG_FUNCTION_INFO_V1( mchar_like );
+Datum mchar_like( PG_FUNCTION_ARGS );
+Datum
+mchar_like( PG_FUNCTION_ARGS ) {
+	MChar *str = PG_GETARG_MCHAR(0);
+	MVarChar *pat = PG_GETARG_MVARCHAR(1);
+	int	        result;
+	bool		isNeedAdd = false;
+	UChar		*cleaned, *filled;
+	int			clen=0, flen=0;
+
+	cleaned = removeTrailingSpaces(pat->data, UVARCHARLENGTH(pat), &clen, &isNeedAdd);
+	if ( isNeedAdd )
+		filled  = addTrailingSpace(str, &flen);
+	else {
+		filled = str->data;
+		flen = UCHARLENGTH(str);
+	}
+
+	result = MatchUChar( filled, flen, cleaned, clen );
+
+	if ( pat->data != cleaned )
+		pfree( cleaned );
+	if ( str->data != filled )
+		pfree( filled );
+
+	PG_FREE_IF_COPY(str,0);
+	PG_FREE_IF_COPY(pat,1);
+
+	PG_RETURN_BOOL(result == LIKE_TRUE);
+}
+
+PG_FUNCTION_INFO_V1( mchar_notlike );
+Datum mchar_notlike( PG_FUNCTION_ARGS );
+Datum
+mchar_notlike( PG_FUNCTION_ARGS ) {
+	bool res = DatumGetInt32( DirectFunctionCall2(
+			mchar_like, 
+			PG_GETARG_DATUM(0), 
+			PG_GETARG_DATUM(1)
+		));
+
+	PG_RETURN_BOOL( !res );
+}
+
+
+
+PG_FUNCTION_INFO_V1( mchar_pattern_fixed_prefix );
+Datum mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS );
+Datum
+mchar_pattern_fixed_prefix( PG_FUNCTION_ARGS ) {
+	Const			*patt = 	(Const*)PG_GETARG_POINTER(0);
+	Pattern_Type	ptype = 	(Pattern_Type)PG_GETARG_INT32(1);
+	Const			**prefix = 	(Const**)PG_GETARG_POINTER(2);
+	UChar			*spatt;	
+	int32			slen, prefixlen=0, restlen=0, i=0;
+	MVarChar		*sprefix;
+	MVarChar		*srest;
+	Pattern_Prefix_Status	status = Pattern_Prefix_None; 
+
+	*prefix = NULL;
+
+	if ( ptype != Pattern_Type_Like )
+		PG_RETURN_INT32(Pattern_Prefix_None);
+
+	SET_UCHAR;
+
+	spatt = ((MVarChar*)DatumGetPointer(patt->constvalue))->data;
+	slen = UVARCHARLENGTH( DatumGetPointer(patt->constvalue) );
+
+	sprefix = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen );
+	srest = (MVarChar*)palloc( MCHARHDRSZ /*The biggest hdr!! */ + sizeof(UChar) * slen );
+
+	while( prefixlen < slen && i < slen ) {
+		if ( spatt[i] == UCharPercent || spatt[i] == UCharUnderLine )
+			break;
+		else if ( spatt[i] == UCharBackSlesh ) {
+			i++;
+			if ( i>= slen )
+				break;
+		}
+		sprefix->data[ prefixlen++ ] = spatt[i++];
+	}
+
+	while( prefixlen > 0 ) {
+		if ( ! u_isspace( sprefix->data[ prefixlen-1 ] ) ) 
+			break;
+		prefixlen--;
+	}
+
+	if ( prefixlen == 0 )
+		PG_RETURN_INT32(Pattern_Prefix_None);
+
+	for(;i<slen;i++) 
+		srest->data[ restlen++ ] = spatt[i];
+
+	SET_VARSIZE(sprefix, sizeof(UChar) * prefixlen + MVARCHARHDRSZ);	
+	SET_VARSIZE(srest, sizeof(UChar) * restlen + MVARCHARHDRSZ);	
+
+	*prefix = makeConst( patt->consttype, -1, InvalidOid, VARSIZE(sprefix), PointerGetDatum(sprefix), false, false );
+
+	if ( prefixlen == slen )	/* in LIKE, an empty pattern is an exact match! */
+		status = Pattern_Prefix_Exact;
+	else if ( prefixlen > 0 )
+		status = Pattern_Prefix_Partial;
+
+	PG_RETURN_INT32( status );	
+}
+
+static bool 
+checkCmp( UChar *left, int32 leftlen, UChar *right, int32 rightlen ) {
+
+	return  (UCharCaseCompare( left, leftlen, right, rightlen) < 0 ) ? true : false;
+}
+
+
+PG_FUNCTION_INFO_V1( mchar_greaterstring );
+Datum mchar_greaterstring( PG_FUNCTION_ARGS );
+Datum
+mchar_greaterstring( PG_FUNCTION_ARGS ) {
+	Const			*patt = 	(Const*)PG_GETARG_POINTER(0);
+	char			*src  = 	(char*)DatumGetPointer( patt->constvalue ); 
+	int				dstlen, srclen  = 	VARSIZE(src);
+	char 			*dst = palloc( srclen );
+	UChar			*ptr, *srcptr;
+
+	memcpy( dst, src, srclen );
+
+	srclen = dstlen = UVARCHARLENGTH( dst );
+	ptr    = ((MVarChar*)dst)->data;
+	srcptr    = ((MVarChar*)src)->data;
+
+	while( dstlen > 0 ) {
+		UChar	*lastchar = ptr + dstlen - 1;
+
+		if ( !U16_IS_LEAD( *lastchar ) ) {
+			while( *lastchar<0xffff ) {
+
+				(*lastchar)++;
+
+				if ( ublock_getCode(*lastchar) == UBLOCK_INVALID_CODE || !checkCmp( srcptr, srclen, ptr, dstlen ) )
+					continue;
+				else {
+					SET_VARSIZE(dst, sizeof(UChar) * dstlen + MVARCHARHDRSZ);
+				
+					PG_RETURN_POINTER( makeConst( patt->consttype, -1,
+												  InvalidOid, VARSIZE(dst), PointerGetDatum(dst), false, false ) );
+				}
+			}
+		}
+				
+		dstlen--;
+	}
+
+	PG_RETURN_POINTER(NULL);
+}
+
+static int 
+do_like_escape( UChar *pat, int plen, UChar *esc, int elen, UChar *result) {
+	UChar	*p = pat,*e =esc ,*r;
+	bool	afterescape;
+
+	r = result;
+	SET_UCHAR;
+
+	if ( elen == 0 ) {
+		/*
+		 * No escape character is wanted.  Double any backslashes in the
+		 * pattern to make them act like ordinary characters.
+		 */
+		while (plen > 0) {
+			if (*p == UCharBackSlesh ) 
+				*r++ = UCharBackSlesh;
+			CopyAdvChar(r, p, plen);
+		}
+	} else {
+		/*
+		 * The specified escape must be only a single character.
+		 */
+		NextChar(e, elen);
+
+		if (elen != 0)
+			ereport(ERROR,
+				(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
+				errmsg("invalid escape string"),
+				errhint("Escape string must be empty or one character.")));
+
+		e = esc;
+
+		/*
+		 * If specified escape is '\', just copy the pattern as-is.
+		 */
+		if ( *e == UCharBackSlesh ) {
+			memcpy(result, pat, plen * sizeof(UChar));
+			return plen;
+		}
+
+		/*
+		 * Otherwise, convert occurrences of the specified escape character to
+		 * '\', and double occurrences of '\' --- unless they immediately
+		 * follow an escape character!
+		 */
+		afterescape = false;
+
+		while (plen > 0) {
+			if ( uchareq(p,e) && !afterescape) {
+				*r++ = UCharBackSlesh;
+				NextChar(p, plen);
+				afterescape = true;
+			} else if ( *p == UCharBackSlesh ) {
+				*r++ = UCharBackSlesh;
+				if (!afterescape)
+					*r++ = UCharBackSlesh;
+				NextChar(p, plen);
+				afterescape = false;
+			} else {
+				CopyAdvChar(r, p, plen);
+				afterescape = false;
+			}
+		}
+	}
+
+	return  ( r -  result );
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_like_escape );
+Datum mvarchar_like_escape( PG_FUNCTION_ARGS );
+Datum
+mvarchar_like_escape( PG_FUNCTION_ARGS ) {
+	MVarChar	*pat = PG_GETARG_MVARCHAR(0);
+	MVarChar	*esc = PG_GETARG_MVARCHAR(1);
+	MVarChar	*result;
+
+	result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*2*UVARCHARLENGTH(pat) );
+	result->len = MVARCHARHDRSZ + do_like_escape( pat->data, UVARCHARLENGTH(pat),
+							 	  				  esc->data, UVARCHARLENGTH(esc),
+								  				  result->data ) * sizeof(UChar);
+
+	SET_VARSIZE(result, result->len);
+	PG_FREE_IF_COPY(pat,0);
+	PG_FREE_IF_COPY(esc,1);
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+#define RE_CACHE_SIZE	32
+typedef struct ReCache {
+	UChar	*pattern;
+	int		length;
+	int		flags;
+	regex_t	re;
+} ReCache;
+
+static int  num_res = 0;
+static ReCache re_array[RE_CACHE_SIZE];  /* cached re's */
+static const int mchar_regex_flavor = REG_ADVANCED | REG_ICASE;
+
+static regex_t *
+URE_compile_and_cache(UChar *text_re, int text_re_len, int cflags) {
+	pg_wchar	*pattern;
+	size_t		pattern_len;
+	int			i;
+	int			regcomp_result;
+	ReCache		re_temp;
+	char		errMsg[128];
+
+
+	for (i = 0; i < num_res; i++) {
+		if ( re_array[i].length == text_re_len &&
+			 re_array[i].flags == cflags &&
+			 memcmp(re_array[i].pattern, text_re, sizeof(UChar)*text_re_len) == 0 ) {
+
+			 /* Found, move it to front */
+			 if ( i>0 ) {
+				re_temp = re_array[i];
+				memmove(&re_array[1], &re_array[0], i * sizeof(ReCache));
+				re_array[0] = re_temp;
+			}
+
+			return &re_array[0].re;
+		}
+	}
+
+	pattern = (pg_wchar *) palloc((1 + text_re_len) * sizeof(pg_wchar));
+	pattern_len =  UChar2Wchar(text_re, text_re_len, pattern);
+
+	regcomp_result = pg_regcomp(&re_temp.re,
+								pattern,
+								pattern_len,
+								cflags,
+								DEFAULT_COLLATION_OID);
+	pfree( pattern );
+
+	if (regcomp_result != REG_OKAY) {
+		pg_regerror(regcomp_result, &re_temp.re, errMsg, sizeof(errMsg));
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+				errmsg("invalid regular expression: %s", errMsg)));
+	}
+
+	re_temp.pattern = malloc( text_re_len*sizeof(UChar) );
+	if ( re_temp.pattern == NULL )
+		elog(ERROR,"Out of memory");
+
+	memcpy(re_temp.pattern, text_re, text_re_len*sizeof(UChar) );
+	re_temp.length = text_re_len;
+	re_temp.flags = cflags;
+
+	if (num_res >= RE_CACHE_SIZE) {
+		--num_res;
+		Assert(num_res < RE_CACHE_SIZE);
+		pg_regfree(&re_array[num_res].re);
+		free(re_array[num_res].pattern);
+	}
+
+	if (num_res > 0)
+		memmove(&re_array[1], &re_array[0], num_res * sizeof(ReCache));
+
+	re_array[0] = re_temp;
+	num_res++;
+
+	return &re_array[0].re;
+}
+
+static bool
+URE_compile_and_execute(UChar *pat, int pat_len, UChar *dat, int dat_len,
+						int cflags, int nmatch, regmatch_t *pmatch) {
+	pg_wchar   *data;
+	size_t      data_len;
+	int         regexec_result;
+	regex_t    *re;
+	char        errMsg[128];
+
+	data = (pg_wchar *) palloc((1+dat_len) * sizeof(pg_wchar));
+	data_len = UChar2Wchar(dat, dat_len, data);
+
+	re = URE_compile_and_cache(pat, pat_len, cflags);
+
+	regexec_result = pg_regexec(re,
+								data,
+								data_len,
+								0,
+								NULL,
+								nmatch,
+								pmatch,
+								0);
+	pfree(data);
+
+	if (regexec_result != REG_OKAY && regexec_result != REG_NOMATCH) {
+		/* re failed??? */
+		pg_regerror(regexec_result, re, errMsg, sizeof(errMsg));
+		ereport(ERROR,
+				(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
+				errmsg("regular expression failed: %s", errMsg)));
+	}
+
+	return (regexec_result == REG_OKAY);
+}
+
+PG_FUNCTION_INFO_V1( mchar_regexeq );
+Datum mchar_regexeq( PG_FUNCTION_ARGS );
+Datum
+mchar_regexeq( PG_FUNCTION_ARGS ) {
+	MChar	*t = PG_GETARG_MCHAR(0);
+	MChar	*p = PG_GETARG_MCHAR(1);
+	bool 	res;
+
+	res = URE_compile_and_execute(p->data, UCHARLENGTH(p),
+								 t->data, UCHARLENGTH(t),
+								 mchar_regex_flavor,
+								 0, NULL);
+	PG_FREE_IF_COPY(t, 0);
+	PG_FREE_IF_COPY(p, 1);
+
+	PG_RETURN_BOOL(res);
+}
+
+PG_FUNCTION_INFO_V1( mchar_regexne );
+Datum mchar_regexne( PG_FUNCTION_ARGS );
+Datum
+mchar_regexne( PG_FUNCTION_ARGS ) {
+	MChar	*t = PG_GETARG_MCHAR(0);
+	MChar	*p = PG_GETARG_MCHAR(1);
+	bool 	res;
+
+	res = URE_compile_and_execute(p->data, UCHARLENGTH(p),
+								 t->data, UCHARLENGTH(t),
+								 mchar_regex_flavor,
+								 0, NULL);
+	PG_FREE_IF_COPY(t, 0);
+	PG_FREE_IF_COPY(p, 1);
+
+	PG_RETURN_BOOL(!res);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_regexeq );
+Datum mvarchar_regexeq( PG_FUNCTION_ARGS );
+Datum
+mvarchar_regexeq( PG_FUNCTION_ARGS ) {
+	MVarChar	*t = PG_GETARG_MVARCHAR(0);
+	MVarChar	*p = PG_GETARG_MVARCHAR(1);
+	bool 	res;
+
+	res = URE_compile_and_execute(p->data, UVARCHARLENGTH(p),
+								 t->data, UVARCHARLENGTH(t),
+								 mchar_regex_flavor,
+								 0, NULL);
+	PG_FREE_IF_COPY(t, 0);
+	PG_FREE_IF_COPY(p, 1);
+
+	PG_RETURN_BOOL(res);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_regexne );
+Datum mvarchar_regexne( PG_FUNCTION_ARGS );
+Datum
+mvarchar_regexne( PG_FUNCTION_ARGS ) {
+	MVarChar	*t = PG_GETARG_MVARCHAR(0);
+	MVarChar	*p = PG_GETARG_MVARCHAR(1);
+	bool 	res;
+
+	res = URE_compile_and_execute(p->data, UVARCHARLENGTH(p),
+								 t->data, UVARCHARLENGTH(t),
+								 mchar_regex_flavor,
+								 0, NULL);
+	PG_FREE_IF_COPY(t, 0);
+	PG_FREE_IF_COPY(p, 1);
+
+	PG_RETURN_BOOL(!res);
+}
+
+static int
+do_similar_escape(UChar *p, int plen, UChar *e, int elen, UChar *result) {
+	UChar	*r;
+	bool	afterescape = false;
+	bool	incharclass = false;
+	int		nquotes = 0;
+
+	SET_UCHAR;
+
+	if (e==NULL || elen <0 ) {
+		e = &UCharBackSlesh;
+		elen = 1;
+	} else {
+		if ( elen == 0 )
+			e = NULL;
+		else if ( elen != 1)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_ESCAPE_SEQUENCE),
+					errmsg("invalid escape string"),
+					errhint("Escape string must be empty or one character.")));
+	}
+
+	/*
+	 * Look explanation of following in ./utils/adt/regexp.c 
+	 */
+	r = result;
+
+	*r++ = UCharUp;
+	*r++ = UCharLBracket;
+	*r++ = UCharQ;
+	*r++ = UCharDotDot;
+
+	while( plen>0 ) {
+		UChar	pchar = *p;
+
+		if (afterescape)
+		{
+			if (pchar == UCharQuote && !incharclass) /* escape-double-quote? */
+			{
+				if (nquotes == 0)
+				{
+					*r++ = UCharRBracket;
+					*r++ = UCharLFBracket;
+					*r++ = UCharOne;
+					*r++ = UCharComma;
+					*r++ = UCharOne;
+					*r++ = UCharRFBracket;
+					*r++ = UCharQ;
+					*r++ = UCharLBracket;
+				}
+				else if (nquotes == 1)
+				{
+					*r++ = UCharRBracket;
+					*r++ = UCharLFBracket;
+					*r++ = UCharOne;
+					*r++ = UCharComma;
+					*r++ = UCharOne;
+					*r++ = UCharRFBracket;
+					*r++ = UCharLBracket;
+					*r++ = UCharQ;
+					*r++ = UCharDotDot;
+				}
+				else
+					ereport(ERROR,
+							(errcode(ERRCODE_INVALID_USE_OF_ESCAPE_CHARACTER),
+							 errmsg("SQL regular expression may not contain more than two escape-double-quote separators")));
+				nquotes++;
+			}
+			else
+			{
+				*r++ = UCharBackSlesh;
+				*r++ = pchar;
+			}
+			afterescape = false;
+		}
+		else if (e && elen > 0 && pchar == *e)
+		{
+			afterescape = true;
+		}
+		else if (incharclass)
+		{
+			if (pchar == UCharBackSlesh)
+				*r++ = UCharBackSlesh;
+			*r++ = pchar;
+			if (pchar == UCharRQBracket)
+				incharclass = false;
+		}
+		else if (pchar == UCharLQBracket)
+		{
+			*r++ = pchar;
+			incharclass = true;
+		}
+		else if (pchar == UCharPercent)
+		{
+			*r++ = UCharDot;
+			*r++ = UCharStar;
+		}
+		else if (pchar == UCharUnderLine)
+			*r++ = UCharDot;
+		else if (pchar == UCharLBracket)
+		{
+			*r++ = UCharLBracket;
+			*r++ = UCharQ;
+			*r++ = UCharDotDot;
+		}
+		else if (pchar == UCharBackSlesh || pchar == UCharDot ||
+				 pchar == UCharUp || pchar == UCharDollar)
+		{
+			*r++ = UCharBackSlesh;
+			*r++ = pchar;
+		}
+		else
+			*r++ = pchar;
+
+		 p++, plen--;
+	}
+
+	*r++ = UCharRBracket;
+	*r++ = UCharDollar;
+
+	return r-result;
+}
+
+PG_FUNCTION_INFO_V1( mchar_similar_escape );
+Datum mchar_similar_escape( PG_FUNCTION_ARGS );
+Datum
+mchar_similar_escape( PG_FUNCTION_ARGS ) {
+	MChar	*pat;
+	MChar	*esc;
+	MChar	*result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+	pat = PG_GETARG_MCHAR(0);
+
+	if (PG_NARGS() < 2 || PG_ARGISNULL(1)) {
+		esc = NULL;
+	} else {
+		esc = PG_GETARG_MCHAR(1);
+	}
+
+	result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar)*(23 + 3*UCHARLENGTH(pat)) );
+	result->len = MCHARHDRSZ + do_similar_escape( pat->data, UCHARLENGTH(pat),
+							 	  (esc) ? esc->data : NULL, (esc) ? UCHARLENGTH(esc) : -1,
+								  result->data ) * sizeof(UChar);
+	result->typmod=-1;
+
+	SET_VARSIZE(result, result->len);
+	PG_FREE_IF_COPY(pat,0);
+	if ( esc )
+		PG_FREE_IF_COPY(esc,1);
+
+	PG_RETURN_MCHAR(result);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_similar_escape );
+Datum mvarchar_similar_escape( PG_FUNCTION_ARGS );
+Datum
+mvarchar_similar_escape( PG_FUNCTION_ARGS ) {
+	MVarChar	*pat;
+	MVarChar	*esc;
+	MVarChar	*result;
+
+	if (PG_ARGISNULL(0))
+		PG_RETURN_NULL();
+	pat = PG_GETARG_MVARCHAR(0);
+
+	if (PG_NARGS() < 2 || PG_ARGISNULL(1)) {
+		esc = NULL;
+	} else {
+		esc = PG_GETARG_MVARCHAR(1);
+	}
+
+	result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar)*(23 + 3*UVARCHARLENGTH(pat)) );
+	result->len = MVARCHARHDRSZ + do_similar_escape( pat->data, UVARCHARLENGTH(pat),
+							 	  				(esc) ? esc->data : NULL, (esc) ? UVARCHARLENGTH(esc) : -1,
+								  				  result->data ) * sizeof(UChar);
+
+	SET_VARSIZE(result, result->len);
+	PG_FREE_IF_COPY(pat,0);
+	if ( esc )
+		PG_FREE_IF_COPY(esc,1);
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+#define RE_CACHE_SIZE	32
diff --git a/contrib/mchar/mchar_op.c b/contrib/mchar/mchar_op.c
new file mode 100644
index 00000000000..4694d9cf3c3
--- /dev/null
+++ b/contrib/mchar/mchar_op.c
@@ -0,0 +1,449 @@
+#include "mchar.h"
+
+int
+lengthWithoutSpaceVarChar(MVarChar	*m) {
+	int l = UVARCHARLENGTH(m);
+
+	while( l>0 && m_isspace( m->data[ l-1 ] ) ) 
+		l--;
+
+	return l;
+}
+
+int
+lengthWithoutSpaceChar(MChar	*m) {
+	int l = UCHARLENGTH(m);
+
+	while( l>0 && m_isspace( m->data[ l-1 ] ) ) 
+		l--;
+
+	return l;
+}
+
+static inline int
+mchar_icase_compare( MChar *a, MChar *b ) {
+	return UCharCaseCompare( 
+		a->data, lengthWithoutSpaceChar(a),
+		b->data, lengthWithoutSpaceChar(b)
+	);
+}
+
+static inline int
+mchar_case_compare( MChar *a, MChar *b ) {
+	return UCharCompare( 
+		a->data, lengthWithoutSpaceChar(a),
+		b->data, lengthWithoutSpaceChar(b)
+	);
+}
+
+#define MCHARCMPFUNC( c, type, action, ret ) 		\
+PG_FUNCTION_INFO_V1( mchar_##c##_##type ); 		\
+Datum      mchar_##c##_##type(PG_FUNCTION_ARGS);\
+Datum											\
+mchar_##c##_##type(PG_FUNCTION_ARGS) {			\
+	MChar *a = PG_GETARG_MCHAR(0);				\
+	MChar *b = PG_GETARG_MCHAR(1);				\
+	int 	res = mchar_##c##_compare(a,b);		\
+												\
+	PG_FREE_IF_COPY(a,0);						\
+	PG_FREE_IF_COPY(b,1);						\
+	PG_RETURN_##ret( res action 0 );			\
+}
+
+
+MCHARCMPFUNC( case, eq, ==, BOOL )
+MCHARCMPFUNC( case, ne, !=, BOOL )
+MCHARCMPFUNC( case, lt, <, BOOL )
+MCHARCMPFUNC( case, le, <=, BOOL )
+MCHARCMPFUNC( case, ge, >=, BOOL )
+MCHARCMPFUNC( case, gt, >, BOOL )
+MCHARCMPFUNC( case, cmp, +, INT32 )
+
+MCHARCMPFUNC( icase, eq, ==, BOOL )
+MCHARCMPFUNC( icase, ne, !=, BOOL )
+MCHARCMPFUNC( icase, lt, <, BOOL )
+MCHARCMPFUNC( icase, le, <=, BOOL )
+MCHARCMPFUNC( icase, ge, >=, BOOL )
+MCHARCMPFUNC( icase, gt, >, BOOL )
+MCHARCMPFUNC( icase, cmp, +, INT32 )
+
+PG_FUNCTION_INFO_V1( mchar_larger );
+Datum mchar_larger( PG_FUNCTION_ARGS );
+Datum
+mchar_larger( PG_FUNCTION_ARGS ) {
+	MChar *a = PG_GETARG_MCHAR(0);
+	MChar *b = PG_GETARG_MCHAR(1);
+	MChar *r;
+
+	r = ( mchar_icase_compare(a,b) > 0 ) ? a : b;
+
+	PG_RETURN_MCHAR(r);
+}
+
+PG_FUNCTION_INFO_V1( mchar_smaller );
+Datum mchar_smaller( PG_FUNCTION_ARGS );
+Datum
+mchar_smaller( PG_FUNCTION_ARGS ) {
+	MChar *a = PG_GETARG_MCHAR(0);
+	MChar *b = PG_GETARG_MCHAR(1);
+	MChar *r;
+
+	r = ( mchar_icase_compare(a,b) < 0 ) ? a : b;
+
+	PG_RETURN_MCHAR(r);
+}
+
+
+PG_FUNCTION_INFO_V1( mchar_concat );
+Datum mchar_concat( PG_FUNCTION_ARGS );
+Datum
+mchar_concat( PG_FUNCTION_ARGS ) {
+	MChar *a = PG_GETARG_MCHAR(0);
+	MChar *b = PG_GETARG_MCHAR(1);
+	MChar *result;
+	int	maxcharlen, curlen;
+	int	acharlen = u_countChar32(a->data, UCHARLENGTH(a)),
+		bcharlen = u_countChar32(b->data, UCHARLENGTH(b));
+
+
+	maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + 
+				 ((b->typmod<=0) ? bcharlen : b->typmod);
+
+	result = (MChar*)palloc( MCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen );
+
+	curlen = UCHARLENGTH( a );
+	if ( curlen > 0 )
+		memcpy( result->data, a->data, MCHARLENGTH(a) );
+	if ( a->typmod > 0 && acharlen < a->typmod  ) {
+		FillWhiteSpace( result->data + curlen, a->typmod-acharlen );
+		curlen += a->typmod-acharlen;
+	}
+
+	if ( UCHARLENGTH(b) > 0 ) {
+		memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) );
+		curlen += UCHARLENGTH( b );
+	}
+	if ( b->typmod > 0 && bcharlen < b->typmod  ) {
+		FillWhiteSpace( result->data + curlen, b->typmod-bcharlen );
+		curlen += b->typmod-bcharlen;
+	}
+
+
+	result->typmod = -1;
+	SET_VARSIZE(result, sizeof(UChar) * curlen + MCHARHDRSZ);
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_MCHAR(result);
+}
+
+static inline int
+mvarchar_icase_compare( MVarChar *a, MVarChar *b ) {
+	
+	return UCharCaseCompare( 
+		a->data, lengthWithoutSpaceVarChar(a),
+		b->data, lengthWithoutSpaceVarChar(b)
+	);
+}
+
+static inline int
+mvarchar_case_compare( MVarChar *a, MVarChar *b ) {
+	return UCharCompare( 
+		a->data, lengthWithoutSpaceVarChar(a),
+		b->data, lengthWithoutSpaceVarChar(b)
+	);
+}
+
+#define MVARCHARCMPFUNC( c, type, action, ret ) 	\
+PG_FUNCTION_INFO_V1( mvarchar_##c##_##type ); 		\
+Datum      mvarchar_##c##_##type(PG_FUNCTION_ARGS);	\
+Datum												\
+mvarchar_##c##_##type(PG_FUNCTION_ARGS) {			\
+	MVarChar *a = PG_GETARG_MVARCHAR(0);			\
+	MVarChar *b = PG_GETARG_MVARCHAR(1);			\
+	int 	res = mvarchar_##c##_compare(a,b);		\
+													\
+	PG_FREE_IF_COPY(a,0);							\
+	PG_FREE_IF_COPY(b,1);							\
+	PG_RETURN_##ret( res action	0 );				\
+}
+
+
+MVARCHARCMPFUNC( case, eq, ==, BOOL )
+MVARCHARCMPFUNC( case, ne, !=, BOOL )
+MVARCHARCMPFUNC( case, lt, <, BOOL )
+MVARCHARCMPFUNC( case, le, <=, BOOL )
+MVARCHARCMPFUNC( case, ge, >=, BOOL )
+MVARCHARCMPFUNC( case, gt, >, BOOL )
+MVARCHARCMPFUNC( case, cmp, +, INT32 )
+
+MVARCHARCMPFUNC( icase, eq, ==, BOOL )
+MVARCHARCMPFUNC( icase, ne, !=, BOOL )
+MVARCHARCMPFUNC( icase, lt, <, BOOL )
+MVARCHARCMPFUNC( icase, le, <=, BOOL )
+MVARCHARCMPFUNC( icase, ge, >=, BOOL )
+MVARCHARCMPFUNC( icase, gt, >, BOOL )
+MVARCHARCMPFUNC( icase, cmp, +, INT32 )
+
+PG_FUNCTION_INFO_V1( mvarchar_larger );
+Datum mvarchar_larger( PG_FUNCTION_ARGS );
+Datum
+mvarchar_larger( PG_FUNCTION_ARGS ) {
+	MVarChar *a = PG_GETARG_MVARCHAR(0);
+	MVarChar *b = PG_GETARG_MVARCHAR(1);
+	MVarChar *r;
+
+	r = ( mvarchar_icase_compare(a,b) > 0 ) ? a : b;
+
+	PG_RETURN_MVARCHAR(r);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_smaller );
+Datum mvarchar_smaller( PG_FUNCTION_ARGS );
+Datum
+mvarchar_smaller( PG_FUNCTION_ARGS ) {
+	MVarChar *a = PG_GETARG_MVARCHAR(0);
+	MVarChar *b = PG_GETARG_MVARCHAR(1);
+	MVarChar *r;
+
+	r = ( mvarchar_icase_compare(a,b) < 0 ) ? a : b;
+
+	PG_RETURN_MVARCHAR(r);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_concat );
+Datum mvarchar_concat( PG_FUNCTION_ARGS );
+Datum
+mvarchar_concat( PG_FUNCTION_ARGS ) {
+	MVarChar *a = PG_GETARG_MVARCHAR(0);
+	MVarChar *b = PG_GETARG_MVARCHAR(1);
+	MVarChar *result;
+	int	curlen;
+	int	acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)),
+		bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b));
+
+	result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * (acharlen + bcharlen) );
+
+	curlen = UVARCHARLENGTH( a );
+	if ( curlen > 0 )
+		memcpy( result->data, a->data, MVARCHARLENGTH(a) );
+
+	if ( UVARCHARLENGTH(b) > 0 ) {
+		memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) );
+		curlen += UVARCHARLENGTH( b );
+	}
+
+	SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ);
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+PG_FUNCTION_INFO_V1( mchar_mvarchar_concat );
+Datum mchar_mvarchar_concat( PG_FUNCTION_ARGS );
+Datum                                            
+mchar_mvarchar_concat( PG_FUNCTION_ARGS ) {
+	MChar *a = PG_GETARG_MCHAR(0);
+	MVarChar *b = PG_GETARG_MVARCHAR(1);
+	MVarChar *result;
+	int	curlen, maxcharlen;
+	int	acharlen = u_countChar32(a->data, UCHARLENGTH(a)),
+		bcharlen = u_countChar32(b->data, UVARCHARLENGTH(b));
+
+	maxcharlen = ((a->typmod<=0) ? acharlen : a->typmod) + bcharlen;
+
+	result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen );
+
+	curlen = UCHARLENGTH( a );
+	if ( curlen > 0 )
+		memcpy( result->data, a->data, MCHARLENGTH(a) );
+	if ( a->typmod > 0 && acharlen < a->typmod  ) {
+		FillWhiteSpace( result->data + curlen, a->typmod-acharlen );
+		curlen += a->typmod-acharlen;
+	}
+
+	if ( UVARCHARLENGTH(b) > 0 ) {
+		memcpy( result->data + curlen, b->data, MVARCHARLENGTH( b ) );
+		curlen += UVARCHARLENGTH( b );
+	}
+
+	SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ);
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+PG_FUNCTION_INFO_V1( mvarchar_mchar_concat );
+Datum mvarchar_mchar_concat( PG_FUNCTION_ARGS );
+Datum                                            
+mvarchar_mchar_concat( PG_FUNCTION_ARGS ) {
+	MVarChar *a = PG_GETARG_MVARCHAR(0);
+	MChar *b = PG_GETARG_MCHAR(1);
+	MVarChar *result;
+	int	curlen, maxcharlen;
+	int	acharlen = u_countChar32(a->data, UVARCHARLENGTH(a)),
+		bcharlen = u_countChar32(b->data, UCHARLENGTH(b));
+
+	maxcharlen = acharlen + ((b->typmod<=0) ? bcharlen : b->typmod);
+
+	result = (MVarChar*)palloc( MVARCHARHDRSZ + sizeof(UChar) * 2 * maxcharlen );
+
+	curlen = UVARCHARLENGTH( a );
+	if ( curlen > 0 )
+		memcpy( result->data, a->data, MVARCHARLENGTH(a) );
+
+	if ( UCHARLENGTH(b) > 0 ) {
+		memcpy( result->data + curlen, b->data, MCHARLENGTH( b ) );
+		curlen += UCHARLENGTH( b );
+	}
+	if ( b->typmod > 0 && bcharlen < b->typmod  ) {
+		FillWhiteSpace( result->data + curlen, b->typmod-bcharlen );
+		curlen += b->typmod-bcharlen;
+	}
+
+	SET_VARSIZE(result, sizeof(UChar) * curlen + MVARCHARHDRSZ);
+
+	PG_FREE_IF_COPY(a,0);
+	PG_FREE_IF_COPY(b,1);
+
+	PG_RETURN_MVARCHAR(result);
+}
+
+/*
+ * mchar <> mvarchar 
+ */
+static inline int
+mc_mv_icase_compare( MChar *a, MVarChar *b ) {
+	return UCharCaseCompare( 
+		a->data, lengthWithoutSpaceChar(a),
+		b->data, lengthWithoutSpaceVarChar(b)
+	);
+}
+
+static inline int
+mc_mv_case_compare( MChar *a, MVarChar *b ) {
+	return UCharCompare( 
+		a->data, lengthWithoutSpaceChar(a),
+		b->data, lengthWithoutSpaceVarChar(b)
+	);
+}
+
+#define MC_MV_CHARCMPFUNC( c, type, action, ret ) 		\
+PG_FUNCTION_INFO_V1( mc_mv_##c##_##type ); 		\
+Datum      mc_mv_##c##_##type(PG_FUNCTION_ARGS);\
+Datum											\
+mc_mv_##c##_##type(PG_FUNCTION_ARGS) {			\
+	MChar *a = PG_GETARG_MCHAR(0);				\
+	MVarChar *b = PG_GETARG_MVARCHAR(1);		\
+	int 	res = mc_mv_##c##_compare(a,b);		\
+												\
+	PG_FREE_IF_COPY(a,0);						\
+	PG_FREE_IF_COPY(b,1);						\
+	PG_RETURN_##ret( res action 0 );			\
+}
+
+
+MC_MV_CHARCMPFUNC( case, eq, ==, BOOL )
+MC_MV_CHARCMPFUNC( case, ne, !=, BOOL )
+MC_MV_CHARCMPFUNC( case, lt, <, BOOL )
+MC_MV_CHARCMPFUNC( case, le, <=, BOOL )
+MC_MV_CHARCMPFUNC( case, ge, >=, BOOL )
+MC_MV_CHARCMPFUNC( case, gt, >, BOOL )
+MC_MV_CHARCMPFUNC( case, cmp, +, INT32 )
+
+MC_MV_CHARCMPFUNC( icase, eq, ==, BOOL )
+MC_MV_CHARCMPFUNC( icase, ne, !=, BOOL )
+MC_MV_CHARCMPFUNC( icase, lt, <, BOOL )
+MC_MV_CHARCMPFUNC( icase, le, <=, BOOL )
+MC_MV_CHARCMPFUNC( icase, ge, >=, BOOL )
+MC_MV_CHARCMPFUNC( icase, gt, >, BOOL )
+MC_MV_CHARCMPFUNC( icase, cmp, +, INT32 )
+
+/*
+ * mvarchar <> mchar 
+ */
+static inline int
+mv_mc_icase_compare( MVarChar *a, MChar *b ) {
+	return UCharCaseCompare( 
+		a->data, lengthWithoutSpaceVarChar(a),
+		b->data, lengthWithoutSpaceChar(b)
+	);
+}
+
+static inline int
+mv_mc_case_compare( MVarChar *a, MChar *b ) {
+	return UCharCompare( 
+		a->data, lengthWithoutSpaceVarChar(a),
+		b->data, lengthWithoutSpaceChar(b)
+	);
+}
+
+#define MV_MC_CHARCMPFUNC( c, type, action, ret ) 		\
+PG_FUNCTION_INFO_V1( mv_mc_##c##_##type ); 		\
+Datum      mv_mc_##c##_##type(PG_FUNCTION_ARGS);\
+Datum											\
+mv_mc_##c##_##type(PG_FUNCTION_ARGS) {			\
+	MVarChar *a = PG_GETARG_MVARCHAR(0);		\
+	MChar *b = PG_GETARG_MCHAR(1);				\
+	int 	res = mv_mc_##c##_compare(a,b);		\
+												\
+	PG_FREE_IF_COPY(a,0);						\
+	PG_FREE_IF_COPY(b,1);						\
+	PG_RETURN_##ret( res action 0 );			\
+}
+
+
+MV_MC_CHARCMPFUNC( case, eq, ==, BOOL )
+MV_MC_CHARCMPFUNC( case, ne, !=, BOOL )
+MV_MC_CHARCMPFUNC( case, lt, <, BOOL )
+MV_MC_CHARCMPFUNC( case, le, <=, BOOL )
+MV_MC_CHARCMPFUNC( case, ge, >=, BOOL )
+MV_MC_CHARCMPFUNC( case, gt, >, BOOL )
+MV_MC_CHARCMPFUNC( case, cmp, +, INT32 )
+
+MV_MC_CHARCMPFUNC( icase, eq, ==, BOOL )
+MV_MC_CHARCMPFUNC( icase, ne, !=, BOOL )
+MV_MC_CHARCMPFUNC( icase, lt, <, BOOL )
+MV_MC_CHARCMPFUNC( icase, le, <=, BOOL )
+MV_MC_CHARCMPFUNC( icase, ge, >=, BOOL )
+MV_MC_CHARCMPFUNC( icase, gt, >, BOOL )
+MV_MC_CHARCMPFUNC( icase, cmp, +, INT32 )
+
+#define NULLHASHVALUE       (-2147483647)
+
+#define FULLEQ_FUNC(type, cmpfunc, hashfunc)            \
+PG_FUNCTION_INFO_V1( isfulleq_##type );                 \
+Datum   isfulleq_##type(PG_FUNCTION_ARGS);              \
+Datum                                                   \
+isfulleq_##type(PG_FUNCTION_ARGS) {                     \
+    if ( PG_ARGISNULL(0) && PG_ARGISNULL(1) )           \
+        PG_RETURN_BOOL(true);                           \
+    else if ( PG_ARGISNULL(0) || PG_ARGISNULL(1) )      \
+        PG_RETURN_BOOL(false);                          \
+                                                        \
+    PG_RETURN_DATUM( DirectFunctionCall2( cmpfunc,      \
+            PG_GETARG_DATUM(0),                         \
+            PG_GETARG_DATUM(1)                          \
+    ) );                                                \
+}                                                       \
+                                                        \
+PG_FUNCTION_INFO_V1( fullhash_##type );                 \
+Datum   fullhash_##type(PG_FUNCTION_ARGS);              \
+Datum                                                   \
+fullhash_##type(PG_FUNCTION_ARGS) {                     \
+    if ( PG_ARGISNULL(0) )                              \
+        PG_RETURN_INT32(NULLHASHVALUE);                 \
+                                                        \
+    PG_RETURN_DATUM( DirectFunctionCall1( hashfunc,     \
+            PG_GETARG_DATUM(0)                          \
+    ) );                                                \
+}
+
+FULLEQ_FUNC( mchar, mchar_icase_eq, mchar_hash );
+FULLEQ_FUNC( mvarchar, mvarchar_icase_eq, mvarchar_hash );
+
diff --git a/contrib/mchar/mchar_proc.c b/contrib/mchar/mchar_proc.c
new file mode 100644
index 00000000000..edabfb5eb66
--- /dev/null
+++ b/contrib/mchar/mchar_proc.c
@@ -0,0 +1,315 @@
+#include "mchar.h"
+#include "mb/pg_wchar.h"
+
+PG_FUNCTION_INFO_V1(mchar_length);
+Datum       mchar_length(PG_FUNCTION_ARGS);
+
+Datum
+mchar_length(PG_FUNCTION_ARGS) {
+	MChar	*m = PG_GETARG_MCHAR(0);
+	int32	l = UCHARLENGTH(m);
+
+	while( l>0 && m_isspace( m->data[ l-1 ] ) )
+		l--;
+
+	l = u_countChar32(m->data, l);
+
+	PG_FREE_IF_COPY(m,0);
+
+	PG_RETURN_INT32(l);
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_length);
+Datum       mvarchar_length(PG_FUNCTION_ARGS);
+
+Datum
+mvarchar_length(PG_FUNCTION_ARGS) {
+	MVarChar	*m = PG_GETARG_MVARCHAR(0);
+	int32	l = UVARCHARLENGTH(m);
+
+	while( l>0 && m_isspace( m->data[ l-1 ] ) )
+		l--;
+
+	l = u_countChar32(m->data, l);
+
+	PG_FREE_IF_COPY(m,0);
+
+	PG_RETURN_INT32(l);
+}
+
+static int32
+uchar_substring( 
+		UChar *str, int32 strl,
+		int32 start, int32 length, bool length_not_specified,
+		UChar *dst) {
+	int32	S = start-1;	/* start position */
+	int32	S1;			/* adjusted start position */
+	int32	L1;			/* adjusted substring length */
+	int32	subbegin=0, subend=0;
+
+	S1 = Max(S, 0);
+	if (length_not_specified)
+		L1 = -1;
+	else {
+		/* end position */
+		int32 E = S + length;
+
+		/*
+		 * A negative value for L is the only way for the end position to
+		 * be before the start. SQL99 says to throw an error.
+		 */
+
+		if (E < S)
+			ereport(ERROR,
+					(errcode(ERRCODE_SUBSTRING_ERROR),
+					 errmsg("negative substring length not allowed")));
+
+		/*
+		 * A zero or negative value for the end position can happen if the
+		 * start was negative or one. SQL99 says to return a zero-length
+		 * string.
+		 */
+		if (E < 0) 
+			return 0;
+
+		L1 = E - S1;
+	}
+		 
+	U16_FWD_N( str, subbegin, strl, S1 );
+	if ( subbegin >= strl ) 
+		return 0;
+	subend = subbegin;
+	U16_FWD_N( str, subend, strl, L1 );
+
+	memcpy( dst, str+subbegin, sizeof(UChar)*(subend-subbegin) );
+
+	return subend-subbegin;
+}
+
+PG_FUNCTION_INFO_V1(mchar_substring);
+Datum       mchar_substring(PG_FUNCTION_ARGS);
+Datum
+mchar_substring(PG_FUNCTION_ARGS) {
+	MChar	*src = PG_GETARG_MCHAR(0);
+	MChar	*dst;
+	int32	length;
+
+	dst = (MChar*)palloc( VARSIZE(src) );
+	length = uchar_substring( 
+		src->data, UCHARLENGTH(src),
+		PG_GETARG_INT32(1), PG_GETARG_INT32(2), false,
+		dst->data);
+
+	dst->typmod = src->typmod;
+	SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar));
+	
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MCHAR(dst);
+}
+
+PG_FUNCTION_INFO_V1(mchar_substring_no_len);
+Datum       mchar_substring_no_len(PG_FUNCTION_ARGS);
+Datum
+mchar_substring_no_len(PG_FUNCTION_ARGS) {
+	MChar	*src = PG_GETARG_MCHAR(0);
+	MChar	*dst;
+	int32	length;
+
+	dst = (MChar*)palloc( VARSIZE(src) );
+	length = uchar_substring( 
+		src->data, UCHARLENGTH(src),
+		PG_GETARG_INT32(1), -1, true,
+		dst->data);
+
+	dst->typmod = src->typmod;
+	SET_VARSIZE(dst, MCHARHDRSZ + length *sizeof(UChar));
+
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MCHAR(dst);
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_substring);
+Datum       mvarchar_substring(PG_FUNCTION_ARGS);
+Datum
+mvarchar_substring(PG_FUNCTION_ARGS) {
+	MVarChar	*src = PG_GETARG_MVARCHAR(0);
+	MVarChar	*dst;
+	int32	length;
+
+	dst = (MVarChar*)palloc( VARSIZE(src) );
+	length = uchar_substring( 
+		src->data, UVARCHARLENGTH(src),
+		PG_GETARG_INT32(1), PG_GETARG_INT32(2), false,
+		dst->data);
+
+	SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar));
+
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MVARCHAR(dst);
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_substring_no_len);
+Datum       mvarchar_substring_no_len(PG_FUNCTION_ARGS);
+Datum
+mvarchar_substring_no_len(PG_FUNCTION_ARGS) {
+	MVarChar	*src = PG_GETARG_MVARCHAR(0);
+	MVarChar	*dst;
+	int32	length;
+
+	dst = (MVarChar*)palloc( VARSIZE(src) );
+	length = uchar_substring( 
+		src->data, UVARCHARLENGTH(src),
+		PG_GETARG_INT32(1), -1, true,
+		dst->data);
+
+	SET_VARSIZE(dst, MVARCHARHDRSZ + length *sizeof(UChar));
+
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MVARCHAR(dst);
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_hash);
+Datum
+mvarchar_hash(PG_FUNCTION_ARGS) {
+	MVarChar	*src = PG_GETARG_MVARCHAR(0);
+	Datum		res;
+
+	res = hash_uchar( src->data, lengthWithoutSpaceVarChar(src) );
+
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_DATUM( res );
+}
+
+PG_FUNCTION_INFO_V1(mchar_hash);
+Datum
+mchar_hash(PG_FUNCTION_ARGS) {
+	MChar	*src = PG_GETARG_MCHAR(0);
+	Datum	res;
+
+	res = hash_uchar( src->data, lengthWithoutSpaceChar(src) );
+
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_DATUM( res );
+}
+
+PG_FUNCTION_INFO_V1(mchar_upper);
+Datum       mchar_upper(PG_FUNCTION_ARGS);
+Datum
+mchar_upper(PG_FUNCTION_ARGS) {
+	MChar	*src = PG_GETARG_MCHAR(0);
+	MChar	*dst = (MChar*)palloc( VARSIZE(src) * 2 );
+
+	dst->len = MCHARHDRSZ;
+	dst->typmod = src->typmod;
+	if ( UCHARLENGTH(src) != 0 ) {
+		int 		length;
+		UErrorCode	err=0;
+
+		length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ,
+								src->data, UCHARLENGTH(src),
+								NULL, &err );
+
+		Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ );
+
+		if ( U_FAILURE(err) )
+			elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err,  u_errorName(err));
+
+		dst->len += sizeof(UChar) * length;	
+	}
+
+	SET_VARSIZE( dst, dst->len );
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MCHAR( dst );
+}
+
+PG_FUNCTION_INFO_V1(mchar_lower);
+Datum       mchar_lower(PG_FUNCTION_ARGS);
+Datum
+mchar_lower(PG_FUNCTION_ARGS) {
+	MChar	*src = PG_GETARG_MCHAR(0);
+	MChar	*dst = (MChar*)palloc( VARSIZE(src) * 2 );
+
+	dst->len = MCHARHDRSZ;
+	dst->typmod = src->typmod;
+	if ( UCHARLENGTH(src) != 0 ) {
+		int 		length;
+		UErrorCode	err=0;
+
+		length = u_strToLower( dst->data, VARSIZE(src) * 2 - MCHARHDRSZ,
+								src->data, UCHARLENGTH(src),
+								NULL, &err );
+
+		Assert( length <= VARSIZE(src) * 2 - MCHARHDRSZ );
+
+		if ( U_FAILURE(err) )
+			elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err,  u_errorName(err));
+
+		dst->len += sizeof(UChar) * length;	
+	}
+
+	SET_VARSIZE( dst, dst->len );
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MCHAR( dst );
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_upper);
+Datum       mvarchar_upper(PG_FUNCTION_ARGS);
+Datum
+mvarchar_upper(PG_FUNCTION_ARGS) {
+	MVarChar	*src = PG_GETARG_MVARCHAR(0);
+	MVarChar	*dst = (MVarChar*)palloc( VARSIZE(src) * 2 );
+
+	dst->len = MVARCHARHDRSZ;
+
+	if ( UVARCHARLENGTH(src) != 0 ) {
+		int 		length;
+		UErrorCode	err=0;
+
+		length = u_strToUpper( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ,
+								src->data, UVARCHARLENGTH(src),
+								NULL, &err );
+
+		Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ );
+
+		if ( U_FAILURE(err) )
+			elog(ERROR,"ICU u_strToUpper fails and returns %d (%s)", err,  u_errorName(err));
+
+		dst->len += sizeof(UChar) * length;	
+	}
+
+	SET_VARSIZE( dst, dst->len );
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MVARCHAR( dst );
+}
+
+PG_FUNCTION_INFO_V1(mvarchar_lower);
+Datum       mvarchar_lower(PG_FUNCTION_ARGS);
+Datum
+mvarchar_lower(PG_FUNCTION_ARGS) {
+	MVarChar	*src = PG_GETARG_MVARCHAR(0);
+	MVarChar	*dst = (MVarChar*)palloc( VARSIZE(src) * 2 );
+
+	dst->len = MVARCHARHDRSZ;
+
+	if ( UVARCHARLENGTH(src) != 0 ) {
+		int 		length;
+		UErrorCode	err=0;
+
+		length = u_strToLower( dst->data, VARSIZE(src) * 2 - MVARCHARHDRSZ,
+								src->data, UVARCHARLENGTH(src),
+								NULL, &err );
+
+		Assert( length <= VARSIZE(src) * 2 - MVARCHARHDRSZ );
+
+		if ( U_FAILURE(err) )
+			elog(ERROR,"ICU u_strToLower fails and returns %d (%s)", err,  u_errorName(err));
+
+		dst->len += sizeof(UChar) * length;	
+	}
+
+	SET_VARSIZE( dst, dst->len );
+	PG_FREE_IF_COPY(src, 0);
+	PG_RETURN_MVARCHAR( dst );
+}
+
+
diff --git a/contrib/mchar/mchar_recode.c b/contrib/mchar/mchar_recode.c
new file mode 100644
index 00000000000..12bc6d4f3aa
--- /dev/null
+++ b/contrib/mchar/mchar_recode.c
@@ -0,0 +1,166 @@
+#include "mchar.h"
+#include "access/hash.h"
+
+#include "unicode/ucol.h"
+#include "unicode/ucnv.h"
+
+static UConverter *cnvDB = NULL;
+static UCollator  *colCaseInsensitive = NULL;
+static UCollator  *colCaseSensitive = NULL;
+
+static void
+createUObjs() {
+	if ( !cnvDB ) {
+		UErrorCode err = 0;
+
+		if ( GetDatabaseEncoding() == PG_UTF8 )
+			cnvDB = ucnv_open("UTF8", &err);
+	 	else
+			cnvDB = ucnv_open(NULL, &err);
+		if ( U_FAILURE(err) || cnvDB == NULL ) 
+			elog(ERROR,"ICU ucnv_open returns %d (%s)", err,  u_errorName(err));
+	}
+
+	if ( !colCaseInsensitive ) {
+		UErrorCode err = 0;
+
+		colCaseInsensitive = ucol_open("", &err);
+		if ( U_FAILURE(err) || cnvDB == NULL ) { 
+			if ( colCaseSensitive )
+				ucol_close( colCaseSensitive );
+			colCaseSensitive = NULL;
+			elog(ERROR,"ICU ucol_open returns %d (%s)", err,  u_errorName(err));
+		}
+
+		ucol_setStrength( colCaseInsensitive, UCOL_SECONDARY );
+	}
+
+	if ( !colCaseSensitive ) {
+		UErrorCode err = 0;
+
+		colCaseSensitive = ucol_open("", &err);
+		if ( U_FAILURE(err) || cnvDB == NULL ) { 
+			if ( colCaseSensitive )
+				ucol_close( colCaseSensitive );
+			colCaseSensitive = NULL;
+			elog(ERROR,"ICU ucol_open returns %d (%s)", err,  u_errorName(err));
+		}
+
+		ucol_setAttribute(colCaseSensitive, UCOL_CASE_FIRST, UCOL_UPPER_FIRST, &err);				
+		if (U_FAILURE(err)) {
+			if ( colCaseSensitive )
+				ucol_close( colCaseSensitive );
+			colCaseSensitive = NULL;
+			elog(ERROR,"ICU ucol_setAttribute returns %d (%s)", err,  u_errorName(err));
+		}
+	}
+}
+
+int
+Char2UChar(const char * src, int srclen, UChar *dst) {
+	int dstlen=0;
+	UErrorCode err = 0;
+
+	createUObjs();
+	dstlen = ucnv_toUChars( cnvDB, dst, srclen*4, src, srclen, &err ); 
+	if ( U_FAILURE(err)) 
+		elog(ERROR,"ICU ucnv_toUChars returns %d (%s)", err,  u_errorName(err));
+
+	return dstlen;
+}
+
+int
+UChar2Char(const UChar * src, int srclen, char *dst) {
+	int dstlen=0;
+	UErrorCode err = 0;
+
+	createUObjs();
+	dstlen = ucnv_fromUChars( cnvDB, dst, srclen*4, src, srclen, &err ); 
+	if ( U_FAILURE(err) )
+		elog(ERROR,"ICU ucnv_fromUChars returns %d (%s)", err,  u_errorName(err));
+
+	return dstlen;
+}
+
+int
+UChar2Wchar(UChar * src, int srclen, pg_wchar *dst) {
+	int dstlen=0;
+	char	*utf = palloc(sizeof(char)*srclen*4);
+
+	dstlen = UChar2Char(src, srclen, utf);
+	dstlen = pg_mb2wchar_with_len( utf, dst, dstlen );
+	pfree(utf);
+
+	return dstlen;
+}
+
+static UChar UCharWhiteSpace = 0;
+
+void
+FillWhiteSpace( UChar *dst, int n ) {
+	if ( UCharWhiteSpace == 0 ) {
+		int len;
+		UErrorCode err = 0;
+
+		u_strFromUTF8( &UCharWhiteSpace, 1, &len, " ", 1, &err);
+
+		Assert( len==1 );
+		Assert( !U_FAILURE(err) );
+	}
+
+	while( n-- > 0 )
+		*dst++ = UCharWhiteSpace;
+}
+
+int
+UCharCaseCompare(UChar * a, int alen, UChar *b, int blen) {
+
+	createUObjs();
+
+	return (int)ucol_strcoll( colCaseInsensitive,
+							  a, alen,
+							  b, blen);
+}
+
+int
+UCharCompare(UChar * a, int alen, UChar *b, int blen) {
+
+	createUObjs();
+
+	return  (int)ucol_strcoll( colCaseSensitive,
+							  a, alen,
+							  b, blen);
+}
+
+Datum
+hash_uchar( UChar *s, int len ) {
+	int32   length = INT_MAX, i;
+	Datum res;
+	uint8   *d;
+
+	if ( len == 0 )
+		return hash_any( NULL, 0 );
+
+	createUObjs();
+
+	for(i=2;; i*=2)
+	{
+		d =  palloc(len * i);
+		length = ucol_getSortKey(colCaseInsensitive, s, len, d, len*i);
+
+		if (length == 0)
+			elog(ERROR,"ICU ucol_getSortKey fails");
+
+		if (length < len*i)
+			break;
+
+		pfree(d);
+	}
+
+	res = hash_any( (unsigned char*) d,  length);
+
+	pfree(d);
+
+	return res;
+}
+
diff --git a/contrib/mchar/sql/compat.sql b/contrib/mchar/sql/compat.sql
new file mode 100644
index 00000000000..d5b6a986960
--- /dev/null
+++ b/contrib/mchar/sql/compat.sql
@@ -0,0 +1,11 @@
+--- table based checks
+
+select '<' || ch || '>', '<' || vch || '>' from chvch;
+select * from chvch where vch = 'One space';
+select * from chvch where vch = 'One space ';
+
+select * from ch where chcol = 'abcd' order by chcol;
+select * from ch t1 join ch t2 on t1.chcol = t2.chcol order by t1.chcol, t2.chcol;
+select * from ch where chcol > 'abcd' and chcol<'ee';
+select * from ch order by chcol;
+
diff --git a/contrib/mchar/sql/init.sql b/contrib/mchar/sql/init.sql
new file mode 100644
index 00000000000..04310044458
--- /dev/null
+++ b/contrib/mchar/sql/init.sql
@@ -0,0 +1,23 @@
+CREATE EXTENSION mchar;
+
+create table ch (
+	chcol mchar(32)
+) without oids;
+
+insert into ch values('abcd');
+insert into ch values('AbcD');
+insert into ch values('abcz');
+insert into ch values('defg');
+insert into ch values('dEfg');
+insert into ch values('ee');
+insert into ch values('Ee');
+
+create table chvch (
+    ch      mchar(12),
+	vch     mvarchar(12)
+) without oids;
+
+insert into chvch values('No spaces', 'No spaces');
+insert into chvch values('One space ', 'One space ');
+insert into chvch values('1 space', '1 space ');
+
diff --git a/contrib/mchar/sql/like.sql b/contrib/mchar/sql/like.sql
new file mode 100644
index 00000000000..c29cf4eb6f9
--- /dev/null
+++ b/contrib/mchar/sql/like.sql
@@ -0,0 +1,231 @@
+-- simplest examples
+-- E061-04 like predicate
+set standard_conforming_strings=off;
+
+SELECT 'hawkeye'::mchar LIKE 'h%' AS "true";
+SELECT 'hawkeye'::mchar NOT LIKE 'h%' AS "false";
+
+SELECT 'hawkeye'::mchar LIKE 'H%' AS "true";
+SELECT 'hawkeye'::mchar NOT LIKE 'H%' AS "false";
+
+SELECT 'hawkeye'::mchar LIKE 'indio%' AS "false";
+SELECT 'hawkeye'::mchar NOT LIKE 'indio%' AS "true";
+
+SELECT 'hawkeye'::mchar LIKE 'h%eye' AS "true";
+SELECT 'hawkeye'::mchar NOT LIKE 'h%eye' AS "false";
+
+SELECT 'indio'::mchar LIKE '_ndio' AS "true";
+SELECT 'indio'::mchar NOT LIKE '_ndio' AS "false";
+
+SELECT 'indio'::mchar LIKE 'in__o' AS "true";
+SELECT 'indio'::mchar NOT LIKE 'in__o' AS "false";
+
+SELECT 'indio'::mchar LIKE 'in_o' AS "false";
+SELECT 'indio'::mchar NOT LIKE 'in_o' AS "true";
+
+SELECT 'hawkeye'::mvarchar LIKE 'h%' AS "true";
+SELECT 'hawkeye'::mvarchar NOT LIKE 'h%' AS "false";
+
+SELECT 'hawkeye'::mvarchar LIKE 'H%' AS "true";
+SELECT 'hawkeye'::mvarchar NOT LIKE 'H%' AS "false";
+
+SELECT 'hawkeye'::mvarchar LIKE 'indio%' AS "false";
+SELECT 'hawkeye'::mvarchar NOT LIKE 'indio%' AS "true";
+
+SELECT 'hawkeye'::mvarchar LIKE 'h%eye' AS "true";
+SELECT 'hawkeye'::mvarchar NOT LIKE 'h%eye' AS "false";
+
+SELECT 'indio'::mvarchar LIKE '_ndio' AS "true";
+SELECT 'indio'::mvarchar NOT LIKE '_ndio' AS "false";
+
+SELECT 'indio'::mvarchar LIKE 'in__o' AS "true";
+SELECT 'indio'::mvarchar NOT LIKE 'in__o' AS "false";
+
+SELECT 'indio'::mvarchar LIKE 'in_o' AS "false";
+SELECT 'indio'::mvarchar NOT LIKE 'in_o' AS "true";
+
+-- unused escape character
+SELECT 'hawkeye'::mchar LIKE 'h%'::mchar ESCAPE '#' AS "true";
+SELECT 'hawkeye'::mchar NOT LIKE 'h%'::mchar ESCAPE '#' AS "false";
+
+SELECT 'indio'::mchar LIKE 'ind_o'::mchar ESCAPE '$' AS "true";
+SELECT 'indio'::mchar NOT LIKE 'ind_o'::mchar ESCAPE '$' AS "false";
+
+-- escape character
+-- E061-05 like predicate with escape clause
+SELECT 'h%'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "true";
+SELECT 'h%'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "false";
+
+SELECT 'h%wkeye'::mchar LIKE 'h#%'::mchar ESCAPE '#' AS "false";
+SELECT 'h%wkeye'::mchar NOT LIKE 'h#%'::mchar ESCAPE '#' AS "true";
+
+SELECT 'h%wkeye'::mchar LIKE 'h#%%'::mchar ESCAPE '#' AS "true";
+SELECT 'h%wkeye'::mchar NOT LIKE 'h#%%'::mchar ESCAPE '#' AS "false";
+
+SELECT 'h%awkeye'::mchar LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "true";
+SELECT 'h%awkeye'::mchar NOT LIKE 'h#%a%k%e'::mchar ESCAPE '#' AS "false";
+
+SELECT 'indio'::mchar LIKE '_ndio'::mchar ESCAPE '$' AS "true";
+SELECT 'indio'::mchar NOT LIKE '_ndio'::mchar ESCAPE '$' AS "false";
+
+SELECT 'i_dio'::mchar LIKE 'i$_d_o'::mchar ESCAPE '$' AS "true";
+SELECT 'i_dio'::mchar NOT LIKE 'i$_d_o'::mchar ESCAPE '$' AS "false";
+
+SELECT 'i_dio'::mchar LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "false";
+SELECT 'i_dio'::mchar NOT LIKE 'i$_nd_o'::mchar ESCAPE '$' AS "true";
+
+SELECT 'i_dio'::mchar LIKE 'i$_d%o'::mchar ESCAPE '$' AS "true";
+SELECT 'i_dio'::mchar NOT LIKE 'i$_d%o'::mchar ESCAPE '$' AS "false";
+
+-- escape character same as pattern character
+SELECT 'maca'::mchar LIKE 'm%aca' ESCAPE '%'::mchar AS "true";
+SELECT 'maca'::mchar NOT LIKE 'm%aca' ESCAPE '%'::mchar AS "false";
+
+SELECT 'ma%a'::mchar LIKE 'm%a%%a' ESCAPE '%'::mchar AS "true";
+SELECT 'ma%a'::mchar NOT LIKE 'm%a%%a' ESCAPE '%'::mchar AS "false";
+
+SELECT 'bear'::mchar LIKE 'b_ear' ESCAPE '_'::mchar AS "true";
+SELECT 'bear'::mchar NOT LIKE 'b_ear'::mchar ESCAPE '_' AS "false";
+
+SELECT 'be_r'::mchar LIKE 'b_e__r' ESCAPE '_'::mchar AS "true";
+SELECT 'be_r'::mchar NOT LIKE 'b_e__r' ESCAPE '_'::mchar AS "false";
+
+SELECT 'be_r'::mchar LIKE '__e__r' ESCAPE '_'::mchar AS "false";
+SELECT 'be_r'::mchar NOT LIKE '__e__r'::mchar ESCAPE '_' AS "true";
+
+-- unused escape character
+SELECT 'hawkeye'::mvarchar LIKE 'h%'::mvarchar ESCAPE '#' AS "true";
+SELECT 'hawkeye'::mvarchar NOT LIKE 'h%'::mvarchar ESCAPE '#' AS "false";
+
+SELECT 'indio'::mvarchar LIKE 'ind_o'::mvarchar ESCAPE '$' AS "true";
+SELECT 'indio'::mvarchar NOT LIKE 'ind_o'::mvarchar ESCAPE '$' AS "false";
+
+-- escape character
+-- E061-05 like predicate with escape clause
+SELECT 'h%'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "true";
+SELECT 'h%'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "false";
+
+SELECT 'h%wkeye'::mvarchar LIKE 'h#%'::mvarchar ESCAPE '#' AS "false";
+SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%'::mvarchar ESCAPE '#' AS "true";
+
+SELECT 'h%wkeye'::mvarchar LIKE 'h#%%'::mvarchar ESCAPE '#' AS "true";
+SELECT 'h%wkeye'::mvarchar NOT LIKE 'h#%%'::mvarchar ESCAPE '#' AS "false";
+
+SELECT 'h%awkeye'::mvarchar LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "true";
+SELECT 'h%awkeye'::mvarchar NOT LIKE 'h#%a%k%e'::mvarchar ESCAPE '#' AS "false";
+
+SELECT 'indio'::mvarchar LIKE '_ndio'::mvarchar ESCAPE '$' AS "true";
+SELECT 'indio'::mvarchar NOT LIKE '_ndio'::mvarchar ESCAPE '$' AS "false";
+
+SELECT 'i_dio'::mvarchar LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "true";
+SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d_o'::mvarchar ESCAPE '$' AS "false";
+
+SELECT 'i_dio'::mvarchar LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "false";
+SELECT 'i_dio'::mvarchar NOT LIKE 'i$_nd_o'::mvarchar ESCAPE '$' AS "true";
+
+SELECT 'i_dio'::mvarchar LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "true";
+SELECT 'i_dio'::mvarchar NOT LIKE 'i$_d%o'::mvarchar ESCAPE '$' AS "false";
+
+-- escape character same as pattern character
+SELECT 'maca'::mvarchar LIKE 'm%aca' ESCAPE '%'::mvarchar AS "true";
+SELECT 'maca'::mvarchar NOT LIKE 'm%aca' ESCAPE '%'::mvarchar AS "false";
+
+SELECT 'ma%a'::mvarchar LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "true";
+SELECT 'ma%a'::mvarchar NOT LIKE 'm%a%%a' ESCAPE '%'::mvarchar AS "false";
+
+SELECT 'bear'::mvarchar LIKE 'b_ear' ESCAPE '_'::mvarchar AS "true";
+SELECT 'bear'::mvarchar NOT LIKE 'b_ear'::mvarchar ESCAPE '_' AS "false";
+
+SELECT 'be_r'::mvarchar LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "true";
+SELECT 'be_r'::mvarchar NOT LIKE 'b_e__r' ESCAPE '_'::mvarchar AS "false";
+
+SELECT 'be_r'::mvarchar LIKE '__e__r' ESCAPE '_'::mvarchar AS "false";
+SELECT 'be_r'::mvarchar NOT LIKE '__e__r'::mvarchar ESCAPE '_' AS "true";
+
+-- similar to
+
+SELECT 'abc'::mchar SIMILAR TO 'abc'::mchar   AS   "true";
+SELECT 'abc'::mchar SIMILAR TO 'a'::mchar      AS  "false";
+SELECT 'abc'::mchar SIMILAR TO '%(b|d)%'::mchar AS "true";
+SELECT 'abc'::mchar SIMILAR TO '(b|c)%'::mchar AS  "false";
+SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar AS "false";
+SELECT 'h%'::mchar SIMILAR TO 'h#%'::mchar ESCAPE '#' AS "true";
+
+SELECT 'abc'::mvarchar SIMILAR TO 'abc'::mvarchar   AS   "true";
+SELECT 'abc'::mvarchar SIMILAR TO 'a'::mvarchar      AS  "false";
+SELECT 'abc'::mvarchar SIMILAR TO '%(b|d)%'::mvarchar AS "true";
+SELECT 'abc'::mvarchar SIMILAR TO '(b|c)%'::mvarchar AS  "false";
+SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar AS "false";
+SELECT 'h%'::mvarchar SIMILAR TO 'h#%'::mvarchar ESCAPE '#' AS "true";
+
+-- index support
+
+SELECT * from ch where chcol like 'aB_d' order by chcol using &<;
+SELECT * from ch where chcol like 'aB%d' order by chcol using &<;
+SELECT * from ch where chcol like 'aB%' order by chcol using &<;
+SELECT * from ch where chcol like '%BC%' order by chcol using &<;
+set enable_seqscan = off;
+explain (costs off)
+SELECT * from ch where chcol like 'aB_d' order by chcol using &<;
+SELECT * from ch where chcol like 'aB_d' order by chcol using &<;
+SELECT * from ch where chcol like 'aB%d' order by chcol using &<;
+SELECT * from ch where chcol like 'aB%' order by chcol using &<;
+SELECT * from ch where chcol like '%BC%' order by chcol using &<;
+set enable_seqscan = on;
+
+
+create table testt (f1 mchar(10));
+insert into testt values ('Abc-000001');
+insert into testt values ('Abc-000002');
+insert into testt values ('0000000001');
+insert into testt values ('0000000002');
+
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+create index testindex on testt(f1);
+set enable_seqscan=off;
+explain (costs off)
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+set enable_seqscan = on;
+drop table testt;
+
+create table testt (f1 mvarchar(10));
+insert into testt values ('Abc-000001');
+insert into testt values ('Abc-000002');
+insert into testt values ('0000000001');
+insert into testt values ('0000000002');
+
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+select * from testt where f1::mchar like E'Abc\\-  %'::mchar;
+select * from testt where f1::mchar like E'   %'::mchar;
+create index testindex on testt(f1);
+set enable_seqscan=off;
+explain (costs off)
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+select f1 from testt where f1::mvarchar like E'Abc\\-%'::mvarchar;
+select * from testt where f1::mchar like E'Abc\\-%'::mchar;
+select * from testt where f1::mchar like E'Abc\\-   %'::mchar;
+select * from testt where f1::mchar like E'   %'::mchar;
+set enable_seqscan = on;
+drop table testt;
+
+
+CREATE TABLE test ( code mchar(5) NOT NULL );
+insert into test values('1111 ');
+insert into test values('111  ');
+insert into test values('11   ');
+insert into test values('1    ');
+
+SELECT * FROM test WHERE code LIKE ('%    ');
+
+set escape_string_warning = off;
+SELECT CASE WHEN ('_'::text SIMILAR TO '[\\_]'::text ESCAPE '\\'::text) THEN TRUE ELSE FALSE END ;
+SELECT CASE WHEN ('_'::mchar SIMILAR TO '[\\_]'::mchar ESCAPE '\\'::mchar) THEN TRUE ELSE FALSE END ;
+SELECT CASE WHEN ('_'::mvarchar SIMILAR TO '[\\_]'::mvarchar ESCAPE '\\'::mvarchar) THEN TRUE ELSE FALSE END ;
+reset escape_string_warning;
+reset standard_conforming_strings;
+
+
diff --git a/contrib/mchar/sql/mchar.sql b/contrib/mchar/sql/mchar.sql
new file mode 100644
index 00000000000..2ec0e659f4f
--- /dev/null
+++ b/contrib/mchar/sql/mchar.sql
@@ -0,0 +1,90 @@
+-- I/O tests
+
+select '1'::mchar;
+select '2  '::mchar;
+select '10          '::mchar;
+
+select '1'::mchar(2);
+select '2 '::mchar(2);
+select '3  '::mchar(2);
+select '10          '::mchar(2);
+
+select '                  '::mchar(10); 
+select '                  '::mchar; 
+
+-- operations & functions
+
+select length('1'::mchar);
+select length('2  '::mchar);
+select length('10          '::mchar);
+
+select length('1'::mchar(2));
+select length('2 '::mchar(2));
+select length('3  '::mchar(2));
+select length('10          '::mchar(2));
+
+select length('                  '::mchar(10)); 
+select length('                  '::mchar); 
+
+select 'asd'::mchar(10) || '>'::mchar(10);
+select length('asd'::mchar(10) || '>'::mchar(10));
+select 'asd'::mchar(2)  || '>'::mchar(10);
+select length('asd'::mchar(2) || '>'::mchar(10));
+
+-- Comparisons
+
+select 'asdf'::mchar = 'aSdf'::mchar;
+select 'asdf'::mchar = 'aSdf '::mchar;
+select 'asdf'::mchar = 'aSdf 1'::mchar(4);
+select 'asdf'::mchar = 'aSdf 1'::mchar(5);
+select 'asdf'::mchar = 'aSdf 1'::mchar(6);
+select 'asdf'::mchar(3) = 'aSdf 1'::mchar(5);
+select 'asdf'::mchar(3) = 'aSdf 1'::mchar(3);
+
+select 'asdf'::mchar < 'aSdf'::mchar;
+select 'asdf'::mchar < 'aSdf '::mchar;
+select 'asdf'::mchar < 'aSdf 1'::mchar(4);
+select 'asdf'::mchar < 'aSdf 1'::mchar(5);
+select 'asdf'::mchar < 'aSdf 1'::mchar(6);
+
+select 'asdf'::mchar <= 'aSdf'::mchar;
+select 'asdf'::mchar <= 'aSdf '::mchar;
+select 'asdf'::mchar <= 'aSdf 1'::mchar(4);
+select 'asdf'::mchar <= 'aSdf 1'::mchar(5);
+select 'asdf'::mchar <= 'aSdf 1'::mchar(6);
+
+select 'asdf'::mchar >= 'aSdf'::mchar;
+select 'asdf'::mchar >= 'aSdf '::mchar;
+select 'asdf'::mchar >= 'aSdf 1'::mchar(4);
+select 'asdf'::mchar >= 'aSdf 1'::mchar(5);
+select 'asdf'::mchar >= 'aSdf 1'::mchar(6);
+
+select 'asdf'::mchar > 'aSdf'::mchar;
+select 'asdf'::mchar > 'aSdf '::mchar;
+select 'asdf'::mchar > 'aSdf 1'::mchar(4);
+select 'asdf'::mchar > 'aSdf 1'::mchar(5);
+select 'asdf'::mchar > 'aSdf 1'::mchar(6);
+
+select max(ch) from chvch;
+select min(ch) from chvch;
+
+select substr('1234567890'::mchar, 3) = '34567890' as "34567890";
+select substr('1234567890'::mchar, 4, 3) = '456' as "456";
+
+select lower('asdfASDF'::mchar);
+select upper('asdfASDF'::mchar);
+
+select 'asd'::mchar == 'aSd'::mchar;
+select 'asd'::mchar == 'aCd'::mchar;
+select 'asd'::mchar == NULL;
+select NULL == 'aCd'::mchar;
+select NULL::mchar == NULL;
+
+
+--Note: here we use different space symbols, be carefull to copy it!
+select v, count(*) from
+(values  (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+set enable_hashagg=off;
+select v, count(*) from
+(values  (1, '4 242'::mchar), (2, '4 242'), (3, 'aSDF'), (4, 'asdf')) as t(i,v) group by v;
+reset enable_hashagg;
diff --git a/contrib/mchar/sql/mm.sql b/contrib/mchar/sql/mm.sql
new file mode 100644
index 00000000000..2e11b937040
--- /dev/null
+++ b/contrib/mchar/sql/mm.sql
@@ -0,0 +1,196 @@
+select 'asd'::mchar::mvarchar;
+select 'asd '::mchar::mvarchar;
+select 'asd'::mchar(2)::mvarchar;
+select 'asd '::mchar(2)::mvarchar;
+select 'asd'::mchar(5)::mvarchar;
+select 'asd '::mchar(5)::mvarchar;
+select 'asd'::mchar::mvarchar(2);
+select 'asd '::mchar::mvarchar(2);
+select 'asd'::mchar(2)::mvarchar(2);
+select 'asd '::mchar(2)::mvarchar(2);
+select 'asd'::mchar(5)::mvarchar(2);
+select 'asd '::mchar(5)::mvarchar(2);
+select 'asd'::mchar::mvarchar(5);
+select 'asd '::mchar::mvarchar(5);
+select 'asd'::mchar(2)::mvarchar(5);
+select 'asd '::mchar(2)::mvarchar(5);
+select 'asd'::mchar(5)::mvarchar(5);
+select 'asd '::mchar(5)::mvarchar(5);
+
+select 'asd'::mvarchar::mchar;
+select 'asd '::mvarchar::mchar;
+select 'asd'::mvarchar(2)::mchar;
+select 'asd '::mvarchar(2)::mchar;
+select 'asd'::mvarchar(5)::mchar;
+select 'asd '::mvarchar(5)::mchar;
+select 'asd'::mvarchar::mchar(2);
+select 'asd '::mvarchar::mchar(2);
+select 'asd'::mvarchar(2)::mchar(2);
+select 'asd '::mvarchar(2)::mchar(2);
+select 'asd'::mvarchar(5)::mchar(2);
+select 'asd '::mvarchar(5)::mchar(2);
+select 'asd'::mvarchar::mchar(5);
+select 'asd '::mvarchar::mchar(5);
+select 'asd'::mvarchar(2)::mchar(5);
+select 'asd '::mvarchar(2)::mchar(5);
+select 'asd'::mvarchar(5)::mchar(5);
+select 'asd '::mvarchar(5)::mchar(5);
+
+select 'asd'::mchar || '123';
+select 'asd'::mchar || '123'::mchar;
+select 'asd'::mchar || '123'::mvarchar;
+
+select 'asd '::mchar || '123';
+select 'asd '::mchar || '123'::mchar;
+select 'asd '::mchar || '123'::mvarchar;
+
+select 'asd '::mchar || '123 ';
+select 'asd '::mchar || '123 '::mchar;
+select 'asd '::mchar || '123 '::mvarchar;
+
+
+select 'asd'::mvarchar || '123';
+select 'asd'::mvarchar || '123'::mchar;
+select 'asd'::mvarchar || '123'::mvarchar;
+
+select 'asd '::mvarchar || '123';
+select 'asd '::mvarchar || '123'::mchar;
+select 'asd '::mvarchar || '123'::mvarchar;
+
+select 'asd '::mvarchar || '123 ';
+select 'asd '::mvarchar || '123 '::mchar;
+select 'asd '::mvarchar || '123 '::mvarchar;
+
+
+select 'asd'::mchar(2) || '123';
+select 'asd'::mchar(2) || '123'::mchar;
+select 'asd'::mchar(2) || '123'::mvarchar;
+
+
+select 'asd '::mchar(2) || '123';
+select 'asd '::mchar(2) || '123'::mchar;
+select 'asd '::mchar(2) || '123'::mvarchar;
+
+
+select 'asd '::mchar(2) || '123 ';
+select 'asd '::mchar(2) || '123 '::mchar;
+select 'asd '::mchar(2) || '123 '::mvarchar;
+
+select 'asd'::mvarchar(2) || '123';
+select 'asd'::mvarchar(2) || '123'::mchar;
+select 'asd'::mvarchar(2) || '123'::mvarchar;
+
+select 'asd '::mvarchar(2) || '123';
+select 'asd '::mvarchar(2) || '123'::mchar;
+select 'asd '::mvarchar(2) || '123'::mvarchar;
+
+select 'asd '::mvarchar(2) || '123 ';
+select 'asd '::mvarchar(2) || '123 '::mchar;
+select 'asd '::mvarchar(2) || '123 '::mvarchar;
+
+select 'asd'::mchar(4) || '143';
+select 'asd'::mchar(4) || '123'::mchar;
+select 'asd'::mchar(4) || '123'::mvarchar;
+
+select 'asd '::mchar(4) || '123';
+select 'asd '::mchar(4) || '123'::mchar;
+select 'asd '::mchar(4) || '123'::mvarchar;
+
+select 'asd '::mchar(4) || '123 ';
+select 'asd '::mchar(4) || '123 '::mchar;
+select 'asd '::mchar(4) || '123 '::mvarchar;
+
+select 'asd'::mvarchar(4) || '123';
+select 'asd'::mvarchar(4) || '123'::mchar;
+select 'asd'::mvarchar(4) || '123'::mvarchar;
+
+select 'asd '::mvarchar(4) || '123';
+select 'asd '::mvarchar(4) || '123'::mchar;
+select 'asd '::mvarchar(4) || '123'::mvarchar;
+
+select 'asd '::mvarchar(4) || '123 ';
+select 'asd '::mvarchar(4) || '123 '::mchar;
+select 'asd '::mvarchar(4) || '123 '::mvarchar;
+
+
+select 'asd '::mvarchar(4) || '123 '::mchar(4);
+select 'asd '::mvarchar(4) || '123 '::mvarchar(4);
+select 'asd '::mvarchar(4) || '123'::mchar(4);
+select 'asd '::mvarchar(4) || '123'::mvarchar(4);
+
+
+select 1 where 'f'::mchar='F'::mvarchar;
+select 1 where 'f'::mchar='F '::mvarchar;
+select 1 where 'f '::mchar='F'::mvarchar;
+select 1 where 'f '::mchar='F '::mvarchar;
+
+select 1 where 'f'::mchar='F'::mvarchar(2);
+select 1 where 'f'::mchar='F '::mvarchar(2);
+select 1 where 'f '::mchar='F'::mvarchar(2);
+select 1 where 'f '::mchar='F '::mvarchar(2);
+
+select 1 where 'f'::mchar(2)='F'::mvarchar;
+select 1 where 'f'::mchar(2)='F '::mvarchar;
+select 1 where 'f '::mchar(2)='F'::mvarchar;
+select 1 where 'f '::mchar(2)='F '::mvarchar;
+
+select 1 where 'f'::mchar(2)='F'::mvarchar(2);
+select 1 where 'f'::mchar(2)='F '::mvarchar(2);
+select 1 where 'f '::mchar(2)='F'::mvarchar(2);
+select 1 where 'f '::mchar(2)='F '::mvarchar(2);
+
+select 1 where 'foo'::mchar='FOO'::mvarchar;
+select 1 where 'foo'::mchar='FOO '::mvarchar;
+select 1 where 'foo '::mchar='FOO'::mvarchar;
+select 1 where 'foo '::mchar='FOO '::mvarchar;
+
+select 1 where 'foo'::mchar='FOO'::mvarchar(2);
+select 1 where 'foo'::mchar='FOO '::mvarchar(2);
+select 1 where 'foo '::mchar='FOO'::mvarchar(2);
+select 1 where 'foo '::mchar='FOO '::mvarchar(2);
+
+select 1 where 'foo'::mchar(2)='FOO'::mvarchar;
+select 1 where 'foo'::mchar(2)='FOO '::mvarchar;
+select 1 where 'foo '::mchar(2)='FOO'::mvarchar;
+select 1 where 'foo '::mchar(2)='FOO '::mvarchar;
+
+select 1 where 'foo'::mchar(2)='FOO'::mvarchar(2);
+select 1 where 'foo'::mchar(2)='FOO '::mvarchar(2);
+select 1 where 'foo '::mchar(2)='FOO'::mvarchar(2);
+select 1 where 'foo '::mchar(2)='FOO '::mvarchar(2);
+
+Select 'f'::mchar(1) Union Select 'o'::mvarchar(1);
+Select 'f'::mvarchar(1) Union Select 'o'::mchar(1);
+
+select * from chvch where ch=vch;
+
+select ch.* from ch, (select 'dEfg'::mvarchar as q) as p  where  chcol > p.q;
+create index qq on ch (chcol);
+set enable_seqscan=off;
+select ch.* from ch, (select 'dEfg'::mvarchar as q) as p  where  chcol > p.q;
+set enable_seqscan=on;
+
+
+--\copy chvch to 'results/chvch.dump' binary
+--truncate table chvch;
+--\copy chvch from 'results/chvch.dump' binary
+
+--test joins
+CREATE TABLE a (mchar2 MCHAR(2) NOT NULL);
+CREATE TABLE c (mvarchar255 mvarchar NOT NULL);
+SELECT * FROM a, c WHERE mchar2 = mvarchar255;
+SELECT * FROM a, c WHERE mvarchar255 = mchar2;
+DROP TABLE a;
+DROP TABLE c;
+
+select * from (values
+    ('е'::mchar),('ё'),('еа'),('еб'),('ее'),('еж'),('ёа'),('ёб'),('ёё'),('ёж'),('ёе'),('её'))
+    z order by 1;
+
+select 'ё'::mchar = 'е';
+select 'Ё'::mchar = 'Е';
+select 'й'::mchar = 'и';
+select 'Й'::mchar = 'И';
+
+select mvarchar_icase_cmp('ёа','еб'), mvarchar_icase_cmp('еб','ё'),
+    mvarchar_icase_cmp('ё', 'ёа');
diff --git a/contrib/mchar/sql/mvarchar.sql b/contrib/mchar/sql/mvarchar.sql
new file mode 100644
index 00000000000..91b0981075d
--- /dev/null
+++ b/contrib/mchar/sql/mvarchar.sql
@@ -0,0 +1,82 @@
+-- I/O tests
+
+select '1'::mvarchar;
+select '2  '::mvarchar;
+select '10          '::mvarchar;
+
+select '1'::mvarchar(2);
+select '2 '::mvarchar(2);
+select '3  '::mvarchar(2);
+select '10          '::mvarchar(2);
+
+select '                  '::mvarchar(10); 
+select '                  '::mvarchar; 
+
+-- operations & functions
+
+select length('1'::mvarchar);
+select length('2  '::mvarchar);
+select length('10          '::mvarchar);
+
+select length('1'::mvarchar(2));
+select length('2 '::mvarchar(2));
+select length('3  '::mvarchar(2));
+select length('10          '::mvarchar(2));
+
+select length('                  '::mvarchar(10)); 
+select length('                  '::mvarchar); 
+
+select 'asd'::mvarchar(10) || '>'::mvarchar(10);
+select length('asd'::mvarchar(10) || '>'::mvarchar(10));
+select 'asd'::mvarchar(2)  || '>'::mvarchar(10);
+select length('asd'::mvarchar(2) || '>'::mvarchar(10));
+
+-- Comparisons
+
+select 'asdf'::mvarchar = 'aSdf'::mvarchar;
+select 'asdf'::mvarchar = 'aSdf '::mvarchar;
+select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(4);
+select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(5);
+select 'asdf'::mvarchar = 'aSdf 1'::mvarchar(6);
+select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(5);
+select 'asdf'::mvarchar(3) = 'aSdf 1'::mvarchar(3);
+
+select 'asdf'::mvarchar < 'aSdf'::mvarchar;
+select 'asdf'::mvarchar < 'aSdf '::mvarchar;
+select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(4);
+select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(5);
+select 'asdf'::mvarchar < 'aSdf 1'::mvarchar(6);
+
+select 'asdf'::mvarchar <= 'aSdf'::mvarchar;
+select 'asdf'::mvarchar <= 'aSdf '::mvarchar;
+select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(4);
+select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(5);
+select 'asdf'::mvarchar <= 'aSdf 1'::mvarchar(6);
+
+select 'asdf'::mvarchar >= 'aSdf'::mvarchar;
+select 'asdf'::mvarchar >= 'aSdf '::mvarchar;
+select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(4);
+select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(5);
+select 'asdf'::mvarchar >= 'aSdf 1'::mvarchar(6);
+
+select 'asdf'::mvarchar > 'aSdf'::mvarchar;
+select 'asdf'::mvarchar > 'aSdf '::mvarchar;
+select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(4);
+select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(5);
+select 'asdf'::mvarchar > 'aSdf 1'::mvarchar(6);
+
+select max(vch) from chvch;
+select min(vch) from chvch;
+
+select substr('1234567890'::mvarchar, 3) = '34567890' as "34567890";
+select substr('1234567890'::mvarchar, 4, 3) = '456' as "456";
+
+select lower('asdfASDF'::mvarchar);
+select upper('asdfASDF'::mvarchar);
+
+select 'asd'::mvarchar == 'aSd'::mvarchar;
+select 'asd'::mvarchar == 'aCd'::mvarchar;
+select 'asd'::mvarchar == NULL;
+select NULL == 'aCd'::mvarchar;
+select NULL::mvarchar == NULL;
+
diff --git a/contrib/online_analyze/COPYRIGHT b/contrib/online_analyze/COPYRIGHT
new file mode 100644
index 00000000000..75fea1f35d6
--- /dev/null
+++ b/contrib/online_analyze/COPYRIGHT
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * 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.
+ * 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.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
diff --git a/contrib/online_analyze/Makefile b/contrib/online_analyze/Makefile
new file mode 100644
index 00000000000..333add2b09b
--- /dev/null
+++ b/contrib/online_analyze/Makefile
@@ -0,0 +1,16 @@
+MODULE_big = online_analyze
+OBJS = online_analyze.o     
+#DATA_built = online_analyze.sql
+DOCS = README.online_analyze
+#REGRESS = online_analyze
+
+ifdef USE_PGXS
+PGXS := $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/online_analyze
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
+
diff --git a/contrib/online_analyze/README.online_analyze b/contrib/online_analyze/README.online_analyze
new file mode 100644
index 00000000000..d72f17db424
--- /dev/null
+++ b/contrib/online_analyze/README.online_analyze
@@ -0,0 +1,46 @@
+Module makes an analyze call immediately after INSERT/UPDATE/DELETE/SELECT INTO
+for affected table(s).
+
+Supported versions of PostgreSQL: 8.4.*, 9.0.*, 9.1.*, 9.2.*, 9.3.*, 9.4*, 9.5*,
+		  9.6*
+
+Usage: LOAD 'online_analyze';
+
+Custom variables (defaults values are shown):
+online_analyze.enable = on  
+	Enables on-line analyze
+
+online_analyze.local_tracking = off
+	Per backend tracking for temp tables (do not use system statistic)
+
+online_analyze.verbose = on
+	Execute ANALYZE VERBOSE
+
+online_analyze.scale_factor = 0.1
+	Fraction of table size to start on-line analyze (similar to
+	autovacuum_analyze_scale_factor)
+
+online_analyze.threshold = 50
+	Min number of row updates before on-line analyze (similar to
+	autovacuum_analyze_threshold)
+
+online_analyze.min_interval = 10000
+    Minimum time interval between analyze call per table (in milliseconds)
+
+online_analyze.lower_limit = 0
+	Min number of rows in table to analyze
+
+online_analyze.table_type = "all"
+	Type(s) of table for online analyze: all, persistent, temporary, none
+
+online_analyze.exclude_tables = ""
+	List of tables which will not online analyze
+
+online_analyze.include_tables = ""
+	List of tables which will online analyze
+	online_analyze.include_tables overwrites online_analyze.exclude_tables.
+
+online_analyze.capacity_threshold = 100000
+	Maximum number of temporary tables to store in local cache
+
+Author: Teodor Sigaev <teodor@sigaev.ru>
diff --git a/contrib/online_analyze/online_analyze.c b/contrib/online_analyze/online_analyze.c
new file mode 100644
index 00000000000..6ffcc30997d
--- /dev/null
+++ b/contrib/online_analyze/online_analyze.c
@@ -0,0 +1,1370 @@
+/*
+ * Copyright (c) 2011 Teodor Sigaev <teodor@sigaev.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * 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.
+ * 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.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#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"
+#include "nodes/nodes.h"
+#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 "catalog/pg_class.h"
+#include "nodes/primnodes.h"
+#include "tcop/utility.h"
+#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
+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
+
+#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,
+	OATT_TEMPORARY  = 0x02,
+	OATT_NONE		= 0x00
+} OnlineAnalyzeTableType;
+
+static const struct config_enum_entry online_analyze_table_type_options[] =
+{
+	{"all", OATT_ALL, false},
+	{"persistent", OATT_PERSISTENT, false},
+	{"temporary", OATT_TEMPORARY, false},
+	{"none", OATT_NONE, false},
+	{NULL, 0, false},
+};
+
+static int online_analyze_table_type = (int)OATT_ALL;
+
+typedef struct TableList {
+	int		nTables;
+	Oid		*tables;
+	char	*tableStr;
+	bool	inited;
+} TableList;
+
+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	changes_since_analyze;
+	TimestampTz		autovac_analyze_timestamp;
+	TimestampTz		analyze_timestamp;
+} 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)
+{
+	if (*(Oid*)a == *(Oid*)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,
+				i = 0;
+
+	rawname = pstrdup(newval);
+
+	if (!SplitIdentifierString(rawname, ',', &namelist))
+		goto cleanup;
+
+	/*
+	* follow work could be done only in normal processing because of
+	* accsess to system catalog
+	*/
+	if (MyBackendId == InvalidBackendId || !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)));
+	}
+
+	foreach(l, namelist)
+	{
+		char	*curname = (char *) lfirst(l);
+#if PG_VERSION_NUM >= 90200
+		Oid		relOid = RangeVarGetRelid(makeRangeVarFromNameList(
+							stringToQualifiedNameList(curname)), NoLock, true);
+#else
+		Oid		relOid = RangeVarGetRelid(makeRangeVarFromNameList(
+							stringToQualifiedNameList(curname)), true);
+#endif
+
+		if (relOid == InvalidOid)
+		{
+#if PG_VERSION_NUM >= 90100
+			if (doit == false)
+#endif
+			elog(WARNING,"'%s' does not exist", curname);
+			continue;
+		}
+		else if ( get_rel_relkind(relOid) != RELKIND_RELATION )
+		{
+#if PG_VERSION_NUM >= 90100
+			if (doit == false)
+#endif
+				elog(WARNING,"'%s' is not an table", curname);
+			continue;
+		}
+		else if (doit)
+		{
+			newOids[i++] = relOid;
+		}
+	}
+
+	if (doit)
+	{
+		tbl->nTables = i;
+		if (tbl->tables)
+			free(tbl->tables);
+		tbl->tables = newOids;
+		if (tbl->nTables > 1)
+			qsort(tbl->tables, tbl->nTables, sizeof(tbl->tables[0]), oid_cmp);
+	}
+
+	pfree(rawname);
+	list_free(namelist);
+
+	return newval;
+
+cleanup:
+	if (newOids)
+		free(newOids);
+	pfree(rawname);
+	list_free(namelist);
+	return NULL;
+}
+
+#if PG_VERSION_NUM >= 90100
+static bool
+excludeTablesCheck(char **newval, void **extra, GucSource source)
+{
+	char *val;
+
+	val = (char*)tableListAssign(*newval, false, &excludeTables);
+
+	if (val)
+	{
+		*newval = val;
+		return true;
+	}
+
+	return false;
+}
+
+static void
+excludeTablesAssign(const char *newval, void *extra)
+{
+	tableListAssign(newval, true, &excludeTables);
+}
+
+static bool
+includeTablesCheck(char **newval, void **extra, GucSource source)
+{
+	char *val;
+
+	val = (char*)tableListAssign(*newval, false, &includeTables);
+
+	if (val)
+	{
+		*newval = val;
+		return true;
+	}
+
+	return false;
+}
+
+static void
+includeTablesAssign(const char *newval, void *extra)
+{
+	tableListAssign(newval, true, &includeTables);
+}
+
+#else /* PG_VERSION_NUM < 90100 */
+
+static const char *
+excludeTablesAssign(const char * newval, bool doit, GucSource source)
+{
+	return tableListAssign(newval, doit, &excludeTables);
+}
+
+static const char *
+includeTablesAssign(const char * newval, bool doit, GucSource source)
+{
+	return tableListAssign(newval, doit, &includeTables);
+}
+
+#endif
+
+static void
+lateInit()
+{
+	TableList	*tl[] = {&includeTables, &excludeTables};
+	int i;
+
+	if (MyBackendId == InvalidBackendId || !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,
+			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);
+
+		if ( relname == NULL || nspOid == InvalidOid || nspname == NULL )
+			continue;
+
+		ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s",
+													(i==0) ? "" : ", ",
+													nspname, relname);
+	}
+
+	return val;
+}
+
+static const char*
+excludeTablesShow(void)
+{
+	return tableListShow(&excludeTables);
+}
+
+static const char*
+includeTablesShow(void)
+{
+	return tableListShow(&includeTables);
+}
+
+static bool
+matchOid(TableList *tbl, Oid oid)
+{
+	Oid	*StopLow = tbl->tables,
+		*StopHigh = tbl->tables + tbl->nTables,
+		*StopMiddle;
+
+	/* Loop invariant: StopLow <= val < StopHigh */
+	while (StopLow < StopHigh)
+	{
+		StopMiddle = StopLow + ((StopHigh - StopLow) >> 1);
+
+		if (*StopMiddle == oid)
+			return true;
+		else  if (*StopMiddle < oid)
+			StopLow = StopMiddle + 1;
+		else
+			StopHigh = StopMiddle;
+	}
+
+	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, CmdKind operation, int64 naffected)
+{
+	TimestampTz				now = GetCurrentTimestamp();
+	Relation				rel;
+	OnlineAnalyzeTableType	reltype;
+	bool					found = false,
+							newTable = false;
+	OnlineAnalyzeTableStat	*rstat,
+							dummyrstat;
+	PgStat_StatTabEntry		*tabentry = NULL;
+
+	if (relOid == InvalidOid)
+		return;
+
+	if (naffected == 0)
+		/* return if there is no changes */
+		return;
+	else if (naffected < 0)
+		/* number if affected rows is unknown */
+		naffected = 0;
+
+	rel = RelationIdGetRelation(relOid);
+	if (rel->rd_rel->relkind != RELKIND_RELATION)
+	{
+		RelationClose(rel);
+		return;
+	}
+
+	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;
+	}
+
+	/*
+	 * 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->changes_since_analyze = 0;
+		rstat->analyze_timestamp = 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 = tabentry->n_dead_tuples + tabentry->n_live_tuples;
+			rstat->changes_since_analyze =
+#if PG_VERSION_NUM >= 90000
+				tabentry->changes_since_analyze;
+#else
+				tabentry->n_live_tuples + tabentry->n_dead_tuples -
+					tabentry->last_anl_tuples;
+#endif
+			rstat->autovac_analyze_timestamp =
+				tabentry->autovac_analyze_timestamp;
+			rstat->analyze_timestamp = tabentry->analyze_timestamp;
+		}
+	}
+
+	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->analyze_timestamp, now, online_analyze_min_interval) &&
+		TimestampDifferenceExceeds(rstat->autovac_analyze_timestamp, now, online_analyze_min_interval) &&
+		/* do not analyze too small tables */
+		rstat->n_tuples + rstat->changes_since_analyze + naffected > online_analyze_lower_limit &&
+		/* be in sync with relation_needs_vacanalyze */
+		((double)(rstat->changes_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.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)
+			vacstmt.options |= VACOPT_VERBOSE;
+#else
+		vacstmt.vacuum = vacstmt.full = false;
+		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();
+
+		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;
+			int		microsecs;
+
+			endStamp = GetCurrentTimestamp();
+			TimestampDifference(startStamp, endStamp, &secs, &microsecs);
+			elog(INFO, "analyze \"%s\" took %.02f seconds",
+				get_rel_name(relOid),
+				((double)secs) + ((double)microsecs)/1.0e6);
+		}
+
+		rstat->autovac_analyze_timestamp = now;
+		rstat->changes_since_analyze = 0;
+
+		switch(operation)
+		{
+			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;
+		}
+
+		/* update last analyze timestamp in local memory of backend */
+		if (tabentry)
+		{
+			tabentry->analyze_timestamp = now;
+			tabentry->changes_since_analyze = 0;
+		}
+#if 0
+		/* force reload stat for new table */
+		if (newTable)
+			pgstat_clear_snapshot();
+#endif
+	}
+	else
+	{
+#if PG_VERSION_NUM >= 90000
+		if (tabentry)
+			tabentry->changes_since_analyze += naffected;
+#endif
+		switch(operation)
+		{
+			case CK_CREATE:
+			case CK_INSERT:
+				rstat->changes_since_analyze += naffected;
+				rstat->n_tuples += naffected;
+				break;
+			case CK_UPDATE:
+				rstat->changes_since_analyze += 2 * naffected;
+				rstat->n_tuples += naffected;
+				break;
+			case CK_DELETE:
+				rstat->changes_since_analyze += naffected;
+				break;
+			case CK_TRUNCATE:
+			case CK_FASTTRUNCATE:
+				rstat->changes_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)
+{
+	int64	naffected = -1;
+	Const	*constval;
+
+	if (queryDesc->estate)
+		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));
+		RangeVar	*tblvar =
+			makeRangeVarFromNameList(stringToQualifiedNameList(tblname));
+
+		makeAnalyze(RangeVarGetRelid(tblvar,
+									 NoLock,
+									 false),
+					CK_FASTTRUNCATE, -1);
+	}
+#endif
+
+	if (online_analyze_enable && queryDesc->plannedstmt &&
+			(queryDesc->operation == CMD_INSERT ||
+			 queryDesc->operation == CMD_UPDATE ||
+			 queryDesc->operation == CMD_DELETE
+#if PG_VERSION_NUM < 90200
+			 || (queryDesc->operation == CMD_SELECT &&
+				 queryDesc->plannedstmt->intoClause)
+#endif
+			 ))
+	{
+#if PG_VERSION_NUM < 90200
+		if (queryDesc->operation == CMD_SELECT)
+		{
+			Oid	relOid = RangeVarGetRelid(queryDesc->plannedstmt->intoClause->rel, true);
+
+			makeAnalyze(relOid, queryDesc->operation, naffected);
+		}
+		else
+#endif
+		if (queryDesc->plannedstmt->resultRelations &&
+				 queryDesc->plannedstmt->rtable)
+		{
+			ListCell	*l;
+
+			foreach(l, queryDesc->plannedstmt->resultRelations)
+			{
+				int				n = lfirst_int(l);
+				RangeTblEntry	*rte = list_nth(queryDesc->plannedstmt->rtable, n-1);
+
+				if (rte->rtekind == RTE_RELATION)
+					makeAnalyze(rte->relid, (CmdKind)queryDesc->operation, naffected);
+			}
+		}
+	}
+
+	if (oldExecutorEndHook)
+		oldExecutorEndHook(queryDesc);
+	else
+		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(
+#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,
+#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 (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->changes_since_analyze = 0;
+						rstat->analyze_timestamp = now;
+					}
+				}
+			}
+			else
+				tblnames = NIL;
+		}
+	}
+
+#if PG_VERSION_NUM >= 100000
+#define parsetree pstmt
+#endif
+
+	if (oldProcessUtilityHook)
+		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,
+#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;
+
+		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));
+
+			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;
+
+#if PG_VERSION_NUM >= 90200
+	oldProcessUtilityHook = ProcessUtility_hook;
+
+	ProcessUtility_hook = onlineAnalyzeHookerUtility;
+#endif
+
+
+	DefineCustomBoolVariable(
+		"online_analyze.enable",
+		"Enable on-line analyze",
+		"Enables analyze of table directly after insert/update/delete/select into",
+		&online_analyze_enable,
+#if PG_VERSION_NUM >= 80400
+		online_analyze_enable,
+#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.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",
+		"Make ANALYZE VERBOSE after table's changes",
+		&online_analyze_verbose,
+#if PG_VERSION_NUM >= 80400
+		online_analyze_verbose,
+#endif
+		PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+		GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+		NULL,
+#endif
+#endif
+		NULL,
+		NULL
+	);
+
+	DefineCustomRealVariable(
+		"online_analyze.scale_factor",
+		"fraction of table size to start on-line analyze",
+		"fraction of table size to start on-line analyze",
+		&online_analyze_scale_factor,
+#if PG_VERSION_NUM >= 80400
+		online_analyze_scale_factor,
+#endif
+		0.0,
+		1.0,
+		PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+		GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+		NULL,
+#endif
+#endif
+		NULL,
+		NULL
+	);
+
+	DefineCustomIntVariable(
+		"online_analyze.threshold",
+		"min number of row updates before on-line analyze",
+		"min number of row updates before on-line analyze",
+		&online_analyze_threshold,
+#if PG_VERSION_NUM >= 80400
+		online_analyze_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
+	);
+
+	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_min_interval,
+#if PG_VERSION_NUM >= 80400
+		online_analyze_min_interval,
+#endif
+		0.0,
+		1e30,
+		PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+		GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+		NULL,
+#endif
+#endif
+		NULL,
+		NULL
+	);
+
+	DefineCustomEnumVariable(
+		"online_analyze.table_type",
+		"Type(s) of table for online analyze: all(default), persistent, temporary, none",
+		NULL,
+		&online_analyze_table_type,
+#if PG_VERSION_NUM >= 80400
+		online_analyze_table_type,
+#endif
+		online_analyze_table_type_options,
+		PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+		GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+		NULL,
+#endif
+#endif
+		NULL,
+		NULL
+	);
+
+	DefineCustomStringVariable(
+		"online_analyze.exclude_tables",
+		"List of tables which will not online analyze",
+		NULL,
+		&excludeTables.tableStr,
+#if PG_VERSION_NUM >= 80400
+		"",
+#endif
+		PGC_USERSET,
+		0,
+#if PG_VERSION_NUM >= 90100
+		excludeTablesCheck,
+		excludeTablesAssign,
+#else
+		excludeTablesAssign,
+#endif
+		excludeTablesShow
+	);
+
+	DefineCustomStringVariable(
+		"online_analyze.include_tables",
+		"List of tables which will online analyze",
+		NULL,
+		&includeTables.tableStr,
+#if PG_VERSION_NUM >= 80400
+		"",
+#endif
+		PGC_USERSET,
+		0,
+#if PG_VERSION_NUM >= 90100
+		includeTablesCheck,
+		includeTablesAssign,
+#else
+		includeTablesAssign,
+#endif
+		includeTablesShow
+	);
+
+	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
+	);
+
+	RegisterXactCallback(removeTable, NULL);
+}
+
+void _PG_fini(void);
+void
+_PG_fini(void)
+{
+	ExecutorEnd_hook = oldExecutorEndHook;
+#if PG_VERSION_NUM >= 90200
+	ProcessUtility_hook = oldProcessUtilityHook;
+#endif
+
+	if (excludeTables.tables)
+		free(excludeTables.tables);
+	if (includeTables.tables)
+		free(includeTables.tables);
+
+	excludeTables.tables = includeTables.tables = NULL;
+	excludeTables.nTables = includeTables.nTables = 0;
+}
diff --git a/contrib/plantuner/COPYRIGHT b/contrib/plantuner/COPYRIGHT
new file mode 100644
index 00000000000..6e4705bc561
--- /dev/null
+++ b/contrib/plantuner/COPYRIGHT
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2009 Teodor Sigaev <teodor@sigaev.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * 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.
+ * 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.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
diff --git a/contrib/plantuner/Makefile b/contrib/plantuner/Makefile
new file mode 100644
index 00000000000..f2e8350e84c
--- /dev/null
+++ b/contrib/plantuner/Makefile
@@ -0,0 +1,15 @@
+MODULE_big = plantuner
+DOCS = README.plantuner
+REGRESS = plantuner
+OBJS=plantuner.o
+
+ifdef USE_PGXS
+PGXS = $(shell pg_config --pgxs)
+include $(PGXS)
+else
+subdir = contrib/plantuner
+top_builddir = ../..
+include $(top_builddir)/src/Makefile.global
+
+include $(top_srcdir)/contrib/contrib-global.mk
+endif
diff --git a/contrib/plantuner/README.plantuner b/contrib/plantuner/README.plantuner
new file mode 100644
index 00000000000..17c8ba010b8
--- /dev/null
+++ b/contrib/plantuner/README.plantuner
@@ -0,0 +1,99 @@
+Plantuner - enable planner hints
+
+   contrib/plantuner is a contribution module for PostgreSQL 8.4+, which
+   enable planner hints.
+
+   All work was done by Teodor Sigaev (teodor@sigaev.ru) and Oleg Bartunov
+   (oleg@sai.msu.su).
+
+   Sponsor: Nomao project (http://www.nomao.com)
+
+Motivation
+
+   Whether somebody think it's bad or not, but sometime it's very
+   interesting to be able to control planner (provide hints, which tells
+   optimizer to ignore its algorithm in part), which is currently
+   impossible in POstgreSQL. Oracle, for example, has over 120 hints, SQL
+   Server also provides hints.
+
+   This first version of plantuner provides a possibility to hide
+   specified indexes from PostgreSQL planner, so it will not use them.
+
+   There are many situation, when developer want to temporarily disable
+   specific index(es), without dropping them, or to instruct planner to
+   use specific index.
+
+   Next, for some workload PostgreSQL could be too pessimistic for
+   newly created tables and assumes much more rows in table than
+   it actually has. If plantuner.fix_empty_table GUC variable is set
+   to true then module will set to zero number of pages/tuples of
+   table which hasn't blocks in file.
+
+Installation
+
+     * Get latest source of plantuner from CVS Repository
+     * gmake && gmake install && gmake installcheck
+
+Syntax
+	plantuner.forbid_index (deprecated)
+	plantuner.disable_index
+		List of indexes invisible to planner
+	plantuner.enable_index
+		List of indexes visible to planner even they are hided
+		by plantuner.disable_index. 
+	plantuner.only_index
+		List of explicitly enabled indexes (overload plantuner.disable_index
+		and plantuner.enable_index), so, only indexes in this list are allowed.
+
+Usage
+
+   To enable the module you can either load shared library 'plantuner' in
+   psql session or specify 'shared_preload_libraries' option in
+   postgresql.conf.
+=# LOAD 'plantuner';
+=# create table test(id int);
+=# create index id_idx on test(id);
+=# create index id_idx2 on test(id);
+=# \d test
+     Table "public.test"
+ Column |  Type   | Modifiers
+--------+---------+-----------
+ id     | integer |
+Indexes:
+    "id_idx" btree (id)
+    "id_idx2" btree (id)
+=# explain select id from test where id=1;
+                              QUERY PLAN
+-----------------------------------------------------------------------
+ Bitmap Heap Scan on test  (cost=4.34..15.03 rows=12 width=4)
+   Recheck Cond: (id = 1)
+   ->  Bitmap Index Scan on id_idx2  (cost=0.00..4.34 rows=12 width=0)
+         Index Cond: (id = 1)
+(4 rows)
+=# set enable_seqscan=off;
+=# set plantuner.disable_index='id_idx2';
+=# explain select id from test where id=1;
+                              QUERY PLAN
+----------------------------------------------------------------------
+ Bitmap Heap Scan on test  (cost=4.34..15.03 rows=12 width=4)
+   Recheck Cond: (id = 1)
+   ->  Bitmap Index Scan on id_idx  (cost=0.00..4.34 rows=12 width=0)
+         Index Cond: (id = 1)
+(4 rows)
+=# set plantuner.disable_index='id_idx2,id_idx';
+=# explain select id from test where id=1;
+                               QUERY PLAN
+-------------------------------------------------------------------------
+ Seq Scan on test  (cost=10000000000.00..10000000040.00 rows=12 width=4)
+   Filter: (id = 1)
+(2 rows)
+=# set plantuner.enable_index='id_idx';
+=# explain select id from test where id=1;
+                              QUERY PLAN
+-----------------------------------------------------------------------
+ Bitmap Heap Scan on test  (cost=4.34..15.03 rows=12 width=4)
+   Recheck Cond: (id = 1)
+   ->  Bitmap Index Scan on id_idx  (cost=0.00..4.34 rows=12 width=0)
+         Index Cond: (id = 1)
+(4 rows)
+
diff --git a/contrib/plantuner/expected/plantuner.out b/contrib/plantuner/expected/plantuner.out
new file mode 100644
index 00000000000..70d2bcaaef2
--- /dev/null
+++ b/contrib/plantuner/expected/plantuner.out
@@ -0,0 +1,96 @@
+LOAD 'plantuner';
+SHOW	plantuner.disable_index;
+ plantuner.disable_index 
+-------------------------
+ 
+(1 row)
+
+CREATE TABLE wow (i int, j int);
+CREATE INDEX i_idx ON wow (i);
+CREATE INDEX j_idx ON wow (j);
+CREATE INDEX i1 ON WOW (i);
+CREATE INDEX i2 ON WOW (i);
+CREATE INDEX i3 ON WOW (i);
+SET enable_seqscan=off;
+SELECT * FROM wow;
+ i | j 
+---+---
+(0 rows)
+
+SET plantuner.disable_index="i_idx, j_idx";
+SELECT * FROM wow;
+ i | j 
+---+---
+(0 rows)
+
+SHOW plantuner.disable_index;
+  plantuner.disable_index   
+----------------------------
+ public.i_idx, public.j_idx
+(1 row)
+
+SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow";
+WARNING:  'nonexistent' does not exist
+WARNING:  'wow' is not an index
+SHOW plantuner.disable_index;
+  plantuner.disable_index   
+----------------------------
+ public.i_idx, public.j_idx
+(1 row)
+
+SET plantuner.enable_index="i_idx";
+SHOW plantuner.enable_index;
+ plantuner.enable_index 
+------------------------
+ public.i_idx
+(1 row)
+
+SELECT * FROM wow;
+ i | j 
+---+---
+(0 rows)
+
+--test only index
+RESET plantuner.disable_index;
+RESET plantuner.enable_index;
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+SET enable_indexonlyscan=off;
+SET plantuner.only_index="i1";
+SHOW plantuner.only_index;
+ plantuner.only_index 
+----------------------
+ public.i1
+(1 row)
+
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+         QUERY PLAN         
+----------------------------
+ Index Scan using i1 on wow
+   Index Cond: (i = 0)
+(2 rows)
+
+SET plantuner.disable_index="i1,i2,i3";
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+         QUERY PLAN         
+----------------------------
+ Index Scan using i1 on wow
+   Index Cond: (i = 0)
+(2 rows)
+
+SET plantuner.only_index="i2";
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+         QUERY PLAN         
+----------------------------
+ Index Scan using i2 on wow
+   Index Cond: (i = 0)
+(2 rows)
+
+RESET plantuner.only_index;
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+          QUERY PLAN           
+-------------------------------
+ Index Scan using i_idx on wow
+   Index Cond: (i = 0)
+(2 rows)
+
diff --git a/contrib/plantuner/plantuner.c b/contrib/plantuner/plantuner.c
new file mode 100644
index 00000000000..f87dcea21ff
--- /dev/null
+++ b/contrib/plantuner/plantuner.c
@@ -0,0 +1,554 @@
+/*
+ * Copyright (c) 2009 Teodor Sigaev <teodor@sigaev.ru>
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * 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.
+ * 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.
+ * 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.
+ *
+ * THIS SOFTWARE IS PROVIDED BY CONTRIBUTORS ``AS IS'' AND ANY EXPRESS
+ * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+ * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+ * IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <postgres.h>
+
+#include <fmgr.h>
+#include <miscadmin.h>
+#include <access/heapam.h>
+#include <access/xact.h>
+#include <catalog/namespace.h>
+#include <catalog/pg_class.h>
+#include <nodes/pg_list.h>
+#include <optimizer/plancat.h>
+#include <storage/bufmgr.h>
+#include <utils/builtins.h>
+#include <utils/guc.h>
+#include <utils/lsyscache.h>
+#include <utils/rel.h>
+#if PG_VERSION_NUM >= 100000
+#include <utils/regproc.h>
+#include <utils/varlena.h>
+#endif
+
+PG_MODULE_MAGIC;
+
+#if PG_VERSION_NUM >= 130000
+#define heap_open(r, l)					table_open(r, l)
+#define heap_close(r, l)				table_close(r, l)
+#endif
+
+static int	nDisabledIndexes = 0;
+static Oid	*disabledIndexes = NULL;
+static char *disableIndexesOutStr = "";
+
+static int	nEnabledIndexes = 0;
+static Oid	*enabledIndexes = NULL;
+static char *enableIndexesOutStr = "";
+
+static int	nOnlyIndexes = 0;
+static Oid	*onlyIndexes = NULL;
+static char *onlyIndexesOutStr = "";
+
+get_relation_info_hook_type	prevHook = NULL;
+static bool	fix_empty_table = false;
+
+static bool	plantuner_enable_inited = false;
+static bool	plantuner_only_inited = false;
+static bool	plantuner_disable_inited = false;
+
+typedef enum IndexListKind {
+	EnabledKind,
+	DisabledKind,
+	OnlyKind
+} IndexListKind;
+
+static const char *
+indexesAssign(const char * newval, bool doit, GucSource source,
+			  IndexListKind kind)
+{
+	char		*rawname;
+	List		*namelist;
+	ListCell	*l;
+	Oid			*newOids = NULL;
+	int			nOids = 0,
+				i = 0;
+
+	rawname = pstrdup(newval);
+
+	if (!SplitIdentifierString(rawname, ',', &namelist))
+		goto cleanup;
+
+	/*
+	 * follow work could be done only in normal processing because of
+	 * accsess to system catalog
+	 */
+	if (MyBackendId == InvalidBackendId || !IsUnderPostmaster ||
+		!IsTransactionState())
+	{
+		/* reset init state */
+		switch(kind)
+		{
+			case EnabledKind:
+				plantuner_enable_inited = false;
+				break;
+			case DisabledKind:
+				plantuner_disable_inited = false;
+				break;
+			case OnlyKind:
+				plantuner_only_inited = false;
+				break;
+			default:
+				elog(ERROR, "wrong kind");
+		}
+
+		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)));
+	}
+
+	switch(kind)
+	{
+		case EnabledKind:
+			plantuner_enable_inited = true;
+			break;
+		case DisabledKind:
+			plantuner_disable_inited = true;
+			break;
+		case OnlyKind:
+			plantuner_only_inited = true;
+			break;
+		default:
+			elog(ERROR, "wrong kind");
+	}
+
+	foreach(l, namelist)
+	{
+		char	*curname = (char *) lfirst(l);
+#if PG_VERSION_NUM >= 90200
+		Oid		indexOid = RangeVarGetRelid(
+				makeRangeVarFromNameList(stringToQualifiedNameList(curname)),
+											NoLock, true);
+#else
+		Oid		indexOid = RangeVarGetRelid(
+				makeRangeVarFromNameList(stringToQualifiedNameList(curname)),
+											true);
+#endif
+
+		if (indexOid == InvalidOid)
+		{
+#if PG_VERSION_NUM >= 90100
+			if (doit == false)
+#endif
+				elog(WARNING,"'%s' does not exist", curname);
+			continue;
+		}
+		else if ( get_rel_relkind(indexOid) != RELKIND_INDEX )
+		{
+#if PG_VERSION_NUM >= 90100
+			if (doit == false)
+#endif
+				elog(WARNING,"'%s' is not an index", curname);
+			continue;
+		}
+		else if (doit)
+		{
+			newOids[i++] = indexOid;
+		}
+	}
+
+	if (doit)
+	{
+		switch(kind)
+		{
+			case EnabledKind:
+				nEnabledIndexes = i;
+				if (enabledIndexes)
+					free(enabledIndexes);
+				enabledIndexes = newOids;
+				break;
+			case DisabledKind:
+				nDisabledIndexes = i;
+				if (disabledIndexes)
+					free(disabledIndexes);
+				disabledIndexes = newOids;
+				break;
+			case OnlyKind:
+				nOnlyIndexes = i;
+				if (onlyIndexes)
+					free(onlyIndexes);
+				onlyIndexes = newOids;
+				break;
+			default:
+				elog(ERROR, "wrong kind");
+		}
+	}
+
+	pfree(rawname);
+	list_free(namelist);
+
+	return newval;
+
+cleanup:
+	if (newOids)
+		free(newOids);
+	pfree(rawname);
+	list_free(namelist);
+	return NULL;
+}
+
+static const char *
+assignDisabledIndexes(const char * newval, bool doit, GucSource source)
+{
+	return indexesAssign(newval, doit, source, DisabledKind);
+}
+
+static const char *
+assignEnabledIndexes(const char * newval, bool doit, GucSource source)
+{
+	return indexesAssign(newval, doit, source, EnabledKind);
+}
+
+static const char *
+assignOnlyIndexes(const char * newval, bool doit, GucSource source)
+{
+	return indexesAssign(newval, doit, source, OnlyKind);
+}
+
+static void
+lateInit()
+{
+	if (!plantuner_only_inited)
+		indexesAssign(onlyIndexesOutStr, true, PGC_S_USER, OnlyKind);
+	if (!plantuner_enable_inited)
+		indexesAssign(enableIndexesOutStr, true, PGC_S_USER, EnabledKind);
+	if (!plantuner_disable_inited)
+		indexesAssign(disableIndexesOutStr, true, PGC_S_USER, DisabledKind);
+}
+
+#if PG_VERSION_NUM >= 90100
+
+static bool
+checkOnlyIndexes(char **newval, void **extra, GucSource source)
+{
+	char *val;
+
+	val = (char*)indexesAssign(*newval, false, source, OnlyKind);
+
+	if (val)
+	{
+		*newval = val;
+		return true;
+	}
+
+	return false;
+}
+
+static bool
+checkDisabledIndexes(char **newval, void **extra, GucSource source)
+{
+	char *val;
+
+	val = (char*)indexesAssign(*newval, false, source, DisabledKind);
+
+	if (val)
+	{
+		*newval = val;
+		return true;
+	}
+
+	return false;
+}
+
+static bool
+checkEnabledIndexes(char **newval, void **extra, GucSource source)
+{
+	char *val;
+
+	val = (char*)indexesAssign(*newval, false, source, EnabledKind);
+
+	if (val)
+	{
+		*newval = val;
+		return true;
+	}
+
+	return false;
+}
+
+static void
+assignDisabledIndexesNew(const char *newval, void *extra)
+{
+	assignDisabledIndexes(newval, true, PGC_S_USER /* doesn't matter */);
+}
+
+static void
+assignEnabledIndexesNew(const char *newval, void *extra)
+{
+	assignEnabledIndexes(newval, true, PGC_S_USER /* doesn't matter */);
+}
+
+static void
+assignOnlyIndexesNew(const char *newval, void *extra)
+{
+	assignOnlyIndexes(newval, true, PGC_S_USER /* doesn't matter */);
+}
+
+#endif
+
+static void
+indexFilter(PlannerInfo *root, Oid relationObjectId, bool inhparent,
+			RelOptInfo *rel)
+{
+	int i;
+
+	lateInit();
+
+	if (nOnlyIndexes > 0)
+	{
+		ListCell	*l;
+
+restart1:
+		foreach(l, rel->indexlist)
+		{
+			IndexOptInfo	*info = (IndexOptInfo*)lfirst(l);
+			bool			remove = true;
+
+			for(i=0; remove && i<nOnlyIndexes; i++)
+				if (onlyIndexes[i] == info->indexoid)
+					remove = false;
+
+			if (remove)
+			{
+				rel->indexlist = list_delete_ptr(rel->indexlist, info);
+				goto restart1;
+			}
+		}
+
+		return;
+	}
+
+	for(i=0; i<nDisabledIndexes; i++)
+	{
+		ListCell   *l;
+
+		foreach(l, rel->indexlist)
+		{
+			IndexOptInfo	*info = (IndexOptInfo*)lfirst(l);
+
+			if (disabledIndexes[i] == info->indexoid)
+			{
+				int j;
+
+				for(j=0; j<nEnabledIndexes; j++)
+					if (enabledIndexes[j] == info->indexoid)
+						break;
+
+				if (j >= nEnabledIndexes)
+					rel->indexlist = list_delete_ptr(rel->indexlist, info);
+
+				break;
+			}
+		}
+	}
+}
+
+static void
+execPlantuner(PlannerInfo *root, Oid relationObjectId, bool inhparent,
+			  RelOptInfo *rel)
+{
+	Relation	relation;
+
+	relation = heap_open(relationObjectId, NoLock);
+	if (relation->rd_rel->relkind == RELKIND_RELATION)
+	{
+		if (fix_empty_table && RelationGetNumberOfBlocks(relation) == 0)
+		{
+			/*
+			 * estimate_rel_size() could be too pessimistic for particular
+			 * workload
+			 */
+			rel->pages = 1.0;
+			rel->tuples = 0.0;
+		}
+
+		indexFilter(root, relationObjectId, inhparent, rel);
+	}
+	heap_close(relation, NoLock);
+
+	/*
+	 * Call next hook if it exists
+	 */
+	if (prevHook)
+		prevHook(root, relationObjectId, inhparent, rel);
+}
+
+static const char*
+IndexFilterShow(Oid* indexes, int nIndexes)
+{
+	char	*val, *ptr;
+	int		i,
+			len;
+
+	lateInit();
+
+	len = 1 /* \0 */ + nIndexes * (2 * NAMEDATALEN + 2 /* ', ' */ + 1 /* . */);
+	ptr = val = palloc(len);
+
+	*ptr =(char)'\0';
+	for(i=0; i<nIndexes; i++)
+	{
+		char	*relname = get_rel_name(indexes[i]);
+		Oid		nspOid = get_rel_namespace(indexes[i]);
+		char	*nspname = get_namespace_name(nspOid);
+
+		if ( relname == NULL || nspOid == InvalidOid || nspname == NULL )
+			continue;
+
+		ptr += snprintf(ptr, len - (ptr - val), "%s%s.%s",
+												(i==0) ? "" : ", ",
+												nspname,
+												relname);
+	}
+
+	return val;
+}
+
+static const char*
+disabledIndexFilterShow(void)
+{
+	return IndexFilterShow(disabledIndexes, nDisabledIndexes);
+}
+
+static const char*
+enabledIndexFilterShow(void)
+{
+	return IndexFilterShow(enabledIndexes, nEnabledIndexes);
+}
+
+static const char*
+onlyIndexFilterShow(void)
+{
+	return IndexFilterShow(onlyIndexes, nOnlyIndexes);
+}
+
+void _PG_init(void);
+void
+_PG_init(void)
+{
+	DefineCustomStringVariable(
+		"plantuner.forbid_index",
+		"List of forbidden indexes (deprecated)",
+		"Listed indexes will not be used in queries (deprecated, use plantuner.disable_index)",
+		&disableIndexesOutStr,
+		"",
+		PGC_USERSET,
+		0,
+#if PG_VERSION_NUM >= 90100
+		checkDisabledIndexes,
+		assignDisabledIndexesNew,
+#else
+		assignDisabledIndexes,
+#endif
+		disabledIndexFilterShow
+	);
+
+	DefineCustomStringVariable(
+		"plantuner.disable_index",
+		"List of disabled indexes",
+		"Listed indexes will not be used in queries",
+		&disableIndexesOutStr,
+		"",
+		PGC_USERSET,
+		0,
+#if PG_VERSION_NUM >= 90100
+		checkDisabledIndexes,
+		assignDisabledIndexesNew,
+#else
+		assignDisabledIndexes,
+#endif
+		disabledIndexFilterShow
+	);
+
+	DefineCustomStringVariable(
+		"plantuner.enable_index",
+		"List of enabled indexes (overload plantuner.disable_index)",
+		"Listed indexes which could be used in queries even they are listed in plantuner.disable_index",
+		&enableIndexesOutStr,
+		"",
+		PGC_USERSET,
+		0,
+#if PG_VERSION_NUM >= 90100
+		checkEnabledIndexes,
+		assignEnabledIndexesNew,
+#else
+		assignEnabledIndexes,
+#endif
+		enabledIndexFilterShow
+	);
+
+	DefineCustomStringVariable(
+		"plantuner.only_index",
+		"List of explicitly enabled indexes (overload plantuner.disable_index and plantuner.enable_index)",
+		"Only indexes in this list are allowed",
+		&onlyIndexesOutStr,
+		"",
+		PGC_USERSET,
+		0,
+#if PG_VERSION_NUM >= 90100
+		checkOnlyIndexes,
+		assignOnlyIndexesNew,
+#else
+		assignOnlyIndexes,
+#endif
+		onlyIndexFilterShow
+	);
+
+	DefineCustomBoolVariable(
+		"plantuner.fix_empty_table",
+		"Sets to zero estimations for empty tables",
+		"Sets to zero estimations for empty or newly created tables",
+		&fix_empty_table,
+#if PG_VERSION_NUM >= 80400
+		fix_empty_table,
+#endif
+		PGC_USERSET,
+#if PG_VERSION_NUM >= 80400
+		GUC_NOT_IN_SAMPLE,
+#if PG_VERSION_NUM >= 90100
+		NULL,
+#endif
+#endif
+		NULL,
+		NULL
+	);
+
+	if (get_relation_info_hook != execPlantuner )
+	{
+		prevHook = get_relation_info_hook;
+		get_relation_info_hook = execPlantuner;
+	}
+}
diff --git a/contrib/plantuner/sql/plantuner.sql b/contrib/plantuner/sql/plantuner.sql
new file mode 100644
index 00000000000..ddd6fcc94f1
--- /dev/null
+++ b/contrib/plantuner/sql/plantuner.sql
@@ -0,0 +1,51 @@
+LOAD 'plantuner';
+
+SHOW	plantuner.disable_index;
+
+CREATE TABLE wow (i int, j int);
+CREATE INDEX i_idx ON wow (i);
+CREATE INDEX j_idx ON wow (j);
+CREATE INDEX i1 ON WOW (i);
+CREATE INDEX i2 ON WOW (i);
+CREATE INDEX i3 ON WOW (i);
+
+SET enable_seqscan=off;
+
+SELECT * FROM wow;
+
+SET plantuner.disable_index="i_idx, j_idx";
+
+SELECT * FROM wow;
+
+SHOW plantuner.disable_index;
+
+SET plantuner.disable_index="i_idx, nonexistent, public.j_idx, wow";
+
+SHOW plantuner.disable_index;
+
+SET plantuner.enable_index="i_idx";
+
+SHOW plantuner.enable_index;
+
+SELECT * FROM wow;
+--test only index
+RESET plantuner.disable_index;
+RESET plantuner.enable_index;
+
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+SET enable_indexonlyscan=off;
+
+SET plantuner.only_index="i1";
+SHOW plantuner.only_index;
+
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+
+SET plantuner.disable_index="i1,i2,i3";
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+
+SET plantuner.only_index="i2";
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
+
+RESET plantuner.only_index;
+EXPLAIN (COSTS OFF) SELECT * FROM wow WHERE i = 0;
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index 3cc4cd3369b..474aac7abd3 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -2496,32 +2496,35 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                ->  Merge Join
                      Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*, ft5.c1, ft5.c2, ft5.c3, ft5.*
                      Merge Cond: (ft1.c2 = ft5.c1)
-                     ->  Merge Join
+                     ->  Sort
                            Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
-                           Merge Cond: (ft1.c2 = ft4.c1)
-                           ->  Sort
-                                 Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                 Sort Key: ft1.c2
-                                 ->  Merge Join
+                           Sort Key: ft1.c2
+                           ->  Merge Join
+                                 Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*, ft4.c1, ft4.c2, ft4.c3, ft4.*
+                                 Merge Cond: (ft1.c2 = ft4.c1)
+                                 ->  Sort
                                        Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                       Merge Cond: (ft1.c1 = ft2.c1)
-                                       ->  Sort
-                                             Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                             Sort Key: ft1.c1
-                                             ->  Foreign Scan on public.ft1
+                                       Sort Key: ft1.c2
+                                       ->  Merge Join
+                                             Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
+                                             Merge Cond: (ft1.c1 = ft2.c1)
+                                             ->  Sort
                                                    Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
-                                       ->  Materialize
-                                             Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                             ->  Foreign Scan on public.ft2
+                                                   Sort Key: ft1.c1
+                                                   ->  Foreign Scan on public.ft1
+                                                         Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft1.*
+                                                         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) FOR UPDATE
+                                             ->  Materialize
                                                    Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
-                                                   Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
-                           ->  Sort
-                                 Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
-                                 Sort Key: ft4.c1
-                                 ->  Foreign Scan on public.ft4
+                                                   ->  Foreign Scan on public.ft2
+                                                         Output: ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft2.*
+                                                         Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" WHERE (("C 1" < 100)) ORDER BY "C 1" ASC NULLS LAST FOR UPDATE
+                                 ->  Sort
                                        Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
-                                       Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
+                                       Sort Key: ft4.c1
+                                       ->  Foreign Scan on public.ft4
+                                             Output: ft4.c1, ft4.c2, ft4.c3, ft4.*
+                                             Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 3" FOR UPDATE
                      ->  Sort
                            Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
                            Sort Key: ft5.c1
@@ -2530,7 +2533,7 @@ SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = f
                                  Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE
          ->  Index Scan using local_tbl_pkey on public.local_tbl
                Output: local_tbl.c1, local_tbl.c2, local_tbl.c3, local_tbl.ctid
-(47 rows)
+(50 rows)
 
 SELECT * FROM ft1, ft2, ft4, ft5, local_tbl WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
     AND ft1.c2 = ft5.c1 AND ft1.c2 = local_tbl.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
@@ -2939,16 +2942,13 @@ select c2 * (random() <= 1)::int as c2 from ft2 group by c2 * (random() <= 1)::i
 -- GROUP BY clause in various forms, cardinal, alias and constant expression
 explain (verbose, costs off)
 select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2;
-                                      QUERY PLAN                                       
----------------------------------------------------------------------------------------
- Sort
+                                                 QUERY PLAN                                                 
+------------------------------------------------------------------------------------------------------------
+ Foreign Scan
    Output: (count(c2)), c2, 5, 7.0, 9
-   Sort Key: ft1.c2
-   ->  Foreign Scan
-         Output: (count(c2)), c2, 5, 7.0, 9
-         Relations: Aggregate on (public.ft1)
-         Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5
-(7 rows)
+   Relations: Aggregate on (public.ft1)
+   Remote SQL: SELECT count(c2), c2, 5, 7.0, 9 FROM "S 1"."T 1" GROUP BY 2, 3, 5 ORDER BY c2 ASC NULLS LAST
+(4 rows)
 
 select count(c2) w, c2 x, 5 y, 7.0 z from ft1 group by 2, y, 9.0::int order by 2;
   w  | x | y |  z  
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 6e1ff45ca43..df9d22df239 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -589,7 +589,8 @@ loop:
 		else if (!ConditionalLockRelationForExtension(relation, ExclusiveLock))
 		{
 			/* Couldn't get the lock immediately; wait for it. */
-			LockRelationForExtension(relation, ExclusiveLock);
+			LockRelationForExtension(relation, ShareLock);
+			UnlockRelationForExtension(relation, ShareLock);
 
 			/*
 			 * Check if some other backend has extended a block for us while
@@ -601,13 +602,20 @@ loop:
 			 * If some other waiter has already extended the relation, we
 			 * don't need to do so; just use the existing freespace.
 			 */
+			if (targetBlock != InvalidBlockNumber)
+				goto loop;
+
+			/* Time to bulk-extend. */
+			LockRelationForExtension(relation, ExclusiveLock);
+
+			/* last chance */
+			targetBlock = GetPageWithFreeSpace(relation, targetFreeSpace);
 			if (targetBlock != InvalidBlockNumber)
 			{
 				UnlockRelationForExtension(relation, ExclusiveLock);
 				goto loop;
 			}
 
-			/* Time to bulk-extend. */
 			RelationAddExtraBlocks(relation, bistate);
 		}
 	}
diff --git a/src/backend/access/heap/visibilitymap.c b/src/backend/access/heap/visibilitymap.c
index e09f25a684c..8b98ab2907e 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -668,6 +668,7 @@ vm_extend(Relation rel, BlockNumber vm_nblocks)
 	 * to keep checking for creation or extension of the file, which happens
 	 * infrequently.
 	 */
+	if (!RELATION_IS_LOCAL(rel))
 	CacheInvalidateSmgr(reln->smgr_rnode);
 
 	UnlockRelationForExtension(rel, ExclusiveLock);
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index 24f1e0184a7..bcc5e2f4e21 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -324,6 +324,27 @@ performDeletion(const ObjectAddress *object,
 	Relation	depRel;
 	ObjectAddresses *targetObjects;
 
+	if (flags & PERFORM_DELETION_CONCURRENTLY)
+	{
+		/*
+		 * We must commit our transaction in order to make the first pg_index
+		 * state update visible to other sessions.  If the DROP machinery has
+		 * already performed any other actions (removal of other objects,
+		 * pg_depend entries, etc), the commit would make those actions
+		 * permanent, which would leave us with inconsistent catalog state if
+		 * we fail partway through the following sequence.  Since DROP INDEX
+		 * CONCURRENTLY is restricted to dropping just one index that has no
+		 * dependencies, we should get here before anything's been done ---
+		 * but let's check that to be sure.  We can verify that the current
+		 * transaction has not executed any transactional updates by checking
+		 * that no XID has been assigned.
+		 */
+		if (GetTopTransactionIdIfAny() != InvalidTransactionId)
+			ereport(ERROR,
+					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+					 errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
+	}
+
 	/*
 	 * We save some cycles by opening pg_depend just once and passing the
 	 * Relation pointer down to all the recursive deletion steps.
diff --git a/src/backend/catalog/index.c b/src/backend/catalog/index.c
index 37693ee6f7b..48697c6bf47 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -60,6 +60,7 @@
 #include "commands/tablecmds.h"
 #include "commands/tablespace.h"
 #include "commands/trigger.h"
+#include "commands/typecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -109,7 +110,7 @@ static TupleDesc ConstructTupleDescriptor(Relation heapRelation,
 										  List *indexColNames,
 										  Oid accessMethodObjectId,
 										  Oid *collationObjectId,
-										  Oid *classObjectId);
+						 Oid *classObjectId);
 static void InitializeAttributeOids(Relation indexRelation,
 									int numatts, Oid indexoid);
 static void AppendAttributeTuples(Relation indexRelation, Datum *attopts);
@@ -136,7 +137,7 @@ static void SetReindexProcessing(Oid heapOid, Oid indexOid);
 static void ResetReindexProcessing(void);
 static void SetReindexPending(List *indexes);
 static void RemoveReindexPending(Oid indexOid);
-
+static void IndexTypeCreate(Relation indexRelation);
 
 /*
  * relationHasPrimaryKey
@@ -650,6 +651,112 @@ UpdateIndexRelation(Oid indexoid,
 	heap_freetuple(tuple);
 }
 
+/*
+ * We only need to create reltype for multicolumn user-defined
+ * B-tree indexes that don't have a reltype yet.
+ */
+#define INDEX_NEEDS_RELTYPE(indexRelation, indexInfo, accessMethodOid) ( \
+	!IsSystemRelation(indexRelation)				\
+	&& indexInfo->ii_NumIndexKeyAttrs > 1			\
+	&& accessMethodOid == BTREE_AM_OID				\
+	&& indexRelation->rd_rel->reltype == InvalidOid	\
+	&& (!IsBinaryUpgrade || binary_upgrade_next_pg_type_oid != InvalidOid))
+
+/*
+ * IndexTypeCreate
+ *
+ * Create type for specified index.
+ */
+void
+IndexTypeCreate(Relation indexRelation)
+{
+	Oid ownerId = GetUserId();
+	Oid namespaceId = RelationGetNamespace(indexRelation);
+	Oid new_array_oid = AssignTypeArrayOid();
+	ObjectAddress new_type_addr;
+	char* relarrayname;
+
+	/* Index must not have a reltype yet */
+	Assert(indexRelation->rd_rel->reltype == InvalidOid);
+
+	/*
+	* Build compound type for compound index to be able to use it in statistic.
+	* We need to collect statistic for compound indexes to be able to better
+	* predict selectivity of multicolumn joins.
+	*/
+	new_type_addr = TypeCreate(InvalidOid,
+			RelationGetRelationName(indexRelation),
+			namespaceId,
+			RelationGetRelid(indexRelation),
+			RELKIND_INDEX,
+			ownerId, 			/* owner's ID */
+			-1,					/* internal size (varlena) */
+			TYPTYPE_COMPOSITE,	/* type-type (composite) */
+			TYPCATEGORY_COMPOSITE,	/* type-category (ditto) */
+			false,				/* composite types are never preferred */
+			DEFAULT_TYPDELIM,	/* default array delimiter */
+			F_RECORD_IN,		/* input procedure */
+			F_RECORD_OUT,		/* output procedure */
+			F_RECORD_RECV,		/* receive procedure */
+			F_RECORD_SEND,		/* send procedure */
+			InvalidOid,			/* typmodin procedure - none */
+			InvalidOid,			/* typmodout procedure - none */
+			InvalidOid,			/* analyze procedure - default */
+			InvalidOid,			/* subscript procedure - default */
+			InvalidOid,			/* array element type - irrelevant */
+			false,				/* this is not an array type */
+			new_array_oid,		/* array type if any */
+			InvalidOid,			/* domain base type - irrelevant */
+			NULL,				/* default value - none */
+			NULL,				/* default binary representation */
+			false,				/* passed by reference */
+			'd',				/* alignment - must be the largest! */
+			'x',				/* fully TOASTable */
+			-1,					/* typmod */
+			0,					/* array dimensions for typBaseType */
+			false,				/* Type NOT NULL */
+			InvalidOid); 		/* rowtypes never have a collation */
+
+	indexRelation->rd_rel->reltype = new_type_addr.objectId;
+
+	relarrayname = makeArrayTypeName(RelationGetRelationName(indexRelation),
+										namespaceId);
+
+	TypeCreate(new_array_oid,	/* force the type's OID to this */
+				relarrayname,	/* Array type name */
+				namespaceId,	/* Same namespace as parent */
+				InvalidOid,		/* Not composite, no relationOid */
+				0,				/* relkind, also N/A here */
+				ownerId,		/* owner's ID */
+				-1,				/* Internal size (varlena) */
+				TYPTYPE_BASE,	/* Not composite - typelem is */
+				TYPCATEGORY_ARRAY,	/* type-category (array) */
+				false,			/* array types are never preferred */
+				DEFAULT_TYPDELIM,	/* default array delimiter */
+				F_ARRAY_IN,		/* array input proc */
+				F_ARRAY_OUT,	/* array output proc */
+				F_ARRAY_RECV,	/* array recv (bin) proc */
+				F_ARRAY_SEND,	/* array send (bin) proc */
+				InvalidOid,		/* typmodin procedure - none */
+				InvalidOid,		/* typmodout procedure - none */
+				F_ARRAY_TYPANALYZE,	/* array analyze procedure */
+				F_ARRAY_SUBSCRIPT_HANDLER,	/* subscript procedure - default */
+				indexRelation->rd_rel->reltype,	/* array element type - the rowtype */
+				true,			/* yes, this is an array type */
+				InvalidOid,		/* this has no array type */
+				InvalidOid,		/* domain base type - irrelevant */
+				NULL,			/* default value - none */
+				NULL,			/* default binary representation */
+				false,			/* passed by reference */
+				'd',			/* alignment - must be the largest! */
+				'x',			/* fully TOASTable */
+				-1,				/* typmod */
+				0,				/* array dimensions for typBaseType */
+				false,			/* Type NOT NULL */
+				InvalidOid);	/* rowtypes never have a collation */
+
+	pfree(relarrayname);
+}
 
 /*
  * index_create
@@ -733,6 +840,7 @@ index_create(Relation heapRelation,
 	bool		invalid = (flags & INDEX_CREATE_INVALID) != 0;
 	bool		concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
 	bool		partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
+	bool		withoutType = (flags & INDEX_CREATE_WITHOUT_TYPE) != 0;
 	char		relkind;
 	TransactionId relfrozenxid;
 	MultiXactId relminmxid;
@@ -967,6 +1075,11 @@ index_create(Relation heapRelation,
 	Assert(relminmxid == InvalidMultiXactId);
 	Assert(indexRelationId == RelationGetRelid(indexRelation));
 
+	/* Create a reltype for index if it is needed */
+	if (withoutType == false && INDEX_NEEDS_RELTYPE(indexRelation, indexInfo, accessMethodObjectId)
+		&& !is_internal)
+		IndexTypeCreate(indexRelation);
+
 	/*
 	 * Obtain exclusive lock on it.  Although no other transactions can see it
 	 * until we commit, this prevents deadlock-risk complaints from lock
@@ -1419,7 +1532,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId,
 							  indclass->values,
 							  indcoloptions->values,
 							  optionDatum,
-							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT,
+							  INDEX_CREATE_SKIP_BUILD | INDEX_CREATE_CONCURRENT | INDEX_CREATE_WITHOUT_TYPE,
 							  0,
 							  true, /* allow table to be a system catalog? */
 							  false,	/* is_internal? */
@@ -1560,6 +1673,32 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
 	newClassForm->relispartition = oldClassForm->relispartition;
 	oldClassForm->relispartition = isPartition;
 
+	/* copy  index type to new index */
+	newClassForm->reltype = oldClassForm->reltype;
+
+	if (OidIsValid(oldClassForm->reltype))
+	{
+		Relation	pg_type;
+		HeapTuple	typeTuple;
+		Form_pg_type	typeForm;
+
+		pg_type = table_open(TypeRelationId, RowExclusiveLock);
+
+		typeTuple = SearchSysCacheCopy1(TYPEOID,
+										ObjectIdGetDatum(oldClassForm->reltype));
+		if (!HeapTupleIsValid(typeTuple))
+			elog(ERROR, "could not find tuple for type %u", oldClassForm->reltype);
+
+		typeForm = (Form_pg_type) GETSTRUCT(typeTuple);
+
+		typeForm->typrelid = newIndexId;
+
+		CatalogTupleUpdate(pg_type, &typeTuple->t_self, typeTuple);
+
+		heap_freetuple(typeTuple);
+		table_close(pg_type, RowExclusiveLock);
+	}
+
 	CatalogTupleUpdate(pg_class, &oldClassTuple->t_self, oldClassTuple);
 	CatalogTupleUpdate(pg_class, &newClassTuple->t_self, newClassTuple);
 
@@ -1748,8 +1887,9 @@ index_concurrently_swap(Oid newIndexId, Oid oldIndexId, const char *oldName)
 	 * vice-versa.  Note that a call to CommandCounterIncrement() would cause
 	 * duplicate entries in pg_depend, so this should not be done.
 	 */
-	changeDependenciesOf(RelationRelationId, newIndexId, oldIndexId);
-	changeDependenciesOn(RelationRelationId, newIndexId, oldIndexId);
+	//changeDependenciesOf(RelationRelationId, newIndexId, oldIndexId);
+	//changeDependenciesOn(RelationRelationId, newIndexId, oldIndexId);
+	deleteDependencyRecordsFor(RelationRelationId, newIndexId, false);
 
 	changeDependenciesOf(RelationRelationId, oldIndexId, newIndexId);
 	changeDependenciesOn(RelationRelationId, oldIndexId, newIndexId);
@@ -2138,6 +2278,7 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	Relation	indexRelation;
 	HeapTuple	tuple;
 	bool		hasexprs;
+	bool		remove_statistics;
 	LockRelId	heaprelid,
 				indexrelid;
 	LOCKTAG		heaplocktag;
@@ -2214,24 +2355,6 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	 */
 	if (concurrent)
 	{
-		/*
-		 * We must commit our transaction in order to make the first pg_index
-		 * state update visible to other sessions.  If the DROP machinery has
-		 * already performed any other actions (removal of other objects,
-		 * pg_depend entries, etc), the commit would make those actions
-		 * permanent, which would leave us with inconsistent catalog state if
-		 * we fail partway through the following sequence.  Since DROP INDEX
-		 * CONCURRENTLY is restricted to dropping just one index that has no
-		 * dependencies, we should get here before anything's been done ---
-		 * but let's check that to be sure.  We can verify that the current
-		 * transaction has not executed any transactional updates by checking
-		 * that no XID has been assigned.
-		 */
-		if (GetTopTransactionIdIfAny() != InvalidTransactionId)
-			ereport(ERROR,
-					(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-					 errmsg("DROP INDEX CONCURRENTLY must be first action in transaction")));
-
 		/*
 		 * Mark index invalid by updating its pg_index entry
 		 */
@@ -2331,6 +2454,16 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	/* ensure that stats are dropped if transaction commits */
 	pgstat_drop_relation(userIndexRelation);
 
+	/*
+	 * We might have stored multicolumn statistics for btree indexes. They are
+	 * created only for non-system and non-TOAST indexes, so check only for such
+	 * such indexes.
+	 */
+	remove_statistics =
+		IndexRelationGetNumberOfKeyAttributes(userIndexRelation) > 1 &&
+		userIndexRelation->rd_rel->relam == BTREE_AM_OID &&
+		!IsSystemRelation(userIndexRelation);
+
 	/*
 	 * Close and flush the index's relcache entry, to ensure relcache doesn't
 	 * try to rebuild it while we're deleting catalog entries. We keep the
@@ -2358,10 +2491,10 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	table_close(indexRelation, RowExclusiveLock);
 
 	/*
-	 * if it has any expression columns, we might have stored statistics about
-	 * them.
+	 * if it has any expression columns or whole index stat, we might have
+	 * stored statistics about them.
 	 */
-	if (hasexprs)
+	if (hasexprs || remove_statistics)
 		RemoveStatistics(indexId, 0);
 
 	/*
@@ -2895,6 +3028,14 @@ index_update_stats(Relation rel,
 		dirty = true;
 	}
 
+	/* If index's reltype has been created, update it in pg_class. */
+	if (rel->rd_rel->relkind == RELKIND_INDEX &&
+		rd_rel->reltype != rel->rd_rel->reltype)
+	{
+		rd_rel->reltype = rel->rd_rel->reltype;
+		dirty = true;
+	}
+
 	if (update_stats)
 	{
 		if (rd_rel->relpages != (int32) relpages)
@@ -3742,6 +3883,10 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
 	/* Create a new physical relation for the index */
 	RelationSetNewRelfilenode(iRel, persistence);
 
+	/* Create a reltype for index if it is needed */
+	if (INDEX_NEEDS_RELTYPE(iRel, indexInfo, iRel->rd_rel->relam))
+		IndexTypeCreate(iRel);
+
 	/* Initialize the index and rebuild */
 	/* Note: we do not need to re-establish pkey setting */
 	index_build(heapRelation, iRel, indexInfo, true, true);
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index 7cbeb16c3a6..4350545c75a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -41,8 +41,11 @@
 #include "common/pg_prng.h"
 #include "executor/executor.h"
 #include "foreign/fdwapi.h"
+#include "funcapi.h"
 #include "miscadmin.h"
 #include "nodes/nodeFuncs.h"
+#include "nodes/makefuncs.h"
+#include "nodes/pg_list.h"
 #include "parser/parse_oper.h"
 #include "parser/parse_relation.h"
 #include "pgstat.h"
@@ -67,6 +70,7 @@
 #include "utils/spccache.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 
 
 /* Per-index data for ANALYZE */
@@ -76,6 +80,7 @@ typedef struct AnlIndexData
 	double		tupleFract;		/* fraction of rows for partial index */
 	VacAttrStats **vacattrstats;	/* index attrs to analyze */
 	int			attr_cnt;
+	bool        multicolumn;    /* Collect compound row statistic for multicolumn index */
 } AnlIndexData;
 
 
@@ -487,6 +492,21 @@ do_analyze_rel(Relation onerel, VacuumParams *params,
 				}
 				thisdata->attr_cnt = tcnt;
 			}
+			else if (indexInfo->ii_NumIndexAttrs > 1 && va_cols == NIL &&
+					 Irel[ind]->rd_rel->reltype != InvalidOid)
+			{
+				/* Collect statistic for multicolumn index for better predicting selectivity of multicolumn joins */
+				RowExpr* row = makeNode(RowExpr);
+				row->row_typeid = Irel[ind]->rd_rel->reltype;
+				row->row_format = COERCE_EXPLICIT_CAST;
+				row->location = -1;
+				row->colnames = NULL;
+				thisdata->vacattrstats = (VacAttrStats **)palloc(sizeof(VacAttrStats *));
+				thisdata->vacattrstats[0] = examine_attribute(Irel[ind], 1, (Node*)row);
+				thisdata->vacattrstats[0]->tupDesc = lookup_type_cache(row->row_typeid, TYPECACHE_TUPDESC)->tupDesc;
+				thisdata->attr_cnt = 1;
+				thisdata->multicolumn = true;
+			}
 		}
 	}
 
@@ -926,28 +946,41 @@ compute_index_stats(Relation onerel, double totalrows,
 							   values,
 							   isnull);
 
-				/*
-				 * Save just the columns we care about.  We copy the values
-				 * into ind_context from the estate's per-tuple context.
-				 */
-				for (i = 0; i < attr_cnt; i++)
+				if (thisdata->multicolumn)
 				{
-					VacAttrStats *stats = thisdata->vacattrstats[i];
-					int			attnum = stats->attr->attnum;
-
-					if (isnull[attnum - 1])
-					{
-						exprvals[tcnt] = (Datum) 0;
-						exprnulls[tcnt] = true;
-					}
-					else
+					/* For multicolumn index construct compound value */
+					VacAttrStats *stats = thisdata->vacattrstats[0];
+					exprvals[tcnt] = HeapTupleGetDatum(heap_form_tuple(stats->tupDesc,
+																	   values,
+																	   isnull));
+					exprnulls[tcnt] = false;
+					tcnt++;
+				}
+				else
+				{
+					/*
+					 * Save just the columns we care about.  We copy the values
+					 * into ind_context from the estate's per-tuple context.
+					 */
+					for (i = 0; i < attr_cnt; i++)
 					{
-						exprvals[tcnt] = datumCopy(values[attnum - 1],
-												   stats->attrtype->typbyval,
-												   stats->attrtype->typlen);
-						exprnulls[tcnt] = false;
+						VacAttrStats *stats = thisdata->vacattrstats[i];
+						int			attnum = stats->attr->attnum;
+
+						if (isnull[attnum - 1])
+						{
+							exprvals[tcnt] = (Datum) 0;
+							exprnulls[tcnt] = true;
+						}
+						else
+						{
+							exprvals[tcnt] = datumCopy(values[attnum - 1],
+													   stats->attrtype->typbyval,
+													   stats->attrtype->typlen);
+							exprnulls[tcnt] = false;
+						}
+						tcnt++;
 					}
-					tcnt++;
 				}
 			}
 		}
@@ -2733,6 +2766,7 @@ compute_scalar_stats(VacAttrStatsP stats,
 		 * histogram won't collapse to empty or a singleton.)
 		 */
 		num_hist = ndistinct - num_mcv;
+
 		if (num_hist > num_bins)
 			num_hist = num_bins + 1;
 		if (num_hist >= 2)
diff --git a/src/backend/commands/explain.c b/src/backend/commands/explain.c
index fa0b79d5449..795c3858f46 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -384,21 +384,15 @@ ExplainOneQuery(Query *query, int cursorOptions,
 	else
 	{
 		PlannedStmt *plan;
-		instr_time	planstart,
-					planduration;
 		BufferUsage bufusage_start,
 					bufusage;
 
 		if (es->buffers)
 			bufusage_start = pgBufferUsage;
-		INSTR_TIME_SET_CURRENT(planstart);
 
 		/* plan the query */
 		plan = pg_plan_query(query, queryString, cursorOptions, params);
 
-		INSTR_TIME_SET_CURRENT(planduration);
-		INSTR_TIME_SUBTRACT(planduration, planstart);
-
 		/* calc differences of buffer counters. */
 		if (es->buffers)
 		{
@@ -408,7 +402,7 @@ ExplainOneQuery(Query *query, int cursorOptions,
 
 		/* run it (if needed) and produce output */
 		ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
-					   &planduration, (es->buffers ? &bufusage : NULL));
+					   &plan->planDuration, (es->buffers ? &bufusage : NULL));
 	}
 }
 
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 4b57f538baf..717ac31f15c 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -251,13 +251,33 @@ ExecInitQual(List *qual, PlanState *parent)
 	foreach(lc, qual)
 	{
 		Expr	   *node = (Expr *) lfirst(lc);
+		ExprEvalStep *lastStep;
 
 		/* first evaluate expression */
 		ExecInitExprRec(node, state, &state->resvalue, &state->resnull);
 
 		/* then emit EEOP_QUAL to detect if it's false (or null) */
 		scratch.d.qualexpr.jumpdone = -1;
+
+		lastStep = &state->steps[state->steps_len-1];
+		if (list_length(qual) == 1 &&
+			(lastStep->opcode == EEOP_BOOL_OR_STEP_LAST ||
+			 lastStep->opcode == EEOP_BOOL_AND_STEP_LAST))
+			scratch.d.qualexpr.guaranteed_empty =
+				lastStep->d.boolexpr.guaranteed_empty;
+		else if (list_length(qual) == 1 &&
+				 lastStep->opcode == EEOP_SUBPLAN)
+		{
+			scratch.d.qualexpr.guaranteed_empty =
+				lastStep->d.subplan.guaranteed_empty =
+					palloc(sizeof(bool));
+			*scratch.d.qualexpr.guaranteed_empty = false;
+		}
+		else
+			scratch.d.qualexpr.guaranteed_empty = NULL;
+
 		ExprEvalPushStep(state, &scratch);
+
 		adjust_jumps = lappend_int(adjust_jumps,
 								   state->steps_len - 1);
 	}
@@ -1315,8 +1335,15 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				ListCell   *lc;
 
 				/* allocate scratch memory used by all steps of AND/OR */
+				scratch.d.boolexpr.guaranteed_empty = NULL;
 				if (boolexpr->boolop != NOT_EXPR)
+				{
 					scratch.d.boolexpr.anynull = (bool *) palloc(sizeof(bool));
+					scratch.d.boolexpr.guaranteed_empty = (bool *) palloc(sizeof(bool));
+					scratch.d.boolexpr.count_guaranteed_empty = (int *) palloc(sizeof(int));
+					*scratch.d.boolexpr.guaranteed_empty = false;
+					scratch.d.boolexpr.nargs = nargs;
+				}
 
 				/*
 				 * For each argument evaluate the argument itself, then
@@ -1335,10 +1362,16 @@ ExecInitExprRec(Expr *node, ExprState *state,
 				foreach(lc, boolexpr->args)
 				{
 					Expr	   *arg = (Expr *) lfirst(lc);
+					ExprEvalStep	*lastStep;
 
 					/* Evaluate argument into our output variable */
 					ExecInitExprRec(arg, state, resv, resnull);
 
+					lastStep = &state->steps[state->steps_len-1];
+					if (lastStep->opcode == EEOP_SUBPLAN)
+						lastStep->d.subplan.guaranteed_empty =
+							scratch.d.boolexpr.guaranteed_empty;
+
 					/* Perform the appropriate step type */
 					switch (boolexpr->boolop)
 					{
@@ -1423,6 +1456,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				scratch.opcode = EEOP_SUBPLAN;
 				scratch.d.subplan.sstate = sstate;
+				scratch.d.subplan.sstate->guaranteed_empty = false;
 
 				ExprEvalPushStep(state, &scratch);
 				break;
@@ -3865,6 +3899,7 @@ ExecBuildGroupingEqual(TupleDesc ldesc, TupleDesc rdesc,
 
 		/* then emit EEOP_QUAL to detect if result is false (or null) */
 		scratch.opcode = EEOP_QUAL;
+		scratch.d.qualexpr.guaranteed_empty = NULL;
 		scratch.d.qualexpr.jumpdone = -1;
 		scratch.resvalue = &state->resvalue;
 		scratch.resnull = &state->resnull;
diff --git a/src/backend/executor/execExprInterp.c b/src/backend/executor/execExprInterp.c
index 55d42cd101d..98a9fdfa9d1 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -807,6 +807,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 				EEO_JUMP(op->d.boolexpr.jumpdone);
 			}
 
+			/* reset */
+			*op->d.boolexpr.guaranteed_empty = false;
+
 			EEO_NEXT();
 		}
 
@@ -815,6 +818,8 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			if (*op->resnull)
 			{
 				/* result is already set to NULL, need not change it */
+				/* reset */
+				*op->d.boolexpr.guaranteed_empty = false;
 			}
 			else if (!DatumGetBool(*op->resvalue))
 			{
@@ -830,10 +835,15 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			{
 				*op->resvalue = (Datum) 0;
 				*op->resnull = true;
+				/* reset */
+				*op->d.boolexpr.guaranteed_empty = false;
 			}
 			else
 			{
 				/* result is already set to TRUE, need not change it */
+				/* reset */
+				*op->d.boolexpr.guaranteed_empty = false;
+
 			}
 
 			EEO_NEXT();
@@ -852,6 +862,7 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 		EEO_CASE(EEOP_BOOL_OR_STEP_FIRST)
 		{
 			*op->d.boolexpr.anynull = false;
+			*op->d.boolexpr.count_guaranteed_empty = 0;
 
 			/*
 			 * EEOP_BOOL_OR_STEP_FIRST resets anynull, otherwise it's the same
@@ -863,6 +874,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 
 		EEO_CASE(EEOP_BOOL_OR_STEP)
 		{
+			*op->d.boolexpr.count_guaranteed_empty +=
+				(int) (*op->d.boolexpr.guaranteed_empty);
+			*op->d.boolexpr.guaranteed_empty = false;
+
 			if (*op->resnull)
 			{
 				*op->d.boolexpr.anynull = true;
@@ -879,6 +894,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 
 		EEO_CASE(EEOP_BOOL_OR_STEP_LAST)
 		{
+			*op->d.boolexpr.count_guaranteed_empty +=
+				(int) (*op->d.boolexpr.guaranteed_empty);
+			*op->d.boolexpr.guaranteed_empty = false;
+
 			if (*op->resnull)
 			{
 				/* result is already set to NULL, need not change it */
@@ -900,6 +919,10 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 			}
 			else
 			{
+				if (*op->d.boolexpr.count_guaranteed_empty == op->d.boolexpr.nargs)
+					*op->d.boolexpr.guaranteed_empty = true;
+				else
+					*op->d.boolexpr.guaranteed_empty = false;
 				/* result is already set to FALSE, need not change it */
 			}
 
@@ -930,6 +953,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 				/* ... bail out early, returning FALSE */
 				*op->resnull = false;
 				*op->resvalue = BoolGetDatum(false);
+				if (op->d.qualexpr.guaranteed_empty &&
+					op - state->steps == state->steps_len - 2 /* + EEOP_DONE */)
+					state->guaranteed_empty = *op->d.qualexpr.guaranteed_empty;
 				EEO_JUMP(op->d.qualexpr.jumpdone);
 			}
 
@@ -3980,7 +4006,16 @@ ExecEvalSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econtext)
 	/* could potentially be nested, so make sure there's enough stack */
 	check_stack_depth();
 
-	*op->resvalue = ExecSubPlan(sstate, econtext, op->resnull);
+	if (sstate->guaranteed_empty == false)
+		*op->resvalue = ExecSubPlan(sstate, econtext, op->resnull);
+	else
+	{
+		*op->resvalue = false;
+		*op->resnull = false;
+	}
+
+	if (op->opcode == EEOP_SUBPLAN && op->d.subplan.guaranteed_empty && sstate->guaranteed_empty)
+		*op->d.subplan.guaranteed_empty = sstate->guaranteed_empty;
 }
 
 /*
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 5ae8f4da72c..d373ec0afc4 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -186,6 +186,7 @@ ExecScan(ScanState *node,
 	 * storage allocated in the previous tuple cycle.
 	 */
 	ResetExprContext(econtext);
+	node->ps.guaranteed_empty = false;
 
 	/*
 	 * get a tuple from the access method.  Loop until we obtain a tuple that
@@ -244,7 +245,14 @@ ExecScan(ScanState *node,
 				return slot;
 			}
 		}
-		else
+		else if (qual && qual->guaranteed_empty)
+		{
+			/* Qual guarantees the absence of results */
+			node->ps.guaranteed_empty = true;
+			ExecClearTuple(slot);
+
+			return slot;
+		} else
 			InstrCountFiltered1(node, 1);
 
 		/*
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 2cb27e0e9ae..284a9f27bad 100644
--- a/src/backend/executor/nodeMaterial.c
+++ b/src/backend/executor/nodeMaterial.c
@@ -135,6 +135,8 @@ ExecMaterial(PlanState *pstate)
 		if (TupIsNull(outerslot))
 		{
 			node->eof_underlying = true;
+			if (tuplestore_tuple_count(tuplestorestate) == 0)
+				node->ss.ps.guaranteed_empty = true;
 			return NULL;
 		}
 
@@ -363,6 +365,9 @@ ExecReScanMaterial(MaterialState *node)
 		 */
 		if (outerPlan->chgParam == NULL)
 			ExecReScan(outerPlan);
+		else
+			node->ss.ps.guaranteed_empty = false;
+
 		node->eof_underlying = false;
 	}
 }
diff --git a/src/backend/executor/nodeNestloop.c b/src/backend/executor/nodeNestloop.c
index 06767c3133f..c5fc14a51bb 100644
--- a/src/backend/executor/nodeNestloop.c
+++ b/src/backend/executor/nodeNestloop.c
@@ -164,6 +164,11 @@ ExecNestLoop(PlanState *pstate)
 		{
 			ENL1_printf("no inner tuple, need new outer tuple");
 
+			if (innerPlan->guaranteed_empty &&
+				(node->js.jointype == JOIN_INNER ||
+				 node->js.jointype == JOIN_SEMI))
+				return NULL;
+
 			node->nl_NeedNewOuter = true;
 
 			if (!node->nl_MatchedOuter &&
diff --git a/src/backend/executor/nodeSubplan.c b/src/backend/executor/nodeSubplan.c
index a33efce3fe1..afb0d728a8a 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -84,7 +84,11 @@ ExecSubPlan(SubPlanState *node,
 
 	/* Select appropriate evaluation strategy */
 	if (subplan->useHashTable)
+	{
 		retval = ExecHashSubPlan(node, econtext, isNull);
+		if (node->planstate->guaranteed_empty)
+			node->guaranteed_empty = true;
+	}
 	else
 		retval = ExecScanSubPlan(node, econtext, isNull);
 
@@ -105,6 +109,9 @@ ExecHashSubPlan(SubPlanState *node,
 	SubPlan    *subplan = node->subplan;
 	PlanState  *planstate = node->planstate;
 	TupleTableSlot *slot;
+	bool	hasParam = (planstate->plan->extParam != NULL ||
+						subplan->setParam != NIL ||
+						planstate->chgParam != NULL);
 
 	/* Shouldn't have any direct correlation Vars */
 	if (subplan->parParam != NIL || node->args != NIL)
@@ -122,8 +129,11 @@ ExecHashSubPlan(SubPlanState *node,
 	 * lefthand side.
 	 */
 	*isNull = false;
-	if (!node->havehashrows && !node->havenullrows)
+	if (!node->havehashrows && !node->havenullrows) {
+		if (hasParam == false)
+			node->planstate->guaranteed_empty = true;
 		return BoolGetDatum(false);
+	}
 
 	/*
 	 * Evaluate lefthand expressions and form a projection tuple. First we
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 675041f9c96..3c78d508e34 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2641,16 +2641,26 @@ range_table_entry_walker(RangeTblEntry *rte,
 Node *
 expression_tree_mutator(Node *node,
 						Node *(*mutator) (),
-						void *context)
+						void *context,
+						int	flags)
 {
 	/*
 	 * The mutator has already decided not to modify the current node, but we
 	 * must call the mutator for any sub-nodes.
 	 */
 
-#define FLATCOPY(newnode, node, nodetype)  \
-	( (newnode) = (nodetype *) palloc(sizeof(nodetype)), \
-	  memcpy((newnode), (node), sizeof(nodetype)) )
+#define FLATCOPY(newnode, node, nodetype, flags)  \
+	do { \
+		if ((flags) & QTW_DONT_COPY_DEFAULT) \
+		{ \
+			(newnode) = (node); \
+		} \
+		else \
+		{ \
+			(newnode) = (nodetype *) palloc(sizeof(nodetype)); \
+			memcpy((newnode), (node), sizeof(nodetype)); \
+		} \
+	} while(0)
 
 #define MUTATE(newfield, oldfield, fieldtype)  \
 		( (newfield) = (fieldtype) mutator((Node *) (oldfield), context) )
@@ -2673,7 +2683,7 @@ expression_tree_mutator(Node *node,
 				Var		   *var = (Var *) node;
 				Var		   *newnode;
 
-				FLATCOPY(newnode, var, Var);
+				FLATCOPY(newnode, var, Var, flags);
 				return (Node *) newnode;
 			}
 			break;
@@ -2682,7 +2692,7 @@ expression_tree_mutator(Node *node,
 				Const	   *oldnode = (Const *) node;
 				Const	   *newnode;
 
-				FLATCOPY(newnode, oldnode, Const);
+				FLATCOPY(newnode, oldnode, Const, flags);
 				/* XXX we don't bother with datumCopy; should we? */
 				return (Node *) newnode;
 			}
@@ -2703,7 +2713,7 @@ expression_tree_mutator(Node *node,
 				WithCheckOption *wco = (WithCheckOption *) node;
 				WithCheckOption *newnode;
 
-				FLATCOPY(newnode, wco, WithCheckOption);
+				FLATCOPY(newnode, wco, WithCheckOption, flags);
 				MUTATE(newnode->qual, wco->qual, Node *);
 				return (Node *) newnode;
 			}
@@ -2712,7 +2722,7 @@ expression_tree_mutator(Node *node,
 				Aggref	   *aggref = (Aggref *) node;
 				Aggref	   *newnode;
 
-				FLATCOPY(newnode, aggref, Aggref);
+				FLATCOPY(newnode, aggref, Aggref, flags);
 				/* assume mutation doesn't change types of arguments */
 				newnode->aggargtypes = list_copy(aggref->aggargtypes);
 				MUTATE(newnode->aggdirectargs, aggref->aggdirectargs, List *);
@@ -2728,7 +2738,7 @@ expression_tree_mutator(Node *node,
 				GroupingFunc *grouping = (GroupingFunc *) node;
 				GroupingFunc *newnode;
 
-				FLATCOPY(newnode, grouping, GroupingFunc);
+				FLATCOPY(newnode, grouping, GroupingFunc, flags);
 				MUTATE(newnode->args, grouping->args, List *);
 
 				/*
@@ -2751,7 +2761,7 @@ expression_tree_mutator(Node *node,
 				WindowFunc *wfunc = (WindowFunc *) node;
 				WindowFunc *newnode;
 
-				FLATCOPY(newnode, wfunc, WindowFunc);
+				FLATCOPY(newnode, wfunc, WindowFunc, flags);
 				MUTATE(newnode->args, wfunc->args, List *);
 				MUTATE(newnode->aggfilter, wfunc->aggfilter, Expr *);
 				return (Node *) newnode;
@@ -2762,7 +2772,7 @@ expression_tree_mutator(Node *node,
 				SubscriptingRef *sbsref = (SubscriptingRef *) node;
 				SubscriptingRef *newnode;
 
-				FLATCOPY(newnode, sbsref, SubscriptingRef);
+				FLATCOPY(newnode, sbsref, SubscriptingRef, flags);
 				MUTATE(newnode->refupperindexpr, sbsref->refupperindexpr,
 					   List *);
 				MUTATE(newnode->reflowerindexpr, sbsref->reflowerindexpr,
@@ -2780,7 +2790,7 @@ expression_tree_mutator(Node *node,
 				FuncExpr   *expr = (FuncExpr *) node;
 				FuncExpr   *newnode;
 
-				FLATCOPY(newnode, expr, FuncExpr);
+				FLATCOPY(newnode, expr, FuncExpr, flags);
 				MUTATE(newnode->args, expr->args, List *);
 				return (Node *) newnode;
 			}
@@ -2790,7 +2800,7 @@ expression_tree_mutator(Node *node,
 				NamedArgExpr *nexpr = (NamedArgExpr *) node;
 				NamedArgExpr *newnode;
 
-				FLATCOPY(newnode, nexpr, NamedArgExpr);
+				FLATCOPY(newnode, nexpr, NamedArgExpr, flags);
 				MUTATE(newnode->arg, nexpr->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -2800,7 +2810,7 @@ expression_tree_mutator(Node *node,
 				OpExpr	   *expr = (OpExpr *) node;
 				OpExpr	   *newnode;
 
-				FLATCOPY(newnode, expr, OpExpr);
+				FLATCOPY(newnode, expr, OpExpr, flags & ~QTW_DONT_COPY_DEFAULT);
 				MUTATE(newnode->args, expr->args, List *);
 				return (Node *) newnode;
 			}
@@ -2810,7 +2820,7 @@ expression_tree_mutator(Node *node,
 				DistinctExpr *expr = (DistinctExpr *) node;
 				DistinctExpr *newnode;
 
-				FLATCOPY(newnode, expr, DistinctExpr);
+				FLATCOPY(newnode, expr, DistinctExpr, flags);
 				MUTATE(newnode->args, expr->args, List *);
 				return (Node *) newnode;
 			}
@@ -2820,7 +2830,7 @@ expression_tree_mutator(Node *node,
 				NullIfExpr *expr = (NullIfExpr *) node;
 				NullIfExpr *newnode;
 
-				FLATCOPY(newnode, expr, NullIfExpr);
+				FLATCOPY(newnode, expr, NullIfExpr, flags);
 				MUTATE(newnode->args, expr->args, List *);
 				return (Node *) newnode;
 			}
@@ -2830,7 +2840,7 @@ expression_tree_mutator(Node *node,
 				ScalarArrayOpExpr *expr = (ScalarArrayOpExpr *) node;
 				ScalarArrayOpExpr *newnode;
 
-				FLATCOPY(newnode, expr, ScalarArrayOpExpr);
+				FLATCOPY(newnode, expr, ScalarArrayOpExpr, flags);
 				MUTATE(newnode->args, expr->args, List *);
 				return (Node *) newnode;
 			}
@@ -2840,7 +2850,7 @@ expression_tree_mutator(Node *node,
 				BoolExpr   *expr = (BoolExpr *) node;
 				BoolExpr   *newnode;
 
-				FLATCOPY(newnode, expr, BoolExpr);
+				FLATCOPY(newnode, expr, BoolExpr, flags);
 				MUTATE(newnode->args, expr->args, List *);
 				return (Node *) newnode;
 			}
@@ -2850,7 +2860,7 @@ expression_tree_mutator(Node *node,
 				SubLink    *sublink = (SubLink *) node;
 				SubLink    *newnode;
 
-				FLATCOPY(newnode, sublink, SubLink);
+				FLATCOPY(newnode, sublink, SubLink, flags);
 				MUTATE(newnode->testexpr, sublink->testexpr, Node *);
 
 				/*
@@ -2866,7 +2876,7 @@ expression_tree_mutator(Node *node,
 				SubPlan    *subplan = (SubPlan *) node;
 				SubPlan    *newnode;
 
-				FLATCOPY(newnode, subplan, SubPlan);
+				FLATCOPY(newnode, subplan, SubPlan, flags);
 				/* transform testexpr */
 				MUTATE(newnode->testexpr, subplan->testexpr, Node *);
 				/* transform args list (params to be passed to subplan) */
@@ -2880,7 +2890,7 @@ expression_tree_mutator(Node *node,
 				AlternativeSubPlan *asplan = (AlternativeSubPlan *) node;
 				AlternativeSubPlan *newnode;
 
-				FLATCOPY(newnode, asplan, AlternativeSubPlan);
+				FLATCOPY(newnode, asplan, AlternativeSubPlan, flags);
 				MUTATE(newnode->subplans, asplan->subplans, List *);
 				return (Node *) newnode;
 			}
@@ -2890,7 +2900,7 @@ expression_tree_mutator(Node *node,
 				FieldSelect *fselect = (FieldSelect *) node;
 				FieldSelect *newnode;
 
-				FLATCOPY(newnode, fselect, FieldSelect);
+				FLATCOPY(newnode, fselect, FieldSelect, flags);
 				MUTATE(newnode->arg, fselect->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -2900,7 +2910,7 @@ expression_tree_mutator(Node *node,
 				FieldStore *fstore = (FieldStore *) node;
 				FieldStore *newnode;
 
-				FLATCOPY(newnode, fstore, FieldStore);
+				FLATCOPY(newnode, fstore, FieldStore, flags);
 				MUTATE(newnode->arg, fstore->arg, Expr *);
 				MUTATE(newnode->newvals, fstore->newvals, List *);
 				newnode->fieldnums = list_copy(fstore->fieldnums);
@@ -2912,7 +2922,7 @@ expression_tree_mutator(Node *node,
 				RelabelType *relabel = (RelabelType *) node;
 				RelabelType *newnode;
 
-				FLATCOPY(newnode, relabel, RelabelType);
+				FLATCOPY(newnode, relabel, RelabelType, flags);
 				MUTATE(newnode->arg, relabel->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -2922,7 +2932,7 @@ expression_tree_mutator(Node *node,
 				CoerceViaIO *iocoerce = (CoerceViaIO *) node;
 				CoerceViaIO *newnode;
 
-				FLATCOPY(newnode, iocoerce, CoerceViaIO);
+				FLATCOPY(newnode, iocoerce, CoerceViaIO, flags);
 				MUTATE(newnode->arg, iocoerce->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -2932,7 +2942,7 @@ expression_tree_mutator(Node *node,
 				ArrayCoerceExpr *acoerce = (ArrayCoerceExpr *) node;
 				ArrayCoerceExpr *newnode;
 
-				FLATCOPY(newnode, acoerce, ArrayCoerceExpr);
+				FLATCOPY(newnode, acoerce, ArrayCoerceExpr, flags);
 				MUTATE(newnode->arg, acoerce->arg, Expr *);
 				MUTATE(newnode->elemexpr, acoerce->elemexpr, Expr *);
 				return (Node *) newnode;
@@ -2943,7 +2953,7 @@ expression_tree_mutator(Node *node,
 				ConvertRowtypeExpr *convexpr = (ConvertRowtypeExpr *) node;
 				ConvertRowtypeExpr *newnode;
 
-				FLATCOPY(newnode, convexpr, ConvertRowtypeExpr);
+				FLATCOPY(newnode, convexpr, ConvertRowtypeExpr, flags);
 				MUTATE(newnode->arg, convexpr->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -2953,7 +2963,7 @@ expression_tree_mutator(Node *node,
 				CollateExpr *collate = (CollateExpr *) node;
 				CollateExpr *newnode;
 
-				FLATCOPY(newnode, collate, CollateExpr);
+				FLATCOPY(newnode, collate, CollateExpr, flags);
 				MUTATE(newnode->arg, collate->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -2963,7 +2973,7 @@ expression_tree_mutator(Node *node,
 				CaseExpr   *caseexpr = (CaseExpr *) node;
 				CaseExpr   *newnode;
 
-				FLATCOPY(newnode, caseexpr, CaseExpr);
+				FLATCOPY(newnode, caseexpr, CaseExpr, flags);
 				MUTATE(newnode->arg, caseexpr->arg, Expr *);
 				MUTATE(newnode->args, caseexpr->args, List *);
 				MUTATE(newnode->defresult, caseexpr->defresult, Expr *);
@@ -2975,7 +2985,7 @@ expression_tree_mutator(Node *node,
 				CaseWhen   *casewhen = (CaseWhen *) node;
 				CaseWhen   *newnode;
 
-				FLATCOPY(newnode, casewhen, CaseWhen);
+				FLATCOPY(newnode, casewhen, CaseWhen, flags);
 				MUTATE(newnode->expr, casewhen->expr, Expr *);
 				MUTATE(newnode->result, casewhen->result, Expr *);
 				return (Node *) newnode;
@@ -2986,7 +2996,7 @@ expression_tree_mutator(Node *node,
 				ArrayExpr  *arrayexpr = (ArrayExpr *) node;
 				ArrayExpr  *newnode;
 
-				FLATCOPY(newnode, arrayexpr, ArrayExpr);
+				FLATCOPY(newnode, arrayexpr, ArrayExpr, flags);
 				MUTATE(newnode->elements, arrayexpr->elements, List *);
 				return (Node *) newnode;
 			}
@@ -2996,7 +3006,7 @@ expression_tree_mutator(Node *node,
 				RowExpr    *rowexpr = (RowExpr *) node;
 				RowExpr    *newnode;
 
-				FLATCOPY(newnode, rowexpr, RowExpr);
+				FLATCOPY(newnode, rowexpr, RowExpr, flags);
 				MUTATE(newnode->args, rowexpr->args, List *);
 				/* Assume colnames needn't be duplicated */
 				return (Node *) newnode;
@@ -3007,7 +3017,7 @@ expression_tree_mutator(Node *node,
 				RowCompareExpr *rcexpr = (RowCompareExpr *) node;
 				RowCompareExpr *newnode;
 
-				FLATCOPY(newnode, rcexpr, RowCompareExpr);
+				FLATCOPY(newnode, rcexpr, RowCompareExpr, flags);
 				MUTATE(newnode->largs, rcexpr->largs, List *);
 				MUTATE(newnode->rargs, rcexpr->rargs, List *);
 				return (Node *) newnode;
@@ -3018,7 +3028,7 @@ expression_tree_mutator(Node *node,
 				CoalesceExpr *coalesceexpr = (CoalesceExpr *) node;
 				CoalesceExpr *newnode;
 
-				FLATCOPY(newnode, coalesceexpr, CoalesceExpr);
+				FLATCOPY(newnode, coalesceexpr, CoalesceExpr, flags);
 				MUTATE(newnode->args, coalesceexpr->args, List *);
 				return (Node *) newnode;
 			}
@@ -3028,7 +3038,7 @@ expression_tree_mutator(Node *node,
 				MinMaxExpr *minmaxexpr = (MinMaxExpr *) node;
 				MinMaxExpr *newnode;
 
-				FLATCOPY(newnode, minmaxexpr, MinMaxExpr);
+				FLATCOPY(newnode, minmaxexpr, MinMaxExpr, flags);
 				MUTATE(newnode->args, minmaxexpr->args, List *);
 				return (Node *) newnode;
 			}
@@ -3038,7 +3048,7 @@ expression_tree_mutator(Node *node,
 				XmlExpr    *xexpr = (XmlExpr *) node;
 				XmlExpr    *newnode;
 
-				FLATCOPY(newnode, xexpr, XmlExpr);
+				FLATCOPY(newnode, xexpr, XmlExpr, flags);
 				MUTATE(newnode->named_args, xexpr->named_args, List *);
 				/* assume mutator does not care about arg_names */
 				MUTATE(newnode->args, xexpr->args, List *);
@@ -3050,7 +3060,7 @@ expression_tree_mutator(Node *node,
 				NullTest   *ntest = (NullTest *) node;
 				NullTest   *newnode;
 
-				FLATCOPY(newnode, ntest, NullTest);
+				FLATCOPY(newnode, ntest, NullTest, flags);
 				MUTATE(newnode->arg, ntest->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -3060,7 +3070,7 @@ expression_tree_mutator(Node *node,
 				BooleanTest *btest = (BooleanTest *) node;
 				BooleanTest *newnode;
 
-				FLATCOPY(newnode, btest, BooleanTest);
+				FLATCOPY(newnode, btest, BooleanTest, flags);
 				MUTATE(newnode->arg, btest->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -3070,7 +3080,7 @@ expression_tree_mutator(Node *node,
 				CoerceToDomain *ctest = (CoerceToDomain *) node;
 				CoerceToDomain *newnode;
 
-				FLATCOPY(newnode, ctest, CoerceToDomain);
+				FLATCOPY(newnode, ctest, CoerceToDomain, flags);
 				MUTATE(newnode->arg, ctest->arg, Expr *);
 				return (Node *) newnode;
 			}
@@ -3080,7 +3090,7 @@ expression_tree_mutator(Node *node,
 				TargetEntry *targetentry = (TargetEntry *) node;
 				TargetEntry *newnode;
 
-				FLATCOPY(newnode, targetentry, TargetEntry);
+				FLATCOPY(newnode, targetentry, TargetEntry, flags & ~QTW_DONT_COPY_DEFAULT);
 				MUTATE(newnode->expr, targetentry->expr, Expr *);
 				return (Node *) newnode;
 			}
@@ -3093,7 +3103,7 @@ expression_tree_mutator(Node *node,
 				WindowClause *wc = (WindowClause *) node;
 				WindowClause *newnode;
 
-				FLATCOPY(newnode, wc, WindowClause);
+				FLATCOPY(newnode, wc, WindowClause, flags);
 				MUTATE(newnode->partitionClause, wc->partitionClause, List *);
 				MUTATE(newnode->orderClause, wc->orderClause, List *);
 				MUTATE(newnode->startOffset, wc->startOffset, Node *);
@@ -3107,7 +3117,7 @@ expression_tree_mutator(Node *node,
 				CTECycleClause *cc = (CTECycleClause *) node;
 				CTECycleClause *newnode;
 
-				FLATCOPY(newnode, cc, CTECycleClause);
+				FLATCOPY(newnode, cc, CTECycleClause, flags);
 				MUTATE(newnode->cycle_mark_value, cc->cycle_mark_value, Node *);
 				MUTATE(newnode->cycle_mark_default, cc->cycle_mark_default, Node *);
 				return (Node *) newnode;
@@ -3118,7 +3128,7 @@ expression_tree_mutator(Node *node,
 				CommonTableExpr *cte = (CommonTableExpr *) node;
 				CommonTableExpr *newnode;
 
-				FLATCOPY(newnode, cte, CommonTableExpr);
+				FLATCOPY(newnode, cte, CommonTableExpr, flags);
 
 				/*
 				 * Also invoke the mutator on the CTE's Query node, so it can
@@ -3137,7 +3147,7 @@ expression_tree_mutator(Node *node,
 				PartitionBoundSpec *pbs = (PartitionBoundSpec *) node;
 				PartitionBoundSpec *newnode;
 
-				FLATCOPY(newnode, pbs, PartitionBoundSpec);
+				FLATCOPY(newnode, pbs, PartitionBoundSpec, flags);
 				MUTATE(newnode->listdatums, pbs->listdatums, List *);
 				MUTATE(newnode->lowerdatums, pbs->lowerdatums, List *);
 				MUTATE(newnode->upperdatums, pbs->upperdatums, List *);
@@ -3149,7 +3159,7 @@ expression_tree_mutator(Node *node,
 				PartitionRangeDatum *prd = (PartitionRangeDatum *) node;
 				PartitionRangeDatum *newnode;
 
-				FLATCOPY(newnode, prd, PartitionRangeDatum);
+				FLATCOPY(newnode, prd, PartitionRangeDatum, flags);
 				MUTATE(newnode->value, prd->value, Node *);
 				return (Node *) newnode;
 			}
@@ -3179,7 +3189,7 @@ expression_tree_mutator(Node *node,
 				FromExpr   *from = (FromExpr *) node;
 				FromExpr   *newnode;
 
-				FLATCOPY(newnode, from, FromExpr);
+				FLATCOPY(newnode, from, FromExpr, flags);
 				MUTATE(newnode->fromlist, from->fromlist, List *);
 				MUTATE(newnode->quals, from->quals, Node *);
 				return (Node *) newnode;
@@ -3190,7 +3200,7 @@ expression_tree_mutator(Node *node,
 				OnConflictExpr *oc = (OnConflictExpr *) node;
 				OnConflictExpr *newnode;
 
-				FLATCOPY(newnode, oc, OnConflictExpr);
+				FLATCOPY(newnode, oc, OnConflictExpr, flags);
 				MUTATE(newnode->arbiterElems, oc->arbiterElems, List *);
 				MUTATE(newnode->arbiterWhere, oc->arbiterWhere, Node *);
 				MUTATE(newnode->onConflictSet, oc->onConflictSet, List *);
@@ -3205,7 +3215,7 @@ expression_tree_mutator(Node *node,
 				MergeAction *action = (MergeAction *) node;
 				MergeAction *newnode;
 
-				FLATCOPY(newnode, action, MergeAction);
+				FLATCOPY(newnode, action, MergeAction, flags);
 				MUTATE(newnode->qual, action->qual, Node *);
 				MUTATE(newnode->targetList, action->targetList, List *);
 
@@ -3217,7 +3227,7 @@ expression_tree_mutator(Node *node,
 				PartitionPruneStepOp *opstep = (PartitionPruneStepOp *) node;
 				PartitionPruneStepOp *newnode;
 
-				FLATCOPY(newnode, opstep, PartitionPruneStepOp);
+				FLATCOPY(newnode, opstep, PartitionPruneStepOp, flags);
 				MUTATE(newnode->exprs, opstep->exprs, List *);
 
 				return (Node *) newnode;
@@ -3231,7 +3241,7 @@ expression_tree_mutator(Node *node,
 				JoinExpr   *join = (JoinExpr *) node;
 				JoinExpr   *newnode;
 
-				FLATCOPY(newnode, join, JoinExpr);
+				FLATCOPY(newnode, join, JoinExpr, flags);
 				MUTATE(newnode->larg, join->larg, Node *);
 				MUTATE(newnode->rarg, join->rarg, Node *);
 				MUTATE(newnode->quals, join->quals, Node *);
@@ -3244,7 +3254,7 @@ expression_tree_mutator(Node *node,
 				SetOperationStmt *setop = (SetOperationStmt *) node;
 				SetOperationStmt *newnode;
 
-				FLATCOPY(newnode, setop, SetOperationStmt);
+				FLATCOPY(newnode, setop, SetOperationStmt, flags);
 				MUTATE(newnode->larg, setop->larg, Node *);
 				MUTATE(newnode->rarg, setop->rarg, Node *);
 				/* We do not mutate groupClauses by default */
@@ -3256,7 +3266,7 @@ expression_tree_mutator(Node *node,
 				IndexClause *iclause = (IndexClause *) node;
 				IndexClause *newnode;
 
-				FLATCOPY(newnode, iclause, IndexClause);
+				FLATCOPY(newnode, iclause, IndexClause, flags);
 				MUTATE(newnode->rinfo, iclause->rinfo, RestrictInfo *);
 				MUTATE(newnode->indexquals, iclause->indexquals, List *);
 				return (Node *) newnode;
@@ -3267,7 +3277,7 @@ expression_tree_mutator(Node *node,
 				PlaceHolderVar *phv = (PlaceHolderVar *) node;
 				PlaceHolderVar *newnode;
 
-				FLATCOPY(newnode, phv, PlaceHolderVar);
+				FLATCOPY(newnode, phv, PlaceHolderVar, flags);
 				MUTATE(newnode->phexpr, phv->phexpr, Expr *);
 				/* Assume we need not copy the relids bitmapset */
 				return (Node *) newnode;
@@ -3278,7 +3288,7 @@ expression_tree_mutator(Node *node,
 				InferenceElem *inferenceelemdexpr = (InferenceElem *) node;
 				InferenceElem *newnode;
 
-				FLATCOPY(newnode, inferenceelemdexpr, InferenceElem);
+				FLATCOPY(newnode, inferenceelemdexpr, InferenceElem, flags);
 				MUTATE(newnode->expr, newnode->expr, Node *);
 				return (Node *) newnode;
 			}
@@ -3288,7 +3298,7 @@ expression_tree_mutator(Node *node,
 				AppendRelInfo *appinfo = (AppendRelInfo *) node;
 				AppendRelInfo *newnode;
 
-				FLATCOPY(newnode, appinfo, AppendRelInfo);
+				FLATCOPY(newnode, appinfo, AppendRelInfo, flags);
 				MUTATE(newnode->translated_vars, appinfo->translated_vars, List *);
 				/* Assume nothing need be done with parent_colnos[] */
 				return (Node *) newnode;
@@ -3299,7 +3309,7 @@ expression_tree_mutator(Node *node,
 				PlaceHolderInfo *phinfo = (PlaceHolderInfo *) node;
 				PlaceHolderInfo *newnode;
 
-				FLATCOPY(newnode, phinfo, PlaceHolderInfo);
+				FLATCOPY(newnode, phinfo, PlaceHolderInfo, flags);
 				MUTATE(newnode->ph_var, phinfo->ph_var, PlaceHolderVar *);
 				/* Assume we need not copy the relids bitmapsets */
 				return (Node *) newnode;
@@ -3310,7 +3320,7 @@ expression_tree_mutator(Node *node,
 				RangeTblFunction *rtfunc = (RangeTblFunction *) node;
 				RangeTblFunction *newnode;
 
-				FLATCOPY(newnode, rtfunc, RangeTblFunction);
+				FLATCOPY(newnode, rtfunc, RangeTblFunction, flags);
 				MUTATE(newnode->funcexpr, rtfunc->funcexpr, Node *);
 				/* Assume we need not copy the coldef info lists */
 				return (Node *) newnode;
@@ -3321,7 +3331,7 @@ expression_tree_mutator(Node *node,
 				TableSampleClause *tsc = (TableSampleClause *) node;
 				TableSampleClause *newnode;
 
-				FLATCOPY(newnode, tsc, TableSampleClause);
+				FLATCOPY(newnode, tsc, TableSampleClause, flags);
 				MUTATE(newnode->args, tsc->args, List *);
 				MUTATE(newnode->repeatable, tsc->repeatable, Expr *);
 				return (Node *) newnode;
@@ -3332,7 +3342,7 @@ expression_tree_mutator(Node *node,
 				TableFunc  *tf = (TableFunc *) node;
 				TableFunc  *newnode;
 
-				FLATCOPY(newnode, tf, TableFunc);
+				FLATCOPY(newnode, tf, TableFunc, flags);
 				MUTATE(newnode->ns_uris, tf->ns_uris, List *);
 				MUTATE(newnode->docexpr, tf->docexpr, Node *);
 				MUTATE(newnode->rowexpr, tf->rowexpr, Node *);
@@ -3382,7 +3392,7 @@ query_tree_mutator(Query *query,
 	{
 		Query	   *newquery;
 
-		FLATCOPY(newquery, query, Query);
+		FLATCOPY(newquery, query, Query, flags);
 		query = newquery;
 	}
 
@@ -3425,7 +3435,7 @@ query_tree_mutator(Query *query,
 			WindowClause *wc = lfirst_node(WindowClause, temp);
 			WindowClause *newnode;
 
-			FLATCOPY(newnode, wc, WindowClause);
+			FLATCOPY(newnode, wc, WindowClause, flags);
 			MUTATE(newnode->startOffset, wc->startOffset, Node *);
 			MUTATE(newnode->endOffset, wc->endOffset, Node *);
 			MUTATE(newnode->runCondition, wc->runCondition, List *);
@@ -3475,7 +3485,7 @@ range_table_mutator(List *rtable,
 		RangeTblEntry *rte = (RangeTblEntry *) lfirst(rt);
 		RangeTblEntry *newrte;
 
-		FLATCOPY(newrte, rte, RangeTblEntry);
+		FLATCOPY(newrte, rte, RangeTblEntry, flags);
 		switch (rte->rtekind)
 		{
 			case RTE_RELATION:
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 3f8e58626cc..c5a1344255e 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1936,6 +1936,7 @@ _outAppendPath(StringInfo str, const AppendPath *node)
 
 	WRITE_NODE_FIELD(subpaths);
 	WRITE_INT_FIELD(first_partial_path);
+	WRITE_BOOL_FIELD(pull_tlist);
 	WRITE_FLOAT_FIELD(limit_tuples, "%.0f");
 }
 
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 1e199ff66f7..06dd07f3270 100644
--- a/src/backend/optimizer/path/Makefile
+++ b/src/backend/optimizer/path/Makefile
@@ -14,6 +14,7 @@ include $(top_builddir)/src/Makefile.global
 
 OBJS = \
 	allpaths.o \
+	appendorpath.o \
 	clausesel.o \
 	costsize.o \
 	equivclass.o \
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 4e02439ce36..70cc18898d7 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -804,6 +804,9 @@ set_plain_rel_pathlist(PlannerInfo *root, RelOptInfo *rel, RangeTblEntry *rte)
 	/* Consider index scans */
 	create_index_paths(root, rel);
 
+	/* Consider index scans with rewrited quals */
+	keybased_rewrite_index_paths(root, rel);
+
 	/* Consider TID scans */
 	create_tidscan_paths(root, rel);
 }
@@ -1475,7 +1478,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 	if (subpaths_valid)
 		add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
 												  NIL, NULL, 0, false,
-												  -1));
+												  -1, false));
 
 	/*
 	 * Consider an append of unordered, unparameterized partial paths.  Make
@@ -1518,7 +1521,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 		appendpath = create_append_path(root, rel, NIL, partial_subpaths,
 										NIL, NULL, parallel_workers,
 										enable_parallel_append,
-										-1);
+										-1, false);
 
 		/*
 		 * Make sure any subsequent partial paths use the same row count
@@ -1567,7 +1570,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 		appendpath = create_append_path(root, rel, pa_nonpartial_subpaths,
 										pa_partial_subpaths,
 										NIL, NULL, parallel_workers, true,
-										partial_rows);
+										partial_rows, false);
 		add_partial_path(rel, (Path *) appendpath);
 	}
 
@@ -1628,7 +1631,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 			add_path(rel, (Path *)
 					 create_append_path(root, rel, subpaths, NIL,
 										NIL, required_outer, 0, false,
-										-1));
+										-1, false));
 	}
 
 	/*
@@ -1655,7 +1658,7 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
 			appendpath = create_append_path(root, rel, NIL, list_make1(path),
 											NIL, NULL,
 											path->parallel_workers, true,
-											partial_rows);
+											partial_rows, false);
 			add_partial_path(rel, (Path *) appendpath);
 		}
 	}
@@ -1907,7 +1910,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 													  NULL,
 													  0,
 													  false,
-													  -1));
+													  -1, false));
 			if (startup_neq_total)
 				add_path(rel, (Path *) create_append_path(root,
 														  rel,
@@ -1917,7 +1920,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 														  NULL,
 														  0,
 														  false,
-														  -1));
+														  -1, false));
 
 			if (fractional_subpaths)
 				add_path(rel, (Path *) create_append_path(root,
@@ -1928,7 +1931,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 														  NULL,
 														  0,
 														  false,
-														  -1));
+														  -1, false));
 		}
 		else
 		{
@@ -2144,7 +2147,7 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
 	/* Set up the dummy path */
 	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
 											  NIL, rel->lateral_relids,
-											  0, false, -1));
+											  0, false, -1, false));
 
 	/*
 	 * We set the cheapest-path fields immediately, just in case they were
diff --git a/src/backend/optimizer/path/appendorpath.c b/src/backend/optimizer/path/appendorpath.c
new file mode 100644
index 00000000000..4a663dadbe0
--- /dev/null
+++ b/src/backend/optimizer/path/appendorpath.c
@@ -0,0 +1,1061 @@
+/*
+ * support Append plan for ORed clauses
+ * Teodor Sigaev <teodor@sigaev.ru>
+ */
+#include "postgres.h"
+
+#include "access/skey.h"
+#include "catalog/pg_am.h"
+#include "optimizer/cost.h"
+#include "optimizer/clauses.h"
+#include "optimizer/optimizer.h"
+#include "optimizer/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
+#include "utils/lsyscache.h"
+
+typedef struct CKey {
+	RestrictInfo	*rinfo;		/* original rinfo */
+	int				n;			/* IndexPath's number in bitmapquals */
+	OpExpr			*normalizedexpr; /* expression with Var on left */
+	Var				*var;
+	Node			*value;
+	Oid				opfamily;
+	int				strategy;
+	uint8			strategyMask;
+} CKey;
+#define	BTMASK(x)	( 1<<(x) )
+
+static	List* find_common_quals( BitmapOrPath *path );
+static	RestrictInfo* unionOperation(PlannerInfo *root, CKey	*key);
+static	BitmapOrPath* cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path );
+static  List* sortIndexScans( List* ipaths );
+static	List* reverseScanDirIdxPaths(List *indexPaths);
+static	IndexPath* reverseScanDirIdxPath(IndexPath *ipath);
+static bool checkSameIndex(Path *path, Oid *indexoid);
+
+#define IS_LESS(a)	( (a) == BTLessStrategyNumber || (a)== BTLessEqualStrategyNumber )
+#define IS_GREATER(a)	( (a) == BTGreaterStrategyNumber || (a) == BTGreaterEqualStrategyNumber )
+#define	IS_ONE_DIRECTION(a,b)	(		\
+	( IS_LESS(a) && IS_LESS(b) )		\
+	||									\
+	( IS_GREATER(a) && IS_GREATER(b) )	\
+)
+
+typedef struct ExExpr {
+	OpExpr		*expr;
+	Oid			opfamily;
+	Oid			lefttype;
+	Oid			righttype;
+	int			strategy;
+	int			attno;
+} ExExpr;
+
+
+typedef struct IndexPathEx {
+	IndexPath	*path;
+	List		*preparedquals; /* list of ExExpr */
+} IndexPathEx;
+
+static List*
+clauses_get_exprs(List *listIndexClause) {
+	ListCell    *i, *c;
+	List        *exprs=NULL;
+
+	foreach(i, listIndexClause)
+	{
+		IndexClause *ic = lfirst(i);
+
+		foreach(c, ic->indexquals)
+		{
+			RestrictInfo *rinfo = lfirst(c);
+			OpExpr  *expr = (OpExpr*)rinfo->clause;
+
+			exprs = lappend(exprs, expr);
+		}
+   }
+
+   return exprs;
+}
+
+
+/*----------
+ * keybased_rewrite_or_index_quals
+ *	  Examine join OR-of-AND quals to see if any useful common restriction
+ *	  clauses can be extracted.  If so, try to use for creating new index paths.
+ *
+ * For example consider
+ *		WHERE ( a.x=5 and a.y>10 ) OR a.x>5
+ *	and there is an index on a.x or (a.x, a.y). So, plan
+ *	will be seqscan or BitmapOr(IndexPath,IndexPath)
+ *  So, we can add some restriction:
+ *		WHERE (( a.x=5 and a.y>10 ) OR a.x>5) AND a.x>=5
+ *	and plan may be so
+ *		Index Scan (a.x>=5)
+ *		Filter( (( a.x=5 and a.y>10 ) OR a.x>5) )
+ *
+ * We don't want to add new clauses to baserestrictinfo, just
+ * use it as index quals.
+ *
+ * Next thing which it possible to test is use append of
+ * searches instead of OR.
+ * For example consider
+ *	WHERE ( a.x=5 and a.y>10 ) OR a.x>6
+ * and there is an index on (a.x) (a.x, a.y)
+ * So, we can suggest follow plan:
+ *	Append
+ *	Filter ( a.x=5 and a.y>10 ) OR (a.x>6)
+ *		Index Scan (a.x=5)	--in case of index on (a.x)
+ *		Index Scan (a.x>6)
+ * For that we should proof that index quals isn't overlapped,
+ * also, some index quals may be containedi in other, so it can be eliminated
+ */
+
+void
+keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel)
+{
+	BitmapOrPath *bestpath = NULL;
+	ListCell   *i;
+	List		*commonquals;
+	AppendPath  *appendidxpath;
+	List		*indexPaths;
+	IndexOptInfo *index;
+
+	foreach(i, rel->baserestrictinfo)
+	{
+		RestrictInfo *rinfo = (RestrictInfo *) lfirst(i);
+
+		if (restriction_is_or_clause(rinfo) &&
+			!rinfo->outerjoin_delayed)
+		{
+			/*
+			 * Use the generate_bitmap_or_paths() machinery to estimate the
+			 * value of each OR clause.  We can use regular restriction
+			 * clauses along with the OR clause contents to generate
+			 * indexquals.	We pass outer_rel = NULL so that sub-clauses
+			 * that are actually joins will be ignored.
+			 */
+			List	   *orpaths;
+			ListCell   *k;
+
+			orpaths = generate_bitmap_or_paths(root, rel,
+											   list_make1(rinfo),
+											   rel->baserestrictinfo);
+
+			/* Locate the cheapest OR path */
+			foreach(k, orpaths)
+			{
+				BitmapOrPath *path = (BitmapOrPath *) lfirst(k);
+				Oid				indexoid = InvalidOid;
+
+				Assert(IsA(path, BitmapOrPath));
+
+				if (checkSameIndex((Path*)path, &indexoid) == false)
+					continue;
+
+				if (bestpath == NULL ||
+					path->path.total_cost < bestpath->path.total_cost)
+				{
+					bestpath = path;
+				}
+			}
+		}
+	}
+
+	/* Fail if no suitable clauses found */
+	if (bestpath == NULL)
+		return;
+
+	commonquals = find_common_quals(bestpath);
+	/* Found quals with the same args, but with, may be, different
+		operations */
+	if ( commonquals != NULL ) {
+		List		*addon=NIL;
+
+		foreach(i, commonquals) {
+			CKey	*key = (CKey*)lfirst(i);
+			RestrictInfo	*rinfo;
+
+			/*
+			 * get 'union' of operation for key
+			 */
+			rinfo = unionOperation(root, key);
+			if ( rinfo )
+				addon = lappend(addon, rinfo);
+		}
+
+		/*
+		 * Ok, we found common quals and union it, so we will try to
+		 * create new possible index paths
+		 */
+		if ( addon ) {
+			List	*origbaserestrictinfo = list_copy(rel->baserestrictinfo);
+
+			rel->baserestrictinfo = list_concat(rel->baserestrictinfo, addon);
+
+			create_index_paths(root, rel);
+
+			rel->baserestrictinfo = origbaserestrictinfo;
+		}
+	}
+
+	/*
+	 * Check if indexquals isn't overlapped and all index scan
+	 * are on the same index.
+	 */
+	if ( (bestpath = cleanup_nested_quals( root, rel, bestpath )) == NULL )
+		return;
+
+	if (IsA(bestpath, IndexPath)) {
+		IndexPath	*ipath = (IndexPath*)bestpath;
+
+		/*
+		 * It's possible to do only one index scan :)
+		 */
+		index = ipath->indexinfo;
+
+		if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) )
+		{
+			List	*pathkeys;
+
+			pathkeys = build_index_pathkeys(root, index,
+													ForwardScanDirection);
+			pathkeys = truncate_useless_pathkeys(root, rel,
+													pathkeys);
+
+			ipath->path.pathkeys = pathkeys;
+			add_path(rel, (Path *) ipath);
+
+			/*
+			 * add path ordered in backward direction if our pathkeys
+			 * is still unusable...
+			 */
+			if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) 
+			{
+				pathkeys = build_index_pathkeys(root, index,
+													BackwardScanDirection);
+				pathkeys = truncate_useless_pathkeys(root, rel,
+														pathkeys);
+
+				ipath = reverseScanDirIdxPath( ipath );
+
+				ipath->path.pathkeys = pathkeys;
+				add_path(rel, (Path *) ipath);
+			}
+		} else
+			add_path(rel, (Path *) ipath);
+		return;
+	}
+
+	/* recount costs */
+	foreach(i, bestpath->bitmapquals ) {
+		IndexPath	*ipath = (IndexPath*)lfirst(i);
+
+		Assert( IsA(ipath, IndexPath) );
+		ipath->path.rows = rel->tuples * clauselist_selectivity(root,
+															clauses_get_exprs(ipath->indexclauses),
+															rel->relid,
+															JOIN_INNER,
+															NULL);
+		ipath->path.rows = clamp_row_est(ipath->path.rows);
+		cost_index(ipath, root, 1, false);
+	}
+
+	/*
+	 * Check if append index can suggest ordering of result
+	 *
+	 * Also, we should say to AppendPath about targetlist:
+	 * target list will be taked from indexscan
+	 */
+	index = ((IndexPath*)linitial(bestpath->bitmapquals))->indexinfo;
+	if ( root->query_pathkeys != NIL && index->sortopfamily && OidIsValid(index->sortopfamily[0]) && 
+				(indexPaths = sortIndexScans( bestpath->bitmapquals )) !=NULL ) {
+		List	*pathkeys;
+
+		pathkeys = build_index_pathkeys(root, index,
+										ForwardScanDirection);
+		pathkeys = truncate_useless_pathkeys(root, rel,
+											 pathkeys);
+
+		appendidxpath = create_append_path(root, rel, indexPaths, NIL, pathkeys, NULL, 0,
+										   false, -1.0, true);
+		add_path(rel, (Path *) appendidxpath);
+
+		/*
+		 * add path ordered in backward direction if our pathkeys
+		 * is still unusable...
+		 */
+		if ( pathkeys == NULL || pathkeys_useful_for_ordering(root, pathkeys) == 0 ) {
+
+			pathkeys = build_index_pathkeys(root, index,
+										BackwardScanDirection);
+			pathkeys = truncate_useless_pathkeys(root, rel,
+											 pathkeys);
+
+			indexPaths = reverseScanDirIdxPaths(indexPaths);
+			appendidxpath = create_append_path(root, rel, indexPaths, NIL,
+											   pathkeys, NULL,
+											   0, false, -1.0,
+											   true);
+			add_path(rel, (Path *) appendidxpath);
+		}
+	} else {
+		appendidxpath = create_append_path(root, rel, bestpath->bitmapquals,
+										   NIL, NIL, NULL,
+										   0, false, -1.0, true);
+		add_path(rel, (Path *) appendidxpath);
+	}
+}
+
+/*
+ * returns true if all indexscan below uses the same index
+ */
+static bool
+checkSameIndex(Path *path, Oid *indexoid) {
+	ListCell   *i;
+	List	   *subpaths;
+
+	if (IsA(path, IndexPath))
+	{
+		IndexPath	*indpath = (IndexPath*)path;
+
+		if (indpath->indexinfo->relam != BTREE_AM_OID)
+			return false;
+
+		if (*indexoid == InvalidOid)
+			*indexoid = indpath->indexinfo->indexoid;
+		else if (*indexoid != indpath->indexinfo->indexoid)
+			return false;
+
+		return true;
+	}
+	else if (IsA(path, BitmapOrPath))
+	{
+		BitmapOrPath *orpath = (BitmapOrPath*)path;
+
+		subpaths = orpath->bitmapquals;
+
+	}
+	else if (IsA(path, BitmapAndPath))
+	{
+		BitmapAndPath	*andpath = (BitmapAndPath*)path;
+
+		subpaths = andpath->bitmapquals;
+	}
+	else
+	{
+		elog(ERROR, "unexpected path type: %d", nodeTag(path));
+	}
+
+	Assert(list_length(subpaths) > 0);
+
+	foreach(i, subpaths)
+	{
+		Path *subpath = (Path *) lfirst(i);
+
+		if (checkSameIndex(subpath, indexoid) == false)
+			return false;
+	}
+
+	return true;
+}
+
+/*
+ * transformToCkey - transform RestrictionInfo
+ * to CKey struct. Fucntion checks possibility and correctness of
+ * RestrictionInfo to use it as common key, normalizes 
+ * expression and "caches" some information. Note,
+ * original RestrictInfo isn't touched
+ */
+
+static CKey*
+transformToCkey( IndexOptInfo *index, RestrictInfo* rinfo, int indexcol) {
+	CKey	*key;
+	OpExpr  *expr = (OpExpr*)rinfo->clause;
+
+	if ( rinfo->outerjoin_delayed )
+		return NULL;
+
+	if ( !IsA(expr, OpExpr) )
+		return NULL;
+
+	if ( contain_mutable_functions((Node*)expr) )
+		return NULL;
+
+	if ( list_length( expr->args ) != 2 )
+		return NULL;
+
+	key = (CKey*)palloc(sizeof(CKey));
+	key->rinfo = rinfo;
+
+	key->normalizedexpr = (OpExpr*)copyObject( expr ); 
+	if (!bms_equal(rinfo->left_relids, index->rel->relids))
+		CommuteOpExpr(key->normalizedexpr);
+
+	/*
+	 * fix_indexqual_operand returns copy of object
+	 */
+	key->var = (Var*)fix_indexqual_operand(linitial(key->normalizedexpr->args), index, indexcol);
+	Assert( IsA(key->var, Var) );
+
+	key->opfamily = index->opfamily[ key->var->varattno - 1 ];
+
+	/* restore varattno, because it may be different in different index */
+	key->var->varattno = key->var->varattnosyn;
+
+	key->value = (Node*)lsecond(key->normalizedexpr->args);
+
+	key->strategy = get_op_opfamily_strategy( key->normalizedexpr->opno, key->opfamily);
+	Assert( key->strategy != InvalidStrategy );
+
+	key->strategyMask = BTMASK(key->strategy);
+
+	return key;
+}
+
+/*
+ * get_index_quals - get list of quals in
+ * CKeys form
+ */
+
+static List*
+get_index_quals(IndexPath *path, int cnt) {
+	ListCell	*i, *c;
+	List	*quals = NIL;
+
+	foreach(i, path->indexclauses) {
+		IndexClause *ic = lfirst(i);
+
+		foreach(c, ic->indexquals) {
+			CKey	*k = transformToCkey( path->indexinfo,
+										  (RestrictInfo*)lfirst(c),
+										  ic->indexcol);
+			if ( k ) {
+				k->n = cnt;
+				quals = lappend(quals, k);
+			}
+		}
+	}
+	return quals;
+}
+
+/*
+ * extract all quals from bitmapquals->indexquals for
+ */
+static List*
+find_all_quals( BitmapOrPath *path, int *counter ) {
+	ListCell   *i,*j;
+	List	*allquals = NIL;
+
+	*counter = 0;
+
+	foreach(i, path->bitmapquals )
+	{
+		Path *subpath = (Path *) lfirst(i);
+
+		if ( IsA(subpath, BitmapAndPath) ) {
+			foreach(j, ((BitmapAndPath*)subpath)->bitmapquals) {
+				Path *subsubpath = (Path *) lfirst(j);
+
+				if ( IsA(subsubpath, IndexPath) ) {
+					if ( ((IndexPath*)subsubpath)->indexinfo->relam != BTREE_AM_OID )
+						return NIL;
+					allquals = list_concat(allquals, get_index_quals( (IndexPath*)subsubpath, *counter ));
+				} else
+					return NIL;
+			}
+		} else if ( IsA(subpath, IndexPath) ) {
+			if ( ((IndexPath*)subpath)->indexinfo->relam != BTREE_AM_OID )
+				return NIL;
+			allquals = list_concat(allquals, get_index_quals( (IndexPath*)subpath, *counter ));
+		} else
+			return NIL;
+
+		(*counter)++;
+	}
+
+	return allquals;
+}
+
+/*
+ * Compares aruments of operation
+ */
+static bool
+iseqCKeyArgs( CKey	*a, CKey *b ) {
+	if ( a->opfamily != b->opfamily )
+		return false;
+
+	if ( !equal( a->value, b->value ) )
+		return false;
+
+	if ( !equal( a->var, b->var ) )
+		return false;
+
+	return true;
+}
+
+/*
+ * Count entries of CKey with the same arguments
+ */
+static int
+count_entry( List *allquals, CKey *tocmp ) {
+	ListCell	*i;
+	int			curcnt=0;
+
+	foreach(i, allquals) {
+		CKey   *key = lfirst(i);
+
+		if ( key->n == curcnt ) {
+			continue;
+		} else if ( key->n == curcnt+1 ) {
+			if ( iseqCKeyArgs( key, tocmp ) ) {
+				tocmp->strategyMask |= key->strategyMask;
+				curcnt++;
+			}
+		} else
+			return -1;
+	}
+
+	return curcnt+1;
+}
+
+/*
+ * Finds all CKey with the same arguments
+ */
+static List*
+find_common_quals( BitmapOrPath *path ) {
+	List *allquals;
+	List *commonquals = NIL;
+	ListCell	*i;
+	int counter;
+
+	if ( (allquals = find_all_quals( path, &counter ))==NIL )
+		return NIL;
+
+	foreach(i, allquals) {
+		CKey	*key = lfirst(i);
+
+		if ( key->n != 0 )
+			break;
+
+		if ( counter == count_entry(allquals, key) )
+			commonquals = lappend( commonquals, key );
+	}
+
+	return commonquals;
+}
+
+/*
+ * unionOperation - make RestrictInfo with combined operation
+ */
+
+static RestrictInfo*
+unionOperation(PlannerInfo *root, CKey	*key) {
+	RestrictInfo	*rinfo;
+	Oid		lefttype, righttype;
+	int		strategy;
+
+	switch( key->strategyMask ) {
+		case	BTMASK(BTLessStrategyNumber):
+		case	BTMASK(BTLessEqualStrategyNumber):
+		case	BTMASK(BTEqualStrategyNumber):
+		case	BTMASK(BTGreaterEqualStrategyNumber):
+		case	BTMASK(BTGreaterStrategyNumber):
+				/* trivial case */
+				break;
+		case	BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber):
+		case	BTMASK(BTLessStrategyNumber) | BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber):
+		case	BTMASK(BTLessStrategyNumber) | BTMASK(BTEqualStrategyNumber):
+		case	BTMASK(BTLessEqualStrategyNumber) | BTMASK(BTEqualStrategyNumber):
+				/* any subset of <, <=, = can be unioned with <= */
+				key->strategy = BTLessEqualStrategyNumber;
+				break;
+		case	BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber):
+		case	BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber):
+		case	BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterStrategyNumber):
+		case	BTMASK(BTEqualStrategyNumber) | BTMASK(BTGreaterEqualStrategyNumber):
+				/* any subset of >, >=, = can be unioned with >= */
+				key->strategy = BTGreaterEqualStrategyNumber;
+				break;
+		default:
+			/*
+			 * Can't make common restrict qual
+			 */
+			return NULL;
+	}
+
+	get_op_opfamily_properties(key->normalizedexpr->opno, key->opfamily, false,
+							  &strategy, &lefttype, &righttype);
+
+	if ( strategy != key->strategy ) {
+		/*
+		 * We should check because it's possible to have "strange"
+		 * opfamilies - without some strategies...
+		 */
+		key->normalizedexpr->opno = get_opfamily_member(key->opfamily, lefttype, righttype, key->strategy);
+
+		if ( key->normalizedexpr->opno == InvalidOid )
+			return NULL;
+
+		key->normalizedexpr->opfuncid = get_opcode( key->normalizedexpr->opno );
+		Assert ( key->normalizedexpr->opfuncid != InvalidOid );
+	}
+
+	rinfo =	make_simple_restrictinfo(root, (Expr*)key->normalizedexpr);
+
+	return rinfo;
+}
+
+/*
+ * Remove unneeded RestrioctionInfo nodes as it
+ * needed by predicate_*_by()
+ */
+static void
+make_predicate(List *indexclauses, List **preds) {
+	ListCell	*i, *c;
+
+	*preds = NIL;
+
+	foreach(i, indexclauses)
+	{
+		IndexClause  *ic = lfirst(i);
+		RestrictInfo *rinfo = ic->rinfo;
+
+		if ( rinfo->outerjoin_delayed )
+			continue;
+
+		foreach(c, ic->indexquals)
+		{
+			RestrictInfo *rinfoq = lfirst(c);
+			OpExpr	*expr = (OpExpr*)rinfoq->clause;
+
+			if ( !IsA(expr, OpExpr) )
+				goto end;
+
+			if ( list_length( expr->args ) != 2 )
+				 goto end;
+		}
+
+		*preds = lappend(*preds, ic);
+
+end:
+		continue;
+	}
+}
+
+#define CELL_GET_CLAUSES(x)	( ((IndexPath*)lfirst(x))->indexclauses )
+
+/*
+ * returns list of all nested quals
+ */
+static List*
+contained_quals(List *nested, List* quals, ListCell *check) {
+	ListCell	*i;
+	List		*checkpred;
+
+	if ( list_member_ptr( nested, lfirst(check) ) )
+		return nested;
+
+	checkpred = clauses_get_exprs(CELL_GET_CLAUSES(check));
+
+	if ( contain_mutable_functions((Node*)checkpred) )
+		return nested;
+
+	foreach(i, quals )
+	{
+		if ( check == i )
+			continue;
+
+		if ( list_member_ptr( nested, lfirst(i) ) )
+			continue;
+
+		if (predicate_implied_by( checkpred,
+								  clauses_get_exprs(CELL_GET_CLAUSES(i)),
+								  false ) )
+			nested = lappend( nested, lfirst(i) );
+	}
+	return nested;
+}
+
+/*
+ * Checks that one row can be in several quals.
+ * It's guaranteed by predicate_refuted_by()
+ */
+static bool
+is_intersect(List *quals, ListCell *check) {
+	ListCell	*i;
+	List		*checkpred=NULL;
+
+	checkpred=clauses_get_exprs(CELL_GET_CLAUSES(check));
+	Assert( checkpred != NULL );
+
+	for_each_cell(i, quals, check) {
+		if ( i==check )
+			continue;
+
+		if ( predicate_refuted_by( checkpred,
+								   clauses_get_exprs(CELL_GET_CLAUSES(i)),
+								   false ) == false )
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Removes nested quals and gurantees that quals are not intersected,
+ * ie one row can't satisfy to several quals. It's open a possibility of
+ * Append node using instead of BitmapOr
+ */
+static	BitmapOrPath*
+cleanup_nested_quals( PlannerInfo *root, RelOptInfo *rel, BitmapOrPath *path ) {
+	ListCell   *i;
+	IndexOptInfo	*index=NULL;
+	List		*nested = NULL;
+
+	/*
+	 * check all path to use only one index
+	 */
+	foreach(i, path->bitmapquals )
+	{
+
+		if ( IsA(lfirst(i), IndexPath) ) {
+			List *preds;
+			IndexPath *subpath = (IndexPath *) lfirst(i);
+
+			if ( subpath->indexinfo->relam != BTREE_AM_OID )
+				return NULL;
+
+			if ( index == NULL )
+				index = subpath->indexinfo;
+			else if ( index->indexoid != subpath->indexinfo->indexoid )
+				return NULL;
+
+			/*
+			 * work only with optimizable quals
+			 */
+			make_predicate(subpath->indexclauses, &preds);
+			if (preds == NIL)
+				return NULL;
+			subpath->indexclauses = preds;
+		} else
+			return NULL;
+	}
+
+	/*
+	 * eliminate nested quals
+	 */
+	foreach(i, path->bitmapquals ) {
+		nested = contained_quals(nested, path->bitmapquals, i);
+	}
+
+	if ( nested != NIL ) {
+		path->bitmapquals = list_difference_ptr( path->bitmapquals, nested );
+
+		Assert( list_length( path->bitmapquals )>0 );
+
+		/*
+		 * All quals becomes only one after eliminating nested quals
+		 */
+		if (list_length( path->bitmapquals ) == 1)
+			return (BitmapOrPath*)linitial(path->bitmapquals);
+	}
+
+	/*
+	 * Checks for intersection
+	 */
+	foreach(i, path->bitmapquals ) {
+		if ( is_intersect( path->bitmapquals,  i ) )
+			return NULL;
+	}
+
+	return path;
+}
+
+/*
+ * Checks if whole result of one simple operation is contained
+ * in another
+ */
+static int
+simpleCmpExpr( ExExpr *a, ExExpr *b ) {
+	if ( predicate_implied_by((List*)a->expr, (List*)b->expr, false) )
+		/*
+		 * a:( Var < 15 ) > b:( Var <= 10 )
+		 */
+		return 1;
+	else if ( predicate_implied_by((List*)b->expr, (List*)a->expr, false) )
+		/*
+		 * a:( Var <= 10 ) < b:( Var < 15 )
+		 */
+		return -1;
+	else
+		return 0;
+}
+
+/*
+ * Trys to define where is equation - on left or right side
+ *		a(< 10)	 b(=11)	- on right
+ *		a(> 10)  b(=9)	- on left
+ *		a(= 10)	 b(=11)	- on right
+ *		a(= 10)  b(=9)	- on left
+ * Any other - result is 0;
+ */
+static int
+cmpEqExpr( ExExpr *a, ExExpr *b ) {
+	Oid oldop = b->expr->opno;
+	int res=0;
+
+	b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTLessStrategyNumber);
+	if ( b->expr->opno != InvalidOid ) {
+		 b->expr->opfuncid = get_opcode( b->expr->opno );
+		res = simpleCmpExpr(a,b);
+	}
+
+	if ( res == 0 ) {
+		b->expr->opno = get_opfamily_member(b->opfamily, b->lefttype, b->righttype, BTGreaterStrategyNumber);
+		if ( b->expr->opno != InvalidOid ) {
+			b->expr->opfuncid = get_opcode( b->expr->opno );
+			res = -simpleCmpExpr(a,b);
+		}
+	}
+
+	b->expr->opno = oldop;
+	b->expr->opfuncid = get_opcode( b->expr->opno );
+
+	return res;
+}
+
+/*
+ * Is result of a contained in result of b or on the contrary?
+ */
+static int
+cmpNegCmp( ExExpr *a, ExExpr *b ) {
+	Oid oldop = b->expr->opno;
+	int	res = 0;
+
+	b->expr->opno = get_negator( b->expr->opno );
+	if ( b->expr->opno != InvalidOid ) {
+		b->expr->opfuncid = get_opcode( b->expr->opno );
+		res = simpleCmpExpr(a,b);
+	}
+
+	b->expr->opno = oldop;
+	b->expr->opfuncid = get_opcode( b->expr->opno );
+
+	return ( IS_LESS(a->strategy) ) ? res : -res;
+}
+
+/*
+ * Returns 1 if whole result of a is on left comparing with result of b
+ * Returns -1 if whole result of a is on right comparing with result of b
+ * Return 0 if it's impossible to define or results is overlapped
+ * Expressions should use the same attribute of index and should be
+ * a simple: just one operation with index.
+ */
+static int
+cmpExpr( ExExpr *a, ExExpr *b ) {
+	int res;
+
+	/*
+	 * If a and b are overlapped, we can't decide which one is
+	 * lefter or righter
+	 */
+	if ( IS_ONE_DIRECTION(a->strategy, b->strategy) ||
+		 predicate_refuted_by((List*)a->expr, (List*)b->expr, false) == false )
+		return 0;
+
+	/*
+	 * In this place it's impossible to have a row which satisfies
+	 * a and b expressions, so we will try to find relatiove position of that results
+	 */
+	if (a->strategy == BTEqualStrategyNumber &&
+		b->strategy == BTEqualStrategyNumber) {
+		return cmpEqExpr(a, b);
+	} else if ( b->strategy == BTEqualStrategyNumber ) {
+		return -cmpEqExpr(a, b); /* Covers cases with any operations in a */
+	} else if ( a->strategy == BTEqualStrategyNumber ) {
+		return cmpEqExpr(b, a);
+	} else if ( (res = cmpNegCmp(a, b)) == 0 ) { /* so, a(<10) b(>20) */
+		res = -cmpNegCmp(b, a);
+	}
+
+	return res;
+}
+
+static IndexOptInfo	*sortingIndex = NULL;
+static bool volatile	unableToDefine = false;
+
+/*
+ * Try to define positions of result which satisfy indexquals a and b per
+ * one index's attribute.
+ */
+static int
+cmpColumnQuals( List *a, List *b, int attno ) {
+	int res = 0;
+	ListCell *ai, *bi;
+
+	foreach(ai, a) {
+		ExExpr	*ae = (ExExpr*)lfirst(ai);
+
+		if ( attno != ae->attno )
+			continue;
+
+		foreach(bi, b) {
+			ExExpr	*be = (ExExpr*)lfirst(bi);
+
+			if ( attno != be->attno )
+				continue;
+
+			if ((res=cmpExpr(ae, be))!=0)
+				return res;
+
+			if (res == 0 && ae->strategy == be->strategy &&
+				be->strategy != BTEqualStrategyNumber &&
+				equal(ae->expr, be->expr))
+			{
+				/*
+				 * It's impossible to get defined order for non-eq the same clauses
+				 */
+				unableToDefine = true;
+				PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */
+			}
+		}
+	}
+
+	return 0;
+}
+
+/*
+ * Compare result of two indexquals.
+ * Warinig: it use PG_RE_THROW(), so any call should be wrapped with
+ * PG_TRY().  Try/catch construction is used here for minimize unneeded
+ * actions when sorting is impossible
+ */
+static int
+cmpIndexPathEx(const void *a, const void *b) {
+	IndexPathEx	*aipe = (IndexPathEx*)a;
+	IndexPathEx	*bipe = (IndexPathEx*)b;
+	int attno, res = 0;
+
+	for(attno=1; res==0 && attno<=sortingIndex->ncolumns; attno++)
+		res=cmpColumnQuals(aipe->preparedquals, bipe->preparedquals, attno);
+
+	if ( res==0 ) {
+		unableToDefine = true;
+		PG_RE_THROW(); /* it should be PG_THROW(), but it's the same */
+	}
+
+	return res;
+}
+
+/*
+ * Initialize lists of operation in useful form
+ */
+static List*
+prepareQuals(IndexOptInfo *index, List *indexclauses) {
+	ListCell	*i, *c;
+	List		*res=NULL;
+	ExExpr		*ex;
+
+	foreach(i, indexclauses)
+	{
+		IndexClause *ic = lfirst(i);
+		RestrictInfo *rinfo = lfirst(i);
+
+		if ( rinfo->outerjoin_delayed )
+			return NULL;
+
+		foreach(c, ic->indexquals)
+		{
+			RestrictInfo *rinfo = lfirst(c);
+			OpExpr  *expr = (OpExpr*)rinfo->clause;
+
+			if ( !IsA(expr, OpExpr) )
+				return NULL;
+
+			if ( list_length( expr->args ) != 2 )
+				return NULL;
+
+			if ( contain_mutable_functions((Node*)expr) )
+				return NULL;
+
+			ex = (ExExpr*)palloc(sizeof(ExExpr));
+			ex->expr = (OpExpr*)copyObject( expr );
+			if (!bms_equal(rinfo->left_relids, index->rel->relids))
+				CommuteOpExpr(ex->expr);
+			linitial(ex->expr->args) = fix_indexqual_operand(linitial(ex->expr->args), index, ic->indexcol);
+			ex->attno = ((Var*)linitial(ex->expr->args))->varattno;
+			ex->opfamily = index->opfamily[ ex->attno - 1 ];
+			get_op_opfamily_properties( ex->expr->opno, ex->opfamily, false,
+				&ex->strategy, &ex->lefttype, &ex->righttype);
+
+			res = lappend(res, ex);
+		}
+	}
+
+	return res;
+}
+
+/*
+ * sortIndexScans - sorts index scans to get sorted results.
+ * Function supposed that index is the same for all
+ * index scans
+ */
+static List*
+sortIndexScans( List* ipaths ) {
+	ListCell	*i;
+	int			j=0;
+	IndexPathEx	*ipe = (IndexPathEx*)palloc( sizeof(IndexPathEx)*list_length(ipaths) );
+	List		*orderedPaths = NIL;
+	IndexOptInfo *index = ((IndexPath*)linitial(ipaths))->indexinfo;
+
+	foreach(i, ipaths) {
+		ipe[j].path = (IndexPath*)lfirst(i);
+		ipe[j].preparedquals = prepareQuals(index, ipe[j].path->indexclauses);
+
+		if (ipe[j].preparedquals == NULL)
+			return NULL;
+		j++;
+	}
+
+	sortingIndex = index;
+	unableToDefine = false;
+	PG_TRY(); {
+		qsort(ipe, list_length(ipaths), sizeof(IndexPathEx), cmpIndexPathEx);
+	} PG_CATCH(); {
+		if ( unableToDefine == false )
+			PG_RE_THROW(); /* not our problem */
+	} PG_END_TRY();
+
+	if ( unableToDefine == true )
+		return NULL;
+
+	for(j=0;j<list_length(ipaths);j++)
+		orderedPaths = lappend(orderedPaths, ipe[j].path);
+
+	return  orderedPaths;
+}
+
+static  IndexPath*
+reverseScanDirIdxPath(IndexPath *ipath) {
+	IndexPath   *n = makeNode(IndexPath);
+
+	*n = *ipath;
+
+	n->indexscandir = BackwardScanDirection;
+
+	return n;
+}
+
+static List*
+reverseScanDirIdxPaths(List *indexPaths) {
+	List		*idxpath = NIL;
+	ListCell	*i;
+
+	foreach(i, indexPaths) {
+		idxpath = lcons(reverseScanDirIdxPath( (IndexPath*)lfirst(i) ), idxpath);
+	}
+
+	return idxpath;
+}
diff --git a/src/backend/optimizer/path/clausesel.c b/src/backend/optimizer/path/clausesel.c
index 06f836308d0..b01cba323d9 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -12,8 +12,15 @@
  *
  *-------------------------------------------------------------------------
  */
+#include <math.h>
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_collation.h"
+#include "common/pg_prng.h"
+#include "commands/vacuum.h"
+#include "funcapi.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -26,6 +33,16 @@
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
 
+#include "parser/parsetree.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
+#include "utils/rel.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
+
+#define EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/4)
+#define RANGE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/20)
+
 /*
  * Data structure for accumulating info about possible range-query
  * clause pairs in clauselist_selectivity.
@@ -51,6 +68,1043 @@ static Selectivity clauselist_selectivity_or(PlannerInfo *root,
 											 SpecialJoinInfo *sjinfo,
 											 bool use_extended_stats);
 
+static bool treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo,
+					 int varRelid, SpecialJoinInfo *sjinfo);
+
+typedef enum CorrelationKind {
+	CKRestrict = 0,
+	CKIndepend,		/* unknown correlation */
+	CKLikelySelf,	/* Seems, should be close to be correlated, like agg with
+					   self join */
+	CKSelf,			/* 100% correlation because of self join */
+	CKMul			/* product of all CKLikelySelf * CKSelf */
+} CorrelationKind;
+static CorrelationKind get_correlation_kind(PlannerInfo *root, int varRelid,
+											OpExpr* expr);
+
+/*
+ *  Get variabe node. Returns null if node is not a Var node.
+ */
+static inline Var*
+get_var(Node* node)
+{
+	if (IsA(node, RelabelType))
+		node = (Node *) ((RelabelType *) node)->arg;
+
+	return IsA(node, Var) ? (Var*)node : NULL;
+}
+
+/*
+ * Locate compound index which can be used for multicolumn clauses/join.
+ */
+static IndexOptInfo*
+locate_inner_multicolumn_index(PlannerInfo *root, Index varno, List* vars,
+						 int n_clauses,
+						 int **permutation, List **missed_vars, int* n_keys)
+{
+	ListCell		*ilist;
+	RelOptInfo		*rel = find_base_rel(root, varno);
+	IndexOptInfo	*index_opt = NULL;
+	List			*missed_vars_opt = NIL;
+	int				*permutation_opt = NULL;
+	int				n_index_cols_opt = 0;
+	bool			used[INDEX_MAX_KEYS];
+	int				posvars[INDEX_MAX_KEYS];
+
+	*n_keys = 0;
+	*missed_vars = NIL;
+
+	Assert(list_length(vars) >= 1);
+	Assert(list_length(vars) <= n_clauses);
+
+	foreach(ilist, rel->indexlist)
+	{
+		IndexOptInfo	*index = (IndexOptInfo *) lfirst(ilist);
+		ListCell		*vlist;
+		int				i, n_index_cols = 0;
+		List			*missed = NIL;
+		int				*perm = NULL;
+		int				last_idx = 0;
+
+		memset(used, 0, sizeof(used));
+		perm = palloc(n_clauses * sizeof(*perm));
+		for(i=0; i<n_clauses; i++)
+			perm[i] = -1;
+
+		i = 0;
+		foreach (vlist, vars)
+		{
+			Var* var = lfirst(vlist);
+			int pos;
+
+			for (pos = 0; pos < index->nkeycolumns; pos++)
+			{
+				if (index->indexkeys[pos] == var->varattno)
+				{
+					if (used[pos])
+						missed = lappend(missed, var);
+					else
+					{
+						used[pos] = true;
+						posvars[pos] = i;
+						perm[i] = pos;
+						n_index_cols++;
+						last_idx = Max(last_idx, pos);
+						break;
+					}
+				}
+			}
+
+			/* var isn't found in index columns */
+			if (pos == index->nkeycolumns && !list_member_ptr(missed, var))
+				missed = lappend(missed, var);
+
+			i += 1;
+		}
+
+		if (n_index_cols == 0)
+			continue;
+
+		/* check that found columns are first columns in index */
+		if (index->nkeycolumns != n_index_cols)
+		{
+			int old_n_index_cols = n_index_cols;
+
+			for (i = 0; i <= last_idx; i++)
+			{
+				if (n_index_cols != old_n_index_cols)
+				{
+					/*
+					 * We will use only first n_index_cols columns instead of
+					 * found old_n_index_cols, so, all other columns should be
+					 * added to missed list
+					 */
+					if (used[i])
+					{
+						Var *var = list_nth(vars, posvars[i]);
+
+						missed = lappend(missed, var);
+					}
+				}
+				else if (!used[i])
+				{
+					if (i==0)
+						/* there isn't useful prefix */
+						goto TryNextIndex;
+
+					/* we will use only first i columns, save as new n_index_cols */
+					n_index_cols = i;
+				}
+			}
+		}
+
+		/* found exact match vars - index, immediately return */
+		if (vlist == NULL && list_length(missed) == 0 && n_index_cols == index->nkeycolumns)
+		{
+			*permutation = perm;
+			*n_keys = n_index_cols;
+			return index;
+		}
+
+		/* save partially matched index */
+		if (index_opt == NULL ||
+			n_index_cols > n_index_cols_opt ||
+			(n_index_cols == n_index_cols_opt && index->nkeycolumns < index_opt->nkeycolumns))
+		{
+			index_opt = index;
+			missed_vars_opt = missed;
+			if (permutation_opt)
+				pfree(permutation_opt);
+			permutation_opt = perm;
+			perm = NULL;
+			n_index_cols_opt = n_index_cols;
+		}
+TryNextIndex:
+		if (perm)
+			pfree(perm);
+	}
+
+	if (index_opt)
+	{
+		*missed_vars = list_concat_unique(*missed_vars, missed_vars_opt);
+		*permutation = permutation_opt;
+		*n_keys = n_index_cols_opt;
+	}
+
+	return index_opt;
+}
+
+/*
+ * verify that used vars are leading columns
+ */
+static bool
+check_leading_vars_index(IndexOptInfo *index, int n_vars,
+						 bool used[INDEX_MAX_KEYS])
+{
+	int	i;
+
+	if (index->nkeycolumns == n_vars)
+		return true;
+
+	for(i=0; i<n_vars; i++)
+		if (used[i] == false)
+			return false;
+
+	return true;
+}
+
+
+/*
+ * Locate index which exactly match joins vars
+ */
+static IndexOptInfo*
+locate_outer_multicolumn_index(PlannerInfo *root, Index varno, List* vars,
+							   int *permutation)
+{
+	ListCell   *ilist;
+	RelOptInfo* rel = find_base_rel(root, varno);
+	int n_vars = list_length(vars);
+	bool	used[INDEX_MAX_KEYS];
+	IndexOptInfo *index_opt = NULL;
+
+	Assert(n_vars >= 1);
+
+	foreach(ilist, rel->indexlist)
+	{
+		IndexOptInfo	*index = (IndexOptInfo *) lfirst(ilist);
+		ListCell		*vlist;
+		int				i;
+
+		if (index->nkeycolumns < n_vars)
+			continue;
+
+		memset(used, 0, sizeof(used));
+
+		i = 0;
+		foreach (vlist, vars)
+		{
+			Var* var = lfirst(vlist);
+
+			if (permutation[i] < 0 ||
+				index->nkeycolumns <= permutation[i] ||
+				index->indexkeys[permutation[i]] != var->varattno)
+				break;
+
+			used[i] = true;
+			i += 1;
+		}
+
+		if (vlist == NULL && check_leading_vars_index(index, n_vars, used))
+		{
+			if (index->nkeycolumns == n_vars)
+				/* found exact match vars - index, immediately return */
+				return index;
+			else if (index_opt == NULL ||
+					 index_opt->nkeycolumns > index->nkeycolumns)
+				/* found better candidate - store it */
+				index_opt = index;
+		}
+	}
+
+	return index_opt;
+}
+
+typedef struct InArrayClause
+{
+	ArrayType*	array;
+	Datum*		elems;
+	bool*		nulls;
+	int			index;
+	int			n_elems;
+	int			curr_elem;
+} InArrayClause;
+
+typedef struct TupleIterator
+{
+	Datum	values	[INDEX_MAX_KEYS];
+	bool	isnull[INDEX_MAX_KEYS];
+	int		n_variants;
+	int		i_variant;
+	int		*permutation;
+	List	*in_clauses;
+	bool	isExhaustive;
+} TupleIterator;
+
+static void
+initTupleIterator(TupleIterator *it, List *consts, int *permutation,
+				  List *in_clauses)
+{
+	ListCell	*l;
+	int			i;
+	double		n_variants = 1;
+
+	it->n_variants = 1;
+	it->permutation = permutation;
+	it->in_clauses = in_clauses;
+	it->isExhaustive = false;
+	for(i = 0; i < INDEX_MAX_KEYS; i++)
+		it->isnull[i] = true;
+
+	i = 0;
+	foreach (l, consts)
+	{
+		Const* c = (Const*) lfirst(l);
+		int j = permutation[i++];
+
+		if (j<0)
+			continue;
+		it->values[j] = c->constvalue;
+		it->isnull[j] = c->constisnull;
+	}
+
+	foreach (l, in_clauses)
+	{
+		InArrayClause*	iac = (InArrayClause*) lfirst(l);
+		int16			elmlen;
+		bool			elmbyval;
+		char			elmalign;
+
+		get_typlenbyvalalign(iac->array->elemtype,
+							 &elmlen, &elmbyval, &elmalign);
+		deconstruct_array(iac->array, iac->array->elemtype,
+						  elmlen, elmbyval, elmalign,
+						  &iac->elems, &iac->nulls, &iac->n_elems);
+		iac->curr_elem = 0;
+		n_variants *= (double)iac->n_elems;
+	}
+
+	if (n_variants > EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD)
+	{
+		it->isExhaustive = true;
+		it->n_variants = EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD;
+	}
+	else
+		it->n_variants = n_variants;
+
+	it->i_variant = it->n_variants;
+}
+
+static void
+resetTupleIterator(TupleIterator *it)
+{
+	ListCell	*l;
+
+	it->i_variant = it->n_variants;
+
+	foreach (l, it->in_clauses)
+	{
+		InArrayClause*  iac = (InArrayClause*) lfirst(l);
+
+		iac->curr_elem = 0;
+	}
+}
+
+static bool
+getTupleIterator(TupleIterator *it)
+{
+	ListCell	*l;
+	int			carry = 1;
+
+	if (it->i_variant == 0)
+		return false;
+
+	it->i_variant--;
+
+	foreach (l, it->in_clauses)
+	{
+		InArrayClause* iac = (InArrayClause*) lfirst(l);
+		int j = it->permutation[iac->index];
+
+		if (j<0)
+			continue;
+
+		if (it->isExhaustive)
+		{
+			/* use random subset of IN list(s) */
+			iac->curr_elem = pg_prng_uint64(&pg_global_prng_state) % iac->n_elems;
+		}
+		else if ((iac->curr_elem += carry) >= iac->n_elems)
+		{
+			iac->curr_elem = 0;
+			carry = 1;
+		}
+		else
+			carry = 0;
+
+		it->values[j] = iac->elems[iac->curr_elem];
+		it->isnull[j] = iac->nulls[iac->curr_elem];
+	}
+
+	return true;
+}
+
+static double
+get_numdistinct(PlannerInfo *root, IndexOptInfo* index, int n_keys)
+{
+	double numdistinct = 1.0;
+	ListCell	*lc;
+	int			i = 0;
+
+	foreach(lc, index->indextlist)
+	{
+		TargetEntry *tle = lfirst(lc);
+		VariableStatData	vardata;
+		bool				isdefault;
+
+		examine_variable(root, (Node*)tle->expr, 0, &vardata);
+
+		numdistinct *= get_variable_numdistinct(&vardata, &isdefault);
+
+		ReleaseVariableStats(vardata);
+
+		if (++i >= n_keys)
+			break;
+	}
+
+	if (numdistinct > index->tuples)
+		numdistinct = index->tuples;
+
+	return numdistinct;
+}
+
+static Selectivity
+estimate_selectivity_by_index(PlannerInfo *root, IndexOptInfo* index,
+							  VariableStatData *vardata,
+							  List *consts, List** missed_vars, int *permutation,
+							  List *in_clauses, int n_keys,
+							  bool *usedEqSel)
+{
+	TupleIterator	it;
+	Selectivity		sum = 0.0;
+	TypeCacheEntry	*typentry;
+	Datum			constant;
+	int				nBins;
+	double			nDistinct = 0.0;
+
+	if (n_keys < index->nkeycolumns )
+	{
+		double	nd;
+		bool				isdefault;
+
+		nDistinct = get_numdistinct(root, index, n_keys);
+		nd = get_variable_numdistinct(vardata, &isdefault);
+
+		if (isdefault == false && nDistinct > nd)
+			nDistinct = sqrt(nDistinct * nd);
+	}
+
+	/*
+	 * Assume that two compound types are coherent, so we can use equality
+	 * function from one type to compare it with other type. Use >= and <= range
+	 * definition.
+	 */
+	typentry = lookup_type_cache(vardata->atttype,
+								 TYPECACHE_EQ_OPR | TYPECACHE_TUPDESC);
+	initTupleIterator(&it, consts, permutation, in_clauses);
+
+	/*
+	 * Try to  simplify calculations: if all variants matches to small amount of
+	 * bins histogram the we don't need to check tuples separately, it's enough
+	 * to checck min and max tuples and compute selecivity by range of bins
+	 */
+
+	if (n_keys != index->nkeycolumns &&
+		it.n_variants > RANGE_IN_SELECTIVITY_THRESHOLD)
+	{
+		Datum	constantMax = 0,
+				constantMin = 0;
+		FmgrInfo		opprocLT, opprocGT;
+
+		fmgr_info(F_RECORD_GT, &opprocGT);
+		fmgr_info(F_RECORD_LT, &opprocLT);
+
+		/*
+		 * Find min and max tuples
+		 */
+		while(getTupleIterator(&it))
+		{
+			/* we check cache invalidation message */
+			if (typentry->tupDesc == NULL)
+				typentry = lookup_type_cache(vardata->atttype,
+											 TYPECACHE_EQ_OPR | TYPECACHE_TUPDESC);
+			constant = HeapTupleGetDatum(heap_form_tuple(typentry->tupDesc,
+														 it.values, it.isnull));
+
+			if (constantMax == 0 ||
+				DatumGetBool(FunctionCall2Coll(&opprocGT,
+											   DEFAULT_COLLATION_OID,
+											   constant, constantMax)))
+			{
+				constantMax = constant;
+				if (constantMin != 0)
+					continue;
+			}
+			if (constantMin == 0 ||
+				DatumGetBool(FunctionCall2Coll(&opprocLT,
+											   DEFAULT_COLLATION_OID,
+											   constant, constantMin)))
+			{
+				constantMin = constant;
+			}
+		}
+
+		sum = prefix_record_histogram_selectivity(vardata,
+												  constantMin, constantMax,
+												  n_keys, nDistinct,
+												  &nBins);
+
+		if (sum > 0 && (nBins == it.n_variants || nBins <=2))
+			/*
+			 * conclude that all  tuples are in the same, rather small, range of
+			 * bins
+			 */
+			goto finish;
+
+		/*
+		 * let try tuples one by one
+		 */
+		sum = 0.0;
+		resetTupleIterator(&it);
+	}
+
+	while(getTupleIterator(&it))
+	{
+		Selectivity	s;
+
+		/* we check cache invalidation message */
+		if (typentry->tupDesc == NULL)
+			typentry = lookup_type_cache(vardata->atttype,
+										 TYPECACHE_EQ_OPR | TYPECACHE_TUPDESC);
+		constant = HeapTupleGetDatum(heap_form_tuple(typentry->tupDesc,
+													 it.values, it.isnull));
+
+		if (n_keys != index->nkeycolumns)
+		{
+			s = prefix_record_histogram_selectivity(vardata,
+													constant, constant,
+													n_keys,
+													nDistinct,
+													&nBins);
+
+			if (s < 0)
+			{
+				/*
+				 * There is no histogram, fallback to single available option
+				 */
+				s = eqconst_selectivity(typentry->eq_opr, DEFAULT_COLLATION_OID, vardata,
+											 constant, false, true, false,
+											 n_keys);
+
+				if (usedEqSel)
+					*usedEqSel = true;
+			}
+		}
+		else
+		{
+			s = eqconst_selectivity(typentry->eq_opr, DEFAULT_COLLATION_OID, vardata,
+									constant, false, true, false,
+									-1);
+		}
+
+		sum += s - s*sum;
+	}
+
+finish:
+	if (it.isExhaustive)
+		sum *= ((double)(it.n_variants))/EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD;
+
+	return sum;
+}
+
+typedef struct ClauseVarPair
+{
+	Var		*var;
+	int		idx;
+} ClauseVarPair;
+
+static void
+appendCVP(List **cvp, Var *var, int idx)
+{
+	ClauseVarPair	*e;
+
+	e = palloc(sizeof(*e));
+	e->var = var;
+	e->idx = idx;
+
+	*cvp = lappend(*cvp, e);
+}
+
+static bool
+initVarData(IndexOptInfo *index, VariableStatData *vardata)
+{
+	Relation	indexRel = index_open(index->indexoid, AccessShareLock);
+
+	if (!indexRel->rd_rel->reltype)
+	{
+		index_close(indexRel, AccessShareLock);
+
+		return false;
+	}
+
+	memset(vardata, 0, sizeof(*vardata));
+	vardata->isunique = index->unique;
+	vardata->atttype = indexRel->rd_rel->reltype;
+	vardata->rel = index->rel;
+	vardata->acl_ok = true;
+	vardata->statsTuple = SearchSysCache3(STATRELATTINH,
+										  ObjectIdGetDatum(index->indexoid),
+										  Int16GetDatum(1),
+										  BoolGetDatum(false));
+	vardata->freefunc = ReleaseSysCache;
+
+	index_close(indexRel, AccessShareLock);
+
+	if (!HeapTupleIsValid(vardata->statsTuple))
+	{
+		ReleaseVariableStats(*vardata);
+		return false;
+	}
+
+	vardata->sslots = index->sslots;
+
+	return true;
+}
+
+static int
+markEstimatedColumns(Bitmapset **estimatedclauses, List	*pairs,
+					 List	*vars, List	*missed_vars)
+{
+	ListCell	*l;
+	int			n_estimated = 0;
+
+	foreach(l, vars)
+	{
+		Var* var = (Var *) lfirst(l);
+		ListCell	*ll;
+
+		if (list_member_ptr(missed_vars, var))
+			continue;
+
+		foreach(ll, pairs)
+		{
+			ClauseVarPair *cvp=(ClauseVarPair*)lfirst(ll);
+
+			if (cvp->var == var)
+			{
+				*estimatedclauses = bms_add_member(*estimatedclauses, cvp->idx);
+				n_estimated += 1;
+				break;
+			}
+		}
+
+		Assert(ll != NULL);
+	}
+
+	return n_estimated;
+}
+
+#define SET_VARNOS(vn) do {										\
+	if ((vn) != 0)												\
+	{															\
+		if (data[0].varno == 0)									\
+			data[0].varno = (vn);								\
+		else if (data[1].varno == 0 && data[0].varno != (vn))	\
+			data[1].varno = (vn);								\
+	}															\
+} while(0)
+
+#define GET_RELBY_NO(vn)	\
+((data[0].varno == (vn) && (vn) != 0) ? &data[0] : ((data[1].varno == (vn) && (vn) != 0) ? &data[1] : NULL))
+
+#define SET_CURDATA(vn)	((cur = GET_RELBY_NO(vn)) != NULL)
+
+static bool
+hasSAOPRestriction(List *clauses, Bitmapset *estimatedclauses)
+{
+	ListCell	*l;
+	int		i = -1;
+
+	foreach(l, clauses)
+	{
+		Node* clause = (Node *) lfirst(l);
+		RestrictInfo	*rinfo = NULL;
+
+		i++;
+		if (bms_is_member(i, estimatedclauses))
+			continue;
+
+		if (IsA(clause, RestrictInfo))
+		{
+			rinfo = (RestrictInfo *) clause;
+			if (!rinfo->orclause)
+				clause = (Node*)rinfo->clause;
+		}
+
+		if (IsA(clause, ScalarArrayOpExpr))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Check if clauses represent multicolumn join with compound indexes available
+ * for both side of comparison of indexed columns of one relation with constant
+ * values. If so, calculates selectivity of compound type comparison and returns
+ * true.
+ */
+static bool
+use_multicolumn_statistic(PlannerInfo *root, List *clauses, int varRelid,
+						  JoinType jointype, SpecialJoinInfo *sjinfo,
+						  Selectivity* restrict_selectivity, Selectivity *join_selectivity,
+						  Bitmapset	**estimatedclauses, CorrelationKind
+						  *correlationKind)
+{
+	ListCell			*l;
+	List*				var_clause_map = NIL;
+	List*				missed_vars = NIL;
+	int					i;
+	int					*permutation = NULL;
+	int					n_estimated = 0;
+	int					n_keys;
+	TypeCacheEntry		*typentry;
+
+	struct	{
+		Index				varno;
+
+		List				*restrictionColumns;
+		List				*restrictionConsts;
+		List				*in_clauses;
+		List				*ineqRestrictionClauses;
+
+		List				*joinColumns;
+
+		IndexOptInfo		*index;
+		VariableStatData	vardata;
+	} data[2], *cur;
+
+	if (list_length(clauses) < 1)
+		return false;
+
+	/*
+	 * For simple queries default estimator is good enough, but multicolumn
+	 * statistic could be too expensive because of search and decompress a lot
+	 * of stat data (histogramm of multicolumn indexes).
+	 */
+	if (root->join_rel_list == NIL && root->parent_root == NULL &&
+		root->simple_rel_array_size <= 2 /* 0th is always empty */ &&
+		/* list_length(clauses) < 4 && */
+		hasSAOPRestriction(clauses, *estimatedclauses) == false)
+		return false;
+
+	*correlationKind = CKIndepend;
+	memset(data, 0, sizeof(data));
+
+	i=-1;
+	foreach(l, clauses)
+	{
+		Node* clause = (Node *) lfirst(l);
+		RestrictInfo* rinfo = NULL;
+		OpExpr	   *opclause = NULL;
+
+		i++;
+
+		/* do not use already estimated clauses */
+		if (bms_is_member(i, *estimatedclauses))
+			continue;
+
+		if (IsA(clause, RestrictInfo))
+		{
+			rinfo = (RestrictInfo *) clause;
+			if (!rinfo->orclause)
+				clause = (Node*)rinfo->clause;
+		}
+		if (IsA(clause, OpExpr))
+			opclause = (OpExpr*)clause;
+
+		if (IsA(clause, Var)) /* boolean variable */
+		{
+			Var* var1 = (Var*)clause;
+
+			SET_VARNOS(var1->varno);
+			if (SET_CURDATA(var1->varno))
+			{
+				cur->restrictionColumns = lappend(cur->restrictionColumns, var1);
+				appendCVP(&var_clause_map, var1, i);
+				cur->restrictionConsts = lappend(cur->restrictionConsts,
+												 makeBoolConst(true, false));
+			}
+		}
+		else if (IsA(clause, BoolExpr) && ((BoolExpr*)clause)->boolop == NOT_EXPR) /* (NOT bool_expr) */
+		{
+			Node* arg1 = (Node*) linitial( ((BoolExpr*)clause)->args);
+			Var* var1 = get_var(arg1);
+
+			if (var1 == NULL)
+				continue;
+
+			SET_VARNOS(var1->varno);
+			if (SET_CURDATA(var1->varno))
+			{
+				cur->restrictionColumns = lappend(cur->restrictionColumns, var1);
+				appendCVP(&var_clause_map, var1, i);
+				cur->restrictionConsts = lappend(cur->restrictionConsts,
+												 makeBoolConst(false, false));
+			}
+		}
+		else if (IsA(clause, ScalarArrayOpExpr))
+		{
+			ScalarArrayOpExpr* in = (ScalarArrayOpExpr*)clause;
+			Var* var1;
+			Node* arg2;
+			InArrayClause* iac;
+
+			var1 = get_var((Node*)linitial(in->args));
+			arg2 = (Node*) lsecond(in->args);
+
+			if (!in->useOr
+				|| list_length(in->args) != 2
+				|| get_oprrest(in->opno) != F_EQSEL
+				|| var1 == NULL
+				|| !IsA(arg2, Const))
+			{
+				continue;
+			}
+
+			SET_VARNOS(var1->varno);
+			if (SET_CURDATA(var1->varno))
+			{
+				cur->restrictionColumns = lappend(cur->restrictionColumns, var1);
+				appendCVP(&var_clause_map, var1, i);
+				cur->restrictionConsts = lappend(cur->restrictionConsts, arg2);
+
+				iac = (InArrayClause*)palloc(sizeof(InArrayClause));
+				iac->array = (ArrayType*)DatumGetPointer(((Const*)arg2)->constvalue);
+				iac->index = list_length(cur->restrictionConsts) - 1;
+
+				cur->in_clauses = lappend(cur->in_clauses, iac);
+			}
+		}
+		else if (opclause
+				 && list_length(opclause->args) == 2)
+		{
+			int oprrest = get_oprrest(opclause->opno);
+			Node* arg1 = (Node*) linitial(opclause->args);
+			Node* arg2 = (Node*) lsecond(opclause->args);
+			Var* var1 = get_var(arg1);
+			Var* var2 = get_var(arg2);
+
+			if (oprrest == F_EQSEL && treat_as_join_clause(root, (Node*)opclause, NULL, varRelid, sjinfo))
+			{
+				if (var1 == NULL || var2 == NULL || var1->vartype != var2->vartype)
+					continue;
+
+				SET_VARNOS(var1->varno);
+				SET_VARNOS(var2->varno);
+
+				if (var1->varno == data[0].varno && var2->varno == data[1].varno)
+				{
+					data[0].joinColumns = lappend(data[0].joinColumns, var1);
+					appendCVP(&var_clause_map, var1, i);
+					data[1].joinColumns = lappend(data[1].joinColumns, var2);
+					appendCVP(&var_clause_map, var2, i);
+				}
+				else if (var1->varno == data[1].varno && var2->varno == data[0].varno)
+				{
+					data[0].joinColumns = lappend(data[0].joinColumns, var2);
+					appendCVP(&var_clause_map, var2, i);
+					data[1].joinColumns = lappend(data[1].joinColumns, var1);
+					appendCVP(&var_clause_map, var1, i);
+				}
+			}
+			else /* Estimate selectivity for a restriction clause. */
+			{
+				/*
+				 * Give up if it is not equality comparison of variable with
+				 * constant or some other clause is treated as join condition
+				 */
+				if (((var1 == NULL) == (var2 == NULL)))
+					continue;
+
+				if (var1 == NULL)
+				{
+					/* swap var1 and var2 */
+					var1 = var2;
+					arg2 = arg1;
+				}
+
+				SET_VARNOS(var1->varno);
+
+				if (SET_CURDATA(var1->varno))
+				{
+					if ((rinfo && is_pseudo_constant_clause_relids(arg2, rinfo->right_relids))
+						|| (!rinfo && NumRelids(root, clause) == 1 && is_pseudo_constant_clause(arg2)))
+					{
+						/* Restriction clause with a pseudoconstant . */
+						Node* const_val = estimate_expression_value(root, arg2);
+
+						if (IsA(const_val, Const))
+						{
+							switch (oprrest)
+							{
+								case F_EQSEL:
+									cur->restrictionColumns =
+										lappend(cur->restrictionColumns, var1);
+									cur->restrictionConsts =
+										lappend(cur->restrictionConsts, const_val);
+									appendCVP(&var_clause_map, var1, i);
+									break;
+								case F_SCALARGTSEL:
+								case F_SCALARGESEL:
+								case F_SCALARLTSEL:
+								case F_SCALARLESEL:
+									/*
+									 * We do not consider range predicates now,
+									 * but we can mark them as estimated
+									 * if their variables are covered by index.
+									 */
+									appendCVP(&var_clause_map, var1, i);
+									cur->ineqRestrictionClauses =
+										lappend(cur->ineqRestrictionClauses, var1);
+									break;
+								default:
+									break;
+							}
+						}
+					}
+
+				}
+			}
+		}
+		/* else just skip clause to work with it later in caller */
+	}
+
+	*restrict_selectivity = 1.0;
+	*join_selectivity = 1.0;
+
+	/*
+	 * First, try to estimate selectivity by restrictions
+	 */
+	for(i=0; i<lengthof(data); i++)
+	{
+		cur = &data[i];
+
+		/* compute restriction clauses if applicable */
+		if (cur->varno == 0 || list_length(cur->restrictionColumns) < 1)
+			continue;
+
+		cur->index = locate_inner_multicolumn_index(
+					root, cur->varno, cur->restrictionColumns,
+					list_length(clauses), &permutation, &missed_vars, &n_keys);
+
+		if (cur->index && n_keys > 1 &&
+			initVarData(cur->index, &cur->vardata))
+		{
+			bool	usedEqSel= false;
+
+			*restrict_selectivity *= estimate_selectivity_by_index(
+									root, cur->index, &cur->vardata,
+									cur->restrictionConsts, &missed_vars, permutation,
+									cur->in_clauses, n_keys, &usedEqSel);
+
+			ReleaseVariableStats(cur->vardata);
+
+			/*
+			 * mark inequality clauses as used, see estimate_selectivity_by_index()
+			 */
+			if (usedEqSel)
+			{
+				foreach(l, cur->ineqRestrictionClauses)
+				{
+					Var* var = (Var *) lfirst(l);
+
+					/*
+					 * Note, restrictionColumns will contains extra columns !
+					 */
+					for(i=0; i<cur->index->nkeycolumns; i++)
+						if (cur->index->indexkeys[i] == var->varattno)
+							cur->restrictionColumns =
+								lappend(cur->restrictionColumns, var);
+				}
+			}
+
+			n_estimated +=
+				markEstimatedColumns(estimatedclauses, var_clause_map,
+									 cur->restrictionColumns, missed_vars);
+		}
+
+		if (permutation)
+		{
+			pfree(permutation);
+			permutation = NULL;
+		}
+	}
+
+	/* Deal with join clauses, if possible */
+	if (list_length(data[0].joinColumns) < 1)
+		goto cleanup;
+
+	data[0].index = locate_inner_multicolumn_index(
+						root,
+						data[0].varno, data[0].joinColumns,
+						list_length(clauses), &permutation, &missed_vars, &n_keys);
+
+	if (!data[0].index || n_keys < 1)
+		goto cleanup;
+
+	Assert(permutation != NULL);
+	Assert(data[1].varno != 0);
+	Assert(list_length(data[0].joinColumns) == list_length(data[1].joinColumns));
+
+	data[1].index = locate_outer_multicolumn_index(
+										root,
+										data[1].varno, data[1].joinColumns,
+										permutation);
+
+	if (!data[1].index)
+		goto cleanup;
+
+	if (!initVarData(data[0].index, &data[0].vardata))
+		goto cleanup;
+
+	if (!initVarData(data[1].index, &data[1].vardata))
+	{
+		ReleaseVariableStats(data[0].vardata);
+		goto cleanup;
+	}
+
+	typentry = lookup_type_cache(data[0].vardata.atttype, TYPECACHE_EQ_OPR);
+	*join_selectivity *= eqjoin_selectivity(root, typentry->eq_opr,
+											DEFAULT_COLLATION_OID,
+											&data[0].vardata, &data[1].vardata,
+											sjinfo, n_keys);
+
+	/* for self join */
+	if (data[0].index->indexoid == data[1].index->indexoid)
+		*correlationKind = CKSelf;
+	else
+	{
+		RangeTblEntry *lrte = planner_rt_fetch(data[0].index->rel->relid, root),
+					  *rrte = planner_rt_fetch(data[1].index->rel->relid, root);
+
+		if (lrte->relid == rrte->relid)
+			*correlationKind = CKSelf;
+	}
+
+	for (i = 0; i < lengthof(data); i++)
+		ReleaseVariableStats(data[i].vardata);
+
+	n_estimated +=
+				markEstimatedColumns(estimatedclauses, var_clause_map,
+									 data[0].joinColumns, missed_vars);
+
+cleanup:
+	if (permutation)
+		pfree(permutation);
+
+	return n_estimated != 0;
+}
+
 /****************************************************************************
  *		ROUTINES TO COMPUTE SELECTIVITIES
  ****************************************************************************/
@@ -98,6 +1152,54 @@ static Selectivity clauselist_selectivity_or(PlannerInfo *root,
  * Of course this is all very dependent on the behavior of the inequality
  * selectivity functions; perhaps some day we can generalize the approach.
  */
+
+static void
+appendSelectivityRes(Selectivity s[5], Selectivity sel, CorrelationKind ck)
+{
+	switch(ck)
+	{
+		case CKRestrict:
+			s[ck] *= sel;
+			break;
+		case CKSelf:
+		case CKLikelySelf:
+			s[CKMul] *= sel;
+			if (s[ck] > sel)
+				s[ck] = sel;
+			/* FALLTHROUGH */
+		case CKIndepend:
+			s[CKIndepend] *= sel;
+			break;
+		default:
+			elog(ERROR, "unknown selectivity kind: %d", ck);
+	}
+}
+
+static Selectivity
+finalizeSelectivityRes(Selectivity s[5])
+{
+	Selectivity	sel;
+
+	sel = s[CKRestrict] * s[CKIndepend];
+
+	if (s[CKIndepend] != s[CKMul])
+	{
+		/* we have both independ and correlated - fallback */
+		sel *= s[CKMul];
+	}
+	else
+	{
+		/* we have only correlated join clauses */
+		if (s[CKLikelySelf] != 1.0 && sel < s[CKLikelySelf])
+			sel = sel + (s[CKLikelySelf] - sel) * 0.25;
+
+		if (s[CKSelf] != 1.0 && sel < s[CKSelf])
+			sel = sel + (s[CKSelf] - sel) * 1.0;
+	}
+
+	return sel;
+}
+
 Selectivity
 clauselist_selectivity(PlannerInfo *root,
 					   List *clauses,
@@ -123,12 +1225,14 @@ clauselist_selectivity_ext(PlannerInfo *root,
 						   SpecialJoinInfo *sjinfo,
 						   bool use_extended_stats)
 {
-	Selectivity s1 = 1.0;
+	Selectivity s[5 /* per CorrelationKind */]  = {1.0, 1.0, 1.0, 1.0, 1.0};
+	Selectivity s2 = 1.0, s3 = 1.0;
 	RelOptInfo *rel;
 	Bitmapset  *estimatedclauses = NULL;
 	RangeQueryClause *rqlist = NULL;
 	ListCell   *l;
 	int			listidx;
+	CorrelationKind ck;
 
 	/*
 	 * If there's exactly one clause, just go directly to
@@ -152,9 +1256,23 @@ clauselist_selectivity_ext(PlannerInfo *root,
 		 * 'estimatedclauses' is populated with the 0-based list position
 		 * index of clauses estimated here, and that should be ignored below.
 		 */
-		s1 = statext_clauselist_selectivity(root, clauses, varRelid,
+		s2 = statext_clauselist_selectivity(root, clauses, varRelid,
 											jointype, sjinfo, rel,
 											&estimatedclauses, false);
+		appendSelectivityRes(s, s2, CKRestrict);
+	}
+
+	/*
+	 * Check if join conjuncts corresponds to some compound indexes on left and
+	 * right joined relations or indexed columns of one relation is compared
+	 * with constant values. In this case selectivity of join can be calculated
+	 * based on statistic of this compound index.
+	 */
+	while(use_multicolumn_statistic(root, clauses, varRelid, jointype, sjinfo,
+									&s2, &s3, &estimatedclauses, &ck))
+	{
+		appendSelectivityRes(s, s2, CKRestrict);
+		appendSelectivityRes(s, s3, ck);
 	}
 
 	/*
@@ -196,7 +1314,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
 			rinfo = (RestrictInfo *) clause;
 			if (rinfo->pseudoconstant)
 			{
-				s1 = s1 * s2;
+				appendSelectivityRes(s, s2, CKRestrict);
 				continue;
 			}
 			clause = (Node *) rinfo->clause;
@@ -210,12 +1328,17 @@ clauselist_selectivity_ext(PlannerInfo *root,
 		 * the simple way we are expecting.)  Most of the tests here can be
 		 * done more efficiently with rinfo than without.
 		 */
+		ck = treat_as_join_clause(root, clause, rinfo, varRelid, sjinfo) ?
+				CKIndepend : CKRestrict;
 		if (is_opclause(clause) && list_length(((OpExpr *) clause)->args) == 2)
 		{
 			OpExpr	   *expr = (OpExpr *) clause;
 			bool		varonleft = true;
 			bool		ok;
 
+			if (ck == CKIndepend)
+				ck = get_correlation_kind(root, varRelid, expr);
+
 			if (rinfo)
 			{
 				ok = (bms_membership(rinfo->clause_relids) == BMS_SINGLETON) &&
@@ -254,7 +1377,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
 						break;
 					default:
 						/* Just merge the selectivity in generically */
-						s1 = s1 * s2;
+						appendSelectivityRes(s, s2, ck);
 						break;
 				}
 				continue;		/* drop to loop bottom */
@@ -262,7 +1385,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
 		}
 
 		/* Not the right form, so treat it generically. */
-		s1 = s1 * s2;
+		appendSelectivityRes(s, s2, ck);
 	}
 
 	/*
@@ -324,15 +1447,13 @@ clauselist_selectivity_ext(PlannerInfo *root,
 				}
 			}
 			/* Merge in the selectivity of the pair of clauses */
-			s1 *= s2;
+			appendSelectivityRes(s, s2, CKRestrict);
 		}
 		else
 		{
 			/* Only found one of a pair, merge it in generically */
-			if (rqlist->have_lobound)
-				s1 *= rqlist->lobound;
-			else
-				s1 *= rqlist->hibound;
+			appendSelectivityRes(s, (rqlist->have_lobound) ? rqlist->lobound :
+								 rqlist->hibound, CKRestrict);
 		}
 		/* release storage and advance */
 		rqnext = rqlist->next;
@@ -340,7 +1461,7 @@ clauselist_selectivity_ext(PlannerInfo *root,
 		rqlist = rqnext;
 	}
 
-	return s1;
+	return finalizeSelectivityRes(s);
 }
 
 /*
@@ -647,6 +1768,137 @@ treat_as_join_clause(PlannerInfo *root, Node *clause, RestrictInfo *rinfo,
 	}
 }
 
+typedef struct RangeTblEntryContext {
+	RangeTblEntry	*rte;
+	int				count;
+} RangeTblEntryContext;
+
+static bool
+find_rte_walker(Node *node, RangeTblEntryContext *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (context->count > 1)
+		return true; /* skip rest */
+
+	if (IsA(node, RangeTblEntry)) {
+		RangeTblEntry	*rte = (RangeTblEntry*)node;
+
+		if (rte->rtekind == RTE_RELATION)
+		{
+			if (context->count == 0)
+			{
+				context->count++;
+				context->rte=rte;
+			}
+			else if (rte->relid != context->rte->relid)
+			{
+				context->count++;
+				return true; /* more that one relation in subtree */
+			}
+		}
+		else if (!(rte->rtekind == RTE_SUBQUERY || rte->rtekind == RTE_JOIN ||
+				   rte->rtekind == RTE_CTE))
+		{
+			context->count++;
+			return true; /* more that one relation in subtree */
+		}
+
+		return false; /* allow range_table_walker to continue */
+	}
+
+	if (IsA(node, Query))
+		return query_tree_walker((Query *) node, find_rte_walker,
+								 (void *) context, QTW_EXAMINE_RTES_BEFORE);
+
+	return expression_tree_walker(node, find_rte_walker, (void *) context);
+}
+
+static RangeTblEntry*
+find_single_rte(RangeTblEntry *node)
+{
+	RangeTblEntryContext	context;
+
+	context.rte = NULL;
+	context.count = 0;
+
+	(void)range_table_walker(list_make1(node),
+							 find_rte_walker,
+							 (void *) &context, QTW_EXAMINE_RTES_BEFORE);
+
+	return context.count == 1 ? context.rte : NULL;
+}
+
+#define IsSameRelationRTE(a, b)	( \
+	(a)->rtekind == (b)->rtekind && \
+	(a)->rtekind == RTE_RELATION && \
+	(a)->relid == (b)->relid \
+)
+
+
+/*
+ * Any self join or join with aggregation over the same table
+ */
+
+static CorrelationKind
+get_correlation_kind(PlannerInfo *root, int varRelid, OpExpr* expr)
+{
+	Node	*left_arg, *right_arg;
+	Relids	left_varnos, right_varnos;
+	int		left_varno, right_varno;
+	RangeTblEntry	*left_rte, *right_rte;
+
+	if (varRelid != 0)
+		/* We consider only case of joins, not restriction mode */
+		return CKIndepend;
+
+	/* Check if it is equality comparison */
+	if (get_oprrest(expr->opno) != F_EQSEL)
+		return CKIndepend;
+
+	left_arg = linitial(expr->args);
+	right_arg = lsecond(expr->args);
+
+	/*
+	 * Check if it is join of two different relations
+	 */
+	left_varnos = pull_varnos(root, left_arg);
+	right_varnos = pull_varnos(root, right_arg);
+	if (!bms_get_singleton_member(left_varnos, &left_varno) ||
+		!bms_get_singleton_member(right_varnos, &right_varno) ||
+		left_varno == right_varno)
+		return CKIndepend;
+
+	left_rte = planner_rt_fetch(left_varno, root);
+	right_rte = planner_rt_fetch(right_varno, root);
+
+	if (IsSameRelationRTE(left_rte, right_rte))
+	{
+		Var *lvar = get_var(left_arg),
+			*rvar = get_var(right_arg);
+
+		/* self join detected, check if it simple a=b clause */
+		if (lvar == NULL || rvar == NULL)
+			return CKLikelySelf;
+		return (lvar->varattno == rvar->varattno) ?
+											CKSelf : CKLikelySelf;
+	}
+
+	if ((left_rte = find_single_rte(left_rte)) == NULL)
+		return CKIndepend;
+	if ((right_rte = find_single_rte(right_rte)) == NULL)
+		return CKIndepend;
+
+	if (IsSameRelationRTE(left_rte, right_rte))
+	{
+		/* self join detected, but over some transformation which cannot be
+		 * flatten */
+		return CKLikelySelf;
+	}
+
+	return CKIndepend;
+}
 
 /*
  * clause_selectivity -
diff --git a/src/backend/optimizer/path/costsize.c b/src/backend/optimizer/path/costsize.c
index 0ba26b207b0..22cd37cf3e2 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -157,6 +157,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	QualCost	total;
+	bool		calccoalesce;
 } cost_qual_eval_context;
 
 static List *extract_nonindex_conditions(List *qual_clauses, List *indexclauses);
@@ -765,7 +766,7 @@ cost_index(IndexPath *path, PlannerInfo *root, double loop_count,
 	cost_qual_eval(&qpqual_cost, qpquals, root);
 
 	startup_cost += qpqual_cost.startup;
-	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+	cpu_per_tuple = cpu_tuple_cost + 2.0*qpqual_cost.per_tuple;
 
 	cpu_run_cost += cpu_per_tuple * tuples_fetched;
 
@@ -1051,7 +1052,7 @@ cost_bitmap_heap_scan(Path *path, PlannerInfo *root, RelOptInfo *baserel,
 	get_restriction_qual_cost(root, baserel, param_info, &qpqual_cost);
 
 	startup_cost += qpqual_cost.startup;
-	cpu_per_tuple = cpu_tuple_cost + qpqual_cost.per_tuple;
+	cpu_per_tuple = cpu_tuple_cost + 2.0*qpqual_cost.per_tuple;
 	cpu_run_cost = cpu_per_tuple * tuples_fetched;
 
 	/* Adjust costing for parallelism, if used. */
@@ -1796,6 +1797,327 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
 									rterm->pathtarget->width);
 }
 
+/*
+ * is_fake_var
+ *		Workaround for generate_append_tlist() which generates fake Vars with
+ *		varno == 0, that will cause a fail of estimate_num_group() call
+ *
+ * XXX Ummm, why would estimate_num_group fail with this?
+ */
+static bool
+is_fake_var(Expr *expr)
+{
+	if (IsA(expr, RelabelType))
+		expr = (Expr *) ((RelabelType *) expr)->arg;
+
+	return (IsA(expr, Var) && ((Var *) expr)->varno == 0);
+}
+
+/*
+ * get_width_cost_multiplier
+ *		Returns relative complexity of comparing two values based on its width.
+ * The idea behind is that the comparison becomes more expensive the longer the
+ * value is. Return value is in cpu_operator_cost units.
+ */
+static double
+get_width_cost_multiplier(PlannerInfo *root, Expr *expr)
+{
+	double		width = -1.0;	/* fake value */
+
+	if (IsA(expr, RelabelType))
+		expr = (Expr *) ((RelabelType *) expr)->arg;
+
+	/* Try to find actual stat in corresponding relation */
+	if (IsA(expr, Var))
+	{
+		Var		   *var = (Var *) expr;
+
+		if (var->varno > 0 && var->varno < root->simple_rel_array_size)
+		{
+			RelOptInfo *rel = root->simple_rel_array[var->varno];
+
+			if (rel != NULL &&
+				var->varattno >= rel->min_attr &&
+				var->varattno <= rel->max_attr)
+			{
+				int			ndx = var->varattno - rel->min_attr;
+
+				if (rel->attr_widths[ndx] > 0)
+					width = rel->attr_widths[ndx];
+			}
+		}
+	}
+
+	/* Didn't find any actual stats, try using type width instead. */
+	if (width < 0.0)
+	{
+		Node	   *node = (Node *) expr;
+
+		width = get_typavgwidth(exprType(node), exprTypmod(node));
+	}
+
+	/*
+	 * Values are passed as Datum type, so comparisons can't be cheaper than
+	 * comparing a Datum value.
+	 *
+	 * FIXME I find this reasoning questionable. We may pass int2, and
+	 * comparing it is probably a bit cheaper than comparing a bigint.
+	 */
+	if (width <= sizeof(Datum))
+		return 1.0;
+
+	/*
+	 * We consider the cost of a comparison not to be directly proportional to
+	 * width of the argument, because widths of the arguments could be
+	 * slightly different (we only know the average width for the whole
+	 * column). So we use log16(width) as an estimate.
+	 */
+	return 1.0 + 0.125 * LOG2(width / sizeof(Datum));
+}
+
+/*
+ * compute_cpu_sort_cost
+ *		compute CPU cost of sort (i.e. in-memory)
+ *
+ * The main thing we need to calculate to estimate sort CPU costs is the number
+ * of calls to the comparator functions. The difficulty is that for multi-column
+ * sorts there may be different data types involved (for some of which the calls
+ * may be much more expensive). Furthermore, columns may have a very different
+ * number of distinct values - the higher the number, the fewer comparisons will
+ * be needed for the following columns.
+ *
+ * The algorithm is incremental - we add pathkeys one by one, and at each step we
+ * estimate the number of necessary comparisons (based on the number of distinct
+ * groups in the current pathkey prefix and the new pathkey), and the comparison
+ * costs (which is data type specific).
+ *
+ * Estimation of the number of comparisons is based on ideas from:
+ *
+ * "Quicksort Is Optimal", Robert Sedgewick, Jon Bentley, 2002
+ * [https://www.cs.princeton.edu/~rs/talks/QuicksortIsOptimal.pdf]
+ *
+ * In term of that paper, let N - number of tuples, Xi - number of identical
+ * tuples with value Ki, then the estimate of number of comparisons is:
+ *
+ *	log(N! / (X1! * X2! * ..))  ~  sum(Xi * log(N/Xi))
+ *
+ * We assume all Xi the same because now we don't have any estimation of
+ * group sizes, we have only know the estimate of number of groups (distinct
+ * values). In that case, formula becomes:
+ *
+ *	N * log(NumberOfGroups)
+ *
+ * For multi-column sorts we need to estimate the number of comparisons for
+ * each individual column - for example with columns (c1, c2, ..., ck) we
+ * can estimate that number of comparisons on ck is roughly
+ *
+ *	ncomparisons(c1, c2, ..., ck) / ncomparisons(c1, c2, ..., c(k-1))
+ *
+ * Let k be a column number, Gk - number of groups defined by k columns, and Fk
+ * the cost of the comparison is
+ *
+ *	N * sum( Fk * log(Gk) )
+ *
+ * Note: We also consider column width, not just the comparator cost.
+ *
+ * NOTE: some callers currently pass NIL for pathkeys because they
+ * can't conveniently supply the sort keys. In this case, it will fallback to
+ * simple comparison cost estimate.
+ */
+static Cost
+compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
+					  Cost comparison_cost, double tuples, double output_tuples,
+					  bool heapSort)
+{
+	Cost		per_tuple_cost = 0.0;
+	ListCell   *lc;
+	List	   *pathkeyExprs = NIL;
+	double		tuplesPerPrevGroup = tuples;
+	double		totalFuncCost = 1.0;
+	bool		has_fake_var = false;
+	int			i = 0;
+	Oid			prev_datatype = InvalidOid;
+	List	   *cache_varinfos = NIL;
+
+	/* fallback if pathkeys is unknown */
+	if (list_length(pathkeys) == 0)
+	{
+		/*
+		 * If we'll use a bounded heap-sort keeping just K tuples in memory,
+		 * for a total number of tuple comparisons of N log2 K; but the
+		 * constant factor is a bit higher than for quicksort. Tweak it so
+		 * that the cost curve is continuous at the crossover point.
+		 */
+		output_tuples = (heapSort) ? 2.0 * output_tuples : tuples;
+		per_tuple_cost += 2.0 * cpu_operator_cost * LOG2(output_tuples);
+
+		/* add cost provided by caller */
+		per_tuple_cost += comparison_cost;
+
+		return per_tuple_cost * tuples;
+	}
+
+	/*
+	 * Computing total cost of sorting takes into account the per-column
+	 * comparison function cost.  We try to compute the needed number of
+	 * comparisons per column.
+	 */
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(lc);
+		EquivalenceMember *em;
+		double		nGroups,
+					correctedNGroups;
+		Cost		funcCost = 1.0;
+
+		/*
+		 * We believe that equivalence members aren't very different, so, to
+		 * estimate cost we consider just the first member.
+		 */
+		em = (EquivalenceMember *) linitial(pathkey->pk_eclass->ec_members);
+
+		if (em->em_datatype != InvalidOid)
+		{
+			/* do not lookup funcCost if the data type is the same */
+			if (prev_datatype != em->em_datatype)
+			{
+				Oid			sortop;
+				QualCost	cost;
+
+				sortop = get_opfamily_member(pathkey->pk_opfamily,
+											 em->em_datatype, em->em_datatype,
+											 pathkey->pk_strategy);
+
+				cost.startup = 0;
+				cost.per_tuple = 0;
+				add_function_cost(root, get_opcode(sortop), NULL, &cost);
+
+				/*
+				 * add_function_cost returns the product of cpu_operator_cost
+				 * and procost, but we need just procost, co undo that.
+				 */
+				funcCost = cost.per_tuple / cpu_operator_cost;
+
+				prev_datatype = em->em_datatype;
+			}
+		}
+
+		/* factor in the width of the values in this column */
+		funcCost *= get_width_cost_multiplier(root, em->em_expr);
+
+		/* now we have per-key cost, so add to the running total */
+		totalFuncCost += funcCost;
+
+		/* remember if we have found a fake Var in pathkeys */
+		has_fake_var |= is_fake_var(em->em_expr);
+		pathkeyExprs = lappend(pathkeyExprs, em->em_expr);
+
+		/*
+		 * We need to calculate the number of comparisons for this column,
+		 * which requires knowing the group size. So we estimate the number of
+		 * groups by calling estimate_num_groups_incremental(), which
+		 * estimates the group size for "new" pathkeys.
+		 *
+		 * Note: estimate_num_groups_incremental does not handle fake Vars, so
+		 * use a default estimate otherwise.
+		 */
+		if (!has_fake_var)
+			nGroups = estimate_num_groups_incremental(root, pathkeyExprs,
+													  tuplesPerPrevGroup, NULL, NULL,
+													  &cache_varinfos,
+													  list_length(pathkeyExprs) - 1);
+		else if (tuples > 4.0)
+
+			/*
+			 * Use geometric mean as estimation if there are no stats.
+			 *
+			 * We don't use DEFAULT_NUM_DISTINCT here, because that's used for
+			 * a single column, but here we're dealing with multiple columns.
+			 */
+			nGroups = ceil(2.0 + sqrt(tuples) * (i + 1) / list_length(pathkeys));
+		else
+			nGroups = tuples;
+
+		/*
+		 * Presorted keys are not considered in the cost above, but we still
+		 * do have to compare them in the qsort comparator. So make sure to
+		 * factor in the cost in that case.
+		 */
+		if (i >= nPresortedKeys)
+		{
+			if (heapSort)
+			{
+				/*
+				 * have to keep at least one group, and a multiple of group
+				 * size
+				 */
+				correctedNGroups = ceil(output_tuples / tuplesPerPrevGroup);
+			}
+			else
+				/* all groups in the input */
+				correctedNGroups = nGroups;
+
+			correctedNGroups = Max(1.0, ceil(correctedNGroups));
+
+			per_tuple_cost += totalFuncCost * LOG2(correctedNGroups);
+		}
+
+		i++;
+
+		/*
+		 * Uniform distributions with all groups being of the same size are
+		 * the best case, with nice smooth behavior. Real-world distributions
+		 * tend not to be uniform, though, and we don't have any reliable
+		 * easy-to-use information. As a basic defense against skewed
+		 * distributions, we use a 1.5 factor to make the expected group a bit
+		 * larger, but we need to be careful not to make the group larger than
+		 * in the preceding step.
+		 */
+		tuplesPerPrevGroup = Min(tuplesPerPrevGroup,
+								 ceil(1.5 * tuplesPerPrevGroup / nGroups));
+
+		/*
+		 * Once we get single-row group, it means tuples in the group are
+		 * unique and we can skip all remaining columns.
+		 */
+		if (tuplesPerPrevGroup <= 1.0)
+			break;
+	}
+
+	list_free(pathkeyExprs);
+
+	/* per_tuple_cost is in cpu_operator_cost units */
+	per_tuple_cost *= cpu_operator_cost;
+
+	/*
+	 * Accordingly to "Introduction to algorithms", Thomas H. Cormen, Charles
+	 * E. Leiserson, Ronald L. Rivest, ISBN 0-07-013143-0, quicksort
+	 * estimation formula has additional term proportional to number of tuples
+	 * (see Chapter 8.2 and Theorem 4.1). That affects cases with a low number
+	 * of tuples, approximately less than 1e4. We could implement it as an
+	 * additional multiplier under the logarithm, but we use a bit more
+	 * complex formula which takes into account the number of unique tuples
+	 * and it's not clear how to combine the multiplier with the number of
+	 * groups. Estimate it as 10 cpu_operator_cost units.
+	 */
+	per_tuple_cost += 10 * cpu_operator_cost;
+
+	per_tuple_cost += comparison_cost;
+
+	return tuples * per_tuple_cost;
+}
+
+/*
+ * simple wrapper just to estimate best sort path
+ */
+Cost
+cost_sort_estimate(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
+				   double tuples)
+{
+	return compute_cpu_sort_cost(root, pathkeys, nPresortedKeys,
+								 0, tuples, tuples, false);
+}
+
 /*
  * cost_tuplesort
  *	  Determines and returns the cost of sorting a relation using tuplesort,
@@ -1812,7 +2134,7 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
  * number of initial runs formed and M is the merge order used by tuplesort.c.
  * Since the average initial run should be about sort_mem, we have
  *		disk traffic = 2 * relsize * ceil(logM(p / sort_mem))
- *		cpu = comparison_cost * t * log2(t)
+ * 		and cpu cost (computed by compute_cpu_sort_cost()).
  *
  * If the sort is bounded (i.e., only the first k result tuples are needed)
  * and k tuples can fit into sort_mem, we use a heap method that keeps only
@@ -1831,9 +2153,11 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
  * 'comparison_cost' is the extra cost per comparison, if any
  * 'sort_mem' is the number of kilobytes of work memory allowed for the sort
  * 'limit_tuples' is the bound on the number of output tuples; -1 if no bound
+ * 'startup_cost' is expected to be 0 at input. If there is "input cost" it should
+ * be added by caller later
  */
 static void
-cost_tuplesort(Cost *startup_cost, Cost *run_cost,
+cost_tuplesort(PlannerInfo *root, List *pathkeys, Cost *startup_cost, Cost *run_cost,
 			   double tuples, int width,
 			   Cost comparison_cost, int sort_mem,
 			   double limit_tuples)
@@ -1850,9 +2174,6 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost,
 	if (tuples < 2.0)
 		tuples = 2.0;
 
-	/* Include the default cost-per-comparison */
-	comparison_cost += 2.0 * cpu_operator_cost;
-
 	/* Do we have a useful LIMIT? */
 	if (limit_tuples > 0 && limit_tuples < tuples)
 	{
@@ -1876,12 +2197,10 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost,
 		double		log_runs;
 		double		npageaccesses;
 
-		/*
-		 * CPU costs
-		 *
-		 * Assume about N log2 N comparisons
-		 */
-		*startup_cost = comparison_cost * tuples * LOG2(tuples);
+		/* CPU costs */
+		*startup_cost = compute_cpu_sort_cost(root, pathkeys, 0,
+											  comparison_cost, tuples,
+											  tuples, false);
 
 		/* Disk costs */
 
@@ -1897,18 +2216,17 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost,
 	}
 	else if (tuples > 2 * output_tuples || input_bytes > sort_mem_bytes)
 	{
-		/*
-		 * We'll use a bounded heap-sort keeping just K tuples in memory, for
-		 * a total number of tuple comparisons of N log2 K; but the constant
-		 * factor is a bit higher than for quicksort.  Tweak it so that the
-		 * cost curve is continuous at the crossover point.
-		 */
-		*startup_cost = comparison_cost * tuples * LOG2(2.0 * output_tuples);
+		/* We'll use a bounded heap-sort keeping just K tuples in memory. */
+		*startup_cost = compute_cpu_sort_cost(root, pathkeys, 0,
+											  comparison_cost, tuples,
+											  output_tuples, true);
 	}
 	else
 	{
 		/* We'll use plain quicksort on all the input tuples */
-		*startup_cost = comparison_cost * tuples * LOG2(tuples);
+		*startup_cost = compute_cpu_sort_cost(root, pathkeys, 0,
+											  comparison_cost, tuples,
+											  tuples, false);
 	}
 
 	/*
@@ -1941,8 +2259,8 @@ cost_incremental_sort(Path *path,
 					  double input_tuples, int width, Cost comparison_cost, int sort_mem,
 					  double limit_tuples)
 {
-	Cost		startup_cost = 0,
-				run_cost = 0,
+	Cost		startup_cost,
+				run_cost,
 				input_run_cost = input_total_cost - input_startup_cost;
 	double		group_tuples,
 				input_groups;
@@ -2027,7 +2345,7 @@ cost_incremental_sort(Path *path,
 	 * pessimistic about incremental sort performance and increase its average
 	 * group size by half.
 	 */
-	cost_tuplesort(&group_startup_cost, &group_run_cost,
+	cost_tuplesort(root, pathkeys, &group_startup_cost, &group_run_cost,
 				   1.5 * group_tuples, width, comparison_cost, sort_mem,
 				   limit_tuples);
 
@@ -2035,7 +2353,7 @@ cost_incremental_sort(Path *path,
 	 * Startup cost of incremental sort is the startup cost of its first group
 	 * plus the cost of its input.
 	 */
-	startup_cost += group_startup_cost
+	startup_cost = group_startup_cost
 		+ input_startup_cost + group_input_run_cost;
 
 	/*
@@ -2044,7 +2362,7 @@ cost_incremental_sort(Path *path,
 	 * group, plus the total cost to process the remaining groups, plus the
 	 * remaining cost of input.
 	 */
-	run_cost += group_run_cost
+	run_cost = group_run_cost
 		+ (group_run_cost + group_startup_cost) * (input_groups - 1)
 		+ group_input_run_cost * (input_groups - 1);
 
@@ -2084,7 +2402,7 @@ cost_sort(Path *path, PlannerInfo *root,
 	Cost		startup_cost;
 	Cost		run_cost;
 
-	cost_tuplesort(&startup_cost, &run_cost,
+	cost_tuplesort(root, pathkeys, &startup_cost, &run_cost,
 				   tuples, width,
 				   comparison_cost, sort_mem,
 				   limit_tuples);
@@ -2182,7 +2500,7 @@ append_nonpartial_cost(List *subpaths, int numpaths, int parallel_workers)
  *	  Determines and returns the cost of an Append node.
  */
 void
-cost_append(AppendPath *apath)
+cost_append(AppendPath *apath, PlannerInfo *root)
 {
 	ListCell   *l;
 
@@ -2250,7 +2568,7 @@ cost_append(AppendPath *apath)
 					 * any child.
 					 */
 					cost_sort(&sort_path,
-							  NULL, /* doesn't currently need root */
+							  root,
 							  pathkeys,
 							  subpath->total_cost,
 							  subpath->rows,
@@ -3413,8 +3731,9 @@ initial_cost_mergejoin(PlannerInfo *root, JoinCostWorkspace *workspace,
 	/* CPU costs left for later */
 
 	/* Public result fields */
-	workspace->startup_cost = startup_cost;
-	workspace->total_cost = startup_cost + run_cost + inner_run_cost;
+	workspace->startup_cost = startup_cost + outer_path->total_cost/outer_rows +
+											  inner_path->total_cost/inner_rows;
+	workspace->total_cost = workspace->startup_cost + run_cost + inner_run_cost;
 	/* Save private data for final_cost_mergejoin */
 	workspace->run_cost = run_cost;
 	workspace->inner_run_cost = inner_run_cost;
@@ -4355,6 +4674,7 @@ cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root)
 	context.root = root;
 	context.total.startup = 0;
 	context.total.per_tuple = 0;
+	context.calccoalesce = true;
 
 	/* We don't charge any cost for the implicit ANDing at top level ... */
 
@@ -4380,6 +4700,22 @@ cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root)
 	context.root = root;
 	context.total.startup = 0;
 	context.total.per_tuple = 0;
+	context.calccoalesce = true;
+
+	cost_qual_eval_walker(qual, &context);
+
+	*cost = context.total;
+}
+
+void
+cost_qual_eval_node_index(QualCost *cost, Node *qual, PlannerInfo *root)
+{
+	cost_qual_eval_context context;
+
+	context.root = root;
+	context.total.startup = 0;
+	context.total.per_tuple = 0;
+	context.calccoalesce = false;
 
 	cost_qual_eval_walker(qual, &context);
 
@@ -4409,6 +4745,7 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 			locContext.root = context->root;
 			locContext.total.startup = 0;
 			locContext.total.per_tuple = 0;
+			locContext.calccoalesce = context->calccoalesce;
 
 			/*
 			 * For an OR clause, recurse into the marked-up tree so that we
@@ -4649,6 +4986,11 @@ cost_qual_eval_walker(Node *node, cost_qual_eval_context *context)
 		 */
 		return false;
 	}
+	else if (IsA(node, CoalesceExpr) && context->calccoalesce)
+	{
+		context->total.per_tuple += cpu_operator_cost *
+			list_length(((CoalesceExpr *) node)->args);
+	}
 
 	/* recurse into children */
 	return expression_tree_walker(node, cost_qual_eval_walker,
@@ -5136,6 +5478,7 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 	Selectivity jselec;
 	Selectivity pselec;
 	double		nrows;
+	bool		apply_righthand = false;
 
 	/*
 	 * Compute joinclause selectivity.  Note that we are only considering
@@ -5174,9 +5517,11 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 		{
 			RestrictInfo *rinfo = lfirst_node(RestrictInfo, l);
 
-			if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids))
+			if (RINFO_IS_PUSHED_DOWN(rinfo, joinrel->relids)) {
 				pushedquals = lappend(pushedquals, rinfo);
-			else
+				apply_righthand |=	bms_overlap(rinfo->clause_relids,
+												sjinfo->min_righthand);
+			} else
 				joinquals = lappend(joinquals, rinfo);
 		}
 
@@ -5228,6 +5573,8 @@ calc_joinrel_size_estimate(PlannerInfo *root,
 			nrows = outer_rows * inner_rows * fkselec * jselec;
 			if (nrows < outer_rows)
 				nrows = outer_rows;
+			if (apply_righthand && inner_rows < outer_rows)
+				pselec *= inner_rows / outer_rows;
 			nrows *= pselec;
 			break;
 		case JOIN_FULL:
diff --git a/src/backend/optimizer/path/equivclass.c b/src/backend/optimizer/path/equivclass.c
index 9357ee4036c..c0eb7397677 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -680,7 +680,18 @@ get_eclass_for_sort_expr(PlannerInfo *root,
 
 			if (opcintype == cur_em->em_datatype &&
 				equal(expr, cur_em->em_expr))
-				return cur_ec;	/* Match! */
+			{
+				/*
+				 * Match!
+				 *
+				 * Copy the sortref if it wasn't set yet. That may happen if
+				 * the ec was constructed from WHERE clause, i.e. it doesn't
+				 * have a target reference at all.
+				 */
+				if (cur_ec->ec_sortref == 0 && sortref > 0)
+					cur_ec->ec_sortref = sortref;
+				return cur_ec;
+			}
 		}
 	}
 
diff --git a/src/backend/optimizer/path/indxpath.c b/src/backend/optimizer/path/indxpath.c
index 3800f0cdfb7..48617bf15c2 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -111,8 +111,6 @@ static List *build_index_paths(PlannerInfo *root, RelOptInfo *rel,
 							   bool *skip_lower_saop);
 static List *build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
 								List *clauses, List *other_clauses);
-static List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
-									  List *clauses, List *other_clauses);
 static Path *choose_bitmap_and(PlannerInfo *root, RelOptInfo *rel,
 							   List *paths);
 static int	path_usage_comparator(const void *a, const void *b);
@@ -1251,7 +1249,7 @@ build_paths_for_OR(PlannerInfo *root, RelOptInfo *rel,
  * for the purpose of generating indexquals, but are not to be searched for
  * ORs.  (See build_paths_for_OR() for motivation.)
  */
-static List *
+List *
 generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
 						 List *clauses, List *other_clauses)
 {
@@ -3277,7 +3275,6 @@ match_clause_to_ordering_op(IndexOptInfo *index,
 	return clause;
 }
 
-
 /****************************************************************************
  *				----  ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS	----
  ****************************************************************************/
@@ -3477,7 +3474,8 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
  * relation_has_unique_index_for
  *	  Determine whether the relation provably has at most one row satisfying
  *	  a set of equality conditions, because the conditions constrain all
- *	  columns of some unique index.
+ *	  columns of some unique index. If index_info is not null, it is set to
+ *	  point to a new UniqueIndexInfo containing the index and conditions.
  *
  * The conditions can be represented in either or both of two ways:
  * 1. A list of RestrictInfo nodes, where the caller has already determined
@@ -3498,7 +3496,8 @@ ec_member_matches_indexcol(PlannerInfo *root, RelOptInfo *rel,
 bool
 relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 							  List *restrictlist,
-							  List *exprlist, List *oprlist)
+							  List *exprlist, List *oprlist,
+							  UniqueIndexInfo **index_info)
 {
 	ListCell   *ic;
 
@@ -3554,6 +3553,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 	{
 		IndexOptInfo *ind = (IndexOptInfo *) lfirst(ic);
 		int			c;
+		List *matched_restrictlist = NIL;
 
 		/*
 		 * If the index is not unique, or not immediately enforced, or if it's
@@ -3605,6 +3605,7 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 				if (match_index_to_operand(rexpr, c, ind))
 				{
 					matched = true; /* column is unique */
+					matched_restrictlist = lappend(matched_restrictlist, rinfo);
 					break;
 				}
 			}
@@ -3647,7 +3648,25 @@ relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 
 		/* Matched all key columns of this index? */
 		if (c == ind->nkeycolumns)
+		{
+			if (index_info != NULL)
+			{
+				/* This may be called in GEQO memory context. */
+				MemoryContext oldContext = MemoryContextSwitchTo(root->planner_cxt);
+				*index_info = palloc(sizeof(UniqueIndexInfo));
+				(*index_info)->index = ind;
+				(*index_info)->clauses = list_copy(matched_restrictlist);
+				MemoryContextSwitchTo(oldContext);
+			}
+
+			if (matched_restrictlist)
+				list_free(matched_restrictlist);
+
 			return true;
+		}
+
+		if (matched_restrictlist)
+			list_free(matched_restrictlist);
 	}
 
 	return false;
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index bad3dd1a736..e8bf85dcafb 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -189,7 +189,8 @@ add_paths_to_joinrel(PlannerInfo *root,
 													innerrel,
 													JOIN_INNER,
 													restrictlist,
-													false);
+													false,
+													NULL /*index_info*/);
 			break;
 		default:
 			extra.inner_unique = innerrel_is_unique(root,
@@ -198,7 +199,8 @@ add_paths_to_joinrel(PlannerInfo *root,
 													innerrel,
 													jointype,
 													restrictlist,
-													false);
+													false,
+													NULL /*index_info*/);
 			break;
 	}
 
diff --git a/src/backend/optimizer/path/joinrels.c b/src/backend/optimizer/path/joinrels.c
index 9da3ff2f9ab..b83520f509a 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1279,7 +1279,7 @@ mark_dummy_rel(RelOptInfo *rel)
 	/* Set up the dummy path */
 	add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
 											  NIL, rel->lateral_relids,
-											  0, false, -1));
+											  0, false, -1, false));
 
 	/* Set or update cheapest_total_path and related fields */
 	set_cheapest(rel);
diff --git a/src/backend/optimizer/path/pathkeys.c b/src/backend/optimizer/path/pathkeys.c
index 86a35cdef17..43bbe73eb3a 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -17,17 +17,24 @@
  */
 #include "postgres.h"
 
+#include <float.h>
+
+#include "miscadmin.h"
 #include "access/stratnum.h"
 #include "catalog/pg_opfamily.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "nodes/plannodes.h"
+#include "optimizer/cost.h"
 #include "optimizer/optimizer.h"
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "partitioning/partbounds.h"
 #include "utils/lsyscache.h"
+#include "utils/selfuncs.h"
 
+/* Consider reordering of GROUP BY keys? */
+bool		enable_group_by_reordering = true;
 
 static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
 static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
@@ -334,6 +341,527 @@ pathkeys_contained_in(List *keys1, List *keys2)
 	return false;
 }
 
+/*
+ * group_keys_reorder_by_pathkeys
+ *		Reorder GROUP BY keys to match pathkeys of input path.
+ *
+ * Function returns new lists (pathkeys and clauses), original GROUP BY lists
+ * stay untouched.
+ *
+ * Returns the number of GROUP BY keys with a matching pathkey.
+ */
+int
+group_keys_reorder_by_pathkeys(List *pathkeys, List **group_pathkeys,
+							   List **group_clauses)
+{
+	List	   *new_group_pathkeys = NIL,
+			   *new_group_clauses = NIL;
+	ListCell   *lc;
+	int			n;
+
+	if (pathkeys == NIL || *group_pathkeys == NIL)
+		return 0;
+
+	/*
+	 * Walk the pathkeys (determining ordering of the input path) and see if
+	 * there's a matching GROUP BY key. If we find one, we append it to the
+	 * list, and do the same for the clauses.
+	 *
+	 * Once we find the first pathkey without a matching GROUP BY key, the
+	 * rest of the pathkeys are useless and can't be used to evaluate the
+	 * grouping, so we abort the loop and ignore the remaining pathkeys.
+	 *
+	 * XXX Pathkeys are built in a way to allow simply comparing pointers.
+	 */
+	foreach(lc, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(lc);
+		SortGroupClause *sgc;
+
+		/* abort on first mismatch */
+		if (!list_member_ptr(*group_pathkeys, pathkey))
+			break;
+
+		new_group_pathkeys = lappend(new_group_pathkeys, pathkey);
+
+		sgc = get_sortgroupref_clause(pathkey->pk_eclass->ec_sortref,
+									  *group_clauses);
+
+		new_group_clauses = lappend(new_group_clauses, sgc);
+	}
+
+	/* remember the number of pathkeys with a matching GROUP BY key */
+	n = list_length(new_group_pathkeys);
+
+	/* append the remaining group pathkeys (will be treated as not sorted) */
+	*group_pathkeys = list_concat_unique_ptr(new_group_pathkeys,
+											 *group_pathkeys);
+	*group_clauses = list_concat_unique_ptr(new_group_clauses,
+											*group_clauses);
+
+	return n;
+}
+
+/*
+ * Used to generate all permutations of a pathkey list.
+ */
+typedef struct PathkeyMutatorState
+{
+	List	   *elemsList;
+	ListCell  **elemCells;
+	void	  **elems;
+	int		   *positions;
+	int			mutatorNColumns;
+	int			count;
+} PathkeyMutatorState;
+
+
+/*
+ * PathkeyMutatorInit
+ *		Initialize state of the permutation generator.
+ *
+ * We want to generate permutations of elements in the "elems" list. We may want
+ * to skip some number of elements at the beginning (when treating as presorted)
+ * or at the end (we only permute a limited number of group keys).
+ *
+ * The list is decomposed into elements, and we also keep pointers to individual
+ * cells. This allows us to build the permuted list quickly and cheaply, without
+ * creating any copies.
+ */
+static void
+PathkeyMutatorInit(PathkeyMutatorState *state, List *elems, int start, int end)
+{
+	int			i;
+	int			n = end - start;
+	ListCell   *lc;
+
+	memset(state, 0, sizeof(*state));
+
+	state->mutatorNColumns = n;
+
+	state->elemsList = list_copy(elems);
+
+	state->elems = palloc(sizeof(void *) * n);
+	state->elemCells = palloc(sizeof(ListCell *) * n);
+	state->positions = palloc(sizeof(int) * n);
+
+	i = 0;
+	for_each_cell(lc, state->elemsList, list_nth_cell(state->elemsList, start))
+	{
+		state->elemCells[i] = lc;
+		state->elems[i] = lfirst(lc);
+		state->positions[i] = i + 1;
+		i++;
+
+		if (i >= n)
+			break;
+	}
+}
+
+/* Swap two elements of an array. */
+static void
+PathkeyMutatorSwap(int *a, int i, int j)
+{
+	int			s = a[i];
+
+	a[i] = a[j];
+	a[j] = s;
+}
+
+/*
+ * Generate the next permutation of elements.
+ */
+static bool
+PathkeyMutatorNextSet(int *a, int n)
+{
+	int			j,
+				k,
+				l,
+				r;
+
+	j = n - 2;
+
+	while (j >= 0 && a[j] >= a[j + 1])
+		j--;
+
+	if (j < 0)
+		return false;
+
+	k = n - 1;
+
+	while (k >= 0 && a[j] >= a[k])
+		k--;
+
+	PathkeyMutatorSwap(a, j, k);
+
+	l = j + 1;
+	r = n - 1;
+
+	while (l < r)
+		PathkeyMutatorSwap(a, l++, r--);
+
+	return true;
+}
+
+/*
+ * PathkeyMutatorNext
+ *		Generate the next permutation of list of elements.
+ *
+ * Returns the next permutation (as a list of elements) or NIL if there are no
+ * more permutations.
+ */
+static List *
+PathkeyMutatorNext(PathkeyMutatorState *state)
+{
+	int			i;
+
+	state->count++;
+
+	/* first permutation is original list */
+	if (state->count == 1)
+		return state->elemsList;
+
+	/* when there are no more permutations, return NIL */
+	if (!PathkeyMutatorNextSet(state->positions, state->mutatorNColumns))
+	{
+		pfree(state->elems);
+		pfree(state->elemCells);
+		pfree(state->positions);
+
+		list_free(state->elemsList);
+
+		return NIL;
+	}
+
+	/* update the list cells to point to the right elements */
+	for (i = 0; i < state->mutatorNColumns; i++)
+		lfirst(state->elemCells[i]) =
+			(void *) state->elems[state->positions[i] - 1];
+
+	return state->elemsList;
+}
+
+/*
+ * Cost of comparing pathkeys.
+ */
+typedef struct PathkeySortCost
+{
+	Cost		cost;
+	PathKey    *pathkey;
+} PathkeySortCost;
+
+static int
+pathkey_sort_cost_comparator(const void *_a, const void *_b)
+{
+	const PathkeySortCost *a = (PathkeySortCost *) _a;
+	const PathkeySortCost *b = (PathkeySortCost *) _b;
+
+	if (a->cost < b->cost)
+		return -1;
+	else if (a->cost == b->cost)
+		return 0;
+	return 1;
+}
+
+/*
+ * get_cheapest_group_keys_order
+ *		Reorders the group pathkeys / clauses to minimize the comparison cost.
+ *
+ * Given the list of pathkeys in '*group_pathkeys', we try to arrange these
+ * in an order that minimizes the sort costs that will be incurred by the
+ * GROUP BY.  The costs mainly depend on the cost of the sort comparator
+ * function(s) and the number of distinct values in each column of the GROUP
+ * BY clause (*group_clauses).  Sorting on subsequent columns is only required
+ * for tiebreak situations where two values sort equally.
+ *
+ * In case the input is partially sorted, only the remaining pathkeys are
+ * considered.  'n_preordered' denotes how many of the leading *group_pathkeys
+ * the input is presorted by.
+ *
+ * Returns true and sets *group_pathkeys and *group_clauses to the newly
+ * ordered versions of the lists that were passed in via these parameters.
+ * If no reordering was deemed necessary then we return false, in which case
+ * the *group_pathkeys and *group_clauses lists are left untouched. The
+ * original *group_pathkeys and *group_clauses parameter values are never
+ * destructively modified in place.
+ */
+static bool
+get_cheapest_group_keys_order(PlannerInfo *root, double nrows,
+							  List **group_pathkeys, List **group_clauses,
+							  int n_preordered)
+{
+	List	   *new_group_pathkeys = NIL,
+			   *new_group_clauses = NIL,
+			   *var_group_pathkeys;
+
+	ListCell   *cell;
+	PathkeyMutatorState mstate;
+	double		cheapest_sort_cost = DBL_MAX;
+
+	int			nFreeKeys;
+	int			nToPermute;
+
+	/* If there are less than 2 unsorted pathkeys, we're done. */
+	if (list_length(*group_pathkeys) - n_preordered < 2)
+		return false;
+
+	/*
+	 * We could exhaustively cost all possible orderings of the pathkeys, but
+	 * for a large number of pathkeys it might be prohibitively expensive. So
+	 * we try to apply simple cheap heuristics first - we sort the pathkeys by
+	 * sort cost (as if the pathkey was sorted independently) and then check
+	 * only the four cheapest pathkeys. The remaining pathkeys are kept
+	 * ordered by cost.
+	 *
+	 * XXX This is a very simple heuristics, but likely to work fine for most
+	 * cases (because the number of GROUP BY clauses tends to be lower than
+	 * 4). But it ignores how the number of distinct values in each pathkey
+	 * affects the following steps. It might be better to use "more expensive"
+	 * pathkey first if it has many distinct values, because it then limits
+	 * the number of comparisons for the remaining pathkeys. But evaluating
+	 * that is likely quite the expensive.
+	 */
+	nFreeKeys = list_length(*group_pathkeys) - n_preordered;
+	nToPermute = 4;
+	if (nFreeKeys > nToPermute)
+	{
+		PathkeySortCost *costs = palloc(sizeof(PathkeySortCost) * nFreeKeys);
+		PathkeySortCost *cost = costs;
+
+		/*
+		 * Estimate cost for sorting individual pathkeys skipping the
+		 * pre-ordered pathkeys.
+		 */
+		for_each_from(cell, *group_pathkeys, n_preordered)
+		{
+			PathKey    *pathkey = (PathKey *) lfirst(cell);
+			List	   *to_cost = list_make1(pathkey);
+
+			cost->pathkey = pathkey;
+			cost->cost = cost_sort_estimate(root, to_cost, 0, nrows);
+			cost++;
+
+			list_free(to_cost);
+		}
+
+		/* sort the pathkeys by sort cost in ascending order */
+		qsort(costs, nFreeKeys, sizeof(*costs), pathkey_sort_cost_comparator);
+
+		/*
+		 * Rebuild the list of pathkeys - first the preordered ones, then the
+		 * rest ordered by cost.
+		 */
+		new_group_pathkeys = list_copy_head(*group_pathkeys, n_preordered);
+
+		for (int i = 0; i < nFreeKeys; i++)
+			new_group_pathkeys = lappend(new_group_pathkeys, costs[i].pathkey);
+
+		pfree(costs);
+	}
+	else
+	{
+		/* Copy the list, so that we can free the new list by list_free. */
+		new_group_pathkeys = list_copy(*group_pathkeys);
+		nToPermute = nFreeKeys;
+	}
+
+	Assert(list_length(new_group_pathkeys) == list_length(*group_pathkeys));
+
+	/*
+	 * Generate pathkey lists with permutations of the first nToPermute
+	 * pathkeys.
+	 *
+	 * XXX We simply calculate sort cost for each individual pathkey list, but
+	 * there's room for two dynamic programming optimizations here. Firstly,
+	 * we may pass the current "best" cost to cost_sort_estimate so that it
+	 * can "abort" if the estimated pathkeys list exceeds it. Secondly, it
+	 * could pass the return information about the position when it exceeded
+	 * the cost, and we could skip all permutations with the same prefix.
+	 *
+	 * Imagine we've already found ordering with cost C1, and we're evaluating
+	 * another ordering - cost_sort_estimate() calculates cost by adding the
+	 * pathkeys one by one (more or less), and the cost only grows. If at any
+	 * point it exceeds C1, it can't possibly be "better" so we can discard
+	 * it. But we also know that we can discard all ordering with the same
+	 * prefix, because if we're estimating (a,b,c,d) and we exceed C1 at (a,b)
+	 * then the same thing will happen for any ordering with this prefix.
+	 */
+	PathkeyMutatorInit(&mstate, new_group_pathkeys, n_preordered, n_preordered + nToPermute);
+
+	while ((var_group_pathkeys = PathkeyMutatorNext(&mstate)) != NIL)
+	{
+		Cost		cost;
+
+		cost = cost_sort_estimate(root, var_group_pathkeys, n_preordered, nrows);
+
+		if (cost < cheapest_sort_cost)
+		{
+			list_free(new_group_pathkeys);
+			new_group_pathkeys = list_copy(var_group_pathkeys);
+			cheapest_sort_cost = cost;
+		}
+	}
+
+	/* Reorder the group clauses according to the reordered pathkeys. */
+	foreach(cell, new_group_pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(cell);
+
+		new_group_clauses = lappend(new_group_clauses,
+									get_sortgroupref_clause(pathkey->pk_eclass->ec_sortref,
+															*group_clauses));
+	}
+
+	/* Just append the rest GROUP BY clauses */
+	new_group_clauses = list_concat_unique_ptr(new_group_clauses,
+											   *group_clauses);
+
+	*group_pathkeys = new_group_pathkeys;
+	*group_clauses = new_group_clauses;
+
+	return true;
+}
+
+/*
+ * get_useful_group_keys_orderings
+ *		Determine which orderings of GROUP BY keys are potentially interesting.
+ *
+ * Returns list of PathKeyInfo items, each representing an interesting ordering
+ * of GROUP BY keys. Each item stores pathkeys and clauses in matching order.
+ *
+ * The function considers (and keeps) multiple group by orderings:
+ *
+ * - the original ordering, as specified by the GROUP BY clause
+ *
+ * - GROUP BY keys reordered to minimize the sort cost
+ *
+ * - GROUP BY keys reordered to match path ordering (as much as possible), with
+ *   the tail reordered to minimize the sort cost
+ *
+ * - GROUP BY keys to match target ORDER BY clause (as much as possible), with
+ *   the tail reordered to minimize the sort cost
+ *
+ * There are other potentially interesting orderings (e.g. it might be best to
+ * match the first ORDER BY key, order the remaining keys differently and then
+ * rely on the incremental sort to fix this), but we ignore those for now. To
+ * make this work we'd have to pretty much generate all possible permutations.
+ */
+List *
+get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
+								List *path_pathkeys,
+								List *group_pathkeys, List *group_clauses)
+{
+	Query	   *parse = root->parse;
+	List	   *infos = NIL;
+	PathKeyInfo *info;
+	int			n_preordered = 0;
+
+	List	   *pathkeys = group_pathkeys;
+	List	   *clauses = group_clauses;
+
+	/* always return at least the original pathkeys/clauses */
+	info = makeNode(PathKeyInfo);
+	info->pathkeys = pathkeys;
+	info->clauses = clauses;
+
+	infos = lappend(infos, info);
+
+	/*
+	 * Should we try generating alternative orderings of the group keys? If
+	 * not, we produce only the order specified in the query, i.e. the
+	 * optimization is effectively disabled.
+	 */
+	if (!enable_group_by_reordering)
+		return infos;
+
+	/* for grouping sets we can't do any reordering */
+	if (parse->groupingSets)
+		return infos;
+
+	/*
+	 * Try reordering pathkeys to minimize the sort cost, ignoring both the
+	 * target ordering (ORDER BY) and ordering of the input path.
+	 */
+	if (get_cheapest_group_keys_order(root, nrows, &pathkeys, &clauses,
+									  n_preordered))
+	{
+		info = makeNode(PathKeyInfo);
+		info->pathkeys = pathkeys;
+		info->clauses = clauses;
+
+		infos = lappend(infos, info);
+	}
+
+	/*
+	 * If the path is sorted in some way, try reordering the group keys to
+	 * match as much of the ordering as possible - we get this sort for free
+	 * (mostly).
+	 *
+	 * We must not do this when there are no grouping sets, because those use
+	 * more complex logic to decide the ordering.
+	 *
+	 * XXX Isn't this somewhat redundant with presorted_keys? Actually, it's
+	 * more a complement, because it allows benefiting from incremental sort
+	 * as much as possible.
+	 *
+	 * XXX This does nothing if (n_preordered == 0). We shouldn't create the
+	 * info in this case.
+	 */
+	if (path_pathkeys)
+	{
+		n_preordered = group_keys_reorder_by_pathkeys(path_pathkeys,
+													  &pathkeys,
+													  &clauses);
+
+		/* reorder the tail to minimize sort cost */
+		get_cheapest_group_keys_order(root, nrows, &pathkeys, &clauses,
+									  n_preordered);
+
+		/*
+		 * reorder the tail to minimize sort cost
+		 *
+		 * XXX Ignore the return value - there may be nothing to reorder, in
+		 * which case get_cheapest_group_keys_order returns false. But we
+		 * still want to keep the keys reordered to path_pathkeys.
+		 */
+		info = makeNode(PathKeyInfo);
+		info->pathkeys = pathkeys;
+		info->clauses = clauses;
+
+		infos = lappend(infos, info);
+	}
+
+	/*
+	 * Try reordering pathkeys to minimize the sort cost (this time consider
+	 * the ORDER BY clause, but only if set debug_group_by_match_order_by).
+	 */
+	if (root->sort_pathkeys)
+	{
+		n_preordered = group_keys_reorder_by_pathkeys(root->sort_pathkeys,
+													  &pathkeys,
+													  &clauses);
+
+		/*
+		 * reorder the tail to minimize sort cost
+		 *
+		 * XXX Ignore the return value - there may be nothing to reorder, in
+		 * which case get_cheapest_group_keys_order returns false. But we
+		 * still want to keep the keys reordered to sort_pathkeys.
+		 */
+		get_cheapest_group_keys_order(root, nrows, &pathkeys, &clauses,
+									  n_preordered);
+
+		/* keep the group keys reordered to match ordering of input path */
+		info = makeNode(PathKeyInfo);
+		info->pathkeys = pathkeys;
+		info->clauses = clauses;
+
+		infos = lappend(infos, info);
+	}
+
+	return infos;
+}
+
 /*
  * pathkeys_count_contained_in
  *    Same as pathkeys_contained_in, but also sets length of longest
@@ -1845,7 +2373,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey)
  * ordering. Thus we return 0, if no valuable keys are found, or the number
  * of leading keys shared by the list and the requested ordering..
  */
-static int
+int
 pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
 {
 	int			n_common_pathkeys;
@@ -1862,6 +2390,54 @@ pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
 	return n_common_pathkeys;
 }
 
+/*
+ * pathkeys_useful_for_grouping
+ *		Count the number of pathkeys that are useful for grouping (instead of
+ *		explicit sort)
+ *
+ * Group pathkeys could be reordered to benefit from the ordering. The
+ * ordering may not be "complete" and may require incremental sort, but that's
+ * fine. So we simply count prefix pathkeys with a matching group key, and
+ * stop once we find the first pathkey without a match.
+ *
+ * So e.g. with pathkeys (a,b,c) and group keys (a,b,e) this determines (a,b)
+ * pathkeys are useful for grouping, and we might do incremental sort to get
+ * path ordered by (a,b,e).
+ *
+ * This logic is necessary to retain paths with ordering not matching grouping
+ * keys directly, without the reordering.
+ *
+ * Returns the length of pathkey prefix with matching group keys.
+ */
+static int
+pathkeys_useful_for_grouping(PlannerInfo *root, List *pathkeys)
+{
+	ListCell   *key;
+	int			n = 0;
+
+	/* no special ordering requested for grouping */
+	if (root->group_pathkeys == NIL)
+		return 0;
+
+	/* unordered path */
+	if (pathkeys == NIL)
+		return 0;
+
+	/* walk the pathkeys and search for matching group key */
+	foreach(key, pathkeys)
+	{
+		PathKey    *pathkey = (PathKey *) lfirst(key);
+
+		/* no matching group key, we're done */
+		if (!list_member_ptr(root->group_pathkeys, pathkey))
+			break;
+
+		n++;
+	}
+
+	return n;
+}
+
 /*
  * truncate_useless_pathkeys
  *		Shorten the given pathkey list to just the useful pathkeys.
@@ -1876,6 +2452,9 @@ truncate_useless_pathkeys(PlannerInfo *root,
 
 	nuseful = pathkeys_useful_for_merging(root, rel, pathkeys);
 	nuseful2 = pathkeys_useful_for_ordering(root, pathkeys);
+	if (nuseful2 > nuseful)
+		nuseful = nuseful2;
+	nuseful2 = pathkeys_useful_for_grouping(root, pathkeys);
 	if (nuseful2 > nuseful)
 		nuseful = nuseful2;
 
@@ -1911,6 +2490,8 @@ has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel)
 {
 	if (rel->joininfo != NIL || rel->has_eclass_joins)
 		return true;			/* might be able to use pathkeys for merging */
+	if (root->group_pathkeys != NIL)
+		return true;			/* might be able to use pathkeys for grouping */
 	if (root->query_pathkeys != NIL)
 		return true;			/* might be able to use them for ordering */
 	return false;				/* definitely useless */
diff --git a/src/backend/optimizer/plan/analyzejoins.c b/src/backend/optimizer/plan/analyzejoins.c
index 34efeee93f9..210d31cc992 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -22,6 +22,7 @@
  */
 #include "postgres.h"
 
+#include "catalog/pg_class.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
 #include "optimizer/joininfo.h"
@@ -29,8 +30,10 @@
 #include "optimizer/pathnode.h"
 #include "optimizer/paths.h"
 #include "optimizer/planmain.h"
+#include "optimizer/restrictinfo.h"
 #include "optimizer/tlist.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 
 /* local functions */
 static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo);
@@ -39,15 +42,17 @@ static void remove_rel_from_query(PlannerInfo *root, int relid,
 static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved);
 static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel);
 static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel,
-								List *clause_list);
+					List *clause_list, UniqueIndexInfo **info);
 static Oid	distinct_col_search(int colno, List *colnos, List *opids);
 static bool is_innerrel_unique_for(PlannerInfo *root,
 								   Relids joinrelids,
 								   Relids outerrelids,
 								   RelOptInfo *innerrel,
 								   JoinType jointype,
-								   List *restrictlist);
+					   List *restrictlist,
+					   UniqueIndexInfo **info);
 
+static void change_rinfo(RestrictInfo* rinfo, Index from, Index to);
 
 /*
  * remove_useless_joins
@@ -58,7 +63,7 @@ static bool is_innerrel_unique_for(PlannerInfo *root,
  * data structures that have to be updated are accessible via "root".
  */
 List *
-remove_useless_joins(PlannerInfo *root, List *joinlist)
+remove_useless_left_joins(PlannerInfo *root, List *joinlist)
 {
 	ListCell   *lc;
 
@@ -161,7 +166,6 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
 	int			innerrelid;
 	RelOptInfo *innerrel;
 	Relids		joinrelids;
-	List	   *clause_list = NIL;
 	ListCell   *l;
 	int			attroff;
 
@@ -245,67 +249,24 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
 	}
 
 	/*
-	 * Search for mergejoinable clauses that constrain the inner rel against
-	 * either the outer rel or a pseudoconstant.  If an operator is
-	 * mergejoinable then it behaves like equality for some btree opclass, so
-	 * it's what we want.  The mergejoinability test also eliminates clauses
-	 * containing volatile functions, which we couldn't depend on.
+	 * Check for pushed-down clauses referencing the inner rel. If there is
+	 * such a clause then join removal has to be disallowed.  We have to
+	 * check this despite the previous attr_needed checks because of the
+	 * possibility of pushed-down clauses referencing the rel.
 	 */
 	foreach(l, innerrel->joininfo)
 	{
 		RestrictInfo *restrictinfo = (RestrictInfo *) lfirst(l);
-
-		/*
-		 * If it's not a join clause for this outer join, we can't use it.
-		 * Note that if the clause is pushed-down, then it is logically from
-		 * above the outer join, even if it references no other rels (it might
-		 * be from WHERE, for example).
-		 */
-		if (RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids))
-		{
-			/*
-			 * If such a clause actually references the inner rel then join
-			 * removal has to be disallowed.  We have to check this despite
-			 * the previous attr_needed checks because of the possibility of
-			 * pushed-down clauses referencing the rel.
-			 */
-			if (bms_is_member(innerrelid, restrictinfo->clause_relids))
+		if (RINFO_IS_PUSHED_DOWN(restrictinfo, joinrelids)
+			&& bms_is_member(innerrel->relid, restrictinfo->clause_relids))
 				return false;
-			continue;			/* else, ignore; not useful here */
-		}
-
-		/* Ignore if it's not a mergejoinable clause */
-		if (!restrictinfo->can_join ||
-			restrictinfo->mergeopfamilies == NIL)
-			continue;			/* not mergejoinable */
-
-		/*
-		 * Check if clause has the form "outer op inner" or "inner op outer",
-		 * and if so mark which side is inner.
-		 */
-		if (!clause_sides_match_join(restrictinfo, sjinfo->min_lefthand,
-									 innerrel->relids))
-			continue;			/* no good for these input relations */
-
-		/* OK, add to list */
-		clause_list = lappend(clause_list, restrictinfo);
 	}
 
-	/*
-	 * Now that we have the relevant equality join clauses, try to prove the
-	 * innerrel distinct.
-	 */
-	if (rel_is_distinct_for(root, innerrel, clause_list))
-		return true;
-
-	/*
-	 * Some day it would be nice to check for other methods of establishing
-	 * distinctness.
-	 */
-	return false;
+	return is_innerrel_unique_for(root, joinrelids, sjinfo->min_lefthand,
+								  innerrel, sjinfo->jointype, innerrel->joininfo,
+								  NULL /*unique_index*/);
 }
 
-
 /*
  * Remove the target relid from the planner's data structures, having
  * determined that there is no need to include it in the query.
@@ -569,7 +530,7 @@ reduce_unique_semijoins(PlannerInfo *root)
 		/* Test whether the innerrel is unique for those clauses. */
 		if (!innerrel_is_unique(root,
 								joinrelids, sjinfo->min_lefthand, innerrel,
-								JOIN_SEMI, restrictlist, true))
+								JOIN_SEMI, restrictlist, true, NULL /*index_info*/))
 			continue;
 
 		/* OK, remove the SpecialJoinInfo from the list. */
@@ -643,9 +604,13 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel)
  * Note that the passed-in clause_list may be destructively modified!  This
  * is OK for current uses, because the clause_list is built by the caller for
  * the sole purpose of passing to this function.
+ *
+ * If unique_index is not null, it is set to point to the index that guarantees
+ * uniqueness for a base relation.
  */
 static bool
-rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list)
+rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list,
+					UniqueIndexInfo **index_info)
 {
 	/*
 	 * We could skip a couple of tests here if we assume all callers checked
@@ -661,8 +626,8 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list)
 		 * relation_has_unique_index_for automatically adds any usable
 		 * restriction clauses for the rel, so we needn't do that here.
 		 */
-		if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL))
-			return true;
+		return relation_has_unique_index_for(root, rel, clause_list, NIL, NIL,
+											 index_info);
 	}
 	else if (rel->rtekind == RTE_SUBQUERY)
 	{
@@ -966,6 +931,10 @@ distinct_col_search(int colno, List *colnos, List *opids)
  * heuristic about whether to cache negative answers; it should be "true"
  * if making an inquiry that is not part of the normal bottom-up join search
  * sequence.
+ *
+ * If index_info_out is not null, it is set to point to a new UniqueIndexInfo
+ * allocated in root memory context, that describes the index that guarantees
+ * uniqueness.
  */
 bool
 innerrel_is_unique(PlannerInfo *root,
@@ -974,12 +943,23 @@ innerrel_is_unique(PlannerInfo *root,
 				   RelOptInfo *innerrel,
 				   JoinType jointype,
 				   List *restrictlist,
-				   bool force_cache)
+				   bool force_cache,
+				   UniqueIndexInfo **index_info_out)
 {
 	MemoryContext old_context;
 	ListCell   *lc;
+	UniqueIndexInfo *index_info;
 
-	/* Certainly can't prove uniqueness when there are no joinclauses */
+	if (index_info_out)
+		*index_info_out = NULL;
+
+	/*
+	 * It is possible to prove uniqueness even in the absence of joinclauses,
+	 * just from baserestrictinfos alone. However, in these cases the inner
+	 * relation returns one row at most, so join removal won't give much
+	 * benefit. It seems better to save some planning time by ignoring these
+	 * cases.
+	 */
 	if (restrictlist == NIL)
 		return false;
 
@@ -999,10 +979,14 @@ innerrel_is_unique(PlannerInfo *root,
 	 */
 	foreach(lc, innerrel->unique_for_rels)
 	{
-		Relids		unique_for_rels = (Relids) lfirst(lc);
+		Relids		unique_for_rels = (Relids) linitial(lfirst(lc));
 
 		if (bms_is_subset(unique_for_rels, outerrelids))
+		{
+			if (index_info_out)
+				*index_info_out = lsecond(lfirst(lc));
 			return true;		/* Success! */
+		}
 	}
 
 	/*
@@ -1019,7 +1003,7 @@ innerrel_is_unique(PlannerInfo *root,
 
 	/* No cached information, so try to make the proof. */
 	if (is_innerrel_unique_for(root, joinrelids, outerrelids, innerrel,
-							   jointype, restrictlist))
+							   jointype, restrictlist, &index_info))
 	{
 		/*
 		 * Cache the positive result for future probes, being sure to keep it
@@ -1033,9 +1017,12 @@ innerrel_is_unique(PlannerInfo *root,
 		 */
 		old_context = MemoryContextSwitchTo(root->planner_cxt);
 		innerrel->unique_for_rels = lappend(innerrel->unique_for_rels,
-											bms_copy(outerrelids));
+							list_make2(bms_copy(outerrelids), index_info));
 		MemoryContextSwitchTo(old_context);
 
+		if (index_info_out)
+			*index_info_out = index_info;
+
 		return true;			/* Success! */
 	}
 	else
@@ -1081,7 +1068,8 @@ is_innerrel_unique_for(PlannerInfo *root,
 					   Relids outerrelids,
 					   RelOptInfo *innerrel,
 					   JoinType jointype,
-					   List *restrictlist)
+					   List *restrictlist,
+					   UniqueIndexInfo **index_info)
 {
 	List	   *clause_list = NIL;
 	ListCell   *lc;
@@ -1123,5 +1111,1056 @@ is_innerrel_unique_for(PlannerInfo *root,
 	}
 
 	/* Let rel_is_distinct_for() do the hard work */
-	return rel_is_distinct_for(root, innerrel, clause_list);
+	return rel_is_distinct_for(root, innerrel, clause_list, index_info);
+}
+
+typedef struct
+{
+	Index oldRelid;
+	Index newRelid;
+} ChangeVarnoContext;
+
+static bool
+change_varno_walker(Node *node, ChangeVarnoContext *context)
+{
+	if (node == NULL)
+		return false;
+
+	if (IsA(node, Var) && ((Var *) node)->varno == context->oldRelid)
+	{
+		((Var *) node)->varno = context->newRelid;
+		((Var *) node)->varnosyn = context->newRelid;
+		return false;
+	}
+	if (IsA(node, RestrictInfo))
+	{
+		change_rinfo((RestrictInfo*)node, context->oldRelid, context->newRelid); 
+		return false;
+	}
+
+	return expression_tree_walker(node, change_varno_walker, context);
+}
+
+/*
+ * For all Vars in the expression that have varno = oldRelid, set
+ * varno = newRelid.
+ */
+static void
+change_varno(Expr *expr, Index oldRelid, Index newRelid)
+{
+	ChangeVarnoContext context;
+	context.oldRelid = oldRelid;
+	context.newRelid = newRelid;
+	change_varno_walker((Node *) expr, &context);
+}
+
+/*
+ * Substitute newId for oldId in relids.
+ */
+static void
+change_relid(Relids *relids, Index oldId, Index newId)
+{
+	if (bms_is_member(oldId, *relids))
+		*relids = bms_add_member(bms_del_member(bms_copy(*relids), oldId), newId);
+}
+
+/*
+ * Substitute newId for oldId in field of RestrictInfo
+ */
+static void
+change_rinfo(RestrictInfo* rinfo, Index from, Index to)
+{
+	bool is_req_equal =
+		(rinfo->required_relids == rinfo->clause_relids) ? true : false;
+
+	/*
+	* At the required_relids initialization stage we do not create independent
+	* bms structure. The field is a link to clause_relids. During the call
+	* of change_relid() routine clause_relids can change memory location.
+	*/
+	change_relid(&rinfo->clause_relids, from, to);
+	if (is_req_equal)
+		rinfo->required_relids = rinfo->clause_relids;
+	else
+		change_relid(&rinfo->required_relids, from, to);
+
+	change_varno(rinfo->clause, from, to);
+	change_varno(rinfo->orclause, from, to);
+	change_relid(&rinfo->left_relids, from, to);
+	change_relid(&rinfo->right_relids, from, to);
+	change_relid(&rinfo->outer_relids, from, to);
+	change_relid(&rinfo->nullable_relids, from, to);
+}
+
+/*
+ * Update EC members to point to the remaining relation instead of the removed
+ * one, removing duplicates.
+ */
+static void
+update_ec_members(EquivalenceClass *ec, Index toRemove, Index toKeep)
+{
+	ListCell *cell = NULL;
+	ListCell *next;
+
+restart:
+	next = list_head(ec->ec_members);
+
+	while (next)
+	{
+		EquivalenceMember *em;
+		ListCell *otherCell;
+
+		cell = next;
+		next = lnext(ec->ec_members, next);
+
+		em = lfirst(cell);
+
+		if (!bms_is_member(toRemove, em->em_relids))
+			continue;
+
+		change_relid(&em->em_relids, toRemove, toKeep);
+		/* We only process inner joins */
+//		Assert(bms_is_empty(em->em_nullable_relids));
+		change_varno(em->em_expr, toRemove, toKeep);
+
+		/*
+		 * After we switched the equivalence member to the remaining relation,
+		 * check that it is not the same as the existing member, and if it
+		 * is, delete it.
+		 */
+		foreach (otherCell, ec->ec_members)
+		{
+			EquivalenceMember *other;
+
+			if (otherCell == cell)
+				continue;
+
+			other = castNode(EquivalenceMember, lfirst(otherCell));
+			if (equal(other->em_expr, em->em_expr))
+			{
+				ec->ec_members = list_delete_cell(ec->ec_members, cell);
+				goto restart;
+			}
+		}
+	}
+}
+
+/*
+ * Update EC sources to point to the remaining relation instead of the
+ * removed one.
+ */
+static void
+update_ec_sources(List **sources, Index toRemove, Index toKeep)
+{
+	ListCell *cell = NULL;
+	ListCell *next;
+
+restart:
+	next = list_head(*sources);
+
+	while (next)
+	{
+		RestrictInfo *rinfo;
+		ListCell *otherCell;
+
+		cell = next;
+		next = lnext(*sources, next);
+
+		rinfo = castNode(RestrictInfo, lfirst(cell));
+
+		if (!bms_is_member(toRemove, rinfo->required_relids))
+			continue;
+
+		change_varno(rinfo->clause, toRemove, toKeep);
+
+		/*
+		 * After switching the clause to the remaining relation, check it
+		 * for redundancy with existing ones. We don't have to check for
+		 * redundancy with derived clauses, because we've just deleted them.
+		 */
+		foreach (otherCell, *sources)
+		{
+			RestrictInfo *other;
+
+			if (otherCell == cell)
+				continue;
+
+			other = castNode(RestrictInfo, lfirst(otherCell));
+
+			if (equal(rinfo->clause, other->clause))
+			{
+				*sources = list_delete_cell(*sources, cell);
+				cell = NULL;
+				break;
+			}
+		}
+
+		if (otherCell == NULL)
+		{
+			/* We will keep this RestrictInfo, correct its relids. */
+			change_relid(&rinfo->required_relids, toRemove, toKeep);
+			change_relid(&rinfo->left_relids, toRemove, toKeep);
+			change_relid(&rinfo->right_relids, toRemove, toKeep);
+			change_relid(&rinfo->clause_relids, toRemove, toKeep);
+		}
+
+		if (cell == NULL)
+			goto restart;
+	}
+}
+
+/*
+ * Scratch space for the unique self join removal code.
+ */
+typedef struct
+{
+	PlannerInfo *root;
+
+	/* Temporary array for relation ids. */
+	Index *relids;
+
+	/*
+	 * Array of Relids, one for each relation, indexed by relation id.
+	 * Each element is a set of relation ids with which this relation
+	 * has a special join.
+	 */
+	Relids *special_join_rels;
+
+	/* Array of row marks indexed by relid. */
+	PlanRowMark **row_marks;
+
+	/* Bitmapset for join relids that is used to avoid reallocation. */
+	Relids joinrelids;
+
+	/*
+	 * Top-level targetlist of the query. We have to update any references
+	 * it has to the relations we remove.
+	 */
+	 List *targetlist;
+} UsjScratch;
+
+
+/*
+ * Remove a relation after we have proven that it participates only in an
+ * unneeded unique self join.
+ *
+ * The joinclauses list is destructively changed.
+ */
+static void
+remove_self_join_rel(UsjScratch *scratch, Relids joinrelids, List *joinclauses,
+					 RelOptInfo *toKeep, RelOptInfo *toRemove)
+{
+	PlannerInfo *root = scratch->root;
+	ListCell *cell;
+	int i;
+
+	/* Merge EC indexes */
+	toKeep->eclass_indexes = bms_add_members(toRemove->eclass_indexes,
+											 toKeep->eclass_indexes);
+
+	/*
+	 * Transfer join and restriction clauses from the removed relation to the
+	 * remaining one. We change the Vars of the clause to point to the remaining
+	 * relation instead of the removed one. The clauses that require a subset of
+	 * joinrelids become restriction clauses of the remaining relation, and
+	 * others remain join clauses. We append them to baserestrictinfo and
+	 * joininfo respectively, trying not to introduce duplicates.
+	 *
+	 * We also have to process the 'joinclauses' list here, because it contains
+	 * EC-derived join clauses which must become filter clauses. It is not enough
+	 * to just correct the ECs, because the EC-derived restrictions are generated
+	 * before join removal (see generate_base_implied_equalities).
+	 */
+	/* Replace removed relation in joininfo */
+	foreach(cell, toRemove->joininfo)
+	{
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell);
+
+		change_rinfo(rinfo, toRemove->relid, toKeep->relid);
+
+		/*
+		 * If this clause is a mergejoinable equality clause that compares a
+		 * variable to itself, i.e., has the form of "X=X", replace it with
+		 * null test.
+		 */
+		if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr))
+		{
+			Expr *leftOp, *rightOp;
+
+			leftOp = (Expr *) get_leftop(rinfo->clause);
+			rightOp = (Expr *) get_rightop(rinfo->clause);
+
+			if (leftOp != NULL && equal(leftOp, rightOp))
+			{
+				NullTest *nullTest = makeNode(NullTest);
+				nullTest->arg = leftOp;
+				nullTest->nulltesttype = IS_NOT_NULL;
+				nullTest->argisrow = false;
+				nullTest->location = -1;
+				rinfo->clause = (Expr*)nullTest;
+			}
+		}
+	}
+
+	foreach(cell, joinclauses)
+	{
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell);
+
+		change_rinfo(rinfo, toRemove->relid, toKeep->relid);
+
+		/*
+		 * If this clause is a mergejoinable equality clause that compares a
+		 * variable to itself, i.e., has the form of "X=X", replace it with
+		 * null test.
+		 */
+		if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr))
+		{
+			Expr *leftOp, *rightOp;
+
+			leftOp = (Expr *) get_leftop(rinfo->clause);
+			rightOp = (Expr *) get_rightop(rinfo->clause);
+
+			if (leftOp != NULL && equal(leftOp, rightOp))
+			{
+				NullTest *nullTest = makeNode(NullTest);
+				nullTest->arg = leftOp;
+				nullTest->nulltesttype = IS_NOT_NULL;
+				nullTest->argisrow = false;
+				nullTest->location = -1;
+				rinfo->clause = (Expr*)nullTest;
+				toKeep->baserestrictinfo = lappend(toKeep->baserestrictinfo, rinfo);
+				//toKeep->joininfo = lappend(toKeep->joininfo, rinfo);
+			}
+		}
+	}
+
+
+	/* Tranfer removed relations clauses to kept relation */
+	foreach(cell, toRemove->baserestrictinfo)
+	{
+		ListCell *otherCell;
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell);
+		List **target = &toKeep->baserestrictinfo;
+
+		/*
+		 * Replace the references to the removed relation with references to
+		 * the remaining one. We won't necessarily add this clause, because
+		 * it may be already present in the joininfo or baserestrictinfo.
+		 * Still, we have to switch it to point to the remaining relation.
+		 * This is important for join clauses that reference both relations,
+		 * because they are included in both joininfos.
+		 */
+		change_rinfo(rinfo, toRemove->relid, toKeep->relid);
+
+		/*
+		 * Don't add the clause if it is already present in the list, or
+		 * derived from the same equivalence class, or is the same as another
+		 * clause.
+		 */
+		foreach (otherCell, *target)
+		{
+			RestrictInfo *other = lfirst(otherCell);
+			if (other == rinfo
+				|| (rinfo->parent_ec != NULL
+					&& other->parent_ec == rinfo->parent_ec)
+				|| equal(rinfo->clause, other->clause))
+			{
+				break;
+			}
+		}
+
+		if (otherCell != NULL)
+			continue;
+
+		*target = lappend(*target, rinfo);
+	}
+
+	/* Tranfer removed relations clauses to kept relation */
+	foreach(cell, toRemove->joininfo)
+	{
+		ListCell *otherCell;
+		RestrictInfo *rinfo = lfirst_node(RestrictInfo, cell);
+		List **target = &toKeep->joininfo;
+
+		/*
+		 * Replace the references to the removed relation with references to
+		 * the remaining one. We won't necessarily add this clause, because
+		 * it may be already present in the joininfo or baserestrictinfo.
+		 * Still, we have to switch it to point to the remaining relation.
+		 * This is important for join clauses that reference both relations,
+		 * because they are included in both joininfos.
+		 */
+		change_varno(rinfo->clause, toRemove->relid, toKeep->relid);
+		change_relid(&rinfo->required_relids, toRemove->relid, toKeep->relid);
+		change_relid(&rinfo->left_relids, toRemove->relid, toKeep->relid);
+		change_relid(&rinfo->right_relids, toRemove->relid, toKeep->relid);
+		change_relid(&rinfo->clause_relids, toRemove->relid, toKeep->relid);
+
+		/*
+		 * Don't add the clause if it is already present in the list, or
+		 * derived from the same equivalence class, or is the same as another
+		 * clause.
+		 */
+		foreach (otherCell, *target)
+		{
+			RestrictInfo *other = lfirst(otherCell);
+			if (other == rinfo
+				|| (rinfo->parent_ec != NULL
+					&& other->parent_ec == rinfo->parent_ec)
+				|| equal(rinfo->clause, other->clause))
+			{
+				break;
+			}
+		}
+
+		if (otherCell != NULL)
+			continue;
+
+		/*
+		 * If this clause is a mergejoinable equality clause that compares a
+		 * variable to itself, i.e., has the form of "X=X", replace it with
+		 * null test.
+		 */
+		if (rinfo->mergeopfamilies && IsA(rinfo->clause, OpExpr))
+		{
+			Expr *leftOp, *rightOp;
+
+			Assert(IsA(rinfo->clause, OpExpr));
+
+			leftOp = (Expr *) get_leftop(rinfo->clause);
+			rightOp = (Expr *) get_rightop(rinfo->clause);
+
+			if (leftOp != NULL && equal(leftOp, rightOp))
+			{
+				NullTest *nullTest = makeNode(NullTest);
+				nullTest->arg = leftOp;
+				nullTest->nulltesttype = IS_NOT_NULL;
+				nullTest->argisrow = false;
+				nullTest->location = -1;
+				rinfo->clause = (Expr*)nullTest;
+			}
+		}
+
+		*target = lappend(*target, rinfo);
+	}
+
+	/*
+	 * Transfer the targetlist and attr_needed flags.
+	 */
+	Assert(toRemove->reltarget->sortgrouprefs == 0);
+
+	foreach (cell, toRemove->reltarget->exprs)
+	{
+		Expr *node = lfirst(cell);
+		change_varno(node, toRemove->relid, toKeep->relid);
+		if (!list_member(toKeep->reltarget->exprs, node))
+			toKeep->reltarget->exprs = lappend(toKeep->reltarget->exprs,
+											   node);
+	}
+
+	for (i = toKeep->min_attr; i <= toKeep->max_attr; i++)
+	{
+		int attno = i - toKeep->min_attr;
+		toKeep->attr_needed[attno] = bms_add_members(toKeep->attr_needed[attno],
+													 toRemove->attr_needed[attno]);
+	}
+
+	/*
+	 * If the removed relation has a row mark, transfer it to the remaining one.
+	 *
+	 * If both rels have row marks, just keep the one corresponding to the
+	 * remaining relation, because we verified earlier that they have the same
+	 * strength.
+	 *
+	 * Also make sure that the scratch->row_marks cache is up to date, because
+	 * we are going to use it for further join removals.
+	 */
+	if (scratch->row_marks[toRemove->relid])
+	{
+		PlanRowMark **markToRemove = &scratch->row_marks[toRemove->relid];
+		PlanRowMark **markToKeep = &scratch->row_marks[toKeep->relid];
+
+		if (*markToKeep)
+		{
+			Assert((*markToKeep)->markType == (*markToRemove)->markType);
+
+			root->rowMarks = list_delete_ptr(root->rowMarks, *markToKeep);
+			*markToKeep = NULL;
+		}
+		else
+		{
+			*markToKeep = *markToRemove;
+			*markToRemove = NULL;
+
+			/* Shouldn't have inheritance children here. */
+			Assert((*markToKeep)->rti == (*markToKeep)->prti);
+
+			(*markToKeep)->rti = toKeep->relid;
+			(*markToKeep)->prti = toKeep->relid;
+		}
+	}
+
+	/*
+	 * Likewise replace references in SpecialJoinInfo data structures.
+	 *
+	 * This is relevant in case the join we're deleting is nested inside
+	 * some special joins: the upper joins' relid sets have to be adjusted.
+	 */
+	foreach (cell, root->join_info_list)
+	{
+		SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) lfirst(cell);
+
+		change_relid(&sjinfo->min_lefthand, toRemove->relid, toKeep->relid);
+		change_relid(&sjinfo->min_righthand, toRemove->relid, toKeep->relid);
+		change_relid(&sjinfo->syn_lefthand, toRemove->relid, toKeep->relid);
+		change_relid(&sjinfo->syn_righthand, toRemove->relid, toKeep->relid);
+		change_varno((Expr*)sjinfo->semi_rhs_exprs, toRemove->relid, toKeep->relid);
+	}
+
+	/*
+	 * Likewise update references in PlaceHolderVar data structures.
+	 */
+	foreach(cell, root->placeholder_list)
+	{
+		PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(cell);
+
+		/*
+		 * We are at an inner join of two base relations. A placeholder
+		 * can't be needed here or evaluated here.
+		 */
+		Assert(!bms_is_subset(phinfo->ph_eval_at, joinrelids));
+		Assert(!bms_is_subset(phinfo->ph_needed, joinrelids));
+
+		change_relid(&phinfo->ph_eval_at, toRemove->relid, toKeep->relid);
+		change_relid(&phinfo->ph_needed, toRemove->relid, toKeep->relid);
+		change_relid(&phinfo->ph_lateral, toRemove->relid, toKeep->relid);
+		change_relid(&phinfo->ph_var->phrels, toRemove->relid, toKeep->relid);
+		change_varno((Expr*)phinfo->ph_var, toRemove->relid, toKeep->relid);
+	}
+
+	/*
+	 * Update the equivalence classes that reference the removed relations.
+	 */
+	foreach(cell, root->eq_classes)
+	{
+		EquivalenceClass *ec = lfirst(cell);
+
+		if (!bms_is_member(toRemove->relid, ec->ec_relids))
+		{
+			/*
+			 * This EC doesn't reference the removed relation,
+			 * nothing to be done for it.
+			 */
+			continue;
+		}
+
+		/*
+		 * Update the EC members to reference the remaining relation
+		 * instead of the removed one.
+		 */
+		update_ec_members(ec, toRemove->relid, toKeep->relid);
+		change_relid(&ec->ec_relids, toRemove->relid, toKeep->relid);
+
+		/*
+		 * We will now update source and derived clauses of the EC.
+		 *
+		 * Restriction clauses for base relations are already distributed
+		 * to the respective baserestrictinfo lists (see
+		 * generate_implied_equalities). The above code has already
+		 * processed this list, and updated these clauses to reference
+		 * the remaining relation, so we can skip them here based on their
+		 * relids.
+		 *
+		 * Likewise, we have already processed the join clauses that join
+		 * the removed relation to the remaining one.
+		 *
+		 * Finally, there are join clauses that join the removed relation
+		 * to some third relation. We could delete just delete them and
+		 * generate on demand, but sometimes we can't do this because there
+		 * is no suitable equality operator (see the handling of ec_broken).
+		 * In such cases we are going to use the source clauses, so we have
+		 * to correct them too.
+		 *
+		 * Derived clauses can be generated again, so it is simpler to just
+		 * delete them.
+		 */
+		list_free(ec->ec_derives);
+		ec->ec_derives = NIL;
+		update_ec_sources(&ec->ec_sources, toRemove->relid, toKeep->relid);
+	}
+
+	/*
+	 * Mark the rel as "dead" to show it is no longer part of the join tree.
+	 * (Removing it from the baserel array altogether seems too risky.)
+	 */
+	toRemove->reloptkind = RELOPT_DEADREL;
+
+	/*
+	 * Update references to the removed relation from other baserels.
+	 */
+	for (i = 1; i < root->simple_rel_array_size; i++)
+	{
+		RelOptInfo *otherrel = root->simple_rel_array[i];
+		int			attroff;
+
+		/* no point in processing target rel itself */
+		if (i == toRemove->relid)
+			continue;
+
+		/* there may be empty slots corresponding to non-baserel RTEs */
+		if (otherrel == NULL)
+			continue;
+
+		Assert(otherrel->relid == i); /* sanity check on array */
+
+		/* Update attr_needed arrays. */
+		for (attroff = otherrel->max_attr - otherrel->min_attr;
+			 attroff >= 0; attroff--)
+		{
+			change_relid(&otherrel->attr_needed[attroff], toRemove->relid,
+						 toKeep->relid);
+		}
+
+		/* Update lateral references. */
+		change_varno((Expr*)otherrel->lateral_vars, toRemove->relid, toKeep->relid);
+	}
+
+	/*
+	 * Change varno in some special cases with non-trivial RangeTblEntry
+	 */
+	foreach(cell, root->parse->rtable)
+	{
+		RangeTblEntry *rte	= lfirst(cell);
+
+		switch(rte->rtekind)
+		{
+			case RTE_FUNCTION:
+				change_varno((Expr*)rte->functions, toRemove->relid, toKeep->relid);
+				break;
+			case RTE_TABLEFUNC:
+				change_varno((Expr*)rte->tablefunc, toRemove->relid, toKeep->relid);
+				break;
+			case RTE_VALUES:
+				change_varno((Expr*)rte->values_lists, toRemove->relid, toKeep->relid);
+				break;
+			default:
+				/* no op */
+				break;
+		}
+	}
+}
+
+/*
+ * Test whether the relations are joined on the same unique column.
+ */
+static bool
+is_unique_self_join(PlannerInfo *root, Relids joinrelids, RelOptInfo *outer,
+					RelOptInfo *inner, List *restrictlist)
+{
+	UniqueIndexInfo *outeridx = NULL;
+	UniqueIndexInfo *inneridx = NULL;
+	ListCell *outerCell, *innerCell;
+
+	innerrel_is_unique(root, joinrelids, inner->relids,
+					   outer, JOIN_INNER, list_concat(list_concat_unique(list_copy(outer->joininfo), restrictlist),
+													  outer->baserestrictinfo), true, &outeridx);
+	if (!outeridx)
+		return false;
+
+	innerrel_is_unique(root, joinrelids, outer->relids,
+					   inner, JOIN_INNER, list_concat(list_concat_unique(list_copy(inner->joininfo), restrictlist),
+													  inner->baserestrictinfo), true, &inneridx);
+	if (!inneridx)
+		return false;
+
+	/* We must have the same unique index for both relations. */
+	if (outeridx->index->indexoid != inneridx->index->indexoid)
+		return false;
+
+	if (restrictlist != NULL && IsA(((RestrictInfo*)linitial(restrictlist))->clause, Const))
+		return false;
+
+	/*
+	 * The clauses that make a relation unique must be the same for both
+	 * relations, or else we won't match the same row on each side of join.
+	 *
+	 * The lists of matching clauses are ordered as the index columns, so we
+	 * just compare the list elements one by one. The varnos are different,
+	 * so we copy the clauses and replace all mentions of outer varno with the
+	 * inner, so that we can use equal().
+	 */
+	forboth(innerCell, inneridx->clauses, outerCell, outeridx->clauses)
+	{
+		Expr *innerExpr = copyObject(castNode(RestrictInfo, lfirst(innerCell))->clause);
+		Expr *outerExpr = copyObject(castNode(RestrictInfo, lfirst(outerCell))->clause);
+		change_varno(outerExpr, outer->relid, inner->relid);
+		change_varno(innerExpr, outer->relid, inner->relid);
+		if (!equal(outerExpr, innerExpr))
+		{
+			pfree(outerExpr);
+			pfree(innerExpr);
+			return false;
+		}
+		pfree(outerExpr);
+		pfree(innerExpr);
+	}
+
+	return true;
+}
+
+static bool
+check_selfjoin(List* join_info_list, Index *relids, int i, int j)
+{
+	ListCell	*cell;
+
+	foreach(cell, join_info_list)
+	{
+		SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(cell);
+
+		if (bms_is_member(relids[i], info->syn_lefthand) &&
+			!bms_is_member(relids[j], info->syn_lefthand))
+			return true;
+
+		if (bms_is_member(relids[i], info->syn_righthand) &&
+			!bms_is_member(relids[j], info->syn_righthand))
+			return true;
+
+		if (bms_is_member(relids[j], info->syn_lefthand) &&
+			!bms_is_member(relids[i], info->syn_lefthand))
+			return true;
+
+		if (bms_is_member(relids[j], info->syn_righthand) &&
+			!bms_is_member(relids[i], info->syn_righthand))
+			return true;
+	}
+
+	return false;
+}
+
+/*
+ * Find and remove unique self joins in a group of base relations that have
+ * the same Oid.
+ *
+ * Returns a set of relids that were removed.
+ */
+static Relids
+remove_self_joins_one_group(UsjScratch *scratch, Index *relids, int n)
+{
+	PlannerInfo *root = scratch->root;
+	Relids joinrelids = scratch->joinrelids;
+	Relids result = NULL;
+	int i, o;
+
+	if (n < 2)
+		return NULL;
+
+	for (o = 0; o < n; o++)
+	{
+		RelOptInfo *outer = root->simple_rel_array[relids[o]];
+
+		if (outer->reloptkind == RELOPT_DEADREL)
+			continue;
+
+		for (i = o + 1; i < n; i++)
+		{
+			RelOptInfo *inner = root->simple_rel_array[relids[i]];
+			List	   *restrictlist;
+			ListCell   *cell;
+
+			/* A sanity check: the relations have the same Oid. */
+			Assert(root->simple_rte_array[relids[i]]->relid
+					== root->simple_rte_array[relids[o]]->relid);
+
+			if (inner->reloptkind == RELOPT_DEADREL)
+				continue;
+
+			/*
+			 * This optimization applies to inner joins only, so skip any relations
+			 * that form a special join.
+			 */
+			if (bms_is_member(relids[i], scratch->special_join_rels[relids[o]]))
+				continue;
+
+			if (check_selfjoin(root->join_info_list, relids, i, o))
+				continue;
+
+			/* Reuse joinrelids bitset to avoid reallocation. */
+			joinrelids = bms_del_members(joinrelids, joinrelids);
+
+			/*
+			 * We only deal with base rels here, so their relids bitset
+			 * contains only one member -- their relid.
+			 */
+			joinrelids = bms_add_member(joinrelids, relids[o]);
+			joinrelids = bms_add_member(joinrelids, relids[i]);
+
+			/* Is it a unique self join? */
+			restrictlist = build_joinrel_restrictlist(root, joinrelids, outer, inner);
+			if (!is_unique_self_join(root, joinrelids, outer, inner,
+									 restrictlist))
+				continue;
+
+			/*
+			 * We can't remove the join if the relations have row marks of
+			 * different strength (e.g. one is locked FOR UPDATE and another
+			 * just has ROW_MARK_REFERENCE for EvalPlanQual rechecking).
+			 */
+			if (scratch->row_marks[relids[i]] && scratch->row_marks[relids[o]]
+				&& scratch->row_marks[relids[i]]->markType
+					!= scratch->row_marks[relids[o]]->markType)
+			{
+				continue;
+			}
+
+			foreach(cell, root->placeholder_list)
+			{
+				PlaceHolderInfo *phinfo = (PlaceHolderInfo *) lfirst(cell);
+
+				/* there isn't any other place to eval PHV */
+				if (bms_is_subset(phinfo->ph_eval_at, joinrelids) ||
+					bms_is_subset(phinfo->ph_needed, joinrelids))
+				break;
+			}
+
+			if (cell)
+				continue;
+
+			/*
+			 * We can remove either relation, so remove the outer one,
+			 * to simplify this loop.
+			 */
+			remove_self_join_rel(scratch, joinrelids, restrictlist, inner, outer);
+			result = bms_add_member(result, relids[o]);
+
+			/*
+			 * Replace varno in root targetlist and HAVING clause.
+			 */
+			change_varno((Expr *) scratch->targetlist, relids[o], relids[i]);
+			change_varno((Expr *) root->parse->havingQual, relids[o], relids[i]);
+
+			/* We removed the outer relation, try the next one. */
+			break;
+		}
+	}
+
+	scratch->joinrelids = joinrelids;
+	return result;
+}
+
+/*
+ * A qsort comparator to sort the relids by the relation Oid.
+ */
+static int
+compare_rte(const Index *left, const Index *right, PlannerInfo *root)
+{
+	Oid l = root->simple_rte_array[*left]->relid;
+	Oid r = root->simple_rte_array[*right]->relid;
+	return l < r ? 1 : ( l == r ? 0 : -1 );
+}
+
+/*
+ * Find and remove unique self joins on a particular level of the join tree.
+ *
+ * We sort the relations by Oid and then examine each group with the same Oid.
+ * If we removed any relation, remove it from joinlist as well.
+ */
+static void
+remove_self_joins_one_level(UsjScratch *scratch, List *joinlist,
+							Relids *relidsToRemove)
+{
+	ListCell *cell;
+	Oid groupOid;
+	int groupStart;
+	int i;
+	int n = 0;
+	Index *relid_ascending = scratch->relids;
+	PlannerInfo *root = scratch->root;
+
+	/*
+	 * Collect the ids of base relations at this level of the join tree.
+	 */
+	foreach (cell, joinlist)
+	{
+		RangeTblEntry *rte;
+		RelOptInfo *rel;
+		RangeTblRef *ref = (RangeTblRef *) lfirst(cell);
+		if (!IsA(ref, RangeTblRef))
+			continue;
+
+		rte = root->simple_rte_array[ref->rtindex];
+		rel = root->simple_rel_array[ref->rtindex];
+
+		/* We only care about base relations from which we select something. */
+		if (rte->rtekind != RTE_RELATION || rte->relkind != RELKIND_RELATION
+			|| rel == NULL)
+		{
+			continue;
+		}
+
+		/* This optimization won't work for tables that have inheritance children. */
+		if (rte->inh)
+			continue;
+
+		relid_ascending[n++] = ref->rtindex;
+
+		/* Limit the number of joins we process to control the quadratic behavior. */
+		if (n > join_collapse_limit)
+			break;
+	}
+
+	if (n < 2)
+		return;
+
+	/*
+	 * Find and process the groups of relations that have same Oid.
+	 */
+	qsort_arg(relid_ascending, n, sizeof(*relid_ascending),
+			  (qsort_arg_comparator) compare_rte, root);
+	groupOid = root->simple_rte_array[relid_ascending[0]]->relid;
+	groupStart = 0;
+	for (i = 1; i < n; i++)
+	{
+		RangeTblEntry *rte = root->simple_rte_array[relid_ascending[i]];
+		Assert(rte->relid != InvalidOid);
+		if (rte->relid != groupOid)
+		{
+			*relidsToRemove = bms_add_members(*relidsToRemove,
+				remove_self_joins_one_group(scratch, &relid_ascending[groupStart],
+					i - groupStart));
+			groupOid = rte->relid;
+			groupStart = i;
+		}
+	}
+	Assert(groupOid != InvalidOid);
+	Assert(groupStart < n);
+	*relidsToRemove = bms_add_members(*relidsToRemove,
+		remove_self_joins_one_group(scratch, &relid_ascending[groupStart],
+			n - groupStart));
+
+	return;
+}
+
+/*
+ * Find and remove unique self joins on a single level of a join tree, and
+ * recurse to handle deeper levels.
+ */
+static void
+remove_self_joins_recurse(UsjScratch *scratch, List *joinlist,
+						  Relids *relidsToRemove)
+{
+	ListCell *lc;
+	foreach (lc, joinlist)
+	{
+		switch (((Node*) lfirst(lc))->type)
+		{
+			case T_List:
+				remove_self_joins_recurse(scratch, (List *) lfirst(lc),
+										  relidsToRemove);
+				break;
+			case T_RangeTblRef:
+				break;
+			default:
+				Assert(false);
+		}
+	}
+
+	remove_self_joins_one_level(scratch, joinlist, relidsToRemove);
+}
+
+bool enable_self_join_removal;
+
+/*
+ * Find and remove useless self joins.
+ *
+ * We search for joins where the same relation is joined to itself on all
+ * columns of some unique index. If this condition holds, then, for
+ * each outer row, only one inner row matches, and it is the same row
+ * of the same relation. This allows us to remove the join and replace
+ * it with a scan that combines WHERE clauses from both sides. The join
+ * clauses themselves assume the form of X = X and can be replaced with
+ * NOT NULL clauses.
+ *
+ * For the sake of simplicity, we don't apply this optimization to special
+ * joins. Here is a list of what we could do in some particular cases:
+ * 'a a1 semi join a a2': is reduced to inner by reduce_unique_semijoins,
+ * and then removed normally.
+ * 'a a1 anti join a a2': could simplify to a scan with 'outer quals AND
+ * (IS NULL on join columns OR NOT inner quals)'.
+ * 'a a1 left join a a2': could simplify to a scan like inner, but without
+ * NOT NULL condtions on join columns.
+ * 'a a1 left join (a a2 join b)': can't simplify this, because join to b
+ * can both remove rows and introduce duplicates.
+ *
+ * To search for removable joins, we order all the relations on their Oid,
+ * go over each set with the same Oid, and consider each pair of relations
+ * in this set. We check that both relation are made unique by the same
+ * unique index with the same clauses.
+ *
+ * To remove the join, we mark one of the participating relation as
+ * dead, and rewrite all references to it to point to the remaining
+ * relation. This includes modifying RestrictInfos, EquivalenceClasses and
+ * EquivalenceMembers. We also have to modify the row marks. The join clauses
+ * of the removed relation become either restriction or join clauses, based on
+ * whether they reference any relations not participating in the removed join.
+ *
+ * 'targetlist' is the top-level targetlist of query. If it has any references
+ * to the removed relations, we update them to point to the remaining ones.
+ */
+void
+remove_useless_self_joins(PlannerInfo *root, List **joinlist, List *targetlist)
+{
+	ListCell *lc;
+	UsjScratch scratch;
+	Relids	relidsToRemove = NULL;
+	int		nremoved = 0,
+			relid = -1;
+
+	if (!enable_self_join_removal)
+		return;
+
+	scratch.root = root;
+	scratch.relids = palloc(root->simple_rel_array_size * sizeof(Index));
+	scratch.special_join_rels = palloc0(root->simple_rel_array_size * sizeof(Relids));
+	scratch.row_marks = palloc0(root->simple_rel_array_size * sizeof(PlanRowMark *));
+	scratch.joinrelids = NULL;
+	scratch.targetlist = targetlist;
+
+	/* Find out which relations have special joins to which. */
+	foreach(lc, root->join_info_list)
+	{
+		SpecialJoinInfo *info = (SpecialJoinInfo *) lfirst(lc);
+		int bit = -1;
+		while ((bit = bms_next_member(info->syn_lefthand, bit)) >= 0)
+		{
+			RelOptInfo *rel = find_base_rel(root, bit);
+			scratch.special_join_rels[rel->relid] =
+				bms_add_members(scratch.special_join_rels[rel->relid],
+					info->syn_righthand);
+		}
+
+		bit = -1;
+		while ((bit = bms_next_member(info->syn_righthand, bit)) >= 0)
+		{
+			RelOptInfo *rel = find_base_rel(root, bit);
+			scratch.special_join_rels[rel->relid] =
+				bms_add_members(scratch.special_join_rels[rel->relid],
+					info->syn_lefthand);
+		}
+	}
+
+	/* Collect row marks. */
+	foreach (lc, root->rowMarks)
+	{
+		PlanRowMark *rowMark = (PlanRowMark *) lfirst(lc);
+
+		/* Can't have more than one row mark for a relation. */
+		Assert(scratch.row_marks[rowMark->rti] == NULL);
+
+		scratch.row_marks[rowMark->rti] = rowMark;
+	}
+
+	/* Finally, remove the joins. */
+	remove_self_joins_recurse(&scratch, *joinlist, &relidsToRemove);
+	while ((relid = bms_next_member(relidsToRemove, relid)) >= 0)
+		*joinlist = remove_rel_from_joinlist(*joinlist, relid, &nremoved);
 }
diff --git a/src/backend/optimizer/plan/createplan.c b/src/backend/optimizer/plan/createplan.c
index 752a7a9a67b..53875d51643 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -171,7 +171,6 @@ static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_pat
 static Node *fix_indexqual_clause(PlannerInfo *root,
 								  IndexOptInfo *index, int indexcol,
 								  Node *clause, List *indexcolnos);
-static Node *fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol);
 static List *get_switched_clauses(List *clauses, Relids outerrelids);
 static List *order_qual_clauses(PlannerInfo *root, List *clauses);
 static void copy_generic_path_info(Plan *dest, Path *src);
@@ -1226,6 +1225,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 		/* Generate a Result plan with constant-FALSE gating qual */
 		Plan	   *plan;
 
+		tlist = build_path_tlist(root, &best_path->path);
 		plan = (Plan *) make_result(tlist,
 									(Node *) list_make1(makeBoolConst(false,
 																	  false)),
@@ -1254,7 +1254,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 	plan->plan.righttree = NULL;
 	plan->apprelids = rel->relids;
 
-	if (pathkeys != NIL)
+	if (pathkeys != NIL && best_path->pull_tlist == false)
 	{
 		/*
 		 * Compute sort column info, and adjust the Append's tlist as needed.
@@ -1288,11 +1288,17 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path, int flags)
 		/* Must insist that all children return the same tlist */
 		subplan = create_plan_recurse(root, subpath, CP_EXACT_TLIST);
 
+		if (tlist == NIL && best_path->pull_tlist)
+			plan->plan.targetlist = tlist = copyObject(subplan->targetlist);
+
 		/*
 		 * For ordered Appends, we must insert a Sort node if subplan isn't
 		 * sufficiently ordered.
+		 * if best_path->pull_tlist = then plan came from
+		 * keybased_rewrite_index_paths() which guarantee correct sorting in
+		 * subplan
 		 */
-		if (pathkeys != NIL)
+		if (pathkeys != NIL && best_path->pull_tlist == false)
 		{
 			int			numsortkeys;
 			AttrNumber *sortColIdx;
@@ -4954,7 +4960,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 	}
 	return expression_tree_mutator(node,
 								   replace_nestloop_params_mutator,
-								   (void *) root);
+								   (void *) root,
+								   0);
 }
 
 /*
@@ -5118,7 +5125,7 @@ fix_indexqual_clause(PlannerInfo *root, IndexOptInfo *index, int indexcol,
  * Most of the code here is just for sanity cross-checking that the given
  * expression actually matches the index column it's claimed to.
  */
-static Node *
+Node *
 fix_indexqual_operand(Node *node, IndexOptInfo *index, int indexcol)
 {
 	Var		   *result;
diff --git a/src/backend/optimizer/plan/planmain.c b/src/backend/optimizer/plan/planmain.c
index 5a1d00662fc..a36d946430a 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -218,7 +218,7 @@ query_planner(PlannerInfo *root,
 	 * jointree preprocessing, but the necessary information isn't available
 	 * until we've built baserel data structures and classified qual clauses.
 	 */
-	joinlist = remove_useless_joins(root, joinlist);
+	joinlist = remove_useless_left_joins(root, joinlist);
 
 	/*
 	 * Also, reduce any semijoins with unique inner rels to plain inner joins.
@@ -226,6 +226,11 @@ query_planner(PlannerInfo *root,
 	 */
 	reduce_unique_semijoins(root);
 
+	/*
+	 * Remove self joins on a unique column.
+	 */
+	remove_useless_self_joins(root, &joinlist, root->processed_tlist);
+
 	/*
 	 * Now distribute "placeholders" to base rels as needed.  This has to be
 	 * done after join removal because removal could change whether a
diff --git a/src/backend/optimizer/plan/planner.c b/src/backend/optimizer/plan/planner.c
index 98eb40cc47e..5f39dfec5bd 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -271,11 +271,22 @@ planner(Query *parse, const char *query_string, int cursorOptions,
 		ParamListInfo boundParams)
 {
 	PlannedStmt *result;
+	instr_time	planstart,
+				planduration;
+
+	INSTR_TIME_SET_CURRENT(planstart);
 
 	if (planner_hook)
 		result = (*planner_hook) (parse, query_string, cursorOptions, boundParams);
 	else
 		result = standard_planner(parse, query_string, cursorOptions, boundParams);
+
+	INSTR_TIME_SET_CURRENT(planduration);
+	INSTR_TIME_SUBTRACT(planduration, planstart);
+
+	/* Record time spent on hooks and standard_planner() */
+	result->planDuration = planduration;
+
 	return result;
 }
 
@@ -2784,8 +2795,9 @@ remove_useless_groupby_columns(PlannerInfo *root)
  *
  * In principle it might be interesting to consider other orderings of the
  * GROUP BY elements, which could match the sort ordering of other
- * possible plans (eg an indexscan) and thereby reduce cost.  We don't
- * bother with that, though.  Hashed grouping will frequently win anyway.
+ * possible plans (eg an indexscan) and thereby reduce cost.  However, we
+ * don't yet have sufficient information to do that here, so that's left until
+ * later in planning.  See get_useful_group_keys_orderings().
  *
  * Note: we need no comparable processing of the distinctClause because
  * the parser already enforced that that matches ORDER BY.
@@ -3592,7 +3604,8 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 							   NULL,
 							   0,
 							   false,
-							   -1);
+							   -1,
+							   false);
 	}
 	else
 	{
@@ -6260,24 +6273,122 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 		 */
 		foreach(lc, input_rel->pathlist)
 		{
+			ListCell   *lc2;
 			Path	   *path = (Path *) lfirst(lc);
 			Path	   *path_original = path;
-			bool		is_sorted;
-			int			presorted_keys;
 
-			is_sorted = pathkeys_count_contained_in(root->group_pathkeys,
-													path->pathkeys,
-													&presorted_keys);
+			List	   *pathkey_orderings = NIL;
+
+			List	   *group_pathkeys = root->group_pathkeys;
+			List	   *group_clauses = parse->groupClause;
 
-			if (path == cheapest_path || is_sorted)
+			/* generate alternative group orderings that might be useful */
+			pathkey_orderings = get_useful_group_keys_orderings(root,
+																path->rows,
+																path->pathkeys,
+																group_pathkeys,
+																group_clauses);
+
+			Assert(list_length(pathkey_orderings) > 0);
+
+			/* process all potentially interesting grouping reorderings */
+			foreach(lc2, pathkey_orderings)
 			{
-				/* Sort the cheapest-total path if it isn't already sorted */
-				if (!is_sorted)
-					path = (Path *) create_sort_path(root,
-													 grouped_rel,
-													 path,
-													 root->group_pathkeys,
-													 -1.0);
+				bool		is_sorted;
+				int			presorted_keys = 0;
+				PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2);
+
+				/* restore the path (we replace it in the loop) */
+				path = path_original;
+
+				is_sorted = pathkeys_count_contained_in(info->pathkeys,
+														path->pathkeys,
+														&presorted_keys);
+
+				if (path == cheapest_path || is_sorted)
+				{
+					/* Sort the cheapest-total path if it isn't already sorted */
+					if (!is_sorted)
+						path = (Path *) create_sort_path(root,
+														 grouped_rel,
+														 path,
+														 info->pathkeys,
+														 -1.0);
+
+					/* Now decide what to stick atop it */
+					if (parse->groupingSets)
+					{
+						consider_groupingsets_paths(root, grouped_rel,
+													path, true, can_hash,
+													gd, agg_costs, dNumGroups);
+					}
+					else if (parse->hasAggs)
+					{
+						/*
+						 * We have aggregation, possibly with plain GROUP BY.
+						 * Make an AggPath.
+						 */
+						add_path(grouped_rel, (Path *)
+								 create_agg_path(root,
+												 grouped_rel,
+												 path,
+												 grouped_rel->reltarget,
+												 info->clauses ? AGG_SORTED : AGG_PLAIN,
+												 AGGSPLIT_SIMPLE,
+												 info->clauses,
+												 havingQual,
+												 agg_costs,
+												 dNumGroups));
+					}
+					else if (group_clauses)
+					{
+						/*
+						 * We have GROUP BY without aggregation or grouping
+						 * sets. Make a GroupPath.
+						 */
+						add_path(grouped_rel, (Path *)
+								 create_group_path(root,
+												   grouped_rel,
+												   path,
+												   info->clauses,
+												   havingQual,
+												   dNumGroups));
+					}
+					else
+					{
+						/* Other cases should have been handled above */
+						Assert(false);
+					}
+				}
+
+				/*
+				 * Now we may consider incremental sort on this path, but only
+				 * when the path is not already sorted and when incremental
+				 * sort is enabled.
+				 */
+				if (is_sorted || !enable_incremental_sort)
+					continue;
+
+				/* Restore the input path (we might have added Sort on top). */
+				path = path_original;
+
+				/* no shared prefix, no point in building incremental sort */
+				if (presorted_keys == 0)
+					continue;
+
+				/*
+				 * We should have already excluded pathkeys of length 1
+				 * because then presorted_keys > 0 would imply is_sorted was
+				 * true.
+				 */
+				Assert(list_length(root->group_pathkeys) != 1);
+
+				path = (Path *) create_incremental_sort_path(root,
+															 grouped_rel,
+															 path,
+															 info->pathkeys,
+															 presorted_keys,
+															 -1.0);
 
 				/* Now decide what to stick atop it */
 				if (parse->groupingSets)
@@ -6297,9 +6408,9 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 											 grouped_rel,
 											 path,
 											 grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+											 info->clauses ? AGG_SORTED : AGG_PLAIN,
 											 AGGSPLIT_SIMPLE,
-											 parse->groupClause,
+											 info->clauses,
 											 havingQual,
 											 agg_costs,
 											 dNumGroups));
@@ -6314,7 +6425,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 							 create_group_path(root,
 											   grouped_rel,
 											   path,
-											   parse->groupClause,
+											   info->clauses,
 											   havingQual,
 											   dNumGroups));
 				}
@@ -6324,79 +6435,6 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 					Assert(false);
 				}
 			}
-
-			/*
-			 * Now we may consider incremental sort on this path, but only
-			 * when the path is not already sorted and when incremental sort
-			 * is enabled.
-			 */
-			if (is_sorted || !enable_incremental_sort)
-				continue;
-
-			/* Restore the input path (we might have added Sort on top). */
-			path = path_original;
-
-			/* no shared prefix, no point in building incremental sort */
-			if (presorted_keys == 0)
-				continue;
-
-			/*
-			 * We should have already excluded pathkeys of length 1 because
-			 * then presorted_keys > 0 would imply is_sorted was true.
-			 */
-			Assert(list_length(root->group_pathkeys) != 1);
-
-			path = (Path *) create_incremental_sort_path(root,
-														 grouped_rel,
-														 path,
-														 root->group_pathkeys,
-														 presorted_keys,
-														 -1.0);
-
-			/* Now decide what to stick atop it */
-			if (parse->groupingSets)
-			{
-				consider_groupingsets_paths(root, grouped_rel,
-											path, true, can_hash,
-											gd, agg_costs, dNumGroups);
-			}
-			else if (parse->hasAggs)
-			{
-				/*
-				 * We have aggregation, possibly with plain GROUP BY. Make an
-				 * AggPath.
-				 */
-				add_path(grouped_rel, (Path *)
-						 create_agg_path(root,
-										 grouped_rel,
-										 path,
-										 grouped_rel->reltarget,
-										 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-										 AGGSPLIT_SIMPLE,
-										 parse->groupClause,
-										 havingQual,
-										 agg_costs,
-										 dNumGroups));
-			}
-			else if (parse->groupClause)
-			{
-				/*
-				 * We have GROUP BY without aggregation or grouping sets. Make
-				 * a GroupPath.
-				 */
-				add_path(grouped_rel, (Path *)
-						 create_group_path(root,
-										   grouped_rel,
-										   path,
-										   parse->groupClause,
-										   havingQual,
-										   dNumGroups));
-			}
-			else
-			{
-				/* Other cases should have been handled above */
-				Assert(false);
-			}
 		}
 
 		/*
@@ -6407,100 +6445,130 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 		{
 			foreach(lc, partially_grouped_rel->pathlist)
 			{
+				ListCell   *lc2;
 				Path	   *path = (Path *) lfirst(lc);
 				Path	   *path_original = path;
-				bool		is_sorted;
-				int			presorted_keys;
 
-				is_sorted = pathkeys_count_contained_in(root->group_pathkeys,
-														path->pathkeys,
-														&presorted_keys);
+				List	   *pathkey_orderings = NIL;
 
-				/*
-				 * Insert a Sort node, if required.  But there's no point in
-				 * sorting anything but the cheapest path.
-				 */
-				if (!is_sorted)
+				List	   *group_pathkeys = root->group_pathkeys;
+				List	   *group_clauses = parse->groupClause;
+
+				/* generate alternative group orderings that might be useful */
+				pathkey_orderings = get_useful_group_keys_orderings(root,
+																	path->rows,
+																	path->pathkeys,
+																	group_pathkeys,
+																	group_clauses);
+
+				Assert(list_length(pathkey_orderings) > 0);
+
+				/* process all potentially interesting grouping reorderings */
+				foreach(lc2, pathkey_orderings)
 				{
-					if (path != partially_grouped_rel->cheapest_total_path)
-						continue;
-					path = (Path *) create_sort_path(root,
-													 grouped_rel,
-													 path,
-													 root->group_pathkeys,
-													 -1.0);
-				}
+					bool		is_sorted;
+					int			presorted_keys = 0;
+					PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2);
 
-				if (parse->hasAggs)
-					add_path(grouped_rel, (Path *)
-							 create_agg_path(root,
-											 grouped_rel,
-											 path,
-											 grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-											 AGGSPLIT_FINAL_DESERIAL,
-											 parse->groupClause,
-											 havingQual,
-											 agg_final_costs,
-											 dNumGroups));
-				else
-					add_path(grouped_rel, (Path *)
-							 create_group_path(root,
-											   grouped_rel,
-											   path,
-											   parse->groupClause,
-											   havingQual,
-											   dNumGroups));
+					/* restore the path (we replace it in the loop) */
+					path = path_original;
 
-				/*
-				 * Now we may consider incremental sort on this path, but only
-				 * when the path is not already sorted and when incremental
-				 * sort is enabled.
-				 */
-				if (is_sorted || !enable_incremental_sort)
-					continue;
+					is_sorted = pathkeys_count_contained_in(info->pathkeys,
+															path->pathkeys,
+															&presorted_keys);
 
-				/* Restore the input path (we might have added Sort on top). */
-				path = path_original;
+					/*
+					 * Insert a Sort node, if required.  But there's no point
+					 * in sorting anything but the cheapest path.
+					 */
+					if (!is_sorted)
+					{
+						if (path != partially_grouped_rel->cheapest_total_path)
+							continue;
+						path = (Path *) create_sort_path(root,
+														 grouped_rel,
+														 path,
+														 info->pathkeys,
+														 -1.0);
+					}
 
-				/* no shared prefix, not point in building incremental sort */
-				if (presorted_keys == 0)
-					continue;
+					if (parse->hasAggs)
+						add_path(grouped_rel, (Path *)
+								 create_agg_path(root,
+												 grouped_rel,
+												 path,
+												 grouped_rel->reltarget,
+												 info->clauses ? AGG_SORTED : AGG_PLAIN,
+												 AGGSPLIT_FINAL_DESERIAL,
+												 info->clauses,
+												 havingQual,
+												 agg_final_costs,
+												 dNumGroups));
+					else
+						add_path(grouped_rel, (Path *)
+								 create_group_path(root,
+												   grouped_rel,
+												   path,
+												   info->clauses,
+												   havingQual,
+												   dNumGroups));
 
-				/*
-				 * We should have already excluded pathkeys of length 1
-				 * because then presorted_keys > 0 would imply is_sorted was
-				 * true.
-				 */
-				Assert(list_length(root->group_pathkeys) != 1);
+					/*
+					 * Now we may consider incremental sort on this path, but
+					 * only when the path is not already sorted and when
+					 * incremental sort is enabled.
+					 */
+					if (is_sorted || !enable_incremental_sort)
+						continue;
 
-				path = (Path *) create_incremental_sort_path(root,
-															 grouped_rel,
-															 path,
-															 root->group_pathkeys,
-															 presorted_keys,
-															 -1.0);
+					/*
+					 * Restore the input path (we might have added Sort on
+					 * top).
+					 */
+					path = path_original;
 
-				if (parse->hasAggs)
-					add_path(grouped_rel, (Path *)
-							 create_agg_path(root,
-											 grouped_rel,
-											 path,
-											 grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-											 AGGSPLIT_FINAL_DESERIAL,
-											 parse->groupClause,
-											 havingQual,
-											 agg_final_costs,
-											 dNumGroups));
-				else
-					add_path(grouped_rel, (Path *)
-							 create_group_path(root,
-											   grouped_rel,
-											   path,
-											   parse->groupClause,
-											   havingQual,
-											   dNumGroups));
+					/*
+					 * no shared prefix, not point in building incremental
+					 * sort
+					 */
+					if (presorted_keys == 0)
+						continue;
+
+					/*
+					 * We should have already excluded pathkeys of length 1
+					 * because then presorted_keys > 0 would imply is_sorted
+					 * was true.
+					 */
+					Assert(list_length(root->group_pathkeys) != 1);
+
+					path = (Path *) create_incremental_sort_path(root,
+																 grouped_rel,
+																 path,
+																 info->pathkeys,
+																 presorted_keys,
+																 -1.0);
+
+					if (parse->hasAggs)
+						add_path(grouped_rel, (Path *)
+								 create_agg_path(root,
+												 grouped_rel,
+												 path,
+												 grouped_rel->reltarget,
+												 info->clauses ? AGG_SORTED : AGG_PLAIN,
+												 AGGSPLIT_FINAL_DESERIAL,
+												 info->clauses,
+												 havingQual,
+												 agg_final_costs,
+												 dNumGroups));
+					else
+						add_path(grouped_rel, (Path *)
+								 create_group_path(root,
+												   grouped_rel,
+												   path,
+												   info->clauses,
+												   havingQual,
+												   dNumGroups));
+				}
 			}
 		}
 	}
@@ -6703,41 +6771,71 @@ create_partial_grouping_paths(PlannerInfo *root,
 		 */
 		foreach(lc, input_rel->pathlist)
 		{
+			ListCell   *lc2;
 			Path	   *path = (Path *) lfirst(lc);
-			bool		is_sorted;
+			Path	   *path_save = path;
+
+			List	   *pathkey_orderings = NIL;
 
-			is_sorted = pathkeys_contained_in(root->group_pathkeys,
-											  path->pathkeys);
-			if (path == cheapest_total_path || is_sorted)
+			List	   *group_pathkeys = root->group_pathkeys;
+			List	   *group_clauses = parse->groupClause;
+
+			/* generate alternative group orderings that might be useful */
+			pathkey_orderings = get_useful_group_keys_orderings(root,
+																path->rows,
+																path->pathkeys,
+																group_pathkeys,
+																group_clauses);
+
+			Assert(list_length(pathkey_orderings) > 0);
+
+			/* process all potentially interesting grouping reorderings */
+			foreach(lc2, pathkey_orderings)
 			{
-				/* Sort the cheapest partial path, if it isn't already */
-				if (!is_sorted)
-					path = (Path *) create_sort_path(root,
-													 partially_grouped_rel,
-													 path,
-													 root->group_pathkeys,
-													 -1.0);
+				bool		is_sorted;
+				int			presorted_keys = 0;
+				PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2);
 
-				if (parse->hasAggs)
-					add_path(partially_grouped_rel, (Path *)
-							 create_agg_path(root,
-											 partially_grouped_rel,
-											 path,
-											 partially_grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-											 AGGSPLIT_INITIAL_SERIAL,
-											 parse->groupClause,
-											 NIL,
-											 agg_partial_costs,
-											 dNumPartialGroups));
-				else
-					add_path(partially_grouped_rel, (Path *)
-							 create_group_path(root,
-											   partially_grouped_rel,
-											   path,
-											   parse->groupClause,
-											   NIL,
-											   dNumPartialGroups));
+				/* restore the path (we replace it in the loop) */
+				path = path_save;
+
+				is_sorted = pathkeys_count_contained_in(info->pathkeys,
+														path->pathkeys,
+														&presorted_keys);
+
+				if (path == cheapest_total_path || is_sorted)
+				{
+					/* Sort the cheapest partial path, if it isn't already */
+					if (!is_sorted)
+					{
+						path = (Path *) create_sort_path(root,
+														 partially_grouped_rel,
+														 path,
+														 info->pathkeys,
+														 -1.0);
+					}
+
+					if (parse->hasAggs)
+						add_path(partially_grouped_rel, (Path *)
+								 create_agg_path(root,
+												 partially_grouped_rel,
+												 path,
+												 partially_grouped_rel->reltarget,
+												 info->clauses ? AGG_SORTED : AGG_PLAIN,
+												 AGGSPLIT_INITIAL_SERIAL,
+												 info->clauses,
+												 NIL,
+												 agg_partial_costs,
+												 dNumPartialGroups));
+					else
+						add_path(partially_grouped_rel, (Path *)
+								 create_group_path(root,
+												   partially_grouped_rel,
+												   path,
+												   info->clauses,
+												   NIL,
+												   dNumPartialGroups));
+				}
 			}
 		}
 
@@ -6747,6 +6845,8 @@ create_partial_grouping_paths(PlannerInfo *root,
 		 * We can also skip the entire loop when we only have a single-item
 		 * group_pathkeys because then we can't possibly have a presorted
 		 * prefix of the list without having the list be fully sorted.
+		 *
+		 * XXX Shouldn't this also consider the group-key-reordering?
 		 */
 		if (enable_incremental_sort && list_length(root->group_pathkeys) > 1)
 		{
@@ -6804,24 +6904,101 @@ create_partial_grouping_paths(PlannerInfo *root,
 		/* Similar to above logic, but for partial paths. */
 		foreach(lc, input_rel->partial_pathlist)
 		{
+			ListCell   *lc2;
 			Path	   *path = (Path *) lfirst(lc);
 			Path	   *path_original = path;
-			bool		is_sorted;
-			int			presorted_keys;
 
-			is_sorted = pathkeys_count_contained_in(root->group_pathkeys,
-													path->pathkeys,
-													&presorted_keys);
+			List	   *pathkey_orderings = NIL;
 
-			if (path == cheapest_partial_path || is_sorted)
+			List	   *group_pathkeys = root->group_pathkeys;
+			List	   *group_clauses = parse->groupClause;
+
+			/* generate alternative group orderings that might be useful */
+			pathkey_orderings = get_useful_group_keys_orderings(root,
+																path->rows,
+																path->pathkeys,
+																group_pathkeys,
+																group_clauses);
+
+			Assert(list_length(pathkey_orderings) > 0);
+
+			/* process all potentially interesting grouping reorderings */
+			foreach(lc2, pathkey_orderings)
 			{
-				/* Sort the cheapest partial path, if it isn't already */
-				if (!is_sorted)
-					path = (Path *) create_sort_path(root,
-													 partially_grouped_rel,
-													 path,
-													 root->group_pathkeys,
-													 -1.0);
+				bool		is_sorted;
+				int			presorted_keys = 0;
+				PathKeyInfo *info = (PathKeyInfo *) lfirst(lc2);
+
+				/* restore the path (we replace it in the loop) */
+				path = path_original;
+
+				is_sorted = pathkeys_count_contained_in(info->pathkeys,
+														path->pathkeys,
+														&presorted_keys);
+
+				if (path == cheapest_partial_path || is_sorted)
+				{
+
+					/* Sort the cheapest partial path, if it isn't already */
+					if (!is_sorted)
+					{
+						path = (Path *) create_sort_path(root,
+														 partially_grouped_rel,
+														 path,
+														 info->pathkeys,
+														 -1.0);
+					}
+
+					if (parse->hasAggs)
+						add_partial_path(partially_grouped_rel, (Path *)
+										 create_agg_path(root,
+														 partially_grouped_rel,
+														 path,
+														 partially_grouped_rel->reltarget,
+														 info->clauses ? AGG_SORTED : AGG_PLAIN,
+														 AGGSPLIT_INITIAL_SERIAL,
+														 info->clauses,
+														 NIL,
+														 agg_partial_costs,
+														 dNumPartialPartialGroups));
+					else
+						add_partial_path(partially_grouped_rel, (Path *)
+										 create_group_path(root,
+														   partially_grouped_rel,
+														   path,
+														   info->clauses,
+														   NIL,
+														   dNumPartialPartialGroups));
+				}
+
+				/*
+				 * Now we may consider incremental sort on this path, but only
+				 * when the path is not already sorted and when incremental
+				 * sort is enabled.
+				 */
+				if (is_sorted || !enable_incremental_sort)
+					continue;
+
+				/* Restore the input path (we might have added Sort on top). */
+				path = path_original;
+
+				/* no shared prefix, not point in building incremental sort */
+				if (presorted_keys == 0)
+					continue;
+
+				/*
+				 * We should have already excluded pathkeys of length 1
+				 * because then presorted_keys > 0 would imply is_sorted was
+				 * true.
+				 */
+				Assert(list_length(root->group_pathkeys) != 1);
+
+				path = (Path *) create_incremental_sort_path(root,
+															 partially_grouped_rel,
+															 path,
+															 info->pathkeys,
+															 presorted_keys,
+															 -1.0);
 
 				if (parse->hasAggs)
 					add_partial_path(partially_grouped_rel, (Path *)
@@ -6829,9 +7006,9 @@ create_partial_grouping_paths(PlannerInfo *root,
 													 partially_grouped_rel,
 													 path,
 													 partially_grouped_rel->reltarget,
-													 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+													 info->clauses ? AGG_SORTED : AGG_PLAIN,
 													 AGGSPLIT_INITIAL_SERIAL,
-													 parse->groupClause,
+													 info->clauses,
 													 NIL,
 													 agg_partial_costs,
 													 dNumPartialPartialGroups));
@@ -6840,59 +7017,10 @@ create_partial_grouping_paths(PlannerInfo *root,
 									 create_group_path(root,
 													   partially_grouped_rel,
 													   path,
-													   parse->groupClause,
+													   info->clauses,
 													   NIL,
 													   dNumPartialPartialGroups));
 			}
-
-			/*
-			 * Now we may consider incremental sort on this path, but only
-			 * when the path is not already sorted and when incremental sort
-			 * is enabled.
-			 */
-			if (is_sorted || !enable_incremental_sort)
-				continue;
-
-			/* Restore the input path (we might have added Sort on top). */
-			path = path_original;
-
-			/* no shared prefix, not point in building incremental sort */
-			if (presorted_keys == 0)
-				continue;
-
-			/*
-			 * We should have already excluded pathkeys of length 1 because
-			 * then presorted_keys > 0 would imply is_sorted was true.
-			 */
-			Assert(list_length(root->group_pathkeys) != 1);
-
-			path = (Path *) create_incremental_sort_path(root,
-														 partially_grouped_rel,
-														 path,
-														 root->group_pathkeys,
-														 presorted_keys,
-														 -1.0);
-
-			if (parse->hasAggs)
-				add_partial_path(partially_grouped_rel, (Path *)
-								 create_agg_path(root,
-												 partially_grouped_rel,
-												 path,
-												 partially_grouped_rel->reltarget,
-												 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
-												 AGGSPLIT_INITIAL_SERIAL,
-												 parse->groupClause,
-												 NIL,
-												 agg_partial_costs,
-												 dNumPartialPartialGroups));
-			else
-				add_partial_path(partially_grouped_rel, (Path *)
-								 create_group_path(root,
-												   partially_grouped_rel,
-												   path,
-												   parse->groupClause,
-												   NIL,
-												   dNumPartialPartialGroups));
 		}
 	}
 
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 59387c21b51..cdc9b9e6678 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -2142,7 +2142,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 									 context);
 	fix_expr_common(context->root, node);
 	return expression_tree_mutator(node, fix_scan_expr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 static bool
@@ -2488,7 +2488,7 @@ convert_combining_aggrefs(Node *node, void *context)
 		return (Node *) parent_agg;
 	}
 	return expression_tree_mutator(node, convert_combining_aggrefs,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
@@ -2853,6 +2853,10 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	{
 		Var		   *var = (Var *) node;
 
+		/* join_references_mutator already checks this node */
+		if (var->varno == OUTER_VAR)
+			return (Node*)copyObject(var);
+
 		/* Look for the var in the input tlists, first in the outer */
 		if (context->outer_itlist)
 		{
@@ -2867,6 +2871,9 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 		/* then in the inner. */
 		if (context->inner_itlist)
 		{
+			if (var->varno == INNER_VAR)
+				return (Node*)copyObject(var);
+
 			newvar = search_indexed_tlist_for_var(var,
 												  context->inner_itlist,
 												  INNER_VAR,
@@ -2941,7 +2948,7 @@ fix_join_expr_mutator(Node *node, fix_join_expr_context *context)
 	fix_expr_common(context->root, node);
 	return expression_tree_mutator(node,
 								   fix_join_expr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
@@ -3062,7 +3069,7 @@ fix_upper_expr_mutator(Node *node, fix_upper_expr_context *context)
 	fix_expr_common(context->root, node);
 	return expression_tree_mutator(node,
 								   fix_upper_expr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
@@ -3157,7 +3164,7 @@ fix_windowagg_condition_expr_mutator(Node *node,
 
 	return expression_tree_mutator(node,
 								   fix_windowagg_condition_expr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index 61b12384313..e517304ec68 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -712,7 +712,7 @@ convert_testexpr_mutator(Node *node,
 	}
 	return expression_tree_mutator(node,
 								   convert_testexpr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
@@ -1898,7 +1898,7 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
 	}
 	return expression_tree_mutator(node,
 								   replace_correlation_vars_mutator,
-								   (void *) root);
+								   (void *) root, 0);
 }
 
 /*
@@ -2046,7 +2046,7 @@ process_sublinks_mutator(Node *node, process_sublinks_context *context)
 
 	return expression_tree_mutator(node,
 								   process_sublinks_mutator,
-								   (void *) &locContext);
+								   (void *) &locContext, 0);
 }
 
 /*
diff --git a/src/backend/optimizer/prep/prepunion.c b/src/backend/optimizer/prep/prepunion.c
index 4b534dff483..2f89d6c3c93 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -621,7 +621,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 	 * Append the child results together.
 	 */
 	path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
-									   NIL, NULL, 0, false, -1);
+									   NIL, NULL, 0, false, -1, false);
 
 	/*
 	 * For UNION ALL, we just need the Append path.  For UNION, need to add
@@ -678,7 +678,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 			create_append_path(root, result_rel, NIL, partial_pathlist,
 							   NIL, NULL,
 							   parallel_workers, enable_parallel_append,
-							   -1);
+							   -1, false);
 		ppath = (Path *)
 			create_gather_path(root, result_rel, ppath,
 							   result_rel->reltarget, NULL, NULL);
@@ -788,7 +788,7 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
 	 * Append the child results together.
 	 */
 	path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
-									   NIL, NULL, 0, false, -1);
+									   NIL, NULL, 0, false, -1, false);
 
 	/* Identify the grouping semantics */
 	groupList = generate_setop_grouplist(op, tlist);
diff --git a/src/backend/optimizer/util/appendinfo.c b/src/backend/optimizer/util/appendinfo.c
index 5c3d5a736ba..3664ed26e70 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -390,7 +390,7 @@ adjust_appendrel_attrs_mutator(Node *node,
 
 		phv = (PlaceHolderVar *) expression_tree_mutator(node,
 														 adjust_appendrel_attrs_mutator,
-														 (void *) context);
+														 (void *) context, 0);
 		/* now fix PlaceHolderVar's relid sets */
 		if (phv->phlevelsup == 0)
 			phv->phrels = adjust_child_relids(phv->phrels, context->nappinfos,
@@ -475,7 +475,7 @@ adjust_appendrel_attrs_mutator(Node *node,
 	Assert(!IsA(node, JoinExpr));
 
 	return expression_tree_mutator(node, adjust_appendrel_attrs_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
diff --git a/src/backend/optimizer/util/clauses.c b/src/backend/optimizer/util/clauses.c
index 66d10b7d0d1..afa17b28c0a 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2323,7 +2323,7 @@ estimate_expression_value(PlannerInfo *root, Node *node)
  */
 #define ece_generic_processing(node) \
 	expression_tree_mutator((Node *) (node), eval_const_expressions_mutator, \
-							(void *) context)
+							(void *) context, 0)
 
 /*
  * Check whether all arguments of the given node were reduced to Consts.
@@ -2459,7 +2459,7 @@ eval_const_expressions_mutator(Node *node,
 				args = (List *)
 					expression_tree_mutator((Node *) args,
 											eval_const_expressions_mutator,
-											(void *) context);
+											(void *) context, 0);
 				/* ... and the filter expression, which isn't */
 				aggfilter = (Expr *)
 					eval_const_expressions_mutator((Node *) expr->aggfilter,
@@ -2603,7 +2603,7 @@ eval_const_expressions_mutator(Node *node,
 				 */
 				args = (List *) expression_tree_mutator((Node *) expr->args,
 														eval_const_expressions_mutator,
-														(void *) context);
+														(void *) context, 0);
 
 				/*
 				 * We must do our own check for NULLs because DistinctExpr has
@@ -3968,7 +3968,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		args = expand_function_arguments(args, false, result_type, func_tuple);
 		args = (List *) expression_tree_mutator((Node *) args,
 												eval_const_expressions_mutator,
-												(void *) context);
+												(void *) context, 0);
 		/* Argument processing done, give it back to the caller */
 		*args_p = args;
 	}
@@ -4823,7 +4823,7 @@ substitute_actual_parameters_mutator(Node *node,
 		return list_nth(context->args, param->paramid - 1);
 	}
 	return expression_tree_mutator(node, substitute_actual_parameters_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
@@ -5296,7 +5296,7 @@ substitute_actual_srf_parameters_mutator(Node *node,
 	}
 	return expression_tree_mutator(node,
 								   substitute_actual_srf_parameters_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index 3df00829e68..0bf57f2219e 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -212,6 +212,18 @@ compare_path_costs_fuzzily(Path *path1, Path *path2, double fuzz_factor)
 		/* ... but path2 fuzzily worse on startup, so path1 wins */
 		return COSTS_BETTER1;
 	}
+
+	if (IsA(path1, IndexPath) && IsA(path2, IndexPath))
+	{
+		IndexPath *ipath1 = (IndexPath*)path1;
+		IndexPath *ipath2 = (IndexPath*)path2;
+
+		if (ipath1->indexselectivity < ipath2->indexselectivity)
+			return COSTS_BETTER1;
+		else if (ipath1->indexselectivity > ipath2->indexselectivity)
+			return COSTS_BETTER2;
+	}
+
 	/* fuzzily the same on both costs */
 	return COSTS_EQUAL;
 
@@ -1251,7 +1263,7 @@ create_append_path(PlannerInfo *root,
 				   List *subpaths, List *partial_subpaths,
 				   List *pathkeys, Relids required_outer,
 				   int parallel_workers, bool parallel_aware,
-				   double rows)
+				   double rows, bool pull_tlist)
 {
 	AppendPath *pathnode = makeNode(AppendPath);
 	ListCell   *l;
@@ -1261,6 +1273,7 @@ create_append_path(PlannerInfo *root,
 	pathnode->path.pathtype = T_Append;
 	pathnode->path.parent = rel;
 	pathnode->path.pathtarget = rel->reltarget;
+	pathnode->pull_tlist = pull_tlist;
 
 	/*
 	 * When generating an Append path for a partitioned table, there may be
@@ -1347,7 +1360,7 @@ create_append_path(PlannerInfo *root,
 		pathnode->path.pathkeys = child->pathkeys;
 	}
 	else
-		cost_append(pathnode);
+		cost_append(pathnode, root);
 
 	/* If the caller provided a row estimate, override the computed value. */
 	if (rows >= 0)
@@ -1713,7 +1726,8 @@ create_unique_path(PlannerInfo *root, RelOptInfo *rel, Path *subpath,
 	if (rel->rtekind == RTE_RELATION && sjinfo->semi_can_btree &&
 		relation_has_unique_index_for(root, rel, NIL,
 									  sjinfo->semi_rhs_exprs,
-									  sjinfo->semi_operators))
+									  sjinfo->semi_operators,
+									  NULL /*index_info*/))
 	{
 		pathnode->umethod = UNIQUE_PATH_NOOP;
 		pathnode->path.rows = rel->rows;
@@ -3839,9 +3853,13 @@ adjust_limit_rows_costs(double *rows,	/* in/out parameter */
 		if (count_rows > *rows)
 			count_rows = *rows;
 		if (input_rows > 0)
+		{
+			*startup_cost = *startup_cost +
+				2*(input_total_cost - input_startup_cost) / input_rows;
 			*total_cost = *startup_cost +
 				(input_total_cost - input_startup_cost)
 				* count_rows / input_rows;
+		}
 		*rows = count_rows;
 		if (*rows < 1)
 			*rows = 1;
@@ -3957,7 +3975,8 @@ reparameterize_path(PlannerInfo *root, Path *path,
 									   apath->path.pathkeys, required_outer,
 									   apath->path.parallel_workers,
 									   apath->path.parallel_aware,
-									   -1);
+									   -1,
+									   apath->pull_tlist);
 			}
 		case T_Memoize:
 			{
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 002cea0c116..79e555b9d30 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -446,6 +446,9 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
 
 			index_close(indexRelation, NoLock);
 
+			info->sslots = palloc0(
+					(STATISTIC_NUM_SLOTS + 1) * sizeof(AttStatsSlot));
+
 			/*
 			 * We've historically used lcons() here.  It'd make more sense to
 			 * use lappend(), but that causes the planner to change behavior
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 591306735a5..03c48f6a011 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -40,14 +40,10 @@ typedef struct JoinHashEntry
 
 static void build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
 								RelOptInfo *input_rel);
-static List *build_joinrel_restrictlist(PlannerInfo *root,
-										RelOptInfo *joinrel,
-										RelOptInfo *outer_rel,
-										RelOptInfo *inner_rel);
 static void build_joinrel_joinlist(RelOptInfo *joinrel,
 								   RelOptInfo *outer_rel,
 								   RelOptInfo *inner_rel);
-static List *subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
+static List *subbuild_joinrel_restrictlist(Relids joinrelids,
 										   List *joininfo_list,
 										   List *new_restrictlist);
 static List *subbuild_joinrel_joinlist(RelOptInfo *joinrel,
@@ -600,7 +596,7 @@ build_join_rel(PlannerInfo *root,
 		 */
 		if (restrictlist_ptr)
 			*restrictlist_ptr = build_joinrel_restrictlist(root,
-														   joinrel,
+														   joinrel->relids,
 														   outer_rel,
 														   inner_rel);
 		return joinrel;
@@ -707,7 +703,7 @@ build_join_rel(PlannerInfo *root,
 	 * caller might or might not need the restrictlist, but I need it anyway
 	 * for set_joinrel_size_estimates().)
 	 */
-	restrictlist = build_joinrel_restrictlist(root, joinrel,
+	restrictlist = build_joinrel_restrictlist(root, joinrel->relids,
 											  outer_rel, inner_rel);
 	if (restrictlist_ptr)
 		*restrictlist_ptr = restrictlist;
@@ -1059,7 +1055,7 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
  *	  the various joinlist entries ultimately refer to RestrictInfos
  *	  pushed into them by distribute_restrictinfo_to_rels().
  *
- * 'joinrel' is a join relation node
+ * 'joinrelids' is a join relation id set
  * 'outer_rel' and 'inner_rel' are a pair of relations that can be joined
  *		to form joinrel.
  *
@@ -1072,9 +1068,9 @@ build_joinrel_tlist(PlannerInfo *root, RelOptInfo *joinrel,
  * RestrictInfo nodes are no longer context-dependent.  Instead, just include
  * the original nodes in the lists made for the join relation.
  */
-static List *
+List *
 build_joinrel_restrictlist(PlannerInfo *root,
-						   RelOptInfo *joinrel,
+						   Relids joinrelids,
 						   RelOptInfo *outer_rel,
 						   RelOptInfo *inner_rel)
 {
@@ -1085,8 +1081,8 @@ build_joinrel_restrictlist(PlannerInfo *root,
 	 * eliminating any duplicates (important since we will see many of the
 	 * same clauses arriving from both input relations).
 	 */
-	result = subbuild_joinrel_restrictlist(joinrel, outer_rel->joininfo, NIL);
-	result = subbuild_joinrel_restrictlist(joinrel, inner_rel->joininfo, result);
+	result = subbuild_joinrel_restrictlist(joinrelids, outer_rel->joininfo, NIL);
+	result = subbuild_joinrel_restrictlist(joinrelids, inner_rel->joininfo, result);
 
 	/*
 	 * Add on any clauses derived from EquivalenceClasses.  These cannot be
@@ -1095,7 +1091,7 @@ build_joinrel_restrictlist(PlannerInfo *root,
 	 */
 	result = list_concat(result,
 						 generate_join_implied_equalities(root,
-														  joinrel->relids,
+														  joinrelids,
 														  outer_rel->relids,
 														  inner_rel));
 
@@ -1120,18 +1116,66 @@ build_joinrel_joinlist(RelOptInfo *joinrel,
 	joinrel->joininfo = result;
 }
 
+typedef struct UniquePtrList {
+	List	*unique_list;
+	HTAB	*h;
+} UniquePtrList;
+
+static void
+addUniquePtrList(UniquePtrList *upl, void *v)
+{
+	if (upl->h != NULL || list_length(upl->unique_list) > 32)
+	{
+		bool		found;
+
+		if (upl->h == NULL)
+		{
+			HASHCTL hash_ctl;
+			ListCell	*l;
+
+			MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+
+			hash_ctl.keysize = sizeof(void*);
+			hash_ctl.entrysize = sizeof(void*);
+
+			upl->h = hash_create("UniquePtrList storage", 64, &hash_ctl,
+								 HASH_BLOBS | HASH_ELEM);
+
+			foreach(l, upl->unique_list)
+			{
+				void	*k =  lfirst(l);
+
+				hash_search(upl->h, &k, HASH_ENTER, &found);
+				Assert(found == false);
+			}
+		}
+
+		hash_search(upl->h, &v, HASH_ENTER, &found);
+		if (found == false)
+			upl->unique_list = lappend(upl->unique_list, v);
+	}
+	else
+	{
+		upl->unique_list = list_append_unique_ptr(upl->unique_list, v);
+	}
+}
+
 static List *
-subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
+subbuild_joinrel_restrictlist(Relids joinrelids,
 							  List *joininfo_list,
 							  List *new_restrictlist)
 {
 	ListCell   *l;
+	UniquePtrList   upl;
+
+	memset(&upl, 0, sizeof(upl));
+	upl.unique_list = new_restrictlist;
 
 	foreach(l, joininfo_list)
 	{
 		RestrictInfo *rinfo = (RestrictInfo *) lfirst(l);
 
-		if (bms_is_subset(rinfo->required_relids, joinrel->relids))
+		if (bms_is_subset(rinfo->required_relids, joinrelids))
 		{
 			/*
 			 * This clause becomes a restriction clause for the joinrel, since
@@ -1140,7 +1184,7 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
 			 * different joinlists will have been multiply-linked rather than
 			 * copied, pointer equality should be a sufficient test.)
 			 */
-			new_restrictlist = list_append_unique_ptr(new_restrictlist, rinfo);
+			addUniquePtrList(&upl, rinfo);
 		}
 		else
 		{
@@ -1151,7 +1195,8 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
 		}
 	}
 
-	return new_restrictlist;
+	hash_destroy(upl.h);
+	return upl.unique_list;
 }
 
 static List *
@@ -1160,6 +1205,10 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 						  List *new_joininfo)
 {
 	ListCell   *l;
+	UniquePtrList	upl;
+
+	memset(&upl, 0, sizeof(upl));
+	upl.unique_list = new_joininfo;
 
 	/* Expected to be called only for join between parent relations. */
 	Assert(joinrel->reloptkind == RELOPT_JOINREL);
@@ -1185,11 +1234,12 @@ subbuild_joinrel_joinlist(RelOptInfo *joinrel,
 			 * multiply-linked rather than copied, pointer equality should be
 			 * a sufficient test.)
 			 */
-			new_joininfo = list_append_unique_ptr(new_joininfo, rinfo);
+			addUniquePtrList(&upl, rinfo);
 		}
 	}
 
-	return new_joininfo;
+	hash_destroy(upl.h);
+	return upl.unique_list;
 }
 
 
diff --git a/src/backend/optimizer/util/var.c b/src/backend/optimizer/util/var.c
index ebc6ce84b0b..3fcbcb68d3e 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -840,7 +840,7 @@ flatten_join_alias_vars_mutator(Node *node,
 
 		phv = (PlaceHolderVar *) expression_tree_mutator(node,
 														 flatten_join_alias_vars_mutator,
-														 (void *) context);
+														 (void *) context, 0);
 		/* now fix PlaceHolderVar's relid sets */
 		if (phv->phlevelsup == context->sublevels_up)
 		{
@@ -876,7 +876,7 @@ flatten_join_alias_vars_mutator(Node *node,
 	Assert(!IsA(node, MinMaxAggInfo));
 
 	return expression_tree_mutator(node, flatten_join_alias_vars_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
diff --git a/src/backend/parser/gram.y b/src/backend/parser/gram.y
index c77060c2cea..ae96b3b4f14 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -14470,7 +14470,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr LIKE a_expr ESCAPE a_expr					%prec LIKE
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("like_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
 												 list_make2($3, $5),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14484,7 +14484,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr NOT_LA LIKE a_expr ESCAPE a_expr			%prec NOT_LA
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("like_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
 												 list_make2($4, $6),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14498,7 +14498,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr ILIKE a_expr ESCAPE a_expr					%prec ILIKE
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("like_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
 												 list_make2($3, $5),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14512,7 +14512,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr NOT_LA ILIKE a_expr ESCAPE a_expr			%prec NOT_LA
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("like_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("like_escape")),
 												 list_make2($4, $6),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14522,7 +14522,7 @@ a_expr:		c_expr									{ $$ = $1; }
 
 			| a_expr SIMILAR TO a_expr							%prec SIMILAR
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("similar_to_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")),
 												 list_make1($4),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14531,7 +14531,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr SIMILAR TO a_expr ESCAPE a_expr			%prec SIMILAR
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("similar_to_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")),
 												 list_make2($4, $6),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14540,7 +14540,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr NOT_LA SIMILAR TO a_expr					%prec NOT_LA
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("similar_to_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")),
 												 list_make1($5),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
@@ -14549,7 +14549,7 @@ a_expr:		c_expr									{ $$ = $1; }
 				}
 			| a_expr NOT_LA SIMILAR TO a_expr ESCAPE a_expr		%prec NOT_LA
 				{
-					FuncCall   *n = makeFuncCall(SystemFuncName("similar_to_escape"),
+					FuncCall *n = makeFuncCall(list_make1(makeString("similar_to_escape")),
 												 list_make2($5, $7),
 												 COERCE_EXPLICIT_CALL,
 												 @2);
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index d821969a7d6..23173c82d89 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1199,6 +1199,7 @@ transformFromClauseItem(ParseState *pstate, Node *n,
 										  &r_namespace);
 
 		/* Remove the left-side RTEs from the namespace list again */
+
 		pstate->p_namespace = list_truncate(pstate->p_namespace,
 											sv_namespace_length);
 
diff --git a/src/backend/parser/parse_relation.c b/src/backend/parser/parse_relation.c
index e0066830299..b1cdb68848f 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2195,7 +2195,6 @@ addRangeTableEntryForJoin(ParseState *pstate,
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
 	Alias	   *eref;
-	int			numaliases;
 	ParseNamespaceItem *nsitem;
 
 	Assert(pstate != NULL);
@@ -2221,19 +2220,37 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	rte->join_using_alias = join_using_alias;
 	rte->alias = alias;
 
-	eref = alias ? copyObject(alias) : makeAlias("unnamed_join", NIL);
-	numaliases = list_length(eref->colnames);
-
 	/* fill in any unspecified alias columns */
-	if (numaliases < list_length(colnames))
-		eref->colnames = list_concat(eref->colnames,
-									 list_copy_tail(colnames, numaliases));
+	if (alias)
+	{
+		int	numaliases;
 
-	if (numaliases > list_length(colnames))
-		ereport(ERROR,
-				(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
-				 errmsg("join expression \"%s\" has %d columns available but %d columns specified",
-						eref->aliasname, list_length(colnames), numaliases)));
+		eref = copyObject(alias);
+
+		numaliases = list_length(eref->colnames);
+
+		if (numaliases == 0)
+		{
+			eref->colnames = colnames;
+		}
+		else if (numaliases > 0 && numaliases < list_length(colnames))
+		{
+			eref->colnames = list_concat(eref->colnames,
+										 list_copy_tail(colnames, numaliases));
+			list_free(colnames);
+		}
+
+		if (numaliases > list_length(colnames))
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_COLUMN_REFERENCE),
+					 errmsg("join expression \"%s\" has %d columns available but %d columns specified",
+							eref->aliasname, list_length(colnames), numaliases)));
+	}
+	else
+	{
+		eref = makeAlias("unnamed_join", NIL);
+		eref->colnames = colnames;
+	}
 
 	rte->eref = eref;
 
@@ -2885,8 +2902,11 @@ expandRTE(RangeTblEntry *rte, int rtindex, int sublevels_up,
 					{
 						char	   *label = strVal(lfirst(colname));
 
-						*colnames = lappend(*colnames,
-											makeString(pstrdup(label)));
+						/*
+						 * Assume label is already pstrdup'ed somewhere, so
+						 * don't duplicate it again
+						 */
+						*colnames = lappend(*colnames, makeString(label));
 					}
 
 					if (colvars)
diff --git a/src/backend/rewrite/rewriteManip.c b/src/backend/rewrite/rewriteManip.c
index 6d4985abad9..3372c9a5fa9 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1119,7 +1119,7 @@ replace_rte_variables(Node *node, int target_varno, int sublevels_up,
 	result = query_or_expression_tree_mutator(node,
 											  replace_rte_variables_mutator,
 											  (void *) &context,
-											  0);
+											  QTW_DONT_COPY_DEFAULT);
 
 	if (context.inserted_sublink)
 	{
@@ -1189,14 +1189,15 @@ replace_rte_variables_mutator(Node *node,
 		newnode = query_tree_mutator((Query *) node,
 									 replace_rte_variables_mutator,
 									 (void *) context,
-									 0);
+									 QTW_DONT_COPY_DEFAULT);
 		newnode->hasSubLinks |= context->inserted_sublink;
 		context->inserted_sublink = save_inserted_sublink;
 		context->sublevels_up--;
 		return (Node *) newnode;
 	}
-	return expression_tree_mutator(node, replace_rte_variables_mutator,
-								   (void *) context);
+
+	return  expression_tree_mutator(node, replace_rte_variables_mutator,
+									(void *) context, QTW_DONT_COPY_DEFAULT);
 }
 
 
@@ -1353,7 +1354,7 @@ map_variable_attnos_mutator(Node *node,
 		return (Node *) newnode;
 	}
 	return expression_tree_mutator(node, map_variable_attnos_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 Node *
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 3c04c9dabda..59f5ee682e7 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -945,6 +945,7 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open)
 	int			used = 0;
 	int			highestfd = 0;
 	int			j;
+	int			fdTest = 0;
 
 #ifdef HAVE_GETRLIMIT
 	struct rlimit rlim;
@@ -964,6 +965,15 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open)
 		ereport(WARNING, (errmsg("getrlimit failed: %m")));
 #endif							/* HAVE_GETRLIMIT */
 
+#ifdef WIN32
+	/*
+	 * we have error on Windows7 with max_files_per_process > 1200 when dup(0) - stdin
+	 * make test on postgresql.conf file
+	 */
+	fdTest = _open(ConfigFileName, _O_RDONLY);
+	if (fdTest < 0)
+		fdTest = 0; /* fallback to stdin */
+#endif
 	/* dup until failure or probe limit reached */
 	for (;;)
 	{
@@ -979,7 +989,7 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open)
 			break;
 #endif
 
-		thisfd = dup(2);
+		thisfd = dup(fdTest);
 		if (thisfd < 0)
 		{
 			/* Expect EMFILE or ENFILE, else it's fishy */
@@ -1006,6 +1016,10 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open)
 	for (j = 0; j < used; j++)
 		close(fd[j]);
 
+#ifdef WIN32
+	if (fdTest>0)
+		_close(fdTest);
+#endif
 	pfree(fd);
 
 	/*
diff --git a/src/backend/storage/ipc/sinval.c b/src/backend/storage/ipc/sinval.c
index b6ae7316472..b13b75f7346 100644
--- a/src/backend/storage/ipc/sinval.c
+++ b/src/backend/storage/ipc/sinval.c
@@ -15,6 +15,7 @@
 #include "postgres.h"
 
 #include "access/xact.h"
+#include "access/xlog.h"
 #include "commands/async.h"
 #include "miscadmin.h"
 #include "nodes/memnodes.h"
@@ -49,6 +50,20 @@ volatile sig_atomic_t catchupInterruptPending = false;
 void
 SendSharedInvalidMessages(const SharedInvalidationMessage *msgs, int n)
 {
+	int i;
+
+	if (MyDatabaseId != InvalidOid)
+	{
+		for(i=0; i<n; i++)
+		{
+			/* sync with SIInsertDataEntries() */
+			 if ((msgs[i].id == SHAREDINVALRELCACHE_ID &&
+				 msgs[i].rc.isLocal == true) ||
+				 (msgs[i].id >= 0 && msgs[i].cc.isLocal == true))
+				LocalExecuteInvalidationMessage(((SharedInvalidationMessage*)msgs) + i);
+		}
+	}
+
 	SIInsertDataEntries(msgs, n);
 }
 
@@ -103,7 +118,6 @@ ReceiveSharedInvalidMessages(void (*invalFunction) (SharedInvalidationMessage *m
 		if (getResult < 0)
 		{
 			/* got a reset message */
-			elog(DEBUG4, "cache state reset");
 			SharedInvalidMessageCounter++;
 			resetFunction();
 			break;				/* nothing more to do */
diff --git a/src/backend/storage/ipc/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index 21f0ff97579..078d874c40e 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -18,6 +18,7 @@
 #include <unistd.h>
 
 #include "access/transam.h"
+#include "access/xlog.h"
 #include "miscadmin.h"
 #include "storage/backendid.h"
 #include "storage/ipc.h"
@@ -127,8 +128,8 @@
  * per iteration.
  */
 
-#define MAXNUMMESSAGES 4096
-#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 262144)
+#define MAXNUMMESSAGES 16384
+#define MSGNUMWRAPAROUND (MAXNUMMESSAGES * 65536)
 #define CLEANUP_MIN (MAXNUMMESSAGES / 2)
 #define CLEANUP_QUANTUM (MAXNUMMESSAGES / 16)
 #define SIG_THRESHOLD (MAXNUMMESSAGES / 2)
@@ -486,8 +487,16 @@ SIInsertDataEntries(const SharedInvalidationMessage *data, int n)
 		max = segP->maxMsgNum;
 		while (nthistime-- > 0)
 		{
-			segP->buffer[max % MAXNUMMESSAGES] = *data++;
-			max++;
+			/* sync with SendSharedInvalidMessages() */
+			if ((MyDatabaseId == InvalidOid) || !((data->id == SHAREDINVALRELCACHE_ID &&
+				   data->rc.isLocal == true) || (
+				   data->id >= 0 && data->cc.isLocal == true)))
+			{
+				segP->buffer[max % MAXNUMMESSAGES] = *data;
+				max++;
+			}
+
+			data++;
 		}
 
 		/* Update current value of maxMsgNum using spinlock */
diff --git a/src/backend/storage/smgr/smgr.c b/src/backend/storage/smgr/smgr.c
index 586f294ffdc..fad41fbf8fb 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -459,7 +459,8 @@ smgrdounlinkall(SMgrRelation *rels, int nrels, bool isRedo)
 	 * closed our own smgr rel.
 	 */
 	for (i = 0; i < nrels; i++)
-		CacheInvalidateSmgr(rnodes[i]);
+		if (!SmgrIsTemp(rels[i]))
+			CacheInvalidateSmgr(rnodes[i]);
 
 	/*
 	 * Delete the physical file(s).
@@ -670,7 +671,8 @@ smgrtruncate2(SMgrRelation reln, ForkNumber *forknum, int nforks,
 	 * is a performance-critical path.)  As in the unlink code, we want to be
 	 * sure the message is sent before we start changing things on-disk.
 	 */
-	CacheInvalidateSmgr(reln->smgr_rnode);
+	if (!SmgrIsTemp(reln))
+		CacheInvalidateSmgr(reln->smgr_rnode);
 
 	/* Do the truncation */
 	for (i = 0; i < nforks; i++)
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index a6a13dc71ba..303cc6f2be1 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -75,6 +75,7 @@
 #include "tcop/pquery.h"
 #include "tcop/tcopprot.h"
 #include "tcop/utility.h"
+#include "utils/builtins.h"
 #include "utils/lsyscache.h"
 #include "utils/memutils.h"
 #include "utils/ps_status.h"
diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c
index 2d3aaaaf6bd..a4609a89861 100644
--- a/src/backend/utils/adt/like_support.c
+++ b/src/backend/utils/adt/like_support.c
@@ -55,20 +55,9 @@
 #include "utils/selfuncs.h"
 #include "utils/varlena.h"
 
-
-typedef enum
-{
-	Pattern_Type_Like,
-	Pattern_Type_Like_IC,
-	Pattern_Type_Regex,
-	Pattern_Type_Regex_IC,
-	Pattern_Type_Prefix
-} Pattern_Type;
-
-typedef enum
-{
-	Pattern_Prefix_None, Pattern_Prefix_Partial, Pattern_Prefix_Exact
-} Pattern_Prefix_Status;
+#include "catalog/pg_proc.h"
+#include "utils/catcache.h"
+#include "utils/syscache.h"
 
 static Node *like_regex_support(Node *rawreq, Pattern_Type ptype);
 static List *match_pattern_prefix(Node *leftop,
@@ -108,6 +97,119 @@ static Datum string_to_datum(const char *str, Oid datatype);
 static Const *string_to_const(const char *str, Oid datatype);
 static Const *string_to_bytea_const(const char *str, size_t str_len);
 
+/****************************************************************************
+ *		  ----  ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS  FOR
+ *						  SPECIAL USER_DEFINED TYPES ----
+ *								  -- teodor
+ ****************************************************************************/
+
+static Oid mmPFPOid = InvalidOid;
+static Oid mmGTOid = InvalidOid;
+static Oid mcharOid = InvalidOid;
+static Oid mvarcharOid = InvalidOid;
+
+#define	HeapTupleGetOid(type, tuple) (((type)GETSTRUCT(tuple))->oid)
+
+static Oid
+findTypeOid(char *typname)
+{
+	CatCList	*catlist;
+	HeapTuple   tup;
+	int		 n_members;
+	Oid		 typoid;
+
+	catlist = SearchSysCacheList(TYPENAMENSP, 1,
+								 CStringGetDatum(typname), 0, 0);
+
+	n_members = catlist->n_members;
+
+	if (n_members != 1)
+	{
+		ReleaseSysCacheList(catlist);
+		if (n_members > 1)
+			elog(ERROR,"There are %d candidates for '%s' type",
+				 n_members, typname);
+		return InvalidOid;
+	}
+
+	tup = &catlist->members[0]->tuple;
+
+	typoid = HeapTupleGetOid(Form_pg_type, tup);
+
+	ReleaseSysCacheList(catlist);
+
+	return typoid;
+}
+
+static bool
+fillMCharOIDS() {
+	CatCList	*catlist;
+	HeapTuple   tup;
+	char		*funcname = "mchar_pattern_fixed_prefix";
+	int		 n_members;
+
+	catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1,
+								 CStringGetDatum(funcname), 0, 0);
+	n_members = catlist->n_members;
+
+	if (n_members != 1) {
+		ReleaseSysCacheList(catlist);
+		if (n_members > 1)
+			elog(ERROR,"There are %d candidates for '%s' function'", n_members, funcname);
+		return false;
+	}
+
+	tup = &catlist->members[0]->tuple;
+
+	if ( HeapTupleGetOid(Form_pg_proc, tup) != mmPFPOid ) {
+		char	   *quals_funcname = "mchar_greaterstring";
+		Oid		 tmp_mmPFPOid = HeapTupleGetOid(Form_pg_proc, tup);
+
+		ReleaseSysCacheList(catlist);
+
+		mcharOid = findTypeOid("mchar");
+		mvarcharOid = findTypeOid("mvarchar");
+
+		if ( mcharOid == InvalidOid || mvarcharOid == InvalidOid ) {
+			elog(LOG,"Can't find mchar/mvarvarchar types: mchar=%d mvarchar=%d", 
+					mcharOid, mvarcharOid);
+			return false;
+		}
+
+		catlist = SearchSysCacheList(PROCNAMEARGSNSP, 1,
+									 CStringGetDatum(quals_funcname), 0, 0);
+		n_members = catlist->n_members;
+
+		if ( n_members != 1 ) {
+			ReleaseSysCacheList(catlist);
+			if ( n_members > 1 )
+				elog(ERROR,"There are %d candidates for '%s' function'", n_members, quals_funcname);
+			return false;
+		}
+
+		tup = &catlist->members[0]->tuple;
+		mmGTOid  = HeapTupleGetOid(Form_pg_proc, tup);
+		mmPFPOid = tmp_mmPFPOid;
+	}
+
+	ReleaseSysCacheList(catlist);
+
+	return true;
+}
+
+static Pattern_Prefix_Status
+mchar_pattern_fixed_prefix(Const *patt, Pattern_Type ptype, Const **prefix)
+{
+	if (!fillMCharOIDS())
+		return Pattern_Prefix_None;
+
+	return (Pattern_Prefix_Status)DatumGetInt32( OidFunctionCall3(
+		mmPFPOid,
+		PointerGetDatum(patt),
+		Int32GetDatum(ptype),
+		PointerGetDatum(prefix)
+	) );
+}
 
 /*
  * Planner support functions for LIKE, regex, and related operators
@@ -260,6 +362,7 @@ match_pattern_prefix(Node *leftop,
 	Expr	   *expr;
 	FmgrInfo	ltproc;
 	Const	   *greaterstr;
+	bool		isMchar = false;
 
 	/*
 	 * Can't do anything with a non-constant or NULL pattern argument.
@@ -292,8 +395,16 @@ match_pattern_prefix(Node *leftop,
 	/*
 	 * Try to extract a fixed prefix from the pattern.
 	 */
-	pstatus = pattern_fixed_prefix(patt, ptype, expr_coll,
-								   &prefix, NULL);
+	ldatatype = exprType(leftop);
+	if (fillMCharOIDS() && (ldatatype == mcharOid ||
+							ldatatype == mvarcharOid))
+	{
+		pstatus = mchar_pattern_fixed_prefix(patt, ptype, &prefix);
+		isMchar = true;
+	}
+	else
+		pstatus = pattern_fixed_prefix(patt, ptype, expr_coll,
+									   &prefix, NULL);
 
 	/* fail if no fixed prefix */
 	if (pstatus == Pattern_Prefix_None)
@@ -308,7 +419,6 @@ match_pattern_prefix(Node *leftop,
 	 * selected operators also determine the needed type of the prefix
 	 * constant.
 	 */
-	ldatatype = exprType(leftop);
 	switch (ldatatype)
 	{
 		case TEXTOID:
@@ -375,7 +485,16 @@ match_pattern_prefix(Node *leftop,
 			break;
 		default:
 			/* Can't get here unless we're attached to the wrong operator */
-			return NIL;
+			if (!isMchar)
+				return NIL;
+			collation_aware = false;
+			rdatatype = mvarcharOid;
+			ltopr = get_opfamily_member(opfamily, ldatatype, rdatatype,
+										BTLessStrategyNumber);
+			eqopr = get_opfamily_member(opfamily, ldatatype, rdatatype,
+										BTEqualStrategyNumber);
+			geopr = get_opfamily_member(opfamily, ldatatype, rdatatype,
+										BTGreaterEqualStrategyNumber);
 	}
 
 	/*
@@ -387,9 +506,10 @@ match_pattern_prefix(Node *leftop,
 	 */
 	if (prefix->consttype != rdatatype)
 	{
-		Assert(prefix->consttype == TEXTOID &&
-			   rdatatype == BPCHAROID);
-		prefix->consttype = rdatatype;
+		Assert(isMchar || (prefix->consttype == TEXTOID &&
+			   rdatatype == BPCHAROID));
+		if (!isMchar)
+			prefix->consttype = rdatatype;
 	}
 
 	/*
@@ -458,7 +578,12 @@ match_pattern_prefix(Node *leftop,
 	if (!op_in_opfamily(ltopr, opfamily))
 		return result;
 	fmgr_info(get_opcode(ltopr), &ltproc);
-	greaterstr = make_greater_string(prefix, &ltproc, indexcollation);
+	if (isMchar)
+		greaterstr = (Const*)DatumGetPointer(OidFunctionCall1(
+												mmGTOid,
+												PointerGetDatum(prefix)));
+	else
+		greaterstr = make_greater_string(prefix, &ltproc, indexcollation);
 	if (greaterstr)
 	{
 		expr = make_opclause(ltopr, BOOLOID, false,
diff --git a/src/backend/utils/adt/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index db843a0fbf0..a115692102a 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -28,7 +28,6 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
-
 /*
  * structure to cache metadata needed for record I/O
  */
@@ -807,6 +806,9 @@ record_cmp(FunctionCallInfo fcinfo)
 {
 	HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
 	HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
+	int				record_cmp_prefix =
+							(PG_NARGS() == 3 && PG_GETARG_INT32(2) > 0) ?
+									PG_GETARG_INT32(2) : INT_MAX;
 	int			result = 0;
 	Oid			tupType1;
 	Oid			tupType2;
@@ -891,6 +893,9 @@ record_cmp(FunctionCallInfo fcinfo)
 	nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
 	heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
 
+	ncolumns1 = Min(ncolumns1, record_cmp_prefix);
+	ncolumns2 = Min(ncolumns2, record_cmp_prefix);
+
 	/*
 	 * Scan corresponding columns, allowing for dropped columns in different
 	 * places in the two rows.  i1 and i2 are physical column indexes, j is
@@ -1051,6 +1056,9 @@ record_eq(PG_FUNCTION_ARGS)
 {
 	HeapTupleHeader record1 = PG_GETARG_HEAPTUPLEHEADER(0);
 	HeapTupleHeader record2 = PG_GETARG_HEAPTUPLEHEADER(1);
+	int				record_cmp_prefix =
+							(PG_NARGS() == 3 && PG_GETARG_INT32(2) > 0) ?
+									PG_GETARG_INT32(2) : INT_MAX;
 	bool		result = true;
 	Oid			tupType1;
 	Oid			tupType2;
@@ -1135,6 +1143,9 @@ record_eq(PG_FUNCTION_ARGS)
 	nulls2 = (bool *) palloc(ncolumns2 * sizeof(bool));
 	heap_deform_tuple(&tuple2, tupdesc2, values2, nulls2);
 
+	ncolumns1 = Min(ncolumns1, record_cmp_prefix);
+	ncolumns2 = Min(ncolumns2, record_cmp_prefix);
+
 	/*
 	 * Scan corresponding columns, allowing for dropped columns in different
 	 * places in the two rows.  i1 and i2 are physical column indexes, j is
diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c
index 59c9fba3827..9a245339088 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -40,6 +40,7 @@
 #include "catalog/pg_type.h"
 #include "commands/defrem.h"
 #include "commands/tablespace.h"
+#include "common/hashfn.h"
 #include "common/keywords.h"
 #include "executor/spi.h"
 #include "funcapi.h"
@@ -291,6 +292,8 @@ typedef struct
 	int		   *leftattnos;		/* left-child varattnos of join cols, or 0 */
 	int		   *rightattnos;	/* right-child varattnos of join cols, or 0 */
 	List	   *usingNames;		/* names assigned to merged columns */
+
+	HTAB		*all_names;		/* hash to store all names colname_is_unique() */
 } deparse_columns;
 
 /* This macro is analogous to rt_fetch(), but for deparse_columns structs */
@@ -371,6 +374,7 @@ static void set_relation_column_names(deparse_namespace *dpns,
 									  deparse_columns *colinfo);
 static void set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 								  deparse_columns *colinfo);
+static void add_colname(deparse_columns *colinfo, char *colname);
 static bool colname_is_unique(const char *colname, deparse_namespace *dpns,
 							  deparse_columns *colinfo);
 static char *make_colname_unique(char *colname, deparse_namespace *dpns,
@@ -4700,7 +4704,10 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 					changed_any = true;
 			}
 			else
+			{
 				colinfo->new_colnames[j] = child_colname;
+				add_colname(colinfo, child_colname);
+			}
 		}
 
 		colinfo->is_new_col[j] = leftcolinfo->is_new_col[jc];
@@ -4749,7 +4756,10 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 					changed_any = true;
 			}
 			else
+			{
 				colinfo->new_colnames[j] = child_colname;
+				add_colname(colinfo, child_colname);
+			}
 		}
 
 		colinfo->is_new_col[j] = rightcolinfo->is_new_col[jc];
@@ -4774,6 +4784,29 @@ set_join_column_names(deparse_namespace *dpns, RangeTblEntry *rte,
 		colinfo->printaliases = false;
 }
 
+static uint32
+pstring_hash(const void *key, Size keysize)
+{
+	return string_hash(*(const void **)key, NAMEDATALEN);
+}
+
+static int
+pstring_compare(const void *key1, const void *key2, Size keysize)
+{
+	return strncmp(*(const void **)key1, *(const void **)key2, keysize - 1);
+}
+
+static void
+add_colname(deparse_columns *colinfo, char *colname)
+{
+	if (colinfo->all_names)
+	{
+		bool found;
+
+		hash_search(colinfo->all_names, &colname, HASH_ENTER, &found);
+	}
+}
+
 /*
  * colname_is_unique: is colname distinct from already-chosen column names?
  *
@@ -4786,6 +4819,75 @@ colname_is_unique(const char *colname, deparse_namespace *dpns,
 	int			i;
 	ListCell   *lc;
 
+	if (colinfo->all_names != NULL ||
+		(colinfo->num_cols + colinfo->num_new_cols +
+		 list_length(dpns->using_names) +
+		 list_length(colinfo->parentUsing)) > 64)
+	{
+		bool	found;
+
+		if (colinfo->all_names == NULL)
+		{
+			HASHCTL	hash_ctl;
+
+			MemSet(&hash_ctl, 0, sizeof(hash_ctl));
+
+			hash_ctl.keysize = sizeof(char*);
+			hash_ctl.entrysize = sizeof(char*);
+			hash_ctl.hash = pstring_hash;
+			hash_ctl.match = pstring_compare;
+
+			colinfo->all_names = hash_create("colname_is_unique storage",
+											 512, &hash_ctl,
+											 HASH_ELEM | HASH_FUNCTION | HASH_COMPARE);
+
+
+			for (i = 0; i < colinfo->num_cols; i++)
+			{
+				if (colinfo->colnames[i] == NULL)
+					continue;
+
+				hash_search(colinfo->all_names, &colinfo->colnames[i],
+							HASH_ENTER, &found);
+			}
+
+			for (i = 0; i < colinfo->num_new_cols; i++)
+			{
+				if (colinfo->new_colnames[i] == NULL)
+					continue;
+
+				hash_search(colinfo->all_names, &colinfo->new_colnames[i],
+							HASH_ENTER, &found);
+			}
+
+			foreach(lc, dpns->using_names)
+			{
+				char	 *oldname = (char *) lfirst(lc);
+
+				hash_search(colinfo->all_names, &oldname,
+							HASH_ENTER, &found);
+			}
+
+			foreach(lc, colinfo->parentUsing)
+			{
+				char	 *oldname = (char *) lfirst(lc);
+
+				hash_search(colinfo->all_names, &oldname,
+							HASH_ENTER, &found);
+			}
+		}
+
+		hash_search(colinfo->all_names, &colname, HASH_FIND, &found);
+
+		if (found)
+			return false;
+
+		hash_search(colinfo->all_names, &colname, HASH_ENTER, &found);
+		Assert(found == false);
+
+		return true;
+	}
+
 	/* Check against already-assigned column aliases within RTE */
 	for (i = 0; i < colinfo->num_cols; i++)
 	{
@@ -4837,6 +4939,8 @@ static char *
 make_colname_unique(char *colname, deparse_namespace *dpns,
 					deparse_columns *colinfo)
 {
+	CHECK_FOR_INTERRUPTS();
+
 	/*
 	 * If the selected name isn't unique, append digits to make it so.  For a
 	 * very long input name, we might have to truncate to stay within
diff --git a/src/backend/utils/adt/selfuncs.c b/src/backend/utils/adt/selfuncs.c
index 829aa0128d9..0da63db952f 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -145,13 +145,14 @@ get_relation_stats_hook_type get_relation_stats_hook = NULL;
 get_index_stats_hook_type get_index_stats_hook = NULL;
 
 static double eqsel_internal(PG_FUNCTION_ARGS, bool negate);
-static double eqjoinsel_inner(Oid opfuncoid, Oid collation,
+static double eqjoinsel_inner(Oid operator, Oid opfuncoid, Oid collation,
 							  VariableStatData *vardata1, VariableStatData *vardata2,
 							  double nd1, double nd2,
 							  bool isdefault1, bool isdefault2,
 							  AttStatsSlot *sslot1, AttStatsSlot *sslot2,
 							  Form_pg_statistic stats1, Form_pg_statistic stats2,
-							  bool have_mcvs1, bool have_mcvs2);
+							  bool have_mcvs1, bool have_mcvs2,
+							  int record_cmp_prefix);
 static double eqjoinsel_semi(Oid opfuncoid, Oid collation,
 							 VariableStatData *vardata1, VariableStatData *vardata2,
 							 double nd1, double nd2,
@@ -159,7 +160,8 @@ static double eqjoinsel_semi(Oid opfuncoid, Oid collation,
 							 AttStatsSlot *sslot1, AttStatsSlot *sslot2,
 							 Form_pg_statistic stats1, Form_pg_statistic stats2,
 							 bool have_mcvs1, bool have_mcvs2,
-							 RelOptInfo *inner_rel);
+							 RelOptInfo *inner_rel,
+							 int record_cmp_prefix);
 static bool estimate_multivariate_ndistinct(PlannerInfo *root,
 											RelOptInfo *rel, List **varinfos, double *ndistinct);
 static bool convert_to_scalar(Datum value, Oid valuetypid, Oid collid,
@@ -211,6 +213,20 @@ static bool get_actual_variable_endpoint(Relation heapRel,
 										 Datum *endpointDatum);
 static RelOptInfo *find_join_input_rel(PlannerInfo *root, Relids relids);
 
+static bool
+join_is_reversed_variables(SpecialJoinInfo *sjinfo,
+						   VariableStatData *vardata1, VariableStatData *vardata2)
+{
+	if (vardata1->rel &&
+		bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand))
+		return true;	/* var1 is on RHS */
+	else if (vardata2->rel &&
+			 bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand))
+		return true;	/* var2 is on LHS */
+	else
+		return false;
+}
+
 
 /*
  *		eqsel			- Selectivity of "=" for any data types.
@@ -283,6 +299,34 @@ eqsel_internal(PG_FUNCTION_ARGS, bool negate)
 	return selec;
 }
 
+static bool
+get_cached_attstatsslot(AttStatsSlot *sslot, VariableStatData *vardata,
+						int reqkind, Oid reqop, int flags)
+{
+	if (vardata->sslots)
+	{
+		/*
+		* vardata has somewhere cache
+		*/
+		AttStatsSlot	*sslotp;
+
+		sslotp = fill_attstatsslot(vardata->sslots,
+									vardata->statsTuple,
+									reqkind, reqop, flags);
+
+		if (sslotp)
+		{
+			*sslot = *sslotp;
+			return true;
+		}
+	}
+
+	return get_attstatsslot(sslot, vardata->statsTuple,
+							reqkind, reqop,
+							flags);
+}
+
+
 /*
  * var_eq_const --- eqsel for var = const case
  *
@@ -292,6 +336,15 @@ double
 var_eq_const(VariableStatData *vardata, Oid operator, Oid collation,
 			 Datum constval, bool constisnull,
 			 bool varonleft, bool negate)
+{
+	return eqconst_selectivity(operator, collation, vardata, constval,
+							   constisnull, varonleft, negate, -1);
+}
+
+Selectivity
+eqconst_selectivity(Oid operator, Oid collation,
+					VariableStatData *vardata, Datum constval, bool constisnull,
+					bool varonleft, bool negate, int record_cmp_prefix)
 {
 	double		selec;
 	double		nullfrac = 0.0;
@@ -324,7 +377,8 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation,
 	 * different from ours, but it's much more likely to be right than
 	 * ignoring the information.)
 	 */
-	if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0)
+	if (vardata->isunique && vardata->rel && vardata->rel->tuples >= 1.0 &&
+		record_cmp_prefix <= 0)
 	{
 		selec = 1.0 / vardata->rel->tuples;
 	}
@@ -343,11 +397,11 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation,
 		 * don't like this, maybe you shouldn't be using eqsel for your
 		 * operator...)
 		 */
-		if (get_attstatsslot(&sslot, vardata->statsTuple,
-							 STATISTIC_KIND_MCV, InvalidOid,
-							 ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))
+		if (get_cached_attstatsslot(&sslot, vardata,
+									STATISTIC_KIND_MCV, InvalidOid,
+									ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS))
 		{
-			LOCAL_FCINFO(fcinfo, 2);
+			LOCAL_FCINFO(fcinfo, 3);
 			FmgrInfo	eqproc;
 
 			fmgr_info(opfuncoid, &eqproc);
@@ -358,15 +412,17 @@ var_eq_const(VariableStatData *vardata, Oid operator, Oid collation,
 			 * eqproc returns NULL, though really equality functions should
 			 * never do that.
 			 */
-			InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation,
+			InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation,
 									 NULL, NULL);
 			fcinfo->args[0].isnull = false;
 			fcinfo->args[1].isnull = false;
+			fcinfo->args[2].isnull = false;
 			/* be careful to apply operator right way 'round */
 			if (varonleft)
 				fcinfo->args[1].value = constval;
 			else
 				fcinfo->args[0].value = constval;
+			fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix);
 
 			for (i = 0; i < sslot.nvalues; i++)
 			{
@@ -1016,6 +1072,138 @@ generic_restriction_selectivity(PlannerInfo *root, Oid oproid, Oid collation,
 	return selec;
 }
 
+/*
+ * Binary search of bound constval in histogramm
+ */
+static int
+prefix_record_histogram_search(AttStatsSlot	*sslot, int start,
+							   Datum constval,  int record_cmp_prefix,
+							   FmgrInfo	*opproc, bool isgt)
+{
+	int			lobound = start;	/* first possible slot to search */
+	int			hibound = sslot->nvalues;	/* last+1 slot to search */
+
+	while (lobound < hibound)
+	{
+		int			probe = (lobound + hibound) / 2;
+		bool		ltcmp;
+
+		ltcmp = DatumGetBool(FunctionCall3Coll(opproc,
+											   DEFAULT_COLLATION_OID,
+											   sslot->values[probe],
+											   constval,
+											   Int32GetDatum(record_cmp_prefix)));
+		if (isgt)
+			ltcmp = !ltcmp;
+		if (ltcmp)
+			lobound = probe + 1;
+		else
+			hibound = probe;
+	}
+
+	return lobound;
+}
+
+/*
+ * Simple function to estimate selctivity by prefix of record, it just counts
+ * number of histogram bins matched by record prefix - similar to
+ * histogram_selectivity() but it knows about sortability of record
+ */
+double
+prefix_record_histogram_selectivity(VariableStatData *vardata,
+									Datum constvalLeft, Datum constvalRight,
+									int record_cmp_prefix,
+									double ndistinct,int *n_bins)
+{
+	double		result = -1.0;
+	AttStatsSlot sslot;
+
+	if (HeapTupleIsValid(vardata->statsTuple) &&
+		get_cached_attstatsslot(&sslot, vardata,
+								STATISTIC_KIND_HISTOGRAM, InvalidOid,
+								ATTSTATSSLOT_VALUES))
+	{
+		FmgrInfo	opprocLT, opprocGT;
+		int			start = -1,
+					end = -1;
+
+
+		if (sslot.nvalues > 2)
+		{
+			fmgr_info(F_RECORD_GE, &opprocGT);
+			fmgr_info(F_RECORD_LE, &opprocLT);
+
+			start = prefix_record_histogram_search(&sslot, 0, constvalLeft,
+												   record_cmp_prefix,
+												   &opprocGT, true);
+			if (start < 0)
+				start = 0;
+			end   = prefix_record_histogram_search(&sslot, start, constvalRight,
+												   -1,
+												   &opprocLT, false);
+			if (end >= sslot.nvalues)
+				end = sslot.nvalues - 1;
+		}
+		else
+		{
+			fmgr_info(F_RECORD_GT, &opprocGT);
+			fmgr_info(F_RECORD_LE, &opprocLT);
+
+			/*
+			 *  Find first bin which start border is less than constant
+			 */
+			for (start = sslot.nvalues - 1; start >= 0; start--)
+			{
+				if (DatumGetBool(FunctionCall3Coll(&opprocGT,
+												   DEFAULT_COLLATION_OID,
+												   constvalLeft,
+												   sslot.values[start],
+												   Int32GetDatum(record_cmp_prefix))))
+				break;
+			}
+
+			if (start < 0)
+				start=0;
+
+			/*
+			 * Find last bin which end border is less than constant
+			 */
+			for (end = start; end <= sslot.nvalues - 2; end ++)
+			{
+				if (DatumGetBool(FunctionCall3Coll(&opprocLT,
+												   DEFAULT_COLLATION_OID,
+												   constvalRight,
+												   sslot.values[end + 1],
+												   Int32GetDatum(-1))))
+					break;
+			}
+		}
+
+		if (opprocGT.fn_extra)
+			pfree(opprocGT.fn_extra);
+		if (opprocLT.fn_extra)
+			pfree(opprocLT.fn_extra);
+
+		*n_bins = (start >= end) ? 0 : end - start;
+		result = (start >= end) ? 0.5 :  end - start;
+		result /= ((double) (sslot.nvalues));
+
+		free_attstatsslot(&sslot);
+
+		if (*n_bins == 0 && ndistinct > 1)
+		{
+			double	ntuples = vardata->rel->tuples;
+			double  ntuplesbin = vardata->rel->tuples / sslot.nvalues;
+
+			result *= (1 - pow((ntuples - ntuplesbin) / ntuples,
+							   ntuples / ndistinct));
+		}
+	}
+
+	return result;
+
+}
+
 /*
  *	ineq_histogram_selectivity	- Examine the histogram for scalarineqsel
  *
@@ -2244,11 +2432,32 @@ eqjoinsel(PG_FUNCTION_ARGS)
 	JoinType	jointype = (JoinType) PG_GETARG_INT16(3);
 #endif
 	SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
-	Oid			collation = PG_GET_COLLATION();
+	VariableStatData	vardata1;
+	VariableStatData	vardata2;
+	Selectivity			s;
+	Oid collation = PG_GET_COLLATION();
+
+	get_join_variables(root, args, sjinfo,
+					   &vardata1, &vardata2, NULL);
+
+
+	s = eqjoin_selectivity(root, operator,  collation, &vardata1, &vardata2,
+						   sjinfo, -1);
+
+	ReleaseVariableStats(vardata1);
+	ReleaseVariableStats(vardata2);
+
+	PG_RETURN_FLOAT8((float8)s);
+}
+
+Selectivity
+eqjoin_selectivity(PlannerInfo *root, Oid operator, Oid collation,
+				   VariableStatData* vardata1,
+				   VariableStatData* vardata2, SpecialJoinInfo *sjinfo,
+				   int record_cmp_prefix)
+{
 	double		selec;
 	double		selec_inner;
-	VariableStatData vardata1;
-	VariableStatData vardata2;
 	double		nd1;
 	double		nd2;
 	bool		isdefault1;
@@ -2263,45 +2472,45 @@ eqjoinsel(PG_FUNCTION_ARGS)
 	bool		join_is_reversed;
 	RelOptInfo *inner_rel;
 
-	get_join_variables(root, args, sjinfo,
-					   &vardata1, &vardata2, &join_is_reversed);
+	join_is_reversed = join_is_reversed_variables(sjinfo, vardata1, vardata2);
 
-	nd1 = get_variable_numdistinct(&vardata1, &isdefault1);
-	nd2 = get_variable_numdistinct(&vardata2, &isdefault2);
+	nd1 = get_variable_numdistinct(vardata1, &isdefault1);
+	nd2 = get_variable_numdistinct(vardata2, &isdefault2);
 
 	opfuncoid = get_opcode(operator);
 
 	memset(&sslot1, 0, sizeof(sslot1));
 	memset(&sslot2, 0, sizeof(sslot2));
 
-	if (HeapTupleIsValid(vardata1.statsTuple))
+	if (HeapTupleIsValid(vardata1->statsTuple))
 	{
 		/* note we allow use of nullfrac regardless of security check */
-		stats1 = (Form_pg_statistic) GETSTRUCT(vardata1.statsTuple);
-		if (statistic_proc_security_check(&vardata1, opfuncoid))
-			have_mcvs1 = get_attstatsslot(&sslot1, vardata1.statsTuple,
-										  STATISTIC_KIND_MCV, InvalidOid,
-										  ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
+		stats1 = (Form_pg_statistic) GETSTRUCT(vardata1->statsTuple);
+		if (statistic_proc_security_check(vardata1, opfuncoid))
+			have_mcvs1 = get_cached_attstatsslot(&sslot1, vardata1,
+												 STATISTIC_KIND_MCV, InvalidOid,
+												 ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
 	}
 
-	if (HeapTupleIsValid(vardata2.statsTuple))
+	if (HeapTupleIsValid(vardata2->statsTuple))
 	{
 		/* note we allow use of nullfrac regardless of security check */
-		stats2 = (Form_pg_statistic) GETSTRUCT(vardata2.statsTuple);
-		if (statistic_proc_security_check(&vardata2, opfuncoid))
-			have_mcvs2 = get_attstatsslot(&sslot2, vardata2.statsTuple,
-										  STATISTIC_KIND_MCV, InvalidOid,
-										  ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
+		stats2 = (Form_pg_statistic) GETSTRUCT(vardata2->statsTuple);
+		if (statistic_proc_security_check(vardata2, opfuncoid))
+			have_mcvs2 = get_cached_attstatsslot(&sslot2, vardata2,
+												 STATISTIC_KIND_MCV, InvalidOid,
+												 ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
 	}
 
 	/* We need to compute the inner-join selectivity in all cases */
-	selec_inner = eqjoinsel_inner(opfuncoid, collation,
-								  &vardata1, &vardata2,
+	selec_inner = eqjoinsel_inner(operator, opfuncoid, collation,
+								  vardata1, vardata2,
 								  nd1, nd2,
 								  isdefault1, isdefault2,
 								  &sslot1, &sslot2,
 								  stats1, stats2,
-								  have_mcvs1, have_mcvs2);
+								  have_mcvs1, have_mcvs2,
+								  record_cmp_prefix);
 
 	switch (sjinfo->jointype)
 	{
@@ -2323,26 +2532,28 @@ eqjoinsel(PG_FUNCTION_ARGS)
 
 			if (!join_is_reversed)
 				selec = eqjoinsel_semi(opfuncoid, collation,
-									   &vardata1, &vardata2,
+									   vardata1, vardata2,
 									   nd1, nd2,
 									   isdefault1, isdefault2,
 									   &sslot1, &sslot2,
 									   stats1, stats2,
 									   have_mcvs1, have_mcvs2,
-									   inner_rel);
+									   inner_rel,
+									   record_cmp_prefix);
 			else
 			{
 				Oid			commop = get_commutator(operator);
 				Oid			commopfuncoid = OidIsValid(commop) ? get_opcode(commop) : InvalidOid;
 
 				selec = eqjoinsel_semi(commopfuncoid, collation,
-									   &vardata2, &vardata1,
+									   vardata2, vardata1,
 									   nd2, nd1,
 									   isdefault2, isdefault1,
 									   &sslot2, &sslot1,
 									   stats2, stats1,
 									   have_mcvs2, have_mcvs1,
-									   inner_rel);
+									   inner_rel,
+									   record_cmp_prefix);
 			}
 
 			/*
@@ -2368,12 +2579,132 @@ eqjoinsel(PG_FUNCTION_ARGS)
 	free_attstatsslot(&sslot1);
 	free_attstatsslot(&sslot2);
 
-	ReleaseVariableStats(vardata1);
-	ReleaseVariableStats(vardata2);
-
 	CLAMP_PROBABILITY(selec);
 
-	PG_RETURN_FLOAT8((float8) selec);
+	return selec;
+}
+
+static int
+cmp_vardata(FmgrInfo *eqproc, FmgrInfo *ltproc,
+			Datum v1, Datum v2, int record_cmp_prefix)
+{
+	int cmp;
+
+	cmp = DatumGetBool(FunctionCall3Coll(ltproc,
+										 DEFAULT_COLLATION_OID,
+										 v1, v2,
+										 Int32GetDatum(record_cmp_prefix)));
+
+	if (cmp)
+		return -1;
+
+	cmp = DatumGetBool(FunctionCall3Coll(eqproc,
+										 DEFAULT_COLLATION_OID,
+										 v1, v2,
+										 Int32GetDatum(record_cmp_prefix)));
+
+	return !cmp;
+}
+static double
+eqjoinsel_histogram(Oid eqop,
+					VariableStatData *vardata1, VariableStatData *vardata2,
+					int record_cmp_prefix, double nd1, double nd2)
+{
+	bool		have_hist1 = false;
+	bool		have_hist2 = false;
+	AttStatsSlot sslot1;
+	AttStatsSlot sslot2;
+	int			i1 = 0, i2 = 0;
+	double		n1 = 0.0, n2 = 0.0;
+	double		result = -1.0;
+	FmgrInfo	eqproc, ltproc;
+	Oid			orderop = InvalidOid;
+	List		*opfamilies;
+	ListCell	*lc;
+
+	if (!(HeapTupleIsValid(vardata1->statsTuple) &&
+		  HeapTupleIsValid(vardata2->statsTuple)))
+		return result;
+
+	memset(&sslot1, 0, sizeof(sslot1));
+	memset(&sslot2, 0, sizeof(sslot2));
+
+	have_hist1 = get_cached_attstatsslot(&sslot1, vardata1,
+										 STATISTIC_KIND_HISTOGRAM, InvalidOid,
+										 ATTSTATSSLOT_VALUES);
+	have_hist2 = get_cached_attstatsslot(&sslot2, vardata2,
+										 STATISTIC_KIND_HISTOGRAM, InvalidOid,
+										 ATTSTATSSLOT_VALUES);
+
+	if (!(have_hist1 && have_hist2))
+		goto out;
+
+	opfamilies = get_mergejoin_opfamilies(eqop);
+	foreach(lc, opfamilies) {
+		Oid opf = lfirst_oid(lc);
+
+		orderop = get_opfamily_member(opf, vardata1->vartype, vardata2->vartype,
+									  BTLessStrategyNumber);
+
+		if (OidIsValid(orderop))
+			break;
+	}
+
+	/* == from fulleq, for example */
+	if (!OidIsValid(orderop))
+		goto out;
+
+	fmgr_info(get_opcode(eqop), &eqproc);
+	fmgr_info(get_opcode(orderop), &ltproc);
+
+	result = 0.0;
+	while(i1 < sslot1.nvalues && i2 < sslot2.nvalues)
+	{
+		int	cmp;
+
+		cmp = cmp_vardata(&eqproc, &ltproc, sslot1.values[i1], sslot2.values[i2],
+						  record_cmp_prefix);
+
+		if (cmp < 0)
+		{
+			i1++;
+			n1++;
+			if (n2 > 0)
+			{
+				result += 0.5 / (sslot1.nvalues*sslot2.nvalues);
+				n2=0.0;
+			}
+		}
+		else if (cmp > 0)
+		{
+			i2++;
+			n2++;
+			if (n1 > 0)
+			{
+				result += 0.5 / (sslot1.nvalues*sslot2.nvalues);
+				n1=0.0;
+			}
+		}
+		else
+		{
+			i1++; i2++;
+			n1++; n2++;
+			result += (n1/sslot1.nvalues)*(n2/sslot2.nvalues);
+			n1 = 0.0; n2 = 0.0;
+		}
+
+	}
+
+	nd1 /= sslot1.nvalues;
+	nd2 /= sslot2.nvalues;
+
+	result /= (nd1 > nd2) ? nd1 : nd2;
+
+out:
+	free_attstatsslot(&sslot1);
+	free_attstatsslot(&sslot2);
+
+	return result;
 }
 
 /*
@@ -2383,13 +2714,14 @@ eqjoinsel(PG_FUNCTION_ARGS)
  * that it's worth trying to distinguish them here.
  */
 static double
-eqjoinsel_inner(Oid opfuncoid, Oid collation,
+eqjoinsel_inner(Oid operator, Oid opfuncoid, Oid collation,
 				VariableStatData *vardata1, VariableStatData *vardata2,
 				double nd1, double nd2,
 				bool isdefault1, bool isdefault2,
 				AttStatsSlot *sslot1, AttStatsSlot *sslot2,
 				Form_pg_statistic stats1, Form_pg_statistic stats2,
-				bool have_mcvs1, bool have_mcvs2)
+				bool have_mcvs1, bool have_mcvs2,
+				int record_cmp_prefix)
 {
 	double		selec;
 
@@ -2407,7 +2739,7 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation,
 		 * results", Technical Report 1018, Computer Science Dept., University
 		 * of Wisconsin, Madison, March 1991 (available from ftp.cs.wisc.edu).
 		 */
-		LOCAL_FCINFO(fcinfo, 2);
+		LOCAL_FCINFO(fcinfo, 3);
 		FmgrInfo	eqproc;
 		bool	   *hasmatch1;
 		bool	   *hasmatch2;
@@ -2433,10 +2765,12 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation,
 		 * returns NULL, though really equality functions should never do
 		 * that.
 		 */
-		InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation,
+		InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation,
 								 NULL, NULL);
 		fcinfo->args[0].isnull = false;
 		fcinfo->args[1].isnull = false;
+		fcinfo->args[2].isnull = false;
+		fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix);
 
 		hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool));
 		hasmatch2 = (bool *) palloc0(sslot2->nvalues * sizeof(bool));
@@ -2562,11 +2896,34 @@ eqjoinsel_inner(Oid opfuncoid, Oid collation,
 		double		nullfrac1 = stats1 ? stats1->stanullfrac : 0.0;
 		double		nullfrac2 = stats2 ? stats2->stanullfrac : 0.0;
 
-		selec = (1.0 - nullfrac1) * (1.0 - nullfrac2);
-		if (nd1 > nd2)
-			selec /= nd1;
-		else
-			selec /= nd2;
+		if (isdefault1 && vardata1->rel && nd1 > vardata1->rel->rows)
+		{
+			nd1 = vardata1->rel->rows;
+			if (nd1 == 0.0)
+				nd1 = 1.0;
+		}
+
+		if (isdefault2 && vardata2->rel && nd2 > vardata2->rel->rows)
+		{
+			nd2 = vardata2->rel->rows;
+			if (nd2 == 0.0)
+				nd2 = 1.0;
+		}
+
+		selec = eqjoinsel_histogram(operator, vardata1, vardata2,
+									record_cmp_prefix, nd1, nd2);
+
+		if (selec < 0)
+		{
+		   selec = 1.0;
+
+		   if (nd1 > nd2)
+				selec /= nd1;
+		   else
+				selec /= nd2;
+		}
+
+		selec *= (1.0 - nullfrac1) * (1.0 - nullfrac2);
 	}
 
 	return selec;
@@ -2587,7 +2944,8 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation,
 			   AttStatsSlot *sslot1, AttStatsSlot *sslot2,
 			   Form_pg_statistic stats1, Form_pg_statistic stats2,
 			   bool have_mcvs1, bool have_mcvs2,
-			   RelOptInfo *inner_rel)
+			   RelOptInfo *inner_rel,
+			   int record_cmp_prefix)
 {
 	double		selec;
 
@@ -2634,7 +2992,7 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation,
 		 * lists.  We still have to estimate for the remaining population, but
 		 * in a skewed distribution this gives us a big leg up in accuracy.
 		 */
-		LOCAL_FCINFO(fcinfo, 2);
+		LOCAL_FCINFO(fcinfo, 3);
 		FmgrInfo	eqproc;
 		bool	   *hasmatch1;
 		bool	   *hasmatch2;
@@ -2663,10 +3021,12 @@ eqjoinsel_semi(Oid opfuncoid, Oid collation,
 		 * returns NULL, though really equality functions should never do
 		 * that.
 		 */
-		InitFunctionCallInfoData(*fcinfo, &eqproc, 2, collation,
+		InitFunctionCallInfoData(*fcinfo, &eqproc, 3, collation,
 								 NULL, NULL);
 		fcinfo->args[0].isnull = false;
 		fcinfo->args[1].isnull = false;
+		fcinfo->args[2].isnull = false;
+		fcinfo->args[2].value = Int32GetDatum(record_cmp_prefix);
 
 		hasmatch1 = (bool *) palloc0(sslot1->nvalues * sizeof(bool));
 		hasmatch2 = (bool *) palloc0(clamped_nvalues2 * sizeof(bool));
@@ -3368,11 +3728,29 @@ double
 estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 					List **pgset, EstimationInfo *estinfo)
 {
-	List	   *varinfos = NIL;
+	return estimate_num_groups_incremental(root, groupExprs,
+										   input_rows, pgset, estinfo,
+										   NULL, 0);
+}
+
+/*
+ * estimate_num_groups_incremental
+ *		An estimate_num_groups variant, optimized for cases that are adding the
+ *		expressions incrementally (e.g. one by one).
+ */
+double
+estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs,
+								double input_rows,
+								List **pgset, EstimationInfo *estinfo,
+								List **cache_varinfos, int prevNExprs)
+{
+	List	   *varinfos = (cache_varinfos) ? *cache_varinfos : NIL;
 	double		srf_multiplier = 1.0;
 	double		numdistinct;
 	ListCell   *l;
-	int			i;
+	int			i,
+				j,
+				k;
 
 	/* Zero the estinfo output parameter, if non-NULL */
 	if (estinfo != NULL)
@@ -3403,7 +3781,7 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 	 */
 	numdistinct = 1.0;
 
-	i = 0;
+	i = j = 0;
 	foreach(l, groupExprs)
 	{
 		Node	   *groupexpr = (Node *) lfirst(l);
@@ -3412,6 +3790,14 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		List	   *varshere;
 		ListCell   *l2;
 
+		/* was done on previous call */
+		if (cache_varinfos && j++ < prevNExprs)
+		{
+			if (pgset)
+				i++;			/* to keep in sync with lines below */
+			continue;
+		}
+
 		/* is expression in this grouping set? */
 		if (pgset && !list_member_int(*pgset, i++))
 			continue;
@@ -3461,6 +3847,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		}
 		ReleaseVariableStats(vardata);
 
+		if (list_length(varinfos) > 2*list_length(groupExprs))
+			continue;
+
 		/*
 		 * Else pull out the component Vars.  Handle PlaceHolderVars by
 		 * recursing into their arguments (effectively assuming that the
@@ -3481,13 +3870,21 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 		if (varshere == NIL)
 		{
 			if (contain_volatile_functions(groupexpr))
+			{
+				if (cache_varinfos)
+					*cache_varinfos = varinfos;
 				return input_rows;
+			}
 			continue;
 		}
 
+		if (list_length(varshere) >= 8)
+			continue;
+
 		/*
 		 * Else add variables to varinfos list
 		 */
+		k = 0;
 		foreach(l2, varshere)
 		{
 			Node	   *var = (Node *) lfirst(l2);
@@ -3495,9 +3892,15 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 			examine_variable(root, var, 0, &vardata);
 			varinfos = add_unique_group_var(root, varinfos, var, &vardata);
 			ReleaseVariableStats(vardata);
+
+			if (++k > 4)
+				break;
 		}
 	}
 
+	if (cache_varinfos)
+		*cache_varinfos = varinfos;
+
 	/*
 	 * If now no Vars, we must have an all-constant or all-boolean GROUP BY
 	 * list.
@@ -4904,14 +5307,8 @@ get_join_variables(PlannerInfo *root, List *args, SpecialJoinInfo *sjinfo,
 	examine_variable(root, left, 0, vardata1);
 	examine_variable(root, right, 0, vardata2);
 
-	if (vardata1->rel &&
-		bms_is_subset(vardata1->rel->relids, sjinfo->syn_righthand))
-		*join_is_reversed = true;	/* var1 is on RHS */
-	else if (vardata2->rel &&
-			 bms_is_subset(vardata2->rel->relids, sjinfo->syn_lefthand))
-		*join_is_reversed = true;	/* var2 is on LHS */
-	else
-		*join_is_reversed = false;
+	if (join_is_reversed)
+		*join_is_reversed = join_is_reversed_variables(sjinfo, vardata1, vardata2);
 }
 
 /* statext_expressions_load copies the tuple, so just pfree it. */
@@ -6433,7 +6830,7 @@ index_other_operands_eval_cost(PlannerInfo *root, List *indexquals)
 			other_operand = NULL;	/* keep compiler quiet */
 		}
 
-		cost_qual_eval_node(&index_qual_cost, other_operand, root);
+		cost_qual_eval_node_index(&index_qual_cost, other_operand, root);
 		qual_arg_cost += index_qual_cost.startup + index_qual_cost.per_tuple;
 	}
 	return qual_arg_cost;
diff --git a/src/backend/utils/cache/attoptcache.c b/src/backend/utils/cache/attoptcache.c
index 9e252a0891e..4680a754ff9 100644
--- a/src/backend/utils/cache/attoptcache.c
+++ b/src/backend/utils/cache/attoptcache.c
@@ -43,12 +43,10 @@ typedef struct
 
 /*
  * InvalidateAttoptCacheCallback
- *		Flush all cache entries when pg_attribute is updated.
+ *		Flush cache entry (or entries) when pg_attribute is updated.
  *
  * When pg_attribute is updated, we must flush the cache entry at least
- * for that attribute.  Currently, we just flush them all.  Since attribute
- * options are not currently used in performance-critical paths (such as
- * query execution), this seems OK.
+ * for that attribute.
  */
 static void
 InvalidateAttoptCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
@@ -56,7 +54,16 @@ InvalidateAttoptCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 	HASH_SEQ_STATUS status;
 	AttoptCacheEntry *attopt;
 
-	hash_seq_init(&status, AttoptCacheHash);
+	/*
+	 * By convection, zero hash value is passed to the callback as a sign
+	 * that it's time to invalidate the cache. See sinval.c, inval.c and
+	 * InvalidateSystemCachesExtended().
+	 */
+	if (hashvalue == 0)
+		hash_seq_init(&status, AttoptCacheHash);
+	else
+		hash_seq_init_with_hash_value(&status, AttoptCacheHash, hashvalue);
+
 	while ((attopt = (AttoptCacheEntry *) hash_seq_search(&status)) != NULL)
 	{
 		if (attopt->opts)
@@ -69,6 +76,17 @@ InvalidateAttoptCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
 	}
 }
 
+/*
+ * Hash function compatible with two-arg system cache hash function.
+ */
+static uint32
+relatt_cache_syshash(const void *key, Size keysize)
+{
+	const AttoptCacheKey* ckey = key;
+
+	return GetSysCacheHashValue2(ATTNUM, ckey->attrelid, ckey->attnum);
+}
+
 /*
  * InitializeAttoptCache
  *		Initialize the attribute options cache.
@@ -81,9 +99,17 @@ InitializeAttoptCache(void)
 	/* Initialize the hash table. */
 	ctl.keysize = sizeof(AttoptCacheKey);
 	ctl.entrysize = sizeof(AttoptCacheEntry);
+
+	/*
+	 * AttoptCacheEntry takes hash value from the system cache. For
+	 * AttoptCacheHash we use the same hash in order to speedup search by hash
+	 * value. This is used by hash_seq_init_with_hash_value().
+	 */
+	ctl.hash = relatt_cache_syshash;
+
 	AttoptCacheHash =
 		hash_create("Attopt cache", 256, &ctl,
-					HASH_ELEM | HASH_BLOBS);
+					HASH_ELEM | HASH_FUNCTION);
 
 	/* Make sure we've initialized CacheMemoryContext. */
 	if (!CacheMemoryContext)
diff --git a/src/backend/utils/cache/catcache.c b/src/backend/utils/cache/catcache.c
index 00b9bf31ef3..7ec90cf9af2 100644
--- a/src/backend/utils/cache/catcache.c
+++ b/src/backend/utils/cache/catcache.c
@@ -22,6 +22,7 @@
 #include "access/valid.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_collation.h"
 #include "catalog/pg_operator.h"
 #include "catalog/pg_type.h"
@@ -2183,7 +2184,7 @@ void
 PrepareToInvalidateCacheTuple(Relation relation,
 							  HeapTuple tuple,
 							  HeapTuple newtuple,
-							  void (*function) (int, uint32, Oid))
+							  void (*function) (int, uint32, Oid, bool))
 {
 	slist_iter	iter;
 	Oid			reloid;
@@ -2213,6 +2214,9 @@ PrepareToInvalidateCacheTuple(Relation relation,
 		CatCache   *ccp = slist_container(CatCache, cc_next, iter.cur);
 		uint32		hashvalue;
 		Oid			dbid;
+		bool		isLocal = false;
+		Oid			relationId = InvalidOid;
+		bool		checkTemp = false;
 
 		if (ccp->cc_reloid != reloid)
 			continue;
@@ -2224,7 +2228,47 @@ PrepareToInvalidateCacheTuple(Relation relation,
 		hashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, tuple);
 		dbid = ccp->cc_relisshared ? (Oid) 0 : MyDatabaseId;
 
-		(*function) (ccp->id, hashvalue, dbid);
+		if (reloid == RelationRelationId)
+		{
+			Form_pg_class classtup = (Form_pg_class) GETSTRUCT(tuple);
+
+			isLocal = (classtup->relpersistence == RELPERSISTENCE_TEMP) ?
+				true : false;
+		}
+		else if (reloid == AttributeRelationId)
+		{
+			Form_pg_attribute atttup = (Form_pg_attribute) GETSTRUCT(tuple);
+
+			relationId = atttup->attrelid;
+			checkTemp = true;
+		}
+		else if (reloid == IndexRelationId)
+		{
+			Form_pg_index indextup = (Form_pg_index) GETSTRUCT(tuple);
+
+			relationId = indextup->indexrelid;
+			checkTemp = true;
+		}
+
+		if (checkTemp)
+		{
+			HeapTuple	htup = SearchSysCache1(RELOID,
+											   ObjectIdGetDatum(relationId));
+
+			if (HeapTupleIsValid(htup))
+			{
+				Form_pg_class c = (Form_pg_class)GETSTRUCT(htup);
+
+				isLocal = (c->relisshared == false &&
+						   c->relpersistence == RELPERSISTENCE_TEMP &&
+						   isTempOrTempToastNamespace(c->relnamespace)) ?
+						true : false;
+				ReleaseSysCache(htup);
+			}
+		}
+
+
+		(*function) (ccp->id, hashvalue, dbid, isLocal);
 
 		if (newtuple)
 		{
@@ -2233,7 +2277,7 @@ PrepareToInvalidateCacheTuple(Relation relation,
 			newhashvalue = CatalogCacheComputeTupleHashValue(ccp, ccp->cc_nkeys, newtuple);
 
 			if (newhashvalue != hashvalue)
-				(*function) (ccp->id, newhashvalue, dbid);
+				(*function) (ccp->id, newhashvalue, dbid, isLocal);
 		}
 	}
 }
diff --git a/src/backend/utils/cache/inval.c b/src/backend/utils/cache/inval.c
index 8b00456ed05..0ef770081eb 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -114,6 +114,7 @@
 #include "access/xact.h"
 #include "access/xloginsert.h"
 #include "catalog/catalog.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_constraint.h"
 #include "miscadmin.h"
 #include "storage/sinval.h"
@@ -394,12 +395,13 @@ AppendInvalidationMessageSubGroup(InvalidationMsgsGroup *dest,
  */
 static void
 AddCatcacheInvalidationMessage(InvalidationMsgsGroup *group,
-							   int id, uint32 hashValue, Oid dbId)
+							   int id, uint32 hashValue, Oid dbId, bool isLocal)
 {
 	SharedInvalidationMessage msg;
 
 	Assert(id < CHAR_MAX);
 	msg.cc.id = (int8) id;
+	msg.cc.isLocal = (int8) isLocal;
 	msg.cc.dbId = dbId;
 	msg.cc.hashValue = hashValue;
 
@@ -440,7 +442,7 @@ AddCatalogInvalidationMessage(InvalidationMsgsGroup *group,
  */
 static void
 AddRelcacheInvalidationMessage(InvalidationMsgsGroup *group,
-							   Oid dbId, Oid relId)
+							   Oid dbId, Oid relId, bool isLocal)
 {
 	SharedInvalidationMessage msg;
 
@@ -457,6 +459,7 @@ AddRelcacheInvalidationMessage(InvalidationMsgsGroup *group,
 
 	/* OK, add the item */
 	msg.rc.id = SHAREDINVALRELCACHE_ID;
+	msg.rc.isLocal = isLocal;
 	msg.rc.dbId = dbId;
 	msg.rc.relId = relId;
 	/* check AddCatcacheInvalidationMessage() for an explanation */
@@ -544,10 +547,10 @@ ProcessInvalidationMessagesMulti(InvalidationMsgsGroup *group,
 static void
 RegisterCatcacheInvalidation(int cacheId,
 							 uint32 hashValue,
-							 Oid dbId)
+							 Oid dbId, bool isLocal)
 {
 	AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
-								   cacheId, hashValue, dbId);
+								   cacheId, hashValue, dbId, isLocal);
 }
 
 /*
@@ -568,10 +571,10 @@ RegisterCatalogInvalidation(Oid dbId, Oid catId)
  * As above, but register a relcache invalidation event.
  */
 static void
-RegisterRelcacheInvalidation(Oid dbId, Oid relId)
+RegisterRelcacheInvalidation(Oid dbId, Oid relId, bool tempRel)
 {
 	AddRelcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
-								   dbId, relId);
+								   dbId, relId, tempRel);
 
 	/*
 	 * Most of the time, relcache invalidation is associated with system
@@ -1211,6 +1214,8 @@ CacheInvalidateHeapTuple(Relation relation,
 	Oid			tupleRelId;
 	Oid			databaseId;
 	Oid			relationId;
+	bool		tempRel = false;
+	bool		checkTemp = false;
 
 	/* Do nothing during bootstrap */
 	if (IsBootstrapProcessingMode())
@@ -1266,6 +1271,9 @@ CacheInvalidateHeapTuple(Relation relation,
 			databaseId = InvalidOid;
 		else
 			databaseId = MyDatabaseId;
+
+		tempRel = (classtup->relpersistence == RELPERSISTENCE_TEMP) ?
+						true : false;
 	}
 	else if (tupleRelId == AttributeRelationId)
 	{
@@ -1284,6 +1292,7 @@ CacheInvalidateHeapTuple(Relation relation,
 		 * never come here for a shared rel anyway.)
 		 */
 		databaseId = MyDatabaseId;
+		checkTemp = true;
 	}
 	else if (tupleRelId == IndexRelationId)
 	{
@@ -1297,6 +1306,7 @@ CacheInvalidateHeapTuple(Relation relation,
 		 */
 		relationId = indextup->indexrelid;
 		databaseId = MyDatabaseId;
+		checkTemp = true;
 	}
 	else if (tupleRelId == ConstraintRelationId)
 	{
@@ -1318,10 +1328,27 @@ CacheInvalidateHeapTuple(Relation relation,
 	else
 		return;
 
+	if (checkTemp)
+	{
+		HeapTuple		htup = SearchSysCache1(RELOID, ObjectIdGetDatum(relationId));
+
+		if (HeapTupleIsValid(htup))
+		{
+			Form_pg_class c = (Form_pg_class)GETSTRUCT(htup);
+
+			tempRel = (c->relisshared == false &&
+					   c->relpersistence == RELPERSISTENCE_TEMP &&
+					   isTempOrTempToastNamespace(c->relnamespace)) ?
+					true : false;
+
+			ReleaseSysCache(htup);
+		}
+	}
+
 	/*
 	 * Yes.  We need to register a relcache invalidation event.
 	 */
-	RegisterRelcacheInvalidation(databaseId, relationId);
+	RegisterRelcacheInvalidation(databaseId, relationId, tempRel);
 }
 
 /*
@@ -1373,7 +1400,10 @@ CacheInvalidateRelcache(Relation relation)
 	else
 		databaseId = MyDatabaseId;
 
-	RegisterRelcacheInvalidation(databaseId, relationId);
+	RegisterRelcacheInvalidation(databaseId, relationId,
+								 relation->rd_rel->relisshared == false &&
+								 RELATION_IS_LOCAL(relation) &&
+								 !RELATION_IS_OTHER_TEMP(relation));
 }
 
 /*
@@ -1388,7 +1418,7 @@ CacheInvalidateRelcacheAll(void)
 {
 	PrepareInvalidationState();
 
-	RegisterRelcacheInvalidation(InvalidOid, InvalidOid);
+	RegisterRelcacheInvalidation(InvalidOid, InvalidOid, false);
 }
 
 /*
@@ -1409,7 +1439,10 @@ CacheInvalidateRelcacheByTuple(HeapTuple classTuple)
 		databaseId = InvalidOid;
 	else
 		databaseId = MyDatabaseId;
-	RegisterRelcacheInvalidation(databaseId, relationId);
+	RegisterRelcacheInvalidation(databaseId, relationId,
+								 classtup->relisshared == false &&
+								 classtup->relpersistence == RELPERSISTENCE_TEMP &&
+								 isTempOrTempToastNamespace(classtup->relnamespace));
 }
 
 /*
diff --git a/src/backend/utils/cache/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 1b7e11b93e0..597a477565b 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -43,6 +43,7 @@
 #include "utils/datum.h"
 #include "utils/fmgroids.h"
 #include "utils/lsyscache.h"
+#include "utils/memutils.h"
 #include "utils/rel.h"
 #include "utils/syscache.h"
 #include "utils/typcache.h"
@@ -3296,6 +3297,52 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 	return true;
 }
 
+AttStatsSlot*
+fill_attstatsslot(AttStatsSlot *sslots, HeapTuple statstuple,
+				  int reqkind, Oid reqop, int flags)
+{
+	int	add_flags = 0, has_flags = 0;
+	AttStatsSlot	*sslot;
+	MemoryContext	oldctx;
+
+	if (reqkind >= STATISTIC_NUM_SLOTS)
+		return NULL;			/* not there */
+
+	sslot = sslots + reqkind;
+
+	if (sslot->values != NULL)
+		has_flags |= ATTSTATSSLOT_VALUES;
+	if (sslot->numbers != NULL)
+		has_flags |= ATTSTATSSLOT_NUMBERS;
+
+	if ((flags & ATTSTATSSLOT_VALUES) && !(has_flags & ATTSTATSSLOT_VALUES))
+		add_flags |= ATTSTATSSLOT_VALUES;
+
+	if ((flags & ATTSTATSSLOT_NUMBERS) && !(has_flags & ATTSTATSSLOT_NUMBERS))
+		add_flags |= ATTSTATSSLOT_NUMBERS;
+
+	if (add_flags == 0 && (reqop == InvalidOid || sslot->staop == reqop))
+		return sslot;
+
+	sslot->incache = false;
+	free_attstatsslot(sslot);
+
+	oldctx = MemoryContextSwitchTo(GetMemoryChunkContext(sslots));
+
+	if (get_attstatsslot(sslot, statstuple, reqkind, reqop,
+						 add_flags | has_flags))
+	{
+		sslot->incache = true;
+		MemoryContextSwitchTo(oldctx);
+		return sslot;
+	}
+	else
+	{
+		MemoryContextSwitchTo(oldctx);
+		return NULL;
+	}
+}
+
 /*
  * free_attstatsslot
  *		Free data allocated by get_attstatsslot
@@ -3303,6 +3350,10 @@ get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 void
 free_attstatsslot(AttStatsSlot *sslot)
 {
+	/* do not free cached slot */
+	if (sslot->incache)
+		return;
+
 	/* The values[] array was separately palloc'd by deconstruct_array */
 	if (sslot->values)
 		pfree(sslot->values);
diff --git a/src/backend/utils/cache/typcache.c b/src/backend/utils/cache/typcache.c
index 62cd300453c..3e7ef8c273d 100644
--- a/src/backend/utils/cache/typcache.c
+++ b/src/backend/utils/cache/typcache.c
@@ -77,6 +77,13 @@
 /* The main type cache hashtable searched by lookup_type_cache */
 static HTAB *TypeCacheHash = NULL;
 
+typedef struct mapRelTypeEntry
+{
+	Oid	typrelid;
+	Oid type_id;
+} mapRelTypeEntry;
+static HTAB *mapRelType = NULL;
+
 /* List of type cache entries for domain types */
 static TypeCacheEntry *firstDomainTypeEntry = NULL;
 
@@ -328,6 +335,15 @@ static TupleDesc find_or_make_matching_shared_tupledesc(TupleDesc tupdesc);
 static dsa_pointer share_tupledesc(dsa_area *area, TupleDesc tupdesc,
 								   uint32 typmod);
 
+/*
+ * Hashing function should compatible with syscache hashing function to use
+ * hash_seq_init_with_hash_value()
+ */
+static uint32
+type_cache_hash(const void *key, Size keysize)
+{
+	return GetSysCacheHashValue1(TYPEOID, ObjectIdGetDatum(*(const Oid*)key));
+}
 
 /*
  * lookup_type_cache
@@ -353,8 +369,14 @@ lookup_type_cache(Oid type_id, int flags)
 
 		ctl.keysize = sizeof(Oid);
 		ctl.entrysize = sizeof(TypeCacheEntry);
+		ctl.hash = type_cache_hash;
 		TypeCacheHash = hash_create("Type information cache", 64,
-									&ctl, HASH_ELEM | HASH_BLOBS);
+									&ctl, HASH_ELEM | HASH_FUNCTION);
+
+		ctl.keysize = sizeof(Oid);
+		ctl.entrysize = sizeof(mapRelTypeEntry);
+		mapRelType = hash_create("Map reloid to typeoid", 64,
+								 &ctl, HASH_ELEM | HASH_BLOBS);
 
 		/* Also set up callbacks for SI invalidations */
 		CacheRegisterRelcacheCallback(TypeCacheRelCallback, (Datum) 0);
@@ -405,8 +427,7 @@ lookup_type_cache(Oid type_id, int flags)
 
 		/* These fields can never change, by definition */
 		typentry->type_id = type_id;
-		typentry->type_id_hash = GetSysCacheHashValue1(TYPEOID,
-													   ObjectIdGetDatum(type_id));
+		typentry->type_id_hash = get_hash_value(TypeCacheHash, &type_id);
 
 		/* Keep this part in sync with the code below */
 		typentry->typlen = typtup->typlen;
@@ -427,6 +448,18 @@ lookup_type_cache(Oid type_id, int flags)
 			firstDomainTypeEntry = typentry;
 		}
 
+		if (OidIsValid(typtup->typrelid))
+		{
+			mapRelTypeEntry *relentry;
+
+			relentry = (mapRelTypeEntry*) hash_search(mapRelType,
+													  &typentry->typrelid,
+													  HASH_ENTER, NULL);
+
+			relentry->typrelid = typentry->typrelid;
+			relentry->type_id = typentry->type_id;
+		}
+
 		ReleaseSysCache(tp);
 	}
 	else if (!(typentry->flags & TCFLAGS_HAVE_PG_TYPE_DATA))
@@ -465,6 +498,18 @@ lookup_type_cache(Oid type_id, int flags)
 		typentry->typcollation = typtup->typcollation;
 		typentry->flags |= TCFLAGS_HAVE_PG_TYPE_DATA;
 
+		if (OidIsValid(typtup->typrelid))
+		{
+			mapRelTypeEntry *relentry;
+
+			relentry = (mapRelTypeEntry*) hash_search(mapRelType,
+													  &typentry->typrelid,
+													  HASH_ENTER, NULL);
+
+			relentry->typrelid = typentry->typrelid;
+			relentry->type_id = typentry->type_id;
+		}
+
 		ReleaseSysCache(tp);
 	}
 
@@ -2286,58 +2331,72 @@ SharedRecordTypmodRegistryAttach(SharedRecordTypmodRegistry *registry)
 static void
 TypeCacheRelCallback(Datum arg, Oid relid)
 {
-	HASH_SEQ_STATUS status;
 	TypeCacheEntry *typentry;
 
 	/* TypeCacheHash must exist, else this callback wouldn't be registered */
-	hash_seq_init(&status, TypeCacheHash);
-	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
+
+	if (OidIsValid(relid))
 	{
-		if (typentry->typtype == TYPTYPE_COMPOSITE)
+		mapRelTypeEntry *relentry;
+
+		relentry = (mapRelTypeEntry *) hash_search(mapRelType,
+												  &relid,
+												  HASH_FIND, NULL);
+
+		if (relentry != NULL)
 		{
-			/* Skip if no match, unless we're zapping all composite types */
-			if (relid != typentry->typrelid && relid != InvalidOid)
-				continue;
+			typentry = (TypeCacheEntry *) hash_search(TypeCacheHash,
+													  &relentry->type_id,
+													  HASH_FIND, NULL);
 
-			/* Delete tupdesc if we have it */
-			if (typentry->tupDesc != NULL)
+			if (typentry != NULL)
 			{
-				/*
-				 * Release our refcount, and free the tupdesc if none remain.
-				 * (Can't use DecrTupleDescRefCount because this reference is
-				 * not logged in current resource owner.)
-				 */
-				Assert(typentry->tupDesc->tdrefcount > 0);
-				if (--typentry->tupDesc->tdrefcount == 0)
-					FreeTupleDesc(typentry->tupDesc);
-				typentry->tupDesc = NULL;
-
-				/*
-				 * Also clear tupDesc_identifier, so that anything watching
-				 * that will realize that the tupdesc has possibly changed.
-				 * (Alternatively, we could specify that to detect possible
-				 * tupdesc change, one must check for tupDesc != NULL as well
-				 * as tupDesc_identifier being the same as what was previously
-				 * seen.  That seems error-prone.)
-				 */
-				typentry->tupDesc_identifier = 0;
-			}
+				Assert(typentry->typtype == TYPTYPE_COMPOSITE);
+				Assert(relid == typentry->typrelid);
 
-			/* Reset equality/comparison/hashing validity information */
-			typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
-		}
-		else if (typentry->typtype == TYPTYPE_DOMAIN)
-		{
-			/*
-			 * If it's domain over composite, reset flags.  (We don't bother
-			 * trying to determine whether the specific base type needs a
-			 * reset.)  Note that if we haven't determined whether the base
-			 * type is composite, we don't need to reset anything.
-			 */
-			if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
+				/* Delete tupdesc if we have it */
+				if (typentry->tupDesc != NULL)
+				{
+					/*
+					 * Release our refcount, and free the tupdesc if none remain.
+					 * (Can't use DecrTupleDescRefCount because this reference is
+					 * not logged in current resource owner.)
+					 */
+					Assert(typentry->tupDesc->tdrefcount > 0);
+					if (--typentry->tupDesc->tdrefcount == 0)
+						FreeTupleDesc(typentry->tupDesc);
+					typentry->tupDesc = NULL;
+
+					/*
+					 * Also clear tupDesc_identifier, so that anything watching
+					 * that will realize that the tupdesc has possibly changed.
+					 * (Alternatively, we could specify that to detect possible
+					 * tupdesc change, one must check for tupDesc != NULL as well
+					 * as tupDesc_identifier being the same as what was previously
+					 * seen.  That seems error-prone.)
+					 */
+					typentry->tupDesc_identifier = 0;
+				}
+
+				/* Reset equality/comparison/hashing validity information */
 				typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
+			}
 		}
 	}
+
+	for (typentry = firstDomainTypeEntry;
+		 typentry != NULL;
+		 typentry = typentry->nextDomain)
+	{
+		/*
+		 * If it's domain over composite, reset flags.  (We don't bother
+		 * trying to determine whether the specific base type needs a
+		 * reset.)  Note that if we haven't determined whether the base
+		 * type is composite, we don't need to reset anything.
+		 */
+		if (typentry->flags & TCFLAGS_DOMAIN_BASE_IS_COMPOSITE)
+			typentry->flags &= ~TCFLAGS_OPERATOR_FLAGS;
+	}
 }
 
 /*
@@ -2355,20 +2414,20 @@ TypeCacheTypCallback(Datum arg, int cacheid, uint32 hashvalue)
 	TypeCacheEntry *typentry;
 
 	/* TypeCacheHash must exist, else this callback wouldn't be registered */
-	hash_seq_init(&status, TypeCacheHash);
+	if (hashvalue == 0)
+		hash_seq_init(&status, TypeCacheHash);
+	else
+		hash_seq_init_with_hash_value(&status, TypeCacheHash, hashvalue);
+
 	while ((typentry = (TypeCacheEntry *) hash_seq_search(&status)) != NULL)
 	{
-		/* Is this the targeted type row (or it's a total cache flush)? */
-		if (hashvalue == 0 || typentry->type_id_hash == hashvalue)
-		{
-			/*
-			 * Mark the data obtained directly from pg_type as invalid.  Also,
-			 * if it's a domain, typnotnull might've changed, so we'll need to
-			 * recalculate its constraints.
-			 */
-			typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA |
-								 TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS);
-		}
+		/*
+		 * Mark the data obtained directly from pg_type as invalid.  Also,
+		 * if it's a domain, typnotnull might've changed, so we'll need to
+		 * recalculate its constraints.
+		 */
+		typentry->flags &= ~(TCFLAGS_HAVE_PG_TYPE_DATA |
+							 TCFLAGS_CHECKED_DOMAIN_CONSTRAINTS);
 	}
 }
 
diff --git a/src/backend/utils/hash/dynahash.c b/src/backend/utils/hash/dynahash.c
index 3babde8d704..ea026061059 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -1428,10 +1428,37 @@ hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp)
 	status->hashp = hashp;
 	status->curBucket = 0;
 	status->curEntry = NULL;
+	status->hasHashVal = false;
 	if (!hashp->frozen)
 		register_seq_scan(hashp);
 }
 
+void
+hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status, HTAB *hashp,
+							  uint32 hashvalue)
+{
+	HASHHDR		*hctl = hashp->hctl;
+	long		 segment_num;
+	long		 segment_ndx;
+	HASHSEGMENT	 segp;
+
+	hash_seq_init(status, hashp);
+	status->hasHashVal = true;
+	status->hashvalue = hashvalue;
+
+	status->curBucket = calc_bucket(hctl, hashvalue);
+
+	segment_num = status->curBucket >> hashp->sshift;
+	segment_ndx = MOD(status->curBucket, hashp->ssize);
+
+	segp = hashp->dir[segment_num];
+
+	if (segp == NULL)
+		hash_corrupted(hashp);
+
+	status->curEntry = segp[segment_ndx];
+}
+
 void *
 hash_seq_search(HASH_SEQ_STATUS *status)
 {
@@ -1445,6 +1472,20 @@ hash_seq_search(HASH_SEQ_STATUS *status)
 	uint32		curBucket;
 	HASHELEMENT *curElem;
 
+	if (status->hasHashVal)
+	{
+		while ((curElem = status->curEntry) != NULL)
+		{
+			status->curEntry = curElem->link;
+			if (status->hashvalue != curElem->hashvalue)
+				continue;
+			return (void *) ELEMENTKEY(curElem);
+		}
+
+		hash_seq_term(status);
+		return NULL;
+	}
+
 	if ((curElem = status->curEntry) != NULL)
 	{
 		/* Continuing scan of curBucket... */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index abfc581171a..ca85280354f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -143,6 +143,7 @@ extern char *temp_tablespaces;
 extern bool ignore_checksum_failure;
 extern bool ignore_invalid_pages;
 extern bool synchronize_seqscans;
+extern bool enable_self_join_removal;
 
 #ifdef TRACE_SYNCSCAN
 extern bool trace_syncscan;
@@ -242,6 +243,7 @@ static bool check_recovery_target_lsn(char **newval, void **extra, GucSource sou
 static void assign_recovery_target_lsn(const char *newval, void *extra);
 static bool check_primary_slot_name(char **newval, void **extra, GucSource source);
 static bool check_default_with_oids(bool *newval, void **extra, GucSource source);
+static void assign_default_with_oids(bool newval, void *extra);
 
 /* Private functions in guc-file.l that need to be called from guc.c */
 static ConfigVariable *ProcessConfigFileInternal(GucContext context,
@@ -1215,6 +1217,26 @@ static struct config_bool ConfigureNamesBool[] =
 		true,
 		NULL, NULL, NULL
 	},
+	{
+		{"enable_self_join_removal", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enable removal of unique self-joins."),
+			NULL,
+			GUC_NO_SHOW_ALL | GUC_NOT_IN_SAMPLE
+		},
+		&enable_self_join_removal,
+		true,
+		NULL, NULL, NULL
+	},
+	{
+		{"enable_group_by_reordering", PGC_USERSET, QUERY_TUNING_METHOD,
+			gettext_noop("Enables reordering of GROUP BY keys."),
+			NULL,
+			GUC_EXPLAIN
+		},
+		&enable_group_by_reordering,
+		true,
+		NULL, NULL, NULL
+	},
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
@@ -1811,7 +1833,7 @@ static struct config_bool ConfigureNamesBool[] =
 		},
 		&default_with_oids,
 		false,
-		check_default_with_oids, NULL, NULL
+		check_default_with_oids, assign_default_with_oids, NULL
 	},
 	{
 		{"logging_collector", PGC_POSTMASTER, LOGGING_WHERE,
@@ -13057,13 +13079,19 @@ check_default_with_oids(bool *newval, void **extra, GucSource source)
 	if (*newval)
 	{
 		/* check the GUC's definition for an explanation */
-		GUC_check_errcode(ERRCODE_FEATURE_NOT_SUPPORTED);
-		GUC_check_errmsg("tables declared WITH OIDS are not supported");
+		ereport(WARNING,
+				(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
+				 errmsg("tables declared WITH OIDS are not supported, ignored")));
 
-		return false;
+		*newval = false;
 	}
 
 	return true;
 }
 
+static void
+assign_default_with_oids(bool newval, void *extra)
+{
+}
+
 #include "guc-file.c"
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 7bf891a9020..0f38f3f2636 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -388,6 +388,7 @@
 #enable_seqscan = on
 #enable_sort = on
 #enable_tidscan = on
+#enable_group_by_reordering = on
 
 # - Planner Cost Constants -
 
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 81117104f9e..93359ba09e0 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -1474,7 +1474,6 @@ AllocSetCheck(MemoryContext context)
 
 			chsize = chunk->size;	/* aligned chunk size */
 			dsize = chunk->requested_size;	/* real data */
-
 			/*
 			 * Check chunk size
 			 */
diff --git a/src/backend/utils/sort/tuplestore.c b/src/backend/utils/sort/tuplestore.c
index f605ece721e..dfc8a6d8109 100644
--- a/src/backend/utils/sort/tuplestore.c
+++ b/src/backend/utils/sort/tuplestore.c
@@ -545,7 +545,7 @@ tuplestore_select_read_pointer(Tuplestorestate *state, int ptr)
 int64
 tuplestore_tuple_count(Tuplestorestate *state)
 {
-	return state->tuples;
+	return (state) ? state->tuples : 0;
 }
 
 /*
diff --git a/src/bin/pg_basebackup/pg_receivewal.c b/src/bin/pg_basebackup/pg_receivewal.c
index 26dcf339f72..8897fff7b34 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -99,6 +99,7 @@ usage(void)
 	printf(_("  -d, --dbname=CONNSTR   connection string\n"));
 	printf(_("  -h, --host=HOSTNAME    database server host or socket directory\n"));
 	printf(_("  -p, --port=PORT        database server port number\n"));
+	printf(_("  -u, --umask            set files mode according to umask (might break security!)\n"));
 	printf(_("  -U, --username=NAME    connect as specified database user\n"));
 	printf(_("  -w, --no-password      never prompt for password\n"));
 	printf(_("  -W, --password         force password prompt (should happen automatically)\n"));
@@ -696,6 +697,7 @@ main(int argc, char **argv)
 		{"endpos", required_argument, NULL, 'E'},
 		{"host", required_argument, NULL, 'h'},
 		{"port", required_argument, NULL, 'p'},
+		{"umask", no_argument, NULL, 'u'},
 		{"username", required_argument, NULL, 'U'},
 		{"no-loop", no_argument, NULL, 'n'},
 		{"no-password", no_argument, NULL, 'w'},
@@ -742,7 +744,7 @@ main(int argc, char **argv)
 		}
 	}
 
-	while ((c = getopt_long(argc, argv, "D:d:E:h:p:U:s:S:nwWvZ:",
+	while ((c = getopt_long(argc, argv, "D:d:E:h:p:U:s:S:nuwWvZ:",
 							long_options, &option_index)) != -1)
 	{
 		switch (c)
@@ -759,6 +761,9 @@ main(int argc, char **argv)
 			case 'p':
 				dbport = pg_strdup(optarg);
 				break;
+			case 'u':
+				useumask = 1;
+				break;
 			case 'U':
 				dbuser = pg_strdup(optarg);
 				break;
diff --git a/src/bin/pg_basebackup/pg_recvlogical.c b/src/bin/pg_basebackup/pg_recvlogical.c
index 02b8e272c37..d4ee0dd879c 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -325,11 +325,14 @@ StreamLogicalLog(void)
 		{
 			struct stat statbuf;
 
+			mode_t mode = (useumask == 1) ?
+				(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR);
+
 			if (strcmp(outfile, "-") == 0)
 				outfd = fileno(stdout);
 			else
 				outfd = open(outfile, O_CREAT | O_APPEND | O_WRONLY | PG_BINARY,
-							 S_IRUSR | S_IWUSR);
+							 mode);
 			if (outfd == -1)
 			{
 				pg_log_error("could not open log file \"%s\": %m", outfile);
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1478aa9f25a..6df1904b4a1 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -49,6 +49,7 @@ char	   *dbhost = NULL;
 char	   *dbuser = NULL;
 char	   *dbport = NULL;
 char	   *dbname = NULL;
+int			useumask = 0;	/* 0=auto, -1=never, 1=always */
 int			dbgetpassword = 0;	/* 0=auto, -1=never, 1=always */
 static char *password = NULL;
 PGconn	   *conn = NULL;
diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h
index 8638f81f3bf..9104d1ccd68 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -23,6 +23,7 @@ extern char *dbhost;
 extern char *dbuser;
 extern char *dbport;
 extern char *dbname;
+extern int  useumask;
 extern int	dbgetpassword;
 extern uint32 WalSegSz;
 
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index 6204ba8aba7..3e7441f9f99 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -119,6 +119,8 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
 	size_t		lz4bufsize = 0;
 	void	   *lz4buf = NULL;
 #endif
+	mode_t		mode = (useumask == 1) ?
+		(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR);
 
 	dir_clear_error();
 
@@ -133,7 +135,7 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
 	 * does not do any system calls to fsync() to make changes permanent on
 	 * disk.
 	 */
-	fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode);
+	fd = open(tmppath, O_WRONLY | O_CREAT | PG_BINARY, pg_file_create_mode | mode);
 	if (fd < 0)
 	{
 		dir_data->lasterrno = errno;
@@ -828,6 +830,8 @@ tar_get_file_name(const char *pathname, const char *temp_suffix)
 static Walfile
 tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_size)
 {
+	mode_t		mode = (useumask == 1) ?
+		(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR);
 	char	   *tmppath;
 
 	tar_clear_error();
@@ -839,7 +843,7 @@ tar_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
 		 */
 		tar_data->fd = open(tar_data->tarfilename,
 							O_WRONLY | O_CREAT | PG_BINARY,
-							pg_file_create_mode);
+							pg_file_create_mode | mode);
 		if (tar_data->fd < 0)
 		{
 			tar_data->lasterrno = errno;
diff --git a/src/bin/pg_dump/common.c b/src/bin/pg_dump/common.c
index 95f8980b691..48b16c77160 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -87,7 +87,6 @@ static void flagInhAttrs(DumpOptions *dopt, TableInfo *tblinfo, int numTables);
 static void findParentsByOid(TableInfo *self,
 							 InhInfo *inhinfo, int numInherits);
 static int	strInArray(const char *pattern, char **arr, int arr_size);
-static IndxInfo *findIndexByOid(Oid oid);
 
 
 /*
@@ -784,7 +783,7 @@ findTableByOid(Oid oid)
  *	  finds the DumpableObject for the index with the given oid
  *	  returns NULL if not found
  */
-static IndxInfo *
+IndxInfo *
 findIndexByOid(Oid oid)
 {
 	CatalogId	catId;
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 8bf14bbf001..232895b801a 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1774,11 +1774,22 @@ selectDumpableType(TypeInfo *tyinfo, Archive *fout)
 	if (OidIsValid(tyinfo->typrelid) &&
 		tyinfo->typrelkind != RELKIND_COMPOSITE_TYPE)
 	{
-		TableInfo  *tytable = findTableByOid(tyinfo->typrelid);
+		DumpableObject *parentRel;
 
 		tyinfo->dobj.objType = DO_DUMMY_TYPE;
-		if (tytable != NULL)
-			tyinfo->dobj.dump = tytable->dobj.dump;
+
+		/* Get associated relation */
+		if (tyinfo->typrelkind == RELKIND_INDEX)
+			parentRel = (DumpableObject *) findIndexByOid(tyinfo->typrelid);
+		else
+			parentRel = (DumpableObject *) findTableByOid(tyinfo->typrelid);
+
+		/*
+		 * If associated relation found, dump based on if the
+		 * contents of the associated relation are being dumped.
+		 */
+		if (parentRel != NULL)
+			tyinfo->dobj.dump = parentRel->dump;
 		else
 			tyinfo->dobj.dump = DUMP_COMPONENT_NONE;
 		return;
@@ -4885,6 +4896,9 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	Oid			pg_type_multirange_oid;
 	Oid			pg_type_multirange_array_oid;
 
+	if (pg_type_oid == InvalidOid)
+		return;
+
 	appendPQExpBufferStr(upgrade_buffer, "\n-- For binary upgrade, must preserve pg_type oid\n");
 	appendPQExpBuffer(upgrade_buffer,
 					  "SELECT pg_catalog.binary_upgrade_set_next_pg_type_oid('%u'::pg_catalog.oid);\n\n",
@@ -4969,6 +4983,17 @@ binary_upgrade_set_type_oids_by_rel(Archive *fout,
 												 pg_type_oid, false, false);
 }
 
+static void
+binary_upgrade_set_type_oids_by_rel_oid(Archive *fout,
+									PQExpBuffer upgrade_buffer,
+									Oid pg_type_oid
+									)
+{
+	if (OidIsValid(pg_type_oid))
+		binary_upgrade_set_type_oids_by_type_oid(fout, upgrade_buffer,
+												 pg_type_oid, false, false);
+}
+
 static void
 binary_upgrade_set_pg_class_oids(Archive *fout,
 								 PQExpBuffer upgrade_buffer, Oid pg_class_oid,
@@ -6920,6 +6945,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_indnkeyatts,
 				i_indnatts,
 				i_indkey,
+				i_indtype,
 				i_indisclustered,
 				i_indisreplident,
 				i_indnullsnotdistinct,
@@ -6968,7 +6994,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 					  "SELECT t.tableoid, t.oid, i.indrelid, "
 					  "t.relname AS indexname, "
 					  "pg_catalog.pg_get_indexdef(i.indexrelid) AS indexdef, "
-					  "i.indkey, i.indisclustered, "
+					  "i.indkey, t.reltype AS indtype, i.indisclustered, "
 					  "c.contype, c.conname, "
 					  "c.condeferrable, c.condeferred, "
 					  "c.tableoid AS contableoid, "
@@ -7072,6 +7098,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	i_indnkeyatts = PQfnumber(res, "indnkeyatts");
 	i_indnatts = PQfnumber(res, "indnatts");
 	i_indkey = PQfnumber(res, "indkey");
+	i_indtype = PQfnumber(res, "indtype");
 	i_indisclustered = PQfnumber(res, "indisclustered");
 	i_indisreplident = PQfnumber(res, "indisreplident");
 	i_indnullsnotdistinct = PQfnumber(res, "indnullsnotdistinct");
@@ -7149,6 +7176,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			indxinfo[j].indkeys = (Oid *) pg_malloc(indxinfo[j].indnattrs * sizeof(Oid));
 			parseOidArray(PQgetvalue(res, j, i_indkey),
 						  indxinfo[j].indkeys, indxinfo[j].indnattrs);
+			indxinfo[j].indtype = atooid(PQgetvalue(res, j, i_indtype));
 			indxinfo[j].indisclustered = (PQgetvalue(res, j, i_indisclustered)[0] == 't');
 			indxinfo[j].indisreplident = (PQgetvalue(res, j, i_indisreplident)[0] == 't');
 			indxinfo[j].indnullsnotdistinct = (PQgetvalue(res, j, i_indnullsnotdistinct)[0] == 't');
@@ -16377,8 +16405,13 @@ dumpIndex(Archive *fout, const IndxInfo *indxinfo)
 		int			nstatvals = 0;
 
 		if (dopt->binary_upgrade)
+		{
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 indxinfo->dobj.catId.oid, true);
+			if (indxinfo->indnkeyattrs > 1)
+				binary_upgrade_set_type_oids_by_rel_oid(fout, q,
+														indxinfo->indtype);
+		}
 
 		/* Plain secondary index */
 		appendPQExpBuffer(q, "%s;\n", indxinfo->indexdef);
@@ -16648,8 +16681,14 @@ dumpConstraint(Archive *fout, const ConstraintInfo *coninfo)
 					 coninfo->dobj.name);
 
 		if (dopt->binary_upgrade)
+		{
+			if (indxinfo->indnkeyattrs > 1)
+				binary_upgrade_set_type_oids_by_rel_oid(fout, q,
+														indxinfo->indtype);
+
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 indxinfo->dobj.catId.oid, true);
+		}
 
 		appendPQExpBuffer(q, "ALTER %sTABLE ONLY %s\n", foreign,
 						  fmtQualifiedDumpable(tbinfo));
@@ -18207,6 +18246,28 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 		 */
 		switch (dobj->objType)
 		{
+			case DO_DUMMY_TYPE:
+			{
+				/*
+				 * In Vanilla, dummy types were only created for tables.
+				 * In Postgres Pro for improving join selectivity estimation
+				 * we also create two types for each composite index:
+				 *   1) a type for attributes of the index
+				 *   2) a type which is an array containing elements of type (1)
+				 * These types depend on indexes, so adding preDataBound -> type
+				 * dependency would create a loop; don't do that.
+				 */
+				TypeInfo *tyinfo = (TypeInfo *) dobj;
+				if (tyinfo->isArray)
+					/* If it's an array, take its element type */
+					tyinfo = findTypeByOid(tyinfo->typelem);
+
+				if (OidIsValid(tyinfo->typrelid) &&
+					(tyinfo->typrelkind == RELKIND_INDEX ||
+					tyinfo->typrelkind == RELKIND_PARTITIONED_INDEX))
+					break;
+			}
+			/* FALLTHROUGH */
 			case DO_NAMESPACE:
 			case DO_EXTENSION:
 			case DO_TYPE:
@@ -18224,7 +18285,6 @@ addBoundaryDependencies(DumpableObject **dobjs, int numObjs,
 			case DO_ATTRDEF:
 			case DO_PROCLANG:
 			case DO_CAST:
-			case DO_DUMMY_TYPE:
 			case DO_TSPARSER:
 			case DO_TSDICT:
 			case DO_TSTEMPLATE:
diff --git a/src/bin/pg_dump/pg_dump.h b/src/bin/pg_dump/pg_dump.h
index a64859a74dd..31e1c8ac546 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -411,6 +411,7 @@ typedef struct _indxInfo
 	int			indnattrs;		/* total number of index attributes */
 	Oid		   *indkeys;		/* In spite of the name 'indkeys' this field
 								 * contains both key and nonkey attributes */
+	Oid			indtype;		/* OID of index's composite type, if any */
 	bool		indisclustered;
 	bool		indisreplident;
 	bool		indnullsnotdistinct;
@@ -703,6 +704,7 @@ extern AccessMethodInfo *findAccessMethodByOid(Oid oid);
 extern CollInfo *findCollationByOid(Oid oid);
 extern NamespaceInfo *findNamespaceByOid(Oid oid);
 extern ExtensionInfo *findExtensionByOid(Oid oid);
+extern IndxInfo *findIndexByOid(Oid oid);
 extern PublicationInfo *findPublicationByOid(Oid oid);
 
 extern void recordExtensionMembership(CatalogId catId, ExtensionInfo *ext);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index a1d6e3b645f..40197eb0422 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -65,6 +65,7 @@ extern void index_check_primary_key(Relation heapRel,
 #define	INDEX_CREATE_IF_NOT_EXISTS			(1 << 4)
 #define	INDEX_CREATE_PARTITIONED			(1 << 5)
 #define INDEX_CREATE_INVALID				(1 << 6)
+#define INDEX_CREATE_WITHOUT_TYPE			(1 << 7)
 
 extern Oid	index_create(Relation heapRelation,
 						 const char *indexRelationName,
diff --git a/src/include/catalog/pg_type.dat b/src/include/catalog/pg_type.dat
index df458794635..8b783e5efe8 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -692,4 +692,12 @@
   typreceive => 'brin_minmax_multi_summary_recv',
   typsend => 'brin_minmax_multi_summary_send', typalign => 'i',
   typstorage => 'x', typcollation => 'default' },
+{ oid => '14756', descr => 'pseudo-type representing removed abstime',
+   typname => 'abstime', typlen => '-1', typbyval => 'f', typtype => 'p',
+   typcategory => 'P', typinput => 'timestamp_in', typoutput => 'timestamp_out',
+   typreceive => 'timestamp_recv', typsend => 'timestamp_send', typalign => 'c' },
+{ oid => '14757', descr => 'pseudo-type representing removed reltime',
+   typname => 'reltime', typlen => '-1', typbyval => 'f', typtype => 'p',
+   typcategory => 'P', typinput => 'timestamp_in', typoutput => 'timestamp_out',
+   typreceive => 'timestamp_recv', typsend => 'timestamp_send', typalign => 'c' },
 ]
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index 77fda6b0f8e..92af777533e 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -352,12 +352,17 @@ typedef struct ExprEvalStep
 		struct
 		{
 			bool	   *anynull;	/* track if any input was NULL */
+			bool		*guaranteed_empty;
+			bool		is_last;
+			int			*count_guaranteed_empty;
+			int			nargs;
 			int			jumpdone;	/* jump here if result determined */
 		}			boolexpr;
 
 		/* for EEOP_QUAL */
 		struct
 		{
+			bool		*guaranteed_empty;
 			int			jumpdone;	/* jump here on false or null */
 		}			qualexpr;
 
@@ -618,6 +623,7 @@ typedef struct ExprEvalStep
 		{
 			/* out-of-line state, created by nodeSubplan.c */
 			SubPlanState *sstate;
+			bool		*guaranteed_empty;
 		}			subplan;
 
 		/* for EEOP_AGG_*DESERIALIZE */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index 9741435b96a..02f7acd681b 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -114,6 +114,8 @@ typedef struct ExprState
 
 	Datum	   *innermost_domainval;
 	bool	   *innermost_domainnull;
+
+	bool		guaranteed_empty;
 } ExprState;
 
 
@@ -968,6 +970,7 @@ typedef struct SubPlanState
 	FmgrInfo   *lhs_hash_funcs; /* hash functions for lefthand datatype(s) */
 	FmgrInfo   *cur_eq_funcs;	/* equality functions for LHS vs. table */
 	ExprState  *cur_eq_comp;	/* equality comparator for LHS vs. table */
+	bool		guaranteed_empty;
 } SubPlanState;
 
 /*
@@ -1073,6 +1076,8 @@ typedef struct PlanState
 	 */
 	TupleDesc	scandesc;
 
+	bool		guaranteed_empty;
+
 	/*
 	 * Define the slot types for inner, outer and scanslots for expression
 	 * contexts with this state as a parent.  If *opsset is set, then
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index 93c60bde667..41327813976 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -28,6 +28,7 @@
 											 * contents */
 #define QTW_DONT_COPY_QUERY			0x40	/* do not copy top Query */
 #define QTW_EXAMINE_SORTGROUP		0x80	/* include SortGroupNode lists */
+#define QTW_DONT_COPY_DEFAULT		0x00	/* only custom mutator will copy */
 
 /* callback function for check_functions_in_node */
 typedef bool (*check_function_callback) (Oid func_id, void *context);
@@ -132,7 +133,7 @@ extern bool check_functions_in_node(Node *node, check_function_callback checker,
 extern bool expression_tree_walker(Node *node, bool (*walker) (),
 								   void *context);
 extern Node *expression_tree_mutator(Node *node, Node *(*mutator) (),
-									 void *context);
+									 void *context, int flags);
 
 extern bool query_tree_walker(Query *query, bool (*walker) (),
 							  void *context, int flags);
diff --git a/src/include/nodes/pathnodes.h b/src/include/nodes/pathnodes.h
index 9d804d4b321..5390e6ada27 100644
--- a/src/include/nodes/pathnodes.h
+++ b/src/include/nodes/pathnodes.h
@@ -15,10 +15,12 @@
 #define PATHNODES_H
 
 #include "access/sdir.h"
+#include "catalog/pg_statistic.h"
 #include "lib/stringinfo.h"
 #include "nodes/params.h"
 #include "nodes/parsenodes.h"
 #include "storage/block.h"
+#include "utils/lsyscache.h"
 
 
 /*
@@ -888,6 +890,10 @@ struct IndexOptInfo
 	bool		amcanmarkpos;	/* does AM support mark/restore? */
 	/* Rather than include amapi.h here, we declare amcostestimate like this */
 	void		(*amcostestimate) ();	/* AM's cost estimator */
+
+	/* cache for per-tuple index statistic. That stats could be large and it
+	 * will be expensive to uncompress it every time */
+	AttStatsSlot	*sslots;
 };
 
 /*
@@ -1071,6 +1077,16 @@ typedef struct PathKey
 	bool		pk_nulls_first; /* do NULLs come before normal values? */
 } PathKey;
 
+/*
+ * Combines information about pathkeys and the associated clauses.
+ */
+typedef struct PathKeyInfo
+{
+	NodeTag		type;
+	List	   *pathkeys;
+	List	   *clauses;
+} PathKeyInfo;
+
 /*
  * VolatileFunctionStatus -- allows nodes to cache their
  * contain_volatile_functions properties. VOLATILITY_UNKNOWN means not yet
@@ -1458,6 +1474,11 @@ typedef struct AppendPath
 	/* Index of first partial path in subpaths; list_length(subpaths) if none */
 	int			first_partial_path;
 	Cardinality limit_tuples;	/* hard limit on output tuples, or -1 */
+	bool		pull_tlist;	/* if = true, create_append_plan()
+							 * should get targetlist from any
+							 * subpath - they are the same,
+							 * because the only place - append
+							 * index scan for range OR */
 } AppendPath;
 
 #define IS_DUMMY_APPEND(p) \
diff --git a/src/include/nodes/plannodes.h b/src/include/nodes/plannodes.h
index b778ba99134..0caec135ebb 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -21,6 +21,7 @@
 #include "nodes/lockoptions.h"
 #include "nodes/parsenodes.h"
 #include "nodes/primnodes.h"
+#include "portability/instr_time.h"
 
 
 /* ----------------------------------------------------------------
@@ -86,6 +87,8 @@ typedef struct PlannedStmt
 
 	Node	   *utilityStmt;	/* non-null if this is utility stmt */
 
+	instr_time	planDuration;	/* time spent on planning */
+
 	/* statement location in source string (copied from Query) */
 	int			stmt_location;	/* start location, or -1 if unknown */
 	int			stmt_len;		/* length in bytes; 0 means "rest of string" */
diff --git a/src/include/nodes/supportnodes.h b/src/include/nodes/supportnodes.h
index 9fcbc399495..aba687f89e8 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -35,6 +35,22 @@
 
 #include "nodes/plannodes.h"
 
+typedef enum
+{
+	Pattern_Type_Like,
+	Pattern_Type_Like_IC,
+	Pattern_Type_Regex,
+	Pattern_Type_Regex_IC,
+	Pattern_Type_Prefix
+} Pattern_Type;
+
+typedef enum
+{
+	Pattern_Prefix_None,
+	Pattern_Prefix_Partial,
+	Pattern_Prefix_Exact
+} Pattern_Prefix_Status;
+
 struct PlannerInfo;				/* avoid including pathnodes.h here */
 struct IndexOptInfo;
 struct SpecialJoinInfo;
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index bc12071af6e..0fd3a1158a5 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -114,7 +114,9 @@ extern void cost_incremental_sort(Path *path,
 								  Cost input_startup_cost, Cost input_total_cost,
 								  double input_tuples, int width, Cost comparison_cost, int sort_mem,
 								  double limit_tuples);
-extern void cost_append(AppendPath *path);
+extern Cost cost_sort_estimate(PlannerInfo *root, List *pathkeys,
+							   int nPresortedKeys, double tuples);
+extern void cost_append(AppendPath *path, PlannerInfo *root);
 extern void cost_merge_append(Path *path, PlannerInfo *root,
 							  List *pathkeys, int n_streams,
 							  Cost input_startup_cost, Cost input_total_cost,
@@ -174,6 +176,7 @@ extern void cost_gather_merge(GatherMergePath *path, PlannerInfo *root,
 extern void cost_subplan(PlannerInfo *root, SubPlan *subplan, Plan *plan);
 extern void cost_qual_eval(QualCost *cost, List *quals, PlannerInfo *root);
 extern void cost_qual_eval_node(QualCost *cost, Node *qual, PlannerInfo *root);
+extern void cost_qual_eval_node_index(QualCost *cost, Node *qual, PlannerInfo *root);
 extern void compute_semi_anti_join_factors(PlannerInfo *root,
 										   RelOptInfo *joinrel,
 										   RelOptInfo *outerrel,
diff --git a/src/include/optimizer/pathnode.h b/src/include/optimizer/pathnode.h
index d2d46b15df5..377c5d15f21 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -71,7 +71,7 @@ extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel,
 									  List *subpaths, List *partial_subpaths,
 									  List *pathkeys, Relids required_outer,
 									  int parallel_workers, bool parallel_aware,
-									  double rows);
+									  double rows, bool pull_tlist);
 extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
 												 RelOptInfo *rel,
 												 List *subpaths,
@@ -309,6 +309,11 @@ extern RelOptInfo *build_join_rel(PlannerInfo *root,
 								  RelOptInfo *inner_rel,
 								  SpecialJoinInfo *sjinfo,
 								  List **restrictlist_ptr);
+
+extern List *build_joinrel_restrictlist(PlannerInfo *root,
+						   Relids joinrelids,
+						   RelOptInfo *outer_rel,
+						   RelOptInfo *inner_rel);
 extern Relids min_join_parameterization(PlannerInfo *root,
 										Relids joinrelids,
 										RelOptInfo *outer_rel,
diff --git a/src/include/optimizer/paths.h b/src/include/optimizer/paths.h
index e313eb21384..a6d1b59f56f 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -24,6 +24,7 @@ extern PGDLLIMPORT bool enable_geqo;
 extern PGDLLIMPORT int geqo_threshold;
 extern PGDLLIMPORT int min_parallel_table_scan_size;
 extern PGDLLIMPORT int min_parallel_index_scan_size;
+extern PGDLLIMPORT bool enable_group_by_reordering;
 
 /* Hook for plugins to get control in set_rel_pathlist() */
 typedef void (*set_rel_pathlist_hook_type) (PlannerInfo *root,
@@ -72,9 +73,22 @@ extern void debug_print_rel(PlannerInfo *root, RelOptInfo *rel);
  *	  routines to generate index paths
  */
 extern void create_index_paths(PlannerInfo *root, RelOptInfo *rel);
+extern List *generate_bitmap_or_paths(PlannerInfo *root, RelOptInfo *rel,
+									  List *clauses, List *other_clauses);
+/*
+ * UniqueIndexInfo describes a unique index and its corresponding clauses
+ * that guarantee the uniqueness of a relation.
+ */
+typedef struct UniqueIndexInfo
+{
+	IndexOptInfo *index;
+	List *clauses;
+} UniqueIndexInfo;
+
 extern bool relation_has_unique_index_for(PlannerInfo *root, RelOptInfo *rel,
 										  List *restrictlist,
-										  List *exprlist, List *oprlist);
+										  List *exprlist, List *oprlist,
+										  UniqueIndexInfo **info);
 extern bool indexcol_is_bool_constant_for_query(PlannerInfo *root,
 												IndexOptInfo *index,
 												int indexcol);
@@ -202,6 +216,12 @@ typedef enum
 extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2);
 extern bool pathkeys_contained_in(List *keys1, List *keys2);
 extern bool pathkeys_count_contained_in(List *keys1, List *keys2, int *n_common);
+extern int	group_keys_reorder_by_pathkeys(List *pathkeys,
+										   List **group_pathkeys,
+										   List **group_clauses);
+extern List *get_useful_group_keys_orderings(PlannerInfo *root, double nrows,
+											 List *path_pathkeys,
+											 List *group_pathkeys, List *group_clauses);
 extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys,
 											Relids required_outer,
 											CostSelector cost_criterion,
@@ -241,6 +261,7 @@ extern List *select_outer_pathkeys_for_merge(PlannerInfo *root,
 extern List *make_inner_pathkeys_for_merge(PlannerInfo *root,
 										   List *mergeclauses,
 										   List *outer_pathkeys);
+extern int pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys);
 extern List *trim_mergeclauses_for_inner_pathkeys(PlannerInfo *root,
 												  List *mergeclauses,
 												  List *pathkeys);
@@ -248,6 +269,7 @@ extern List *truncate_useless_pathkeys(PlannerInfo *root,
 									   RelOptInfo *rel,
 									   List *pathkeys);
 extern bool has_useful_pathkeys(PlannerInfo *root, RelOptInfo *rel);
+extern void keybased_rewrite_index_paths(PlannerInfo *root, RelOptInfo *rel);
 extern PathKey *make_canonical_pathkey(PlannerInfo *root,
 									   EquivalenceClass *eclass, Oid opfamily,
 									   int strategy, bool nulls_first);
diff --git a/src/include/optimizer/planmain.h b/src/include/optimizer/planmain.h
index 6f87ee3258d..e8782df76fa 100644
--- a/src/include/optimizer/planmain.h
+++ b/src/include/optimizer/planmain.h
@@ -14,6 +14,7 @@
 #ifndef PLANMAIN_H
 #define PLANMAIN_H
 
+#include "optimizer/paths.h"
 #include "nodes/pathnodes.h"
 #include "nodes/plannodes.h"
 
@@ -49,6 +50,9 @@ extern Plan *materialize_finished_plan(Plan *subplan);
 extern bool is_projection_capable_path(Path *path);
 extern bool is_projection_capable_plan(Plan *plan);
 
+extern Node * fix_indexqual_operand(Node *node, IndexOptInfo *index, int
+									indexcol);
+
 /* External use of these functions is deprecated: */
 extern Sort *make_sort_from_sortclauses(List *sortcls, Plan *lefttree);
 extern Agg *make_agg(List *tlist, List *qual,
@@ -100,13 +104,18 @@ extern void match_foreign_keys_to_quals(PlannerInfo *root);
 /*
  * prototypes for plan/analyzejoins.c
  */
-extern List *remove_useless_joins(PlannerInfo *root, List *joinlist);
+extern List *remove_useless_left_joins(PlannerInfo *root, List *joinlist);
 extern void reduce_unique_semijoins(PlannerInfo *root);
 extern bool query_supports_distinctness(Query *query);
 extern bool query_is_distinct_for(Query *query, List *colnos, List *opids);
+
 extern bool innerrel_is_unique(PlannerInfo *root,
 							   Relids joinrelids, Relids outerrelids, RelOptInfo *innerrel,
-							   JoinType jointype, List *restrictlist, bool force_cache);
+				   JoinType jointype, List *restrictlist, bool force_cache,
+				   UniqueIndexInfo **index_info);
+
+extern void remove_useless_self_joins(PlannerInfo *root, List **jointree,
+									  List *tlist);
 
 /*
  * prototypes for plan/setrefs.c
diff --git a/src/include/port.h b/src/include/port.h
index 3f67ab18607..99cdd4d189b 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -107,6 +107,53 @@ extern void pgfnames_cleanup(char **filenames);
 #define is_absolute_path(filename) is_windows_absolute_path(filename)
 #endif
 
+
+/*
+ * Socket error codes handling constants.
+ * Any socket related routines must use SOCK_ERRNO instead of errno!
+ *
+ * In Windows socket errors are checked using WSAGetLastError instead of errno -
+ * errno might return 0 while WSAGetLastError returns actual socket error codes.
+ * WSAGetLastError uses own error codes different from errno.
+ */
+#ifdef WIN32
+#define SOCK_EWOULDBLOCK WSAEWOULDBLOCK
+#define SOCK_EINTR WSAEINTR
+#define SOCK_EINVAL WSAEINVAL
+#define SOCK_EIO WSAEFAULT /* used only for setting error code */
+#define SOCK_EINPROGRESS WSAEINPROGRESS
+#define SOCK_ECONNRESET WSAECONNRESET
+#define SOCK_ECONNABORTED WSAECONNABORTED
+#define SOCK_EHOSTDOWN WSAEHOSTDOWN
+#define SOCK_EHOSTUNREACH WSAEHOSTUNREACH
+#define SOCK_ENETDOWN WSAENETDOWN
+#define SOCK_ENETRESET WSAENETRESET
+#define SOCK_ENETUNREACH WSAENETUNREACH
+#define SOCK_ETIMEDOUT WSAETIMEDOUT
+#else
+#ifdef EAGAIN
+#define SOCK_EAGAIN EAGAIN
+#endif
+#ifdef EWOULDBLOCK
+#define SOCK_EWOULDBLOCK EWOULDBLOCK
+#endif
+#define SOCK_EINTR EINTR
+#define SOCK_EINVAL EINVAL
+#define SOCK_EIO EIO
+#define SOCK_EINPROGRESS EINPROGRESS
+#define SOCK_ECONNRESET ECONNRESET
+#ifdef EPIPE
+#define SOCK_EPIPE EPIPE
+#endif
+#define SOCK_ECONNABORTED ECONNABORTED
+#define SOCK_EHOSTDOWN EHOSTDOWN
+#define SOCK_EHOSTUNREACH EHOSTUNREACH
+#define SOCK_ENETDOWN ENETDOWN
+#define SOCK_ENETRESET ENETRESET
+#define SOCK_ENETUNREACH ENETUNREACH
+#define SOCK_ETIMEDOUT ETIMEDOUT
+#endif
+
 /*
  * This macro provides a centralized list of all errnos that identify
  * hard failure of a previously-established network connection.
@@ -119,16 +166,22 @@ extern void pgfnames_cleanup(char **filenames);
  * are actually reporting errors typically single out EPIPE and ECONNRESET,
  * while allowing the network failures to be reported generically.
  */
-#define ALL_CONNECTION_FAILURE_ERRNOS \
-	EPIPE: \
-	case ECONNRESET: \
-	case ECONNABORTED: \
-	case EHOSTDOWN: \
-	case EHOSTUNREACH: \
-	case ENETDOWN: \
-	case ENETRESET: \
-	case ENETUNREACH: \
-	case ETIMEDOUT
+
+#define ALL_CONNECTION_FAILURE_ERRNOS_COMMON \
+	SOCK_ECONNRESET: \
+	case SOCK_ECONNABORTED: \
+	case SOCK_EHOSTDOWN: \
+	case SOCK_EHOSTUNREACH: \
+	case SOCK_ENETDOWN: \
+	case SOCK_ENETRESET: \
+	case SOCK_ENETUNREACH: \
+	case SOCK_ETIMEDOUT
+
+#ifdef SOCK_EPIPE
+#define ALL_CONNECTION_FAILURE_ERRNOS SOCK_EPIPE: case ALL_CONNECTION_FAILURE_ERRNOS_COMMON
+#else
+#define ALL_CONNECTION_FAILURE_ERRNOS ALL_CONNECTION_FAILURE_ERRNOS_COMMON
+#endif
 
 /* Portable locale initialization (in exec.c) */
 extern void set_pglocale_pgservice(const char *argv0, const char *app);
diff --git a/src/include/storage/s_lock.h b/src/include/storage/s_lock.h
index 4d3ffc767f4..eac431a0624 100644
--- a/src/include/storage/s_lock.h
+++ b/src/include/storage/s_lock.h
@@ -458,6 +458,17 @@ do \
 #endif	 /* __sparc__ */
 
 
+/* Elbrus */
+#ifdef __e2k__
+#define HAS_TEST_AND_SET
+typedef int slock_t;
+/* There is no need to check for sync_lock availability. */
+#define TAS(lock) __sync_lock_test_and_set(lock, 1)
+#define S_UNLOCK(lock) __sync_lock_release(lock)
+#define SPIN_DELAY() do { __asm__ __volatile__ ("nop" : : ); } while(0)
+#endif
+
+
 /* PowerPC */
 #if defined(__ppc__) || defined(__powerpc__) || defined(__ppc64__) || defined(__powerpc64__)
 #define HAS_TEST_AND_SET
diff --git a/src/include/storage/sinval.h b/src/include/storage/sinval.h
index e7cd45658c6..2dd66d8d559 100644
--- a/src/include/storage/sinval.h
+++ b/src/include/storage/sinval.h
@@ -60,6 +60,7 @@
 typedef struct
 {
 	int8		id;				/* cache ID --- must be first */
+	bool		isLocal;
 	Oid			dbId;			/* database ID, or 0 if a shared relation */
 	uint32		hashValue;		/* hash value of key for this catcache */
 } SharedInvalCatcacheMsg;
@@ -78,6 +79,7 @@ typedef struct
 typedef struct
 {
 	int8		id;				/* type field --- must be first */
+	bool		isLocal;
 	Oid			dbId;			/* database ID, or 0 if a shared relation */
 	Oid			relId;			/* relation ID, or 0 if whole relcache */
 } SharedInvalRelcacheMsg;
diff --git a/src/include/utils/catcache.h b/src/include/utils/catcache.h
index c886d2a866e..8d13f8cedf3 100644
--- a/src/include/utils/catcache.h
+++ b/src/include/utils/catcache.h
@@ -224,7 +224,7 @@ extern void CatCacheInvalidate(CatCache *cache, uint32 hashValue);
 extern void PrepareToInvalidateCacheTuple(Relation relation,
 										  HeapTuple tuple,
 										  HeapTuple newtuple,
-										  void (*function) (int, uint32, Oid));
+										  void (*function) (int, uint32, Oid, bool));
 
 extern void PrintCatCacheLeakWarning(HeapTuple tuple);
 extern void PrintCatCacheListLeakWarning(CatCList *list);
diff --git a/src/include/utils/hsearch.h b/src/include/utils/hsearch.h
index 854c3312414..ea95dad7f54 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -122,6 +122,8 @@ typedef struct
 	HTAB	   *hashp;
 	uint32		curBucket;		/* index of current bucket */
 	HASHELEMENT *curEntry;		/* current entry in bucket */
+	bool		hasHashVal;
+	uint32		hashvalue;
 } HASH_SEQ_STATUS;
 
 /*
@@ -141,6 +143,8 @@ extern bool hash_update_hash_key(HTAB *hashp, void *existingEntry,
 								 const void *newKeyPtr);
 extern long hash_get_num_entries(HTAB *hashp);
 extern void hash_seq_init(HASH_SEQ_STATUS *status, HTAB *hashp);
+extern void hash_seq_init_with_hash_value(HASH_SEQ_STATUS *status, HTAB *hashp,
+										  uint32 hashvalue);
 extern void *hash_seq_search(HASH_SEQ_STATUS *status);
 extern void hash_seq_term(HASH_SEQ_STATUS *status);
 extern void hash_freeze(HTAB *hashp);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index b8dd27d4a96..bd815c1231c 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -59,6 +59,8 @@ typedef struct AttStatsSlot
 	/* Remaining fields are private to get_attstatsslot/free_attstatsslot */
 	void	   *values_arr;		/* palloc'd values array, if any */
 	void	   *numbers_arr;	/* palloc'd numbers array, if any */
+
+	bool		incache;		/* do not free because struct is cached */
 } AttStatsSlot;
 
 /* Hook for plugins to get control in get_attavgwidth() */
@@ -187,6 +189,8 @@ extern int32 get_typavgwidth(Oid typid, int32 typmod);
 extern int32 get_attavgwidth(Oid relid, AttrNumber attnum);
 extern bool get_attstatsslot(AttStatsSlot *sslot, HeapTuple statstuple,
 							 int reqkind, Oid reqop, int flags);
+extern AttStatsSlot* fill_attstatsslot(AttStatsSlot *sslots, HeapTuple statstuple,
+				 int reqkind, Oid reqop, int flags);
 extern void free_attstatsslot(AttStatsSlot *sslot);
 extern char *get_namespace_name(Oid nspid);
 extern char *get_namespace_name_or_temp(Oid nspid);
diff --git a/src/include/utils/selfuncs.h b/src/include/utils/selfuncs.h
index 28775518e53..cc22ad5eabe 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -97,6 +97,7 @@ typedef struct VariableStatData
 	bool		isunique;		/* matches unique index or DISTINCT clause */
 	bool		acl_ok;			/* true if user has SELECT privilege on all
 								 * rows from the table or column */
+	AttStatsSlot	*sslots;
 } VariableStatData;
 
 #define ReleaseVariableStats(vardata)  \
@@ -176,6 +177,9 @@ extern double generic_restriction_selectivity(PlannerInfo *root,
 											  Oid oproid, Oid collation,
 											  List *args, int varRelid,
 											  double default_selectivity);
+double prefix_record_histogram_selectivity(VariableStatData *vardata,
+							Datum constvalLeft, Datum constvalRight, int record_cmp_prefix,
+							double ndistinct,int *n_bins);
 extern double ineq_histogram_selectivity(PlannerInfo *root,
 										 VariableStatData *vardata,
 										 Oid opoid, FmgrInfo *opproc,
@@ -190,7 +194,6 @@ extern double var_eq_non_const(VariableStatData *vardata,
 							   Oid oproid, Oid collation,
 							   Node *other,
 							   bool varonleft, bool negate);
-
 extern Selectivity boolvarsel(PlannerInfo *root, Node *arg, int varRelid);
 extern Selectivity booltestsel(PlannerInfo *root, BoolTestType booltesttype,
 							   Node *arg, int varRelid,
@@ -216,6 +219,11 @@ extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
 								  double input_rows, List **pgset,
 								  EstimationInfo *estinfo);
 
+extern double estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs,
+											  double input_rows, List **pgset,
+											  EstimationInfo *estinfo,
+											  List **cache_varinfos, int prevNExprs);
+
 extern void estimate_hash_bucket_stats(PlannerInfo *root,
 									   Node *hashkey, double nbuckets,
 									   Selectivity *mcv_freq,
@@ -239,5 +247,13 @@ extern Selectivity scalararraysel_containment(PlannerInfo *root,
 											  Node *leftop, Node *rightop,
 											  Oid elemtype, bool isEquality, bool useOr,
 											  int varRelid);
-
+extern Selectivity eqjoin_selectivity(PlannerInfo *root, Oid operator, Oid
+									  collation,
+									  VariableStatData* vardata1,
+									  VariableStatData* vardata2,
+									  SpecialJoinInfo *sjinfo,
+									  int record_cmp_prefix);
+extern Selectivity eqconst_selectivity(Oid operator, Oid collation,
+					VariableStatData *vardata, Datum constval, bool constisnull, 
+					bool varonleft, bool negate, int record_cmp_prefix);
 #endif							/* SELFUNCS_H */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index fd875e9eeb2..256d4bea4c5 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2722,11 +2722,11 @@ keep_going:						/* We will come back to here until there is
 					if (connect(conn->sock, addr_cur->ai_addr,
 								addr_cur->ai_addrlen) < 0)
 					{
-						if (SOCK_ERRNO == EINPROGRESS ||
+						if (SOCK_ERRNO == SOCK_EINPROGRESS ||
 #ifdef WIN32
-							SOCK_ERRNO == EWOULDBLOCK ||
+							SOCK_ERRNO == SOCK_EWOULDBLOCK ||
 #endif
-							SOCK_ERRNO == EINTR)
+							SOCK_ERRNO == SOCK_EINTR)
 						{
 							/*
 							 * This is fine - we're in non-blocking mode, and
@@ -4585,7 +4585,7 @@ retry3:
 	if (connect(tmpsock, (struct sockaddr *) &cancel->raddr.addr,
 				cancel->raddr.salen) < 0)
 	{
-		if (SOCK_ERRNO == EINTR)
+		if (SOCK_ERRNO == SOCK_EINTR)
 			/* Interrupted system call - we'll just try again */
 			goto retry3;
 		strlcpy(errbuf, "PQcancel() -- connect() failed: ", errbufsize);
@@ -4602,7 +4602,7 @@ retry3:
 retry4:
 	if (send(tmpsock, (char *) &crp, sizeof(crp), 0) != (int) sizeof(crp))
 	{
-		if (SOCK_ERRNO == EINTR)
+		if (SOCK_ERRNO == SOCK_EINTR)
 			/* Interrupted system call - we'll just try again */
 			goto retry4;
 		strlcpy(errbuf, "PQcancel() -- send() failed: ", errbufsize);
@@ -4619,7 +4619,7 @@ retry4:
 retry5:
 	if (recv(tmpsock, (char *) &crp, 1, 0) < 0)
 	{
-		if (SOCK_ERRNO == EINTR)
+		if (SOCK_ERRNO == SOCK_EINTR)
 			/* Interrupted system call - we'll just try again */
 			goto retry5;
 		/* we ignore other error conditions */
diff --git a/src/interfaces/libpq/fe-misc.c b/src/interfaces/libpq/fe-misc.c
index d416760127d..c2e961d5a1d 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -649,16 +649,16 @@ retry3:
 	{
 		switch (SOCK_ERRNO)
 		{
-			case EINTR:
+			case SOCK_EINTR:
 				goto retry3;
 
 				/* Some systems return EAGAIN/EWOULDBLOCK for no data */
-#ifdef EAGAIN
-			case EAGAIN:
+#ifdef SOCK_EAGAIN
+			case SOCK_EAGAIN:
 				return someread;
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-			case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+			case SOCK_EWOULDBLOCK:
 				return someread;
 #endif
 
@@ -744,16 +744,16 @@ retry4:
 	{
 		switch (SOCK_ERRNO)
 		{
-			case EINTR:
+			case SOCK_EINTR:
 				goto retry4;
 
 				/* Some systems return EAGAIN/EWOULDBLOCK for no data */
-#ifdef EAGAIN
-			case EAGAIN:
+#ifdef SOCK_EAGAIN
+			case SOCK_EAGAIN:
 				return 0;
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-			case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+			case SOCK_EWOULDBLOCK:
 				return 0;
 #endif
 
@@ -873,15 +873,15 @@ pqSendSome(PGconn *conn, int len)
 			/* Anything except EAGAIN/EWOULDBLOCK/EINTR is trouble */
 			switch (SOCK_ERRNO)
 			{
-#ifdef EAGAIN
-				case EAGAIN:
+#ifdef SOCK_EAGAIN
+				case SOCK_EAGAIN:
 					break;
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-				case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+				case SOCK_EWOULDBLOCK:
 					break;
 #endif
-				case EINTR:
+				case SOCK_EINTR:
 					continue;
 
 				default:
@@ -1092,7 +1092,7 @@ pqSocketCheck(PGconn *conn, int forRead, int forWrite, time_t end_time)
 	/* We will retry as long as we get EINTR */
 	do
 		result = pqSocketPoll(conn->sock, forRead, forWrite, end_time);
-	while (result < 0 && SOCK_ERRNO == EINTR);
+	while (result < 0 && SOCK_ERRNO == SOCK_EINTR);
 
 	if (result < 0)
 	{
diff --git a/src/interfaces/libpq/fe-secure-gssapi.c b/src/interfaces/libpq/fe-secure-gssapi.c
index de482cd8575..dc837d01f9d 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -84,8 +84,8 @@
  * transport negotiation is complete).
  *
  * On success, returns the number of data bytes consumed (possibly less than
- * len).  On failure, returns -1 with errno set appropriately.  If the errno
- * indicates a non-retryable error, a message is added to conn->errorMessage.
+ * len). On failure, returns -1 with SOCK_ERRNO (need to use SOCK_ERRNO since it is different from errno in Windows)
+ * set appropriately. If the SOCK_ERRNO indicates a non-retryable error, a message is added to conn->errorMessage.
  * For retryable errors, caller should call again (passing the same or more
  * data) once the socket is ready.
  */
@@ -121,7 +121,7 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
 	{
 		appendPQExpBufferStr(&conn->errorMessage,
 							 "GSSAPI caller failed to retransmit all data needing to be retried\n");
-		errno = EINVAL;
+		SOCK_ERRNO_SET(SOCK_EINVAL);
 		return -1;
 	}
 
@@ -199,7 +199,7 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
 		if (major != GSS_S_COMPLETE)
 		{
 			pg_GSS_error(libpq_gettext("GSSAPI wrap error"), conn, major, minor);
-			errno = EIO;		/* for lack of a better idea */
+			SOCK_ERRNO_SET(SOCK_EIO);		/* for lack of a better idea */
 			goto cleanup;
 		}
 
@@ -207,7 +207,7 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
 		{
 			appendPQExpBufferStr(&conn->errorMessage,
 								 libpq_gettext("outgoing GSSAPI message would not use confidentiality\n"));
-			errno = EIO;		/* for lack of a better idea */
+			SOCK_ERRNO_SET(SOCK_EIO);		/* for lack of a better idea */
 			goto cleanup;
 		}
 
@@ -217,7 +217,7 @@ pg_GSS_write(PGconn *conn, const void *ptr, size_t len)
 							  libpq_gettext("client tried to send oversize GSSAPI packet (%zu > %zu)\n"),
 							  (size_t) output.length,
 							  PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32));
-			errno = EIO;		/* for lack of a better idea */
+			SOCK_ERRNO_SET(SOCK_EIO);		/* for lack of a better idea */
 			goto cleanup;
 		}
 
@@ -260,8 +260,8 @@ cleanup:
  * transport negotiation is complete).
  *
  * Returns the number of data bytes read, or on failure, returns -1
- * with errno set appropriately.  If the errno indicates a non-retryable
- * error, a message is added to conn->errorMessage.  For retryable errors,
+ * with SOCK_ERRNO (need to use SOCK_ERRNO since it is different from errno in Windows) set appropriately.
+ * If the SOCK_ERRNO indicates a non-retryable error, a message is added to conn->errorMessage.  For retryable errors,
  * caller should call again once the socket is ready.
  */
 ssize_t
@@ -343,7 +343,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 			/* If we still haven't got the length, return to the caller */
 			if (PqGSSRecvLength < sizeof(uint32))
 			{
-				errno = EWOULDBLOCK;
+				SOCK_ERRNO_SET(SOCK_EWOULDBLOCK);
 				return -1;
 			}
 		}
@@ -357,7 +357,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 							  libpq_gettext("oversize GSSAPI packet sent by the server (%zu > %zu)\n"),
 							  (size_t) input.length,
 							  PQ_GSS_MAX_PACKET_SIZE - sizeof(uint32));
-			errno = EIO;		/* for lack of a better idea */
+			SOCK_ERRNO_SET(SOCK_EIO);		/* for lack of a better idea */
 			return -1;
 		}
 
@@ -376,7 +376,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 		/* If we don't yet have the whole packet, return to the caller */
 		if (PqGSSRecvLength - sizeof(uint32) < input.length)
 		{
-			errno = EWOULDBLOCK;
+			SOCK_ERRNO_SET(SOCK_EWOULDBLOCK);
 			return -1;
 		}
 
@@ -396,7 +396,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 			pg_GSS_error(libpq_gettext("GSSAPI unwrap error"), conn,
 						 major, minor);
 			ret = -1;
-			errno = EIO;		/* for lack of a better idea */
+			SOCK_ERRNO_SET(SOCK_EIO);		/* for lack of a better idea */
 			goto cleanup;
 		}
 
@@ -405,7 +405,7 @@ pg_GSS_read(PGconn *conn, void *ptr, size_t len)
 			appendPQExpBufferStr(&conn->errorMessage,
 								 libpq_gettext("incoming GSSAPI message did not use confidentiality\n"));
 			ret = -1;
-			errno = EIO;		/* for lack of a better idea */
+			SOCK_ERRNO_SET(SOCK_EIO);		/* for lack of a better idea */
 			goto cleanup;
 		}
 
@@ -441,7 +441,15 @@ gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
 	*ret = pqsecure_raw_read(conn, recv_buffer, length);
 	if (*ret < 0)
 	{
-		if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+		int err = SOCK_ERRNO;
+		if (
+#ifdef SOCK_EAGAIN
+			err == SOCK_EAGAIN ||
+#endif
+#ifdef SOCK_EWOULDBLOCK
+			err == SOCK_EWOULDBLOCK ||
+#endif
+			err == SOCK_EINTR)
 			return PGRES_POLLING_READING;
 		else
 			return PGRES_POLLING_FAILED;
@@ -461,7 +469,16 @@ gss_read(PGconn *conn, void *recv_buffer, size_t length, ssize_t *ret)
 		*ret = pqsecure_raw_read(conn, recv_buffer, length);
 		if (*ret < 0)
 		{
-			if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+			int err = SOCK_ERRNO;
+			
+			if (
+#ifdef SOCK_EAGAIN
+				err == SOCK_EAGAIN ||
+#endif
+#ifdef SOCK_EWOULDBLOCK
+				err == SOCK_EWOULDBLOCK ||
+#endif
+				err == SOCK_EINTR)
 				return PGRES_POLLING_READING;
 			else
 				return PGRES_POLLING_FAILED;
@@ -524,7 +541,15 @@ pqsecure_open_gss(PGconn *conn)
 		ret = pqsecure_raw_write(conn, PqGSSSendBuffer + PqGSSSendNext, amount);
 		if (ret < 0)
 		{
-			if (errno == EAGAIN || errno == EWOULDBLOCK || errno == EINTR)
+			int err = SOCK_ERRNO;
+			if (
+#ifdef SOCK_EAGAIN
+				err == SOCK_EAGAIN ||
+#endif
+#ifdef SOCK_EWOULDBLOCK
+				err == SOCK_EWOULDBLOCK ||
+#endif
+				err == SOCK_EINTR)
 				return PGRES_POLLING_WRITING;
 			else
 				return PGRES_POLLING_FAILED;
diff --git a/src/interfaces/libpq/fe-secure-openssl.c b/src/interfaces/libpq/fe-secure-openssl.c
index 50d14eac0ee..8934a9a2cd5 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -188,7 +188,7 @@ rloop:
 				appendPQExpBufferStr(&conn->errorMessage,
 									 "SSL_read failed but did not provide error information\n");
 				/* assume the connection is broken */
-				result_errno = ECONNRESET;
+				result_errno = SOCK_ECONNRESET;
 			}
 			break;
 		case SSL_ERROR_WANT_READ:
@@ -207,8 +207,11 @@ rloop:
 			if (n < 0 && SOCK_ERRNO != 0)
 			{
 				result_errno = SOCK_ERRNO;
-				if (result_errno == EPIPE ||
-					result_errno == ECONNRESET)
+				if (
+#ifdef SOCK_EPIPE
+					result_errno == SOCK_EPIPE ||
+#endif
+					result_errno == SOCK_ECONNRESET)
 					appendPQExpBufferStr(&conn->errorMessage,
 										 libpq_gettext("server closed the connection unexpectedly\n"
 													   "\tThis probably means the server terminated abnormally\n"
@@ -224,7 +227,7 @@ rloop:
 				appendPQExpBufferStr(&conn->errorMessage,
 									 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
 				/* assume the connection is broken */
-				result_errno = ECONNRESET;
+				result_errno = SOCK_ECONNRESET;
 				n = -1;
 			}
 			break;
@@ -236,7 +239,7 @@ rloop:
 								  libpq_gettext("SSL error: %s\n"), errm);
 				SSLerrfree(errm);
 				/* assume the connection is broken */
-				result_errno = ECONNRESET;
+				result_errno = SOCK_ECONNRESET;
 				n = -1;
 				break;
 			}
@@ -249,7 +252,7 @@ rloop:
 			 */
 			appendPQExpBufferStr(&conn->errorMessage,
 								 libpq_gettext("SSL connection has been closed unexpectedly\n"));
-			result_errno = ECONNRESET;
+			result_errno = SOCK_ECONNRESET;
 			n = -1;
 			break;
 		default:
@@ -257,7 +260,7 @@ rloop:
 							  libpq_gettext("unrecognized SSL error code: %d\n"),
 							  err);
 			/* assume the connection is broken */
-			result_errno = ECONNRESET;
+			result_errno = SOCK_ECONNRESET;
 			n = -1;
 			break;
 	}
@@ -297,7 +300,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 				appendPQExpBufferStr(&conn->errorMessage,
 									 "SSL_write failed but did not provide error information\n");
 				/* assume the connection is broken */
-				result_errno = ECONNRESET;
+				result_errno = SOCK_ECONNRESET;
 			}
 			break;
 		case SSL_ERROR_WANT_READ:
@@ -321,7 +324,11 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 			if (n < 0 && SOCK_ERRNO != 0)
 			{
 				result_errno = SOCK_ERRNO;
-				if (result_errno == EPIPE || result_errno == ECONNRESET)
+				if (
+#ifdef SOCK_EPIPE
+					result_errno == SOCK_EPIPE ||
+#endif
+					result_errno == SOCK_ECONNRESET)
 					appendPQExpBufferStr(&conn->errorMessage,
 										 libpq_gettext("server closed the connection unexpectedly\n"
 													   "\tThis probably means the server terminated abnormally\n"
@@ -337,7 +344,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 				appendPQExpBufferStr(&conn->errorMessage,
 									 libpq_gettext("SSL SYSCALL error: EOF detected\n"));
 				/* assume the connection is broken */
-				result_errno = ECONNRESET;
+				result_errno = SOCK_ECONNRESET;
 				n = -1;
 			}
 			break;
@@ -349,7 +356,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 								  libpq_gettext("SSL error: %s\n"), errm);
 				SSLerrfree(errm);
 				/* assume the connection is broken */
-				result_errno = ECONNRESET;
+				result_errno = SOCK_ECONNRESET;
 				n = -1;
 				break;
 			}
@@ -362,7 +369,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 			 */
 			appendPQExpBufferStr(&conn->errorMessage,
 								 libpq_gettext("SSL connection has been closed unexpectedly\n"));
-			result_errno = ECONNRESET;
+			result_errno = SOCK_ECONNRESET;
 			n = -1;
 			break;
 		default:
@@ -370,7 +377,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 							  libpq_gettext("unrecognized SSL error code: %d\n"),
 							  err);
 			/* assume the connection is broken */
-			result_errno = ECONNRESET;
+			result_errno = SOCK_ECONNRESET;
 			n = -1;
 			break;
 	}
@@ -1821,13 +1828,13 @@ my_sock_read(BIO *h, char *buf, int size)
 		/* If we were interrupted, tell caller to retry */
 		switch (SOCK_ERRNO)
 		{
-#ifdef EAGAIN
-			case EAGAIN:
+#ifdef SOCK_EAGAIN
+			case SOCK_EAGAIN:
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-			case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+			case SOCK_EWOULDBLOCK:
 #endif
-			case EINTR:
+			case SOCK_EINTR:
 				BIO_set_retry_read(h);
 				break;
 
@@ -1851,13 +1858,13 @@ my_sock_write(BIO *h, const char *buf, int size)
 		/* If we were interrupted, tell caller to retry */
 		switch (SOCK_ERRNO)
 		{
-#ifdef EAGAIN
-			case EAGAIN:
+#ifdef SOCK_EAGAIN
+			case SOCK_EAGAIN:
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-			case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+			case SOCK_EWOULDBLOCK:
 #endif
-			case EINTR:
+			case SOCK_EINTR:
 				BIO_set_retry_write(h);
 				break;
 
diff --git a/src/interfaces/libpq/fe-secure.c b/src/interfaces/libpq/fe-secure.c
index 4c85b7299cc..210b4648dab 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -246,18 +246,19 @@ pqsecure_raw_read(PGconn *conn, void *ptr, size_t len)
 		/* Set error message if appropriate */
 		switch (result_errno)
 		{
-#ifdef EAGAIN
-			case EAGAIN:
+#ifdef SOCK_EAGAIN
+			case SOCK_EAGAIN:
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-			case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+			case SOCK_EWOULDBLOCK:
 #endif
-			case EINTR:
+			case SOCK_EINTR:
 				/* no error message, caller is expected to retry */
 				break;
-
-			case EPIPE:
-			case ECONNRESET:
+#ifdef SOCK_EPIPE
+			case SOCK_EPIPE:
+#endif
+			case SOCK_ECONNRESET:
 				appendPQExpBufferStr(&conn->errorMessage,
 									 libpq_gettext("server closed the connection unexpectedly\n"
 												   "\tThis probably means the server terminated abnormally\n"
@@ -406,23 +407,24 @@ retry_masked:
 		/* Set error message if appropriate */
 		switch (result_errno)
 		{
-#ifdef EAGAIN
-			case EAGAIN:
+#ifdef SOCK_EAGAIN
+			case SOCK_EAGAIN:
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-			case EWOULDBLOCK:
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+			case SOCK_EWOULDBLOCK:
 #endif
-			case EINTR:
+			case SOCK_EINTR:
 				/* no error message, caller is expected to retry */
 				break;
-
-			case EPIPE:
+#ifdef SOCK_EPIPE
+			case SOCK_EPIPE:
 				/* Set flag for EPIPE */
 				REMEMBER_EPIPE(spinfo, true);
 
 				/* FALL THRU */
+#endif
 
-			case ECONNRESET:
+			case SOCK_ECONNRESET:
 				conn->write_failed = true;
 				/* Store error message in conn->write_err_msg, if possible */
 				/* (strdup failure is OK, we'll cope later) */
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index 6b3b9f103d8..02b51223400 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1210,7 +1210,8 @@ explain (costs off)
   select distinct min(f1), max(f1) from minmaxtest;
                                          QUERY PLAN                                          
 ---------------------------------------------------------------------------------------------
- Unique
+ HashAggregate
+   Group Key: $0, $1
    InitPlan 1 (returns $0)
      ->  Limit
            ->  Merge Append
@@ -1233,10 +1234,8 @@ explain (costs off)
                  ->  Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest_8
                        Index Cond: (f1 IS NOT NULL)
                  ->  Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest_9
-   ->  Sort
-         Sort Key: ($0), ($1)
-         ->  Result
-(26 rows)
+   ->  Result
+(25 rows)
 
 select distinct min(f1), max(f1) from minmaxtest;
  min | max 
@@ -2485,6 +2484,241 @@ SELECT balk(hundred) FROM tenk1;
 (1 row)
 
 ROLLBACK;
+-- GROUP BY optimization by reorder columns
+SELECT
+	i AS id,
+	i/2 AS p,
+	format('%60s', i%2) AS v,
+	i/4 AS c,
+	i/8 AS d,
+	(random() * (10000/8))::int as e --the same as d but no correlation with p
+	INTO btg
+FROM
+	generate_series(1, 10000) i;
+VACUUM btg;
+ANALYZE btg;
+-- GROUP BY optimization by reorder columns by frequency
+SET enable_hashagg=off;
+SET max_parallel_workers= 0;
+SET max_parallel_workers_per_gather = 0;
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, v;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, v
+   ->  Sort
+         Sort Key: p, v
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, v
+   ->  Sort
+         Sort Key: p, v
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, c, v
+   ->  Sort
+         Sort Key: p, c, v
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY v, p, c;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: v, p, c
+   ->  Sort
+         Sort Key: v, p, c
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, d, c;
+          QUERY PLAN          
+------------------------------
+ GroupAggregate
+   Group Key: p, d, c, v
+   ->  Sort
+         Sort Key: p, d, c, v
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY v, p, d ,c;
+          QUERY PLAN          
+------------------------------
+ GroupAggregate
+   Group Key: v, p, d, c
+   ->  Sort
+         Sort Key: v, p, d, c
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY p, v, d ,c;
+          QUERY PLAN          
+------------------------------
+ GroupAggregate
+   Group Key: p, v, d, c
+   ->  Sort
+         Sort Key: p, v, d, c
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, d, e;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, d, e
+   ->  Sort
+         Sort Key: p, d, e
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, e, d;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, e, d
+   ->  Sort
+         Sort Key: p, e, d
+         ->  Seq Scan on btg
+(5 rows)
+
+CREATE STATISTICS btg_dep ON d, e, p FROM btg;
+ANALYZE btg;
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, d, e;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, d, e
+   ->  Sort
+         Sort Key: p, d, e
+         ->  Seq Scan on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, e, d;
+         QUERY PLAN          
+-----------------------------
+ GroupAggregate
+   Group Key: p, e, d
+   ->  Sort
+         Sort Key: p, e, d
+         ->  Seq Scan on btg
+(5 rows)
+
+-- GROUP BY optimization by reorder columns by index scan
+CREATE INDEX ON btg(p, v);
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+VACUUM btg;
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, v;
+                   QUERY PLAN                   
+------------------------------------------------
+ GroupAggregate
+   Group Key: p, v
+   ->  Index Only Scan using btg_p_v_idx on btg
+(3 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, v ORDER BY p, v;
+                   QUERY PLAN                   
+------------------------------------------------
+ GroupAggregate
+   Group Key: p, v
+   ->  Index Only Scan using btg_p_v_idx on btg
+(3 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p;
+                   QUERY PLAN                   
+------------------------------------------------
+ GroupAggregate
+   Group Key: p, v
+   ->  Index Only Scan using btg_p_v_idx on btg
+(3 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p ORDER BY p, v;
+                   QUERY PLAN                   
+------------------------------------------------
+ GroupAggregate
+   Group Key: p, v
+   ->  Index Only Scan using btg_p_v_idx on btg
+(3 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c;
+                   QUERY PLAN                    
+-------------------------------------------------
+ GroupAggregate
+   Group Key: p, c, v
+   ->  Incremental Sort
+         Sort Key: p, c, v
+         Presorted Key: p
+         ->  Index Scan using btg_p_v_idx on btg
+(6 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY p, v;
+                   QUERY PLAN                    
+-------------------------------------------------
+ GroupAggregate
+   Group Key: p, v, c
+   ->  Incremental Sort
+         Sort Key: p, v, c
+         Presorted Key: p, v
+         ->  Index Scan using btg_p_v_idx on btg
+(6 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, c, p, d;
+                   QUERY PLAN                    
+-------------------------------------------------
+ GroupAggregate
+   Group Key: p, c, d, v
+   ->  Incremental Sort
+         Sort Key: p, c, d, v
+         Presorted Key: p
+         ->  Index Scan using btg_p_v_idx on btg
+(6 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, c, p, d ORDER BY p, v;
+                   QUERY PLAN                    
+-------------------------------------------------
+ GroupAggregate
+   Group Key: p, v, c, d
+   ->  Incremental Sort
+         Sort Key: p, v, c, d
+         Presorted Key: p, v
+         ->  Index Scan using btg_p_v_idx on btg
+(6 rows)
+
+DROP TABLE btg;
+RESET enable_hashagg;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
 -- Secondly test the case of a parallel aggregate combiner function
 -- returning NULL. For that use normal transition function, but a
 -- combiner function returning NULL.
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index d082b0b6b41..5a4ccf7757c 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1832,18 +1832,12 @@ DROP TABLE onek_with_null;
 EXPLAIN (COSTS OFF)
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
-                                                               QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
- Bitmap Heap Scan on tenk1
-   Recheck Cond: (((thousand = 42) AND (tenthous = 1)) OR ((thousand = 42) AND (tenthous = 3)) OR ((thousand = 42) AND (tenthous = 42)))
-   ->  BitmapOr
-         ->  Bitmap Index Scan on tenk1_thous_tenthous
-               Index Cond: ((thousand = 42) AND (tenthous = 1))
-         ->  Bitmap Index Scan on tenk1_thous_tenthous
-               Index Cond: ((thousand = 42) AND (tenthous = 3))
-         ->  Bitmap Index Scan on tenk1_thous_tenthous
-               Index Cond: ((thousand = 42) AND (tenthous = 42))
-(9 rows)
+                           QUERY PLAN                            
+-----------------------------------------------------------------
+ Index Scan using tenk1_thous_tenthous on tenk1
+   Index Cond: ((thousand = 42) AND (thousand = 42))
+   Filter: ((tenthous = 1) OR (tenthous = 3) OR (tenthous = 42))
+(3 rows)
 
 SELECT * FROM tenk1
   WHERE thousand = 42 AND (tenthous = 1 OR tenthous = 3 OR tenthous = 42);
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fed..335929704f4 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -439,6 +439,38 @@ explain (costs off)
    Filter: ((unique1 = unique1) OR (unique2 = unique2))
 (2 rows)
 
+-- Test that broken ECs are processed correctly during self join removal.
+-- Disable merge joins so that we don't get an error about missing commutator.
+-- Test both orientations of the join clause, because only one of them breaks
+-- the EC.
+set enable_mergejoin to off;
+explain (costs off)
+  select * from ec0 m join ec0 n on m.ff = n.ff
+  join ec1 p on m.ff + n.ff = p.f1;
+               QUERY PLAN               
+----------------------------------------
+ Nested Loop
+   Join Filter: ((n.ff + n.ff) = p.f1)
+   ->  Seq Scan on ec1 p
+   ->  Materialize
+         ->  Seq Scan on ec0 n
+               Filter: (ff IS NOT NULL)
+(6 rows)
+
+explain (costs off)
+  select * from ec0 m join ec0 n on m.ff = n.ff
+  join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1;
+                          QUERY PLAN                           
+---------------------------------------------------------------
+ Nested Loop
+   Join Filter: ((p.f1)::bigint = ((n.ff + n.ff))::int8alias1)
+   ->  Seq Scan on ec1 p
+   ->  Materialize
+         ->  Seq Scan on ec0 n
+               Filter: (ff IS NOT NULL)
+(6 rows)
+
+reset enable_mergejoin;
 -- check that we recognize equivalence with dummy domains in the way
 create temp table undername (f1 name, f2 int);
 create temp view overview as
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index b1ea04181c6..b30774c7480 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -823,7 +823,7 @@ reset check_function_bodies;
 set default_with_oids to f;
 -- Should not allow to set it to true.
 set default_with_oids to t;
-ERROR:  tables declared WITH OIDS are not supported
+WARNING:  tables declared WITH OIDS are not supported, ignored
 -- Test GUC categories and flag patterns
 SELECT pg_settings_get_flags(NULL);
  pg_settings_get_flags 
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index 0a631124c22..f35bd97bec6 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -1439,7 +1439,7 @@ set parallel_setup_cost = 0;
 set parallel_tuple_cost = 0;
 set max_parallel_workers_per_gather = 2;
 create table t (a int, b int, c int);
-insert into t select mod(i,10),mod(i,10),i from generate_series(1,10000) s(i);
+insert into t select mod(i,10),mod(i,10),i from generate_series(1,60000) s(i);
 create index on t (a);
 analyze t;
 set enable_incremental_sort = off;
@@ -1460,21 +1460,19 @@ explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1
 
 set enable_incremental_sort = on;
 explain (costs off) select a,b,sum(c) from t group by 1,2 order by 1,2,3 limit 1;
-                              QUERY PLAN                              
-----------------------------------------------------------------------
+                      QUERY PLAN                      
+------------------------------------------------------
  Limit
-   ->  Incremental Sort
+   ->  Sort
          Sort Key: a, b, (sum(c))
-         Presorted Key: a, b
-         ->  GroupAggregate
+         ->  Finalize HashAggregate
                Group Key: a, b
-               ->  Gather Merge
+               ->  Gather
                      Workers Planned: 2
-                     ->  Incremental Sort
-                           Sort Key: a, b
-                           Presorted Key: a
-                           ->  Parallel Index Scan using t_a_idx on t
-(12 rows)
+                     ->  Partial HashAggregate
+                           Group Key: a, b
+                           ->  Parallel Seq Scan on t
+(10 rows)
 
 -- Incremental sort vs. set operations with varno 0
 set enable_hashagg to off;
diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out
index ea8b2454bf8..346bf8f2094 100644
--- a/src/test/regress/expected/index_including.out
+++ b/src/test/regress/expected/index_including.out
@@ -129,13 +129,11 @@ DETAIL:  Failing row contains (1, null, 3, (4,4),(4,4)).
 INSERT INTO tbl SELECT x, 2*x, NULL, NULL FROM generate_series(1,300) AS x;
 explain (costs off)
 select * from tbl where (c1,c2,c3) < (2,5,1);
-                   QUERY PLAN                   
-------------------------------------------------
- Bitmap Heap Scan on tbl
+                 QUERY PLAN                 
+--------------------------------------------
+ Seq Scan on tbl
    Filter: (ROW(c1, c2, c3) < ROW(2, 5, 1))
-   ->  Bitmap Index Scan on covering
-         Index Cond: (ROW(c1, c2) <= ROW(2, 5))
-(4 rows)
+(2 rows)
 
 select * from tbl where (c1,c2,c3) < (2,5,1);
  c1 | c2 | c3 | c4 
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index b356153900f..5ae4be7cbf2 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1984,8 +1984,8 @@ USING (name);
 ------+----+----
  bb   | 12 | 13
  cc   | 22 | 23
- dd   |    | 33
  ee   | 42 |   
+ dd   |    | 33
 (4 rows)
 
 -- Cases with non-nullable expressions in subquery results;
@@ -2019,8 +2019,8 @@ NATURAL FULL JOIN
 ------+------+------+------+------
  bb   |   12 |    2 |   13 |    3
  cc   |   22 |    2 |   23 |    3
- dd   |      |      |   33 |    3
  ee   |   42 |    2 |      |     
+ dd   |      |      |   33 |    3
 (4 rows)
 
 SELECT * FROM
@@ -4518,15 +4518,16 @@ explain (costs off)
 
 explain (costs off)
   select * from tenk1 a full join tenk1 b using(unique2) where unique2 = 42;
-                   QUERY PLAN                    
--------------------------------------------------
- Merge Full Join
-   Merge Cond: (a.unique2 = b.unique2)
+                      QUERY PLAN                       
+-------------------------------------------------------
+ Hash Full Join
+   Hash Cond: (a.unique2 = b.unique2)
    ->  Index Scan using tenk1_unique2 on tenk1 a
          Index Cond: (unique2 = 42)
-   ->  Index Scan using tenk1_unique2 on tenk1 b
-         Index Cond: (unique2 = 42)
-(6 rows)
+   ->  Hash
+         ->  Index Scan using tenk1_unique2 on tenk1 b
+               Index Cond: (unique2 = 42)
+(7 rows)
 
 --
 -- test that quals attached to an outer join have correct semantics,
@@ -4665,18 +4666,20 @@ select d.* from d left join (select * from b group by b.id, b.c_id) s
 explain (costs off)
 select d.* from d left join (select distinct * from b) s
   on d.a = s.id;
-              QUERY PLAN              
---------------------------------------
- Merge Right Join
-   Merge Cond: (b.id = d.a)
-   ->  Unique
-         ->  Sort
-               Sort Key: b.id, b.c_id
-               ->  Seq Scan on b
+                 QUERY PLAN                  
+---------------------------------------------
+ Merge Left Join
+   Merge Cond: (d.a = s.id)
    ->  Sort
          Sort Key: d.a
          ->  Seq Scan on d
-(9 rows)
+   ->  Sort
+         Sort Key: s.id
+         ->  Subquery Scan on s
+               ->  HashAggregate
+                     Group Key: b.id, b.c_id
+                     ->  Seq Scan on b
+(11 rows)
 
 -- check join removal works when uniqueness of the join condition is enforced
 -- by a UNION
@@ -4969,6 +4972,226 @@ select * from
 ----+----+----+----
 (0 rows)
 
+--
+-- test that semi- or inner self-joins on a unique column are removed
+--
+-- enable only nestloop to get more predictable plans
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+create table sj (a int unique, b int);
+insert into sj values (1, null), (null, 2), (2, 1);
+analyze sj;
+select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1;
+ a | b 
+---+---
+ 2 | 1
+(1 row)
+
+explain (costs off)
+select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1;
+                  QUERY PLAN                   
+-----------------------------------------------
+ Seq Scan on sj q
+   Filter: ((a IS NOT NULL) AND (b = (a - 1)))
+(2 rows)
+
+explain (costs off)
+select * from sj p
+where exists (select * from sj q
+				where q.a = p.a and q.b < 10);
+                QUERY PLAN                
+------------------------------------------
+ Seq Scan on sj q
+   Filter: ((a IS NOT NULL) AND (b < 10))
+(2 rows)
+
+-- Double self-join removal.
+-- Use a condition on "b + 1", not on "b", for the second join, so that
+-- the equivalence class is different from the first one, and we can
+-- test the non-ec code path.
+explain (costs off)
+select * from sj t1 join sj t2 on t1.a = t2.a and t1.b = t2.b
+	join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1;
+                                QUERY PLAN                                 
+---------------------------------------------------------------------------
+ Seq Scan on sj t3
+   Filter: ((a IS NOT NULL) AND (b IS NOT NULL) AND ((b + 1) IS NOT NULL))
+(2 rows)
+
+-- subselect that references the removed relation
+explain (costs off)
+select t1.a, (select a from sj where a = t2.a and a = t1.a)
+from sj t1, sj t2
+where t1.a = t2.a;
+                QUERY PLAN                
+------------------------------------------
+ Seq Scan on sj t2
+   Filter: (a IS NOT NULL)
+   SubPlan 1
+     ->  Result
+           One-Time Filter: (t2.a = t2.a)
+           ->  Seq Scan on sj
+                 Filter: (a = t2.a)
+(7 rows)
+
+-- self-join under outer join
+explain (costs off)
+select * from sj x join sj y on x.a = y.a
+left join int8_tbl z on x.a = z.q1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop Left Join
+   Join Filter: (y.a = z.q1)
+   ->  Seq Scan on sj y
+         Filter: (a IS NOT NULL)
+   ->  Materialize
+         ->  Seq Scan on int8_tbl z
+(6 rows)
+
+explain (costs off)
+select * from sj x join sj y on x.a = y.a
+left join int8_tbl z on y.a = z.q1;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop Left Join
+   Join Filter: (y.a = z.q1)
+   ->  Seq Scan on sj y
+         Filter: (a IS NOT NULL)
+   ->  Materialize
+         ->  Seq Scan on int8_tbl z
+(6 rows)
+
+-- Test that placeholders are updated correctly after join removal
+explain (costs off)
+select * from (values (1)) x
+left join (select coalesce(y.q1, 1) from int8_tbl y
+	right join sj j1 inner join sj j2 on j1.a = j2.a
+	on true) z
+on true;
+                QUERY PLAN                
+------------------------------------------
+ Nested Loop Left Join
+   ->  Result
+   ->  Nested Loop Left Join
+         ->  Seq Scan on sj j2
+               Filter: (a IS NOT NULL)
+         ->  Materialize
+               ->  Seq Scan on int8_tbl y
+(7 rows)
+
+-- update lateral references and range table entry reference 
+explain (verbose, costs off)
+select 1 from (select x.* from sj x, sj y where x.a = y.a) q,
+  lateral generate_series(1, q.a) gs(i);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Nested Loop
+   Output: 1
+   ->  Seq Scan on public.sj y
+         Output: y.a, y.b
+         Filter: (y.a IS NOT NULL)
+   ->  Function Scan on pg_catalog.generate_series gs
+         Output: gs.i
+         Function Call: generate_series(1, y.a)
+(8 rows)
+
+explain (verbose, costs off)
+select 1 from (select y.* from sj x, sj y where x.a = y.a) q,
+  lateral generate_series(1, q.a) gs(i);
+                      QUERY PLAN                      
+------------------------------------------------------
+ Nested Loop
+   Output: 1
+   ->  Seq Scan on public.sj y
+         Output: y.a, y.b
+         Filter: (y.a IS NOT NULL)
+   ->  Function Scan on pg_catalog.generate_series gs
+         Output: gs.i
+         Function Call: generate_series(1, y.a)
+(8 rows)
+
+-- Test that a non-EC-derived join clause is processed correctly. Use an
+-- outer join so that we can't form an EC.
+explain (costs off) select * from sj p join sj q on p.a = q.a
+  left join sj r on p.a + q.a = r.a;
+             QUERY PLAN             
+------------------------------------
+ Nested Loop Left Join
+   Join Filter: ((q.a + q.a) = r.a)
+   ->  Seq Scan on sj q
+         Filter: (a IS NOT NULL)
+   ->  Materialize
+         ->  Seq Scan on sj r
+(6 rows)
+
+-- Check that attr_needed is updated correctly after self-join removal. In
+-- this test, k1.b is required at either j1 or j2. If this info is lost,
+-- join targetlist for (k1, k2) will not contain k1.b. Use index scan for
+-- k1 so that we don't get 'b' from physical tlist used for seqscan. Also
+-- disable reordering of joins because this test depends on a particular
+-- join tree.
+create table sk (a int, b int);
+create index sk_a_idx on sk(a);
+set join_collapse_limit to 1;
+set enable_seqscan to off;
+explain (costs off) select 1 from
+	(sk k1 join sk k2 on k1.a = k2.a)
+	join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   Join Filter: (k1.b = j2.b)
+   ->  Nested Loop
+         ->  Index Scan using sk_a_idx on sk k1
+         ->  Index Only Scan using sk_a_idx on sk k2
+               Index Cond: (a = k1.a)
+   ->  Materialize
+         ->  Index Scan using sj_a_key on sj j2
+               Index Cond: (a IS NOT NULL)
+(9 rows)
+
+explain (costs off) select 1 from
+	(sk k1 join sk k2 on k1.a = k2.a)
+	join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b;
+                     QUERY PLAN                      
+-----------------------------------------------------
+ Nested Loop
+   Join Filter: (k1.b = j2.b)
+   ->  Nested Loop
+         ->  Index Scan using sk_a_idx on sk k1
+         ->  Index Only Scan using sk_a_idx on sk k2
+               Index Cond: (a = k1.a)
+   ->  Materialize
+         ->  Index Scan using sj_a_key on sj j2
+               Index Cond: (a IS NOT NULL)
+(9 rows)
+
+reset join_collapse_limit;
+reset enable_seqscan;
+-- If index conditions are different for each side, we won't select the same
+-- row on both sides, so the join can't be removed.
+create table sl(a int, b int);
+create unique index sl_ab on sl(a, b);
+explain (costs off)
+select * from sl t1, sl t2
+where t1.a = t2.a and t1.b = 1 and t2.b = 2;
+                  QUERY PLAN                  
+----------------------------------------------
+ Nested Loop
+   Join Filter: (t1.a = t2.a)
+   ->  Bitmap Heap Scan on sl t1
+         Recheck Cond: (b = 1)
+         ->  Bitmap Index Scan on sl_ab
+               Index Cond: (b = 1)
+   ->  Materialize
+         ->  Bitmap Heap Scan on sl t2
+               Recheck Cond: (b = 2)
+               ->  Bitmap Index Scan on sl_ab
+                     Index Cond: (b = 2)
+(11 rows)
+
+reset enable_hashjoin;
+reset enable_mergejoin;
 --
 -- Test hints given on incorrect column references are useful
 --
@@ -5388,15 +5611,15 @@ select * from
   lateral (values(x.q1,y.q1,y.q2)) v(xq1,yq1,yq2);
         q1        |        q2         |        q1        |        q2         |       xq1        |       yq1        |        yq2        
 ------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
-              123 |               456 |                  |                   |              123 |                  |                  
-              123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
-              123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
-              123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
- 4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
  4567890123456789 |               123 |              123 |               456 | 4567890123456789 |              123 |               456
- 4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
- 4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
+ 4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
  4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789 | 4567890123456789 |               123
+              123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
+ 4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
+              123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
+ 4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
+              123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
+              123 |               456 |                  |                   |              123 |                  |                  
  4567890123456789 | -4567890123456789 |                  |                   | 4567890123456789 |                  |                  
 (10 rows)
 
@@ -5405,15 +5628,15 @@ select * from
   lateral (select x.q1,y.q1,y.q2) v(xq1,yq1,yq2);
         q1        |        q2         |        q1        |        q2         |       xq1        |       yq1        |        yq2        
 ------------------+-------------------+------------------+-------------------+------------------+------------------+-------------------
-              123 |               456 |                  |                   |              123 |                  |                  
-              123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
-              123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
-              123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
- 4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
  4567890123456789 |               123 |              123 |               456 | 4567890123456789 |              123 |               456
- 4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
- 4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
+ 4567890123456789 |               123 |              123 |  4567890123456789 | 4567890123456789 |              123 |  4567890123456789
  4567890123456789 |  4567890123456789 | 4567890123456789 |               123 | 4567890123456789 | 4567890123456789 |               123
+              123 |  4567890123456789 | 4567890123456789 |               123 |              123 | 4567890123456789 |               123
+ 4567890123456789 |  4567890123456789 | 4567890123456789 |  4567890123456789 | 4567890123456789 | 4567890123456789 |  4567890123456789
+              123 |  4567890123456789 | 4567890123456789 |  4567890123456789 |              123 | 4567890123456789 |  4567890123456789
+ 4567890123456789 |  4567890123456789 | 4567890123456789 | -4567890123456789 | 4567890123456789 | 4567890123456789 | -4567890123456789
+              123 |  4567890123456789 | 4567890123456789 | -4567890123456789 |              123 | 4567890123456789 | -4567890123456789
+              123 |               456 |                  |                   |              123 |                  |                  
  4567890123456789 | -4567890123456789 |                  |                   | 4567890123456789 |                  |                  
 (10 rows)
 
@@ -5440,24 +5663,24 @@ select v.* from
   lateral (select x.q1,y.q1 union all select x.q2,y.q2) v(vx,vy);
         vx         |        vy         
 -------------------+-------------------
-               123 |                  
-               456 |                  
-               123 |  4567890123456789
-  4567890123456789 | -4567890123456789
+  4567890123456789 |               123
+               123 |               456
+  4567890123456789 |               123
                123 |  4567890123456789
   4567890123456789 |  4567890123456789
-               123 |  4567890123456789
-  4567890123456789 |               123
   4567890123456789 |               123
                123 |  4567890123456789
   4567890123456789 |               123
-               123 |               456
   4567890123456789 |  4567890123456789
-  4567890123456789 | -4567890123456789
   4567890123456789 |  4567890123456789
+               123 |  4567890123456789
   4567890123456789 |  4567890123456789
   4567890123456789 |  4567890123456789
-  4567890123456789 |               123
+  4567890123456789 | -4567890123456789
+               123 |  4567890123456789
+  4567890123456789 | -4567890123456789
+               123 |                  
+               456 |                  
   4567890123456789 |                  
  -4567890123456789 |                  
 (20 rows)
@@ -5784,15 +6007,15 @@ select * from
          Hash Cond: (d.q1 = c.q2)
          ->  Nested Loop
                Output: a.q1, a.q2, b.q1, d.q1, (COALESCE(b.q2, '42'::bigint)), (COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2))
-               ->  Hash Left Join
+               ->  Hash Right Join
                      Output: a.q1, a.q2, b.q1, (COALESCE(b.q2, '42'::bigint))
-                     Hash Cond: (a.q2 = b.q1)
-                     ->  Seq Scan on public.int8_tbl a
-                           Output: a.q1, a.q2
+                     Hash Cond: (b.q1 = a.q2)
+                     ->  Seq Scan on public.int8_tbl b
+                           Output: b.q1, COALESCE(b.q2, '42'::bigint)
                      ->  Hash
-                           Output: b.q1, (COALESCE(b.q2, '42'::bigint))
-                           ->  Seq Scan on public.int8_tbl b
-                                 Output: b.q1, COALESCE(b.q2, '42'::bigint)
+                           Output: a.q1, a.q2
+                           ->  Seq Scan on public.int8_tbl a
+                                 Output: a.q1, a.q2
                ->  Seq Scan on public.int8_tbl d
                      Output: d.q1, COALESCE((COALESCE(b.q2, '42'::bigint)), d.q2)
          ->  Hash
@@ -6443,44 +6666,39 @@ select * from j1 natural join j2;
 explain (verbose, costs off)
 select * from j1
 inner join (select distinct id from j3) j3 on j1.id = j3.id;
-               QUERY PLAN                
------------------------------------------
+            QUERY PLAN             
+-----------------------------------
  Nested Loop
    Output: j1.id, j3.id
    Inner Unique: true
    Join Filter: (j1.id = j3.id)
-   ->  Unique
+   ->  HashAggregate
          Output: j3.id
-         ->  Sort
+         Group Key: j3.id
+         ->  Seq Scan on public.j3
                Output: j3.id
-               Sort Key: j3.id
-               ->  Seq Scan on public.j3
-                     Output: j3.id
    ->  Seq Scan on public.j1
          Output: j1.id
-(13 rows)
+(11 rows)
 
 -- ensure group by clause allows the inner to become unique
 explain (verbose, costs off)
 select * from j1
 inner join (select id from j3 group by id) j3 on j1.id = j3.id;
-               QUERY PLAN                
------------------------------------------
+            QUERY PLAN             
+-----------------------------------
  Nested Loop
    Output: j1.id, j3.id
    Inner Unique: true
    Join Filter: (j1.id = j3.id)
-   ->  Group
+   ->  HashAggregate
          Output: j3.id
          Group Key: j3.id
-         ->  Sort
+         ->  Seq Scan on public.j3
                Output: j3.id
-               Sort Key: j3.id
-               ->  Seq Scan on public.j3
-                     Output: j3.id
    ->  Seq Scan on public.j1
          Output: j1.id
-(14 rows)
+(11 rows)
 
 drop table j1;
 drop table j2;
@@ -6643,10 +6861,9 @@ where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1 and j2.id1 >= any (array[1,5]);
    Merge Cond: (j1.id1 = j2.id1)
    Join Filter: (j1.id2 = j2.id2)
    ->  Index Scan using j1_id1_idx on j1
-   ->  Index Only Scan using j2_pkey on j2
+   ->  Index Scan using j2_id1_idx on j2
          Index Cond: (id1 >= ANY ('{1,5}'::integer[]))
-         Filter: ((id1 % 1000) = 1)
-(7 rows)
+(6 rows)
 
 select * from j1
 inner join j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
diff --git a/src/test/regress/expected/merge.out b/src/test/regress/expected/merge.out
index 9e5e4d9055a..994584e3ec3 100644
--- a/src/test/regress/expected/merge.out
+++ b/src/test/regress/expected/merge.out
@@ -1468,18 +1468,15 @@ WHEN MATCHED AND t.a < 10 THEN
                            explain_merge                            
 --------------------------------------------------------------------
  Merge on ex_mtarget t (actual rows=0 loops=1)
-   ->  Merge Join (actual rows=0 loops=1)
-         Merge Cond: (t.a = s.a)
-         ->  Sort (actual rows=0 loops=1)
-               Sort Key: t.a
-               Sort Method: quicksort  Memory: xxx
+   ->  Hash Join (actual rows=0 loops=1)
+         Hash Cond: (s.a = t.a)
+         ->  Seq Scan on ex_msource s (actual rows=1 loops=1)
+         ->  Hash (actual rows=0 loops=1)
+               Buckets: xxx  Batches: xxx  Memory Usage: xxx
                ->  Seq Scan on ex_mtarget t (actual rows=0 loops=1)
                      Filter: (a < '-1000'::integer)
                      Rows Removed by Filter: 54
-         ->  Sort (never executed)
-               Sort Key: s.a
-               ->  Seq Scan on ex_msource s (never executed)
-(12 rows)
+(9 rows)
 
 DROP TABLE ex_msource, ex_mtarget;
 DROP FUNCTION explain_merge(text);
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index 0d69619a2f6..71f1debc4ca 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -729,10 +729,10 @@ EXPLAIN (COSTS OFF)
 SELECT a.x, b.y, count(*) FROM (SELECT * FROM pagg_tab1 WHERE x < 20) a LEFT JOIN (SELECT * FROM pagg_tab2 WHERE y > 10) b ON a.x = b.y WHERE a.x > 5 or b.y < 20  GROUP BY a.x, b.y ORDER BY 1, 2;
                              QUERY PLAN                             
 --------------------------------------------------------------------
- Sort
-   Sort Key: pagg_tab1.x, pagg_tab2.y
-   ->  HashAggregate
-         Group Key: pagg_tab1.x, pagg_tab2.y
+ GroupAggregate
+   Group Key: pagg_tab1.x, pagg_tab2.y
+   ->  Sort
+         Sort Key: pagg_tab1.x, pagg_tab2.y
          ->  Hash Left Join
                Hash Cond: (pagg_tab1.x = pagg_tab2.y)
                Filter: ((pagg_tab1.x > 5) OR (pagg_tab2.y < 20))
@@ -949,12 +949,12 @@ SET parallel_setup_cost = 0;
 -- is not partial agg safe.
 EXPLAIN (COSTS OFF)
 SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3;
-                                      QUERY PLAN                                      
---------------------------------------------------------------------------------------
- Sort
-   Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c))
-   ->  Gather
-         Workers Planned: 2
+                                         QUERY PLAN                                         
+--------------------------------------------------------------------------------------------
+ Gather Merge
+   Workers Planned: 2
+   ->  Sort
+         Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c))
          ->  Parallel Append
                ->  GroupAggregate
                      Group Key: pagg_tab_ml.a
@@ -1381,28 +1381,26 @@ SELECT x, sum(y), avg(y), count(*) FROM pagg_tab_para GROUP BY x HAVING avg(y) <
 -- When GROUP BY clause does not match; partial aggregation is performed for each partition.
 EXPLAIN (COSTS OFF)
 SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3;
-                                        QUERY PLAN                                         
--------------------------------------------------------------------------------------------
+                                     QUERY PLAN                                      
+-------------------------------------------------------------------------------------
  Sort
    Sort Key: pagg_tab_para.y, (sum(pagg_tab_para.x)), (avg(pagg_tab_para.x))
-   ->  Finalize GroupAggregate
+   ->  Finalize HashAggregate
          Group Key: pagg_tab_para.y
          Filter: (avg(pagg_tab_para.x) < '12'::numeric)
-         ->  Gather Merge
+         ->  Gather
                Workers Planned: 2
-               ->  Sort
-                     Sort Key: pagg_tab_para.y
-                     ->  Parallel Append
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_para.y
-                                 ->  Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_para_1.y
-                                 ->  Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1
-                           ->  Partial HashAggregate
-                                 Group Key: pagg_tab_para_2.y
-                                 ->  Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2
-(19 rows)
+               ->  Parallel Append
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_para.y
+                           ->  Parallel Seq Scan on pagg_tab_para_p1 pagg_tab_para
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_para_1.y
+                           ->  Parallel Seq Scan on pagg_tab_para_p2 pagg_tab_para_1
+                     ->  Partial HashAggregate
+                           Group Key: pagg_tab_para_2.y
+                           ->  Parallel Seq Scan on pagg_tab_para_p3 pagg_tab_para_2
+(17 rows)
 
 SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) < 12 ORDER BY 1, 2, 3;
  y  |  sum  |         avg         | count 
diff --git a/src/test/regress/expected/partition_join.out b/src/test/regress/expected/partition_join.out
index 8b179fa60d7..39a8cfe1cc7 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -410,34 +410,34 @@ EXPLAIN (COSTS OFF)
 SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
 			  (SELECT t2.a AS t2a, t3.a AS t3a, t2.b t2b, t2.c t2c, least(t1.a,t2.a,t3.b) FROM prt1 t2 JOIN prt2 t3 ON (t2.a = t3.b)) ss
 			  ON t1.c = ss.t2c WHERE (t1.b + coalesce(ss.t2b, 0)) = 0 ORDER BY t1.a;
-                          QUERY PLAN                          
---------------------------------------------------------------
+                       QUERY PLAN                       
+--------------------------------------------------------
  Sort
    Sort Key: t1.a
-   ->  Hash Left Join
-         Hash Cond: ((t1.c)::text = (t2.c)::text)
+   ->  Hash Right Join
+         Hash Cond: ((t2.c)::text = (t1.c)::text)
          Filter: ((t1.b + COALESCE(t2.b, 0)) = 0)
          ->  Append
-               ->  Seq Scan on prt1_p1 t1_1
-               ->  Seq Scan on prt1_p2 t1_2
-               ->  Seq Scan on prt1_p3 t1_3
+               ->  Hash Join
+                     Hash Cond: (t2_1.a = t3_1.b)
+                     ->  Seq Scan on prt1_p1 t2_1
+                     ->  Hash
+                           ->  Seq Scan on prt2_p1 t3_1
+               ->  Hash Join
+                     Hash Cond: (t2_2.a = t3_2.b)
+                     ->  Seq Scan on prt1_p2 t2_2
+                     ->  Hash
+                           ->  Seq Scan on prt2_p2 t3_2
+               ->  Hash Join
+                     Hash Cond: (t2_3.a = t3_3.b)
+                     ->  Seq Scan on prt1_p3 t2_3
+                     ->  Hash
+                           ->  Seq Scan on prt2_p3 t3_3
          ->  Hash
                ->  Append
-                     ->  Hash Join
-                           Hash Cond: (t2_1.a = t3_1.b)
-                           ->  Seq Scan on prt1_p1 t2_1
-                           ->  Hash
-                                 ->  Seq Scan on prt2_p1 t3_1
-                     ->  Hash Join
-                           Hash Cond: (t2_2.a = t3_2.b)
-                           ->  Seq Scan on prt1_p2 t2_2
-                           ->  Hash
-                                 ->  Seq Scan on prt2_p2 t3_2
-                     ->  Hash Join
-                           Hash Cond: (t2_3.a = t3_3.b)
-                           ->  Seq Scan on prt1_p3 t2_3
-                           ->  Hash
-                                 ->  Seq Scan on prt2_p3 t3_3
+                     ->  Seq Scan on prt1_p1 t1_1
+                     ->  Seq Scan on prt1_p2 t1_2
+                     ->  Seq Scan on prt1_p3 t1_3
 (26 rows)
 
 SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
@@ -559,52 +559,41 @@ EXPLAIN (COSTS OFF)
 SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b)
   WHERE a BETWEEN 490 AND 510
   GROUP BY 1, 2 ORDER BY 1, 2;
-                                                   QUERY PLAN                                                    
------------------------------------------------------------------------------------------------------------------
+                                                QUERY PLAN                                                 
+-----------------------------------------------------------------------------------------------------------
  Group
    Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
-   ->  Merge Append
+   ->  Sort
          Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
-         ->  Group
-               Group Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
-               ->  Sort
-                     Sort Key: (COALESCE(prt1.a, p2.a)), (COALESCE(prt1.b, p2.b))
-                     ->  Merge Full Join
-                           Merge Cond: ((prt1.a = p2.a) AND (prt1.b = p2.b))
-                           Filter: ((COALESCE(prt1.a, p2.a) >= 490) AND (COALESCE(prt1.a, p2.a) <= 510))
-                           ->  Sort
-                                 Sort Key: prt1.a, prt1.b
-                                 ->  Seq Scan on prt1_p1 prt1
-                           ->  Sort
-                                 Sort Key: p2.a, p2.b
-                                 ->  Seq Scan on prt2_p1 p2
-         ->  Group
-               Group Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b))
-               ->  Sort
-                     Sort Key: (COALESCE(prt1_1.a, p2_1.a)), (COALESCE(prt1_1.b, p2_1.b))
-                     ->  Merge Full Join
-                           Merge Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b))
-                           Filter: ((COALESCE(prt1_1.a, p2_1.a) >= 490) AND (COALESCE(prt1_1.a, p2_1.a) <= 510))
-                           ->  Sort
-                                 Sort Key: prt1_1.a, prt1_1.b
-                                 ->  Seq Scan on prt1_p2 prt1_1
-                           ->  Sort
-                                 Sort Key: p2_1.a, p2_1.b
-                                 ->  Seq Scan on prt2_p2 p2_1
-         ->  Group
-               Group Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b))
-               ->  Sort
-                     Sort Key: (COALESCE(prt1_2.a, p2_2.a)), (COALESCE(prt1_2.b, p2_2.b))
-                     ->  Merge Full Join
-                           Merge Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b))
-                           Filter: ((COALESCE(prt1_2.a, p2_2.a) >= 490) AND (COALESCE(prt1_2.a, p2_2.a) <= 510))
-                           ->  Sort
-                                 Sort Key: prt1_2.a, prt1_2.b
-                                 ->  Seq Scan on prt1_p3 prt1_2
-                           ->  Sort
-                                 Sort Key: p2_2.a, p2_2.b
-                                 ->  Seq Scan on prt2_p3 p2_2
-(43 rows)
+         ->  Append
+               ->  Merge Full Join
+                     Merge Cond: ((prt1_1.a = p2_1.a) AND (prt1_1.b = p2_1.b))
+                     Filter: ((COALESCE(prt1_1.a, p2_1.a) >= 490) AND (COALESCE(prt1_1.a, p2_1.a) <= 510))
+                     ->  Sort
+                           Sort Key: prt1_1.a, prt1_1.b
+                           ->  Seq Scan on prt1_p1 prt1_1
+                     ->  Sort
+                           Sort Key: p2_1.a, p2_1.b
+                           ->  Seq Scan on prt2_p1 p2_1
+               ->  Merge Full Join
+                     Merge Cond: ((prt1_2.a = p2_2.a) AND (prt1_2.b = p2_2.b))
+                     Filter: ((COALESCE(prt1_2.a, p2_2.a) >= 490) AND (COALESCE(prt1_2.a, p2_2.a) <= 510))
+                     ->  Sort
+                           Sort Key: prt1_2.a, prt1_2.b
+                           ->  Seq Scan on prt1_p2 prt1_2
+                     ->  Sort
+                           Sort Key: p2_2.a, p2_2.b
+                           ->  Seq Scan on prt2_p2 p2_2
+               ->  Merge Full Join
+                     Merge Cond: ((prt1_3.b = p2_3.b) AND (prt1_3.a = p2_3.a))
+                     Filter: ((COALESCE(prt1_3.a, p2_3.a) >= 490) AND (COALESCE(prt1_3.a, p2_3.a) <= 510))
+                     ->  Sort
+                           Sort Key: prt1_3.b, prt1_3.a
+                           ->  Seq Scan on prt1_p3 prt1_3
+                     ->  Sort
+                           Sort Key: p2_3.b, p2_3.a
+                           ->  Seq Scan on prt2_p3 p2_3
+(32 rows)
 
 SELECT a, b FROM prt1 FULL JOIN prt2 p2(b,a,c) USING(a,b)
   WHERE a BETWEEN 490 AND 510
@@ -1069,8 +1058,8 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1, prt1_e t2 WHER
 
 EXPLAIN (COSTS OFF)
 SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a;
-                                QUERY PLAN                                 
----------------------------------------------------------------------------
+                                   QUERY PLAN                                    
+---------------------------------------------------------------------------------
  Sort
    Sort Key: t1.a
    ->  Append
@@ -1099,18 +1088,19 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
                      Index Cond: (a = t1_7.b)
                      Filter: (b = 0)
          ->  Nested Loop
-               ->  HashAggregate
-                     Group Key: t1_8.b
-                     ->  Hash Semi Join
-                           Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
-                           ->  Seq Scan on prt2_p3 t1_8
-                           ->  Hash
-                                 ->  Seq Scan on prt1_e_p3 t1_11
-                                       Filter: (c = 0)
+               ->  Unique
+                     ->  Sort
+                           Sort Key: t1_8.b
+                           ->  Hash Semi Join
+                                 Hash Cond: (t1_8.b = ((t1_11.a + t1_11.b) / 2))
+                                 ->  Seq Scan on prt2_p3 t1_8
+                                 ->  Hash
+                                       ->  Seq Scan on prt1_e_p3 t1_11
+                                             Filter: (c = 0)
                ->  Index Scan using iprt1_p3_a on prt1_p3 t1_5
                      Index Cond: (a = t1_8.b)
                      Filter: (b = 0)
-(39 rows)
+(40 rows)
 
 SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (SELECT (t1.a + t1.b)/2 FROM prt1_e t1 WHERE t1.c = 0)) AND t1.b = 0 ORDER BY t1.a;
   a  | b |  c   
@@ -1188,60 +1178,54 @@ SELECT t1.* FROM prt1 t1 WHERE t1.a IN (SELECT t1.b FROM prt2 t1 WHERE t1.b IN (
 
 EXPLAIN (COSTS OFF)
 SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
-                                 QUERY PLAN                                 
-----------------------------------------------------------------------------
+                           QUERY PLAN                           
+----------------------------------------------------------------
  Sort
    Sort Key: t1.a, t2.b, ((t3.a + t3.b))
    ->  Append
-         ->  Merge Left Join
-               Merge Cond: (t1_1.a = t2_1.b)
-               ->  Sort
-                     Sort Key: t1_1.a
-                     ->  Merge Left Join
-                           Merge Cond: ((((t3_1.a + t3_1.b) / 2)) = t1_1.a)
-                           ->  Sort
-                                 Sort Key: (((t3_1.a + t3_1.b) / 2))
-                                 ->  Seq Scan on prt1_e_p1 t3_1
-                                       Filter: (c = 0)
-                           ->  Sort
-                                 Sort Key: t1_1.a
-                                 ->  Seq Scan on prt1_p1 t1_1
-               ->  Sort
-                     Sort Key: t2_1.b
-                     ->  Seq Scan on prt2_p1 t2_1
-         ->  Merge Left Join
-               Merge Cond: (t1_2.a = t2_2.b)
-               ->  Sort
-                     Sort Key: t1_2.a
-                     ->  Merge Left Join
-                           Merge Cond: ((((t3_2.a + t3_2.b) / 2)) = t1_2.a)
-                           ->  Sort
-                                 Sort Key: (((t3_2.a + t3_2.b) / 2))
-                                 ->  Seq Scan on prt1_e_p2 t3_2
-                                       Filter: (c = 0)
-                           ->  Sort
-                                 Sort Key: t1_2.a
-                                 ->  Seq Scan on prt1_p2 t1_2
+         ->  Merge Right Join
+               Merge Cond: (t1_1.a = (((t3_1.a + t3_1.b) / 2)))
+               ->  Merge Left Join
+                     Merge Cond: (t1_1.a = t2_1.b)
+                     ->  Sort
+                           Sort Key: t1_1.a
+                           ->  Seq Scan on prt1_p1 t1_1
+                     ->  Sort
+                           Sort Key: t2_1.b
+                           ->  Seq Scan on prt2_p1 t2_1
                ->  Sort
-                     Sort Key: t2_2.b
-                     ->  Seq Scan on prt2_p2 t2_2
-         ->  Merge Left Join
-               Merge Cond: (t1_3.a = t2_3.b)
+                     Sort Key: (((t3_1.a + t3_1.b) / 2))
+                     ->  Seq Scan on prt1_e_p1 t3_1
+                           Filter: (c = 0)
+         ->  Merge Right Join
+               Merge Cond: (t1_2.a = (((t3_2.a + t3_2.b) / 2)))
+               ->  Merge Left Join
+                     Merge Cond: (t1_2.a = t2_2.b)
+                     ->  Sort
+                           Sort Key: t1_2.a
+                           ->  Seq Scan on prt1_p2 t1_2
+                     ->  Sort
+                           Sort Key: t2_2.b
+                           ->  Seq Scan on prt2_p2 t2_2
                ->  Sort
-                     Sort Key: t1_3.a
-                     ->  Merge Left Join
-                           Merge Cond: ((((t3_3.a + t3_3.b) / 2)) = t1_3.a)
-                           ->  Sort
-                                 Sort Key: (((t3_3.a + t3_3.b) / 2))
-                                 ->  Seq Scan on prt1_e_p3 t3_3
-                                       Filter: (c = 0)
-                           ->  Sort
-                                 Sort Key: t1_3.a
-                                 ->  Seq Scan on prt1_p3 t1_3
+                     Sort Key: (((t3_2.a + t3_2.b) / 2))
+                     ->  Seq Scan on prt1_e_p2 t3_2
+                           Filter: (c = 0)
+         ->  Merge Right Join
+               Merge Cond: (t1_3.a = (((t3_3.a + t3_3.b) / 2)))
+               ->  Merge Left Join
+                     Merge Cond: (t1_3.a = t2_3.b)
+                     ->  Sort
+                           Sort Key: t1_3.a
+                           ->  Seq Scan on prt1_p3 t1_3
+                     ->  Sort
+                           Sort Key: t2_3.b
+                           ->  Seq Scan on prt2_p3 t2_3
                ->  Sort
-                     Sort Key: t2_3.b
-                     ->  Seq Scan on prt2_p3 t2_3
-(51 rows)
+                     Sort Key: (((t3_3.a + t3_3.b) / 2))
+                     ->  Seq Scan on prt1_e_p3 t3_3
+                           Filter: (c = 0)
+(45 rows)
 
 SELECT t1.a, t1.c, t2.b, t2.c, t3.a + t3.b, t3.c FROM (prt1 t1 LEFT JOIN prt2 t2 ON t1.a = t2.b) RIGHT JOIN prt1_e t3 ON (t1.a = (t3.a + t3.b)/2) WHERE t3.c = 0 ORDER BY t1.a, t2.b, t3.a + t3.b;
   a  |  c   |  b  |  c   | ?column? | c 
@@ -2163,29 +2147,24 @@ SELECT t1.a, t1.c, t2.b, t2.c FROM prt1 t1, prt4_n t2, prt2 t3 WHERE t1.a = t2.a
                        QUERY PLAN                       
 --------------------------------------------------------
  Hash Join
-   Hash Cond: (t2.a = t1.a)
+   Hash Cond: (t1.a = t2.a)
    ->  Append
-         ->  Seq Scan on prt4_n_p1 t2_1
-         ->  Seq Scan on prt4_n_p2 t2_2
-         ->  Seq Scan on prt4_n_p3 t2_3
+         ->  Seq Scan on prt1_p1 t1_1
+         ->  Seq Scan on prt1_p2 t1_2
+         ->  Seq Scan on prt1_p3 t1_3
    ->  Hash
-         ->  Append
-               ->  Hash Join
-                     Hash Cond: (t1_1.a = t3_1.b)
-                     ->  Seq Scan on prt1_p1 t1_1
-                     ->  Hash
+         ->  Hash Join
+               Hash Cond: (t2.a = t3.b)
+               ->  Append
+                     ->  Seq Scan on prt4_n_p1 t2_1
+                     ->  Seq Scan on prt4_n_p2 t2_2
+                     ->  Seq Scan on prt4_n_p3 t2_3
+               ->  Hash
+                     ->  Append
                            ->  Seq Scan on prt2_p1 t3_1
-               ->  Hash Join
-                     Hash Cond: (t1_2.a = t3_2.b)
-                     ->  Seq Scan on prt1_p2 t1_2
-                     ->  Hash
                            ->  Seq Scan on prt2_p2 t3_2
-               ->  Hash Join
-                     Hash Cond: (t1_3.a = t3_3.b)
-                     ->  Seq Scan on prt1_p3 t1_3
-                     ->  Hash
                            ->  Seq Scan on prt2_p3 t3_3
-(23 rows)
+(18 rows)
 
 -- partitionwise join can not be applied if there are no equi-join conditions
 -- between partition keys
diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out
index 33a6dceb0e3..e39d4594787 100644
--- a/src/test/regress/expected/select.out
+++ b/src/test/regress/expected/select.out
@@ -521,6 +521,124 @@ SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v;
 --
 (1 row)
 
+--
+-- test order by NULLS (FIRST|LAST)
+--
+select unique1, unique2 into onek_with_null from onek;
+insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL);
+select * from onek_with_null order by unique1 nulls first	, unique2				limit 3;
+ unique1 | unique2 
+---------+---------
+         |      -1
+         |        
+       0 |     998
+(3 rows)
+
+select * from onek_with_null order by unique1 nulls last	, unique2				limit 3;
+ unique1 | unique2 
+---------+---------
+       0 |     998
+       1 |     214
+       2 |     326
+(3 rows)
+
+select * from onek_with_null order by unique1 nulls first	, unique2	nulls first	limit 3;
+ unique1 | unique2 
+---------+---------
+         |        
+         |      -1
+       0 |     998
+(3 rows)
+
+select * from onek_with_null order by unique1 nulls last	, unique2	nulls first	limit 3;
+ unique1 | unique2 
+---------+---------
+       0 |     998
+       1 |     214
+       2 |     326
+(3 rows)
+
+select * from onek_with_null order by unique1 nulls first	, unique2	nulls last	limit 3;
+ unique1 | unique2 
+---------+---------
+         |      -1
+         |        
+       0 |     998
+(3 rows)
+
+select * from onek_with_null order by unique1 nulls last	, unique2	nulls last	limit 3;
+ unique1 | unique2 
+---------+---------
+       0 |     998
+       1 |     214
+       2 |     326
+(3 rows)
+
+select * from onek_with_null order by unique1 desc nulls first	, unique2	desc 			limit 3;
+ unique1 | unique2 
+---------+---------
+         |        
+         |      -1
+     999 |     152
+(3 rows)
+
+select * from onek_with_null order by unique1 desc nulls last	, unique2	desc 			limit 3;
+ unique1 | unique2 
+---------+---------
+     999 |     152
+     998 |     549
+     997 |      21
+(3 rows)
+
+select * from onek_with_null order by unique1 desc nulls first	, unique2	desc nulls first	limit 3;
+ unique1 | unique2 
+---------+---------
+         |        
+         |      -1
+     999 |     152
+(3 rows)
+
+select * from onek_with_null order by unique1 desc nulls last	, unique2	desc nulls first	limit 3;
+ unique1 | unique2 
+---------+---------
+     999 |     152
+     998 |     549
+     997 |      21
+(3 rows)
+
+select * from onek_with_null order by unique1 desc nulls first	, unique2	desc nulls last	limit 3;
+ unique1 | unique2 
+---------+---------
+         |      -1
+         |        
+     999 |     152
+(3 rows)
+
+select * from onek_with_null order by unique1 desc nulls last	, unique2	desc nulls last	limit 3;
+ unique1 | unique2 
+---------+---------
+     999 |     152
+     998 |     549
+     997 |      21
+(3 rows)
+
+select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first	, u2	nulls first	limit 3;
+ u1 | u2  
+----+-----
+    |    
+    |  -1
+  0 | 998
+(3 rows)
+
+select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first	, u2 desc	nulls first	limit 3;
+ u1 | u2  
+----+-----
+    |    
+    |  -1
+  0 | 998
+(3 rows)
+
+drop table onek_with_null;
 --
 -- Test ORDER BY options
 --
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index 34293c043c5..280505b79da 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -4,6 +4,7 @@
 -- with autovacuum_enabled = off, so that we don't have unstable results
 -- from auto-analyze happening when we didn't expect it.
 --
+set default_statistics_target=10000; --prevent random subset for joinsel
 -- check the number of estimated/actual rows in the top node
 create function check_estimated_rows(text) returns table (estimated int, actual int)
 language plpgsql as
@@ -1113,43 +1114,43 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b = ''1''');
  estimated | actual 
 -----------+--------
-         2 |    100
+        99 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 51) AND b IN (''1'', ''2'')');
  estimated | actual 
 -----------+--------
-         4 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b IN (''1'', ''2'')');
  estimated | actual 
 -----------+--------
-         8 |    200
+       197 |    200
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 51, 52) AND b = ''1''');
  estimated | actual 
 -----------+--------
-         4 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c = 1');
  estimated | actual 
 -----------+--------
-         1 |    200
+       197 |    200
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 26, 51, 76) AND b IN (''1'', ''26'') AND c IN (1)');
  estimated | actual 
 -----------+--------
-         1 |    200
+       197 |    200
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a IN (1, 2, 26, 27, 51, 52, 76, 77) AND b IN (''1'', ''2'', ''26'', ''27'') AND c IN (1, 2)');
  estimated | actual 
 -----------+--------
-         3 |    400
+       386 |    400
 (1 row)
 
 -- OR clauses referencing the same attribute
@@ -1182,37 +1183,37 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ''1''');
  estimated | actual 
 -----------+--------
-         2 |    100
+        99 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 51]) AND b = ANY (ARRAY[''1'', ''2''])');
  estimated | actual 
 -----------+--------
-         4 |    100
+       100 |    100
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 51, 52]) AND b = ANY (ARRAY[''1'', ''2''])');
  estimated | actual 
 -----------+--------
-         8 |    200
+       197 |    200
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = 1');
  estimated | actual 
 -----------+--------
-         1 |    200
+       197 |    200
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 26, 51, 76]) AND b = ANY (ARRAY[''1'', ''26'']) AND c = ANY (ARRAY[1])');
  estimated | actual 
 -----------+--------
-         1 |    200
+       197 |    200
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a = ANY (ARRAY[1, 2, 26, 27, 51, 52, 76, 77]) AND b = ANY (ARRAY[''1'', ''2'', ''26'', ''27'']) AND c = ANY (ARRAY[1, 2])');
  estimated | actual 
 -----------+--------
-         3 |    400
+       386 |    400
 (1 row)
 
 -- ANY with inequalities should not benefit from functional dependencies
@@ -1878,7 +1879,7 @@ ANALYZE mcv_lists;
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1''');
  estimated | actual 
 -----------+--------
-         3 |      4
+         4 |      4
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists WHERE a = 1 AND b = ''1'' AND c = 1');
@@ -2815,7 +2816,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 0');
  estimated | actual 
 -----------+--------
-        96 |    102
+       102 |    102
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 10 AND b = 10 AND c = 10');
@@ -2839,7 +2840,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE a = 0 OR b = 0 OR c = 10');
  estimated | actual 
 -----------+--------
-       102 |    104
+       104 |    104
 (1 row)
 
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0 AND c = 0) OR (a = 1 AND b = 1 AND c = 1) OR (a = 2 AND b = 2 AND c = 2)');
@@ -2851,7 +2852,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0
 SELECT * FROM check_estimated_rows('SELECT * FROM mcv_lists_partial WHERE (a = 0 AND b = 0) OR (a = 0 AND c = 0) OR (b = 0 AND c = 0)');
  estimated | actual 
 -----------+--------
-       108 |    102
+       102 |    102
 (1 row)
 
 DROP TABLE mcv_lists_partial;
diff --git a/src/test/regress/expected/sysviews.out b/src/test/regress/expected/sysviews.out
index 579b861d84f..4e775af1758 100644
--- a/src/test/regress/expected/sysviews.out
+++ b/src/test/regress/expected/sysviews.out
@@ -114,6 +114,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_async_append            | on
  enable_bitmapscan              | on
  enable_gathermerge             | on
+ enable_group_by_reordering     | on
  enable_hashagg                 | on
  enable_hashjoin                | on
  enable_incremental_sort        | on
@@ -131,7 +132,7 @@ select name, setting from pg_settings where name like 'enable%';
  enable_seqscan                 | on
  enable_sort                    | on
  enable_tidscan                 | on
-(20 rows)
+(21 rows)
 
 -- Test that the pg_timezone_names and pg_timezone_abbrevs views are
 -- more-or-less working.  We can't test their contents in any great detail
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index d3ac08c9ee3..5404e59bc45 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -171,10 +171,12 @@ WHERE t1.typinput = p1.oid AND t1.typtype in ('b', 'p') AND NOT
     (t1.typelem != 0 AND t1.typlen < 0) AND NOT
     (p1.prorettype = t1.oid AND NOT p1.proretset)
 ORDER BY 1;
- oid  |  typname  | oid | proname 
-------+-----------+-----+---------
- 1790 | refcursor |  46 | textin
-(1 row)
+  oid  |  typname  | oid  |   proname    
+-------+-----------+------+--------------
+  1790 | refcursor |   46 | textin
+ 14756 | abstime   | 1312 | timestamp_in
+ 14757 | reltime   | 1312 | timestamp_in
+(3 rows)
 
 -- Varlena array types will point to array_in
 -- Exception as of 8.1: int2vector and oidvector have their own I/O routines
@@ -223,10 +225,12 @@ WHERE t1.typoutput = p1.oid AND t1.typtype in ('b', 'p') AND NOT
       (p1.oid = 'array_out'::regproc AND
        t1.typelem != 0 AND t1.typlen = -1)))
 ORDER BY 1;
- oid  |  typname  | oid | proname 
-------+-----------+-----+---------
- 1790 | refcursor |  47 | textout
-(1 row)
+  oid  |  typname  | oid  |    proname    
+-------+-----------+------+---------------
+  1790 | refcursor |   47 | textout
+ 14756 | abstime   | 1313 | timestamp_out
+ 14757 | reltime   | 1313 | timestamp_out
+(3 rows)
 
 SELECT t1.oid, t1.typname, p1.oid, p1.proname
 FROM pg_type AS t1, pg_proc AS p1
@@ -287,10 +291,12 @@ WHERE t1.typreceive = p1.oid AND t1.typtype in ('b', 'p') AND NOT
     (t1.typelem != 0 AND t1.typlen < 0) AND NOT
     (p1.prorettype = t1.oid AND NOT p1.proretset)
 ORDER BY 1;
- oid  |  typname  | oid  | proname  
-------+-----------+------+----------
- 1790 | refcursor | 2414 | textrecv
-(1 row)
+  oid  |  typname  | oid  |    proname     
+-------+-----------+------+----------------
+  1790 | refcursor | 2414 | textrecv
+ 14756 | abstime   | 2474 | timestamp_recv
+ 14757 | reltime   | 2474 | timestamp_recv
+(3 rows)
 
 -- Varlena array types will point to array_recv
 -- Exception as of 8.1: int2vector and oidvector have their own I/O routines
@@ -348,10 +354,12 @@ WHERE t1.typsend = p1.oid AND t1.typtype in ('b', 'p') AND NOT
       (p1.oid = 'array_send'::regproc AND
        t1.typelem != 0 AND t1.typlen = -1)))
 ORDER BY 1;
- oid  |  typname  | oid  | proname  
-------+-----------+------+----------
- 1790 | refcursor | 2415 | textsend
-(1 row)
+  oid  |  typname  | oid  |    proname     
+-------+-----------+------+----------------
+  1790 | refcursor | 2415 | textsend
+ 14756 | abstime   | 2475 | timestamp_send
+ 14757 | reltime   | 2475 | timestamp_send
+(3 rows)
 
 SELECT t1.oid, t1.typname, p1.oid, p1.proname
 FROM pg_type AS t1, pg_proc AS p1
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index dece7310cfe..7ac4a9380e2 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -1303,24 +1303,22 @@ select distinct q1 from
    union all
    select distinct * from int8_tbl i82) ss
 where q2 = q2;
-                        QUERY PLAN                        
-----------------------------------------------------------
- Unique
-   ->  Merge Append
-         Sort Key: "*SELECT* 1".q1
+                     QUERY PLAN                     
+----------------------------------------------------
+ HashAggregate
+   Group Key: "*SELECT* 1".q1
+   ->  Append
          ->  Subquery Scan on "*SELECT* 1"
-               ->  Unique
-                     ->  Sort
-                           Sort Key: i81.q1, i81.q2
-                           ->  Seq Scan on int8_tbl i81
-                                 Filter: (q2 IS NOT NULL)
+               ->  HashAggregate
+                     Group Key: i81.q1, i81.q2
+                     ->  Seq Scan on int8_tbl i81
+                           Filter: (q2 IS NOT NULL)
          ->  Subquery Scan on "*SELECT* 2"
-               ->  Unique
-                     ->  Sort
-                           Sort Key: i82.q1, i82.q2
-                           ->  Seq Scan on int8_tbl i82
-                                 Filter: (q2 IS NOT NULL)
-(15 rows)
+               ->  HashAggregate
+                     Group Key: i82.q1, i82.q2
+                     ->  Seq Scan on int8_tbl i82
+                           Filter: (q2 IS NOT NULL)
+(13 rows)
 
 select distinct q1 from
   (select distinct * from int8_tbl i81
@@ -1339,24 +1337,22 @@ select distinct q1 from
    union all
    select distinct * from int8_tbl i82) ss
 where -q1 = q2;
-                       QUERY PLAN                       
---------------------------------------------------------
- Unique
-   ->  Merge Append
-         Sort Key: "*SELECT* 1".q1
+                    QUERY PLAN                    
+--------------------------------------------------
+ HashAggregate
+   Group Key: "*SELECT* 1".q1
+   ->  Append
          ->  Subquery Scan on "*SELECT* 1"
-               ->  Unique
-                     ->  Sort
-                           Sort Key: i81.q1, i81.q2
-                           ->  Seq Scan on int8_tbl i81
-                                 Filter: ((- q1) = q2)
+               ->  HashAggregate
+                     Group Key: i81.q1, i81.q2
+                     ->  Seq Scan on int8_tbl i81
+                           Filter: ((- q1) = q2)
          ->  Subquery Scan on "*SELECT* 2"
-               ->  Unique
-                     ->  Sort
-                           Sort Key: i82.q1, i82.q2
-                           ->  Seq Scan on int8_tbl i82
-                                 Filter: ((- q1) = q2)
-(15 rows)
+               ->  HashAggregate
+                     Group Key: i82.q1, i82.q2
+                     ->  Seq Scan on int8_tbl i82
+                           Filter: ((- q1) = q2)
+(13 rows)
 
 select distinct q1 from
   (select distinct * from int8_tbl i81
diff --git a/src/test/regress/expected/updatable_views.out b/src/test/regress/expected/updatable_views.out
index 78791442490..571fc391329 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2502,16 +2502,13 @@ SELECT * FROM rw_view1;
 (1 row)
 
 EXPLAIN (costs off) DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
-                            QUERY PLAN                             
--------------------------------------------------------------------
+                    QUERY PLAN                    
+--------------------------------------------------
  Update on base_tbl base_tbl_1
-   ->  Nested Loop
-         ->  Index Scan using base_tbl_pkey on base_tbl base_tbl_1
-               Index Cond: (id = 1)
-         ->  Index Scan using base_tbl_pkey on base_tbl
-               Index Cond: (id = 1)
-               Filter: ((NOT deleted) AND snoop(data))
-(7 rows)
+   ->  Index Scan using base_tbl_pkey on base_tbl
+         Index Cond: (id = 1)
+         Filter: ((NOT deleted) AND snoop(data))
+(4 rows)
 
 DELETE FROM rw_view1 WHERE id = 1 AND snoop(data);
 NOTICE:  snooped value: Row 1
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index b89cf26c5a2..1025eb09fdb 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -1038,6 +1038,105 @@ SELECT balk(hundred) FROM tenk1;
 
 ROLLBACK;
 
+-- GROUP BY optimization by reorder columns
+
+SELECT
+	i AS id,
+	i/2 AS p,
+	format('%60s', i%2) AS v,
+	i/4 AS c,
+	i/8 AS d,
+	(random() * (10000/8))::int as e --the same as d but no correlation with p
+	INTO btg
+FROM
+	generate_series(1, 10000) i;
+
+VACUUM btg;
+ANALYZE btg;
+
+-- GROUP BY optimization by reorder columns by frequency
+
+SET enable_hashagg=off;
+SET max_parallel_workers= 0;
+SET max_parallel_workers_per_gather = 0;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, v;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY v, p, c;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, d, c;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY v, p, d ,c;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, d, c ORDER BY p, v, d ,c;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, d, e;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, e, d;
+
+CREATE STATISTICS btg_dep ON d, e, p FROM btg;
+ANALYZE btg;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, d, e;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, e, d;
+
+
+-- GROUP BY optimization by reorder columns by index scan
+
+CREATE INDEX ON btg(p, v);
+SET enable_seqscan=off;
+SET enable_bitmapscan=off;
+VACUUM btg;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, v;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY p, v ORDER BY p, v;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p ORDER BY p, v;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, p, c ORDER BY p, v;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, c, p, d;
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, c, p, d ORDER BY p, v;
+
+DROP TABLE btg;
+
+RESET enable_hashagg;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+
+
 -- Secondly test the case of a parallel aggregate combiner function
 -- returning NULL. For that use normal transition function, but a
 -- combiner function returning NULL.
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a31055..9a70be2d7aa 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -263,6 +263,22 @@ explain (costs off)
 explain (costs off)
   select * from tenk1 where unique1 = unique1 or unique2 = unique2;
 
+-- Test that broken ECs are processed correctly during self join removal.
+-- Disable merge joins so that we don't get an error about missing commutator.
+-- Test both orientations of the join clause, because only one of them breaks
+-- the EC.
+set enable_mergejoin to off;
+
+explain (costs off)
+  select * from ec0 m join ec0 n on m.ff = n.ff
+  join ec1 p on m.ff + n.ff = p.f1;
+
+explain (costs off)
+  select * from ec0 m join ec0 n on m.ff = n.ff
+  join ec1 p on p.f1::int8 = (m.ff + n.ff)::int8alias1;
+
+reset enable_mergejoin;
+
 -- check that we recognize equivalence with dummy domains in the way
 create temp table undername (f1 name, f2 int);
 create temp view overview as
diff --git a/src/test/regress/sql/incremental_sort.sql b/src/test/regress/sql/incremental_sort.sql
index 284a354dbb7..6a0e87c7f64 100644
--- a/src/test/regress/sql/incremental_sort.sql
+++ b/src/test/regress/sql/incremental_sort.sql
@@ -213,7 +213,7 @@ set parallel_tuple_cost = 0;
 set max_parallel_workers_per_gather = 2;
 
 create table t (a int, b int, c int);
-insert into t select mod(i,10),mod(i,10),i from generate_series(1,10000) s(i);
+insert into t select mod(i,10),mod(i,10),i from generate_series(1,60000) s(i);
 create index on t (a);
 analyze t;
 
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 11a857041a8..6c9b278fc09 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1785,6 +1785,104 @@ select * from
 select * from
   int8_tbl x join (int4_tbl x cross join int4_tbl y(ff)) j on q1 = f1; -- ok
 
+--
+-- test that semi- or inner self-joins on a unique column are removed
+--
+
+-- enable only nestloop to get more predictable plans
+set enable_hashjoin to off;
+set enable_mergejoin to off;
+
+create table sj (a int unique, b int);
+insert into sj values (1, null), (null, 2), (2, 1);
+analyze sj;
+
+select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1;
+
+explain (costs off)
+select p.* from sj p, sj q where q.a = p.a and q.b = q.a - 1;
+
+explain (costs off)
+select * from sj p
+where exists (select * from sj q
+				where q.a = p.a and q.b < 10);
+
+-- Double self-join removal.
+-- Use a condition on "b + 1", not on "b", for the second join, so that
+-- the equivalence class is different from the first one, and we can
+-- test the non-ec code path.
+explain (costs off)
+select * from sj t1 join sj t2 on t1.a = t2.a and t1.b = t2.b
+	join sj t3 on t2.a = t3.a and t2.b + 1 = t3.b + 1;
+
+-- subselect that references the removed relation
+explain (costs off)
+select t1.a, (select a from sj where a = t2.a and a = t1.a)
+from sj t1, sj t2
+where t1.a = t2.a;
+
+-- self-join under outer join
+explain (costs off)
+select * from sj x join sj y on x.a = y.a
+left join int8_tbl z on x.a = z.q1;
+
+explain (costs off)
+select * from sj x join sj y on x.a = y.a
+left join int8_tbl z on y.a = z.q1;
+
+-- Test that placeholders are updated correctly after join removal
+explain (costs off)
+select * from (values (1)) x
+left join (select coalesce(y.q1, 1) from int8_tbl y
+	right join sj j1 inner join sj j2 on j1.a = j2.a
+	on true) z
+on true;
+
+-- update lateral references and range table entry reference 
+explain (verbose, costs off)
+select 1 from (select x.* from sj x, sj y where x.a = y.a) q,
+  lateral generate_series(1, q.a) gs(i);
+
+explain (verbose, costs off)
+select 1 from (select y.* from sj x, sj y where x.a = y.a) q,
+  lateral generate_series(1, q.a) gs(i);
+
+-- Test that a non-EC-derived join clause is processed correctly. Use an
+-- outer join so that we can't form an EC.
+explain (costs off) select * from sj p join sj q on p.a = q.a
+  left join sj r on p.a + q.a = r.a;
+
+-- Check that attr_needed is updated correctly after self-join removal. In
+-- this test, k1.b is required at either j1 or j2. If this info is lost,
+-- join targetlist for (k1, k2) will not contain k1.b. Use index scan for
+-- k1 so that we don't get 'b' from physical tlist used for seqscan. Also
+-- disable reordering of joins because this test depends on a particular
+-- join tree.
+create table sk (a int, b int);
+create index sk_a_idx on sk(a);
+set join_collapse_limit to 1;
+set enable_seqscan to off;
+explain (costs off) select 1 from
+	(sk k1 join sk k2 on k1.a = k2.a)
+	join (sj j1 join sj j2 on j1.a = j2.a) on j1.b = k1.b;
+explain (costs off) select 1 from
+	(sk k1 join sk k2 on k1.a = k2.a)
+	join (sj j1 join sj j2 on j1.a = j2.a) on j2.b = k1.b;
+reset join_collapse_limit;
+reset enable_seqscan;
+
+-- If index conditions are different for each side, we won't select the same
+-- row on both sides, so the join can't be removed.
+create table sl(a int, b int);
+create unique index sl_ab on sl(a, b);
+
+explain (costs off)
+select * from sl t1, sl t2
+where t1.a = t2.a and t1.b = 1 and t2.b = 2;
+
+reset enable_hashjoin;
+reset enable_mergejoin;
+
 --
 -- Test hints given on incorrect column references are useful
 --
diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql
index 019f1e76739..3dd3a606e2a 100644
--- a/src/test/regress/sql/select.sql
+++ b/src/test/regress/sql/select.sql
@@ -148,6 +148,33 @@ CREATE TEMP TABLE nocols();
 INSERT INTO nocols DEFAULT VALUES;
 SELECT * FROM nocols n, LATERAL (VALUES(n.*)) v;
 
+--
+-- test order by NULLS (FIRST|LAST)
+--
+
+select unique1, unique2 into onek_with_null from onek;
+insert into onek_with_null (unique1,unique2) values (NULL, -1), (NULL, NULL);
+
+
+select * from onek_with_null order by unique1 nulls first	, unique2				limit 3;
+select * from onek_with_null order by unique1 nulls last	, unique2				limit 3;
+select * from onek_with_null order by unique1 nulls first	, unique2	nulls first	limit 3;
+select * from onek_with_null order by unique1 nulls last	, unique2	nulls first	limit 3;
+select * from onek_with_null order by unique1 nulls first	, unique2	nulls last	limit 3;
+select * from onek_with_null order by unique1 nulls last	, unique2	nulls last	limit 3;
+
+select * from onek_with_null order by unique1 desc nulls first	, unique2	desc 			limit 3;
+select * from onek_with_null order by unique1 desc nulls last	, unique2	desc 			limit 3;
+select * from onek_with_null order by unique1 desc nulls first	, unique2	desc nulls first	limit 3;
+select * from onek_with_null order by unique1 desc nulls last	, unique2	desc nulls first	limit 3;
+select * from onek_with_null order by unique1 desc nulls first	, unique2	desc nulls last	limit 3;
+select * from onek_with_null order by unique1 desc nulls last	, unique2	desc nulls last	limit 3;
+
+select unique1 as u1, unique2 as u2 from onek_with_null order by u1 nulls first	, u2	nulls first	limit 3;
+select unique1 as u1, unique2 as u2 from onek_with_null order by u1 asc nulls first	, u2 desc	nulls first	limit 3;
+
+drop table onek_with_null;
+
 --
 -- Test ORDER BY options
 --
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index 9eec5b3b92d..673ba1b3f0c 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -6,6 +6,8 @@
 -- from auto-analyze happening when we didn't expect it.
 --
 
+set default_statistics_target=10000; --prevent random subset for joinsel
+
 -- check the number of estimated/actual rows in the top node
 create function check_estimated_rows(text) returns table (estimated int, actual int)
 language plpgsql as
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index 3cbc8a83984..840fc4e7cc1 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -39,8 +39,8 @@ my $contrib_defines        = {};
 my @contrib_uselibpq       = ();
 my @contrib_uselibpgport   = ();
 my @contrib_uselibpgcommon = ();
-my $contrib_extralibs      = { 'libpq_pipeline' => ['ws2_32.lib'] };
-my $contrib_extraincludes  = {};
+my $contrib_extralibs      = { 'libpq_pipeline' => ['ws2_32.lib'], 'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'], 'dbcopies_decoding' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'] };
+my $contrib_extraincludes  = { 'mchar' => ['$(ICU46_INCLUDE)'], 'dbcopies_decoding' => ['$(ICU46_INCLUDE)', 'contrib/mchar'] };
 my $contrib_extrasource    = {};
 my @contrib_excludes       = (
 	'bool_plperl',     'commit_ts',
@@ -71,11 +71,14 @@ my $frontend_extralibs = {
 	'pg_amcheck' => ['ws2_32.lib'],
 	'pg_restore' => ['ws2_32.lib'],
 	'pgbench'    => ['ws2_32.lib'],
+	'mchar'      => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'],
+	'dbcopies_decoding' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'],
 	'psql'       => ['ws2_32.lib']
 };
 my $frontend_extraincludes = {
 	'initdb' => ['src/timezone'],
-	'psql'   => ['src/backend']
+	'psql'   => ['src/backend'], 
+	'dbcopies_decoding' => ['$(ICU46_INCLUDE)', 'contrib/mchar']
 };
 my $frontend_extrasource = {
 	'psql' => ['src/bin/psql/psqlscanslash.l'],
@@ -1069,6 +1072,8 @@ sub AddContrib
 
 	# Are there any output data files to build?
 	GenerateContribSqlFiles($n, $mf);
+	GenerateFulleqSql();
+
 	return;
 }
 
@@ -1119,6 +1124,59 @@ sub GenerateContribSqlFiles
 	return;
 }
 
+sub GenerateFulleqSql
+{
+	my @argtypes = ('bool', 'bytea', 'char', 'name', 'int8', 'int2', 'int4',
+					'text', 'oid', 'xid', 'cid', 'oidvector', 'float4',
+					'float8', 'abstime', 'reltime', 'macaddr', 'inet', 'cidr',
+					'varchar', 'date', 'time', 'timestamp', 'timestamptz',
+					'interval', 'timetz');
+
+	#form extension script
+	my $i;
+	open($i, '<', "contrib/fulleq/fulleq.sql.in") ||
+		croak "Could not read contrib/fulleq/fulleq.sql.in";
+	my $template = do { local $/; <$i> };
+	close($i);
+
+	my $o;
+	open($o, '>', "contrib/fulleq/fulleq--2.0.sql") ||
+		croak "Could not write to contrib/fulleq/fulleq--2.0.sql";
+	print $o "\\echo Use \"CREATE EXTENSION fulleq\" to load this file. \\quit\n";
+	foreach my $argtype (@argtypes)
+	{
+		my $newtype = $template;
+		$newtype =~ s/ARGTYPE/$argtype/g;
+		print $o $newtype;
+	}
+	close($o);
+
+	#form migration script
+	$template = undef;
+	open($i, '<', "contrib/fulleq/fulleq-unpackaged.sql.in") ||
+		croak "Could not read contrib/fulleq/fulleq-unpackaged.sql.in";
+	$template = do { local $/; <$i> };
+	close($i);
+
+	open($o, '>', "contrib/fulleq/fulleq--unpackaged--2.0.sql") ||
+		croak "Could not write to contrib/fulleq/fulleq--2.0.sql";
+
+	print $o "\\echo Use \"CREATE EXTENSION fulleq FROM unpackaged\" to load this file. \\quit\n";
+	print $o "DROP OPERATOR CLASS IF EXISTS int2vector_fill_ops USING hash;\n";
+	print $o "DROP OPERATOR FAMILY IF EXISTS int2vector_fill_ops USING hash;\n";
+	print $o "DROP FUNCTION IF EXISTS fullhash_int2vector(int2vector);\n";
+	print $o "DROP OPERATOR IF EXISTS == (int2vector, int2vector);\n";
+	print $o "DROP FUNCTION IF EXISTS isfulleq_int2vector(int2vector, int2vector);\n";
+
+	foreach my $argtype (@argtypes)
+	{
+		my $newtype = $template;
+		$newtype =~ s/ARGTYPE/$argtype/g;
+		print $o $newtype;
+	}
+	close($o);
+}
+
 sub AdjustContribProj
 {
 	my $proj = shift;
openSUSE Build Service is sponsored by