File 0001-1C_FULL.patch of Package postgresql13

diff --git a/contrib/Makefile b/contrib/Makefile
index 1846d415b6f..aff18f93481 100644
--- a/contrib/Makefile
+++ b/contrib/Makefile
@@ -48,7 +48,13 @@ SUBDIRS = \
 		tsm_system_rows \
 		tsm_system_time \
 		unaccent	\
-		vacuumlo
+		vacuumlo	\
+		mchar       	\
+		fulleq          \
+		fasttrun	\
+		online_analyze	\
+		plantuner  \
+        dbcopies_decoding
 
 ifeq ($(with_openssl),yes)
 SUBDIRS += sslinfo
diff --git a/contrib/auto_explain/auto_explain.c b/contrib/auto_explain/auto_explain.c
index dcff9cba48c..a218852a74d 100644
--- a/contrib/auto_explain/auto_explain.c
+++ b/contrib/auto_explain/auto_explain.c
@@ -18,6 +18,7 @@
 #include "commands/explain.h"
 #include "executor/instrument.h"
 #include "jit/jit.h"
+#include "optimizer/planner.h"
 #include "utils/guc.h"
 
 PG_MODULE_MAGIC;
@@ -391,6 +392,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;
@@ -428,8 +430,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..785d400e3c9
--- /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 CASHOID:
+					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/file_fdw/input/file_fdw.source b/contrib/file_fdw/input/file_fdw.source
index d8101d49840..72a8b2733dd 100644
--- a/contrib/file_fdw/input/file_fdw.source
+++ b/contrib/file_fdw/input/file_fdw.source
@@ -53,7 +53,7 @@ CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', delimiter '---');     -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---');         -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---');        -- ERROR
-CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\');       -- ERROR
+CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\\');       -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.');       -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1');       -- ERROR
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter 'a');       -- ERROR
@@ -67,7 +67,7 @@ CREATE FOREIGN TABLE agg_text (
 	a	int2 CHECK (a >= 0),
 	b	float4
 ) SERVER file_server
-OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter '	', null '\N');
+OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter '	', null '\\N');
 GRANT SELECT ON agg_text TO regress_file_fdw_user;
 CREATE FOREIGN TABLE agg_csv (
 	a	int2,
diff --git a/contrib/file_fdw/output/file_fdw.source b/contrib/file_fdw/output/file_fdw.source
index dfcc833d80b..77b77480c53 100644
--- a/contrib/file_fdw/output/file_fdw.source
+++ b/contrib/file_fdw/output/file_fdw.source
@@ -66,8 +66,8 @@ CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', quote '---
 ERROR:  COPY quote must be a single one-byte character
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'csv', escape '---');        -- ERROR
 ERROR:  COPY escape must be a single one-byte character
-CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\');       -- ERROR
-ERROR:  COPY delimiter cannot be "\"
+CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '\\');       -- ERROR
+ERROR:  COPY delimiter must be a single one-byte character
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '.');       -- ERROR
 ERROR:  COPY delimiter cannot be "."
 CREATE FOREIGN TABLE tbl () SERVER file_server OPTIONS (format 'text', delimiter '1');       -- ERROR
@@ -86,7 +86,7 @@ CREATE FOREIGN TABLE agg_text (
 	a	int2 CHECK (a >= 0),
 	b	float4
 ) SERVER file_server
-OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter '	', null '\N');
+OPTIONS (format 'text', filename '@abs_srcdir@/data/agg.data', delimiter '	', null '\\N');
 GRANT SELECT ON agg_text TO regress_file_fdw_user;
 CREATE FOREIGN TABLE agg_csv (
 	a	int2,
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..b6647282d43
--- /dev/null
+++ b/contrib/fulleq/fulleq.c
@@ -0,0 +1,113 @@
+#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/int8.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..ac268a00807
--- /dev/null
+++ b/contrib/online_analyze/online_analyze.c
@@ -0,0 +1,1316 @@
+/*
+ * 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 "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;
+} TableList;
+
+static TableList excludeTables = {0, NULL, NULL};
+static TableList includeTables = {0, NULL, NULL};
+
+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;
+
+	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, &excludeTables);
+}
+
+#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 const char*
+tableListShow(TableList *tbl)
+{
+	char	*val, *ptr;
+	int		i,
+			len;
+
+	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;
+
+
+		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;
+
+#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 >= 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
+
+	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 >= 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 >= 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 87ddaaceb38..6fa68666d49 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1165,13 +1165,15 @@ ANALYZE ft5;
 -- join two tables
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
-                                                                                                       QUERY PLAN                                                                                                       
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                        QUERY PLAN                                                                                        
+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c1, t1.c3
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint
-(4 rows)
+   ->  Foreign Scan
+         Output: t1.c1, t2.c1, t1.c3
+         Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+         Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
+(6 rows)
 
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
  c1  | c1  
@@ -1191,13 +1193,15 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
 -- join three tables
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
-                                                                                                                                   QUERY PLAN                                                                                                                                    
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                     QUERY PLAN                                                                                                                     
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c2, t3.c3, t1.c3
-   Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3)
-   Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 10::bigint
-(4 rows)
+   ->  Foreign Scan
+         Output: t1.c1, t2.c2, t3.c3, t1.c3
+         Relations: ((public.ft1 t1) INNER JOIN (public.ft2 t2)) INNER JOIN (public.ft4 t3)
+         Remote SQL: SELECT r1."C 1", r2.c2, r4.c3, r1.c3 FROM (("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) INNER JOIN "S 1"."T 3" r4 ON (((r1."C 1" = r4.c1)))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
+(6 rows)
 
 SELECT t1.c1, t2.c2, t3.c3 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) JOIN ft4 t3 ON (t3.c1 = t1.c1) ORDER BY t1.c3, t1.c1 OFFSET 10 LIMIT 10;
  c1 | c2 |   c3   
@@ -1804,13 +1808,33 @@ ALTER SERVER loopback OPTIONS (ADD extensions 'postgres_fdw');
 -- tests whole-row reference for row marks
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1;
-                                                                                                                                                                                                                           QUERY PLAN                                                                                                                                                                                                                            
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                                
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1
-(4 rows)
+   ->  LockRows
+         Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+         ->  Foreign Scan
+               Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+               Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+               Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1
+               ->  Result
+                     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+                     ->  Sort
+                           Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                           Sort Key: t1.c3, t1.c1
+                           ->  Hash Join
+                                 Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                                 Hash Cond: (t1.c1 = t2.c1)
+                                 ->  Foreign Scan on public.ft1 t1
+                                       Output: t1.c1, t1.c3, t1.*
+                                       Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
+                                 ->  Hash
+                                       Output: t2.c1, t2.*
+                                       ->  Foreign Scan on public.ft2 t2
+                                             Output: t2.c1, t2.*
+                                             Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+(24 rows)
 
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE OF t1;
  c1  | c1  
@@ -1829,13 +1853,33 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
-                                                                                                                                                                                                                                    QUERY PLAN                                                                                                                                                                                                                                    
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                                                                                                                        QUERY PLAN                                                                                                                                                                                                                        
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR UPDATE OF r1 FOR UPDATE OF r2
-(4 rows)
+   ->  LockRows
+         Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+         ->  Foreign Scan
+               Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+               Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+               Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR UPDATE OF r1 FOR UPDATE OF r2
+               ->  Result
+                     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+                     ->  Sort
+                           Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                           Sort Key: t1.c3, t1.c1
+                           ->  Hash Join
+                                 Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                                 Hash Cond: (t1.c1 = t2.c1)
+                                 ->  Foreign Scan on public.ft1 t1
+                                       Output: t1.c1, t1.c3, t1.*
+                                       Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
+                                 ->  Hash
+                                       Output: t2.c1, t2.*
+                                       ->  Foreign Scan on public.ft2 t2
+                                             Output: t2.c1, t2.*
+                                             Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
+(24 rows)
 
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR UPDATE;
  c1  | c1  
@@ -1855,13 +1899,33 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
 -- join two tables with FOR SHARE clause
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
-                                                                                                                                                                                                                           QUERY PLAN                                                                                                                                                                                                                           
-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                                                                                                               QUERY PLAN                                                                                                                                                                                                               
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1
-(4 rows)
+   ->  LockRows
+         Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+         ->  Foreign Scan
+               Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+               Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+               Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1
+               ->  Result
+                     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+                     ->  Sort
+                           Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                           Sort Key: t1.c3, t1.c1
+                           ->  Hash Join
+                                 Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                                 Hash Cond: (t1.c1 = t2.c1)
+                                 ->  Foreign Scan on public.ft1 t1
+                                       Output: t1.c1, t1.c3, t1.*
+                                       Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
+                                 ->  Hash
+                                       Output: t2.c1, t2.*
+                                       ->  Foreign Scan on public.ft2 t2
+                                             Output: t2.c1, t2.*
+                                             Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+(24 rows)
 
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE OF t1;
  c1  | c1  
@@ -1880,13 +1944,33 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
 
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
-                                                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                                                   
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                                                                                                                       QUERY PLAN                                                                                                                                                                                                                       
+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint FOR SHARE OF r1 FOR SHARE OF r2
-(4 rows)
+   ->  LockRows
+         Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+         ->  Foreign Scan
+               Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+               Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+               Remote SQL: SELECT r1."C 1", r2."C 1", r1.c3, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST FOR SHARE OF r1 FOR SHARE OF r2
+               ->  Result
+                     Output: t1.c1, t2.c1, t1.c3, t1.*, t2.*
+                     ->  Sort
+                           Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                           Sort Key: t1.c3, t1.c1
+                           ->  Hash Join
+                                 Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
+                                 Hash Cond: (t1.c1 = t2.c1)
+                                 ->  Foreign Scan on public.ft1 t1
+                                       Output: t1.c1, t1.c3, t1.*
+                                       Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
+                                 ->  Hash
+                                       Output: t2.c1, t2.*
+                                       ->  Foreign Scan on public.ft2 t2
+                                             Output: t2.c1, t2.*
+                                             Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
+(24 rows)
 
 SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10 FOR SHARE;
  c1  | c1  
@@ -1940,13 +2024,15 @@ WITH t (c1_1, c1_3, c2_1) AS MATERIALIZED (SELECT t1.c1, t1.c3, t2.c1 FROM ft1 t
 -- ctid with whole-row reference
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.ctid, t1, t2, t1.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t1.c1 OFFSET 100 LIMIT 10;
-                                                                                                                                                                                                                  QUERY PLAN                                                                                                                                                                                                                   
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                                                                                                                                                   QUERY PLAN                                                                                                                                                                                                    
+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r1."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint
-(4 rows)
+   ->  Foreign Scan
+         Output: t1.ctid, t1.*, t2.*, t1.c1, t1.c3
+         Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+         Remote SQL: SELECT r1.ctid, CASE WHEN (r1.*)::text IS NOT NULL THEN ROW(r1."C 1", r1.c2, r1.c3, r1.c4, r1.c5, r1.c6, r1.c7, r1.c8) END, CASE WHEN (r2.*)::text IS NOT NULL THEN ROW(r2."C 1", r2.c2, r2.c3, r2.c4, r2.c5, r2.c6, r2.c7, r2.c8) END, r1."C 1", r1.c3 FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (((r1."C 1" = r2."C 1")))) ORDER BY r1.c3 ASC NULLS LAST, r1."C 1" ASC NULLS LAST
+(6 rows)
 
 -- SEMI JOIN, not pushed down
 EXPLAIN (VERBOSE, COSTS OFF)
@@ -2017,13 +2103,15 @@ SELECT t1.c1 FROM ft1 t1 WHERE NOT EXISTS (SELECT 1 FROM ft2 t2 WHERE t1.c1 = t2
 -- CROSS JOIN can be pushed down
 EXPLAIN (VERBOSE, COSTS OFF)
 SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
-                                                                                           QUERY PLAN                                                                                            
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
+                                                                            QUERY PLAN                                                                             
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
    Output: t1.c1, t2.c1
-   Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
-   Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST LIMIT 10::bigint OFFSET 100::bigint
-(4 rows)
+   ->  Foreign Scan
+         Output: t1.c1, t2.c1
+         Relations: (public.ft1 t1) INNER JOIN (public.ft2 t2)
+         Remote SQL: SELECT r1."C 1", r2."C 1" FROM ("S 1"."T 1" r1 INNER JOIN "S 1"."T 1" r2 ON (TRUE)) ORDER BY r1."C 1" ASC NULLS LAST, r2."C 1" ASC NULLS LAST
+(6 rows)
 
 SELECT t1.c1, t2.c1 FROM ft1 t1 CROSS JOIN ft2 t2 ORDER BY t1.c1, t2.c1 OFFSET 100 LIMIT 10;
  c1 | c1  
@@ -2389,32 +2477,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
@@ -2423,7 +2514,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;
@@ -2687,13 +2778,15 @@ select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (ran
 
 explain (verbose, costs off)
 select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
-                                                                                                      QUERY PLAN                                                                                                       
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan
-   Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2
-   Relations: Aggregate on (public.ft1)
-   Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST LIMIT 1::bigint
-(4 rows)
+                                                                                                 QUERY PLAN                                                                                                  
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ Limit
+   Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), (((sum(c1)) * ((random() <= '1'::double precision))::integer)), c2
+   ->  Foreign Scan
+         Output: (count(c6)), (sum(c1)), (avg(c1)), (min(c2)), (max(c1)), (stddev(c2)), ((sum(c1)) * ((random() <= '1'::double precision))::integer), c2
+         Relations: Aggregate on (public.ft1)
+         Remote SQL: SELECT count(c6), sum("C 1"), avg("C 1"), min(c2), max("C 1"), stddev(c2), c2 FROM "S 1"."T 1" WHERE ((c2 < 5)) GROUP BY 7 ORDER BY count(c6) ASC NULLS LAST, sum("C 1") ASC NULLS LAST
+(6 rows)
 
 select count(c6), sum(c1), avg(c1), min(c2), max(c1), stddev(c2), sum(c1) * (random() <= 1)::int as sum2 from ft1 where c2 < 5 group by c2 order by 1, 2 limit 1;
  count |  sum  |         avg          | min | max | stddev | sum2  
@@ -2832,16 +2925,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/contrib/test_decoding/expected/time.out b/contrib/test_decoding/expected/time.out
index 3b06849d697..166ef85c519 100644
--- a/contrib/test_decoding/expected/time.out
+++ b/contrib/test_decoding/expected/time.out
@@ -1,3 +1,4 @@
+SET standard_conforming_strings=on;
 SET synchronous_commit = on;
 CREATE TABLE test_time(data text);
 -- remember the current time
diff --git a/contrib/test_decoding/expected/toast.out b/contrib/test_decoding/expected/toast.out
index a757e7dc8d5..8fe72e1dbd2 100644
--- a/contrib/test_decoding/expected/toast.out
+++ b/contrib/test_decoding/expected/toast.out
@@ -1,4 +1,5 @@
 -- predictability
+SET standard_conforming_strings=on;
 SET synchronous_commit = on;
 DROP TABLE IF EXISTS xpto;
 NOTICE:  table "xpto" does not exist, skipping
diff --git a/contrib/test_decoding/sql/time.sql b/contrib/test_decoding/sql/time.sql
index a47c9731f34..1f04eb59893 100644
--- a/contrib/test_decoding/sql/time.sql
+++ b/contrib/test_decoding/sql/time.sql
@@ -1,3 +1,4 @@
+SET standard_conforming_strings=on;
 SET synchronous_commit = on;
 
 CREATE TABLE test_time(data text);
diff --git a/contrib/test_decoding/sql/toast.sql b/contrib/test_decoding/sql/toast.sql
index d1c560a174d..44b39e692c0 100644
--- a/contrib/test_decoding/sql/toast.sql
+++ b/contrib/test_decoding/sql/toast.sql
@@ -1,4 +1,5 @@
 -- predictability
+SET standard_conforming_strings=on;
 SET synchronous_commit = on;
 
 DROP TABLE IF EXISTS xpto;
diff --git a/src/backend/access/heap/hio.c b/src/backend/access/heap/hio.c
index 9e2d09122bb..9a26467d893 100644
--- a/src/backend/access/heap/hio.c
+++ b/src/backend/access/heap/hio.c
@@ -563,7 +563,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
@@ -575,13 +576,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, len + saveFreeSpace);
 			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 6a14039ea7b..567e8803827 100644
--- a/src/backend/access/heap/visibilitymap.c
+++ b/src/backend/access/heap/visibilitymap.c
@@ -667,6 +667,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);
 
 	/* Update local cache with the up-to-date size */
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index ef7be47212a..27ffdddcdd7 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -318,6 +318,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 13a14425177..95d2325285e 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -58,6 +58,7 @@
 #include "commands/progress.h"
 #include "commands/tablecmds.h"
 #include "commands/trigger.h"
+#include "commands/typecmds.h"
 #include "executor/executor.h"
 #include "miscadmin.h"
 #include "nodes/makefuncs.h"
@@ -105,7 +106,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, int numatts,
@@ -133,7 +134,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
@@ -643,6 +644,110 @@ 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,			/* 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 */
+				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
@@ -726,6 +831,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;
@@ -942,6 +1048,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
@@ -1395,7 +1506,7 @@ index_concurrently_create_copy(Relation heapRelation, Oid oldIndexId, const char
 							  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? */
@@ -1536,6 +1647,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);
 
@@ -1724,8 +1861,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);
@@ -2133,6 +2271,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;
@@ -2209,24 +2348,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
 		 */
@@ -2323,6 +2444,16 @@ index_drop(Oid indexId, bool concurrent, bool concurrent_lock_mode)
 	if (userIndexRelation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
 		RelationDropStorage(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
@@ -2350,10 +2481,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);
 
 	/*
@@ -2882,6 +3013,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)
@@ -3656,6 +3795,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 01b797e32dd..31030949bf4 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -40,8 +40,11 @@
 #include "commands/vacuum.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"
@@ -65,6 +68,7 @@
 #include "utils/sortsupport.h"
 #include "utils/syscache.h"
 #include "utils/timestamp.h"
+#include "utils/typcache.h"
 
 
 /* Per-index data for ANALYZE */
@@ -74,6 +78,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;
 
 
@@ -471,6 +476,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;
+			}
 		}
 	}
 
@@ -840,28 +860,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++;
 				}
 			}
 		}
