File 00001-1C-FULL.patch of Package postgresql11
diff --git a/contrib/Makefile b/contrib/Makefile
index 92184ed487..9c931d533e 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 5c6f9d8d14..26a193fd59 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;
@@ -342,6 +343,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;
@@ -377,8 +379,9 @@ explain_ExecutorEnd(QueryDesc *queryDesc)
* often result in duplication.
*/
ereport(LOG,
- (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 0000000000..edb533292c
--- /dev/null
+++ b/contrib/dbcopies_decoding/Makefile
@@ -0,0 +1,33 @@
+MODULE_big = dbcopies_decoding
+OBJS=dbcopies_decoding.o ../mchar/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)
diff --git a/contrib/dbcopies_decoding/dbcopies_decoding.c b/contrib/dbcopies_decoding/dbcopies_decoding.c
new file mode 100644
index 0000000000..9cf766a543
--- /dev/null
+++ b/contrib/dbcopies_decoding/dbcopies_decoding.c
@@ -0,0 +1,899 @@
+#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_atoi(strVal(elem->arg), sizeof(int32), 0);
+ 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 = HeapTupleGetOid(&catlist->members[0]->tuple);
+ 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;
+
+ readTypeOID("mchar", &MCHAROID);
+ readTypeOID("mvarchar", &MVARCHAROID);
+}
+
+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 char* pg_ultostr_zeropad(char *str, int32 value, int32 minwidth)
+{
+ return pg_ltostr_zeropad(str, value, minwidth);
+}
+
+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 == MCHAROID)
+ printMChar(ctx, val);
+ else if (typid == MVARCHAROID)
+ printMVarchar(ctx, val);
+ 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 0000000000..4053ec1935
--- /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 0000000000..367f706651
--- /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/results/simple.out b/contrib/dbcopies_decoding/results/simple.out
new file mode 100644
index 0000000000..4053ec1935
--- /dev/null
+++ b/contrib/dbcopies_decoding/results/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/sql/simple.sql b/contrib/dbcopies_decoding/sql/simple.sql
new file mode 100644
index 0000000000..1e9d2f7232
--- /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 0000000000..78e92b86cb
--- /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 0000000000..4b1dfdc1e6
--- /dev/null
+++ b/contrib/fasttrun/README.fasttrun
@@ -0,0 +1,17 @@
+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 0000000000..ef64fa6400
--- /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 0000000000..5548402460
--- /dev/null
+++ b/contrib/fasttrun/fasttrun--2.0.sql
@@ -0,0 +1,7 @@
+\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 0000000000..f97896ac5c
--- /dev/null
+++ b/contrib/fasttrun/fasttrun--unpackaged--2.0.sql
@@ -0,0 +1,4 @@
+\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 0000000000..ed59cdc890
--- /dev/null
+++ b/contrib/fasttrun/fasttrun.c
@@ -0,0 +1,84 @@
+#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 = heap_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.
+ */
+
+ heap_close(rel, AccessExclusiveLock);
+
+ if ( makeanalyze ) {
+ VacuumParams params;
+ VacuumRelation *rel;
+
+ 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(VACOPT_ANALYZE, list_make1(rel), ¶ms,
+ GetAccessStrategy(BAS_VACUUM), false);
+ }
+
+ PG_RETURN_VOID();
+}
diff --git a/contrib/fasttrun/fasttrun.control b/contrib/fasttrun/fasttrun.control
new file mode 100644
index 0000000000..00271c75a5
--- /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
+
diff --git a/contrib/fasttrun/sql/fasttrun.sql b/contrib/fasttrun/sql/fasttrun.sql
new file mode 100644
index 0000000000..0e3cb6c9be
--- /dev/null
+++ b/contrib/fasttrun/sql/fasttrun.sql
@@ -0,0 +1,48 @@
+CREATE EXTENSION fasttrun;
+
+create table persist ( a int );
+insert into persist values (1);
+select fasttruncate('persist');
+insert into persist values (2);
+select * from persist order by a;
+
+create temp table temp1 (a int);
+insert into temp1 values (1);
+
+BEGIN;
+
+create temp table temp2 (a int);
+insert into temp2 values (1);
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+insert into temp1 (select * from generate_series(1,10000));
+insert into temp2 (select * from generate_series(1,11000));
+
+analyze temp2;
+select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+
+select fasttruncate('temp1');
+select fasttruncate('temp2');
+
+insert into temp1 values (-2);
+insert into temp2 values (-2);
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+COMMIT;
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
+
+select fasttruncate('temp1');
+select fasttruncate('temp2');
+
+select * from temp1 order by a;
+select * from temp2 order by a;
+
+select relname, relpages>0, reltuples>0 from pg_class where relname in ('temp1', 'temp2') order by relname;
diff --git a/contrib/fulleq/Makefile b/contrib/fulleq/Makefile
new file mode 100644
index 0000000000..45c11ab802
--- /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 abstime reltime 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 0000000000..a677c49c41
--- /dev/null
+++ b/contrib/fulleq/README.fulleq
@@ -0,0 +1,3 @@
+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 0000000000..452f859343
--- /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 0000000000..8d759d8221
--- /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 0000000000..689a1ff33c
--- /dev/null
+++ b/contrib/fulleq/fulleq.c
@@ -0,0 +1,102 @@
+#include "postgres.h"
+#include "fmgr.h"
+#include "access/hash.h"
+#include "utils/builtins.h"
+#include "utils/bytea.h"
+#include "utils/int8.h"
+#include "utils/nabstime.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( 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) \
+ ) ); \
+}
+
+
+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 );
diff --git a/contrib/fulleq/fulleq.control b/contrib/fulleq/fulleq.control
new file mode 100644
index 0000000000..c827b9fb4f
--- /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
+
diff --git a/contrib/fulleq/fulleq.sql.in b/contrib/fulleq/fulleq.sql.in
new file mode 100644
index 0000000000..55e980e123
--- /dev/null
+++ b/contrib/fulleq/fulleq.sql.in
@@ -0,0 +1,26 @@
+-- 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 0000000000..e491e27648
--- /dev/null
+++ b/contrib/fulleq/sql/fulleq.sql
@@ -0,0 +1,14 @@
+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 0000000000..b7f6e0c571
--- /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 0000000000..0b8bd3fa65
--- /dev/null
+++ b/contrib/mchar/Makefile
@@ -0,0 +1,28 @@
+MODULE_big = mchar
+OBJS = mchar_io.o mchar_proc.o mchar_op.o mchar_recode.o \
+ mchar_like.o
+EXTENSION=mchar
+DATA = mchar--2.0.1.sql mchar--2.0--2.0.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 0000000000..479a7d1f40
--- /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 0000000000..480a286e8f
--- /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 0000000000..7bae978ec3
--- /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 0000000000..1e9759f65d
--- /dev/null
+++ b/contrib/mchar/expected/like.out
@@ -0,0 +1,813 @@
+set standard_conforming_strings=off;
+-- simplest examples
+-- E061-04 like predicate
+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;
+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;
+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;
+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 0000000000..f6c592fd16
--- /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 0000000000..c5b36c2161
--- /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 0000000000..5c866b43e7
--- /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.0.1.sql b/contrib/mchar/mchar--2.0--2.0.1.sql
new file mode 100644
index 0000000000..a1d951b227
--- /dev/null
+++ b/contrib/mchar/mchar--2.0--2.0.1.sql
@@ -0,0 +1,9 @@
+CREATE FUNCTION mvarchar_support(internal)
+ RETURNS internal
+ AS 'MODULE_PATHNAME'
+ LANGUAGE C IMMUTABLE RETURNS NULL ON NULL INPUT
+ PARALLEL SAFE;
+
+UPDATE pg_proc
+SET protransform = 'mvarchar_support(internal)'::regprocedure
+WHERE oid = 'mvarchar(mvarchar, integer, boolean)'::regprocedure;
diff --git a/contrib/mchar/mchar--2.0.1.sql b/contrib/mchar/mchar--2.0.1.sql
new file mode 100644
index 0000000000..81227aae68
--- /dev/null
+++ b/contrib/mchar/mchar--2.0.1.sql
@@ -0,0 +1,1334 @@
+\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;
+
+UPDATE pg_proc
+SET protransform = 'mvarchar_support(internal)'::regprocedure
+WHERE oid = 'mvarchar(mvarchar, integer, boolean)'::regprocedure;
+
+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
+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
+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);
+
+
diff --git a/contrib/mchar/mchar--unpackaged--2.0.sql b/contrib/mchar/mchar--unpackaged--2.0.sql
new file mode 100644
index 0000000000..1acc4ccec1
--- /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 0000000000..811a799b09
--- /dev/null
+++ b/contrib/mchar/mchar.control
@@ -0,0 +1,6 @@
+# mchar extension
+comment = 'SQL Server text type'
+default_version = '2.0.1'
+module_pathname = '$libdir/mchar'
+relocatable = true
+
diff --git a/contrib/mchar/mchar.h b/contrib/mchar/mchar.h
new file mode 100644
index 0000000000..a88a0e1eb7
--- /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 0000000000..4a4974b75b
--- /dev/null
+++ b/contrib/mchar/mchar_io.c
@@ -0,0 +1,396 @@
+#include "mchar.h"
+#include "mb/pg_wchar.h"
+#include "fmgr.h"
+#include "libpq/pqformat.h"
+#include "nodes/nodeFuncs.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 mvarchar_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)
+{
+ FuncExpr *expr = castNode(FuncExpr, PG_GETARG_POINTER(0));
+ Node *ret = NULL;
+ 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 0000000000..5a281d7ee7
--- /dev/null
+++ b/contrib/mchar/mchar_like.c
@@ -0,0 +1,927 @@
+#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 "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)
+ )) == LIKE_TRUE;
+
+ 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, DEFAULT_COLLATION_OID, 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, DEFAULT_COLLATION_OID, 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 *
+RE_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
+RE_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 = RE_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 = RE_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 = RE_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 = RE_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 = RE_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 0000000000..4694d9cf3c
--- /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 0000000000..edabfb5eb6
--- /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 0000000000..12bc6d4f3a
--- /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 0000000000..d5b6a98696
--- /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 0000000000..0431004445
--- /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 0000000000..105be6db7b
--- /dev/null
+++ b/contrib/mchar/sql/like.sql
@@ -0,0 +1,222 @@
+set standard_conforming_strings=off;
+-- simplest examples
+-- E061-04 like predicate
+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;
+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;
+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;
+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 0000000000..2ec0e659f4
--- /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 0000000000..2e11b93704
--- /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 0000000000..91b0981075
--- /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 0000000000..75fea1f35d
--- /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 0000000000..333add2b09
--- /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 0000000000..d72f17db42
--- /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 0000000000..90f261cfdb
--- /dev/null
+++ b/contrib/online_analyze/online_analyze.c
@@ -0,0 +1,1253 @@
+/*
+ * 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"
+#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
+
+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;
+
+
+ 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();
+
+ 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),
+ VACOPT_ANALYZE | VACOPT_NOWAIT | ((online_analyze_verbose) ? VACOPT_VERBOSE : 0),
+ &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, µsecs);
+ 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;
+ 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;
+ 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 >= 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, char *completionTag) {
+ 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;
+
+#if PG_VERSION_NUM >= 110000
+ tblnames = vac->rels;
+#else
+ if (vac->relation)
+ tblnames = list_make1(vac->relation);
+#endif
+
+ if (vac->options & (VACOPT_VACUUM | VACOPT_FULL | VACOPT_FREEZE))
+ {
+ /* optionally with analyze */
+ op = CK_VACUUM;
+
+ /* drop all collected stat */
+ if (tblnames == NIL)
+ relstatsInit();
+ }
+ else if (vac->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/pg_trgm/expected/pg_trgm.out b/contrib/pg_trgm/expected/pg_trgm.out
index 6efc54356a..481666b98d 100644
--- a/contrib/pg_trgm/expected/pg_trgm.out
+++ b/contrib/pg_trgm/expected/pg_trgm.out
@@ -3910,16 +3910,15 @@ SELECT similarity('Szczecin', 'Warsaw');
EXPLAIN (COSTS OFF)
SELECT DISTINCT city, similarity(city, 'Warsaw'), show_limit()
FROM restaurants WHERE city % 'Warsaw';
- QUERY PLAN
--------------------------------------------------------------
- Unique
- -> Sort
- Sort Key: city, (similarity(city, 'Warsaw'::text))
- -> Bitmap Heap Scan on restaurants
- Recheck Cond: (city % 'Warsaw'::text)
- -> Bitmap Index Scan on restaurants_city_idx
- Index Cond: (city % 'Warsaw'::text)
-(7 rows)
+ QUERY PLAN
+-------------------------------------------------------------------
+ HashAggregate
+ Group Key: city, similarity(city, 'Warsaw'::text), show_limit()
+ -> Bitmap Heap Scan on restaurants
+ Recheck Cond: (city % 'Warsaw'::text)
+ -> Bitmap Index Scan on restaurants_city_idx
+ Index Cond: (city % 'Warsaw'::text)
+(6 rows)
SELECT set_limit(0.3);
set_limit
diff --git a/contrib/plantuner/COPYRIGHT b/contrib/plantuner/COPYRIGHT
new file mode 100644
index 0000000000..6e4705bc56
--- /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 0000000000..f2e8350e84
--- /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 0000000000..6b16fbab11
--- /dev/null
+++ b/contrib/plantuner/README.plantuner
@@ -0,0 +1,96 @@
+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.
+
+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 0000000000..0d372cac22
--- /dev/null
+++ b/contrib/plantuner/expected/plantuner.out
@@ -0,0 +1,49 @@
+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);
+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)
+
diff --git a/contrib/plantuner/plantuner.c b/contrib/plantuner/plantuner.c
new file mode 100644
index 0000000000..8595463770
--- /dev/null
+++ b/contrib/plantuner/plantuner.c
@@ -0,0 +1,431 @@
+/*
+ * 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;
+
+static int nDisabledIndexes = 0;
+static Oid *disabledIndexes = NULL;
+static char *disableIndexesOutStr = "";
+
+static int nEnabledIndexes = 0;
+static Oid *enabledIndexes = NULL;
+static char *enableIndexesOutStr = "";
+
+get_relation_info_hook_type prevHook = NULL;
+static bool fix_empty_table = false;
+
+static bool plantuner_enable_inited = false;
+static bool plantuner_disable_inited = false;
+
+static const char *
+indexesAssign(const char * newval, bool doit, GucSource source, bool isDisable)
+{
+ 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 */
+ if (isDisable)
+ plantuner_disable_inited = false;
+ else
+ plantuner_enable_inited = false;
+
+ return newval;
+ }
+
+ if (doit)
+ {
+ nOids = list_length(namelist);
+ newOids = malloc(sizeof(Oid) * (nOids+1));
+ if (!newOids)
+ elog(ERROR,"could not allocate %d bytes",
+ (int)(sizeof(Oid) * (nOids+1)));
+ }
+
+ if (isDisable)
+ plantuner_disable_inited = true;
+ else
+ plantuner_enable_inited = true;
+
+ 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)
+ {
+ if (isDisable)
+ {
+ nDisabledIndexes = i;
+ if (disabledIndexes)
+ free(disabledIndexes);
+ disabledIndexes = newOids;
+ }
+ else
+ {
+ nEnabledIndexes = i;
+ if (enabledIndexes)
+ free(enabledIndexes);
+ enabledIndexes = newOids;
+ }
+ }
+
+ 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, true);
+}
+
+static const char *
+assignEnabledIndexes(const char * newval, bool doit, GucSource source)
+{
+ return indexesAssign(newval, doit, source, false);
+}
+
+static void
+lateInit()
+{
+ if (!plantuner_enable_inited)
+ indexesAssign(enableIndexesOutStr, true, PGC_S_USER, false);
+ if (!plantuner_disable_inited)
+ indexesAssign(disableIndexesOutStr, true, PGC_S_USER, true);
+}
+
+#if PG_VERSION_NUM >= 90100
+
+static bool
+checkDisabledIndexes(char **newval, void **extra, GucSource source)
+{
+ char *val;
+
+ val = (char*)indexesAssign(*newval, false, source, true);
+
+ 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, false);
+
+ 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 */);
+}
+
+#endif
+
+static void
+indexFilter(PlannerInfo *root, Oid relationObjectId, bool inhparent,
+ RelOptInfo *rel)
+{
+ int i;
+
+ lateInit();
+
+ 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);
+}
+
+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
+ );
+
+ 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 0000000000..f0bda23833
--- /dev/null
+++ b/contrib/plantuner/sql/plantuner.sql
@@ -0,0 +1,27 @@
+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);
+
+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;
diff --git a/contrib/postgres_fdw/expected/postgres_fdw.out b/contrib/postgres_fdw/expected/postgres_fdw.out
index ec650c6624..9e42a9d19b 100644
--- a/contrib/postgres_fdw/expected/postgres_fdw.out
+++ b/contrib/postgres_fdw/expected/postgres_fdw.out
@@ -1071,18 +1071,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
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Limit
Output: t1.c1, t2.c2, t3.c3, t1.c3
- -> Sort
+ -> Foreign Scan
Output: t1.c1, t2.c2, t3.c3, t1.c3
- Sort Key: t1.c3, t1.c1
- -> 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))))
-(9 rows)
+ 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
@@ -1734,22 +1731,18 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
-> Sort
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
Sort Key: t1.c3, t1.c1
- -> Merge Join
+ -> Hash Join
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
- Merge Cond: (t1.c1 = t2.c1)
- -> Sort
+ Hash Cond: (t1.c1 = t2.c1)
+ -> Foreign Scan on public.ft1 t1
Output: t1.c1, t1.c3, t1.*
- Sort Key: t1.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
- -> Sort
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
+ -> Hash
Output: t2.c1, t2.*
- Sort Key: t2.c1
-> 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"
-(28 rows)
+(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
@@ -1783,22 +1776,18 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
-> Sort
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
Sort Key: t1.c3, t1.c1
- -> Merge Join
+ -> Hash Join
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
- Merge Cond: (t1.c1 = t2.c1)
- -> Sort
+ Hash Cond: (t1.c1 = t2.c1)
+ -> Foreign Scan on public.ft1 t1
Output: t1.c1, t1.c3, t1.*
- Sort Key: t1.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
- -> Sort
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR UPDATE
+ -> Hash
Output: t2.c1, t2.*
- Sort Key: t2.c1
-> 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
-(28 rows)
+(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
@@ -1833,22 +1822,18 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
-> Sort
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
Sort Key: t1.c3, t1.c1
- -> Merge Join
+ -> Hash Join
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
- Merge Cond: (t1.c1 = t2.c1)
- -> Sort
+ Hash Cond: (t1.c1 = t2.c1)
+ -> Foreign Scan on public.ft1 t1
Output: t1.c1, t1.c3, t1.*
- Sort Key: t1.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
- -> Sort
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
+ -> Hash
Output: t2.c1, t2.*
- Sort Key: t2.c1
-> 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"
-(28 rows)
+(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
@@ -1882,22 +1867,18 @@ SELECT t1.c1, t2.c1 FROM ft1 t1 JOIN ft2 t2 ON (t1.c1 = t2.c1) ORDER BY t1.c3, t
-> Sort
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
Sort Key: t1.c3, t1.c1
- -> Merge Join
+ -> Hash Join
Output: t1.c1, t1.c3, t1.*, t2.c1, t2.*
- Merge Cond: (t1.c1 = t2.c1)
- -> Sort
+ Hash Cond: (t1.c1 = t2.c1)
+ -> Foreign Scan on public.ft1 t1
Output: t1.c1, t1.c3, t1.*
- Sort Key: t1.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
- -> Sort
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" FOR SHARE
+ -> Hash
Output: t2.c1, t2.*
- Sort Key: t2.c1
-> 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
-(28 rows)
+(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
@@ -2380,39 +2361,42 @@ SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
-> Merge Join
Output: ft1.c1, ft1.c2, ft1.c3, ft1.c4, ft1.c5, ft1.c6, ft1.c7, ft1.c8, ft2.c1, ft2.c2, ft2.c3, ft2.c4, ft2.c5, ft2.c6, ft2.c7, ft2.c8, ft4.c1, ft4.c2, ft4.c3, ft5.c1, ft5.c2, ft5.c3, ft1.*, ft2.*, ft4.*, 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
-> Foreign Scan on public.ft5
Output: ft5.c1, ft5.c2, ft5.c3, ft5.*
Remote SQL: SELECT c1, c2, c3 FROM "S 1"."T 4" FOR UPDATE
-(41 rows)
+(44 rows)
SELECT * FROM ft1, ft2, ft4, ft5 WHERE ft1.c1 = ft2.c1 AND ft1.c2 = ft4.c1
AND ft1.c2 = ft5.c1 AND ft1.c1 < 100 AND ft2.c1 < 100 FOR UPDATE;
@@ -3409,12 +3393,15 @@ select array_agg(c1 order by c1 using operator(public.<^)) from ft2 where c2 = 6
-- This should be pushed too.
explain (verbose, costs off)
select * from ft2 order by c1 using operator(public.<^);
- QUERY PLAN
------------------------------------------------------------------------------------------------------------------------------
- Foreign Scan on public.ft2
+ QUERY PLAN
+-------------------------------------------------------------------------------
+ Sort
Output: c1, c2, c3, c4, c5, c6, c7, c8
- Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1" ORDER BY "C 1" USING OPERATOR(public.<^) NULLS LAST
-(3 rows)
+ Sort Key: ft2.c1 USING <^
+ -> Foreign Scan on public.ft2
+ Output: c1, c2, c3, c4, c5, c6, c7, c8
+ Remote SQL: SELECT "C 1", c2, c3, c4, c5, c6, c7, c8 FROM "S 1"."T 1"
+(6 rows)
-- Remove from extension
alter extension postgres_fdw drop operator class my_op_class using btree;
diff --git a/src/backend/access/common/indextuple.c b/src/backend/access/common/indextuple.c
index aa52a96259..05d0bf1b68 100644
--- a/src/backend/access/common/indextuple.c
+++ b/src/backend/access/common/indextuple.c
@@ -423,14 +423,70 @@ void
index_deform_tuple(IndexTuple tup, TupleDesc tupleDescriptor,
Datum *values, bool *isnull)
{
- int i;
+ int natts = tupleDescriptor->natts; /* number of atts to extract */
+ int attnum;
+ char *tp; /* ptr to tuple data */
+ int off; /* offset in tuple data */
+ bits8 *bp; /* ptr to null bitmap in tuple */
+ bool slow = false; /* can we use/set attcacheoff? */
/* Assert to protect callers who allocate fixed-size arrays */
- Assert(tupleDescriptor->natts <= INDEX_MAX_KEYS);
+ Assert(natts <= INDEX_MAX_KEYS);
+
+ tp = (char *) tup + IndexInfoFindDataOffset(tup->t_info);
+ off = 0;
+ /* XXX "knows" t_bits are just after fixed tuple header! */
+ bp = (bits8 *) ((char *) tup + sizeof(IndexTupleData));
- for (i = 0; i < tupleDescriptor->natts; i++)
+ for (attnum = 0; attnum < natts; attnum++)
{
- values[i] = index_getattr(tup, i + 1, tupleDescriptor, &isnull[i]);
+ Form_pg_attribute thisatt = TupleDescAttr(tupleDescriptor, attnum);
+
+ if (IndexTupleHasNulls(tup) && att_isnull(attnum, bp))
+ {
+ values[attnum] = (Datum) 0;
+ isnull[attnum] = true;
+ slow = true; /* can't use attcacheoff anymore */
+ continue;
+ }
+
+ isnull[attnum] = false;
+
+ if (!slow && thisatt->attcacheoff >= 0)
+ off = thisatt->attcacheoff;
+ else if (thisatt->attlen == -1)
+ {
+ /*
+ * We can only cache the offset for a varlena attribute if the
+ * offset is already suitably aligned, so that there would be no
+ * pad bytes in any case: then the offset will be valid for either
+ * an aligned or unaligned value.
+ */
+ if (!slow &&
+ off == att_align_nominal(off, thisatt->attalign))
+ thisatt->attcacheoff = off;
+ else
+ {
+ off = att_align_pointer(off, thisatt->attalign, -1,
+ tp + off);
+ slow = true;
+ }
+ }
+ else
+ {
+ /* not varlena, so safe to use att_align_nominal */
+ off = att_align_nominal(off, thisatt->attalign);
+
+ if (!slow)
+ thisatt->attcacheoff = off;
+ }
+
+ values[attnum] = fetchatt(thisatt, tp + off);
+
+ off = att_addlength_pointer(off, thisatt->attlen, tp + off);
+
+ if (thisatt->attlen <= 0)
+ slow = true; /* can't use attcacheoff anymore */
}
}
diff --git a/src/backend/catalog/dependency.c b/src/backend/catalog/dependency.c
index f8510a1483..65e611eb13 100644
--- a/src/backend/catalog/dependency.c
+++ b/src/backend/catalog/dependency.c
@@ -301,6 +301,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 c7dd7629e3..31cc833aea 100644
--- a/src/backend/catalog/index.c
+++ b/src/backend/catalog/index.c
@@ -52,6 +52,7 @@
#include "commands/tablecmds.h"
#include "commands/event_trigger.h"
#include "commands/trigger.h"
+#include "commands/typecmds.h"
#include "executor/executor.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
@@ -144,7 +145,7 @@ static void SetReindexProcessing(Oid heapOid, Oid indexOid);
static void ResetReindexProcessing(void);
static void SetReindexPending(List *indexes);
static void RemoveReindexPending(Oid indexOid);
-
+static Oid IndexTypeCreate(Relation indexRelation);
/*
* relationHasPrimaryKey
@@ -706,6 +707,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) ( \
+ !IsSystemRelation(indexRelation) \
+ && indexInfo->ii_NumIndexAttrs > 1 \
+ && indexInfo->ii_Am == BTREE_AM_OID \
+ && indexRelation->rd_rel->reltype == InvalidOid \
+ && (!IsBinaryUpgrade || binary_upgrade_next_pg_type_oid != InvalidOid))
+
+/*
+ * IndexTypeCreate
+ *
+ * Create type for specified index.
+ */
+Oid
+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 */
+
+ 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 */
+ new_type_addr.objectId, /* 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);
+
+ return new_type_addr.objectId;
+}
/*
* index_create
@@ -790,6 +895,7 @@ index_create(Relation heapRelation,
bool concurrent = (flags & INDEX_CREATE_CONCURRENT) != 0;
bool partitioned = (flags & INDEX_CREATE_PARTITIONED) != 0;
char relkind;
+ Oid new_reltype = InvalidOid;
/* constraint flags can only be set when a constraint is requested */
Assert((constr_flags == 0) ||
@@ -953,6 +1059,10 @@ index_create(Relation heapRelation,
Assert(indexRelationId == RelationGetRelid(indexRelation));
+ /* Create a reltype for index if it is needed */
+ if (INDEX_NEEDS_RELTYPE(indexRelation, indexInfo))
+ new_reltype = IndexTypeCreate(indexRelation);
+
/*
* Obtain exclusive lock on it. Although no other transactions can see it
* until we commit, this prevents deadlock-risk complaints from lock
@@ -966,6 +1076,7 @@ index_create(Relation heapRelation,
*
* XXX should have a cleaner way to create cataloged indexes
*/
+ indexRelation->rd_rel->reltype = new_reltype;
indexRelation->rd_rel->relowner = heapRelation->rd_rel->relowner;
indexRelation->rd_rel->relam = accessMethodObjectId;
indexRelation->rd_rel->relhasoids = false;
@@ -1498,6 +1609,7 @@ index_drop(Oid indexId, bool concurrent)
Relation indexRelation;
HeapTuple tuple;
bool hasexprs;
+ bool remove_statistics;
LockRelId heaprelid,
indexrelid;
LOCKTAG heaplocktag;
@@ -1574,24 +1686,6 @@ index_drop(Oid indexId, bool concurrent)
*/
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
*/
@@ -1712,6 +1806,16 @@ index_drop(Oid indexId, bool concurrent)
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 =
+ IndexRelationGetNumberOfAttributes(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
@@ -1739,10 +1843,10 @@ index_drop(Oid indexId, bool concurrent)
heap_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);
/*
@@ -2285,6 +2389,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 (reltuples >= 0)
{
BlockNumber relpages = RelationGetNumberOfBlocks(rel);
@@ -3848,6 +3960,41 @@ reindex_index(Oid indexId, bool skip_constraint_checks, char persistence,
/* Re-allow use of target index */
ResetReindexProcessing();
+ /*
+ * 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.
+ */
+ if (indexInfo->ii_NumIndexAttrs > 1 && indexInfo->ii_Am == BTREE_AM_OID &&
+ !IsSystemRelation(iRel))
+ RemoveStatistics(indexId, 0);
+
+ /* Create a reltype for index if it is needed */
+ if (INDEX_NEEDS_RELTYPE(iRel, indexInfo))
+ {
+ Relation pg_class;
+ HeapTuple tuple;
+ Form_pg_class rd_rel;
+ Oid new_reltype;
+
+ new_reltype = IndexTypeCreate(iRel);
+
+ pg_class = heap_open(RelationRelationId, RowExclusiveLock);
+ tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(indexId));
+ if (!HeapTupleIsValid(tuple))
+ elog(ERROR, "could not find tuple for relation %u", indexId);
+ rd_rel = (Form_pg_class) GETSTRUCT(tuple);
+
+ rd_rel->reltype = new_reltype;
+
+ CatalogTupleUpdate(pg_class, &tuple->t_self, tuple);
+
+ heap_freetuple(tuple);
+ heap_close(pg_class, RowExclusiveLock);
+
+ iRel->rd_rel->reltype = new_reltype;
+ }
+
/*
* If the index is marked invalid/not-ready/dead (ie, it's from a failed
* CREATE INDEX CONCURRENTLY, or a DROP INDEX CONCURRENTLY failed midway),
diff --git a/src/backend/commands/analyze.c b/src/backend/commands/analyze.c
index f47ef379b5..352a13724a 100644
--- a/src/backend/commands/analyze.c
+++ b/src/backend/commands/analyze.c
@@ -35,8 +35,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"
@@ -61,6 +64,7 @@
#include "utils/syscache.h"
#include "utils/timestamp.h"
#include "utils/tqual.h"
+#include "utils/typcache.h"
/* Per-index data for ANALYZE */
@@ -70,6 +74,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;
@@ -514,6 +519,21 @@ do_analyze_rel(Relation onerel, int options, 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;
+ }
}
}
@@ -853,28 +873,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++;
}
}
}
@@ -2674,6 +2707,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 e57cf3ad35..bf6b428329 100644
--- a/src/backend/commands/explain.c
+++ b/src/backend/commands/explain.c
@@ -357,20 +357,13 @@ ExplainOneQuery(Query *query, int cursorOptions,
else
{
PlannedStmt *plan;
- instr_time planstart,
- planduration;
-
- INSTR_TIME_SET_CURRENT(planstart);
/* plan the query */
plan = pg_plan_query(query, cursorOptions, params);
- INSTR_TIME_SET_CURRENT(planduration);
- INSTR_TIME_SUBTRACT(planduration, planstart);
-
/* run it (if needed) and produce output */
ExplainOnePlan(plan, into, es, queryString, params, queryEnv,
- &planduration);
+ &plan->planDuration);
}
}
diff --git a/src/backend/executor/execExpr.c b/src/backend/executor/execExpr.c
index 6c6fda1bf6..df6958d84f 100644
--- a/src/backend/executor/execExpr.c
+++ b/src/backend/executor/execExpr.c
@@ -244,13 +244,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);
}
@@ -1034,8 +1054,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
@@ -1054,10 +1081,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)
{
@@ -1144,6 +1178,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;
@@ -3367,6 +3402,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 5f5840bf35..1863e68ddf 100644
--- a/src/backend/executor/execExprInterp.c
+++ b/src/backend/executor/execExprInterp.c
@@ -739,6 +739,9 @@ ExecInterpExpr(ExprState *state, ExprContext *econtext, bool *isnull)
EEO_JUMP(op->d.boolexpr.jumpdone);
}
+ /* reset */
+ *op->d.boolexpr.guaranteed_empty = false;
+
EEO_NEXT();
}
@@ -747,6 +750,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))
{
@@ -762,10 +767,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();
@@ -784,6 +794,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
@@ -795,6 +806,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;
@@ -811,6 +826,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 */
@@ -832,6 +851,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 */
}
@@ -862,6 +885,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);
}
@@ -3809,7 +3835,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 f84a3fb0db..76a8fcc6e7 100644
--- a/src/backend/executor/execScan.c
+++ b/src/backend/executor/execScan.c
@@ -150,6 +150,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
@@ -208,7 +209,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/nodeIndexonlyscan.c b/src/backend/executor/nodeIndexonlyscan.c
index c7d18bd0c3..8f57fbfeca 100644
--- a/src/backend/executor/nodeIndexonlyscan.c
+++ b/src/backend/executor/nodeIndexonlyscan.c
@@ -262,11 +262,6 @@ IndexOnlyNext(IndexOnlyScanState *node)
static void
StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup, TupleDesc itupdesc)
{
- int nindexatts = itupdesc->natts;
- Datum *values = slot->tts_values;
- bool *isnull = slot->tts_isnull;
- int i;
-
/*
* Note: we must use the tupdesc supplied by the AM in index_getattr, not
* the slot's tupdesc, in case the latter has different datatypes (this
@@ -274,11 +269,10 @@ StoreIndexTuple(TupleTableSlot *slot, IndexTuple itup, TupleDesc itupdesc)
* number of columns though, as well as being datatype-compatible which is
* something we can't so easily check.
*/
- Assert(slot->tts_tupleDescriptor->natts == nindexatts);
+ Assert(slot->tts_tupleDescriptor->natts == itupdesc->natts);
ExecClearTuple(slot);
- for (i = 0; i < nindexatts; i++)
- values[i] = index_getattr(itup, i + 1, itupdesc, &isnull[i]);
+ index_deform_tuple(itup, itupdesc, slot->tts_values, slot->tts_isnull);
ExecStoreVirtualTuple(slot);
}
diff --git a/src/backend/executor/nodeMaterial.c b/src/backend/executor/nodeMaterial.c
index 8c2e57dbd0..a9bbb06d46 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;
}
@@ -365,6 +367,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 9ae9863226..338573206b 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 61965579e8..b3dc989fec 100644
--- a/src/backend/executor/nodeSubplan.c
+++ b/src/backend/executor/nodeSubplan.c
@@ -106,6 +106,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)
@@ -123,8 +126,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
@@ -1369,7 +1375,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;
@@ -1393,6 +1406,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 95903d60f8..435d9de7e8 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
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif
@@ -1938,3 +1941,30 @@ pq_setkeepalivescount(int count, 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 1119a65b8d..b409dd1e88 100644
--- a/src/backend/nodes/nodeFuncs.c
+++ b/src/backend/nodes/nodeFuncs.c
@@ -2476,21 +2476,40 @@ range_table_walker(List *rtable,
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) )
@@ -2513,7 +2532,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;
@@ -2522,7 +2541,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;
}
@@ -2542,7 +2561,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;
}
@@ -2551,7 +2570,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 *);
@@ -2567,7 +2586,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 *);
/*
@@ -2590,7 +2609,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;
@@ -2601,7 +2620,7 @@ expression_tree_mutator(Node *node,
ArrayRef *arrayref = (ArrayRef *) node;
ArrayRef *newnode;
- FLATCOPY(newnode, arrayref, ArrayRef);
+ FLATCOPY(newnode, arrayref, ArrayRef, flags);
MUTATE(newnode->refupperindexpr, arrayref->refupperindexpr,
List *);
MUTATE(newnode->reflowerindexpr, arrayref->reflowerindexpr,
@@ -2618,7 +2637,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;
}
@@ -2628,7 +2647,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;
}
@@ -2638,7 +2657,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;
}
@@ -2648,7 +2667,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;
}
@@ -2658,7 +2677,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;
}
@@ -2668,7 +2687,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;
}
@@ -2678,7 +2697,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;
}
@@ -2688,7 +2707,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 *);
/*
@@ -2704,7 +2723,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) */
@@ -2718,7 +2737,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;
}
@@ -2728,7 +2747,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;
}
@@ -2738,7 +2757,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);
@@ -2750,7 +2769,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;
}
@@ -2760,7 +2779,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;
}
@@ -2770,7 +2789,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;
@@ -2781,7 +2800,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;
}
@@ -2791,7 +2810,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;
}
@@ -2801,7 +2820,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 *);
@@ -2813,7 +2832,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;
@@ -2824,7 +2843,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;
}
@@ -2834,7 +2853,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;
@@ -2845,7 +2864,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;
@@ -2856,7 +2875,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;
}
@@ -2866,7 +2885,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;
}
@@ -2876,7 +2895,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 *);
@@ -2888,7 +2907,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;
}
@@ -2898,7 +2917,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;
}
@@ -2908,7 +2927,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;
}
@@ -2918,7 +2937,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;
}
@@ -2931,7 +2950,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 *);
@@ -2944,7 +2963,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
@@ -2979,7 +2998,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;
@@ -2990,7 +3009,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 *);
@@ -3005,7 +3024,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;
@@ -3019,7 +3038,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 *);
@@ -3032,7 +3051,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 */
@@ -3044,7 +3063,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;
@@ -3055,7 +3074,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;
}
@@ -3065,7 +3084,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 *);
return (Node *) newnode;
}
@@ -3075,7 +3094,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;
@@ -3086,7 +3105,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;
@@ -3097,7 +3116,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;
@@ -3108,7 +3127,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 *);
@@ -3158,7 +3177,7 @@ query_tree_mutator(Query *query,
{
Query *newquery;
- FLATCOPY(newquery, query, Query);
+ FLATCOPY(newquery, query, Query, flags);
query = newquery;
}
@@ -3200,7 +3219,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 *);
@@ -3249,7 +3268,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:
@@ -3264,7 +3283,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 7b2647c225..57d1f4c489 100644
--- a/src/backend/nodes/outfuncs.c
+++ b/src/backend/nodes/outfuncs.c
@@ -1945,6 +1945,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);
}
static void
diff --git a/src/backend/optimizer/path/Makefile b/src/backend/optimizer/path/Makefile
index 6864a62132..ef250e0f24 100644
--- a/src/backend/optimizer/path/Makefile
+++ b/src/backend/optimizer/path/Makefile
@@ -12,7 +12,7 @@ subdir = src/backend/optimizer/path
top_builddir = ../../../..
include $(top_builddir)/src/Makefile.global
-OBJS = allpaths.o clausesel.o costsize.o equivclass.o indxpath.o \
- joinpath.o joinrels.o pathkeys.o tidpath.o
+OBJS = allpaths.o appendorpath.o clausesel.o costsize.o equivclass.o \
+ indxpath.o joinpath.o joinrels.o pathkeys.o tidpath.o
include $(top_srcdir)/src/backend/common.mk
diff --git a/src/backend/optimizer/path/allpaths.c b/src/backend/optimizer/path/allpaths.c
index 79e114743f..2a3fd34e6a 100644
--- a/src/backend/optimizer/path/allpaths.c
+++ b/src/backend/optimizer/path/allpaths.c
@@ -729,6 +729,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);
}
@@ -1604,7 +1607,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
if (subpaths_valid)
add_path(rel, (Path *) create_append_path(root, rel, subpaths, NIL,
NULL, 0, false,
- partitioned_rels, -1));
+ partitioned_rels, -1,
+ false, NIL));
/*
* Consider an append of unordered, unparameterized partial paths. Make
@@ -1647,7 +1651,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
appendpath = create_append_path(root, rel, NIL, partial_subpaths,
NULL, parallel_workers,
enable_parallel_append,
- partitioned_rels, -1);
+ partitioned_rels, -1,
+ false, NIL);
/*
* Make sure any subsequent partial paths use the same row count
@@ -1696,7 +1701,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
appendpath = create_append_path(root, rel, pa_nonpartial_subpaths,
pa_partial_subpaths,
NULL, parallel_workers, true,
- partitioned_rels, partial_rows);
+ partitioned_rels, partial_rows,
+ false, NIL);
add_partial_path(rel, (Path *) appendpath);
}
@@ -1758,7 +1764,8 @@ add_paths_to_append_rel(PlannerInfo *root, RelOptInfo *rel,
add_path(rel, (Path *)
create_append_path(root, rel, subpaths, NIL,
required_outer, 0, false,
- partitioned_rels, -1));
+ partitioned_rels, -1,
+ false, NIL));
}
}
@@ -2027,7 +2034,8 @@ set_dummy_rel_pathlist(RelOptInfo *rel)
/* Set up the dummy path */
add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
rel->lateral_relids,
- 0, false, NIL, -1));
+ 0, false, NIL, -1,
+ false, NIL));
/*
* 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 0000000000..e992025859
--- /dev/null
+++ b/src/backend/optimizer/path/appendorpath.c
@@ -0,0 +1,1045 @@
+/*
+ * 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/paths.h"
+#include "optimizer/pathnode.h"
+#include "optimizer/planmain.h"
+#include "optimizer/predtest.h"
+#include "optimizer/restrictinfo.h"
+#include "utils/fmgroids.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;
+
+
+/*----------
+ * 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;
+
+ Assert(list_length(ipath->indexquals) == list_length(ipath->indexqualcols));
+ /*
+ * 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) );
+ Assert(list_length(ipath->indexquals) == list_length(ipath->indexqualcols));
+ ipath->path.rows = rel->tuples * clauselist_selectivity(root,
+ ipath->indexquals,
+ 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, NULL, 0,
+ false, NIL, -1.0, true, pathkeys);
+ 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, NULL,
+ 0, false, NIL, -1.0,
+ true, pathkeys);
+ add_path(rel, (Path *) appendidxpath);
+ }
+ } else {
+ appendidxpath = create_append_path(root, rel, bestpath->bitmapquals,
+ NIL, NULL,
+ 0, false, NIL, -1.0, true, NIL);
+ 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->varoattno;
+
+ 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;
+
+ Assert(list_length(path->indexquals) == list_length(path->indexqualcols));
+ forboth(i, path->indexquals, c, path->indexqualcols) {
+ CKey *k = transformToCkey( path->indexinfo, (RestrictInfo*)lfirst(i), lfirst_int(c) );
+ 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(i);
+
+ 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 *indexquals, List *indexqualcols, List **preds, List **predcols) {
+ ListCell *i, *c;
+
+ *preds = NIL;
+ *predcols = NIL;
+
+ forboth(i, indexquals, c, indexqualcols)
+ {
+ RestrictInfo *rinfo = lfirst(i);
+ OpExpr *expr = (OpExpr*)rinfo->clause;
+
+ if ( rinfo->outerjoin_delayed )
+ continue;
+
+ if ( !IsA(expr, OpExpr) )
+ continue;
+
+ if ( list_length( expr->args ) != 2 )
+ continue;
+
+ *preds = lappend(*preds, rinfo);
+ *predcols = lappend(*predcols, lfirst(c));
+ }
+}
+
+#define CELL_GET_QUALS(x) ( ((IndexPath*)lfirst(x))->indexquals )
+#define CELL_GET_CLAUSES(x) ( ((IndexPath*)lfirst(x))->indexclauses )
+
+static List*
+listRInfo2OpExpr(List *listRInfo) {
+ ListCell *i;
+ List *listOpExpr=NULL;
+
+ foreach(i, listRInfo)
+ {
+ RestrictInfo *rinfo = lfirst(i);
+ OpExpr *expr = (OpExpr*)rinfo->clause;
+
+ listOpExpr = lappend(listOpExpr, expr);
+ }
+
+ return listOpExpr;
+}
+
+/*
+ * 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;
+
+ if (equal(CELL_GET_QUALS(check), CELL_GET_CLAUSES(check)) == false)
+ return nested;
+
+ checkpred = listRInfo2OpExpr(CELL_GET_QUALS(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 ( equal(CELL_GET_QUALS(i), CELL_GET_CLAUSES(i)) &&
+ predicate_implied_by( checkpred, CELL_GET_QUALS(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(ListCell *check) {
+ ListCell *i;
+ List *checkpred=NULL;
+
+ checkpred=listRInfo2OpExpr(CELL_GET_QUALS(check));
+ Assert( checkpred != NULL );
+
+ for_each_cell(i, check) {
+ if ( i==check )
+ continue;
+
+ if ( predicate_refuted_by( checkpred, CELL_GET_QUALS(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, *predcols;
+ 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
+ */
+ Assert(list_length(subpath->indexquals) == list_length(subpath->indexqualcols));
+ make_predicate(subpath->indexquals, subpath->indexqualcols, &preds, &predcols);
+ if (preds == NIL)
+ return NULL;
+ subpath->indexquals = preds;
+ subpath->indexqualcols = predcols;
+ Assert(list_length(subpath->indexquals) == list_length(subpath->indexqualcols));
+ } 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( 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 *indexquals, List *indexqualcols) {
+ ListCell *i, *c;
+ List *res=NULL;
+ ExExpr *ex;
+
+ Assert(list_length(indexquals) == list_length(indexqualcols));
+ forboth(i, indexquals, c, indexqualcols)
+ {
+ RestrictInfo *rinfo = lfirst(i);
+ OpExpr *expr = (OpExpr*)rinfo->clause;
+
+ if ( rinfo->outerjoin_delayed )
+ return NULL;
+
+ 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, lfirst_int(c));
+ 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->indexquals, ipe[j].path->indexqualcols );
+
+ 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 f4717942c3..b4af791d0c 100644
--- a/src/backend/optimizer/path/clausesel.c
+++ b/src/backend/optimizer/path/clausesel.c
@@ -12,18 +12,34 @@
*
*-------------------------------------------------------------------------
*/
+#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"
#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/plancat.h"
+#include "optimizer/var.h"
+#include "parser/parsetree.h"
+#include "utils/array.h"
+#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
+#include "utils/rel.h"
#include "utils/selfuncs.h"
+#include "utils/syscache.h"
+#include "utils/typcache.h"
#include "statistics/statistics.h"
+#define EXHAUSTIVE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/4)
+#define RANGE_IN_SELECTIVITY_THRESHOLD (default_statistics_target/20)
/*
* Data structure for accumulating info about possible range-query
@@ -43,6 +59,1041 @@ 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(Node *clause, RestrictInfo *rinfo,
+ int varRelid, SpecialJoinInfo *sjinfo);
+
+typedef enum CorrelationKind {
+ CKRestrict = 0,
+ CKIndepend, /* unknown correlation */
+ CKLikelySelf, /* Seems, should be close to be correlated, like agg with
+ self join */
+ CKSelf, /* 100% correlation because of self join */
+ CKMul /* product of all CKLikelySelf * CKSelf */
+} CorrelationKind;
+static CorrelationKind get_correlation_kind(PlannerInfo *root, int varRelid,
+ OpExpr* expr);
+
+/*
+ * Get variabe node. Returns null if node is not a Var node.
+ */
+static inline Var*
+get_var(Node* node)
+{
+ if (IsA(node, RelabelType))
+ node = (Node *) ((RelabelType *) node)->arg;
+
+ return IsA(node, Var) ? (Var*)node : NULL;
+}
+
+/*
+ * Locate compound index which can be used for multicolumn clauses/join.
+ */
+static IndexOptInfo*
+locate_inner_multicolumn_index(PlannerInfo *root, Index varno, List* vars,
+ int n_clauses,
+ int **permutation, List **missed_vars, int* n_keys)
+{
+ ListCell *ilist;
+ RelOptInfo *rel = find_base_rel(root, varno);
+ IndexOptInfo *index_opt = NULL;
+ List *missed_vars_opt = NIL;
+ int *permutation_opt = NULL;
+ int n_index_cols_opt = 0;
+ bool used[INDEX_MAX_KEYS];
+ int posvars[INDEX_MAX_KEYS];
+
+ *n_keys = 0;
+ *missed_vars = NIL;
+
+ Assert(list_length(vars) >= 1);
+ Assert(list_length(vars) <= n_clauses);
+
+ foreach(ilist, rel->indexlist)
+ {
+ IndexOptInfo *index = (IndexOptInfo *) lfirst(ilist);
+ ListCell *vlist;
+ int i, n_index_cols = 0;
+ List *missed = NIL;
+ int *perm = NULL;
+
+ 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++;
+ 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 < old_n_index_cols; 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, vardata,
+ constant, false, true, false,
+ n_keys);
+
+ if (usedEqSel)
+ *usedEqSel = true;
+ }
+ }
+ else
+ {
+ s = eqconst_selectivity(typentry->eq_opr, 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);
+ TypeCacheEntry *typentry = NULL;
+
+ if (indexRel->rd_rel->reltype != InvalidOid)
+ typentry = lookup_type_cache(indexRel->rd_rel->reltype, TYPECACHE_TUPDESC);
+
+ if (typentry == NULL || typentry->tupDesc == NULL)
+ {
+ 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;
+
+ /*
+ * Do not use expensive machinery for simple cases, we believe that default
+ * selectivity estimator works well enough
+ */
+ 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((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(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 > 0 &&
+ 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,
+ &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
@@ -95,6 +1146,28 @@ static RelOptInfo *find_single_rel_for_clauses(PlannerInfo *root,
* Of course this is all very dependent on the behavior of the inequality
* selectivity functions; perhaps some day we can generalize the approach.
*/
+
+static void
+appendSelectivityRes(Selectivity s[5], Selectivity sel, CorrelationKind ck)
+{
+ switch(ck)
+ {
+ case CKRestrict:
+ s[ck] *= sel;
+ break;
+ case CKSelf:
+ case CKLikelySelf:
+ s[CKMul] *= sel;
+ if (s[ck] > sel)
+ s[ck] = sel;
+ case CKIndepend:
+ s[CKIndepend] *= sel;
+ break;
+ default:
+ elog(ERROR, "unknown selectivity kind: %d", ck);
+ }
+}
+
Selectivity
clauselist_selectivity(PlannerInfo *root,
List *clauses,
@@ -102,12 +1175,14 @@ 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;
RangeQueryClause *rqlist = NULL;
ListCell *l;
int listidx;
+ CorrelationKind ck;
/*
* If there's exactly one clause, just go directly to
@@ -130,9 +1205,10 @@ clauselist_selectivity(PlannerInfo *root,
* filled with the 0-based list positions of clauses used that way, so
* that we can ignore them below.
*/
- s1 *= dependencies_clauselist_selectivity(root, clauses, varRelid,
+ s2 = dependencies_clauselist_selectivity(root, clauses, varRelid,
jointype, sjinfo, rel,
&estimatedclauses);
+ appendSelectivityRes(s, s2, CKRestrict);
/*
* This would be the place to apply any other types of extended
@@ -140,12 +1216,25 @@ clauselist_selectivity(PlannerInfo *root,
*/
}
+ /*
+ * 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 remaining clauses. We'll be
* careful to skip any clauses which were already estimated above.
*
* 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;
@@ -153,7 +1242,6 @@ clauselist_selectivity(PlannerInfo *root,
{
Node *clause = (Node *) lfirst(l);
RestrictInfo *rinfo;
- Selectivity s2;
listidx++;
@@ -178,7 +1266,7 @@ clauselist_selectivity(PlannerInfo *root,
rinfo = (RestrictInfo *) clause;
if (rinfo->pseudoconstant)
{
- s1 = s1 * s2;
+ appendSelectivityRes(s, s2, CKRestrict);
continue;
}
clause = (Node *) rinfo->clause;
@@ -192,12 +1280,17 @@ clauselist_selectivity(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(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) &&
@@ -236,7 +1329,7 @@ clauselist_selectivity(PlannerInfo *root,
break;
default:
/* Just merge the selectivity in generically */
- s1 = s1 * s2;
+ appendSelectivityRes(s, s2, ck);
break;
}
continue; /* drop to loop bottom */
@@ -244,7 +1337,7 @@ clauselist_selectivity(PlannerInfo *root,
}
/* Not the right form, so treat it generically. */
- s1 = s1 * s2;
+ appendSelectivityRes(s, s2, ck);
}
/*
@@ -306,15 +1399,13 @@ clauselist_selectivity(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;
@@ -322,7 +1413,25 @@ clauselist_selectivity(PlannerInfo *root,
rqlist = rqnext;
}
- return s1;
+ /* count final selectivity */
+ s2 = s[CKRestrict] * s[CKIndepend];
+
+ if (s[CKIndepend] != s[CKMul])
+ {
+ /* we hahe both independ and correlated - fallback */
+ s2 *= s[CKMul];
+ }
+ else
+ {
+ /* we have only correlated join clauses */
+ if (s[CKLikelySelf] != 1.0 && s2 < s[CKLikelySelf])
+ s2 = s2 + (s[CKLikelySelf] - s2) * 0.25;
+
+ if (s[CKSelf] != 1.0 && s2 < s[CKSelf])
+ s2 = s2 + (s[CKSelf] - s2) * 1.0;
+ }
+
+ return s2;
}
/*
@@ -531,6 +1640,137 @@ treat_as_join_clause(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);
+
+ 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);
+
+ 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 a2af8111f7..873d5a2647 100644
--- a/src/backend/optimizer/path/costsize.c
+++ b/src/backend/optimizer/path/costsize.c
@@ -144,6 +144,7 @@ typedef struct
{
PlannerInfo *root;
QualCost total;
+ bool calccoalesce;
} cost_qual_eval_context;
static List *extract_nonindex_conditions(List *qual_clauses, List *indexquals);
@@ -724,7 +725,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 +1014,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. */
@@ -1612,6 +1613,283 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
rterm->pathtarget->width);
}
+/*
+ * is_fake_var
+ * Workaround for generate_append_tlist() which generates fake Vars with
+ * varno == 0, that will cause a fail of estimate_num_group() call
+ */
+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;
+
+ sortop = get_opfamily_member(pathkey->pk_opfamily,
+ em->em_datatype, em->em_datatype,
+ pathkey->pk_strategy);
+
+ funcCost = get_func_cost(get_opcode(sortop));
+ 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
@@ -1628,7 +1906,7 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
* number of initial runs formed and M is the merge order used by tuplesort.c.
* Since the average initial run should be about sort_mem, we have
* disk traffic = 2 * relsize * ceil(logM(p / sort_mem))
- * cpu = comparison_cost * t * log2(t)
+ * and cpu cost computed by compute_cpu_sort_cost().
*
* If the sort is bounded (i.e., only the first k result tuples are needed)
* and k tuples can fit into sort_mem, we use a heap method that keeps only
@@ -1649,13 +1927,6 @@ cost_recursive_union(Path *runion, Path *nrterm, Path *rterm)
* 'comparison_cost' is the extra cost per comparison, if any
* 'sort_mem' is the number of kilobytes of work memory allowed for the sort
* 'limit_tuples' is the bound on the number of output tuples; -1 if no bound
- *
- * NOTE: some callers currently pass NIL for pathkeys because they
- * can't conveniently supply the sort keys. Since this routine doesn't
- * currently do anything with pathkeys anyway, that doesn't matter...
- * but if it ever does, it should react gracefully to lack of key data.
- * (Actually, the thing we'd most likely be interested in is just the number
- * of sort keys, which all callers *could* supply.)
*/
void
cost_sort(Path *path, PlannerInfo *root,
@@ -1682,9 +1953,6 @@ cost_sort(Path *path, PlannerInfo *root,
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)
{
@@ -1713,7 +1981,9 @@ cost_sort(Path *path, PlannerInfo *root,
*
* 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 */
@@ -1729,18 +1999,17 @@ cost_sort(Path *path, PlannerInfo *root,
}
else if (tuples > 2 * output_tuples || input_bytes > sort_mem_bytes)
{
- /*
- * We'll use a bounded heap-sort keeping just K tuples in memory, for
- * a total number of tuple comparisons of N log2 K; but the constant
- * factor is a bit higher than for quicksort. Tweak it so that the
- * cost curve is continuous at the crossover point.
- */
- startup_cost += comparison_cost * tuples * LOG2(2.0 * output_tuples);
+ /* We'll use a bounded heap-sort keeping just K tuples in memory. */
+ startup_cost += compute_cpu_sort_cost(root, pathkeys, 0,
+ comparison_cost, tuples,
+ output_tuples, true);
}
else
{
/* We'll use plain quicksort on all the input tuples */
- startup_cost += comparison_cost * tuples * LOG2(tuples);
+ startup_cost += compute_cpu_sort_cost(root, pathkeys, 0,
+ comparison_cost, tuples,
+ tuples, false);
}
/*
@@ -2790,8 +3059,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;
@@ -3728,6 +3998,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 ... */
@@ -3753,6 +4024,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);
@@ -3782,6 +4069,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
@@ -3986,6 +4274,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,
@@ -4474,6 +4767,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
@@ -4512,9 +4806,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);
}
@@ -4566,6 +4862,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 9f39068127..a75326ef95 100644
--- a/src/backend/optimizer/path/equivclass.c
+++ b/src/backend/optimizer/path/equivclass.c
@@ -680,7 +680,18 @@ get_eclass_for_sort_expr(PlannerInfo *root,
if (opcintype == cur_em->em_datatype &&
equal(expr, cur_em->em_expr))
- return cur_ec; /* Match! */
+ {
+ /*
+ * Match!
+ *
+ * Copy 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 9bf2ee4adc..89c3f73138 100644
--- a/src/backend/optimizer/path/indxpath.c
+++ b/src/backend/optimizer/path/indxpath.c
@@ -39,6 +39,14 @@
#include "utils/pg_locale.h"
#include "utils/selfuncs.h"
+/*
+ * index support for LIKE mchar
+ */
+#include "fmgr.h"
+#include "access/htup_details.h"
+#include "utils/catcache.h"
+#include "utils/syscache.h"
+#include "parser/parse_type.h"
/* XXX see PartCollMatchesExprColl */
#define IndexCollMatchesExprColl(idxcollation, exprcollation) \
@@ -115,8 +123,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);
@@ -1250,7 +1256,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)
{
@@ -2908,7 +2914,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
@@ -2929,7 +2936,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;
@@ -2985,6 +2993,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
@@ -3033,6 +3042,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;
}
}
@@ -3075,7 +3085,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;
@@ -3211,6 +3239,224 @@ match_index_to_operand(Node *operand,
return false;
}
+/****************************************************************************
+ * ---- 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;
+
+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(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(tup) != mmPFPOid ) {
+ char *quals_funcname = "mchar_greaterstring";
+ Oid tmp_mmPFPOid = HeapTupleGetOid(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(tup);
+ mmPFPOid = tmp_mmPFPOid;
+ }
+
+ ReleaseSysCacheList(catlist);
+
+ return true;
+}
+
+static Pattern_Prefix_Status
+mchar_pattern_fixed_prefix(Oid opOid, Oid opfamilyOid, Const *patt,
+ Pattern_Type ptype, Const **prefix, Oid *leftTypeOid)
+{
+ HeapTuple tup;
+ Form_pg_operator oprForm;
+ bool isMCharLike = true;
+
+ if ( !fillMCharOIDS() )
+ return Pattern_Prefix_None;
+
+ tup = SearchSysCache(OPEROID, opOid, 0, 0, 0);
+ oprForm = (Form_pg_operator) GETSTRUCT(tup);
+
+ if ( strncmp(oprForm->oprname.data, "~~", 2) != 0 )
+ isMCharLike = false;
+
+ if ( oprForm->oprright != mvarcharOid )
+ isMCharLike = false;
+
+ if ( !( oprForm->oprleft == mcharOid || oprForm->oprleft == mvarcharOid ) )
+ isMCharLike = false;
+
+ if ( patt->consttype != mvarcharOid )
+ isMCharLike = false;
+
+ if (leftTypeOid)
+ *leftTypeOid = oprForm->oprleft;
+
+ ReleaseSysCache(tup);
+
+ if ( !isMCharLike )
+ return Pattern_Prefix_None;
+
+ if ( opfamilyOid != InvalidOid ) {
+ Form_pg_opfamily claForm;
+
+ tup = SearchSysCache(OPFAMILYOID, opfamilyOid, 0, 0, 0);
+ claForm = (Form_pg_opfamily) GETSTRUCT(tup);
+
+ if ( claForm->opfmethod != BTREE_AM_OID )
+ isMCharLike = false;
+
+ if ( mcharOid && strncmp(claForm->opfname.data, "icase_ops", 9 /* strlen(icase_ops) */ ) != 0 )
+ isMCharLike = false;
+
+ ReleaseSysCache(tup);
+ }
+
+ if ( !isMCharLike )
+ return Pattern_Prefix_None;
+
+ return (Pattern_Prefix_Status)DatumGetInt32( OidFunctionCall3(
+ mmPFPOid,
+ PointerGetDatum( patt ),
+ Int32GetDatum( ptype ),
+ PointerGetDatum( prefix )
+ ) );
+}
+
+static Oid
+get_opclass_member_mchar(Oid opclass, Oid leftTypeOid, int strategy) {
+ Oid oproid;
+
+ oproid = get_opfamily_member(opclass, leftTypeOid, mvarcharOid, strategy);
+
+ if ( oproid == InvalidOid )
+ elog(ERROR, "no operator for opclass %u for strategy %u for left type %u", opclass, strategy, leftTypeOid);
+
+ return oproid;
+}
+
+static List *
+mchar_prefix_quals(Node *leftop, Oid leftTypeOid, Oid opclass,
+ Const *prefix_const, Pattern_Prefix_Status pstatus) {
+ Oid oproid;
+ Expr *expr;
+ List *result;
+ Const *greaterstr;
+
+ Assert(pstatus != Pattern_Prefix_None);
+ if ( pstatus == Pattern_Prefix_Exact ) {
+ oproid = get_opclass_member_mchar(opclass, leftTypeOid, BTEqualStrategyNumber);
+
+ expr = make_opclause(oproid, BOOLOID, false,
+ (Expr *) leftop, (Expr *) prefix_const,
+ InvalidOid, InvalidOid);
+ result = list_make1(make_simple_restrictinfo(expr));
+ return result;
+ }
+
+ /* We can always say "x >= prefix". */
+ oproid = get_opclass_member_mchar(opclass, leftTypeOid, BTGreaterEqualStrategyNumber);
+
+ expr = make_opclause(oproid, BOOLOID, false,
+ (Expr *) leftop, (Expr *) prefix_const,
+ InvalidOid, InvalidOid);
+ result = list_make1(make_simple_restrictinfo(expr));
+
+ /* If we can create a string larger than the prefix, we can say
+ * "x < greaterstr". */
+
+ greaterstr = (Const*)DatumGetPointer( OidFunctionCall1(
+ mmGTOid,
+ PointerGetDatum( prefix_const )
+ ) );
+
+ if (greaterstr) {
+ oproid = get_opclass_member_mchar(opclass, leftTypeOid, BTLessStrategyNumber);
+
+ expr = make_opclause(oproid, BOOLOID, false,
+ (Expr *) leftop, (Expr *) greaterstr,
+ InvalidOid, InvalidOid);
+ result = lappend(result, make_simple_restrictinfo(expr));
+ }
+
+ return result;
+}
+
+
/****************************************************************************
* ---- ROUTINES FOR "SPECIAL" INDEXABLE OPERATORS ----
****************************************************************************/
@@ -3403,9 +3649,16 @@ match_special_index_operator(Expr *clause, Oid opfamily, Oid idxcollation,
pfree(prefix);
}
- /* done if the expression doesn't look indexable */
- if (!isIndexable)
+ if ( !isIndexable ) {
+ /* done if the expression doesn't look indexable,
+ but we should previously check it for mchar/mvarchar types */
+ if ( mchar_pattern_fixed_prefix(expr_op, InvalidOid,
+ patt, Pattern_Type_Like,
+ &prefix, NULL) != Pattern_Prefix_None ) {
+ return true;
+ }
return false;
+ }
/*
* Must also check that index's opfamily supports the operators we will
@@ -3661,6 +3914,14 @@ expand_indexqual_opclause(RestrictInfo *rinfo, Oid opfamily, Oid idxcollation)
Const *patt = (Const *) rightop;
Const *prefix = NULL;
Pattern_Prefix_Status pstatus;
+ Oid leftTypeOid;
+
+ pstatus = mchar_pattern_fixed_prefix(expr_op, opfamily,
+ patt, Pattern_Type_Like,
+ &prefix, &leftTypeOid);
+
+ if ( pstatus != Pattern_Prefix_None )
+ return mchar_prefix_quals(leftop, leftTypeOid, opfamily, prefix, pstatus);
/*
* LIKE and regex operators are not members of any btree index opfamily,
diff --git a/src/backend/optimizer/path/joinpath.c b/src/backend/optimizer/path/joinpath.c
index 7844a5b2a0..a06e5133ef 100644
--- a/src/backend/optimizer/path/joinpath.c
+++ b/src/backend/optimizer/path/joinpath.c
@@ -176,7 +176,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,
@@ -185,7 +186,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 7079b6ac3f..b76e0d8edb 100644
--- a/src/backend/optimizer/path/joinrels.c
+++ b/src/backend/optimizer/path/joinrels.c
@@ -1255,7 +1255,8 @@ mark_dummy_rel(RelOptInfo *rel)
/* Set up the dummy path */
add_path(rel, (Path *) create_append_path(NULL, rel, NIL, NIL,
rel->lateral_relids,
- 0, false, NIL, -1));
+ 0, false, NIL, -1,
+ false, NIL));
/* 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 5579c6b067..e6bb4e553f 100644
--- a/src/backend/optimizer/path/pathkeys.c
+++ b/src/backend/optimizer/path/pathkeys.c
@@ -17,15 +17,18 @@
*/
#include "postgres.h"
+#include "miscadmin.h"
#include "access/stratnum.h"
#include "nodes/makefuncs.h"
#include "nodes/nodeFuncs.h"
#include "nodes/plannodes.h"
#include "optimizer/clauses.h"
+#include "optimizer/cost.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/tlist.h"
#include "utils/lsyscache.h"
+#include "utils/selfuncs.h"
static bool pathkey_is_redundant(PathKey *new_pathkey, List *pathkeys);
@@ -328,6 +331,296 @@ pathkeys_contained_in(List *keys1, List *keys2)
return false;
}
+/*
+ * 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, 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(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 = *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
@@ -1625,7 +1918,7 @@ right_merge_direction(PlannerInfo *root, PathKey *pathkey)
* no good to order by just the first key(s) of the requested ordering.
* So the result is always either 0 or list_length(root->query_pathkeys).
*/
-static int
+int
pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
{
if (root->query_pathkeys == NIL)
@@ -1643,6 +1936,39 @@ pathkeys_useful_for_ordering(PlannerInfo *root, List *pathkeys)
return 0; /* path ordering not useful */
}
+/*
+ * 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.
@@ -1657,6 +1983,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;
@@ -1692,6 +2021,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 0e73f9cf4c..c5e84222b8 100644
--- a/src/backend/optimizer/plan/analyzejoins.c
+++ b/src/backend/optimizer/plan/analyzejoins.c
@@ -22,15 +22,19 @@
*/
#include "postgres.h"
+#include "catalog/pg_class.h"
#include "nodes/nodeFuncs.h"
#include "optimizer/clauses.h"
#include "optimizer/joininfo.h"
#include "optimizer/pathnode.h"
#include "optimizer/paths.h"
#include "optimizer/planmain.h"
+#include "optimizer/predtest.h"
+#include "optimizer/restrictinfo.h"
#include "optimizer/tlist.h"
#include "optimizer/var.h"
#include "utils/lsyscache.h"
+#include "utils/memutils.h"
/* local functions */
static bool join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo);
@@ -39,15 +43,17 @@ static void remove_rel_from_query(PlannerInfo *root, int relid,
static List *remove_rel_from_joinlist(List *joinlist, int relid, int *nremoved);
static bool rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel);
static bool rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel,
- List *clause_list);
+ List *clause_list, UniqueIndexInfo **info);
static Oid distinct_col_search(int colno, List *colnos, List *opids);
static bool is_innerrel_unique_for(PlannerInfo *root,
Relids joinrelids,
Relids outerrelids,
RelOptInfo *innerrel,
JoinType jointype,
- List *restrictlist);
+ List *restrictlist,
+ UniqueIndexInfo **info);
+static void change_rinfo(RestrictInfo* rinfo, Index from, Index to);
/*
* remove_useless_joins
@@ -58,7 +64,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;
@@ -162,7 +168,6 @@ join_is_removable(PlannerInfo *root, SpecialJoinInfo *sjinfo)
int innerrelid;
RelOptInfo *innerrel;
Relids joinrelids;
- List *clause_list = NIL;
ListCell *l;
int attroff;
@@ -238,67 +243,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.
@@ -568,7 +530,7 @@ reduce_unique_semijoins(PlannerInfo *root)
/* Test whether the innerrel is unique for those clauses. */
if (!innerrel_is_unique(root,
joinrelids, sjinfo->min_lefthand, innerrel,
- JOIN_SEMI, restrictlist, true))
+ JOIN_SEMI, restrictlist, true, NULL /*index_info*/))
continue;
/* OK, remove the SpecialJoinInfo from the list. */
@@ -643,9 +605,13 @@ rel_supports_distinctness(PlannerInfo *root, RelOptInfo *rel)
* Note that the passed-in clause_list may be destructively modified! This
* is OK for current uses, because the clause_list is built by the caller for
* the sole purpose of passing to this function.
+ *
+ * If unique_index is not null, it is set to point to the index that guarantees
+ * uniqueness for a base relation.
*/
static bool
-rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list)
+rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list,
+ UniqueIndexInfo **index_info)
{
/*
* We could skip a couple of tests here if we assume all callers checked
@@ -661,8 +627,8 @@ rel_is_distinct_for(PlannerInfo *root, RelOptInfo *rel, List *clause_list)
* relation_has_unique_index_for automatically adds any usable
* restriction clauses for the rel, so we needn't do that here.
*/
- if (relation_has_unique_index_for(root, rel, clause_list, NIL, NIL))
- return true;
+ return relation_has_unique_index_for(root, rel, clause_list, NIL, NIL,
+ index_info);
}
else if (rel->rtekind == RTE_SUBQUERY)
{
@@ -966,6 +932,10 @@ distinct_col_search(int colno, List *colnos, List *opids)
* heuristic about whether to cache negative answers; it should be "true"
* if making an inquiry that is not part of the normal bottom-up join search
* sequence.
+ *
+ * If index_info_out is not null, it is set to point to a new UniqueIndexInfo
+ * allocated in root memory context, that describes the index that guarantees
+ * uniqueness.
*/
bool
innerrel_is_unique(PlannerInfo *root,
@@ -974,12 +944,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;
+
+ if (index_info_out)
+ *index_info_out = NULL;
- /* Certainly can't prove uniqueness when there are no joinclauses */
+ /*
+ * It is possible to prove uniqueness even in the absence of joinclauses,
+ * just from baserestrictinfos alone. However, in these cases the inner
+ * relation returns one row at most, so join removal won't give much
+ * benefit. It seems better to save some planning time by ignoring these
+ * cases.
+ */
if (restrictlist == NIL)
return false;
@@ -999,10 +980,14 @@ innerrel_is_unique(PlannerInfo *root,
*/
foreach(lc, innerrel->unique_for_rels)
{
- Relids unique_for_rels = (Relids) lfirst(lc);
+ Relids unique_for_rels = (Relids) linitial(lfirst(lc));
if (bms_is_subset(unique_for_rels, outerrelids))
+ {
+ if (index_info_out)
+ *index_info_out = lsecond(lfirst(lc));
return true; /* Success! */
+ }
}
/*
@@ -1019,7 +1004,7 @@ innerrel_is_unique(PlannerInfo *root,
/* No cached information, so try to make the proof. */
if (is_innerrel_unique_for(root, joinrelids, outerrelids, innerrel,
- jointype, restrictlist))
+ jointype, restrictlist, &index_info))
{
/*
* Cache the positive result for future probes, being sure to keep it
@@ -1033,9 +1018,12 @@ innerrel_is_unique(PlannerInfo *root,
*/
old_context = MemoryContextSwitchTo(root->planner_cxt);
innerrel->unique_for_rels = lappend(innerrel->unique_for_rels,
- bms_copy(outerrelids));
+ list_make2(bms_copy(outerrelids), index_info));
MemoryContextSwitchTo(old_context);
+ if (index_info_out)
+ *index_info_out = index_info;
+
return true; /* Success! */
}
else
@@ -1081,7 +1069,8 @@ is_innerrel_unique_for(PlannerInfo *root,
Relids outerrelids,
RelOptInfo *innerrel,
JoinType jointype,
- List *restrictlist)
+ List *restrictlist,
+ UniqueIndexInfo **index_info)
{
List *clause_list = NIL;
ListCell *lc;
@@ -1123,5 +1112,1097 @@ 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)->varnoold = 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 *prev = NULL;
+ ListCell *cell = NULL;
+ ListCell *next = list_head(ec->ec_members);
+
+ while (next)
+ {
+ EquivalenceMember *em;
+ ListCell *otherCell;
+
+ prev = cell;
+ cell = next;
+ next = lnext(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, prev);
+ cell = prev;
+ break;
+ }
+ }
+ }
+}
+
+/*
+ * 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 *prev = NULL;
+ ListCell *cell = NULL;
+ ListCell *next = list_head(*sources);
+
+ while (next)
+ {
+ RestrictInfo *rinfo;
+ ListCell *otherCell;
+
+ prev = cell;
+ cell = next;
+ next = lnext(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, prev);
+ cell = prev;
+ 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);
+ }
+ }
+}
+
+/*
+ * 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;
+
+ /*
+ * 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, toKeep->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;
+}
+
+static int
+get_rte_level(Node *jtnode, Index rtindex, int level) {
+ if (jtnode == NULL)
+ return -1;
+ else if (IsA(jtnode, RangeTblRef))
+ {
+ if (rtindex == ((RangeTblRef *) jtnode)->rtindex)
+ return level;
+ }
+ else if (IsA(jtnode, FromExpr))
+ {
+ FromExpr *f = (FromExpr *) jtnode;
+ ListCell *l;
+
+ foreach(l, f->fromlist)
+ {
+ int sublevel = get_rte_level(lfirst(l), rtindex, level+1);
+
+ if (sublevel >= 0)
+ return sublevel;
+ }
+ }
+ else if (IsA(jtnode, JoinExpr))
+ {
+ JoinExpr *j = (JoinExpr *) jtnode;
+ int sublevel;
+
+ if ((sublevel = get_rte_level(j->larg, rtindex, level+1)) >= 0)
+ return sublevel;
+ if ((sublevel = get_rte_level(j->rarg, rtindex, level+1)) >= 0)
+ return sublevel;
+ }
+ else
+ elog(ERROR, "unrecognized node type: %d",
+ (int) nodeTag(jtnode));
+
+ return -1;
+}
+
+/*
+ * 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;
+
+ if (get_rte_level((Node*)root->parse->jointree, relids[o], 0) >
+ get_rte_level((Node*)root->parse->jointree, relids[i], 0))
+ {
+ /* wrap inner and outer to leave most deep relation */
+ int t = o;
+ RelOptInfo *ti = outer;
+
+ o = i; i = t;
+ outer = inner; inner = ti;
+ }
+
+ /*
+ * 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 90661f2b14..e83c139337 100644
--- a/src/backend/optimizer/plan/createplan.c
+++ b/src/backend/optimizer/plan/createplan.c
@@ -154,7 +154,6 @@ static Node *replace_nestloop_params(PlannerInfo *root, Node *expr);
static Node *replace_nestloop_params_mutator(Node *node, PlannerInfo *root);
static List *fix_indexqual_references(PlannerInfo *root, IndexPath *index_path);
static List *fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path);
-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);
@@ -1044,7 +1043,7 @@ static Plan *
create_append_plan(PlannerInfo *root, AppendPath *best_path)
{
Append *plan;
- List *tlist = build_path_tlist(root, &best_path->path);
+ List *tlist;
List *subplans = NIL;
ListCell *subpaths;
RelOptInfo *rel = best_path->path.parent;
@@ -1064,6 +1063,7 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
/* 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)),
@@ -1132,6 +1132,11 @@ create_append_plan(PlannerInfo *root, AppendPath *best_path)
* parent-rel Vars it'll be asked to emit.
*/
+ if (best_path->pull_tlist)
+ tlist = copyObject(((Plan*)linitial(subplans))->targetlist);
+ else
+ tlist = build_path_tlist(root, &best_path->path);
+
plan = make_append(subplans, best_path->first_partial_path,
tlist, best_path->partitioned_rels,
partpruneinfo);
@@ -4365,7 +4370,8 @@ replace_nestloop_params_mutator(Node *node, PlannerInfo *root)
}
return expression_tree_mutator(node,
replace_nestloop_params_mutator,
- (void *) root);
+ (void *) root,
+ 0);
}
/*
@@ -4575,7 +4581,7 @@ fix_indexorderby_references(PlannerInfo *root, IndexPath *index_path)
* 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 b05adc70c4..49c5f619a9 100644
--- a/src/backend/optimizer/plan/planmain.c
+++ b/src/backend/optimizer/plan/planmain.c
@@ -196,7 +196,7 @@ query_planner(PlannerInfo *root, List *tlist,
* 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.
@@ -204,6 +204,11 @@ query_planner(PlannerInfo *root, List *tlist,
*/
reduce_unique_semijoins(root);
+ /*
+ * Remove self joins on a unique column.
+ */
+ remove_useless_self_joins(root, &joinlist, 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 9b97937eb8..0930baa38e 100644
--- a/src/backend/optimizer/plan/planner.c
+++ b/src/backend/optimizer/plan/planner.c
@@ -256,11 +256,22 @@ PlannedStmt *
planner(Query *parse, int cursorOptions, ParamListInfo boundParams)
{
PlannedStmt *result;
+ instr_time planstart,
+ planduration;
+
+ INSTR_TIME_SET_CURRENT(planstart);
if (planner_hook)
result = (*planner_hook) (parse, cursorOptions, boundParams);
else
result = standard_planner(parse, 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;
}
@@ -1594,7 +1605,8 @@ inheritance_planner(PlannerInfo *root)
/* Make a dummy path, cf set_dummy_rel_pathlist() */
dummy_path = (Path *) create_append_path(NULL, final_rel, NIL, NIL,
- NULL, 0, false, NIL, -1);
+ NULL, 0, false, NIL, -1,
+ false, NIL);
/* These lists must be nonempty to make a valid ModifyTable node */
subpaths = list_make1(dummy_path);
@@ -3962,7 +3974,8 @@ create_degenerate_grouping_paths(PlannerInfo *root, RelOptInfo *input_rel,
0,
false,
NIL,
- -1);
+ -1,
+ false, NIL);
}
else
{
@@ -6237,18 +6250,45 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
{
Path *path = (Path *) lfirst(lc);
bool is_sorted;
+ List *group_pathkeys = root->group_pathkeys,
+ *group_clauses = parse->groupClause;
+ int n_preordered_groups = 0;
+
+ if (parse->groupingSets)
+ {
+ /*
+ * prevent group pathkey rreordering, just check the same
+ * order paths pathkeys and group pathkeys
+ */
+ is_sorted = pathkeys_contained_in(group_pathkeys,
+ path->pathkeys);
+ }
+ else
+ {
+ 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_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)
@@ -6268,9 +6308,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_SIMPLE,
- parse->groupClause,
+ group_clauses,
havingQual,
agg_costs,
dNumGroups));
@@ -6285,7 +6325,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
create_group_path(root,
grouped_rel,
path,
- parse->groupClause,
+ group_clauses,
havingQual,
dNumGroups));
}
@@ -6306,19 +6346,31 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
foreach(lc, partially_grouped_rel->pathlist)
{
Path *path = (Path *) lfirst(lc);
+ 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);
/*
* Insert a Sort node, if required. But there's no point in
* sorting anything but the cheapest path.
*/
- if (!pathkeys_contained_in(root->group_pathkeys, path->pathkeys))
+ if (n_preordered_groups != list_length(group_pathkeys))
{
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);
}
@@ -6328,9 +6380,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));
@@ -6339,7 +6391,7 @@ add_paths_to_grouping_rel(PlannerInfo *root, RelOptInfo *input_rel,
create_group_path(root,
grouped_rel,
path,
- parse->groupClause,
+ group_clauses,
havingQual,
dNumGroups));
}
@@ -6576,18 +6628,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 *)
@@ -6595,9 +6660,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));
@@ -6606,7 +6671,7 @@ create_partial_grouping_paths(PlannerInfo *root,
create_group_path(root,
partially_grouped_rel,
path,
- parse->groupClause,
+ group_clauses,
NIL,
dNumPartialGroups));
}
@@ -6620,18 +6685,32 @@ 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_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 *)
@@ -6639,9 +6718,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));
@@ -6650,7 +6729,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 a6ad28570a..b2019d97a8 100644
--- a/src/backend/optimizer/plan/setrefs.c
+++ b/src/backend/optimizer/plan/setrefs.c
@@ -1596,7 +1596,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
@@ -1920,7 +1920,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);
}
/*
@@ -2276,6 +2276,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)
{
@@ -2290,6 +2294,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,
@@ -2359,7 +2366,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);
}
/*
@@ -2480,7 +2487,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 04456df10d..30eb242989 100644
--- a/src/backend/optimizer/plan/subselect.c
+++ b/src/backend/optimizer/plan/subselect.c
@@ -716,7 +716,7 @@ convert_testexpr_mutator(Node *node,
}
return expression_tree_mutator(node,
convert_testexpr_mutator,
- (void *) context);
+ (void *) context, 0);
}
/*
@@ -1785,7 +1785,7 @@ replace_correlation_vars_mutator(Node *node, PlannerInfo *root)
}
return expression_tree_mutator(node,
replace_correlation_vars_mutator,
- (void *) root);
+ (void *) root, 0);
}
/*
@@ -1933,7 +1933,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 c8465a14af..6ac2082686 100644
--- a/src/backend/optimizer/prep/prepunion.c
+++ b/src/backend/optimizer/prep/prepunion.c
@@ -656,7 +656,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
* Append the child results together.
*/
path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
- NULL, 0, false, NIL, -1);
+ NULL, 0, false, NIL, -1, false, NIL);
/*
* For UNION ALL, we just need the Append path. For UNION, need to add
@@ -712,7 +712,7 @@ generate_union_paths(SetOperationStmt *op, PlannerInfo *root,
ppath = (Path *)
create_append_path(root, result_rel, NIL, partial_pathlist,
NULL, parallel_workers, enable_parallel_append,
- NIL, -1);
+ NIL, -1, false, NIL);
ppath = (Path *)
create_gather_path(root, result_rel, ppath,
result_rel->reltarget, NULL, NULL);
@@ -822,7 +822,7 @@ generate_nonunion_paths(SetOperationStmt *op, PlannerInfo *root,
* Append the child results together.
*/
path = (Path *) create_append_path(root, result_rel, pathlist, NIL,
- NULL, 0, false, NIL, -1);
+ NULL, 0, false, NIL, -1, false, NIL);
/* Identify the grouping semantics */
groupList = generate_setop_grouplist(op, tlist);
@@ -2238,7 +2238,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++)
{
@@ -2259,7 +2259,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,
@@ -2341,7 +2341,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 edb56ab3a4..29224f24ba 100644
--- a/src/backend/optimizer/util/clauses.c
+++ b/src/backend/optimizer/util/clauses.c
@@ -2626,7 +2626,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.
@@ -2754,7 +2754,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,
@@ -2898,7 +2898,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
@@ -4192,7 +4192,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;
}
@@ -4950,7 +4950,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);
}
/*
@@ -5414,7 +5414,7 @@ substitute_actual_srf_parameters_mutator(Node *node,
}
return expression_tree_mutator(node,
substitute_actual_srf_parameters_mutator,
- (void *) context);
+ (void *) context, 0);
}
/*
diff --git a/src/backend/optimizer/util/pathnode.c b/src/backend/optimizer/util/pathnode.c
index aa8f2de72a..b0244828f1 100644
--- a/src/backend/optimizer/util/pathnode.c
+++ b/src/backend/optimizer/util/pathnode.c
@@ -207,6 +207,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;
@@ -1253,7 +1265,8 @@ create_append_path(PlannerInfo *root,
List *subpaths, List *partial_subpaths,
Relids required_outer,
int parallel_workers, bool parallel_aware,
- List *partitioned_rels, double rows)
+ List *partitioned_rels, double rows,
+ bool pull_tlist, List *pathkeys)
{
AppendPath *pathnode = makeNode(AppendPath);
ListCell *l;
@@ -1285,8 +1298,10 @@ create_append_path(PlannerInfo *root,
pathnode->path.parallel_aware = parallel_aware;
pathnode->path.parallel_safe = rel->consider_parallel;
pathnode->path.parallel_workers = parallel_workers;
- pathnode->path.pathkeys = NIL; /* result is always considered unsorted */
+ pathnode->path.pathkeys = pathkeys; /* !=NIL in case of append OR index
+ scans */
pathnode->partitioned_rels = list_copy(partitioned_rels);
+ pathnode->pull_tlist = pull_tlist;
/*
* For parallel append, non-partial paths are sorted by descending total
@@ -1617,7 +1632,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;
@@ -3547,9 +3563,13 @@ create_limit_path(PlannerInfo *root, RelOptInfo *rel,
if (count_rows > pathnode->path.rows)
count_rows = pathnode->path.rows;
if (subpath->rows > 0)
+ {
+ pathnode->path.startup_cost +=
+ 2*(subpath->total_cost - subpath->startup_cost) / subpath->rows;
pathnode->path.total_cost = pathnode->path.startup_cost +
(subpath->total_cost - subpath->startup_cost)
* count_rows / subpath->rows;
+ }
pathnode->path.rows = count_rows;
if (pathnode->path.rows < 1)
pathnode->path.rows = 1;
@@ -3663,7 +3683,8 @@ reparameterize_path(PlannerInfo *root, Path *path,
apath->path.parallel_workers,
apath->path.parallel_aware,
apath->partitioned_rels,
- -1);
+ -1,
+ apath->pull_tlist, apath->path.pathkeys);
}
default:
break;
diff --git a/src/backend/optimizer/util/plancat.c b/src/backend/optimizer/util/plancat.c
index c8bab50e89..779f52603d 100644
--- a/src/backend/optimizer/util/plancat.c
+++ b/src/backend/optimizer/util/plancat.c
@@ -423,6 +423,8 @@ get_relation_info(PlannerInfo *root, Oid relationObjectId, bool inhparent,
index_close(indexRelation, NoLock);
+ memset(info->sslots, 0, sizeof(info->sslots));
+
indexinfos = lcons(info, indexinfos);
}
diff --git a/src/backend/optimizer/util/relnode.c b/src/backend/optimizer/util/relnode.c
index 2dc8b16765..15e95aa22c 100644
--- a/src/backend/optimizer/util/relnode.c
+++ b/src/backend/optimizer/util/relnode.c
@@ -38,14 +38,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,
@@ -548,7 +544,7 @@ build_join_rel(PlannerInfo *root,
*/
if (restrictlist_ptr)
*restrictlist_ptr = build_joinrel_restrictlist(root,
- joinrel,
+ joinrel->relids,
outer_rel,
inner_rel);
return joinrel;
@@ -651,7 +647,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;
@@ -973,7 +969,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.
*
@@ -986,9 +982,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)
{
@@ -999,8 +995,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
@@ -1009,7 +1005,7 @@ build_joinrel_restrictlist(PlannerInfo *root,
*/
result = list_concat(result,
generate_join_implied_equalities(root,
- joinrel->relids,
+ joinrelids,
outer_rel->relids,
inner_rel));
@@ -1034,18 +1030,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
@@ -1054,7 +1098,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
{
@@ -1065,7 +1109,8 @@ subbuild_joinrel_restrictlist(RelOptInfo *joinrel,
}
}
- return new_restrictlist;
+ hash_destroy(upl.h);
+ return upl.unique_list;
}
static List *
@@ -1074,6 +1119,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);
@@ -1099,11 +1148,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 d5f8e879d8..a76b71abcd 100644
--- a/src/backend/optimizer/util/var.c
+++ b/src/backend/optimizer/util/var.c
@@ -770,7 +770,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)
{
@@ -806,7 +806,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 2a4ce294eb..5eb64dcd36 100644
--- a/src/backend/parser/gram.y
+++ b/src/backend/parser/gram.y
@@ -13029,7 +13029,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, "~~",
@@ -13042,7 +13042,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, "!~~",
@@ -13055,7 +13055,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, "~~*",
@@ -13068,7 +13068,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, "!~~*",
@@ -13077,7 +13077,7 @@ a_expr: c_expr { $$ = $1; }
| a_expr SIMILAR TO a_expr %prec SIMILAR
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($4, makeNullAConst(-1)),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
@@ -13085,7 +13085,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr SIMILAR TO a_expr ESCAPE a_expr %prec SIMILAR
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($4, $6),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "~",
@@ -13093,7 +13093,7 @@ a_expr: c_expr { $$ = $1; }
}
| a_expr NOT_LA SIMILAR TO a_expr %prec NOT_LA
{
- FuncCall *n = makeFuncCall(SystemFuncName("similar_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_escape")),
list_make2($5, makeNullAConst(-1)),
@2);
$$ = (Node *) makeSimpleA_Expr(AEXPR_SIMILAR, "!~",
@@ -13101,7 +13101,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_escape"),
+ FuncCall *n = makeFuncCall(list_make1(makeString("similar_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 e3106a7218..2060e901d1 100644
--- a/src/backend/parser/parse_clause.c
+++ b/src/backend/parser/parse_clause.c
@@ -327,6 +327,9 @@ extractRemainingColumns(List *common_colnames,
}
}
+ list_free(*res_colnames);
+ list_free(*res_colvars);
+
*res_colnames = new_colnames;
*res_colvars = new_colvars;
}
@@ -1274,6 +1277,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 384f33aef8..37efe81695 100644
--- a/src/backend/parser/parse_relation.c
+++ b/src/backend/parser/parse_relation.c
@@ -1840,7 +1840,6 @@ addRangeTableEntryForJoin(ParseState *pstate,
{
RangeTblEntry *rte = makeNode(RangeTblEntry);
Alias *eref;
- int numaliases;
Assert(pstate != NULL);
@@ -1861,19 +1860,37 @@ addRangeTableEntryForJoin(ParseState *pstate,
rte->joinaliasvars = aliasvars;
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;
@@ -2449,8 +2466,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 f1f4212b5d..af1d5689d4 100644
--- a/src/backend/rewrite/rewriteManip.c
+++ b/src/backend/rewrite/rewriteManip.c
@@ -1112,7 +1112,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)
{
@@ -1182,14 +1182,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);
}
@@ -1344,7 +1345,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 03ec6b0142..74841c734e 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -836,6 +836,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;
@@ -855,6 +856,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 (;;)
{
@@ -870,12 +880,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;
}
@@ -897,6 +907,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/sinvaladt.c b/src/backend/storage/ipc/sinvaladt.c
index 569f1058fa..e803ed3550 100644
--- a/src/backend/storage/ipc/sinvaladt.c
+++ b/src/backend/storage/ipc/sinvaladt.c
@@ -128,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)
diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c
index 5c6a2fbcc9..49e8028379 100644
--- a/src/backend/tcop/postgres.c
+++ b/src/backend/tcop/postgres.c
@@ -70,6 +70,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"
@@ -2987,6 +2988,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 */
@@ -4190,6 +4193,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/rowtypes.c b/src/backend/utils/adt/rowtypes.c
index 5f3dbc59d5..4695c19a2a 100644
--- a/src/backend/utils/adt/rowtypes.c
+++ b/src/backend/utils/adt/rowtypes.c
@@ -26,7 +26,6 @@
#include "utils/lsyscache.h"
#include "utils/typcache.h"
-
/*
* structure to cache metadata needed for record I/O
*/
@@ -785,6 +784,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;
@@ -869,6 +871,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
@@ -1027,6 +1032,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;
@@ -1111,6 +1119,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 3749aa988e..124f4ab830 100644
--- a/src/backend/utils/adt/ruleutils.c
+++ b/src/backend/utils/adt/ruleutils.c
@@ -273,6 +273,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 */
@@ -348,6 +350,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,
@@ -4205,7 +4208,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];
@@ -4254,7 +4260,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];
@@ -4279,6 +4288,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?
*
@@ -4291,6 +4323,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++)
{
@@ -4342,6 +4443,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 4ad90a800a..7c6679a301 100644
--- a/src/backend/utils/adt/selfuncs.c
+++ b/src/backend/utils/adt/selfuncs.c
@@ -165,15 +165,12 @@ static double var_eq_const(VariableStatData *vardata, Oid operator,
static double var_eq_non_const(VariableStatData *vardata, Oid operator,
Node *other,
bool varonleft, bool negate);
-static double ineq_histogram_selectivity(PlannerInfo *root,
- VariableStatData *vardata,
- FmgrInfo *opproc, bool isgt, bool iseq,
- Datum constval, Oid consttype);
static double eqjoinsel_inner(Oid operator,
- VariableStatData *vardata1, VariableStatData *vardata2);
+ VariableStatData *vardata1, VariableStatData *vardata2,
+ int record_cmp_prefix);
static double eqjoinsel_semi(Oid operator,
VariableStatData *vardata1, VariableStatData *vardata2,
- 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, double *scaledvalue,
@@ -300,6 +297,31 @@ 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
*
@@ -309,6 +331,18 @@ static double
var_eq_const(VariableStatData *vardata, Oid operator,
Datum constval, bool constisnull,
bool varonleft, bool negate)
+{
+ return eqconst_selectivity(operator, vardata, constval, constisnull,
+ varonleft, negate, -1);
+}
+
+
+Selectivity
+eqconst_selectivity(Oid operator,
+ VariableStatData *vardata,
+ Datum constval, bool constisnull,
+ bool varonleft, bool negate,
+ int record_cmp_prefix)
{
double selec;
double nullfrac = 0.0;
@@ -341,7 +375,8 @@ var_eq_const(VariableStatData *vardata, Oid operator,
* 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;
}
@@ -360,9 +395,9 @@ var_eq_const(VariableStatData *vardata, Oid operator,
* 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))
{
FmgrInfo eqproc;
@@ -372,15 +407,17 @@ var_eq_const(VariableStatData *vardata, Oid operator,
{
/* be careful to apply operator right way 'round */
if (varonleft)
- match = DatumGetBool(FunctionCall2Coll(&eqproc,
+ match = DatumGetBool(FunctionCall3Coll(&eqproc,
DEFAULT_COLLATION_OID,
sslot.values[i],
- constval));
+ constval,
+ Int32GetDatum(record_cmp_prefix)));
else
- match = DatumGetBool(FunctionCall2Coll(&eqproc,
+ match = DatumGetBool(FunctionCall3Coll(&eqproc,
DEFAULT_COLLATION_OID,
constval,
- sslot.values[i]));
+ sslot.values[i],
+ Int32GetDatum(record_cmp_prefix)));
if (match)
break;
}
@@ -606,7 +643,8 @@ scalarineqsel(PlannerInfo *root, Oid operator, bool isgt, bool iseq,
*/
hist_selec = ineq_histogram_selectivity(root, vardata,
&opproc, isgt, iseq,
- constval, consttype);
+ constval, consttype,
+ -1);
/*
* Now merge the results from the MCV and histogram calculations,
@@ -771,6 +809,136 @@ histogram_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
return result;
}
+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
*
@@ -784,11 +952,12 @@ histogram_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
* null entries. The caller is expected to combine this result with
* statistics for those portions of the column population.
*/
-static double
+Selectivity
ineq_histogram_selectivity(PlannerInfo *root,
VariableStatData *vardata,
FmgrInfo *opproc, bool isgt, bool iseq,
- Datum constval, Oid consttype)
+ Datum constval, Oid consttype,
+ int record_cmp_prefix)
{
double hist_selec;
AttStatsSlot sslot;
@@ -875,10 +1044,11 @@ ineq_histogram_selectivity(PlannerInfo *root,
NULL,
&sslot.values[probe]);
- ltcmp = DatumGetBool(FunctionCall2Coll(opproc,
+ ltcmp = DatumGetBool(FunctionCall3Coll(opproc,
DEFAULT_COLLATION_OID,
sslot.values[probe],
- constval));
+ constval,
+ Int32GetDatum(record_cmp_prefix)));
if (isgt)
ltcmp = !ltcmp;
if (ltcmp)
@@ -2302,21 +2472,39 @@ eqjoinsel(PG_FUNCTION_ARGS)
JoinType jointype = (JoinType) PG_GETARG_INT16(3);
#endif
SpecialJoinInfo *sjinfo = (SpecialJoinInfo *) PG_GETARG_POINTER(4);
- double selec;
VariableStatData vardata1;
VariableStatData vardata2;
bool join_is_reversed;
- RelOptInfo *inner_rel;
+ double selec;
get_join_variables(root, args, sjinfo,
&vardata1, &vardata2, &join_is_reversed);
+ selec = join_is_reversed
+ ? eqjoin_selectivity(root, operator, &vardata2, &vardata1, sjinfo, -1)
+ : eqjoin_selectivity(root, operator, &vardata1, &vardata2, sjinfo, -1);
+
+ ReleaseVariableStats(vardata1);
+ ReleaseVariableStats(vardata2);
+
+ PG_RETURN_FLOAT8((float8)selec);
+}
+
+Selectivity
+eqjoin_selectivity(PlannerInfo *root, Oid operator, VariableStatData* vardata1,
+ VariableStatData* vardata2, SpecialJoinInfo *sjinfo,
+ int record_cmp_prefix)
+{
+ Selectivity selec;
+ RelOptInfo *inner_rel;
+
switch (sjinfo->jointype)
{
case JOIN_INNER:
case JOIN_LEFT:
case JOIN_FULL:
- selec = eqjoinsel_inner(operator, &vardata1, &vardata2);
+ selec = eqjoinsel_inner(operator, vardata1, vardata2,
+ record_cmp_prefix);
break;
case JOIN_SEMI:
case JOIN_ANTI:
@@ -2329,13 +2517,8 @@ eqjoinsel(PG_FUNCTION_ARGS)
*/
inner_rel = find_join_input_rel(root, sjinfo->min_righthand);
- if (!join_is_reversed)
- selec = eqjoinsel_semi(operator, &vardata1, &vardata2,
- inner_rel);
- else
- selec = eqjoinsel_semi(get_commutator(operator),
- &vardata2, &vardata1,
- inner_rel);
+ selec = eqjoinsel_semi(operator, vardata1, vardata2, inner_rel,
+ record_cmp_prefix);
break;
default:
/* other values not expected here */
@@ -2345,12 +2528,121 @@ eqjoinsel(PG_FUNCTION_ARGS)
break;
}
- 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;
+
+ 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;
+
+ /* == from fulleq, for example */
+ orderop = get_ordering_op_for_equality_op(eqop, true);
+
+ if (!OidIsValid(orderop))
+ goto out;
+
+ fmgr_info(get_opcode(eqop), &eqproc);
+ fmgr_info(get_opcode(orderop), <proc);
+
+ result = 0.0;
+ while(i1 < sslot1.nvalues && i2 < sslot2.nvalues)
+ {
+ int cmp;
+
+ cmp = cmp_vardata(&eqproc, <proc, 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;
}
/*
@@ -2361,7 +2653,8 @@ eqjoinsel(PG_FUNCTION_ARGS)
*/
static double
eqjoinsel_inner(Oid operator,
- VariableStatData *vardata1, VariableStatData *vardata2)
+ VariableStatData *vardata1, VariableStatData *vardata2,
+ int record_cmp_prefix)
{
double selec;
double nd1;
@@ -2389,9 +2682,9 @@ eqjoinsel_inner(Oid operator,
/* 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);
+ have_mcvs1 = get_cached_attstatsslot(&sslot1, vardata1,
+ STATISTIC_KIND_MCV, InvalidOid,
+ ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
}
if (HeapTupleIsValid(vardata2->statsTuple))
@@ -2399,9 +2692,9 @@ eqjoinsel_inner(Oid operator,
/* 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);
+ have_mcvs2 = get_cached_attstatsslot(&sslot2, vardata2,
+ STATISTIC_KIND_MCV, InvalidOid,
+ ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
}
if (have_mcvs1 && have_mcvs2)
@@ -2455,10 +2748,11 @@ eqjoinsel_inner(Oid operator,
{
if (hasmatch2[j])
continue;
- if (DatumGetBool(FunctionCall2Coll(&eqproc,
+ if (DatumGetBool(FunctionCall3Coll(&eqproc,
DEFAULT_COLLATION_OID,
sslot1.values[i],
- sslot2.values[j])))
+ sslot2.values[j],
+ Int32GetDatum(record_cmp_prefix))))
{
hasmatch1[i] = hasmatch2[j] = true;
matchprodfreq += sslot1.numbers[i] * sslot2.numbers[j];
@@ -2556,11 +2850,34 @@ eqjoinsel_inner(Oid operator,
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);
}
free_attstatsslot(&sslot1);
@@ -2579,7 +2896,7 @@ eqjoinsel_inner(Oid operator,
static double
eqjoinsel_semi(Oid operator,
VariableStatData *vardata1, VariableStatData *vardata2,
- RelOptInfo *inner_rel)
+ RelOptInfo *inner_rel, int record_cmp_prefix)
{
double selec;
double nd1;
@@ -2639,17 +2956,17 @@ eqjoinsel_semi(Oid operator,
/* 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);
+ have_mcvs1 = get_cached_attstatsslot(&sslot1, vardata1,
+ STATISTIC_KIND_MCV, InvalidOid,
+ ATTSTATSSLOT_VALUES | ATTSTATSSLOT_NUMBERS);
}
if (HeapTupleIsValid(vardata2->statsTuple) &&
statistic_proc_security_check(vardata2, opfuncoid))
{
- have_mcvs2 = get_attstatsslot(&sslot2, vardata2->statsTuple,
- STATISTIC_KIND_MCV, InvalidOid,
- ATTSTATSSLOT_VALUES);
+ have_mcvs2 = get_cached_attstatsslot(&sslot2, vardata2,
+ STATISTIC_KIND_MCV, InvalidOid,
+ ATTSTATSSLOT_VALUES);
/* note: currently don't need stanumbers from RHS */
}
@@ -2702,10 +3019,11 @@ eqjoinsel_semi(Oid operator,
{
if (hasmatch2[j])
continue;
- if (DatumGetBool(FunctionCall2Coll(&eqproc,
+ if (DatumGetBool(FunctionCall3Coll(&eqproc,
DEFAULT_COLLATION_OID,
sslot1.values[i],
- sslot2.values[j])))
+ sslot2.values[j],
+ Int32GetDatum(record_cmp_prefix))))
{
hasmatch1[i] = hasmatch2[j] = true;
nmatches++;
@@ -3362,7 +3680,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
@@ -3430,11 +3751,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
@@ -3444,6 +3774,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
@@ -3461,7 +3794,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);
@@ -3470,6 +3803,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;
@@ -3511,6 +3852,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
@@ -3531,13 +3875,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);
@@ -3545,9 +3897,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.
@@ -6324,7 +6682,8 @@ prefix_selectivity(PlannerInfo *root, VariableStatData *vardata,
prefixsel = ineq_histogram_selectivity(root, vardata,
&opproc, true, true,
prefixcon->constvalue,
- prefixcon->consttype);
+ prefixcon->consttype,
+ -1);
if (prefixsel < 0.0)
{
@@ -6351,7 +6710,8 @@ prefix_selectivity(PlannerInfo *root, VariableStatData *vardata,
topsel = ineq_histogram_selectivity(root, vardata,
&opproc, false, false,
greaterstrcon->constvalue,
- greaterstrcon->consttype);
+ greaterstrcon->consttype,
+ -1);
/* ineq_histogram_selectivity worked before, it shouldn't fail now */
Assert(topsel >= 0.0);
@@ -6964,7 +7324,7 @@ other_operands_eval_cost(PlannerInfo *root, List *qinfos)
IndexQualInfo *qinfo = (IndexQualInfo *) lfirst(lc);
QualCost index_qual_cost;
- cost_qual_eval_node(&index_qual_cost, qinfo->other_operand, root);
+ cost_qual_eval_node_index(&index_qual_cost, qinfo->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/lsyscache.c b/src/backend/utils/cache/lsyscache.c
index 913cad3510..f9ba744663 100644
--- a/src/backend/utils/cache/lsyscache.c
+++ b/src/backend/utils/cache/lsyscache.c
@@ -42,6 +42,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"
@@ -3045,6 +3046,53 @@ 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 cntx;
+
+ 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);
+
+ /*
+ * GEQO could call us in short-lived memory context, use rather long-lived
+ * context to cache statstic data
+ */
+ cntx = MemoryContextSwitchTo(MessageContext);
+
+ if (get_attstatsslot(sslot, statstuple, reqkind, reqop,
+ add_flags | has_flags))
+ sslot->incache = true;
+ else
+ sslot = NULL;
+
+ MemoryContextSwitchTo(cntx);
+
+ return sslot;
+}
+
/*
* free_attstatsslot
* Free data allocated by get_attstatsslot
@@ -3052,6 +3100,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/init/globals.c b/src/backend/utils/init/globals.c
index f7d6617a13..af85f53a18 100644
--- a/src/backend/utils/init/globals.c
+++ b/src/backend/utils/init/globals.c
@@ -30,6 +30,7 @@ ProtocolVersion FrontendProtocol;
volatile bool InterruptPending = false;
volatile bool QueryCancelPending = false;
volatile bool ProcDiePending = false;
+volatile sig_atomic_t CheckClientConnectionPending = false;
volatile bool ClientConnectionLost = false;
volatile bool IdleInTransactionSessionTimeoutPending = false;
volatile sig_atomic_t ConfigReloadPending = false;
diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c
index 6dd67c054d..011a212f88 100644
--- a/src/backend/utils/init/postinit.c
+++ b/src/backend/utils/init/postinit.c
@@ -72,6 +72,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);
@@ -608,6 +609,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);
}
/*
@@ -1208,6 +1210,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 1a9705af66..67ef7b8ad3 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -119,6 +119,7 @@ extern char *default_tablespace;
extern char *temp_tablespaces;
extern bool ignore_checksum_failure;
extern bool synchronize_seqscans;
+extern bool enable_self_join_removal;
#ifdef TRACE_SYNCSCAN
extern bool trace_syncscan;
@@ -974,6 +975,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."),
@@ -1824,7 +1835,6 @@ static struct config_bool ConfigureNamesBool[] =
*/
NULL, NULL, NULL
},
-
{
{"jit_tuple_deforming", PGC_USERSET, DEVELOPER_OPTIONS,
gettext_noop("Allow JIT compilation of tuple deforming."),
diff --git a/src/backend/utils/mmgr/aset.c b/src/backend/utils/mmgr/aset.c
index 77727f60ce..fb595189f3 100644
--- a/src/backend/utils/mmgr/aset.c
+++ b/src/backend/utils/mmgr/aset.c
@@ -1439,7 +1439,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 c24147a5c9..e0a3cd8780 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 ac04dcd944..43fd17bec0 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"));
@@ -477,6 +478,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'},
@@ -518,7 +520,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)
@@ -541,6 +543,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 1f93907157..2a7e72badc 100644
--- a/src/bin/pg_basebackup/pg_recvlogical.c
+++ b/src/bin/pg_basebackup/pg_recvlogical.c
@@ -335,11 +335,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)
{
fprintf(stderr,
diff --git a/src/bin/pg_basebackup/streamutil.c b/src/bin/pg_basebackup/streamutil.c
index 1510b7cc08..4362087646 100644
--- a/src/bin/pg_basebackup/streamutil.c
+++ b/src/bin/pg_basebackup/streamutil.c
@@ -49,6 +49,7 @@ char *dbhost = NULL;
char *dbuser = NULL;
char *dbport = NULL;
char *dbname = NULL;
+int useumask = 0; /* 0=auto, -1=never, 1=always */
int dbgetpassword = 0; /* 0=auto, -1=never, 1=always */
static 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 6854bbc31d..217b80a20b 100644
--- a/src/bin/pg_basebackup/streamutil.h
+++ b/src/bin/pg_basebackup/streamutil.h
@@ -23,6 +23,7 @@ extern char *dbhost;
extern char *dbuser;
extern char *dbport;
extern char *dbname;
+extern int useumask;
extern int dbgetpassword;
extern uint32 WalSegSz;
diff --git a/src/bin/pg_basebackup/walmethods.c b/src/bin/pg_basebackup/walmethods.c
index fdcb2c9de2..c2f7098635 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;
@@ -627,6 +629,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();
@@ -638,7 +642,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 da5556035f..26f4235213 100644
--- a/src/bin/pg_dump/common.c
+++ b/src/bin/pg_dump/common.c
@@ -54,6 +54,7 @@ static DumpableObject **oprinfoindex;
static DumpableObject **collinfoindex;
static DumpableObject **nspinfoindex;
static DumpableObject **extinfoindex;
+static DumpableObject **idxinfoindex;
static DumpableObject **pubinfoindex;
static int numTables;
static int numTypes;
@@ -62,6 +63,7 @@ static int numOperators;
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 */
@@ -79,9 +81,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
@@ -99,6 +100,7 @@ getSchemaData(Archive *fout, int *numTablesPtr)
ExtensionInfo *extinfo;
PublicationInfo *pubinfo;
InhInfo *inhinfo;
+ IndxInfo *idxinfo;
int numAggregates;
int numInherits;
int numRules;
@@ -261,7 +263,8 @@ getSchemaData(Archive *fout, int *numTablesPtr)
if (g_verbose)
write_msg(NULL, "reading indexes\n");
- getIndexes(fout, tblinfo, numTables);
+ idxinfo = getIndexes(fout, tblinfo, numTables, &numIndexes);
+ idxinfoindex = buildIndexArray(idxinfo, numIndexes, sizeof(IndxInfo));
if (g_verbose)
write_msg(NULL, "flagging indexes in partitioned tables\n");
@@ -419,7 +422,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)
@@ -953,15 +956,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 56d5571366..071daba221 100644
--- a/src/bin/pg_dump/pg_dump.c
+++ b/src/bin/pg_dump/pg_dump.c
@@ -1527,11 +1527,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;
@@ -4335,6 +4346,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",
@@ -6803,8 +6817,8 @@ getInherits(Archive *fout, int *numInherits)
* 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;
@@ -6835,6 +6849,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++)
{
@@ -7080,6 +7097,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++)
{
@@ -7149,6 +7167,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;
}
/*
@@ -16489,8 +16528,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);
@@ -18305,6 +18349,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:
@@ -18321,7 +18386,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 c4c34513eb..f0a9effcd4 100644
--- a/src/bin/pg_dump/pg_dump.h
+++ b/src/bin/pg_dump/pg_dump.h
@@ -672,6 +672,7 @@ extern OprInfo *findOprByOid(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);
@@ -701,7 +702,8 @@ extern ConvInfo *getConversions(Archive *fout, int *numConversions);
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 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/bin/pg_upgrade/test.sh b/src/bin/pg_upgrade/test.sh
index d4c4320a04..05f0ec2346 100644
--- a/src/bin/pg_upgrade/test.sh
+++ b/src/bin/pg_upgrade/test.sh
@@ -193,7 +193,7 @@ if "$MAKE" -C "$oldsrc" installcheck; then
|| psql_fix_sql_status=$?
fi
- pg_dumpall --no-sync -f "$temp_root"/dump1.sql || pg_dumpall1_status=$?
+ pg_dumpall --no-sync | sort > "$temp_root"/dump1.sql || pg_dumpall1_status=$?
if [ "$newsrc" != "$oldsrc" ]; then
# update references to old source tree's regress.so etc
@@ -263,7 +263,7 @@ case $testhost in
*) sh ./analyze_new_cluster.sh ;;
esac
-pg_dumpall --no-sync -f "$temp_root"/dump2.sql || pg_dumpall2_status=$?
+pg_dumpall --no-sync | sort > "$temp_root"/dump2.sql || pg_dumpall2_status=$?
pg_ctl -m fast stop
if [ -n "$pg_dumpall2_status" ]; then
diff --git a/src/include/executor/execExpr.h b/src/include/executor/execExpr.h
index b0a34a10ec..5e8a0316f2 100644
--- a/src/include/executor/execExpr.h
+++ b/src/include/executor/execExpr.h
@@ -336,12 +336,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;
@@ -600,6 +605,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 23e9905b1e..8803760cf9 100644
--- a/src/include/libpq/libpq.h
+++ b/src/include/libpq/libpq.h
@@ -72,6 +72,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 7c9f319b67..c8e80dde60 100644
--- a/src/include/miscadmin.h
+++ b/src/include/miscadmin.h
@@ -92,6 +92,7 @@ extern PGDLLIMPORT volatile bool ProcDiePending;
extern PGDLLIMPORT volatile bool IdleInTransactionSessionTimeoutPending;
extern PGDLLIMPORT volatile sig_atomic_t ConfigReloadPending;
+extern PGDLLIMPORT volatile sig_atomic_t CheckClientConnectionPending;
extern volatile bool 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 0ae7ac88d6..6f58939d94 100644
--- a/src/include/nodes/execnodes.h
+++ b/src/include/nodes/execnodes.h
@@ -108,6 +108,8 @@ typedef struct ExprState
Datum *innermost_domainval;
bool *innermost_domainnull;
+
+ bool guaranteed_empty;
} ExprState;
@@ -868,6 +870,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;
/*
@@ -969,6 +972,8 @@ typedef struct PlanState
* descriptor, without encoding knowledge about all executor nodes.
*/
TupleDesc scandesc;
+
+ bool guaranteed_empty;
} PlanState;
/* ----------------
diff --git a/src/include/nodes/nodeFuncs.h b/src/include/nodes/nodeFuncs.h
index 0c9a2bc469..64efa64a8c 100644
--- a/src/include/nodes/nodeFuncs.h
+++ b/src/include/nodes/nodeFuncs.h
@@ -25,6 +25,7 @@
#define QTW_EXAMINE_RTES 0x10 /* examine RTEs */
#define QTW_DONT_COPY_QUERY 0x20 /* 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);
@@ -54,7 +55,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/plannodes.h b/src/include/nodes/plannodes.h
index 6a161b2df9..029b9b744d 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"
/* ----------------------------------------------------------------
@@ -96,6 +97,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/relation.h b/src/include/nodes/relation.h
index c658571418..fa8d88706e 100644
--- a/src/include/nodes/relation.h
+++ b/src/include/nodes/relation.h
@@ -15,11 +15,13 @@
#define RELATION_H
#include "access/sdir.h"
+#include "catalog/pg_statistic.h"
#include "fmgr.h"
#include "lib/stringinfo.h"
#include "nodes/params.h"
#include "nodes/parsenodes.h"
#include "storage/block.h"
+#include "utils/lsyscache.h"
/*
@@ -515,8 +517,10 @@ typedef struct PartitionSchemeData *PartitionScheme;
* populate these fields, for base rels; but someday they might be used for
* join rels too:
*
- * unique_for_rels - list of Relid sets, each one being a set of other
- * rels for which this one has been proven unique
+ * unique_for_rels - list of (Relids, UniqueIndexInfo*) lists, where Relids
+ * is a set of other rels for which this one has been proven
+ * unique, and UniqueIndexInfo* stores information about the
+ * index that makes it unique, if any.
* non_unique_for_rels - list of Relid sets, each one being a set of
* other rels for which we have tried and failed to prove
* this one unique
@@ -812,6 +816,10 @@ typedef 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 uncomress it every time */
+ AttStatsSlot sslots[STATISTIC_NUM_SLOTS + 1];
} IndexOptInfo;
/*
@@ -1320,6 +1328,11 @@ typedef struct AppendPath
/* RT indexes of non-leaf tables in a partition tree */
List *partitioned_rels;
List *subpaths; /* list of component Paths */
+ 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 */
/* Index of first partial path in subpaths */
int first_partial_path;
diff --git a/src/include/optimizer/cost.h b/src/include/optimizer/cost.h
index 77ca7ff837..201ceefd5e 100644
--- a/src/include/optimizer/cost.h
+++ b/src/include/optimizer/cost.h
@@ -110,6 +110,8 @@ extern void cost_sort(Path *path, PlannerInfo *root,
List *pathkeys, Cost input_cost, double tuples, int width,
Cost comparison_cost, int sort_mem,
double limit_tuples);
+extern Cost cost_sort_estimate(PlannerInfo *root, List *pathkeys,
+ int nPresortedKeys, double tuples);
extern void cost_append(AppendPath *path);
extern void cost_merge_append(Path *path, PlannerInfo *root,
List *pathkeys, int n_streams,
@@ -166,6 +168,7 @@ extern void cost_gather(GatherPath *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 7c5ff22650..f8caa234bc 100644
--- a/src/include/optimizer/pathnode.h
+++ b/src/include/optimizer/pathnode.h
@@ -68,7 +68,8 @@ extern AppendPath *create_append_path(PlannerInfo *root, RelOptInfo *rel,
List *subpaths, List *partial_subpaths,
Relids required_outer,
int parallel_workers, bool parallel_aware,
- List *partitioned_rels, double rows);
+ List *partitioned_rels, double rows,
+ bool pull_tlist, List *pathkeys);
extern MergeAppendPath *create_merge_append_path(PlannerInfo *root,
RelOptInfo *rel,
List *subpaths,
@@ -271,6 +272,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 cafde307ad..a945828058 100644
--- a/src/include/optimizer/paths.h
+++ b/src/include/optimizer/paths.h
@@ -71,9 +71,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(IndexOptInfo *index,
int indexcol);
extern bool match_index_to_operand(Node *operand, int indexcol,
@@ -190,6 +204,14 @@ typedef enum
extern PathKeysComparison compare_pathkeys(List *keys1, List *keys2);
extern bool pathkeys_contained_in(List *keys1, List *keys2);
+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,
@@ -227,6 +249,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);
@@ -234,6 +257,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 a081ca689a..a81edf7575 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/plannodes.h"
#include "nodes/relation.h"
@@ -59,6 +60,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,
@@ -105,13 +109,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/storage/s_lock.h b/src/include/storage/s_lock.h
index f4cbac2ee8..90535ef5b7 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/utils/builtins.h b/src/include/utils/builtins.h
index 81616d4d6d..93a97ac9f3 100644
--- a/src/include/utils/builtins.h
+++ b/src/include/utils/builtins.h
@@ -18,7 +18,6 @@
#include "nodes/nodes.h"
#include "utils/fmgrprotos.h"
-
/* bool.c */
extern bool parse_bool(const char *value, bool *result);
extern bool parse_bool_with_len(const char *value, size_t len, bool *result);
diff --git a/src/include/utils/lsyscache.h b/src/include/utils/lsyscache.h
index 21ec4cbd35..0af223e9d8 100644
--- a/src/include/utils/lsyscache.h
+++ b/src/include/utils/lsyscache.h
@@ -55,6 +55,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() */
@@ -173,6 +175,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 95e44280c4..199f854442 100644
--- a/src/include/utils/selfuncs.h
+++ b/src/include/utils/selfuncs.h
@@ -76,6 +76,7 @@ typedef struct VariableStatData
int32 atttypmod; /* actual typmod (after stripping relabel) */
bool isunique; /* matches unique index or DISTINCT clause */
bool acl_ok; /* result of ACL check on table or column */
+ AttStatsSlot *sslots;
} VariableStatData;
#define ReleaseVariableStats(vardata) \
@@ -176,6 +177,10 @@ extern double histogram_selectivity(VariableStatData *vardata, FmgrInfo *opproc,
Datum constval, bool varonleft,
int min_hist_size, int n_skip,
int *hist_size);
+double prefix_record_histogram_selectivity(VariableStatData *vardata,
+ Datum constvalLeft, Datum constvalRight,
+ int record_cmp_prefix,
+ double ndistinct, int *n_bins);
extern Pattern_Prefix_Status pattern_fixed_prefix(Const *patt,
Pattern_Type ptype,
@@ -208,6 +213,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,
@@ -226,5 +234,19 @@ 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,
+ VariableStatData* vardata1,
+ VariableStatData* vardata2,
+ SpecialJoinInfo *sjinfo,
+ int record_cmp_prefix);
+extern Selectivity eqconst_selectivity(Oid operator, VariableStatData *vardata,
+ Datum constval, bool constisnull,
+ bool varonleft, bool negate,
+ int record_cmp_prefix);
+extern Selectivity ineq_histogram_selectivity(PlannerInfo *root,
+ VariableStatData *vardata,
+ FmgrInfo *opproc, bool isgt, bool iseq,
+ Datum constval, Oid consttype,
+ int record_cmp_prefix);
#endif /* SELFUNCS_H */
diff --git a/src/include/utils/timeout.h b/src/include/utils/timeout.h
index dcc7307c16..e19a3976e3 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/test/regress/expected/aggregates.out b/src/test/regress/expected/aggregates.out
index bacec2216b..57c627738c 100644
--- a/src/test/regress/expected/aggregates.out
+++ b/src/test/regress/expected/aggregates.out
@@ -906,7 +906,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
@@ -929,10 +930,8 @@ explain (costs off)
-> Index Only Scan using minmaxtest2i on minmaxtest2 minmaxtest2_1
Index Cond: (f1 IS NOT NULL)
-> Index Only Scan Backward using minmaxtest3i on minmaxtest3 minmaxtest3_1
- -> Sort
- Sort Key: ($0), ($1)
- -> Result
-(26 rows)
+ -> Result
+(25 rows)
select distinct min(f1), max(f1) from minmaxtest;
min | max
@@ -2138,6 +2137,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 156b77f319..3184608641 100644
--- a/src/test/regress/expected/create_index.out
+++ b/src/test/regress/expected/create_index.out
@@ -2962,18 +2962,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);
@@ -3115,6 +3109,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
@@ -3153,6 +3148,7 @@ explain (costs off)
Filter: (NOT b)
(4 rows)
+reset enable_seqscan;
--
-- Test for multilevel page deletion
--
diff --git a/src/test/regress/expected/create_table.out b/src/test/regress/expected/create_table.out
index 75ee55644b..7d978a78e7 100644
--- a/src/test/regress/expected/create_table.out
+++ b/src/test/regress/expected/create_table.out
@@ -222,6 +222,7 @@ CREATE TABLE tas_case (a text) WITH ("Oids" = true);
ERROR: unrecognized parameter "Oids"
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
----------------+---------+----------------
@@ -242,6 +243,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 c448d85dec..0fa5309d34 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,3 +403,35 @@ 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;
diff --git a/src/test/regress/expected/insert_conflict.out b/src/test/regress/expected/insert_conflict.out
index a28611745c..4430628194 100644
--- a/src/test/regress/expected/insert_conflict.out
+++ b/src/test/regress/expected/insert_conflict.out
@@ -891,6 +891,17 @@ create table parted_conflict_1 (drp text, c int, a int, b text);
alter table parted_conflict_1 drop column drp;
create unique index on parted_conflict (a, b);
alter table parted_conflict attach partition parted_conflict_1 for values from (0) to (1000);
+-- test that index types were created after ALTER TABLE
+select p.reltype > 0
+from pg_index i
+join pg_inherits inh on inh.inhparent = i.indexrelid
+join pg_class p on p.oid = inh.inhrelid
+where i.indrelid = 'parted_conflict'::regclass::oid;
+ ?column?
+----------
+ t
+(1 row)
+
truncate parted_conflict;
insert into parted_conflict values (50, 'cincuenta', 1);
insert into parted_conflict values (50, 'cincuenta', 2)
diff --git a/src/test/regress/expected/join.out b/src/test/regress/expected/join.out
index c50c3826fc..615754ca92 100644
--- a/src/test/regress/expected/join.out
+++ b/src/test/regress/expected/join.out
@@ -1928,8 +1928,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;
@@ -1963,8 +1963,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
@@ -4112,15 +4112,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,
@@ -4259,18 +4260,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
@@ -4499,6 +4502,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 p
+ 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 t2
+ Filter: ((a IS NOT NULL) AND (b IS NOT NULL) AND (a 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
--
@@ -4907,15 +5130,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)
@@ -4924,15 +5147,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)
@@ -4959,24 +5182,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)
@@ -5306,15 +5529,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
@@ -5858,44 +6081,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;
@@ -5975,6 +6193,8 @@ left join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
Output: j2.id1, j2.id2
(8 rows)
+-- !!!FIXME this test doesn't break if I set skip_mark_restore to true.
+-- Also should avoid unique self join removal by using two different relations.
-- validate logic in merge joins which skips mark and restore.
-- it should only do this if all quals which were used to detect the unique
-- are present as join quals, and not plain quals.
@@ -5986,14 +6206,11 @@ create index j1_id1_idx on j1 (id1) where id1 % 1000 = 1;
explain (costs off) select * from j1 j1
inner join j1 j2 on j1.id1 = j2.id1 and j1.id2 = j2.id2
where j1.id1 % 1000 = 1 and j2.id1 % 1000 = 1;
- QUERY PLAN
---------------------------------------------
- Merge Join
- Merge Cond: (j1.id1 = j2.id1)
- Join Filter: (j1.id2 = j2.id2)
- -> Index Scan using j1_id1_idx on j1
- -> Index Scan using j1_id1_idx on j1 j2
-(5 rows)
+ QUERY PLAN
+----------------------------------------------------------------------------
+ Seq Scan on j1 j2
+ Filter: ((id1 IS NOT NULL) AND (id2 IS NOT NULL) AND ((id1 % 1000) = 1))
+(2 rows)
select * from j1 j1
inner join j1 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 e9c44d3546..4c1f56995b 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
@@ -2597,3 +2598,4 @@ select ts_headline('[]'::json, tsquery('aaa & bbb'));
[]
(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 d8d34f4ff6..afcb704aca 100644
--- a/src/test/regress/expected/json_encoding_1.out
+++ b/src/test/regress/expected/json_encoding_1.out
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
-- encoding-sensitive tests for json and jsonb
-- first json
-- basic unicode input
@@ -245,3 +246,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/jsonb.out b/src/test/regress/expected/jsonb.out
index e9f22a4279..5de21f89c0 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
@@ -4532,3 +4533,4 @@ select '12345.0000000000000000000000000000000000000000000005'::jsonb::int8;
12345
(1 row)
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/numeric.out b/src/test/regress/expected/numeric.out
index ced7980709..330bdededb 100644
--- a/src/test/regress/expected/numeric.out
+++ b/src/test/regress/expected/numeric.out
@@ -1267,6 +1267,7 @@ SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999');
(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
------------+---------
@@ -1327,6 +1328,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 e1549cbb5c..2a7aa1cfb5 100644
--- a/src/test/regress/expected/partition_aggregate.out
+++ b/src/test/regress/expected/partition_aggregate.out
@@ -724,10 +724,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_p1.x, pagg_tab2_p2.y
- -> HashAggregate
- Group Key: pagg_tab1_p1.x, pagg_tab2_p2.y
+ GroupAggregate
+ Group Key: pagg_tab1_p1.x, pagg_tab2_p2.y
+ -> Sort
+ Sort Key: pagg_tab1_p1.x, pagg_tab2_p2.y
-> Hash Left Join
Hash Cond: (pagg_tab1_p1.x = pagg_tab2_p2.y)
Filter: ((pagg_tab1_p1.x > 5) OR (pagg_tab2_p2.y < 20))
@@ -943,36 +943,34 @@ SET max_parallel_workers_per_gather TO 2;
-- is not partial agg safe.
EXPLAIN (COSTS OFF)
SELECT a, sum(b), array_agg(distinct c), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3;
- QUERY PLAN
---------------------------------------------------------------------------------------------------------
+ QUERY PLAN
+-----------------------------------------------------------------------------------------------
Sort
- Sort Key: pagg_tab_ml_p2_s1.a, (sum(pagg_tab_ml_p2_s1.b)), (array_agg(DISTINCT pagg_tab_ml_p2_s1.c))
- -> Gather
- Workers Planned: 2
- -> Parallel Append
- -> GroupAggregate
- Group Key: pagg_tab_ml_p2_s1.a
- Filter: (avg(pagg_tab_ml_p2_s1.b) < '3'::numeric)
- -> Sort
- Sort Key: pagg_tab_ml_p2_s1.a
- -> Append
- -> Seq Scan on pagg_tab_ml_p2_s1
- -> Seq Scan on pagg_tab_ml_p2_s2
- -> GroupAggregate
- Group Key: pagg_tab_ml_p3_s1.a
- Filter: (avg(pagg_tab_ml_p3_s1.b) < '3'::numeric)
- -> Sort
- Sort Key: pagg_tab_ml_p3_s1.a
- -> Append
- -> Seq Scan on pagg_tab_ml_p3_s1
- -> Seq Scan on pagg_tab_ml_p3_s2
- -> GroupAggregate
- Group Key: pagg_tab_ml_p1.a
- Filter: (avg(pagg_tab_ml_p1.b) < '3'::numeric)
- -> Sort
- Sort Key: pagg_tab_ml_p1.a
- -> Seq Scan on pagg_tab_ml_p1
-(27 rows)
+ Sort Key: pagg_tab_ml_p1.a, (sum(pagg_tab_ml_p1.b)), (array_agg(DISTINCT pagg_tab_ml_p1.c))
+ -> Append
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_p1.a
+ Filter: (avg(pagg_tab_ml_p1.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_p1.a
+ -> Seq Scan on pagg_tab_ml_p1
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_p2_s1.a
+ Filter: (avg(pagg_tab_ml_p2_s1.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_p2_s1.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p2_s1
+ -> Seq Scan on pagg_tab_ml_p2_s2
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_p3_s1.a
+ Filter: (avg(pagg_tab_ml_p3_s1.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_p3_s1.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p3_s1
+ -> Seq Scan on pagg_tab_ml_p3_s2
+(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
@@ -991,34 +989,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_p2_s1.a
- Filter: (avg(pagg_tab_ml_p2_s1.b) < '3'::numeric)
- -> Sort
- Sort Key: pagg_tab_ml_p2_s1.a
- -> Append
- -> Seq Scan on pagg_tab_ml_p2_s1
- -> Seq Scan on pagg_tab_ml_p2_s2
- -> GroupAggregate
- Group Key: pagg_tab_ml_p3_s1.a
- Filter: (avg(pagg_tab_ml_p3_s1.b) < '3'::numeric)
- -> Sort
- Sort Key: pagg_tab_ml_p3_s1.a
- -> Append
- -> Seq Scan on pagg_tab_ml_p3_s1
- -> Seq Scan on pagg_tab_ml_p3_s2
- -> GroupAggregate
- Group Key: pagg_tab_ml_p1.a
- Filter: (avg(pagg_tab_ml_p1.b) < '3'::numeric)
- -> Sort
- Sort Key: pagg_tab_ml_p1.a
- -> Seq Scan on pagg_tab_ml_p1
-(25 rows)
+ QUERY PLAN
+-----------------------------------------------------------
+ Append
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_p1.a
+ Filter: (avg(pagg_tab_ml_p1.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_p1.a
+ -> Seq Scan on pagg_tab_ml_p1
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_p2_s1.a
+ Filter: (avg(pagg_tab_ml_p2_s1.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_p2_s1.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p2_s1
+ -> Seq Scan on pagg_tab_ml_p2_s2
+ -> GroupAggregate
+ Group Key: pagg_tab_ml_p3_s1.a
+ Filter: (avg(pagg_tab_ml_p3_s1.b) < '3'::numeric)
+ -> Sort
+ Sort Key: pagg_tab_ml_p3_s1.a
+ -> Append
+ -> Seq Scan on pagg_tab_ml_p3_s1
+ -> Seq Scan on pagg_tab_ml_p3_s2
+(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
@@ -1184,20 +1180,18 @@ SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER B
-> Partial HashAggregate
Group Key: pagg_tab_ml_p1.a
-> Parallel Seq Scan on pagg_tab_ml_p1
- -> Finalize GroupAggregate
+ -> Finalize HashAggregate
Group Key: pagg_tab_ml_p2_s1.a
Filter: (avg(pagg_tab_ml_p2_s1.b) < '3'::numeric)
- -> Gather Merge
+ -> Gather
Workers Planned: 2
- -> Sort
- Sort Key: pagg_tab_ml_p2_s1.a
- -> Parallel Append
- -> Partial HashAggregate
- Group Key: pagg_tab_ml_p2_s1.a
- -> Parallel Seq Scan on pagg_tab_ml_p2_s1
- -> Partial HashAggregate
- Group Key: pagg_tab_ml_p2_s2.a
- -> Parallel Seq Scan on pagg_tab_ml_p2_s2
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_p2_s1.a
+ -> Parallel Seq Scan on pagg_tab_ml_p2_s1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_ml_p2_s2.a
+ -> Parallel Seq Scan on pagg_tab_ml_p2_s2
-> Finalize GroupAggregate
Group Key: pagg_tab_ml_p3_s1.a
Filter: (avg(pagg_tab_ml_p3_s1.b) < '3'::numeric)
@@ -1212,7 +1206,7 @@ SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER B
-> Partial HashAggregate
Group Key: pagg_tab_ml_p3_s2.a
-> Parallel Seq Scan on pagg_tab_ml_p3_s2
-(41 rows)
+(39 rows)
SELECT a, sum(b), count(*) FROM pagg_tab_ml GROUP BY a HAVING avg(b) < 3 ORDER BY 1, 2, 3;
a | sum | count
@@ -1378,24 +1372,22 @@ SELECT y, sum(x), avg(x), count(*) FROM pagg_tab_para GROUP BY y HAVING avg(x) <
--------------------------------------------------------------------------------------
Sort
Sort Key: pagg_tab_para_p1.y, (sum(pagg_tab_para_p1.x)), (avg(pagg_tab_para_p1.x))
- -> Finalize GroupAggregate
+ -> Finalize HashAggregate
Group Key: pagg_tab_para_p1.y
Filter: (avg(pagg_tab_para_p1.x) < '12'::numeric)
- -> Gather Merge
+ -> Gather
Workers Planned: 2
- -> Sort
- Sort Key: pagg_tab_para_p1.y
- -> Parallel Append
- -> Partial HashAggregate
- Group Key: pagg_tab_para_p1.y
- -> Parallel Seq Scan on pagg_tab_para_p1
- -> Partial HashAggregate
- Group Key: pagg_tab_para_p2.y
- -> Parallel Seq Scan on pagg_tab_para_p2
- -> Partial HashAggregate
- Group Key: pagg_tab_para_p3.y
- -> Parallel Seq Scan on pagg_tab_para_p3
-(19 rows)
+ -> Parallel Append
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_p1.y
+ -> Parallel Seq Scan on pagg_tab_para_p1
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_p2.y
+ -> Parallel Seq Scan on pagg_tab_para_p2
+ -> Partial HashAggregate
+ Group Key: pagg_tab_para_p3.y
+ -> Parallel Seq Scan on pagg_tab_para_p3
+(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 767d666a80..21df9a316d 100644
--- a/src/test/regress/expected/partition_join.out
+++ b/src/test/regress/expected/partition_join.out
@@ -411,34 +411,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
- -> Seq Scan on prt1_p2 t1_1
- -> Seq Scan on prt1_p3 t1_2
+ -> Hash Join
+ Hash Cond: (t2.a = t3.b)
+ -> Seq Scan on prt1_p1 t2
+ -> Hash
+ -> Seq Scan on prt2_p1 t3
+ -> Hash Join
+ Hash Cond: (t2_1.a = t3_1.b)
+ -> Seq Scan on prt1_p2 t2_1
+ -> Hash
+ -> Seq Scan on prt2_p2 t3_1
+ -> Hash Join
+ Hash Cond: (t2_2.a = t3_2.b)
+ -> Seq Scan on prt1_p3 t2_2
+ -> Hash
+ -> Seq Scan on prt2_p3 t3_2
-> Hash
-> Append
- -> Hash Join
- Hash Cond: (t2.a = t3.b)
- -> Seq Scan on prt1_p1 t2
- -> Hash
- -> Seq Scan on prt2_p1 t3
- -> Hash Join
- Hash Cond: (t2_1.a = t3_1.b)
- -> Seq Scan on prt1_p2 t2_1
- -> Hash
- -> Seq Scan on prt2_p2 t3_1
- -> Hash Join
- Hash Cond: (t2_2.a = t3_2.b)
- -> Seq Scan on prt1_p3 t2_2
- -> Hash
- -> Seq Scan on prt2_p3 t3_2
+ -> Seq Scan on prt1_p1 t1
+ -> Seq Scan on prt1_p2 t1_1
+ -> Seq Scan on prt1_p3 t1_2
(26 rows)
SELECT t1.a, ss.t2a, ss.t2c FROM prt1 t1 LEFT JOIN LATERAL
@@ -910,60 +910,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.a = t2.b)
- -> Sort
- Sort Key: t1.a
- -> Merge Left Join
- Merge Cond: ((((t3.a + t3.b) / 2)) = t1.a)
- -> Sort
- Sort Key: (((t3.a + t3.b) / 2))
- -> Seq Scan on prt1_e_p1 t3
- Filter: (c = 0)
- -> Sort
- Sort Key: t1.a
- -> Seq Scan on prt1_p1 t1
- -> Sort
- Sort Key: t2.b
- -> Seq Scan on prt2_p1 t2
- -> 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_p2 t3_1
- Filter: (c = 0)
- -> Sort
- Sort Key: t1_1.a
- -> Seq Scan on prt1_p2 t1_1
+ -> Merge Right Join
+ Merge Cond: (t1.a = (((t3.a + t3.b) / 2)))
+ -> Merge Left Join
+ Merge Cond: (t1.a = t2.b)
+ -> Sort
+ Sort Key: t1.a
+ -> Seq Scan on prt1_p1 t1
+ -> Sort
+ Sort Key: t2.b
+ -> Seq Scan on prt2_p1 t2
-> Sort
- Sort Key: t2_1.b
- -> Seq Scan on prt2_p2 t2_1
- -> Merge Left Join
- Merge Cond: (t1_2.a = t2_2.b)
+ Sort Key: (((t3.a + t3.b) / 2))
+ -> Seq Scan on prt1_e_p1 t3
+ Filter: (c = 0)
+ -> 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_p2 t1_1
+ -> Sort
+ Sort Key: t2_1.b
+ -> Seq Scan on prt2_p2 t2_1
-> 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_p3 t3_2
- Filter: (c = 0)
- -> Sort
- Sort Key: t1_2.a
- -> Seq Scan on prt1_p3 t1_2
+ Sort Key: (((t3_1.a + t3_1.b) / 2))
+ -> Seq Scan on prt1_e_p2 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_p3 t1_2
+ -> Sort
+ Sort Key: t2_2.b
+ -> Seq Scan on prt2_p3 t2_2
-> Sort
- Sort Key: t2_2.b
- -> Seq Scan on prt2_p3 t2_2
-(51 rows)
+ Sort Key: (((t3_2.a + t3_2.b) / 2))
+ -> Seq Scan on prt1_e_p3 t3_2
+ 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
@@ -1154,7 +1148,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
@@ -1298,7 +1292,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
@@ -1757,29 +1751,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
- -> Seq Scan on prt4_n_p2 t2_1
- -> Seq Scan on prt4_n_p3 t2_2
+ -> Seq Scan on prt1_p1 t1
+ -> Seq Scan on prt1_p2 t1_1
+ -> Seq Scan on prt1_p3 t1_2
-> Hash
- -> Append
- -> Hash Join
- Hash Cond: (t1.a = t3.b)
- -> Seq Scan on prt1_p1 t1
- -> Hash
+ -> Hash Join
+ Hash Cond: (t2.a = t3.b)
+ -> Append
+ -> Seq Scan on prt4_n_p1 t2
+ -> Seq Scan on prt4_n_p2 t2_1
+ -> Seq Scan on prt4_n_p3 t2_2
+ -> Hash
+ -> Append
-> Seq Scan on prt2_p1 t3
- -> Hash Join
- Hash Cond: (t1_1.a = t3_1.b)
- -> Seq Scan on prt1_p2 t1_1
- -> Hash
-> Seq Scan on prt2_p2 t3_1
- -> Hash Join
- Hash Cond: (t1_2.a = t3_2.b)
- -> Seq Scan on prt1_p3 t1_2
- -> Hash
-> Seq Scan on prt2_p3 t3_2
-(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 28ce692eea..00e94efd26 100644
--- a/src/test/regress/expected/partition_prune.out
+++ b/src/test/regress/expected/partition_prune.out
@@ -2605,9 +2605,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_a1_b3_1 (actual rows=0 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (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 (actual rows=0 loops=1)
@@ -2646,9 +2646,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_a1_b3_1 (actual rows=0 loops=1)
+ -> Bitmap Heap Scan on ab_a1_b3 ab_a1_b3_1 (never executed)
Recheck Cond: (a = 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 (actual rows=0 loops=1)
diff --git a/src/test/regress/expected/password.out b/src/test/regress/expected/password.out
index f1ae34f15f..103d4319eb 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 b687fbfddc..b970e34a80 100644
--- a/src/test/regress/expected/plpgsql.out
+++ b/src/test/regress/expected/plpgsql.out
@@ -4429,6 +4429,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
@@ -4501,6 +4502,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 9c389c8f04..e1d5bb4ead 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
@@ -3247,3 +3248,4 @@ last error message: division by zero
\echo 'last error code:' :LAST_ERROR_SQLSTATE
last error code: 22012
\unset FETCH_COUNT
+reset standard_conforming_strings;
diff --git a/src/test/regress/expected/rangetypes.out b/src/test/regress/expected/rangetypes.out
index 348235a15e..888e2059b5 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 53fda82f77..2c3aa3668b 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 7361eec6da..ca6ea833de 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 cb30bd787f..c6a18a76db 100644
--- a/src/test/regress/expected/stats_ext.out
+++ b/src/test/regress/expected/stats_ext.out
@@ -394,9 +394,9 @@ EXPLAIN (COSTS off)
QUERY PLAN
-----------------------------------
GroupAggregate
- Group Key: a, b
+ Group Key: b, a
-> Sort
- Sort Key: a, b
+ Sort Key: b, a
-> Seq Scan on ndistinct
(5 rows)
@@ -405,9 +405,9 @@ EXPLAIN (COSTS off)
QUERY PLAN
-----------------------------------
GroupAggregate
- Group Key: a, b, c
+ Group Key: b, a, c
-> Sort
- Sort Key: a, b, c
+ Sort Key: b, a, c
-> Seq Scan on ndistinct
(5 rows)
@@ -416,9 +416,9 @@ EXPLAIN (COSTS off)
QUERY PLAN
-----------------------------------
GroupAggregate
- Group Key: a, b, c, d
+ Group Key: d, a, b, c
-> Sort
- Sort Key: a, b, c, d
+ Sort Key: d, a, b, c
-> Seq Scan on ndistinct
(5 rows)
@@ -507,6 +507,14 @@ CREATE TABLE functional_dependencies (
SET random_page_cost = 1.2;
CREATE INDEX fdeps_ab_idx ON functional_dependencies (a, b);
CREATE INDEX fdeps_abc_idx ON functional_dependencies (a, b, c);
+-- test that index types were created
+SELECT reltype > 0 FROM pg_class where relname in ('fdeps_ab_idx', 'fdeps_abc_idx');
+ ?column?
+----------
+ t
+ t
+(2 rows)
+
-- random data (no functional dependencies)
INSERT INTO functional_dependencies (a, b, c, filler1)
SELECT mod(i, 23), mod(i, 29), mod(i, 31), i FROM generate_series(1,5000) s(i);
@@ -598,6 +606,14 @@ EXPLAIN (COSTS OFF)
-- check change of column type doesn't break it
ALTER TABLE functional_dependencies ALTER COLUMN c TYPE numeric;
+-- test that index types were created after ALTER TABLE
+SELECT reltype > 0 FROM pg_class where relname in ('fdeps_ab_idx', 'fdeps_abc_idx');
+ ?column?
+----------
+ t
+ t
+(2 rows)
+
EXPLAIN (COSTS OFF)
SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
QUERY PLAN
diff --git a/src/test/regress/expected/strings.out b/src/test/regress/expected/strings.out
index 6fbaf152cd..479b14df96 100644
--- a/src/test/regress/expected/strings.out
+++ b/src/test/regress/expected/strings.out
@@ -1527,6 +1527,7 @@ SELECT sha512('The quick brown fox jumps over the lazy dog.');
--
-- encode/decode
--
+set standard_conforming_strings=on;
SELECT encode('\x1234567890abcdef00', 'hex');
encode
--------------------
@@ -1600,6 +1601,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 28950eeafe..e5910034eb 100644
--- a/src/test/regress/expected/subselect.out
+++ b/src/test/regress/expected/subselect.out
@@ -1407,6 +1407,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;
@@ -1442,6 +1443,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 bbee01047d..8fe9e26ddd 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
@@ -2348,3 +2349,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/union.out b/src/test/regress/expected/union.out
index cd3cb3775f..bdeb946152 100644
--- a/src/test/regress/expected/union.out
+++ b/src/test/regress/expected/union.out
@@ -924,24 +924,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
@@ -960,24 +958,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 109fa0660c..fba9ed4b58 100644
--- a/src/test/regress/expected/updatable_views.out
+++ b/src/test/regress/expected/updatable_views.out
@@ -2177,16 +2177,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 b918ad0f98..daf579a488 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
@@ -1396,3 +1397,4 @@ SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c
14
(4 rows)
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/aggregates.sql b/src/test/regress/sql/aggregates.sql
index 4b35839729..16625077fc 100644
--- a/src/test/regress/sql/aggregates.sql
+++ b/src/test/regress/sql/aggregates.sql
@@ -941,6 +941,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 c11a34ff03..6f47429d94 100644
--- a/src/test/regress/sql/create_index.sql
+++ b/src/test/regress/sql/create_index.sql
@@ -1104,6 +1104,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)
@@ -1112,6 +1113,7 @@ explain (costs off)
select * from boolindex where b = true order by i desc limit 10;
explain (costs off)
select * from boolindex where not b order by i limit 10;
+reset enable_seqscan;
--
-- Test for multilevel page deletion
diff --git a/src/test/regress/sql/create_table.sql b/src/test/regress/sql/create_table.sql
index dc5b729ad8..63d58c07b2 100644
--- a/src/test/regress/sql/create_table.sql
+++ b/src/test/regress/sql/create_table.sql
@@ -259,10 +259,12 @@ CREATE TABLE tas_case (a text) WITH ("Oids" = true);
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 85aa65de39..13fbf5fb45 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;
@@ -262,3 +266,19 @@ explain (costs off)
-- this could be converted, but isn't at present
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;
diff --git a/src/test/regress/sql/insert_conflict.sql b/src/test/regress/sql/insert_conflict.sql
index c68013e179..3a5fa16e7a 100644
--- a/src/test/regress/sql/insert_conflict.sql
+++ b/src/test/regress/sql/insert_conflict.sql
@@ -566,6 +566,14 @@ create table parted_conflict_1 (drp text, c int, a int, b text);
alter table parted_conflict_1 drop column drp;
create unique index on parted_conflict (a, b);
alter table parted_conflict attach partition parted_conflict_1 for values from (0) to (1000);
+
+-- test that index types were created after ALTER TABLE
+select p.reltype > 0
+from pg_index i
+join pg_inherits inh on inh.inhparent = i.indexrelid
+join pg_class p on p.oid = inh.inhrelid
+where i.indrelid = 'parted_conflict'::regclass::oid;
+
truncate parted_conflict;
insert into parted_conflict values (50, 'cincuenta', 1);
insert into parted_conflict values (50, 'cincuenta', 2)
diff --git a/src/test/regress/sql/join.sql b/src/test/regress/sql/join.sql
index 737b85925c..31dbe44d46 100644
--- a/src/test/regress/sql/join.sql
+++ b/src/test/regress/sql/join.sql
@@ -1560,6 +1560,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
--
@@ -2012,6 +2110,9 @@ explain (verbose, costs off)
select * from j1
left join j2 on j1.id1 = j2.id1 where j1.id2 = 1;
+-- !!!FIXME this test doesn't break if I set skip_mark_restore to true.
+-- Also should avoid unique self join removal by using two different relations.
+
-- validate logic in merge joins which skips mark and restore.
-- it should only do this if all quals which were used to detect the unique
-- are present as join quals, and not plain quals.
diff --git a/src/test/regress/sql/json.sql b/src/test/regress/sql/json.sql
index 44fd7e4066..7aecd522d2 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
@@ -829,3 +831,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 87a2d564ff..7fd16a5912 100644
--- a/src/test/regress/sql/json_encoding.sql
+++ b/src/test/regress/sql/json_encoding.sql
@@ -1,3 +1,4 @@
+set standard_conforming_strings=on;
-- encoding-sensitive tests for json and jsonb
@@ -65,3 +66,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 06c2916893..b14f8733c2 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
@@ -1182,3 +1184,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/numeric.sql b/src/test/regress/sql/numeric.sql
index e5ff2b5902..528860c16f 100644
--- a/src/test/regress/sql/numeric.sql
+++ b/src/test/regress/sql/numeric.sql
@@ -801,6 +801,7 @@ SELECT '' AS to_char_25, to_char('100'::numeric, 'FM999.');
SELECT '' AS to_char_26, to_char('100'::numeric, 'FM999');
-- 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');
@@ -811,6 +812,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 fad88ba689..5081463226 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 e71d072aa9..a7f84f8de8 100644
--- a/src/test/regress/sql/plpgsql.sql
+++ b/src/test/regress/sql/plpgsql.sql
@@ -3632,6 +3632,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 $$
@@ -3673,6 +3674,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 7279b98470..43834c01c5 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
@@ -691,3 +693,5 @@ select 1/(15-unique2) from tenk1 order by unique2 limit 19;
\echo 'last error code:' :LAST_ERROR_SQLSTATE
\unset FETCH_COUNT
+
+reset standard_conforming_strings;
diff --git a/src/test/regress/sql/rangetypes.sql b/src/test/regress/sql/rangetypes.sql
index 85eaa9b34c..b7a921dc14 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 0b040abba9..ca0d6bcd62 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 605eb4a175..dd1ac61112 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 e3c8cbca5b..bc96d2b2a0 100644
--- a/src/test/regress/sql/stats_ext.sql
+++ b/src/test/regress/sql/stats_ext.sql
@@ -297,6 +297,9 @@ SET random_page_cost = 1.2;
CREATE INDEX fdeps_ab_idx ON functional_dependencies (a, b);
CREATE INDEX fdeps_abc_idx ON functional_dependencies (a, b, c);
+-- test that index types were created
+SELECT reltype > 0 FROM pg_class where relname in ('fdeps_ab_idx', 'fdeps_abc_idx');
+
-- random data (no functional dependencies)
INSERT INTO functional_dependencies (a, b, c, filler1)
SELECT mod(i, 23), mod(i, 29), mod(i, 31), i FROM generate_series(1,5000) s(i);
@@ -349,6 +352,9 @@ EXPLAIN (COSTS OFF)
-- check change of column type doesn't break it
ALTER TABLE functional_dependencies ALTER COLUMN c TYPE numeric;
+-- test that index types were created after ALTER TABLE
+SELECT reltype > 0 FROM pg_class where relname in ('fdeps_ab_idx', 'fdeps_abc_idx');
+
EXPLAIN (COSTS OFF)
SELECT * FROM functional_dependencies WHERE a = 1 AND b = '1' AND c = 1;
diff --git a/src/test/regress/sql/strings.sql b/src/test/regress/sql/strings.sql
index 8eb6bf30af..9bb278c69e 100644
--- a/src/test/regress/sql/strings.sql
+++ b/src/test/regress/sql/strings.sql
@@ -534,6 +534,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');
@@ -553,6 +554,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 88d54ed382..fd8c8a0fcb 100644
--- a/src/test/regress/sql/subselect.sql
+++ b/src/test/regress/sql/subselect.sql
@@ -745,6 +745,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;
@@ -766,6 +767,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 677fb1efaa..1cc12d9d5e 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
@@ -683,3 +685,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/xml.sql b/src/test/regress/sql/xml.sql
index 2ad15c6a92..090cc33557 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
@@ -609,3 +611,5 @@ INSERT INTO xmltest2 VALUES('<d><r><dc>2</dc></r></d>', 'D');
SELECT xmltable.* FROM xmltest2, LATERAL xmltable('/d/r' PASSING x COLUMNS a int PATH '' || lower(_path) || 'c');
SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH '.');
SELECT xmltable.* FROM xmltest2, LATERAL xmltable(('/d/r/' || lower(_path) || 'c') PASSING x COLUMNS a int PATH 'x' DEFAULT ascii(_path) - 54);
+
+reset standard_conforming_strings;
diff --git a/src/tools/msvc/Mkvcbuild.pm b/src/tools/msvc/Mkvcbuild.pm
index b52baa9098..9fcfebafee 100644
--- a/src/tools/msvc/Mkvcbuild.pm
+++ b/src/tools/msvc/Mkvcbuild.pm
@@ -35,8 +35,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' ],
@@ -65,11 +65,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'],
@@ -462,6 +465,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')
{
@@ -1017,6 +1021,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;