@@ -2578,6 +2611,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 bc05c96b4ce..8e97d84b93c 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -378,21 +378,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)
 		{
@@ -402,7 +396,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/commands/extension.c b/src/backend/commands/extension.c
index ccffec91132..55006d6399e 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1391,6 +1391,7 @@ static ObjectAddress
 CreateExtensionInternal(char *extensionName,
 						char *schemaName,
 						const char *versionName,
+						const char *oldVersionName,
 						bool cascade,
 						List *parents,
 						bool is_create)
@@ -1400,8 +1401,6 @@ CreateExtensionInternal(char *extensionName,
 	Oid			extowner = GetUserId();
 	ExtensionControlFile *pcontrol;
 	ExtensionControlFile *control;
-	char	   *filename;
-	struct stat fst;
 	List	   *updateVersions;
 	List	   *requiredExtensions;
 	List	   *requiredSchemas;
@@ -1436,6 +1435,56 @@ CreateExtensionInternal(char *extensionName,
 	 * does what is needed, we try to find a sequence of update scripts that
 	 * will get us there.
 	 */
+	if (oldVersionName)
+	{
+		/*
+		 * "FROM old_version" was specified, indicating that we're trying to
+		 * update from some unpackaged version of the extension.  Locate a
+		 * series of update scripts that will do it.
+		 */
+		check_valid_version_name(oldVersionName);
+
+		if (strcmp(oldVersionName, versionName) == 0)
+			ereport(ERROR,
+					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
+					 errmsg("FROM version must be different from installation target version \"%s\"",
+							versionName)));
+
+		updateVersions = identify_update_path(pcontrol,
+											  oldVersionName,
+											  versionName);
+
+		if (list_length(updateVersions) == 1)
+		{
+			/*
+			 * Simple case where there's just one update script to run. We
+			 * will not need any follow-on update steps.
+			 */
+			Assert(strcmp((char *) linitial(updateVersions), versionName) == 0);
+			updateVersions = NIL;
+		}
+		else
+		{
+			/*
+			 * Multi-step sequence.  We treat this as installing the version
+			 * that is the target of the first script, followed by successive
+			 * updates to the later versions.
+			 */
+			versionName = (char *) linitial(updateVersions);
+			updateVersions = list_delete_first(updateVersions);
+		}
+	}
+	else
+	{
+		/*
+		 * No FROM, so we're installing from scratch.  If there is an install
+		 * script for the desired version, we only need to run that one.
+		 */
+		char	   *filename;
+		struct stat fst;
+
+		oldVersionName = NULL;
+
 	filename = get_extension_script_filename(pcontrol, NULL, versionName);
 	if (stat(filename, &fst) == 0)
 	{
@@ -1469,6 +1518,7 @@ CreateExtensionInternal(char *extensionName,
 		/* Otherwise, install best starting point and then upgrade */
 		versionName = evi_start->name;
 	}
+	}
 
 	/*
 	 * Fetch control parameters for installation target version
@@ -1608,7 +1658,7 @@ CreateExtensionInternal(char *extensionName,
 	 * Execute the installation script file
 	 */
 	execute_extension_script(extensionOid, control,
-							 NULL, versionName,
+							 oldVersionName, versionName,
 							 requiredSchemas,
 							 schemaName, schemaOid);
 
@@ -1675,6 +1725,7 @@ get_required_extension(char *reqExtensionName,
 			addr = CreateExtensionInternal(reqExtensionName,
 										   origSchemaName,
 										   NULL,
+										   NULL,
 										   cascade,
 										   cascade_parents,
 										   is_create);
@@ -1702,9 +1753,11 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
 {
 	DefElem    *d_schema = NULL;
 	DefElem    *d_new_version = NULL;
+	DefElem    *d_old_version = NULL;
 	DefElem    *d_cascade = NULL;
 	char	   *schemaName = NULL;
 	char	   *versionName = NULL;
+	char	   *oldVersionName = NULL;
 	bool		cascade = false;
 	ListCell   *lc;
 
@@ -1768,6 +1821,16 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
 			d_new_version = defel;
 			versionName = defGetString(d_new_version);
 		}
+		else if (strcmp(defel->defname, "old_version") == 0)
+		{
+			if (d_old_version)
+				ereport(ERROR,
+						(errcode(ERRCODE_SYNTAX_ERROR),
+						 errmsg("conflicting or redundant options"),
+						 parser_errposition(pstate, defel->location)));
+			d_old_version = defel;
+			oldVersionName = defGetString(d_old_version);
+		}
 		else if (strcmp(defel->defname, "cascade") == 0)
 		{
 			if (d_cascade)
@@ -1786,6 +1849,7 @@ CreateExtension(ParseState *pstate, CreateExtensionStmt *stmt)
 	return CreateExtensionInternal(stmt->extname,
 								   schemaName,
 								   versionName,
+								   oldVersionName,
 								   cascade,
 								   NIL,
 								   true);
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 458a0c388ac..2b269bdf14b 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_ALTERNATIVE_SUBPLAN)
+		{
+			scratch.d.qualexpr.guaranteed_empty =
+				lastStep->d.alternative_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);
 	}
@@ -1048,8 +1068,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
@@ -1068,10 +1095,17 @@ 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_ALTERNATIVE_SUBPLAN)
+						lastStep->d.alternative_subplan.guaranteed_empty =
+							scratch.d.boolexpr.guaranteed_empty;
+
 					/* Perform the appropriate step type */
 					switch (boolexpr->boolop)
 					{
@@ -1173,6 +1207,7 @@ ExecInitExprRec(Expr *node, ExprState *state,
 
 				scratch.opcode = EEOP_ALTERNATIVE_SUBPLAN;
 				scratch.d.alternative_subplan.asstate = asstate;
+				scratch.d.alternative_subplan.guaranteed_empty = NULL;
 
 				ExprEvalPushStep(state, &scratch);
 				break;
@@ -3588,6 +3623,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 8c1227ee11e..6936a198dcd 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -760,6 +760,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
 				EEO_JUMP(op->d.boolexpr.jumpdone);
 			}
 
+			/* reset */
+			*op->d.boolexpr.guaranteed_empty = false;
+
 			EEO_NEXT();
 		}
 
@@ -768,6 +771,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))
 			{
@@ -783,10 +788,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();
@@ -805,6 +815,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
@@ -816,6 +827,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;
@@ -832,6 +847,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 */
@@ -853,6 +872,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 */
 			}
 
@@ -883,6 +906,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);
 			}
 
@@ -3934,7 +3960,16 @@ ExecEvalAlternativeSubPlan(ExprState *state, ExprEvalStep *op, ExprContext *econ
 	/* could potentially be nested, so make sure there's enough stack */
 	check_stack_depth();
 
-	*op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
+	if (asstate->guaranteed_empty == false)
+		*op->resvalue = ExecAlternativeSubPlan(asstate, econtext, op->resnull);
+	else
+	{
+		*op->resvalue = false;
+		*op->resnull = false;
+	}
+
+	if (op->d.alternative_subplan.guaranteed_empty)
+		*op->d.alternative_subplan.guaranteed_empty = asstate->guaranteed_empty;
 }
 
 /*
diff --git a/src/backend/executor/execScan.c b/src/backend/executor/execScan.c
index 642805d90cd..2d2e571189d 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -187,6 +187,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
@@ -245,7 +246,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 dd077f43230..cae566402dd 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 b07c2996d4c..be69381238d 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 88604884cd8..db96c402e57 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -105,6 +105,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 +125,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
@@ -1379,7 +1385,14 @@ ExecInitAlternativeSubPlan(AlternativeSubPlan *asplan, PlanState *parent)
 	cost1 = subplan1->startup_cost + num_calls * subplan1->per_call_cost;
 	cost2 = subplan2->startup_cost + num_calls * subplan2->per_call_cost;
 
-	if (cost1 < cost2)
+	/*
+	 * Prefer hashed subplan if difference isn't very big. Hashed subplan could
+	 * set guaranteed_empty flags so multiple execution will be prevented
+	 */
+	if (subplan1->useHashTable != subplan2->useHashTable &&
+		Abs(cost1 - cost2) < 0.1 * Max(cost1, cost2))
+		asstate->active = (subplan1->useHashTable) ? 0 : 1;
+	else if (cost1 < cost2)
 		asstate->active = 0;
 	else
 		asstate->active = 1;
@@ -1403,6 +1416,12 @@ ExecAlternativeSubPlan(AlternativeSubPlanState *node,
 	/* Just pass control to the active subplan */
 	SubPlanState *activesp = list_nth_node(SubPlanState,
 										   node->subplans, node->active);
+	Datum	res = ExecSubPlan(activesp, econtext, isNull); 
+
+	if (activesp->subplan->useHashTable && activesp->planstate->guaranteed_empty)
+		node->guaranteed_empty = true;
+	else
+		node->guaranteed_empty = false;
 
-	return ExecSubPlan(activesp, econtext, isNull);
+	return res;
 }
diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c
index 733306b1ed9..07c04427fa5 100644
--- a/src/backend/libpq/pqcomm.c
+++ b/src/backend/libpq/pqcomm.c
@@ -81,6 +81,9 @@
 #ifdef HAVE_NETINET_TCP_H
 #include <netinet/tcp.h>
 #endif
+#ifdef HAVE_POLL_H
+#include <poll.h>
+#endif
 #include <utime.h>
 #ifdef _MSC_VER					/* mstcpip.h is missing on mingw */
 #include <mstcpip.h>
@@ -2000,3 +2003,30 @@ pq_settcpusertimeout(int timeout, Port *port)
 
 	return STATUS_OK;
 }
+
+void
+pq_check_client_connection(void)
+{
+	CheckClientConnectionPending = false;
+#ifndef WIN32
+	if (IsUnderPostmaster &&
+		MyProcPort != NULL && !PqCommReadingMsg && !PqCommBusy)
+	{
+		int r;
+		struct	pollfd fd;
+
+		fd.fd = MyProcPort->sock;
+		fd.events = 0;
+		fd.revents = 0;
+
+		r = poll(&fd, 1, 0);
+
+		if ((r < 0 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR)
+			|| (r>=0 && fd.revents & (POLLHUP | POLLERR)) != 0)
+		{
+			ClientConnectionLost = true;
+			InterruptPending = true;
+		}
+	}
+#endif
+}
diff --git a/src/backend/nodes/nodeFuncs.c b/src/backend/nodes/nodeFuncs.c
index 585b0046f4f..c860aa98525 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2572,21 +2572,40 @@ 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 CHECKFLATCOPY(newnode, node, nodetype)	\
-	( AssertMacro(IsA((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 CHECKFLATCOPY(newnode, node, nodetype, flags)	\
+	do { \
+		AssertMacro(IsA((node), nodetype)); \
+		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) )
@@ -2609,7 +2628,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;
@@ -2618,7 +2637,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;
 			}
@@ -2638,7 +2657,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;
 			}
@@ -2647,7 +2666,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 *);
@@ -2663,7 +2682,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 *);
 
 				/*
@@ -2686,7 +2705,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;
@@ -2697,7 +2716,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,
@@ -2715,7 +2734,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;
 			}
@@ -2725,7 +2744,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;
 			}
@@ -2735,7 +2754,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;
 			}
@@ -2745,7 +2764,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;
 			}
@@ -2755,7 +2774,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;
 			}
@@ -2765,7 +2784,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;
 			}
@@ -2775,7 +2794,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;
 			}
@@ -2785,7 +2804,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 *);
 
 				/*
@@ -2801,7 +2820,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) */
@@ -2815,7 +2834,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;
 			}
@@ -2825,7 +2844,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;
 			}
@@ -2835,7 +2854,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);
@@ -2847,7 +2866,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;
 			}
@@ -2857,7 +2876,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;
 			}
@@ -2867,7 +2886,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;
@@ -2878,7 +2897,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;
 			}
@@ -2888,7 +2907,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;
 			}
@@ -2898,7 +2917,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 *);
@@ -2910,7 +2929,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;
@@ -2921,7 +2940,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;
 			}
@@ -2931,7 +2950,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;
@@ -2942,7 +2961,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;
@@ -2953,7 +2972,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;
 			}
@@ -2963,7 +2982,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;
 			}
@@ -2973,7 +2992,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 *);
@@ -2985,7 +3004,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;
 			}
@@ -2995,7 +3014,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;
 			}
@@ -3005,7 +3024,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;
 			}
@@ -3015,7 +3034,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;
 			}
@@ -3028,7 +3047,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 *);
@@ -3041,7 +3060,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
@@ -3076,7 +3095,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;
@@ -3087,7 +3106,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 *);
@@ -3102,7 +3121,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;
@@ -3116,7 +3135,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 *);
@@ -3129,7 +3148,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 */
@@ -3141,7 +3160,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;
@@ -3152,7 +3171,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;
@@ -3163,7 +3182,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;
 			}
@@ -3173,7 +3192,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;
@@ -3184,7 +3203,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;
@@ -3195,7 +3214,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;
@@ -3206,7 +3225,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;
@@ -3217,7 +3236,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 *);
@@ -3267,7 +3286,7 @@ query_tree_mutator(Query *query,
 	{
 		Query	   *newquery;
 
-		FLATCOPY(newquery, query, Query);
+		FLATCOPY(newquery, query, Query, flags);
 		query = newquery;
 	}
 
@@ -3309,7 +3328,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 *);
 
@@ -3358,7 +3377,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:
@@ -3369,7 +3388,7 @@ range_table_mutator(List *rtable,
 			case RTE_SUBQUERY:
 				if (!(flags & QTW_IGNORE_RT_SUBQUERIES))
 				{
-					CHECKFLATCOPY(newrte->subquery, rte->subquery, Query);
+					CHECKFLATCOPY(newrte->subquery, rte->subquery, Query, flags);
 					MUTATE(newrte->subquery, newrte->subquery, Query *);
 				}
 				else
diff --git a/src/backend/nodes/outfuncs.c b/src/backend/nodes/outfuncs.c
index 21ececf0c2f..9fb3490d9fa 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1875,6 +1875,7 @@ _outAppendPath(StringInfo str, const AppendPath *node)
 	WRITE_NODE_FIELD(partitioned_rels);
 	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 00e37620ff3..6f7c9fa0ed0 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -784,6 +784,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);
 }
@@ -1525,7 +1528,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,
-												  partitioned_rels, -1));
+												  partitioned_rels, -1, false));
 
 	/*
 	 * Consider an append of unordered, unparameterized partial paths.  Make
@@ -1568,7 +1571,8 @@ 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,
-										partitioned_rels, -1);
+										partitioned_rels, -1,
+										false);
 
 		/*
 		 * Make sure any subsequent partial paths use the same row count
@@ -1617,7 +1621,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,
-										partitioned_rels, partial_rows);
+										partitioned_rels, partial_rows, false);
 		add_partial_path(rel, (Path *) appendpath);
 	}
 
@@ -1679,7 +1683,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,
-										partitioned_rels, -1));
+										partitioned_rels, -1, false));
 	}
 
 	/*
@@ -1709,7 +1713,8 @@ 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,
-											partitioned_rels, partial_rows);
+											partitioned_rels, partial_rows,
+											false);
 			add_partial_path(rel, (Path *) appendpath);
 		}
 	}
@@ -1913,7 +1918,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 													  0,
 													  false,
 													  partitioned_rels,
-													  -1));
+													  -1, false));
 			if (startup_neq_total)
 				add_path(rel, (Path *) create_append_path(root,
 														  rel,
@@ -1924,7 +1929,7 @@ generate_orderedappend_paths(PlannerInfo *root, RelOptInfo *rel,
 														  0,
 														  false,
 														  partitioned_rels,
-														  -1));
+														  -1, false));
 		}
 		else
 		{
@@ -2135,7 +2140,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, NIL, -1));
+											  0, false, NIL, -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..37b90e48701
--- /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(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(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, NIL, -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, NIL, -1.0,
+											   true);
+			add_path(rel, (Path *) appendidxpath);
+		}
+	} else {
+		appendidxpath = create_append_path(root, rel, bestpath->bitmapquals,
+										   NIL, NIL, NULL,
+										   0, false, NIL, -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(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((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 79d8f761ab2..bd092715280 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -12,8 +12,14 @@
  *
  *-------------------------------------------------------------------------
  */
+#include <math.h>
 #include "postgres.h"
 
+#include "access/genam.h"
+#include "access/htup_details.h"
+#include "catalog/pg_collation.h"
+#include "commands/vacuum.h"
+#include "funcapi.h"
 #include "nodes/makefuncs.h"
 #include "nodes/nodeFuncs.h"
 #include "optimizer/clauses.h"
@@ -26,6 +32,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)
+
 /* source-code-compatibility hacks for pull_varnos() API change */
 #define NumRelids(a,b) NumRelids_new(a,b)
 
@@ -47,6 +63,1050 @@ static void addRangeClause(RangeQueryClause **rqlist, Node *clause,
 						   bool varonleft, bool isLTsel, Selectivity s2);
 static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root,
 											   List *clauses);
+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);
+
+static void
+clauselist_selectivity_simple_ext(PlannerInfo *root,
+							  List *clauses,
+							  int varRelid,
+							  JoinType jointype,
+							  SpecialJoinInfo *sjinfo,
+							  Bitmapset *estimatedclauses,
+							  Selectivity s[5 /* per CorrelationKind */]);
+/*
+ *  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 = random() % 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)
+			/*
+			 * 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
@@ -68,6 +1128,54 @@ static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root,
  * for individual columns.  This is done by simply passing the clauses to
  * clauselist_selectivity_simple.
  */
+
+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,
@@ -75,9 +1183,11 @@ clauselist_selectivity(PlannerInfo *root,
 					   JoinType jointype,
 					   SpecialJoinInfo *sjinfo)
 {
-	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;
+	CorrelationKind	ck;
 
 	/*
 	 * Determine if these clauses reference a single relation.  If so, and if
@@ -93,18 +1203,34 @@ clauselist_selectivity(PlannerInfo *root,
 		 * clauses that we've estimated using extended statistics, and that
 		 * should be ignored.
 		 */
-		s1 *= statext_clauselist_selectivity(root, clauses, varRelid,
-											 jointype, sjinfo, rel,
-											 &estimatedclauses);
+		s2 = statext_clauselist_selectivity(root, clauses, varRelid,
+											jointype, sjinfo, rel,
+											&estimatedclauses);
+		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);
 	}
 
 	/*
 	 * Apply normal selectivity estimates for the remaining clauses, passing
 	 * 'estimatedclauses' so that it skips already estimated ones.
 	 */
-	return s1 * clauselist_selectivity_simple(root, clauses, varRelid,
-											  jointype, sjinfo,
-											  estimatedclauses);
+	clauselist_selectivity_simple_ext(root, clauses, varRelid,
+									  jointype, sjinfo,
+									  estimatedclauses, s);
+
+	return finalizeSelectivityRes(s);
 }
 
 /*
@@ -157,24 +1283,32 @@ clauselist_selectivity_simple(PlannerInfo *root,
 							  SpecialJoinInfo *sjinfo,
 							  Bitmapset *estimatedclauses)
 {
-	Selectivity s1 = 1.0;
+	Selectivity s[5 /* per CorrelationKind */] = {1.0, 1.0, 1.0, 1.0, 1.0};
+
+	clauselist_selectivity_simple_ext(root, clauses, varRelid,
+									  jointype, sjinfo,
+									  estimatedclauses, s);
+
+	return finalizeSelectivityRes(s);
+}
+
+static void
+clauselist_selectivity_simple_ext(PlannerInfo *root,
+							  List *clauses,
+							  int varRelid,
+							  JoinType jointype,
+							  SpecialJoinInfo *sjinfo,
+							  Bitmapset *estimatedclauses,
+							  Selectivity s[5 /* per CorrelationKind */])
+{
 	RangeQueryClause *rqlist = NULL;
+	CorrelationKind		ck;
 	ListCell   *l;
 	int			listidx;
 
-	/*
-	 * If there's exactly one clause (and it was not estimated yet), just go
-	 * directly to clause_selectivity(). None of what we might do below is
-	 * relevant.
-	 */
-	if ((list_length(clauses) == 1) &&
-		bms_num_members(estimatedclauses) == 0)
-		return clause_selectivity(root, (Node *) linitial(clauses),
-								  varRelid, jointype, sjinfo);
-
 	/*
 	 * Anything that doesn't look like a potential rangequery clause gets
-	 * multiplied into s1 and forgotten. Anything that does gets inserted into
+	 * multiplied into s and forgotten. Anything that does gets inserted into
 	 * an rqlist entry.
 	 */
 	listidx = -1;
@@ -207,7 +1341,7 @@ clauselist_selectivity_simple(PlannerInfo *root,
 			rinfo = (RestrictInfo *) clause;
 			if (rinfo->pseudoconstant)
 			{
-				s1 = s1 * s2;
+				appendSelectivityRes(s, s2, CKRestrict);
 				continue;
 			}
 			clause = (Node *) rinfo->clause;
@@ -221,12 +1355,17 @@ clauselist_selectivity_simple(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) &&
@@ -265,7 +1404,7 @@ clauselist_selectivity_simple(PlannerInfo *root,
 						break;
 					default:
 						/* Just merge the selectivity in generically */
-						s1 = s1 * s2;
+						appendSelectivityRes(s, s2, ck);
 						break;
 				}
 				continue;		/* drop to loop bottom */
@@ -273,7 +1412,7 @@ clauselist_selectivity_simple(PlannerInfo *root,
 		}
 
 		/* Not the right form, so treat it generically. */
-		s1 = s1 * s2;
+		appendSelectivityRes(s, s2, ck);
 	}
 
 	/*
@@ -335,23 +1474,19 @@ clauselist_selectivity_simple(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;
 		pfree(rqlist);
 		rqlist = rqnext;
 	}
-
-	return s1;
 }
 
 /*
@@ -560,6 +1695,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(left_arg);
+	right_varnos = pull_varnos(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 4edc859cb57..9dfca34c156 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -148,6 +148,7 @@ typedef struct
 {
 	PlannerInfo *root;
 	QualCost	total;
+	bool		calccoalesce;
 } cost_qual_eval_context;
 
 static List *extract_nonindex_conditions(List *qual_clauses, List *indexclauses);
@@ -182,7 +183,9 @@ static void set_rel_width(PlannerInfo *root, RelOptInfo *rel);
 static double relation_byte_size(double tuples, int width);
 static double page_size(double tuples, int width);
 static double get_parallel_divisor(Path *path);
-
+static Cost compute_cpu_sort_cost(PlannerInfo *root, List *pathkeys, int nPresortedKeys,
+					  Cost comparison_cost, double tuples, double output_tuples,
+					  bool heapSort);
 
 /*
  * clamp_row_est
@@ -727,7 +730,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;
 
@@ -1013,7 +1016,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. */
@@ -1686,7 +1689,8 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
  * 'limit_tuples' is the bound on the number of output tuples; -1 if no bound
  */
 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)
@@ -1703,9 +1707,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)
 	{
@@ -1734,7 +1735,9 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost,
 		 *
 		 * Assume about N log2 N comparisons
 		 */
-		*startup_cost = comparison_cost * tuples * LOG2(tuples);
+		*startup_cost = compute_cpu_sort_cost(root, pathkeys, 0,
+											  comparison_cost, tuples,
+											  tuples, false);
 
 		/* Disk costs */
 
@@ -1756,12 +1759,16 @@ cost_tuplesort(Cost *startup_cost, Cost *run_cost,
 		 * 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);
+		*startup_cost = compute_cpu_sort_cost(root, pathkeys, 0,
+											  comparison_cost, tuples,
+											  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);
 	}
 
 	/*
@@ -1879,7 +1886,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);
 
@@ -1914,6 +1921,287 @@ cost_incremental_sort(Path *path,
 	path->total_cost = startup_cost + run_cost;
 }
 
+/*
+ * 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
+ */
+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 valyes based on it's width.
+ * The idea behind - long values have more expensive comparison. Return value is
+ * in cpu_operator_cost unit.
+ */
+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 corresonding 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, use estimation by type */
+	if (width < 0.0)
+	{
+		Node	*node = (Node*) expr;
+
+		width = get_typavgwidth(exprType(node), exprTypmod(node));
+	}
+
+	/*
+	 * Any value in pgsql is passed by Datum type, so any operation with value
+	 * could not be cheaper than operation with Datum type
+	 */
+	if (width <= sizeof(Datum))
+		return 1.0;
+
+	/*
+	 * Seems, cost of comparision is not directly proportional to args width,
+	 * because comparing args could be differ width (we known only average over
+	 * column) and difference often could be defined only by looking on first
+	 * bytes. So, use log16(width) as estimation.
+	 */
+	return 1.0 + 0.125 * LOG2(width / sizeof(Datum));
+}
+
+/*
+ * compute_cpu_sort_cost
+ *		compute CPU cost of sort (i.e. in-memory)
+ *
+ * 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.
+ *
+ * Estimation algorithm is based on ideas from course Algorithms,
+ * Robert Sedgewick, Kevin Wayne, https://algs4.cs.princeton.edu/home/ and paper
+ * "Quicksort Is Optimal For Many Equal Keys", Sebastian Wild,
+ * arXiv:1608.04906v4 [cs.DS] 1 Nov 2017.
+ *
+ * In term of that papers, let N - number of tuples, Xi - number of tuples with
+ * key Ki, then estimation is:
+ * log(N! / (X1! * X2! * ..))  ~  sum(Xi * log(N/Xi))
+ * In our case all Xi are the same because noew we don't have an estimation of
+ * group sizes, we have only estimation of number of groups. In this case,
+ * formula becomes: N * log(NumberOfGroups). Next, to support correct estimation
+ * of multicolumn sort we need separately compute each column, so, let k is a
+ * column number, Gk - number of groups  defined by k columns:
+ * N * sum( Fk * log(Gk) )
+ * Fk is a function costs (includeing width) for k columns.
+ */
+
+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;
+	Cost		funcCost = 0.;
+	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:
+	 * - per column comparison function cost
+	 * - we try to compute needed number of comparison per column
+	 */
+
+	foreach(lc, pathkeys)
+	{
+		PathKey				*pathkey = (PathKey*)lfirst(lc);
+		EquivalenceMember	*em;
+		double				 nGroups,
+							 correctedNGroups;
+
+		/*
+		 * We believe than equivalence members aren't very  different, so, to
+		 * estimate cost we take just first member
+		 */
+		em = (EquivalenceMember *) linitial(pathkey->pk_eclass->ec_members);
+
+		if (em->em_datatype != InvalidOid)
+		{
+			/* do not lookup funcCost if data type is the same as previous */
+			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);
+				funcCost = cost.per_tuple / cpu_operator_cost;
+				prev_datatype = em->em_datatype;
+			}
+		}
+		else
+			funcCost = 1.0; /* fallback */
+
+		/* Try to take into account actual width fee */
+		funcCost *= get_width_cost_multiplier(root, em->em_expr);
+
+		totalFuncCost += funcCost;
+
+		/* remeber if we have a fake var in pathkeys */
+		has_fake_var |= is_fake_var(em->em_expr);
+		pathkeyExprs = lappend(pathkeyExprs, em->em_expr);
+
+		/*
+		 * Prevent call estimate_num_groups() with fake Var. Note,
+		 * pathkeyExprs contains only previous columns
+		 */
+		if (has_fake_var == false)
+			/*
+			 * Recursively compute number of group in group from previous step
+			 */
+			nGroups = estimate_num_groups_incremental(root, pathkeyExprs,
+													  tuplesPerPrevGroup, NULL,
+													  &cache_varinfos,
+													  list_length(pathkeyExprs) - 1);
+		else if (tuples > 4.0)
+			/*
+			 * Use geometric mean as estimation if there is no any stats.
+			 * Don't use DEFAULT_NUM_DISTINCT because it used for only one
+			 * column while here we try to estimate number of groups over
+			 * set of columns.
+			 */
+			nGroups = ceil(2.0 + sqrt(tuples) *
+				list_length(pathkeyExprs) / list_length(pathkeys));
+		else
+			nGroups = tuples;
+
+		/*
+		 * Presorted keys aren't participated in comparison but still checked
+		 * by qsort comparator.
+		 */
+		if (i >= nPresortedKeys)
+		{
+			if (heapSort)
+			{
+				if (tuplesPerPrevGroup < output_tuples)
+					/* comparing only inside output_tuples */
+					correctedNGroups =
+						ceil(2.0 * output_tuples / (tuplesPerPrevGroup / nGroups));
+				else
+					/* two groups - in output and out */
+					correctedNGroups = 2.0;
+			}
+			else
+				correctedNGroups = nGroups;
+
+			if (correctedNGroups <= 1.0)
+				correctedNGroups = 2.0;
+			else
+				correctedNGroups = ceil(correctedNGroups);
+			per_tuple_cost += totalFuncCost * LOG2(correctedNGroups);
+		}
+
+		i++;
+
+		/*
+		 * Real-world distribution isn't uniform but now we don't have a way to
+		 * determine that, so, add multiplier to get closer to worst case.
+		 */
+		tuplesPerPrevGroup = ceil(1.5 * tuplesPerPrevGroup / nGroups);
+
+		/*
+		 * We could skip all followed columns for cost estimation, because we
+		 * believe that tuples are unique by set ot previous 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). It has meaning with low number of tuples,
+	 * approximately less that 1e4. Of course, it could be inmplemented as
+	 * additional multiplier under logarithm, but use more complicated formula
+	 * which takes into account number of unique tuples and it isn't clear how
+	 * to combine multiplier with groups. Estimate it as 10 in cpu_operator_cost
+	 * unit.
+	 */
+	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_sort
  *	  Determines and returns the cost of sorting a relation, including
@@ -1936,7 +2224,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);
@@ -2034,7 +2322,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;
 
@@ -2102,7 +2390,7 @@ cost_append(AppendPath *apath)
 					 * any child.
 					 */
 					cost_sort(&sort_path,
-							  NULL, /* doesn't currently need root */
+							  root,
 							  pathkeys,
 							  subpath->total_cost,
 							  subpath->rows,
@@ -3126,8 +3414,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;
@@ -4063,6 +4352,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 ... */
 
@@ -4088,6 +4378,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);
 
@@ -4117,6 +4423,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
@@ -4328,6 +4635,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,
@@ -4814,6 +5126,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
@@ -4852,9 +5165,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);
 		}
 
@@ -4906,6 +5221,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 010f1751440..9abe87d0037 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -681,7 +681,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 sortref if it wasn't set yet, it's possible if ec was
+				 * constructed from WHERE clause, ie it doesn't have 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 1d899f80ae9..0ed379cff7e 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -35,7 +35,6 @@
 #include "utils/lsyscache.h"
 #include "utils/selfuncs.h"
 
-
 /* source-code-compatibility hacks for pull_varnos() API change */
 #define pull_varnos(a,b) pull_varnos_new(a,b)
 #undef make_simple_restrictinfo
@@ -117,8 +116,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);
@@ -1257,7 +1254,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)
 {
@@ -3295,7 +3292,6 @@ match_clause_to_ordering_op(IndexOptInfo *index,
 	return clause;
 }
 
-
 /****************************************************************************
  *				----  ROUTINES TO DO PARTIAL INDEX PREDICATE TESTS	----
  ****************************************************************************/
@@ -3494,7 +3490,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
@@ -3515,7 +3512,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;
 
@@ -3571,6 +3569,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
@@ -3622,6 +3621,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;
 				}
 			}
@@ -3664,7 +3664,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 1e444e8addf..8bea6d7aa03 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -178,7 +178,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,
@@ -187,7 +188,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 77585609ae6..2c565763c02 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, NIL, -1));
+											  0, false, NIL, -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 4ce77ca5eed..4cea8b8e998 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -17,17 +17,20 @@
  */
 #include "postgres.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/clauses.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"
 
 static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
 static bool matches_boolean_partition_clause(RestrictInfo *rinfo,
@@ -388,6 +391,296 @@ pathkeys_count_contained_in(List *keys1, List *keys2, int *n_common)
 	return (key1 == NULL);
 }
 
+/*
+ * Reorder GROUP BY pathkeys and clauses to match order of pathkeys. Function
+ * returns new lists,  original GROUP BY lists stay untouched.
+ */
+int
+group_keys_reorder_by_pathkeys(List *pathkeys, List **group_pathkeys,
+							   List **group_clauses)
+{
+	List		*new_group_pathkeys= NIL,
+				*new_group_clauses = NIL;
+	ListCell	*key;
+	int			n;
+
+	if (pathkeys == NIL || *group_pathkeys == NIL)
+		return 0;
+
+	/*
+	 * For each pathkey it tries to find corresponding GROUP BY pathkey and
+	 * clause.
+	 */
+	foreach(key, pathkeys)
+	{
+		PathKey			*pathkey = (PathKey *) lfirst(key);
+		SortGroupClause	*sgc;
+
+		/*
+		 * Pathkey should use the same allocated struct, so, equiality of
+		 * pointers is enough
+		 */
+		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);
+	}
+
+	n = list_length(new_group_pathkeys);
+
+	/*
+	 * Just append the rest of pathkeys and clauses
+	 */
+	*group_pathkeys = list_concat_unique_ptr(new_group_pathkeys,
+												 *group_pathkeys);
+	*group_clauses = list_concat_unique_ptr(new_group_clauses,
+											*group_clauses);
+
+	return n;
+}
+
+typedef struct MutatorState {
+	List		*elemsList;
+	ListCell	**elemCells;
+	void		**elems;
+	int			*positions;
+	int			 mutatorNColumns;
+	int			 count;
+} MutatorState;
+
+static void
+initMutator(MutatorState *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;
+	}
+}
+
+static void
+swap(int *a, int i, int j)
+{
+  int s = a[i];
+
+  a[i] = a[j];
+  a[j] = s;
+}
+
+static bool
+getNextSet(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--;
+	swap(a, j, k);
+
+	l = j + 1;
+	r = n - 1;
+	while (l < r)
+		swap(a, l++, r--);
+
+
+	return true;
+}
+
+static List*
+doMutator(MutatorState *state)
+{
+	int	i;
+
+	state->count++;
+
+	/* first set is original set */
+	if (state->count == 1)
+		return state->elemsList;
+
+	if (getNextSet(state->positions, state->mutatorNColumns) == false)
+	{
+		pfree(state->elems);
+		pfree(state->elemCells);
+		pfree(state->positions);
+		list_free(state->elemsList);
+
+		return NIL;
+	}
+
+	for(i=0; i<state->mutatorNColumns; i++)
+		lfirst(state->elemCells[i]) =
+			(void*) state->elems[ state->positions[i] - 1 ];
+
+	return state->elemsList;
+}
+
+typedef struct {
+	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;
+}
+/*
+ * Order tail of list of group pathkeys by uniqueness descendetly. It allows to
+ * speedup sorting. Returns newly allocated lists, old ones stay untouched.
+ * n_preordered defines a head of list which order should be prevented.
+ */
+void
+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;
+	MutatorState	mstate;
+	double			cheapest_sort_cost = -1.0;
+
+	int nFreeKeys;
+	int nToPermute;
+	int i;
+
+	if (list_length(*group_pathkeys) - n_preordered < 2)
+		return; /* nothing to do */
+
+	/*
+	 * Will try to match ORDER BY pathkeys in hope that one sort is cheaper than
+	 * two
+	 */
+	if (n_preordered == 0 && root->sort_pathkeys)
+	{
+		n_preordered = group_keys_reorder_by_pathkeys(root->sort_pathkeys,
+													  group_pathkeys,
+													  group_clauses);
+
+		if (list_length(*group_pathkeys) - n_preordered < 2)
+			return; /* nothing to do */
+	}
+
+	/*
+	 * Try all permutations of at most 4 cheapeast pathkeys.
+	 */
+	nFreeKeys = list_length(*group_pathkeys) - n_preordered;
+
+	nToPermute = 4;
+
+	if (list_length(*group_pathkeys) > 64 || nrows <= 1024.0 ||
+		n_preordered > nToPermute)
+		return;
+
+	if (nFreeKeys > nToPermute)
+	{
+		/*
+		 * Sort the remaining pathkeys cheapest first.
+		 */
+		PathkeySortCost *costs = palloc(sizeof(*costs) * nFreeKeys);
+		cell = list_nth_cell(*group_pathkeys, n_preordered);
+		for (i = 0; cell != NULL; i++, (cell = lnext(*group_pathkeys, cell)))
+		{
+			List *to_cost = list_make1(lfirst(cell));
+
+			Assert(i < nFreeKeys);
+
+			costs[i].pathkey = lfirst(cell);
+			costs[i].cost = cost_sort_estimate(root, to_cost, 0, nrows);
+
+			pfree(to_cost);
+		}
+		qsort(costs, nFreeKeys, sizeof(*costs), pathkey_sort_cost_comparator);
+
+		/* Construct the sorted list. First, the preordered pathkeys. */
+		new_group_pathkeys = list_truncate(list_copy(*group_pathkeys), n_preordered);
+
+		/* The rest, ordered by increasing cost */
+		for (i = 0; i < nFreeKeys; i++)
+			new_group_pathkeys = lappend(new_group_pathkeys, costs[i].pathkey);
+
+		pfree(costs);
+	}
+	else
+	{
+		new_group_pathkeys = list_copy(*group_pathkeys);
+		nToPermute = nFreeKeys;
+	}
+
+	initMutator(&mstate, new_group_pathkeys, n_preordered, n_preordered + nToPermute);
+
+	while((var_group_pathkeys = doMutator(&mstate)) != NIL)
+	{
+		Cost	cost;
+
+		cost = cost_sort_estimate(root, var_group_pathkeys, n_preordered, nrows);
+
+		if (cost < cheapest_sort_cost || cheapest_sort_cost < 0)
+		{
+			list_free(new_group_pathkeys);
+			new_group_pathkeys = list_copy(var_group_pathkeys);
+			cheapest_sort_cost = cost;
+		}
+	}
+
+	/*
+	 * repeat order of pathkeys for clauses
+	 */
+	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;
+}
+
 /*
  * get_cheapest_path_for_pathkeys
  *	  Find the cheapest path (according to the specified criterion) that
@@ -1845,7 +2138,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 +2155,39 @@ 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, so we don't bother about actual order in
+ * pathkeys
+ */
+static int
+pathkeys_useful_for_grouping(PlannerInfo *root, List *pathkeys)
+{
+	ListCell *key;
+	int		  n = 0;
+
+	if (root->group_pathkeys == NIL)
+		return 0;				/* no special ordering requested */
+
+	if (pathkeys == NIL)
+		return 0;				/* unordered path */
+
+	foreach(key, pathkeys)
+	{
+		PathKey	*pathkey = (PathKey *) lfirst(key);
+
+		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 +2202,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 +2240,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 4d42625e1d4..2c8e6cf21b2 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"
 
 /* source-code-compatibility hacks for pull_varnos() API change */
 #define pull_varnos(a,b) pull_varnos_new(a,b)
@@ -42,15 +45,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
@@ -61,7 +66,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;
 
@@ -164,7 +169,6 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
 	int			innerrelid;
 	RelOptInfo *innerrel;
 	Relids		joinrelids;
-	List	   *clause_list = NIL;
 	ListCell   *l;
 	int			attroff;
 
@@ -240,67 +244,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.
@@ -564,7 +525,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. */
@@ -638,9 +599,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
@@ -656,8 +621,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)
 	{
@@ -961,6 +926,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,
@@ -969,12 +938,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;
 
@@ -994,10 +974,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! */
+		}
 	}
 
 	/*
@@ -1014,7 +998,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
@@ -1028,9 +1012,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
@@ -1076,7 +1063,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;
@@ -1118,5 +1106,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 21ee744d8fa..d4bdf3ba944 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -164,7 +164,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);
@@ -1120,6 +1119,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)),
@@ -1148,7 +1148,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.
@@ -1177,11 +1177,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;
@@ -4745,7 +4751,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
 	}
 	return expression_tree_mutator(node,
 								   replace_nestloop_params_mutator,
-								   (void *) root);
+								   (void *) root,
+								   0);
 }
 
 /*
@@ -4909,7 +4916,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 62dfc6d44a8..3e71a7d18a2 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 34328bff5fd..b460aa9703b 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -269,11 +269,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;
 }
 
@@ -1773,7 +1784,7 @@ inheritance_planner(PlannerInfo *root)
 		/* Make a dummy path, cf set_dummy_rel_pathlist() */
 		dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
 												 NIL, NULL, 0, false,
-												 NIL, -1);
+												 NIL, -1, false);
 
 		/* These lists must be nonempty to make a valid ModifyTable node */
 		subpaths = list_make1(dummy_path);
@@ -4063,7 +4074,8 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
 							   0,
 							   false,
 							   NIL,
-							   -1);
+							   -1,
+							   false);
 	}
 	else
 	{
@@ -6545,21 +6557,44 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 			Path	   *path = (Path *) lfirst(lc);
 			Path	   *path_original = path;
 			bool		is_sorted;
-			int			presorted_keys;
+			int			presorted_keys = 0;
+			List		*group_pathkeys = root->group_pathkeys,
+						*group_clauses = parse->groupClause;
+			int			n_preordered_groups = 0;
 
-			is_sorted = pathkeys_count_contained_in(root->group_pathkeys,
-													path->pathkeys,
-													&presorted_keys);
+			if (parse->groupingSets)
+			{
+				is_sorted = pathkeys_count_contained_in(group_pathkeys,
+														path->pathkeys,
+														&presorted_keys);
+			}
+			else
+			{
+				n_preordered_groups =
+					group_keys_reorder_by_pathkeys(path->pathkeys,
+												   &group_pathkeys,
+												   &group_clauses);
+				is_sorted = (n_preordered_groups ==
+							 list_length(group_pathkeys));
+			}
 
 			if (path == cheapest_path || is_sorted)
 			{
 				/* Sort the cheapest-total path if it isn't already sorted */
 				if (!is_sorted)
+				{
+					if (!parse->groupingSets)
+						get_cheapest_group_keys_order(root,
+													  path->rows,
+													  &group_pathkeys,
+													  &group_clauses,
+													  n_preordered_groups);
 					path = (Path *) create_sort_path(root,
 													 grouped_rel,
 													 path,
-													 root->group_pathkeys,
+													 group_pathkeys,
 													 -1.0);
+				}
 
 				/* Now decide what to stick atop it */
 				if (parse->groupingSets)
@@ -6579,14 +6614,14 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 											 grouped_rel,
 											 path,
 											 grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+											 group_clauses ? AGG_SORTED : AGG_PLAIN,
 											 AGGSPLIT_SIMPLE,
-											 parse->groupClause,
+											 group_clauses,
 											 havingQual,
 											 agg_costs,
 											 dNumGroups));
 				}
-				else if (parse->groupClause)
+				else if (group_clauses)
 				{
 					/*
 					 * We have GROUP BY without aggregation or grouping sets.
@@ -6596,7 +6631,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 							 create_group_path(root,
 											   grouped_rel,
 											   path,
-											   parse->groupClause,
+											   group_clauses,
 											   havingQual,
 											   dNumGroups));
 				}
@@ -6692,12 +6727,22 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 				Path	   *path = (Path *) lfirst(lc);
 				Path	   *path_original = path;
 				bool		is_sorted;
-				int			presorted_keys;
+				int			presorted_keys = 0;
+				List		*group_pathkeys = root->group_pathkeys,
+							*group_clauses = parse->groupClause;
+				int			n_preordered_groups;
 
-				is_sorted = pathkeys_count_contained_in(root->group_pathkeys,
+				is_sorted = pathkeys_count_contained_in(group_pathkeys,
 														path->pathkeys,
 														&presorted_keys);
 
+				n_preordered_groups =
+					group_keys_reorder_by_pathkeys(path->pathkeys,
+												   &group_pathkeys,
+												   &group_clauses);
+				is_sorted = is_sorted ||
+					(n_preordered_groups == list_length(group_pathkeys));
+
 				/*
 				 * Insert a Sort node, if required.  But there's no point in
 				 * sorting anything but the cheapest path.
@@ -6706,10 +6751,15 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 				{
 					if (path != partially_grouped_rel->cheapest_total_path)
 						continue;
+					get_cheapest_group_keys_order(root,
+												  path->rows,
+												  &group_pathkeys,
+												  &group_clauses,
+												  n_preordered_groups);
 					path = (Path *) create_sort_path(root,
 													 grouped_rel,
 													 path,
-													 root->group_pathkeys,
+													 group_pathkeys,
 													 -1.0);
 				}
 
@@ -6719,9 +6769,9 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 											 grouped_rel,
 											 path,
 											 grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+											 group_clauses ? AGG_SORTED : AGG_PLAIN,
 											 AGGSPLIT_FINAL_DESERIAL,
-											 parse->groupClause,
+											 group_clauses,
 											 havingQual,
 											 agg_final_costs,
 											 dNumGroups));
@@ -6730,7 +6780,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
 							 create_group_path(root,
 											   grouped_rel,
 											   path,
-											   parse->groupClause,
+											   group_clauses,
 											   havingQual,
 											   dNumGroups));
 
@@ -6995,18 +7045,31 @@ create_partial_grouping_paths(PlannerInfo *root,
 		{
 			Path	   *path = (Path *) lfirst(lc);
 			bool		is_sorted;
+			List	   *group_pathkeys = root->group_pathkeys,
+					   *group_clauses = parse->groupClause;
+			int			n_preordered_groups;
+
+			n_preordered_groups = group_keys_reorder_by_pathkeys(path->pathkeys,
+																 &group_pathkeys,
+																 &group_clauses);
+			is_sorted = (n_preordered_groups == list_length(group_pathkeys));
 
-			is_sorted = pathkeys_contained_in(root->group_pathkeys,
-											  path->pathkeys);
 			if (path == cheapest_total_path || is_sorted)
 			{
 				/* Sort the cheapest partial path, if it isn't already */
 				if (!is_sorted)
+				{
+					get_cheapest_group_keys_order(root,
+												  path->rows,
+												  &group_pathkeys,
+												  &group_clauses,
+												  n_preordered_groups);
 					path = (Path *) create_sort_path(root,
 													 partially_grouped_rel,
 													 path,
-													 root->group_pathkeys,
+													 group_pathkeys,
 													 -1.0);
+				}
 
 				if (parse->hasAggs)
 					add_path(partially_grouped_rel, (Path *)
@@ -7014,9 +7077,9 @@ create_partial_grouping_paths(PlannerInfo *root,
 											 partially_grouped_rel,
 											 path,
 											 partially_grouped_rel->reltarget,
-											 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+											 group_clauses ? AGG_SORTED : AGG_PLAIN,
 											 AGGSPLIT_INITIAL_SERIAL,
-											 parse->groupClause,
+											 group_clauses,
 											 NIL,
 											 agg_partial_costs,
 											 dNumPartialGroups));
@@ -7025,7 +7088,7 @@ create_partial_grouping_paths(PlannerInfo *root,
 							 create_group_path(root,
 											   partially_grouped_rel,
 											   path,
-											   parse->groupClause,
+											   group_clauses,
 											   NIL,
 											   dNumPartialGroups));
 			}
@@ -7098,21 +7161,37 @@ create_partial_grouping_paths(PlannerInfo *root,
 			Path	   *path = (Path *) lfirst(lc);
 			Path	   *path_original = path;
 			bool		is_sorted;
-			int			presorted_keys;
+			int			presorted_keys = 0;
+			List		*group_pathkeys = root->group_pathkeys,
+						*group_clauses = parse->groupClause;
+			int			n_preordered_groups;
 
-			is_sorted = pathkeys_count_contained_in(root->group_pathkeys,
+			is_sorted = pathkeys_count_contained_in(group_pathkeys,
 													path->pathkeys,
 													&presorted_keys);
 
+			n_preordered_groups = group_keys_reorder_by_pathkeys(path->pathkeys,
+																 &group_pathkeys,
+																 &group_clauses);
+			is_sorted = is_sorted || (n_preordered_groups == list_length(group_pathkeys));
+
 			if (path == cheapest_partial_path || is_sorted)
 			{
 				/* Sort the cheapest partial path, if it isn't already */
 				if (!is_sorted)
+				{
+					get_cheapest_group_keys_order(root,
+												  path->rows,
+												  &group_pathkeys,
+												  &group_clauses,
+												  n_preordered_groups);
+
 					path = (Path *) create_sort_path(root,
 													 partially_grouped_rel,
 													 path,
-													 root->group_pathkeys,
+													 group_pathkeys,
 													 -1.0);
+				}
 
 				if (parse->hasAggs)
 					add_partial_path(partially_grouped_rel, (Path *)
@@ -7120,9 +7199,9 @@ create_partial_grouping_paths(PlannerInfo *root,
 													 partially_grouped_rel,
 													 path,
 													 partially_grouped_rel->reltarget,
-													 parse->groupClause ? AGG_SORTED : AGG_PLAIN,
+													 group_clauses ? AGG_SORTED : AGG_PLAIN,
 													 AGGSPLIT_INITIAL_SERIAL,
-													 parse->groupClause,
+													 group_clauses,
 													 NIL,
 													 agg_partial_costs,
 													 dNumPartialPartialGroups));
@@ -7131,7 +7210,7 @@ create_partial_grouping_paths(PlannerInfo *root,
 									 create_group_path(root,
 													   partially_grouped_rel,
 													   path,
-													   parse->groupClause,
+													   group_clauses,
 													   NIL,
 													   dNumPartialPartialGroups));
 			}
diff --git a/src/backend/optimizer/plan/setrefs.c b/src/backend/optimizer/plan/setrefs.c
index 86d29ecede3..fe35b91377b 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1796,7 +1796,7 @@ fix_scan_expr_mutator(Node *node, fix_scan_expr_context *context)
 	}
 	fix_expr_common(context->root, node);
 	return expression_tree_mutator(node, fix_scan_expr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 static bool
@@ -2130,7 +2130,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);
 }
 
 /*
@@ -2487,6 +2487,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)
 		{
@@ -2501,6 +2505,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,
@@ -2570,7 +2577,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);
 }
 
 /*
@@ -2683,7 +2690,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);
 }
 
 /*
diff --git a/src/backend/optimizer/plan/subselect.c b/src/backend/optimizer/plan/subselect.c
index cac2aaca2e8..9c2afaeb11a 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -714,7 +714,7 @@ convert_testexpr_mutator(Node *node,
 	}
 	return expression_tree_mutator(node,
 								   convert_testexpr_mutator,
-								   (void *) context);
+								   (void *) context, 0);
 }
 
 /*
@@ -1872,7 +1872,7 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
 	}
 	return expression_tree_mutator(node,
 								   replace_correlation_vars_mutator,
-								   (void *) root);
+								   (void *) root, 0);
 }
 
 /*
@@ -2020,7 +2020,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 3616ef1d65a..613e31bd8c0 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -620,7 +620,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, NIL, -1);
+									   NIL, NULL, 0, false, NIL, -1,false);
 
 	/*
 	 * For UNION ALL, we just need the Append path.  For UNION, need to add
@@ -677,7 +677,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
 			create_append_path(root, result_rel, NIL, partial_pathlist,
 							   NIL, NULL,
 							   parallel_workers, enable_parallel_append,
-							   NIL, -1);
+							   NIL, -1,false);
 		ppath = (Path *)
 			create_gather_path(root, result_rel, ppath,
 							   result_rel->reltarget, NULL, NULL);
@@ -787,7 +787,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, NIL, -1);
+									   NIL, NULL, 0, false, NIL, -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 d722063cf3b..45df729f7ca 100644
--- a/src/backend/optimizer/util/appendinfo.c
+++ b/src/backend/optimizer/util/appendinfo.c
@@ -385,7 +385,7 @@ adjust_appendrel_attrs_mutator(Node *node,
 
 		j = (JoinExpr *) expression_tree_mutator(node,
 												 adjust_appendrel_attrs_mutator,
-												 (void *) context);
+												 (void *) context, 0);
 		/* now fix JoinExpr's rtindex (probably never happens) */
 		for (cnt = 0; cnt < nappinfos; cnt++)
 		{
@@ -406,7 +406,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,
@@ -488,7 +488,7 @@ adjust_appendrel_attrs_mutator(Node *node,
 	Assert(!IsA(node, Query));
 
 	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 0f6f7eb7953..38b6e761e55 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2432,7 +2432,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.
@@ -2564,7 +2564,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,
@@ -2708,7 +2708,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
@@ -4038,7 +4038,7 @@ simplify_function(Oid funcid, Oid result_type, int32 result_typmod,
 		args = expand_function_arguments(args, 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;
 	}
@@ -4818,7 +4818,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);
 }
 
 /*
@@ -5263,5 +5263,5 @@ 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 7069cafe38f..55002a25bf3 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;
 
@@ -1222,7 +1234,8 @@ create_append_path(PlannerInfo *root,
 				   List *subpaths, List *partial_subpaths,
 				   List *pathkeys, Relids required_outer,
 				   int parallel_workers, bool parallel_aware,
-				   List *partitioned_rels, double rows)
+				   List *partitioned_rels, double rows,
+				   bool pull_tlist)
 {
 	AppendPath *pathnode = makeNode(AppendPath);
 	ListCell   *l;
@@ -1256,6 +1269,7 @@ create_append_path(PlannerInfo *root,
 	pathnode->path.parallel_workers = parallel_workers;
 	pathnode->path.pathkeys = pathkeys;
 	pathnode->partitioned_rels = list_copy(partitioned_rels);
+	pathnode->pull_tlist = pull_tlist;
 
 	/*
 	 * For parallel append, non-partial paths are sorted by descending total
@@ -1320,7 +1334,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)
@@ -1639,7 +1653,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;
@@ -3753,9 +3768,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;
@@ -3872,7 +3891,8 @@ reparameterize_path(PlannerInfo *root, Path *path,
 									   apath->path.parallel_workers,
 									   apath->path.parallel_aware,
 									   apath->partitioned_rels,
-									   -1);
+									   -1,
+									   apath->pull_tlist);
 			}
 		default:
 			break;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index 3b1ada1127e..85e283167ea 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -444,6 +444,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 283c0446b7f..1f2a3dcc80d 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;
@@ -706,7 +702,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;
@@ -1041,7 +1037,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.
  *
@@ -1054,9 +1050,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)
 {
@@ -1067,8 +1063,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
@@ -1077,7 +1073,7 @@ build_joinrel_restrictlist(PlannerInfo *root,
 	 */
 	result = list_concat(result,
 						 generate_join_implied_equalities(root,
-														  joinrel->relids,
+														  joinrelids,
 														  outer_rel->relids,
 														  inner_rel));
 
@@ -1102,18 +1098,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
@@ -1122,7 +1166,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
 		{
@@ -1133,7 +1177,8 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
 		}
 	}
 
-	return new_restrictlist;
+	hash_destroy(upl.h);
+	return upl.unique_list;
 }
 
 static List *
@@ -1142,6 +1187,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);
@@ -1167,11 +1216,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 109a0d978fd..0682f300f1b 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -844,7 +844,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)
 		{
@@ -880,7 +880,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 24c964f0e19..3b10ff70a29 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -4469,7 +4469,7 @@ DropTableSpaceStmt: DROP TABLESPACE name
  *
  *		QUERY:
  *             CREATE EXTENSION extension
- *             [ WITH ] [ SCHEMA schema ] [ VERSION version ]
+ *             [ WITH ] [ SCHEMA schema ] [ VERSION version ] [ FROM oldversion ]
  *
  *****************************************************************************/
 
@@ -4509,10 +4509,7 @@ create_extension_opt_item:
 				}
 			| FROM NonReservedWord_or_Sconst
 				{
-					ereport(ERROR,
-							(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
-							 errmsg("CREATE EXTENSION ... FROM is no longer supported"),
-							 parser_errposition(@1)));
+					$$ = makeDefElem("old_version", (Node *)makeString($2), @1);
 				}
 			| CASCADE
 				{
@@ -13246,7 +13243,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "~~",
@@ -13259,7 +13256,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_LIKE, "!~~",
@@ -13272,7 +13269,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "~~*",
@@ -13285,7 +13282,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_ILIKE, "!~~*",
@@ -13294,7 +13291,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
@@ -13302,7 +13299,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
@@ -13310,7 +13307,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
@@ -13318,7 +13315,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),
 											   @2);
 					$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
diff --git a/src/backend/parser/parse_clause.c b/src/backend/parser/parse_clause.c
index c6a328c1103..dc15f29c251 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -1198,6 +1198,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 0ca5bdea60f..d35cb3368b4 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -2142,7 +2142,6 @@ addRangeTableEntryForJoin(ParseState *pstate,
 {
 	RangeTblEntry *rte = makeNode(RangeTblEntry);
 	Alias	   *eref;
-	int			numaliases;
 	ParseNamespaceItem *nsitem;
 
 	Assert(pstate != NULL);
@@ -2167,19 +2166,37 @@ addRangeTableEntryForJoin(ParseState *pstate,
 	rte->joinrightcols = rightcols;
 	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;
 
@@ -2790,8 +2807,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 506959b7b67..7e19675796c 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1118,7 +1118,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)
 	{
@@ -1188,14 +1188,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);
 }
 
 
@@ -1352,7 +1353,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 7bc1495981b..6a10e8d4f20 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -873,6 +873,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;
@@ -892,6 +893,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 (;;)
 	{
@@ -907,12 +917,12 @@ count_usable_fds(int max_to_probe, int *usable_fds, int *already_open)
 			break;
 #endif
 
-		thisfd = dup(0);
+		thisfd = dup(fdTest);
 		if (thisfd < 0)
 		{
 			/* Expect EMFILE or ENFILE, else it's fishy */
 			if (errno != EMFILE && errno != ENFILE)
-				elog(WARNING, "dup(0) failed after %d successes: %m", used);
+				elog(WARNING, "dup() failed after %d successes: %m", used);
 			break;
 		}
 
@@ -934,6 +944,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 2800a89c25b..f75cf57ac01 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 0a21fb98047..e9c985758fb 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)
@@ -479,8 +480,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 f01ba06b302..2c42e04cee2 100644
--- a/src/backend/storage/smgr/smgr.c
+++ b/src/backend/storage/smgr/smgr.c
@@ -427,7 +427,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).
@@ -596,7 +597,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 bc8dd553a49..a055a9e048f 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -73,6 +73,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"
@@ -3076,6 +3077,8 @@ ProcessInterrupts(void)
 					(errcode(ERRCODE_ADMIN_SHUTDOWN),
 					 errmsg("terminating connection due to administrator command")));
 	}
+	if (CheckClientConnectionPending)
+		pq_check_client_connection();
 	if (ClientConnectionLost)
 	{
 		QueryCancelPending = false; /* lost connection trumps QueryCancel */
@@ -4357,6 +4360,7 @@ PostgresMain(int argc, char *argv[],
 		 */
 		CHECK_FOR_INTERRUPTS();
 		DoingCommandRead = false;
+		enable_timeout_after(SKIP_CLIENT_CHECK_TIMEOUT, 1000);
 
 		/*
 		 * (6) check for any other interesting events that happened while we
diff --git a/src/backend/utils/adt/like_support.c b/src/backend/utils/adt/like_support.c
index ed4d6a8dc67..0fdec4f2372 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
@@ -251,6 +353,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.
@@ -283,8 +386,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)
@@ -299,7 +410,6 @@ match_pattern_prefix(Node *leftop,
 	 * selected operators also determine the needed type of the prefix
 	 * constant.
 	 */
-	ldatatype = exprType(leftop);
 	switch (ldatatype)
 	{
 		case TEXTOID:
@@ -358,7 +468,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);
 	}
 
 	/*
@@ -384,9 +503,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;
 	}
 
 	/*
@@ -432,7 +552,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 80cba2f4c26..8c562e2ce52 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -27,7 +27,6 @@
 #include "utils/lsyscache.h"
 #include "utils/typcache.h"
 
-
 /*
  * structure to cache metadata needed for record I/O
  */
@@ -786,6 +785,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;
@@ -870,6 +872,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
@@ -1030,6 +1035,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;
@@ -1114,6 +1122,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 660bf69a023..a3789b29285 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -42,6 +42,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"
@@ -284,6 +285,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 */
@@ -362,6 +365,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,
@@ -4316,7 +4320,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];
@@ -4365,7 +4372,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];
@@ -4390,6 +4400,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?
  *
@@ -4402,6 +4435,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++)
 	{
@@ -4453,6 +4555,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 ea0b3a97ce7..a19b8b76e61 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -149,13 +149,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,
@@ -163,7 +164,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,
@@ -215,6 +217,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.
@@ -287,6 +303,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
  *
@@ -296,6 +340,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;
@@ -328,7 +381,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;
 	}
@@ -347,11 +401,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);
@@ -362,15 +416,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++)
 			{
@@ -1020,6 +1076,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
  *
@@ -2248,11 +2436,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;
@@ -2267,45 +2476,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)
 	{
@@ -2327,26 +2536,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);
 			}
 
 			/*
@@ -2372,12 +2583,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;
 }
 
 /*
@@ -2387,13 +2718,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;
 
@@ -2411,7 +2743,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;
@@ -2437,10 +2769,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));
@@ -2566,11 +2900,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;
@@ -2591,7 +2948,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;
 
@@ -2638,7 +2996,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;
@@ -2667,10 +3025,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));
@@ -3296,7 +3656,10 @@ add_unique_group_var(PlannerInfo *root, List *varinfos,
 }
 
 /*
- * estimate_num_groups		- Estimate number of groups in a grouped query
+ * estimate_num_groups/estimate_num_groups_incremental
+ *		- Estimate number of groups in a grouped query.
+ *		  _incremental variant is performance optimization for
+ *		  case of adding one-by-one column
  *
  * Given a query having a GROUP BY clause, estimate how many groups there
  * will be --- ie, the number of distinct combinations of the GROUP BY
@@ -3364,11 +3727,20 @@ double
 estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 					List **pgset)
 {
-	List	   *varinfos = NIL;
+	return estimate_num_groups_incremental(root, groupExprs,
+										   input_rows, pgset, NULL, 0);
+}
+
+double
+estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs,
+					double input_rows,
+					List **pgset, 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;
 
 	/*
 	 * We don't ever want to return an estimate of zero groups, as that tends
@@ -3378,6 +3750,9 @@ estimate_num_groups(PlannerInfo *root, List *groupExprs, double input_rows,
 	 */
 	input_rows = clamp_row_est(input_rows);
 
+	if (input_rows == 1.0)
+		return 1.0;
+
 	/*
 	 * If no grouping columns, there's exactly one group.  (This can't happen
 	 * for normal cases with GROUP BY or DISTINCT, but it is possible for
@@ -3395,7 +3770,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);
@@ -3404,6 +3779,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;
@@ -3445,6 +3828,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
@@ -3465,13 +3851,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);
@@ -3479,9 +3873,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.
@@ -4696,14 +5096,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);
 }
 
 /*
@@ -6135,7 +6529,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 05ac366b40d..a8f007837d2 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.
@@ -82,9 +100,17 @@ InitializeAttoptCache(void)
 	MemSet(&ctl, 0, sizeof(ctl));
 	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 5032f9a33e4..79f3658ca98 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"
@@ -2188,7 +2189,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;
@@ -2218,6 +2219,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;
@@ -2229,7 +2233,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)
 		{
@@ -2238,7 +2282,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 fc4412df4a1..a79fbe37d7e 100644
--- a/src/backend/utils/cache/inval.c
+++ b/src/backend/utils/cache/inval.c
@@ -115,6 +115,7 @@
 #include "access/htup_details.h"
 #include "access/xact.h"
 #include "catalog/catalog.h"
+#include "catalog/namespace.h"
 #include "catalog/pg_constraint.h"
 #include "miscadmin.h"
 #include "storage/sinval.h"
@@ -351,12 +352,13 @@ AppendInvalidationMessageList(InvalidationChunk **destHdr,
  */
 static void
 AddCatcacheInvalidationMessage(InvalidationListHeader *hdr,
-							   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;
 
@@ -397,7 +399,7 @@ AddCatalogInvalidationMessage(InvalidationListHeader *hdr,
  */
 static void
 AddRelcacheInvalidationMessage(InvalidationListHeader *hdr,
-							   Oid dbId, Oid relId)
+							   Oid dbId, Oid relId, bool isLocal)
 {
 	SharedInvalidationMessage msg;
 
@@ -414,6 +416,7 @@ AddRelcacheInvalidationMessage(InvalidationListHeader *hdr,
 
 	/* 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 */
@@ -499,10 +502,10 @@ ProcessInvalidationMessagesMulti(InvalidationListHeader *hdr,
 static void
 RegisterCatcacheInvalidation(int cacheId,
 							 uint32 hashValue,
-							 Oid dbId)
+							 Oid dbId, bool isLocal)
 {
 	AddCatcacheInvalidationMessage(&transInvalInfo->CurrentCmdInvalidMsgs,
-								   cacheId, hashValue, dbId);
+								   cacheId, hashValue, dbId, isLocal);
 }
 
 /*
@@ -523,10 +526,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
@@ -1138,6 +1141,8 @@ CacheInvalidateHeapTuple(Relation relation,
 	Oid			tupleRelId;
 	Oid			databaseId;
 	Oid			relationId;
+	bool		tempRel = false;
+	bool		checkTemp = false;
 
 	/* Do nothing during bootstrap */
 	if (IsBootstrapProcessingMode())
@@ -1193,6 +1198,9 @@ CacheInvalidateHeapTuple(Relation relation,
 			databaseId = InvalidOid;
 		else
 			databaseId = MyDatabaseId;
+
+		tempRel = (classtup->relpersistence == RELPERSISTENCE_TEMP) ?
+						true : false;
 	}
 	else if (tupleRelId == AttributeRelationId)
 	{
@@ -1211,6 +1219,7 @@ CacheInvalidateHeapTuple(Relation relation,
 		 * never come here for a shared rel anyway.)
 		 */
 		databaseId = MyDatabaseId;
+		checkTemp = true;
 	}
 	else if (tupleRelId == IndexRelationId)
 	{
@@ -1224,6 +1233,7 @@ CacheInvalidateHeapTuple(Relation relation,
 		 */
 		relationId = indextup->indexrelid;
 		databaseId = MyDatabaseId;
+		checkTemp = true;
 	}
 	else if (tupleRelId == ConstraintRelationId)
 	{
@@ -1245,10 +1255,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);
 }
 
 /*
@@ -1300,7 +1327,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));
 }
 
 /*
@@ -1315,7 +1345,7 @@ CacheInvalidateRelcacheAll(void)
 {
 	PrepareInvalidationState();
 
-	RegisterRelcacheInvalidation(InvalidOid, InvalidOid);
+	RegisterRelcacheInvalidation(InvalidOid, InvalidOid, false);
 }
 
 /*
@@ -1336,7 +1366,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 3303c31e575..1f6eb7736f0 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"
@@ -3188,6 +3189,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
@@ -3195,6 +3242,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 09ac55a3cc9..143f0ecaf83 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;
 
@@ -320,6 +327,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
@@ -346,8 +362,14 @@ lookup_type_cache(Oid type_id, int flags)
 		MemSet(&ctl, 0, sizeof(ctl));
 		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);
@@ -398,8 +420,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;
@@ -419,6 +440,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))
@@ -456,6 +489,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);
 	}
 
@@ -2167,58 +2212,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;
+	}
 }
 
 /*
@@ -2236,20 +2295,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 5948b01abc3..3c1d058aab7 100644
--- a/src/backend/utils/hash/dynahash.c
+++ b/src/backend/utils/hash/dynahash.c
@@ -1392,10 +1392,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)
 {
@@ -1409,6 +1436,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/init/globals.c b/src/backend/utils/init/globals.c
index 9793354a02f..eda120fddab 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -30,6 +30,7 @@ ProtocolVersion FrontendProtocol;
 volatile sig_atomic_t InterruptPending = false;
 volatile sig_atomic_t QueryCancelPending = false;
 volatile sig_atomic_t ProcDiePending = false;
+volatile sig_atomic_t CheckClientConnectionPending = false;
 volatile sig_atomic_t ClientConnectionLost = false;
 volatile sig_atomic_t IdleInTransactionSessionTimeoutPending = false;
 volatile sig_atomic_t ProcSignalBarrierPending = false;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index fc2b4f6c479..5946345ccac 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -73,6 +73,7 @@ static void ShutdownPostgres(int code, Datum arg);
 static void StatementTimeoutHandler(void);
 static void LockTimeoutHandler(void);
 static void IdleInTransactionSessionTimeoutHandler(void);
+static void ClientCheckTimeoutHandler(void);
 static bool ThereIsAtLeastOneRole(void);
 static void process_startup_options(Port *port, bool am_superuser);
 static void process_settings(Oid databaseid, Oid roleid);
@@ -623,6 +624,7 @@ InitPostgres(const char *in_dbname, Oid dboid, const char *username,
 		RegisterTimeout(LOCK_TIMEOUT, LockTimeoutHandler);
 		RegisterTimeout(IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
 						IdleInTransactionSessionTimeoutHandler);
+		RegisterTimeout(SKIP_CLIENT_CHECK_TIMEOUT, ClientCheckTimeoutHandler);
 	}
 
 	/*
@@ -1243,6 +1245,14 @@ IdleInTransactionSessionTimeoutHandler(void)
 	SetLatch(MyLatch);
 }
 
+static void
+ClientCheckTimeoutHandler(void)
+{
+	CheckClientConnectionPending = true;
+	InterruptPending = true;
+	enable_timeout_after(SKIP_CLIENT_CHECK_TIMEOUT, 1000);
+}
+
 /*
  * Returns true if at least one role is defined in this database cluster.
  */
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 1fabbb11ffa..4a72988847f 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -128,6 +128,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;
@@ -222,6 +223,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,
@@ -1114,6 +1116,16 @@ 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
+	},
 	{
 		{"geqo", PGC_USERSET, QUERY_TUNING_GEQO,
 			gettext_noop("Enables genetic query optimization."),
@@ -1692,7 +1704,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,
@@ -2023,7 +2035,6 @@ static struct config_bool ConfigureNamesBool[] =
 		 */
 		NULL, NULL, NULL
 	},
-
 	{
 		{"jit_tuple_deforming", PGC_USERSET, DEVELOPER_OPTIONS,
 			gettext_noop("Allow JIT compilation of tuple deforming."),
@@ -12141,13 +12152,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/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 3c70a7b5f24..468dfab50c8 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -1472,7 +1472,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 452a85a423b..6def5175fbe 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 854990081f6..ae1d3c3d5c9 100644
--- a/src/bin/pg_basebackup/pg_receivewal.c
+++ b/src/bin/pg_basebackup/pg_receivewal.c
@@ -96,6 +96,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"));
@@ -475,6 +476,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'},
@@ -517,7 +519,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)
@@ -539,6 +541,9 @@ main(int argc, char **argv)
 				}
 				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 f049a2480ed..3b2b7961ccb 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -324,11 +324,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 e2033b7e377..8cbc0d087ce 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -48,6 +48,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 bool have_password = false;
 static char password[100];
diff --git a/src/bin/pg_basebackup/streamutil.h b/src/bin/pg_basebackup/streamutil.h
index 57448656e3d..eaed4cedb3f 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -22,6 +22,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 19dc6816298..384c4157bb8 100644
--- a/src/bin/pg_basebackup/walmethods.c
+++ b/src/bin/pg_basebackup/walmethods.c
@@ -99,6 +99,8 @@ dir_open_for_write(const char *pathname, const char *temp_suffix, size_t pad_to_
 #ifdef HAVE_LIBZ
 	gzFile		gzfp = NULL;
 #endif
+	mode_t		mode = (useumask == 1) ?
+		(S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) : (S_IRUSR | S_IWUSR);
 
 	dir_clear_error();
 
@@ -113,7 +115,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;
@@ -630,6 +632,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();
@@ -641,7 +645,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 59f00740bfb..7054cb33aa4 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -54,6 +54,7 @@ static DumpableObject **aminfoindex;
 static DumpableObject **collinfoindex;
 static DumpableObject **nspinfoindex;
 static DumpableObject **extinfoindex;
+static DumpableObject **idxinfoindex;
 static DumpableObject **pubinfoindex;
 static int	numTables;
 static int	numTypes;
@@ -63,6 +64,7 @@ static int	numAccessMethods;
 static int	numCollations;
 static int	numNamespaces;
 static int	numExtensions;
+static int	numIndexes;
 static int	numPublications;
 
 /* This is an array of object identities, not actual DumpableObjects */
@@ -80,9 +82,8 @@ static int	ExtensionMemberIdCompare(const void *p1, const void *p2);
 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, DumpableObject **idxinfoindex,
-								int numIndexes);
-
+static IndxInfo *findTableIndexByOid(Oid oid,
+				DumpableObject **idxinfoTableIndex, int numTableIndexes);
 
 /*
  * getSchemaData
@@ -101,6 +102,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	ExtensionInfo *extinfo;
 	PublicationInfo *pubinfo;
 	InhInfo    *inhinfo;
+	IndxInfo   *idxinfo;
 	int			numAggregates;
 	int			numInherits;
 	int			numRules;
@@ -236,7 +238,8 @@ getSchemaData(Archive *fout, int *numTablesPtr)
 	getPartitioningInfo(fout);
 
 	pg_log_info("reading indexes");
-	getIndexes(fout, tblinfo, numTables);
+	idxinfo = getIndexes(fout, tblinfo, numTables, &numIndexes);
+	idxinfoindex = buildIndexArray(idxinfo, numIndexes, sizeof(IndxInfo));
 
 	pg_log_info("flagging indexes in partitioned tables");
 	flagInhIndexes(fout, tblinfo, numTables);
@@ -384,7 +387,7 @@ flagInhIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			if (index->parentidx == 0)
 				continue;
 
-			parentidx = findIndexByOid(index->parentidx,
+			parentidx = findTableIndexByOid(index->parentidx,
 									   parentIndexArray[parenttbl->dobj.dumpId],
 									   parenttbl->numIndexes);
 			if (parentidx == NULL)
@@ -951,15 +954,26 @@ findPublicationByOid(Oid oid)
 
 /*
  * findIndexByOid
+ *	  find the entry (in idxinfo) of the index with the given oid
+ *	  returns NULL if not found
+ */
+IndxInfo *
+findIndexByOid(Oid oid)
+{
+	return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes);
+}
+
+/*
+ * findTableIndexByOid
  *		find the entry of the index with the given oid
  *
- * This one's signature is different from the previous ones because we lack a
- * global array of all indexes, so caller must pass their array as argument.
+ * This one's signature is different from the previous ones because we use
+ * it to find an index of specific table who passes its index array as argument.
  */
 static IndxInfo *
-findIndexByOid(Oid oid, DumpableObject **idxinfoindex, int numIndexes)
+findTableIndexByOid(Oid oid, DumpableObject **tbl_idxinfoindex, int tblNumIndexes)
 {
-	return (IndxInfo *) findObjectByOid(oid, idxinfoindex, numIndexes);
+	return (IndxInfo *) findObjectByOid(oid, tbl_idxinfoindex, tblNumIndexes);
 }
 
 /*
diff --git a/src/bin/pg_dump/pg_dump.c b/src/bin/pg_dump/pg_dump.c
index 161dd5ca1ea..aaac3c6d0fa 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1667,11 +1667,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;
@@ -4633,6 +4644,9 @@ binary_upgrade_set_type_oids_by_type_oid(Archive *fout,
 	PGresult   *res;
 	Oid			pg_type_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",
@@ -7262,8 +7276,8 @@ getPartitioningInfo(Archive *fout)
  * Note: index data is not returned directly to the caller, but it
  * does get entered into the DumpableObject tables.
  */
-void
-getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
+IndxInfo *
+getIndexes(Archive *fout, TableInfo tblinfo[], int numTables, int *numIndexes)
 {
 	int			i,
 				j;
@@ -7293,6 +7307,9 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 				i_indstatcols,
 				i_indstatvals;
 	int			ntups;
+	Size		off = 0;
+
+	*numIndexes = 0;
 
 	for (i = 0; i < numTables; i++)
 	{
@@ -7536,6 +7553,7 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 			(IndxInfo *) pg_malloc(ntups * sizeof(IndxInfo));
 		constrinfo = (ConstraintInfo *) pg_malloc(ntups * sizeof(ConstraintInfo));
 		tbinfo->numIndexes = ntups;
+		*numIndexes += ntups;
 
 		for (j = 0; j < ntups; j++)
 		{
@@ -7608,6 +7626,27 @@ getIndexes(Archive *fout, TableInfo tblinfo[], int numTables)
 	}
 
 	destroyPQExpBuffer(query);
+
+	/*
+	 * A second pass to form an array of all index infos.
+	 * Now that we know the total number of indexes after the first pass,
+	 * we can allocate all needed memory in one call instead of using realloc.
+	 */
+	indxinfo = (IndxInfo *) pg_malloc(*numIndexes * sizeof(IndxInfo));
+
+	for (i = 0; i < numTables; i++)
+	{
+		TableInfo  *tbinfo = &tblinfo[i];
+		int			copynum = tbinfo->numIndexes;
+
+		if (copynum < 1)
+			continue;
+
+		memcpy(indxinfo + off, tbinfo->indexes, copynum * sizeof(IndxInfo));
+		off += copynum;
+	}
+
+	return indxinfo;
 }
 
 /*
@@ -17025,8 +17064,13 @@ dumpIndex(Archive *fout, IndxInfo *indxinfo)
 		int			nstatvals;
 
 		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->dobj.catId.oid);
+		}
 
 		/* Plain secondary index */
 		appendPQExpBuffer(q, "%s;\n", indxinfo->indexdef);
@@ -17290,8 +17334,14 @@ dumpConstraint(Archive *fout, ConstraintInfo *coninfo)
 				  coninfo->dobj.name);
 
 		if (dopt->binary_upgrade)
+		{
+			if (indxinfo->indnkeyattrs > 1)
+				binary_upgrade_set_type_oids_by_rel_oid(fout, q,
+														indxinfo->dobj.catId.oid);
+
 			binary_upgrade_set_pg_class_oids(fout, q,
 											 indxinfo->dobj.catId.oid, true);
+		}
 
 		appendPQExpBuffer(q, "ALTER %sTABLE ONLY %s\n", foreign,
 						  fmtQualifiedDumpable(tbinfo));
@@ -18881,6 +18931,27 @@ 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;
+			}
 			case DO_NAMESPACE:
 			case DO_EXTENSION:
 			case DO_TYPE:
@@ -18897,7 +18968,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 7fcc544b2dc..7ecb9bde59d 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -671,6 +671,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 setExtensionMembership(ExtensionMemberId *extmems, int nextmems);
@@ -700,7 +701,8 @@ extern TableInfo *getTables(Archive *fout, int *numTables);
 extern void getOwnedSeqs(Archive *fout, TableInfo tblinfo[], int numTables);
 extern InhInfo *getInherits(Archive *fout, int *numInherits);
 extern void getPartitioningInfo(Archive *fout);
-extern void getIndexes(Archive *fout, TableInfo tblinfo[], int numTables);
+extern IndxInfo *getIndexes(Archive *fout, TableInfo tblinfo[], int numTables,
+						int *numIndexes);
 extern void getExtendedStatistics(Archive *fout);
 extern void getConstraints(Archive *fout, TableInfo tblinfo[], int numTables);
 extern RuleInfo *getRules(Archive *fout, int *numRules);
diff --git a/src/include/catalog/index.h b/src/include/catalog/index.h
index f58e8675f32..c40e6b11029 100644
--- a/src/include/catalog/index.h
+++ b/src/include/catalog/index.h
@@ -51,6 +51,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 e8be0008353..6bf8a57977f 100644
--- a/src/include/catalog/pg_type.dat
+++ b/src/include/catalog/pg_type.dat
@@ -630,4 +630,12 @@
   typoutput => 'anycompatiblerange_out', typreceive => '-', typsend => '-',
   typalign => 'd', typstorage => 'x' },
 
+{ oid => '14756', descr => 'pseudo-type representing removed abstime',
+	typname => 'abstime', typlen => '-1', typbyval => 'f', typtype => 'p',
+	typcategory => 'P', typinput => '-', typoutput => '-',
+	typreceive => '-', typsend => '-', typalign => 'c' },
+{ oid => '14757', descr => 'pseudo-type representing removed reltime',
+	typname => 'reltime', typlen => '-1', typbyval => 'f', typtype => 'p',
+	typcategory => 'P', typinput => '-', typoutput => '-',
+	typreceive => '-', typsend => '-', typalign => 'c' },
 ]
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index fa3beec2e8e..9861fa4eb38 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -346,12 +346,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;
 
@@ -609,6 +614,7 @@ typedef struct ExprEvalStep
 		{
 			/* out-of-line state, created by nodeSubplan.c */
 			AlternativeSubPlanState *asstate;
+			bool	*guaranteed_empty;
 		}			alternative_subplan;
 
 		/* for EEOP_AGG_*DESERIALIZE */
diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h
index 54c5fa77977..15b76aa2ab6 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -74,6 +74,7 @@ extern int	pq_peekbyte(void);
 extern int	pq_getbyte_if_available(unsigned char *c);
 extern bool pq_buffer_has_data(void);
 extern int	pq_putbytes(const char *s, size_t len);
+extern void pq_check_client_connection(void);
 
 /*
  * prototypes for functions in be-secure.c
diff --git a/src/include/miscadmin.h b/src/include/miscadmin.h
index 8ce6c13fffc..9f93365d551 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -93,6 +93,7 @@ extern PGDLLIMPORT volatile sig_atomic_t ProcDiePending;
 extern PGDLLIMPORT volatile sig_atomic_t IdleInTransactionSessionTimeoutPending;
 extern PGDLLIMPORT volatile sig_atomic_t ProcSignalBarrierPending;
 
+extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
 extern PGDLLIMPORT volatile sig_atomic_t ClientConnectionLost;
 
 /* these are marked volatile because they are examined by signal handlers: */
diff --git a/src/include/nodes/execnodes.h b/src/include/nodes/execnodes.h
index d05ec67231f..d0403cab8ff 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -113,6 +113,8 @@ typedef struct ExprState
 
 	Datum	   *innermost_domainval;
 	bool	   *innermost_domainnull;
+
+	bool		guaranteed_empty;
 } ExprState;
 
 
@@ -914,6 +916,7 @@ typedef struct AlternativeSubPlanState
 	AlternativeSubPlan *subplan;	/* expression plan node */
 	List	   *subplans;		/* SubPlanStates of alternative subplans */
 	int			active;			/* list index of the one we're using */
+	bool		guaranteed_empty;
 } AlternativeSubPlanState;
 
 /*
@@ -1017,6 +1020,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 9cc56eecaa3..d5616d4cb2f 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 d2b4271de9d..1e851837c1c 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"
 
 
 /*
@@ -868,6 +870,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;
 };
 
 /*
@@ -1410,6 +1416,12 @@ typedef struct AppendPath
 	/* Index of first partial path in subpaths; list_length(subpaths) if none */
 	int			first_partial_path;
 	double		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 90f02ce6fdd..531da7d5b15 100644
--- a/src/include/nodes/plannodes.h
+++ b/src/include/nodes/plannodes.h
@@ -20,6 +20,7 @@
 #include "nodes/bitmapset.h"
 #include "nodes/lockoptions.h"
 #include "nodes/primnodes.h"
+#include "portability/instr_time.h"
 
 
 /* ----------------------------------------------------------------
@@ -91,6 +92,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 9a33c4c896f..e93e7b15bc0 100644
--- a/src/include/nodes/supportnodes.h
+++ b/src/include/nodes/supportnodes.h
@@ -34,6 +34,20 @@
 #define SUPPORTNODES_H
 
 #include "nodes/primnodes.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;
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 6141654e478..403023d6751 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -107,7 +107,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,
@@ -167,6 +169,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 3bd7072ae8c..806b5a42632 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -67,7 +67,8 @@ 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,
-									  List *partitioned_rels, double rows);
+				   List *partitioned_rels, double rows,
+				   bool pull_tlist);
 extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
 												 RelOptInfo *rel,
 												 List *subpaths,
@@ -295,6 +296,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 9254ee088b4..feb4e882098 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -72,9 +72,23 @@ 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,14 @@ 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 void get_cheapest_group_keys_order(PlannerInfo *root,
+										   double nrows,
+										   List **group_pathkeys,
+										   List **group_clauses,
+										   int   n_preordered);
 extern Path *get_cheapest_path_for_pathkeys(List *paths, List *pathkeys,
 											Relids required_outer,
 											CostSelector cost_criterion,
@@ -241,6 +263,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 +271,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 3c1314e7c85..b74e55b5430 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 0e86dfb9280..efabbf4c2e5 100644
--- a/src/include/port.h
+++ b/src/include/port.h
@@ -104,6 +104,55 @@ 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
+#ifdef ECONNRESET
+#define SOCK_ECONNRESET ECONNRESET
+#endif
+#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
+
 /* 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 7fde4ab790d..b208e0023bd 100644
--- a/src/include/storage/s_lock.h
+++ b/src/include/storage/s_lock.h
@@ -441,6 +441,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 903818bea31..fd6f2c7e65a 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 3f8688d4ae3..be27c4b33fa 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 f1deb9beab0..9b422b2dc81 100644
--- a/src/include/utils/hsearch.h
+++ b/src/include/utils/hsearch.h
@@ -114,6 +114,8 @@ typedef struct
 	HTAB	   *hashp;
 	uint32		curBucket;		/* index of current bucket */
 	HASHELEMENT *curEntry;		/* current entry in bucket */
+	bool		hasHashVal;
+	uint32		hashvalue;
 } HASH_SEQ_STATUS;
 
 /*
@@ -137,6 +139,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 ee35686a660..53ce9c48ae9 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -56,6 +56,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() */
@@ -179,6 +181,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 06e9defb54b..1e81814d261 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -80,6 +80,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)  \
@@ -159,6 +160,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,
@@ -173,7 +177,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,
@@ -197,6 +200,9 @@ extern void mergejoinscansel(PlannerInfo *root, Node *clause,
 
 extern double estimate_num_groups(PlannerInfo *root, List *groupExprs,
 								  double input_rows, List **pgset);
+extern double estimate_num_groups_incremental(PlannerInfo *root, List *groupExprs,
+					double input_rows, List **pgset,
+					List **cache_varinfos, int prevNExprs);
 
 extern void estimate_hash_bucket_stats(PlannerInfo *root,
 									   Node *hashkey, double nbuckets,
@@ -221,5 +227,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/include/utils/timeout.h b/src/include/utils/timeout.h
index 83a15f67952..5c2cde94536 100644
--- a/src/include/utils/timeout.h
+++ b/src/include/utils/timeout.h
@@ -31,6 +31,7 @@ typedef enum TimeoutId
 	STANDBY_TIMEOUT,
 	STANDBY_LOCK_TIMEOUT,
 	IDLE_IN_TRANSACTION_SESSION_TIMEOUT,
+	SKIP_CLIENT_CHECK_TIMEOUT,
 	/* First user-definable timeout reason */
 	USER_TIMEOUT,
 	/* Maximum number of timeout reasons */
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 9b781c35002..239208c1664 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -2698,11 +2698,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
@@ -4401,7 +4401,7 @@ retry3:
 	if (connect(tmpsock, (struct sockaddr *) &raddr->addr,
 				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);
@@ -4422,7 +4422,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);
@@ -4439,7 +4439,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 17cd3a733e0..43ff6dfb03f 100644
--- a/src/interfaces/libpq/fe-misc.c
+++ b/src/interfaces/libpq/fe-misc.c
@@ -707,20 +707,20 @@ retry3:
 						  conn->inBufSize - conn->inEnd);
 	if (nread < 0)
 	{
-		if (SOCK_ERRNO == EINTR)
+		if (SOCK_ERRNO == SOCK_EINTR)
 			goto retry3;
 		/* Some systems return EAGAIN/EWOULDBLOCK for no data */
-#ifdef EAGAIN
-		if (SOCK_ERRNO == EAGAIN)
+#ifdef SOCK_EAGAIN
+		if (SOCK_ERRNO == SOCK_EAGAIN)
 			return someread;
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-		if (SOCK_ERRNO == EWOULDBLOCK)
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+		if (SOCK_ERRNO == SOCK_EWOULDBLOCK)
 			return someread;
 #endif
 		/* We might get ECONNRESET here if using TCP and backend died */
-#ifdef ECONNRESET
-		if (SOCK_ERRNO == ECONNRESET)
+#ifdef SOCK_ECONNRESET
+		if (SOCK_ERRNO == SOCK_ECONNRESET)
 			goto definitelyFailed;
 #endif
 		/* pqsecure_read set the error message for us */
@@ -797,20 +797,20 @@ retry4:
 						  conn->inBufSize - conn->inEnd);
 	if (nread < 0)
 	{
-		if (SOCK_ERRNO == EINTR)
+		if (SOCK_ERRNO == SOCK_EINTR)
 			goto retry4;
 		/* Some systems return EAGAIN/EWOULDBLOCK for no data */
-#ifdef EAGAIN
-		if (SOCK_ERRNO == EAGAIN)
+#ifdef SOCK_EAGAIN
+		if (SOCK_ERRNO == SOCK_EAGAIN)
 			return 0;
 #endif
-#if defined(EWOULDBLOCK) && (!defined(EAGAIN) || (EWOULDBLOCK != EAGAIN))
-		if (SOCK_ERRNO == EWOULDBLOCK)
+#if defined(SOCK_EWOULDBLOCK) && (!defined(SOCK_EAGAIN) || (SOCK_EWOULDBLOCK != SOCK_EAGAIN))
+		if (SOCK_ERRNO == SOCK_EWOULDBLOCK)
 			return 0;
 #endif
 		/* We might get ECONNRESET here if using TCP and backend died */
-#ifdef ECONNRESET
-		if (SOCK_ERRNO == ECONNRESET)
+#ifdef SOCK_ECONNRESET
+		if (SOCK_ERRNO == SOCK_ECONNRESET)
 			goto definitelyFailed;
 #endif
 		/* pqsecure_read set the error message for us */
@@ -925,15 +925,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:
@@ -1147,7 +1147,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 44d43b7b0ee..d9fa8d75538 100644
--- a/src/interfaces/libpq/fe-secure-gssapi.c
+++ b/src/interfaces/libpq/fe-secure-gssapi.c
@@ -84,7 +84,7 @@
  * 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
+ * len). On failure, returns -1 with SOCK_ERRNO (need to use SOCK_ERRNO since it is different from errno in Windows)
  * indicates a non-retryable error, a message is put into 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)
 	{
 		printfPQExpBuffer(&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)
 		{
 			printfPQExpBuffer(&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;
 		}
 
@@ -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)
 			printfPQExpBuffer(&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 cdb47ba479f..9b4f1b4b477 100644
--- a/src/interfaces/libpq/fe-secure-openssl.c
+++ b/src/interfaces/libpq/fe-secure-openssl.c
@@ -179,7 +179,7 @@ rloop:
 				printfPQExpBuffer(&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:
@@ -198,8 +198,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)
 					printfPQExpBuffer(&conn->errorMessage,
 									  libpq_gettext("server closed the connection unexpectedly\n"
 													"\tThis probably means the server terminated abnormally\n"
@@ -215,7 +218,7 @@ rloop:
 				printfPQExpBuffer(&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;
@@ -227,7 +230,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;
 			}
@@ -240,7 +243,7 @@ rloop:
 			 */
 			printfPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("SSL connection has been closed unexpectedly\n"));
-			result_errno = ECONNRESET;
+			result_errno = SOCK_ECONNRESET;
 			n = -1;
 			break;
 		default:
@@ -248,7 +251,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;
 	}
@@ -288,7 +291,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 				printfPQExpBuffer(&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:
@@ -312,7 +315,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)
 					printfPQExpBuffer(&conn->errorMessage,
 									  libpq_gettext("server closed the connection unexpectedly\n"
 													"\tThis probably means the server terminated abnormally\n"
@@ -328,7 +335,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 				printfPQExpBuffer(&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;
@@ -340,7 +347,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;
 			}
@@ -353,7 +360,7 @@ pgtls_write(PGconn *conn, const void *ptr, size_t len)
 			 */
 			printfPQExpBuffer(&conn->errorMessage,
 							  libpq_gettext("SSL connection has been closed unexpectedly\n"));
-			result_errno = ECONNRESET;
+			result_errno = SOCK_ECONNRESET;
 			n = -1;
 			break;
 		default:
@@ -361,7 +368,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;
 	}
@@ -1623,13 +1630,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;
 
@@ -1653,13 +1660,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 377fc37d083..30142bde19a 100644
--- a/src/interfaces/libpq/fe-secure.c
+++ b/src/interfaces/libpq/fe-secure.c
@@ -253,18 +253,20 @@ 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;
-
-#ifdef ECONNRESET
-			case ECONNRESET:
+#ifdef SOCK_EPIPE
+			case SOCK_EPIPE:
+#endif
+#ifdef SOCK_ECONNRESET
+			case SOCK_ECONNRESET:
 				printfPQExpBuffer(&conn->errorMessage,
 								  libpq_gettext("server closed the connection unexpectedly\n"
 												"\tThis probably means the server terminated abnormally\n"
@@ -367,31 +369,31 @@ 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:
+#if defined(SOCK_EPIPE) || defined(SOCK_ECONNRESET)
+#ifdef SOCK_EPIPE
+			case SOCK_EPIPE:
 				/* Set flag for EPIPE */
 				REMEMBER_EPIPE(spinfo, true);
-
-#ifdef ECONNRESET
+#endif
+#ifdef SOCK_ECONNRESET
 				/* FALL THRU */
-
-			case ECONNRESET:
+			case SOCK_ECONNRESET:
 #endif
 				printfPQExpBuffer(&conn->errorMessage,
 								  libpq_gettext("server closed the connection unexpectedly\n"
 												"\tThis probably means the server terminated abnormally\n"
 												"\tbefore or while processing the request.\n"));
 				break;
-
+#endif
 			default:
 				printfPQExpBuffer(&conn->errorMessage,
 								  libpq_gettext("could not send data to server: %s\n"),
diff --git a/src/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index ddc2f12c283..dcf346ca5e7 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -1139,7 +1139,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
@@ -1162,10 +1163,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 
@@ -2458,6 +2457,236 @@ 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, v, c
+   ->  Sort
+         Sort Key: p, v, c
+         ->  Index Scan using btg_p_v_idx on btg
+(5 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
+   ->  Sort
+         Sort Key: p, v, c
+         ->  Index Scan using btg_p_v_idx on btg
+(5 rows)
+
+EXPLAIN (COSTS off)
+SELECT count(*) FROM btg GROUP BY v, c, p, d;
+                   QUERY PLAN                    
+-------------------------------------------------
+ GroupAggregate
+   Group Key: p, v, c, d
+   ->  Sort
+         Sort Key: p, v, c, d
+         ->  Index Scan using btg_p_v_idx on btg
+(5 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
+   ->  Sort
+         Sort Key: p, v, c, d
+         ->  Index Scan using btg_p_v_idx on btg
+(5 rows)
+
+RESET enable_hashagg;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
 -- test coverage for aggregate combine/serial/deserial functions
 BEGIN ISOLATION LEVEL REPEATABLE READ;
 SET parallel_setup_cost = 0;
diff --git a/src/test/regress/expected/create_index.out b/src/test/regress/expected/create_index.out
index 65713c2c144..c760b2c65dd 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -1837,18 +1837,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);
@@ -1990,6 +1984,7 @@ explain (costs off)
 -- Check matching of boolean index columns to WHERE conditions and sort keys
 --
 create temp table boolindex (b bool, i int, unique(b, i), junk float);
+set enable_seqscan=off;
 explain (costs off)
   select * from boolindex order by b, i limit 10;
                       QUERY PLAN                       
@@ -2043,6 +2038,7 @@ explain (costs off)
          Index Cond: (b = false)
 (3 rows)
 
+reset enable_seqscan;
 --
 -- REINDEX (VERBOSE)
 --
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 86ca1381fc2..6343adddcfc 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -220,6 +220,7 @@ CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a;
 ERROR:  unrecognized parameter "Fillfactor"
 CREATE UNLOGGED TABLE unlogged1 (a int primary key);			-- OK
 CREATE TEMPORARY TABLE unlogged2 (a int primary key);			-- OK
+set standard_conforming_strings=on;
 SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname;
     relname     | relkind | relpersistence 
 ----------------+---------+----------------
@@ -240,6 +241,7 @@ SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged
  unlogged2_pkey | i       | t
 (4 rows)
 
+reset standard_conforming_strings;
 DROP TABLE unlogged2;
 INSERT INTO unlogged1 VALUES (42);
 CREATE UNLOGGED TABLE public.unlogged2 (a int primary key);		-- also OK
diff --git a/src/test/regress/expected/equivclass.out b/src/test/regress/expected/equivclass.out
index 126f7047fed..3a9d986c20a 100644
--- a/src/test/regress/expected/equivclass.out
+++ b/src/test/regress/expected/equivclass.out
@@ -285,6 +285,7 @@ explain (costs off)
 -- let's try that as a mergejoin
 set enable_mergejoin = on;
 set enable_nestloop = off;
+/*
 explain (costs off)
   select * from ec1,
     (select ff + 1 as x from
@@ -300,29 +301,7 @@ explain (costs off)
      union all
      select ff + 4 as x from ec1) as ss2
   where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
-                           QUERY PLAN                            
------------------------------------------------------------------
- Merge Join
-   Merge Cond: ((((ec1_4.ff + 2) + 1)) = (((ec1_1.ff + 2) + 1)))
-   ->  Merge Append
-         Sort Key: (((ec1_4.ff + 2) + 1))
-         ->  Index Scan using ec1_expr2 on ec1 ec1_4
-         ->  Index Scan using ec1_expr3 on ec1 ec1_5
-         ->  Index Scan using ec1_expr4 on ec1 ec1_6
-   ->  Materialize
-         ->  Merge Join
-               Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
-               ->  Merge Append
-                     Sort Key: (((ec1_1.ff + 2) + 1))
-                     ->  Index Scan using ec1_expr2 on ec1 ec1_1
-                     ->  Index Scan using ec1_expr3 on ec1 ec1_2
-                     ->  Index Scan using ec1_expr4 on ec1 ec1_3
-               ->  Sort
-                     Sort Key: ec1.f1 USING <
-                     ->  Index Scan using ec1_pkey on ec1
-                           Index Cond: (ff = '42'::bigint)
-(19 rows)
-
+*/
 -- check partially indexed scan
 set enable_nestloop = on;
 set enable_mergejoin = off;
@@ -353,6 +332,7 @@ explain (costs off)
 -- let's try that as a mergejoin
 set enable_mergejoin = on;
 set enable_nestloop = off;
+/*
 explain (costs off)
   select * from ec1,
     (select ff + 1 as x from
@@ -362,23 +342,7 @@ explain (costs off)
      union all
      select ff + 4 as x from ec1) as ss1
   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
-                     QUERY PLAN                      
------------------------------------------------------
- Merge Join
-   Merge Cond: ((((ec1_1.ff + 2) + 1)) = ec1.f1)
-   ->  Merge Append
-         Sort Key: (((ec1_1.ff + 2) + 1))
-         ->  Index Scan using ec1_expr2 on ec1 ec1_1
-         ->  Sort
-               Sort Key: (((ec1_2.ff + 3) + 1))
-               ->  Seq Scan on ec1 ec1_2
-         ->  Index Scan using ec1_expr4 on ec1 ec1_3
-   ->  Sort
-         Sort Key: ec1.f1 USING <
-         ->  Index Scan using ec1_pkey on ec1
-               Index Cond: (ff = '42'::bigint)
-(13 rows)
-
+*/
 -- check effects of row-level security
 set enable_nestloop = on;
 set enable_mergejoin = off;
@@ -439,6 +403,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/explain.out b/src/test/regress/expected/explain.out
index dc7ab2ce8bf..4edc21cd468 100644
--- a/src/test/regress/expected/explain.out
+++ b/src/test/regress/expected/explain.out
@@ -8,6 +8,7 @@
 -- To produce stable regression test output, it's usually necessary to
 -- ignore details such as exact costs or row counts.  These filter
 -- functions replace changeable output details with fixed strings.
+set standard_conforming_strings=on;
 create function explain_filter(text) returns setof text
 language plpgsql as
 $$
@@ -472,3 +473,4 @@ select jsonb_pretty(
 (1 row)
 
 rollback;
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 811f80a0976..49741cc8c67 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -775,4 +775,4 @@ 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
diff --git a/src/test/regress/expected/incremental_sort.out b/src/test/regress/expected/incremental_sort.out
index d9d08b78805..541675fc748 100644
--- a/src/test/regress/expected/incremental_sort.out
+++ b/src/test/regress/expected/incremental_sort.out
@@ -1479,9 +1479,8 @@ set enable_hashagg to off;
 explain (costs off) select * from t union select * from t order by 1,3;
                         QUERY PLAN                        
 ----------------------------------------------------------
- Incremental Sort
+ Sort
    Sort Key: t.a, t.c
-   Presorted Key: t.a
    ->  Unique
          ->  Sort
                Sort Key: t.a, t.b, t.c
@@ -1492,7 +1491,7 @@ explain (costs off) select * from t union select * from t order by 1,3;
                      ->  Gather
                            Workers Planned: 2
                            ->  Parallel Seq Scan on t t_1
-(13 rows)
+(12 rows)
 
 -- Full sort, not just incremental sort can be pushed below a gather merge path
 -- by generate_useful_gather_paths.
diff --git a/src/test/regress/expected/index_including.out b/src/test/regress/expected/index_including.out
index 4aacf355821..744b38c8e41 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 2f4d2b20ada..5cd29b99e12 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1932,8 +1932,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;
@@ -1967,8 +1967,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
@@ -4422,15 +4422,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,
@@ -4569,18 +4570,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
@@ -4873,6 +4876,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
 --
@@ -5281,15 +5504,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)
 
@@ -5298,15 +5521,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)
 
@@ -5333,24 +5556,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)
@@ -5677,15 +5900,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
@@ -6286,44 +6509,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;
@@ -6486,10 +6704,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/json.out b/src/test/regress/expected/json.out
index c4156cf2a66..1af2aab8b0b 100644
--- a/src/test/regress/expected/json.out
+++ b/src/test/regress/expected/json.out
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
 -- Strings.
 SELECT '""'::json;				-- OK.
  json 
@@ -2601,3 +2602,4 @@ select ts_headline('[]'::json, tsquery('aaa & bbb'));
  []
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/json_encoding.out b/src/test/regress/expected/json_encoding.out
index fa41b401030..49c36df4141 100644
--- a/src/test/regress/expected/json_encoding.out
+++ b/src/test/regress/expected/json_encoding.out
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for json and jsonb
 --
+set standard_conforming_strings=on;
 -- We provide expected-results files for UTF8 (json_encoding.out)
 -- and for SQL_ASCII (json_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
@@ -260,3 +261,4 @@ SELECT jsonb '{ "a":  "null \\u0000 escape" }' ->> 'a' as not_an_escape;
  null \u0000 escape
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/json_encoding_1.out b/src/test/regress/expected/json_encoding_1.out
index 938f8e24aaf..e8ff74e1e26 100644
--- a/src/test/regress/expected/json_encoding_1.out
+++ b/src/test/regress/expected/json_encoding_1.out
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for json and jsonb
 --
+set standard_conforming_strings=on;
 -- We provide expected-results files for UTF8 (json_encoding.out)
 -- and for SQL_ASCII (json_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
@@ -244,3 +245,4 @@ SELECT jsonb '{ "a":  "null \\u0000 escape" }' ->> 'a' as not_an_escape;
  null \u0000 escape
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/json_encoding_2.out b/src/test/regress/expected/json_encoding_2.out
index 4fc8f0241ab..4ce4fd09668 100644
--- a/src/test/regress/expected/json_encoding_2.out
+++ b/src/test/regress/expected/json_encoding_2.out
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for json and jsonb
 --
+set standard_conforming_strings=on;
 -- We provide expected-results files for UTF8 (json_encoding.out)
 -- and for SQL_ASCII (json_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
diff --git a/src/test/regress/expected/jsonb.out b/src/test/regress/expected/jsonb.out
index edb520815c5..3e17746510d 100644
--- a/src/test/regress/expected/jsonb.out
+++ b/src/test/regress/expected/jsonb.out
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
 -- Strings.
 SELECT '""'::jsonb;				-- OK.
  jsonb 
@@ -5053,3 +5054,4 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
  12345
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/jsonb_jsonpath.out b/src/test/regress/expected/jsonb_jsonpath.out
index 6659bc9091a..da2c8bf2357 100644
--- a/src/test/regress/expected/jsonb_jsonpath.out
+++ b/src/test/regress/expected/jsonb_jsonpath.out
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
 select jsonb '{"a": 12}' @? '$';
  ?column? 
 ----------
@@ -2584,3 +2585,4 @@ ORDER BY s1.num, s2.num;
  {"s": "B"}    | {"s": "B"}    | false | true  | true  | true  | false
 (144 rows)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/jsonpath.out b/src/test/regress/expected/jsonpath.out
index 6dab98d03a9..9b718eee0dc 100644
--- a/src/test/regress/expected/jsonpath.out
+++ b/src/test/regress/expected/jsonpath.out
@@ -1,4 +1,5 @@
 --jsonpath io
+set standard_conforming_strings=on;
 select ''::jsonpath;
 ERROR:  invalid input syntax for type jsonpath: ""
 LINE 1: select ''::jsonpath;
@@ -962,3 +963,4 @@ select '(1.).e3'::jsonpath;
 ERROR:  syntax error at or near ")" of jsonpath input
 LINE 1: select '(1.).e3'::jsonpath;
                ^
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/jsonpath_encoding.out b/src/test/regress/expected/jsonpath_encoding.out
index 7cbfb6abcf3..412bb8c5422 100644
--- a/src/test/regress/expected/jsonpath_encoding.out
+++ b/src/test/regress/expected/jsonpath_encoding.out
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for jsonpath
 --
+set standard_conforming_strings=on;
 -- We provide expected-results files for UTF8 (jsonpath_encoding.out)
 -- and for SQL_ASCII (jsonpath_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
@@ -178,3 +179,4 @@ select '$."null \\u0000 escape"'::jsonpath as not_an_escape;
  $."null \\u0000 escape"
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/jsonpath_encoding_1.out b/src/test/regress/expected/jsonpath_encoding_1.out
index 005136c9657..a1a49a0ebd1 100644
--- a/src/test/regress/expected/jsonpath_encoding_1.out
+++ b/src/test/regress/expected/jsonpath_encoding_1.out
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for jsonpath
 --
+set standard_conforming_strings=on;
 -- We provide expected-results files for UTF8 (jsonpath_encoding.out)
 -- and for SQL_ASCII (jsonpath_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
@@ -166,3 +167,4 @@ select '$."null \\u0000 escape"'::jsonpath as not_an_escape;
  $."null \\u0000 escape"
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/jsonpath_encoding_2.out b/src/test/regress/expected/jsonpath_encoding_2.out
index bb71bfe72c4..87fda7a489f 100644
--- a/src/test/regress/expected/jsonpath_encoding_2.out
+++ b/src/test/regress/expected/jsonpath_encoding_2.out
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for jsonpath
 --
+set standard_conforming_strings=on;
 -- We provide expected-results files for UTF8 (jsonpath_encoding.out)
 -- and for SQL_ASCII (jsonpath_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index cecd29cea2a..8d2a15c917a 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -1375,6 +1375,7 @@ SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000');
 (1 row)
 
 -- Check parsing of literal text in a format string
+set standard_conforming_strings=on;
 SELECT '' AS to_char_27, to_char('100'::numeric, 'foo999');
  to_char_27 | to_char 
 ------------+---------
@@ -1435,6 +1436,7 @@ SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999');
             | fool\ 100
 (1 row)
 
+reset standard_conforming_strings;
 -- Test scientific notation with various exponents
 WITH v(exp) AS
   (VALUES(-16379),(-16378),(-1234),(-789),(-45),(-5),(-4),(-3),(-2),(-1),(0),
diff --git a/src/test/regress/expected/partition_aggregate.out b/src/test/regress/expected/partition_aggregate.out
index 45c698daf48..9237a54a3bf 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))
@@ -952,32 +952,30 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA
 --------------------------------------------------------------------------------------
  Sort
    Sort Key: pagg_tab_ml.a, (sum(pagg_tab_ml.b)), (array_agg(DISTINCT pagg_tab_ml.c))
-   ->  Gather
-         Workers Planned: 2
-         ->  Parallel Append
-               ->  GroupAggregate
-                     Group Key: pagg_tab_ml.a
-                     Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
-                     ->  Sort
-                           Sort Key: pagg_tab_ml.a
-                           ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
-               ->  GroupAggregate
-                     Group Key: pagg_tab_ml_5.a
-                     Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
-                     ->  Sort
-                           Sort Key: pagg_tab_ml_5.a
-                           ->  Append
-                                 ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
-                                 ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
-               ->  GroupAggregate
-                     Group Key: pagg_tab_ml_2.a
-                     Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
-                     ->  Sort
-                           Sort Key: pagg_tab_ml_2.a
-                           ->  Append
-                                 ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
-                                 ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
-(27 rows)
+   ->  Append
+         ->  GroupAggregate
+               Group Key: pagg_tab_ml.a
+               Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
+               ->  Sort
+                     Sort Key: pagg_tab_ml.a
+                     ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+         ->  GroupAggregate
+               Group Key: pagg_tab_ml_2.a
+               Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
+               ->  Sort
+                     Sort Key: pagg_tab_ml_2.a
+                     ->  Append
+                           ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+                           ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+         ->  GroupAggregate
+               Group Key: pagg_tab_ml_5.a
+               Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
+               ->  Sort
+                     Sort Key: pagg_tab_ml_5.a
+                     ->  Append
+                           ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+                           ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+(25 rows)
 
 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;
  a  | sum  |  array_agg  | count 
@@ -996,34 +994,32 @@ SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HA
 -- Without ORDER BY clause, to test Gather at top-most path
 EXPLAIN (COSTS OFF)
 SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3;
-                                QUERY PLAN                                 
----------------------------------------------------------------------------
- Gather
-   Workers Planned: 2
-   ->  Parallel Append
-         ->  GroupAggregate
-               Group Key: pagg_tab_ml.a
-               Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
-               ->  Sort
-                     Sort Key: pagg_tab_ml.a
-                     ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
-         ->  GroupAggregate
-               Group Key: pagg_tab_ml_5.a
-               Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
-               ->  Sort
-                     Sort Key: pagg_tab_ml_5.a
-                     ->  Append
-                           ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
-                           ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
-         ->  GroupAggregate
-               Group Key: pagg_tab_ml_2.a
-               Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
-               ->  Sort
-                     Sort Key: pagg_tab_ml_2.a
-                     ->  Append
-                           ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
-                           ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
-(25 rows)
+                             QUERY PLAN                              
+---------------------------------------------------------------------
+ Append
+   ->  GroupAggregate
+         Group Key: pagg_tab_ml.a
+         Filter: (avg(pagg_tab_ml.b) < '3'::numeric)
+         ->  Sort
+               Sort Key: pagg_tab_ml.a
+               ->  Seq Scan on pagg_tab_ml_p1 pagg_tab_ml
+   ->  GroupAggregate
+         Group Key: pagg_tab_ml_2.a
+         Filter: (avg(pagg_tab_ml_2.b) < '3'::numeric)
+         ->  Sort
+               Sort Key: pagg_tab_ml_2.a
+               ->  Append
+                     ->  Seq Scan on pagg_tab_ml_p2_s1 pagg_tab_ml_2
+                     ->  Seq Scan on pagg_tab_ml_p2_s2 pagg_tab_ml_3
+   ->  GroupAggregate
+         Group Key: pagg_tab_ml_5.a
+         Filter: (avg(pagg_tab_ml_5.b) < '3'::numeric)
+         ->  Sort
+               Sort Key: pagg_tab_ml_5.a
+               ->  Append
+                     ->  Seq Scan on pagg_tab_ml_p3_s1 pagg_tab_ml_5
+                     ->  Seq Scan on pagg_tab_ml_p3_s2 pagg_tab_ml_6
+(23 rows)
 
 -- Full aggregation at level 1 as GROUP BY clause matches with PARTITION KEY
 -- for level 1 only. For subpartitions, GROUP BY clause does not match with
@@ -1379,28 +1375,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 7539e42e02c..dd3f2ee6ae8 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 
@@ -1432,7 +1416,7 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM plt1 t1, pl
                                    QUERY PLAN                                   
 --------------------------------------------------------------------------------
  GroupAggregate
-   Group Key: t1.c, t2.c, t3.c
+   Group Key: t1.c, t3.c, t2.c
    ->  Sort
          Sort Key: t1.c, t3.c
          ->  Append
@@ -1576,7 +1560,7 @@ SELECT avg(t1.a), avg(t2.b), avg(t3.a + t3.b), t1.c, t2.c, t3.c FROM pht1 t1, ph
                                    QUERY PLAN                                   
 --------------------------------------------------------------------------------
  GroupAggregate
-   Group Key: t1.c, t2.c, t3.c
+   Group Key: t1.c, t3.c, t2.c
    ->  Sort
          Sort Key: t1.c, t3.c
          ->  Append
@@ -2173,29 +2157,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/partition_prune.out b/src/test/regress/expected/partition_prune.out
index 1f31dd4cf24..083fb3d389d 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2840,9 +2840,9 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed)
                      Recheck Cond: (a = 1)
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=0 loops=1)
+                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
                            Index Cond: (a = 1)
          ->  Materialize (actual rows=0 loops=1)
                ->  Bitmap Heap Scan on ab_a1_b1 ab_a1_1 (actual rows=0 loops=1)
@@ -2882,10 +2882,9 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b2_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (actual rows=0 loops=1)
+               ->  Bitmap Heap Scan on ab_a1_b3 ab_3 (never executed)
                      Recheck Cond: (a = 1)
-                     Heap Blocks: exact=1
-                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
+                     ->  Bitmap Index Scan on ab_a1_b3_a_idx (never executed)
                            Index Cond: (a = 1)
          ->  Materialize (actual rows=0 loops=1)
                ->  Bitmap Heap Scan on ab_a1_b3 ab_a1_3 (actual rows=0 loops=1)
@@ -2893,7 +2892,7 @@ update ab_a1 set b = 3 from ab where ab.a = 1 and ab.a = ab_a1.a;
                      Heap Blocks: exact=1
                      ->  Bitmap Index Scan on ab_a1_b3_a_idx (actual rows=1 loops=1)
                            Index Cond: (a = 1)
-(68 rows)
+(67 rows)
 
 table ab;
  a | b 
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index 2b852aa324d..b2b29331ab1 100644
--- a/src/test/regress/expected/password.out
+++ b/src/test/regress/expected/password.out
@@ -8,6 +8,7 @@ HINT:  Available values: md5, scram-sha-256.
 SET password_encryption = true; -- ok
 SET password_encryption = 'md5'; -- ok
 SET password_encryption = 'scram-sha-256'; -- ok
+set standard_conforming_strings=on;
 -- consistency of password entries
 SET password_encryption = 'md5';
 CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
@@ -141,3 +142,4 @@ SELECT rolname, rolpassword
 ---------+-------------
 (0 rows)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/plpgsql.out b/src/test/regress/expected/plpgsql.out
index feee22be770..c45c5650703 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4473,6 +4473,7 @@ CONTEXT:  SQL statement "SELECT 1/0"
 PL/pgSQL function fail() line 3 at RETURN
 drop function fail();
 -- Test handling of string literals.
+set escape_string_warning=on;
 set standard_conforming_strings = off;
 create or replace function strtest() returns text as $$
 begin
@@ -4545,6 +4546,7 @@ NOTICE:  foo\bar!baz
 (1 row)
 
 drop function strtest();
+reset escape_string_warning;
 -- Test anonymous code blocks.
 DO $$
 DECLARE r record;
diff --git a/src/test/regress/expected/psql.out b/src/test/regress/expected/psql.out
index 8c893b14c91..3a360aed4e7 100644
--- a/src/test/regress/expected/psql.out
+++ b/src/test/regress/expected/psql.out
@@ -2,6 +2,7 @@
 -- Tests for psql features that aren't closely connected to any
 -- specific server features
 --
+set standard_conforming_strings=on;
 -- \set
 -- fail: invalid name
 \set invalid/name foo
@@ -5029,3 +5030,4 @@ List of access methods
  hash  | uuid_ops        | uuid                 | uuid                  |      2 | uuid_hash_extended
 (5 rows)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index c421f5394fe..a81c747015f 100644
--- a/src/test/regress/expected/rangetypes.out
+++ b/src/test/regress/expected/rangetypes.out
@@ -120,6 +120,7 @@ select '[",",","]'::textrange;
  [",",","]
 (1 row)
 
+set standard_conforming_strings=on;
 select '["\\","\\"]'::textrange;
   textrange  
 -------------
@@ -132,6 +133,7 @@ select '(\\,a)'::textrange;
  ("\\",a)
 (1 row)
 
+reset standard_conforming_strings;
 select '((,z)'::textrange;
  textrange 
 -----------
diff --git a/src/test/regress/expected/select.out b/src/test/regress/expected/select.out
index 6dcd4fa8f32..926b989855f 100644
--- a/src/test/regress/expected/select.out
+++ b/src/test/regress/expected/select.out
@@ -524,6 +524,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/select_parallel.out b/src/test/regress/expected/select_parallel.out
index ac32a1bc9be..5597bdf16aa 100644
--- a/src/test/regress/expected/select_parallel.out
+++ b/src/test/regress/expected/select_parallel.out
@@ -564,6 +564,7 @@ explain (analyze, timing off, summary off, costs off)
 
 alter table tenk2 reset (parallel_workers);
 reset work_mem;
+set standard_conforming_strings=on;
 create function explain_parallel_sort_stats() returns setof text
 language plpgsql as
 $$
@@ -599,6 +600,7 @@ select * from explain_parallel_sort_stats();
                      Filter: (ten < 100)
 (14 rows)
 
+reset standard_conforming_strings;
 reset enable_indexscan;
 reset enable_hashjoin;
 reset enable_mergejoin;
diff --git a/src/test/regress/expected/stats_ext.out b/src/test/regress/expected/stats_ext.out
index f58dd031f4e..6c8dc446773 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -520,45 +520,40 @@ 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
-(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
+       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)');
 -- OR clauses referencing the same attribute
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1''');
  estimated | actual 
@@ -589,39 +584,34 @@ 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
-(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
+       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])'); 
 -- ANY with inequalities should not benefit from functional dependencies
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1''');
  estimated | actual 
@@ -719,12 +709,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
        200 |    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 
------------+--------
-       400 |    400
-(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)');
 -- OR clauses referencing the same attribute
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1''');
  estimated | actual 
@@ -782,12 +767,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
        200 |    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 
------------+--------
-       400 |    400
-(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])'); 
 -- ANY with inequalities should not benefit from functional dependencies
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1''');
  estimated | actual 
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index bc2c09fc7d1..1cff59d4fdc 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -534,12 +534,14 @@ SELECT 'abcd%' SIMILAR TO '_bcd#%' ESCAPE '#' AS true;
 (1 row)
 
 -- Postgres uses '\' as the default escape character, which is not per spec
+set standard_conforming_strings=on;
 SELECT 'abcdefg' SIMILAR TO '_bcd\%' AS false;
  false 
 -------
  f
 (1 row)
 
+reset standard_conforming_strings;
 -- and an empty string to mean "no escape", which is also not per spec
 SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true;
  true 
@@ -1811,6 +1813,7 @@ SELECT sha512('The quick brown fox jumps over the lazy dog.');
 --
 -- encode/decode
 --
+set standard_conforming_strings=on;
 SELECT encode('\x1234567890abcdef00', 'hex');
        encode       
 --------------------
@@ -1884,6 +1887,7 @@ SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11);
 
 SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11);  -- error
 ERROR:  index 99 out of valid range, 0..8
+reset standard_conforming_strings;
 --
 -- test behavior of escape_string_warning and standard_conforming_strings options
 --
diff --git a/src/test/regress/expected/subselect.out b/src/test/regress/expected/subselect.out
index b81923f2e74..f3900e1fc4c 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1437,6 +1437,7 @@ insert into sq_limit values
     (6, 2, 2),
     (7, 3, 3),
     (8, 4, 4);
+set standard_conforming_strings=on;
 create function explain_sq_limit() returns setof text language plpgsql as
 $$
 declare ln text;
@@ -1470,6 +1471,7 @@ select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
 (3 rows)
 
 drop function explain_sq_limit();
+reset standard_conforming_strings;
 drop table sq_limit;
 --
 -- Ensure that backward scan direction isn't propagated into
diff --git a/src/test/regress/expected/tsearch.out b/src/test/regress/expected/tsearch.out
index 73f2d13297c..48bd502bd42 100644
--- a/src/test/regress/expected/tsearch.out
+++ b/src/test/regress/expected/tsearch.out
@@ -5,6 +5,7 @@
 -- that is OID or REGPROC fields that are not zero and do not match some
 -- row in the linked-to table.  However, if we want to enforce that a link
 -- field can't be 0, we have to check it here.
+set standard_conforming_strings=on;
 -- Find unexpected zero link entries
 SELECT oid, prsname
 FROM pg_ts_parser
@@ -2869,3 +2870,4 @@ NOTICE:  text-search query contains only stop words or doesn't contain lexemes,
  
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/type_sanity.out b/src/test/regress/expected/type_sanity.out
index 9d4d8b4f05a..8d3fc461478 100644
--- a/src/test/regress/expected/type_sanity.out
+++ b/src/test/regress/expected/type_sanity.out
@@ -111,9 +111,11 @@ WHERE p1.typtype = 'r' AND
 SELECT p1.oid, p1.typname
 FROM pg_type as p1
 WHERE (p1.typinput = 0 OR p1.typoutput = 0);
- oid | typname 
------+---------
-(0 rows)
+  oid  | typname 
+-------+---------
+ 14756 | abstime
+ 14757 | reltime
+(2 rows)
 
 -- Check for bogus typinput routines
 SELECT p1.oid, p1.typname, p2.oid, p2.proname
diff --git a/src/test/regress/expected/unicode.out b/src/test/regress/expected/unicode.out
index f2713a23268..3b99b5d17a9 100644
--- a/src/test/regress/expected/unicode.out
+++ b/src/test/regress/expected/unicode.out
@@ -2,6 +2,7 @@ SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
 \if :skip_test
 \quit
 \endif
+set standard_conforming_strings=on;
 SELECT U&'\0061\0308bc' <> U&'\00E4bc' COLLATE "C" AS sanity_check;
  sanity_check 
 --------------
@@ -87,3 +88,4 @@ ORDER BY num;
 
 SELECT is_normalized('abc', 'def');  -- run-time error
 ERROR:  invalid normalization form: def
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/union.out b/src/test/regress/expected/union.out
index 6e72e92d801..17629ac4de4 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -923,24 +923,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
@@ -959,24 +957,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 30ee176306a..b2f1d1c6c33 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2274,16 +2274,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/expected/xml.out b/src/test/regress/expected/xml.out
index fcf5d0f4aa2..32270cdcad9 100644
--- a/src/test/regress/expected/xml.out
+++ b/src/test/regress/expected/xml.out
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
 CREATE TABLE xmltest (
     id int,
     data xml
@@ -1417,3 +1418,4 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH
  <foo/> | &lt;foo/&gt;
 (1 row)
 
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/xml_1.out b/src/test/regress/expected/xml_1.out
index fcf5d0f4aa2..32270cdcad9 100644
--- a/src/test/regress/expected/xml_1.out
+++ b/src/test/regress/expected/xml_1.out
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
 CREATE TABLE xmltest (
     id int,
     data xml
@@ -1417,3 +1418,4 @@ SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH
 ERROR:  unsupported XML feature
 DETAIL:  This functionality requires the server to be built with libxml support.
 HINT:  You need to rebuild PostgreSQL using --with-libxml.
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index c0ef2b66b12..572bd19d379 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -1036,6 +1036,102 @@ 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;
+
+RESET enable_hashagg;
+RESET max_parallel_workers;
+RESET max_parallel_workers_per_gather;
+RESET enable_seqscan;
+RESET enable_bitmapscan;
+
 -- test coverage for aggregate combine/serial/deserial functions
 BEGIN ISOLATION LEVEL REPEATABLE READ;
 
diff --git a/src/test/regress/sql/create_index.sql b/src/test/regress/sql/create_index.sql
index a5eaef442f8..79a3a477d95 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -783,6 +783,7 @@ explain (costs off)
 
 create temp table boolindex (b bool, i int, unique(b, i), junk float);
 
+set enable_seqscan=off;
 explain (costs off)
   select * from boolindex order by b, i limit 10;
 explain (costs off)
@@ -795,6 +796,7 @@ explain (costs off)
   select * from boolindex where b is true order by i desc limit 10;
 explain (costs off)
   select * from boolindex where b is false order by i desc limit 10;
+reset enable_seqscan;
 
 --
 -- REINDEX (VERBOSE)
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index ad55a24a7bf..cdf8b628eb5 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -258,10 +258,12 @@ CREATE TABLE tas_case WITH ("Fillfactor" = 10) AS SELECT 1 a;
 
 CREATE UNLOGGED TABLE unlogged1 (a int primary key);			-- OK
 CREATE TEMPORARY TABLE unlogged2 (a int primary key);			-- OK
+set standard_conforming_strings=on;
 SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname;
 REINDEX INDEX unlogged1_pkey;
 REINDEX INDEX unlogged2_pkey;
 SELECT relname, relkind, relpersistence FROM pg_class WHERE relname ~ '^unlogged\d' ORDER BY relname;
+reset standard_conforming_strings;
 DROP TABLE unlogged2;
 INSERT INTO unlogged1 VALUES (42);
 CREATE UNLOGGED TABLE public.unlogged2 (a int primary key);		-- also OK
diff --git a/src/test/regress/sql/equivclass.sql b/src/test/regress/sql/equivclass.sql
index 247b0a31055..a76483ffeb8 100644
--- a/src/test/regress/sql/equivclass.sql
+++ b/src/test/regress/sql/equivclass.sql
@@ -177,6 +177,7 @@ explain (costs off)
 set enable_mergejoin = on;
 set enable_nestloop = off;
 
+/*
 explain (costs off)
   select * from ec1,
     (select ff + 1 as x from
@@ -192,6 +193,7 @@ explain (costs off)
      union all
      select ff + 4 as x from ec1) as ss2
   where ss1.x = ec1.f1 and ss1.x = ss2.x and ec1.ff = 42::int8;
+*/
 
 -- check partially indexed scan
 set enable_nestloop = on;
@@ -213,6 +215,7 @@ explain (costs off)
 set enable_mergejoin = on;
 set enable_nestloop = off;
 
+/*
 explain (costs off)
   select * from ec1,
     (select ff + 1 as x from
@@ -222,6 +225,7 @@ explain (costs off)
      union all
      select ff + 4 as x from ec1) as ss1
   where ss1.x = ec1.f1 and ec1.ff = 42::int8;
+*/
 
 -- check effects of row-level security
 set enable_nestloop = on;
@@ -263,6 +267,21 @@ 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/explain.sql b/src/test/regress/sql/explain.sql
index c79116c927b..c3cc10bca1b 100644
--- a/src/test/regress/sql/explain.sql
+++ b/src/test/regress/sql/explain.sql
@@ -10,6 +10,8 @@
 -- ignore details such as exact costs or row counts.  These filter
 -- functions replace changeable output details with fixed strings.
 
+set standard_conforming_strings=on;
+
 create function explain_filter(text) returns setof text
 language plpgsql as
 $$
@@ -105,3 +107,5 @@ select jsonb_pretty(
 );
 
 rollback;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 35651f86e86..33e418ca5db 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1755,6 +1755,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/json.sql b/src/test/regress/sql/json.sql
index 20354f04e37..57066990184 100644
--- a/src/test/regress/sql/json.sql
+++ b/src/test/regress/sql/json.sql
@@ -1,3 +1,5 @@
+set standard_conforming_strings=on;
+
 -- Strings.
 SELECT '""'::json;				-- OK.
 SELECT $$''$$::json;			-- ERROR, single quotes are not allowed
@@ -833,3 +835,5 @@ select ts_headline('english', '{"a": "aaa bbb", "b": {"c": "ccc ddd fff", "c1":
 select ts_headline('null'::json, tsquery('aaa & bbb'));
 select ts_headline('{}'::json, tsquery('aaa & bbb'));
 select ts_headline('[]'::json, tsquery('aaa & bbb'));
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/json_encoding.sql b/src/test/regress/sql/json_encoding.sql
index d7fac69733d..7ce79f378e3 100644
--- a/src/test/regress/sql/json_encoding.sql
+++ b/src/test/regress/sql/json_encoding.sql
@@ -1,6 +1,7 @@
 --
 -- encoding-sensitive tests for json and jsonb
 --
+set standard_conforming_strings=on;
 
 -- We provide expected-results files for UTF8 (json_encoding.out)
 -- and for SQL_ASCII (json_encoding_1.out).  Skip otherwise.
@@ -76,3 +77,5 @@ SELECT jsonb '{ "a":  "dollar \u0024 character" }' ->> 'a' as correct_everywhere
 SELECT jsonb '{ "a":  "dollar \\u0024 character" }' ->> 'a' as not_an_escape;
 SELECT jsonb '{ "a":  "null \u0000 escape" }' ->> 'a' as fails;
 SELECT jsonb '{ "a":  "null \\u0000 escape" }' ->> 'a' as not_an_escape;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/jsonb.sql b/src/test/regress/sql/jsonb.sql
index 418589611f2..f38e45bfbc8 100644
--- a/src/test/regress/sql/jsonb.sql
+++ b/src/test/regress/sql/jsonb.sql
@@ -1,3 +1,5 @@
+set standard_conforming_strings=on;
+
 -- Strings.
 SELECT '""'::jsonb;				-- OK.
 SELECT $$''$$::jsonb;			-- ERROR, single quotes are not allowed
@@ -1288,3 +1290,5 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::float8;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int2;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int4;
 select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/jsonb_jsonpath.sql b/src/test/regress/sql/jsonb_jsonpath.sql
index e0ce509264a..661e35a7607 100644
--- a/src/test/regress/sql/jsonb_jsonpath.sql
+++ b/src/test/regress/sql/jsonb_jsonpath.sql
@@ -1,3 +1,5 @@
+set standard_conforming_strings=on;
+
 select jsonb '{"a": 12}' @? '$';
 select jsonb '{"a": 12}' @? '1';
 select jsonb '{"a": 12}' @? '$.a.b';
@@ -596,3 +598,5 @@ SELECT
 	jsonb_path_query_first(s1.j, '$.s > $s', vars => s2.j) gt
 FROM str s1, str s2
 ORDER BY s1.num, s2.num;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/jsonpath.sql b/src/test/regress/sql/jsonpath.sql
index 17ab7757831..d948b1b9bb9 100644
--- a/src/test/regress/sql/jsonpath.sql
+++ b/src/test/regress/sql/jsonpath.sql
@@ -1,5 +1,7 @@
 --jsonpath io
 
+set standard_conforming_strings=on;
+
 select ''::jsonpath;
 select '$'::jsonpath;
 select 'strict $'::jsonpath;
@@ -179,3 +181,5 @@ select '1..e'::jsonpath;
 select '1..e3'::jsonpath;
 select '(1.).e'::jsonpath;
 select '(1.).e3'::jsonpath;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/jsonpath_encoding.sql b/src/test/regress/sql/jsonpath_encoding.sql
index 55d9e30b95c..c1fed3d1ea1 100644
--- a/src/test/regress/sql/jsonpath_encoding.sql
+++ b/src/test/regress/sql/jsonpath_encoding.sql
@@ -2,6 +2,8 @@
 -- encoding-sensitive tests for jsonpath
 --
 
+set standard_conforming_strings=on;
+
 -- We provide expected-results files for UTF8 (jsonpath_encoding.out)
 -- and for SQL_ASCII (jsonpath_encoding_1.out).  Skip otherwise.
 SELECT getdatabaseencoding() NOT IN ('UTF8', 'SQL_ASCII')
@@ -57,3 +59,5 @@ select '$."dollar \u0024 character"'::jsonpath as correct_everywhere;
 select '$."dollar \\u0024 character"'::jsonpath as not_an_escape;
 select '$."null \u0000 escape"'::jsonpath as not_unescaped;
 select '$."null \\u0000 escape"'::jsonpath as not_an_escape;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/numeric.sql b/src/test/regress/sql/numeric.sql
index aa3877a0172..3fa865deb2b 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -827,6 +827,7 @@ SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999');
 SELECT to_char('12345678901'::float8, 'FM9999999999D9999900000000000000000');
 
 -- Check parsing of literal text in a format string
+set standard_conforming_strings=on;
 SELECT '' AS to_char_27, to_char('100'::numeric, 'foo999');
 SELECT '' AS to_char_28, to_char('100'::numeric, 'f\oo999');
 SELECT '' AS to_char_29, to_char('100'::numeric, 'f\\oo999');
@@ -837,6 +838,7 @@ SELECT '' AS to_char_33, to_char('100'::numeric, 'f"\ool"999');
 SELECT '' AS to_char_34, to_char('100'::numeric, 'f"\\ool"999');
 SELECT '' AS to_char_35, to_char('100'::numeric, 'f"ool\"999');
 SELECT '' AS to_char_36, to_char('100'::numeric, 'f"ool\\"999');
+reset standard_conforming_strings;
 
 -- Test scientific notation with various exponents
 WITH v(exp) AS
diff --git a/src/test/regress/sql/password.sql b/src/test/regress/sql/password.sql
index 1e7e19eafa8..2905de4da60 100644
--- a/src/test/regress/sql/password.sql
+++ b/src/test/regress/sql/password.sql
@@ -7,7 +7,7 @@ SET password_encryption = 'novalue'; -- error
 SET password_encryption = true; -- ok
 SET password_encryption = 'md5'; -- ok
 SET password_encryption = 'scram-sha-256'; -- ok
-
+set standard_conforming_strings=on;
 -- consistency of password entries
 SET password_encryption = 'md5';
 CREATE ROLE regress_passwd1 PASSWORD 'role_pwd1';
@@ -108,3 +108,5 @@ SELECT rolname, rolpassword
     FROM pg_authid
     WHERE rolname LIKE 'regress_passwd%'
     ORDER BY rolname, rolpassword;
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/plpgsql.sql b/src/test/regress/sql/plpgsql.sql
index 8b7b154b0ef..48982ddffa9 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3687,6 +3687,7 @@ drop function fail();
 
 -- Test handling of string literals.
 
+set escape_string_warning=on;
 set standard_conforming_strings = off;
 
 create or replace function strtest() returns text as $$
@@ -3728,6 +3729,7 @@ $$ language plpgsql;
 select strtest();
 
 drop function strtest();
+reset escape_string_warning;
 
 -- Test anonymous code blocks.
 
diff --git a/src/test/regress/sql/psql.sql b/src/test/regress/sql/psql.sql
index 3eae271ef89..18edaa30015 100644
--- a/src/test/regress/sql/psql.sql
+++ b/src/test/regress/sql/psql.sql
@@ -3,6 +3,8 @@
 -- specific server features
 --
 
+set standard_conforming_strings=on;
+
 -- \set
 
 -- fail: invalid name
@@ -1220,3 +1222,5 @@ drop role regress_partitioning_role;
 \dAo * pg_catalog.jsonb_path_ops
 \dAp+ btree float_ops
 \dAp * pg_catalog.uuid_ops
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 4048b1d1854..ba881bde0b2 100644
--- a/src/test/regress/sql/rangetypes.sql
+++ b/src/test/regress/sql/rangetypes.sql
@@ -30,8 +30,10 @@ select '(,)'::textrange;
 select '[ , ]'::textrange;
 select '["",""]'::textrange;
 select '[",",","]'::textrange;
+set standard_conforming_strings=on;
 select '["\\","\\"]'::textrange;
 select '(\\,a)'::textrange;
+reset standard_conforming_strings;
 select '((,z)'::textrange;
 select '([,z)'::textrange;
 select '(!,()'::textrange;
diff --git a/src/test/regress/sql/select.sql b/src/test/regress/sql/select.sql
index d6f42aa0d41..876e97d14f9 100644
--- a/src/test/regress/sql/select.sql
+++ b/src/test/regress/sql/select.sql
@@ -153,6 +153,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/select_parallel.sql b/src/test/regress/sql/select_parallel.sql
index f4e8c8a1aa3..1619b91adf5 100644
--- a/src/test/regress/sql/select_parallel.sql
+++ b/src/test/regress/sql/select_parallel.sql
@@ -223,6 +223,8 @@ explain (analyze, timing off, summary off, costs off)
 alter table tenk2 reset (parallel_workers);
 
 reset work_mem;
+
+set standard_conforming_strings=on;
 create function explain_parallel_sort_stats() returns setof text
 language plpgsql as
 $$
@@ -240,6 +242,7 @@ begin
 end;
 $$;
 select * from explain_parallel_sort_stats();
+reset standard_conforming_strings;
 
 reset enable_indexscan;
 reset enable_hashjoin;
diff --git a/src/test/regress/sql/stats_ext.sql b/src/test/regress/sql/stats_ext.sql
index f11143d20bc..9ccbd62e964 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -335,7 +335,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 
 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)');
 
-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)');
+--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)');
 
 -- OR clauses referencing the same attribute
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1''');
@@ -358,7 +358,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 
 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])');
 
-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])');
+--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])'); 
 
 -- ANY with inequalities should not benefit from functional dependencies
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1''');
@@ -399,7 +399,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 
 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)');
 
-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)');
+--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)');
 
 -- OR clauses referencing the same attribute
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE (a = 1 OR a = 51) AND b = ''1''');
@@ -422,7 +422,7 @@ SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE
 
 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])');
 
-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])');
+--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])'); 
 
 -- ANY with inequalities should not benefit from functional dependencies
 SELECT * FROM check_estimated_rows('SELECT * FROM functional_dependencies WHERE a < ANY (ARRAY[1, 51]) AND b > ''1''');
diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql
index fbdc1ab2a56..5960fe7ab8a 100644
--- a/src/test/regress/sql/strings.sql
+++ b/src/test/regress/sql/strings.sql
@@ -176,7 +176,9 @@ SELECT 'abcdefg' SIMILAR TO 'bcd%' AS false;
 SELECT 'abcdefg' SIMILAR TO '_bcd#%' ESCAPE '#' AS false;
 SELECT 'abcd%' SIMILAR TO '_bcd#%' ESCAPE '#' AS true;
 -- Postgres uses '\' as the default escape character, which is not per spec
+set standard_conforming_strings=on;
 SELECT 'abcdefg' SIMILAR TO '_bcd\%' AS false;
+reset standard_conforming_strings;
 -- and an empty string to mean "no escape", which is also not per spec
 SELECT 'abcd\efg' SIMILAR TO '_bcd\%' ESCAPE '' AS true;
 -- these behaviors are per spec, though:
@@ -625,6 +627,7 @@ SELECT sha512('The quick brown fox jumps over the lazy dog.');
 --
 -- encode/decode
 --
+set standard_conforming_strings=on;
 SELECT encode('\x1234567890abcdef00', 'hex');
 SELECT decode('1234567890abcdef00', 'hex');
 SELECT encode(('\x' || repeat('1234567890abcdef0001', 7))::bytea, 'base64');
@@ -644,6 +647,7 @@ SELECT get_byte('\x1234567890abcdef00'::bytea, 3);
 SELECT get_byte('\x1234567890abcdef00'::bytea, 99);  -- error
 SELECT set_byte('\x1234567890abcdef00'::bytea, 7, 11);
 SELECT set_byte('\x1234567890abcdef00'::bytea, 99, 11);  -- error
+reset standard_conforming_strings;
 
 --
 -- test behavior of escape_string_warning and standard_conforming_strings options
diff --git a/src/test/regress/sql/subselect.sql b/src/test/regress/sql/subselect.sql
index cce8ebdb3d9..691b3f0f60a 100644
--- a/src/test/regress/sql/subselect.sql
+++ b/src/test/regress/sql/subselect.sql
@@ -768,6 +768,7 @@ insert into sq_limit values
     (7, 3, 3),
     (8, 4, 4);
 
+set standard_conforming_strings=on;
 create function explain_sq_limit() returns setof text language plpgsql as
 $$
 declare ln text;
@@ -787,6 +788,7 @@ select * from explain_sq_limit();
 select * from (select pk,c2 from sq_limit order by c1,pk) as x limit 3;
 
 drop function explain_sq_limit();
+reset standard_conforming_strings;
 
 drop table sq_limit;
 
diff --git a/src/test/regress/sql/tsearch.sql b/src/test/regress/sql/tsearch.sql
index a6088288bc7..988d99b2986 100644
--- a/src/test/regress/sql/tsearch.sql
+++ b/src/test/regress/sql/tsearch.sql
@@ -6,6 +6,8 @@
 -- row in the linked-to table.  However, if we want to enforce that a link
 -- field can't be 0, we have to check it here.
 
+set standard_conforming_strings=on;
+
 -- Find unexpected zero link entries
 
 SELECT oid, prsname
@@ -801,3 +803,5 @@ select websearch_to_tsquery('''');
 select websearch_to_tsquery('''abc''''def''');
 select websearch_to_tsquery('\abc');
 select websearch_to_tsquery('\');
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/unicode.sql b/src/test/regress/sql/unicode.sql
index 63cd523f85f..d7ca106a79c 100644
--- a/src/test/regress/sql/unicode.sql
+++ b/src/test/regress/sql/unicode.sql
@@ -3,6 +3,8 @@ SELECT getdatabaseencoding() <> 'UTF8' AS skip_test \gset
 \quit
 \endif
 
+set standard_conforming_strings=on;
+
 SELECT U&'\0061\0308bc' <> U&'\00E4bc' COLLATE "C" AS sanity_check;
 
 SELECT normalize('');
@@ -32,3 +34,5 @@ FROM
 ORDER BY num;
 
 SELECT is_normalized('abc', 'def');  -- run-time error
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/xml.sql b/src/test/regress/sql/xml.sql
index e908b6c3957..22621ec008f 100644
--- a/src/test/regress/sql/xml.sql
+++ b/src/test/regress/sql/xml.sql
@@ -1,3 +1,5 @@
+set standard_conforming_strings=on;
+
 CREATE TABLE xmltest (
     id int,
     data xml
@@ -619,3 +621,5 @@ SELECT * FROM XMLTABLE('*' PASSING '<e>pre<!--c1--><?pi arg?><![CDATA[&ent1]]><n
 \x
 
 SELECT * FROM XMLTABLE('.' PASSING XMLELEMENT(NAME a) columns a varchar(20) PATH '"<foo/>"', b xml PATH '"<foo/>"');
+
+reset standard_conforming_strings;
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index eda267bdf1b..5a46547423a 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -36,8 +36,8 @@ my $contrib_defines = { 'refint' => 'REFINT_VERBOSE' };
 my @contrib_uselibpq = ('dblink', 'oid2name', 'postgres_fdw', 'vacuumlo');
 my @contrib_uselibpgport   = ('oid2name', 'pg_standby', 'vacuumlo');
 my @contrib_uselibpgcommon = ('oid2name', 'pg_standby', 'vacuumlo');
-my $contrib_extralibs      = undef;
-my $contrib_extraincludes = { 'dblink' => ['src/backend'] };
+my $contrib_extralibs      = {'mchar' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib'], 'dbcopies_decoding' => ['$(ICU46_LIB)\icuin.lib', '$(ICU46_LIB)\icuuc.lib']};
+my $contrib_extraincludes = { 'dblink' => ['src/backend'], 'mchar' => ['$(ICU46_INCLUDE)'], 'dbcopies_decoding' => ['$(ICU46_INCLUDE)', 'contrib/mchar'] };
 my $contrib_extrasource = {
 	'cube' => [ 'contrib/cube/cubescan.l', 'contrib/cube/cubeparse.y' ],
 	'seg'  => [ 'contrib/seg/segscan.l',   'contrib/seg/segparse.y' ],
@@ -68,11 +68,14 @@ my $frontend_extralibs = {
 	'initdb'     => ['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'],
@@ -475,6 +478,7 @@ sub mkvcbuild
 	$pgcrypto->AddLibrary('ws2_32.lib');
 	my $mf = Project::read_file('contrib/pgcrypto/Makefile');
 	GenerateContribSqlFiles('pgcrypto', $mf);
+	GenerateFulleqSql();
 
 	foreach my $subdir ('contrib', 'src/test/modules')
 	{
@@ -1040,6 +1044,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